From ff283ff912fab0f7a6abfd0f98256e112e8dba88 Mon Sep 17 00:00:00 2001 From: spycrab Date: Tue, 19 Sep 2017 14:14:45 +0200 Subject: [PATCH 1/6] Qt/Settings: Add debug mode flag --- Source/Core/DolphinQt2/Main.cpp | 1 + Source/Core/DolphinQt2/Settings.cpp | 14 ++++++++++++++ Source/Core/DolphinQt2/Settings.h | 7 ++++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Source/Core/DolphinQt2/Main.cpp b/Source/Core/DolphinQt2/Main.cpp index 8b29f6590c..b1616a30ab 100644 --- a/Source/Core/DolphinQt2/Main.cpp +++ b/Source/Core/DolphinQt2/Main.cpp @@ -73,6 +73,7 @@ int main(int argc, char* argv[]) UICommon::CreateDirectories(); UICommon::Init(); Resources::Init(); + Settings::Instance().SetDebugModeEnabled(options.is_set("debugger")); // Hook up alerts from core RegisterMsgAlertHandler(QtMsgAlertHandler); diff --git a/Source/Core/DolphinQt2/Settings.cpp b/Source/Core/DolphinQt2/Settings.cpp index 53178baa16..30dbde00d8 100644 --- a/Source/Core/DolphinQt2/Settings.cpp +++ b/Source/Core/DolphinQt2/Settings.cpp @@ -196,3 +196,17 @@ void Settings::SetCheatsEnabled(bool enabled) emit EnableCheatsChanged(enabled); } } + +void Settings::SetDebugModeEnabled(bool enabled) +{ + if (IsDebugModeEnabled() != enabled) + { + SConfig::GetInstance().bEnableDebugging = enabled; + emit DebugModeToggled(enabled); + } +} + +bool Settings::IsDebugModeEnabled() const +{ + return SConfig::GetInstance().bEnableDebugging; +} diff --git a/Source/Core/DolphinQt2/Settings.h b/Source/Core/DolphinQt2/Settings.h index 4177fe6b77..4ab31ad2cc 100644 --- a/Source/Core/DolphinQt2/Settings.h +++ b/Source/Core/DolphinQt2/Settings.h @@ -78,9 +78,12 @@ public: bool GetCheatsEnabled() const; void SetCheatsEnabled(bool enabled); + // Debug + void SetDebugModeEnabled(bool enabled); + bool IsDebugModeEnabled() const; + // Other GameListModel* GetGameListModel() const; - signals: void ConfigChanged(); void EmulationStateChanged(Core::State new_state); @@ -93,8 +96,10 @@ signals: void LogVisibilityChanged(bool visible); void LogConfigVisibilityChanged(bool visible); void EnableCheatsChanged(bool enabled); + void DebugModeToggled(bool enabled); private: + bool m_registers_visible = false; std::unique_ptr m_client; std::unique_ptr m_server; Settings(); From 2a19ccf806e79f5d712c2b141a6353bc87be8c38 Mon Sep 17 00:00:00 2001 From: spycrab Date: Mon, 25 Sep 2017 10:21:04 +0200 Subject: [PATCH 2/6] Qt/InterfaceSettings: Add "Show Debugging UI" option --- Source/Core/DolphinQt2/Settings/InterfacePane.cpp | 6 ++++++ Source/Core/DolphinQt2/Settings/InterfacePane.h | 1 + 2 files changed, 7 insertions(+) diff --git a/Source/Core/DolphinQt2/Settings/InterfacePane.cpp b/Source/Core/DolphinQt2/Settings/InterfacePane.cpp index a377050871..a48c8c94e0 100644 --- a/Source/Core/DolphinQt2/Settings/InterfacePane.cpp +++ b/Source/Core/DolphinQt2/Settings/InterfacePane.cpp @@ -123,10 +123,13 @@ void InterfacePane::CreateUI() m_checkbox_top_window = new QCheckBox(tr("Keep Window on Top")); m_checkbox_render_to_window = new QCheckBox(tr("Render to Main Window")); m_checkbox_use_builtin_title_database = new QCheckBox(tr("Use Built-In Database of Game Names")); + m_checkbox_show_debugging_ui = new QCheckBox(tr("Show Debugging UI")); + groupbox_layout->addWidget(m_checkbox_auto_window); groupbox_layout->addWidget(m_checkbox_top_window); groupbox_layout->addWidget(m_checkbox_render_to_window); groupbox_layout->addWidget(m_checkbox_use_builtin_title_database); + groupbox_layout->addWidget(m_checkbox_show_debugging_ui); } void InterfacePane::CreateInGame() @@ -158,6 +161,7 @@ void InterfacePane::ConnectLayout() connect(m_checkbox_render_to_window, &QCheckBox::clicked, this, &InterfacePane::OnSaveConfig); connect(m_checkbox_use_builtin_title_database, &QCheckBox::clicked, this, &InterfacePane::OnSaveConfig); + connect(m_checkbox_show_debugging_ui, &QCheckBox::clicked, this, &InterfacePane::OnSaveConfig); connect(m_combobox_theme, static_cast(&QComboBox::activated), &Settings::Instance(), &Settings::SetThemeName); connect(m_combobox_language, static_cast(&QComboBox::activated), this, @@ -177,6 +181,7 @@ void InterfacePane::LoadConfig() m_checkbox_top_window->setChecked(startup_params.bKeepWindowOnTop); m_checkbox_render_to_window->setChecked(startup_params.bRenderToMain); m_checkbox_use_builtin_title_database->setChecked(startup_params.m_use_builtin_title_database); + m_checkbox_show_debugging_ui->setChecked(Settings::Instance().IsDebugModeEnabled()); m_combobox_language->setCurrentIndex(m_combobox_language->findData( QString::fromStdString(SConfig::GetInstance().m_InterfaceLanguage))); m_combobox_theme->setCurrentIndex( @@ -198,6 +203,7 @@ void InterfacePane::OnSaveConfig() settings.bKeepWindowOnTop = m_checkbox_top_window->isChecked(); settings.bRenderToMain = m_checkbox_render_to_window->isChecked(); settings.m_use_builtin_title_database = m_checkbox_use_builtin_title_database->isChecked(); + Settings::Instance().SetDebugModeEnabled(m_checkbox_show_debugging_ui->isChecked()); // In Game Options settings.bConfirmStop = m_checkbox_confirm_on_stop->isChecked(); diff --git a/Source/Core/DolphinQt2/Settings/InterfacePane.h b/Source/Core/DolphinQt2/Settings/InterfacePane.h index 85301a42b3..46abc99761 100644 --- a/Source/Core/DolphinQt2/Settings/InterfacePane.h +++ b/Source/Core/DolphinQt2/Settings/InterfacePane.h @@ -32,6 +32,7 @@ private: QCheckBox* m_checkbox_top_window; QCheckBox* m_checkbox_render_to_window; QCheckBox* m_checkbox_use_builtin_title_database; + QCheckBox* m_checkbox_show_debugging_ui; QCheckBox* m_checkbox_confirm_on_stop; QCheckBox* m_checkbox_use_panic_handlers; From ec37ce093f5349a785d056958a187f5391322b4c Mon Sep 17 00:00:00 2001 From: spycrab Date: Wed, 13 Sep 2017 19:33:45 +0200 Subject: [PATCH 3/6] Qt/Debugger: Implement "Registers" window --- Source/Core/DolphinQt2/CMakeLists.txt | 2 + .../DolphinQt2/Debugger/RegisterColumn.cpp | 123 +++++++ .../Core/DolphinQt2/Debugger/RegisterColumn.h | 68 ++++ .../DolphinQt2/Debugger/RegisterWidget.cpp | 337 ++++++++++++++++++ .../Core/DolphinQt2/Debugger/RegisterWidget.h | 46 +++ Source/Core/DolphinQt2/DolphinQt2.vcxproj | 8 +- Source/Core/DolphinQt2/MainWindow.cpp | 6 + Source/Core/DolphinQt2/MainWindow.h | 3 + Source/Core/DolphinQt2/MenuBar.cpp | 19 + Source/Core/DolphinQt2/MenuBar.h | 4 + Source/Core/DolphinQt2/Settings.cpp | 15 + Source/Core/DolphinQt2/Settings.h | 4 +- 12 files changed, 633 insertions(+), 2 deletions(-) create mode 100644 Source/Core/DolphinQt2/Debugger/RegisterColumn.cpp create mode 100644 Source/Core/DolphinQt2/Debugger/RegisterColumn.h create mode 100644 Source/Core/DolphinQt2/Debugger/RegisterWidget.cpp create mode 100644 Source/Core/DolphinQt2/Debugger/RegisterWidget.h diff --git a/Source/Core/DolphinQt2/CMakeLists.txt b/Source/Core/DolphinQt2/CMakeLists.txt index ea8b9be430..793bda50cd 100644 --- a/Source/Core/DolphinQt2/CMakeLists.txt +++ b/Source/Core/DolphinQt2/CMakeLists.txt @@ -70,6 +70,8 @@ set(SRCS Config/Mapping/WiimoteEmuMotionControl.cpp Config/PropertiesDialog.cpp Config/SettingsWindow.cpp + Debugger/RegisterColumn.cpp + Debugger/RegisterWidget.cpp GameList/GameFileCache.cpp GameList/GameFile.cpp GameList/GameList.cpp diff --git a/Source/Core/DolphinQt2/Debugger/RegisterColumn.cpp b/Source/Core/DolphinQt2/Debugger/RegisterColumn.cpp new file mode 100644 index 0000000000..e93f6aee9e --- /dev/null +++ b/Source/Core/DolphinQt2/Debugger/RegisterColumn.cpp @@ -0,0 +1,123 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt2/Debugger/RegisterColumn.h" + +#include +#include + +#include + +RegisterColumn::RegisterColumn(RegisterType type, std::function get, + std::function set) + : m_type(type), m_get_register(std::move(get)), m_set_register(std::move(set)) +{ + RefreshValue(); + Update(); + + setFlags(set == nullptr ? flags() ^ Qt::ItemIsEditable : flags()); + setData(DATA_TYPE, static_cast(type)); +} + +RegisterDisplay RegisterColumn::GetDisplay() const +{ + return m_display; +} + +void RegisterColumn::SetDisplay(RegisterDisplay display) +{ + m_display = display; + Update(); +} + +void RegisterColumn::RefreshValue() +{ + QBrush brush = QPalette().brush(QPalette::Text); + + if (m_value != m_get_register()) + { + m_value = m_get_register(); + brush.setColor(Qt::red); + } + + setForeground(brush); + + Update(); +} + +u64 RegisterColumn::GetValue() const +{ + return m_value; +} + +void RegisterColumn::SetValue() +{ + u64 value = 0; + + bool valid = false; + + switch (m_display) + { + case RegisterDisplay::Hex: + value = text().toULongLong(&valid, 16); + break; + case RegisterDisplay::SInt32: + value = text().toInt(&valid); + break; + case RegisterDisplay::UInt32: + value = text().toUInt(&valid); + break; + case RegisterDisplay::Float: + { + float f = text().toFloat(&valid); + std::memcpy(&value, &f, sizeof(u32)); + break; + } + } + + if (!valid) + { + QMessageBox::critical(nullptr, QObject::tr("Invalid input"), + QObject::tr("Bad input for field")); + } + else + { + m_set_register(value); + } + + RefreshValue(); +} + +void RegisterColumn::Update() +{ + QString text; + + switch (m_display) + { + case RegisterDisplay::Hex: + text = QStringLiteral("%1").arg(m_value, + (m_type == RegisterType::ibat || m_type == RegisterType::dbat || + m_type == RegisterType::fpr ? + sizeof(u64) : + sizeof(u32)) * + 2, + 16, QLatin1Char('0')); + break; + case RegisterDisplay::SInt32: + text = QString::number(static_cast(m_value)); + break; + case RegisterDisplay::UInt32: + text = QString::number(static_cast(m_value)); + break; + case RegisterDisplay::Float: + { + float tmp; + std::memcpy(&tmp, &m_value, sizeof(float)); + text = QString::number(tmp); + break; + } + } + + setText(text); +} diff --git a/Source/Core/DolphinQt2/Debugger/RegisterColumn.h b/Source/Core/DolphinQt2/Debugger/RegisterColumn.h new file mode 100644 index 0000000000..ccfaf2b7ca --- /dev/null +++ b/Source/Core/DolphinQt2/Debugger/RegisterColumn.h @@ -0,0 +1,68 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include + +#include "Common/CommonTypes.h" + +enum class RegisterType +{ + gpr, // General purpose registers, int (r0-r31) + fpr, // General purpose registers, float (f0-f31) + ibat, // Instruction BATs (IBAT0-IBAT7) + dbat, // Data BATs (DBAT0-DBAT7) + pc, // Program counter + lr, // Link register + ctr, // Decremented and incremented by branch and count instructions + cr, // Condition register + fpscr, // Floating point status and control register + msr, // Machine state register + srr, // Machine status save/restore register (SRR0 - SRR1) + sr, // Segment register (SR0 - SR15) + exceptions, // Keeps track of currently triggered exceptions + int_mask, // ??? + int_cause, // ??? + dsisr, // Defines the cause of data / alignment exceptions + dar, // Data adress register + pt_hashmask // ??? +}; + +enum class RegisterDisplay +{ + Hex, + SInt32, + UInt32, + Float +}; + +constexpr int DATA_TYPE = Qt::UserRole; + +class RegisterColumn : public QTableWidgetItem +{ +public: + explicit RegisterColumn(RegisterType type, std::function get, + std::function set); + + void RefreshValue(); + + RegisterDisplay GetDisplay() const; + void SetDisplay(RegisterDisplay display); + u64 GetValue() const; + void SetValue(); + +private: + void Update(); + + RegisterType m_type; + + std::function m_get_register; + std::function m_set_register; + + u64 m_value; + RegisterDisplay m_display = RegisterDisplay::Hex; +}; diff --git a/Source/Core/DolphinQt2/Debugger/RegisterWidget.cpp b/Source/Core/DolphinQt2/Debugger/RegisterWidget.cpp new file mode 100644 index 0000000000..e9817b84ff --- /dev/null +++ b/Source/Core/DolphinQt2/Debugger/RegisterWidget.cpp @@ -0,0 +1,337 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt2/Debugger/RegisterWidget.h" + +#include "Core/Core.h" +#include "Core/HW/ProcessorInterface.h" +#include "Core/PowerPC/PowerPC.h" +#include "DolphinQt2/QtUtils/ActionHelper.h" +#include "DolphinQt2/Settings.h" + +#include +#include +#include +#include +#include + +RegisterWidget::RegisterWidget(QWidget* parent) : QDockWidget(parent) +{ + setWindowTitle(tr("Registers")); + setAllowedAreas(Qt::AllDockWidgetAreas); + + QSettings settings; + + restoreGeometry(settings.value(QStringLiteral("registerwidget/geometry")).toByteArray()); + setFloating(settings.value(QStringLiteral("registerwidget/floating")).toBool()); + + CreateWidgets(); + PopulateTable(); + ConnectWidgets(); + + connect(&Settings::Instance(), &Settings::EmulationStateChanged, [this](Core::State state) { + if (Settings::Instance().IsDebugModeEnabled() && Core::GetState() == Core::State::Paused) + emit RequestTableUpdate(); + }); + + connect(this, &RegisterWidget::RequestTableUpdate, [this] { + m_updating = true; + emit UpdateTable(); + m_updating = false; + }); + + connect(&Settings::Instance(), &Settings::RegistersVisibilityChanged, + [this](bool visible) { setHidden(!visible); }); + + connect(&Settings::Instance(), &Settings::DebugModeToggled, [this](bool enabled) { + setHidden(!enabled || !Settings::Instance().IsRegistersVisible()); + }); + + setHidden(!Settings::Instance().IsRegistersVisible() || + !Settings::Instance().IsDebugModeEnabled()); +} + +RegisterWidget::~RegisterWidget() +{ + QSettings settings; + + settings.setValue(QStringLiteral("registerwidget/geometry"), saveGeometry()); + settings.setValue(QStringLiteral("registerwidget/floating"), isFloating()); +} + +void RegisterWidget::closeEvent(QCloseEvent*) +{ + Settings::Instance().SetRegistersVisible(false); +} + +void RegisterWidget::CreateWidgets() +{ + m_table = new QTableWidget; + + m_table->setColumnCount(9); + + m_table->verticalHeader()->setVisible(false); + m_table->setContextMenuPolicy(Qt::CustomContextMenu); + m_table->setSelectionMode(QAbstractItemView::SingleSelection); + + QStringList empty_list; + + for (auto i = 0; i < 9; i++) + empty_list << QStringLiteral(""); + + m_table->setHorizontalHeaderLabels(empty_list); + + QWidget* widget = new QWidget; + auto* layout = new QVBoxLayout; + layout->addWidget(m_table); + widget->setLayout(layout); + + setWidget(widget); +} + +void RegisterWidget::ConnectWidgets() +{ + connect(m_table, &QTableWidget::customContextMenuRequested, this, + &RegisterWidget::ShowContextMenu); + connect(m_table, &QTableWidget::itemChanged, this, &RegisterWidget::OnItemChanged); +} + +void RegisterWidget::OnItemChanged(QTableWidgetItem* item) +{ + if (!item->data(DATA_TYPE).isNull() && !m_updating) + static_cast(item)->SetValue(); +} + +void RegisterWidget::ShowContextMenu() +{ + QMenu* menu = new QMenu(this); + + if (m_table->selectedItems().size()) + { + auto variant = m_table->selectedItems()[0]->data(DATA_TYPE); + + if (!variant.isNull()) + { + auto* item = reinterpret_cast(m_table->selectedItems()[0]); + auto type = static_cast(item->data(DATA_TYPE).toInt()); + auto display = item->GetDisplay(); + + menu->addAction(tr("Add to &watch")); + menu->addAction(tr("View &memory")); + menu->addAction(tr("View &code")); + + menu->addSeparator(); + + QActionGroup* group = new QActionGroup(menu); + group->setExclusive(true); + + auto* view_hex = menu->addAction(tr("Hexadecimal")); + auto* view_int = menu->addAction(tr("Signed Integer")); + auto* view_uint = menu->addAction(tr("Unsigned Integer")); + auto* view_float = menu->addAction(tr("Float")); + + for (auto* action : {view_hex, view_int, view_uint, view_float}) + { + action->setCheckable(true); + action->setVisible(false); + action->setActionGroup(group); + } + + switch (display) + { + case RegisterDisplay::Hex: + view_hex->setChecked(true); + break; + case RegisterDisplay::SInt32: + view_int->setChecked(true); + break; + case RegisterDisplay::UInt32: + view_uint->setChecked(true); + break; + case RegisterDisplay::Float: + view_float->setChecked(true); + break; + } + + switch (type) + { + case RegisterType::gpr: + view_hex->setVisible(true); + view_int->setVisible(true); + view_uint->setVisible(true); + view_float->setVisible(true); + break; + case RegisterType::fpr: + view_hex->setVisible(true); + view_float->setVisible(true); + break; + default: + break; + } + + connect(view_hex, &QAction::triggered, [this, item] { + m_updating = true; + item->SetDisplay(RegisterDisplay::Hex); + m_updating = false; + }); + + connect(view_int, &QAction::triggered, [this, item] { + m_updating = true; + item->SetDisplay(RegisterDisplay::SInt32); + m_updating = false; + }); + + connect(view_uint, &QAction::triggered, [this, item] { + m_updating = true; + item->SetDisplay(RegisterDisplay::UInt32); + m_updating = false; + }); + + connect(view_float, &QAction::triggered, [this, item] { + m_updating = true; + item->SetDisplay(RegisterDisplay::Float); + m_updating = false; + }); + + menu->addSeparator(); + } + } + + AddAction(menu, tr("Update"), this, [this] { emit RequestTableUpdate(); }); + + menu->exec(QCursor::pos()); +} + +void RegisterWidget::PopulateTable() +{ + for (int i = 0; i < 32; i++) + { + // General purpose registers (int) + AddRegister(i, 0, RegisterType::gpr, "r" + std::to_string(i), [i] { return GPR(i); }, + [i](u64 value) { GPR(i) = value; }); + + // General purpose registers (float) + AddRegister(i, 2, RegisterType::fpr, "f" + std::to_string(i), [i] { return riPS0(i); }, + [i](u64 value) { riPS0(i) = value; }); + + AddRegister(i, 4, RegisterType::fpr, "", [i] { return riPS1(i); }, + [i](u64 value) { riPS1(i) = value; }); + } + + for (int i = 0; i < 8; i++) + { + // IBAT registers + AddRegister(i, 5, RegisterType::ibat, "IBAT" + std::to_string(i), + [i] { + return (static_cast(PowerPC::ppcState.spr[SPR_IBAT0U + i * 2]) << 32) + + PowerPC::ppcState.spr[SPR_IBAT0L + i * 2]; + }, + nullptr); + // DBAT registers + AddRegister(i + 8, 5, RegisterType::dbat, "DBAT" + std::to_string(i), + [i] { + return (static_cast(PowerPC::ppcState.spr[SPR_DBAT0U + i * 2]) << 32) + + PowerPC::ppcState.spr[SPR_DBAT0L + i * 2]; + }, + nullptr); + } + + for (int i = 0; i < 16; i++) + { + // SR registers + AddRegister(i, 7, RegisterType::sr, "SR" + std::to_string(i), + [i] { return PowerPC::ppcState.sr[i]; }, + [i](u64 value) { PowerPC::ppcState.sr[i] = value; }); + } + + // Special registers + // PC + AddRegister(16, 5, RegisterType::pc, "PC", [] { return PowerPC::ppcState.pc; }, + [](u64 value) { PowerPC::ppcState.pc = value; }); + + // LR + AddRegister(17, 5, RegisterType::fpscr, "LR", [] { return PowerPC::ppcState.spr[SPR_LR]; }, + [](u64 value) { PowerPC::ppcState.spr[SPR_LR] = value; }); + + // CTR + AddRegister(18, 5, RegisterType::fpscr, "FPSCR", [] { return PowerPC::ppcState.spr[SPR_CTR]; }, + [](u64 value) { PowerPC::ppcState.spr[SPR_CTR] = value; }); + + // CR + AddRegister(19, 5, RegisterType::cr, "CR", [] { return GetCR(); }, + [](u64 value) { SetCR(value); }); + + // FPSCR + AddRegister(20, 5, RegisterType::fpscr, "FPSCR", [] { return PowerPC::ppcState.fpscr; }, + [](u64 value) { PowerPC::ppcState.fpscr = value; }); + + // MSR + AddRegister(21, 5, RegisterType::msr, "MSR", [] { return PowerPC::ppcState.msr; }, + [](u64 value) { PowerPC::ppcState.msr = value; }); + + // SRR 0-1 + AddRegister(22, 5, RegisterType::srr, "SRR0", [] { return PowerPC::ppcState.spr[SPR_SRR0]; }, + [](u64 value) { PowerPC::ppcState.spr[SPR_SRR0] = value; }); + AddRegister(23, 5, RegisterType::srr, "SRR1", [] { return PowerPC::ppcState.spr[SPR_SRR1]; }, + [](u64 value) { PowerPC::ppcState.spr[SPR_SRR1] = value; }); + + // Exceptions + AddRegister(24, 5, RegisterType::exceptions, "Exceptions", + [] { return PowerPC::ppcState.Exceptions; }, + [](u64 value) { PowerPC::ppcState.Exceptions = value; }); + + // Int Mask + AddRegister(25, 5, RegisterType::int_mask, "Int Mask", + [] { return ProcessorInterface::GetMask(); }, nullptr); + + // Int Cause + AddRegister(26, 5, RegisterType::int_cause, "Int Cause", + [] { return ProcessorInterface::GetCause(); }, nullptr); + + // DSISR + AddRegister(27, 5, RegisterType::dsisr, "DSISR", [] { return PowerPC::ppcState.spr[SPR_DSISR]; }, + [](u64 value) { PowerPC::ppcState.spr[SPR_DSISR] = value; }); + // DAR + AddRegister(28, 5, RegisterType::dar, "DAR", [] { return PowerPC::ppcState.spr[SPR_DAR]; }, + [](u64 value) { PowerPC::ppcState.spr[SPR_DAR] = value; }); + + // Hash Mask + AddRegister( + 29, 5, RegisterType::pt_hashmask, "Hash Mask", + [] { return (PowerPC::ppcState.pagetable_hashmask << 6) | PowerPC::ppcState.pagetable_base; }, + nullptr); + + emit RequestTableUpdate(); + m_table->resizeColumnsToContents(); +} + +void RegisterWidget::AddRegister(int row, int column, RegisterType type, std::string register_name, + std::function get_reg, std::function set_reg) +{ + auto* value = new RegisterColumn(type, get_reg, set_reg); + + if (m_table->rowCount() <= row) + m_table->setRowCount(row + 1); + + bool has_label = !register_name.empty(); + + if (has_label) + { + auto* label = new QTableWidgetItem(QString::fromStdString(register_name)); + label->setFlags(Qt::ItemIsEnabled); + + QFont label_font = label->font(); + label_font.setBold(true); + label->setFont(label_font); + + m_table->setItem(row, column, label); + m_table->setItem(row, column + 1, value); + } + else + { + m_table->setItem(row, column, value); + } + + connect(this, &RegisterWidget::UpdateTable, [value] { value->RefreshValue(); }); +} diff --git a/Source/Core/DolphinQt2/Debugger/RegisterWidget.h b/Source/Core/DolphinQt2/Debugger/RegisterWidget.h new file mode 100644 index 0000000000..3793e12e6e --- /dev/null +++ b/Source/Core/DolphinQt2/Debugger/RegisterWidget.h @@ -0,0 +1,46 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include + +#include "Common/CommonTypes.h" +#include "DolphinQt2/Debugger/RegisterColumn.h" + +class QTableWidget; +class QCloseEvent; + +class RegisterWidget : public QDockWidget +{ + Q_OBJECT +public: + explicit RegisterWidget(QWidget* parent = nullptr); + ~RegisterWidget(); + +signals: + void RequestTableUpdate(); + void UpdateTable(); + void UpdateValue(QTableWidgetItem* item); + void UpdateValueType(QTableWidgetItem* item); + +protected: + void closeEvent(QCloseEvent*) override; + +private: + void CreateWidgets(); + void ConnectWidgets(); + void PopulateTable(); + + void ShowContextMenu(); + void OnItemChanged(QTableWidgetItem* item); + + void AddRegister(int row, int column, RegisterType type, std::string register_name, + std::function get_reg, std::function set_reg); + + QTableWidget* m_table; + bool m_updating = false; +}; diff --git a/Source/Core/DolphinQt2/DolphinQt2.vcxproj b/Source/Core/DolphinQt2/DolphinQt2.vcxproj index 82d1a653bb..966234241a 100644 --- a/Source/Core/DolphinQt2/DolphinQt2.vcxproj +++ b/Source/Core/DolphinQt2/DolphinQt2.vcxproj @@ -47,7 +47,7 @@ avrt.lib;iphlpapi.lib;winmm.lib;setupapi.lib;opengl32.lib;glu32.lib;rpcrt4.lib;comctl32.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;%(AdditionalDependencies) - $(ProjectDir)VideoInterface;$(ProjectDir)GameList;$(ProjectDir)Settings;$(ProjectDir)Config;$(ProjectDir)Config\Mapping;$(ProjectDir)Config\Graphics;$(ProjectDir)NetPlay;$(ProjectDir)QtUtils;%(AdditionalIncludeDirectories) + $(ProjectDir)VideoInterface;$(ProjectDir)GameList;$(ProjectDir)Debugger;$(ProjectDir)Settings;$(ProjectDir)Config;$(ProjectDir)Config\Mapping;$(ProjectDir)Config\Graphics;$(ProjectDir)NetPlay;$(ProjectDir)QtUtils;%(AdditionalIncludeDirectories) DolphinQt2.manifest;%(AdditionalManifestFiles) @@ -84,6 +84,7 @@ + @@ -156,6 +157,7 @@ + @@ -201,6 +203,8 @@ + + @@ -255,6 +259,8 @@ + + diff --git a/Source/Core/DolphinQt2/MainWindow.cpp b/Source/Core/DolphinQt2/MainWindow.cpp index 99e762cdb8..99278149ca 100644 --- a/Source/Core/DolphinQt2/MainWindow.cpp +++ b/Source/Core/DolphinQt2/MainWindow.cpp @@ -46,6 +46,7 @@ #include "DolphinQt2/Config/LogWidget.h" #include "DolphinQt2/Config/Mapping/MappingWindow.h" #include "DolphinQt2/Config/SettingsWindow.h" +#include "DolphinQt2/Debugger/RegisterWidget.h" #include "DolphinQt2/FIFOPlayerWindow.h" #include "DolphinQt2/Host.h" #include "DolphinQt2/HotkeyScheduler.h" @@ -97,6 +98,8 @@ MainWindow::~MainWindow() { m_render_widget->deleteLater(); ShutdownControllers(); + + Config::Save(); } void MainWindow::InitControllers() @@ -166,6 +169,7 @@ void MainWindow::CreateComponents() connect(m_fifo_window, &FIFOPlayerWindow::LoadFIFORequested, this, [this](const QString& path) { StartGame(path); }); + m_register_widget = new RegisterWidget(this); #if defined(HAVE_XRANDR) && HAVE_XRANDR m_graphics_window = new GraphicsWindow( @@ -314,8 +318,10 @@ void MainWindow::ConnectStack() setTabPosition(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea, QTabWidget::North); addDockWidget(Qt::RightDockWidgetArea, m_log_widget); addDockWidget(Qt::RightDockWidgetArea, m_log_config_widget); + addDockWidget(Qt::RightDockWidgetArea, m_register_widget); tabifyDockWidget(m_log_widget, m_log_config_widget); + tabifyDockWidget(m_log_widget, m_register_widget); } void MainWindow::Open() diff --git a/Source/Core/DolphinQt2/MainWindow.h b/Source/Core/DolphinQt2/MainWindow.h index a230a2322f..1eb3c8c008 100644 --- a/Source/Core/DolphinQt2/MainWindow.h +++ b/Source/Core/DolphinQt2/MainWindow.h @@ -31,6 +31,7 @@ class SettingsWindow; class ControllersWindow; class DragEnterEvent; class GraphicsWindow; +class RegisterWidget; class MainWindow final : public QMainWindow { @@ -138,7 +139,9 @@ private: NetPlayDialog* m_netplay_dialog; NetPlaySetupDialog* m_netplay_setup_dialog; GraphicsWindow* m_graphics_window; + LogWidget* m_log_widget; LogConfigWidget* m_log_config_widget; FIFOPlayerWindow* m_fifo_window; + RegisterWidget* m_register_widget; }; diff --git a/Source/Core/DolphinQt2/MenuBar.cpp b/Source/Core/DolphinQt2/MenuBar.cpp index aaea20cbc7..d0143eb9f9 100644 --- a/Source/Core/DolphinQt2/MenuBar.cpp +++ b/Source/Core/DolphinQt2/MenuBar.cpp @@ -50,6 +50,7 @@ MenuBar::MenuBar(QWidget* parent) : QMenuBar(parent) connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [=](Core::State state) { OnEmulationStateChanged(state); }); OnEmulationStateChanged(Core::GetState()); + connect(&Settings::Instance(), &Settings::DebugModeToggled, this, &MenuBar::OnDebugModeToggled); connect(this, &MenuBar::SelectionChanged, this, &MenuBar::OnSelectionChanged); connect(this, &MenuBar::RecordingStatusChanged, this, &MenuBar::OnRecordingStatusChanged); @@ -83,6 +84,13 @@ void MenuBar::OnEmulationStateChanged(Core::State state) UpdateStateSlotMenu(); UpdateToolsMenu(running); + + OnDebugModeToggled(Settings::Instance().IsDebugModeEnabled()); +} + +void MenuBar::OnDebugModeToggled(bool enabled) +{ + m_show_registers->setVisible(enabled); } void MenuBar::AddFileMenu() @@ -255,6 +263,17 @@ void MenuBar::AddViewMenu() view_menu->addSeparator(); + m_show_registers = view_menu->addAction(tr("&Registers")); + m_show_registers->setCheckable(true); + m_show_registers->setChecked(Settings::Instance().IsRegistersVisible()); + + connect(m_show_registers, &QAction::toggled, &Settings::Instance(), + &Settings::SetRegistersVisible); + connect(&Settings::Instance(), &Settings::RegistersVisibilityChanged, m_show_registers, + &QAction::setChecked); + + view_menu->addSeparator(); + AddGameListTypeSection(view_menu); view_menu->addSeparator(); AddListColumnsMenu(view_menu); diff --git a/Source/Core/DolphinQt2/MenuBar.h b/Source/Core/DolphinQt2/MenuBar.h index 3bbb51345a..c8af98d00b 100644 --- a/Source/Core/DolphinQt2/MenuBar.h +++ b/Source/Core/DolphinQt2/MenuBar.h @@ -119,6 +119,7 @@ private: void OnSelectionChanged(QSharedPointer game_file); void OnRecordingStatusChanged(bool recording); void OnReadOnlyModeChanged(bool read_only); + void OnDebugModeToggled(bool enabled); // File QAction* m_open_action; @@ -157,4 +158,7 @@ private: QAction* m_recording_start; QAction* m_recording_stop; QAction* m_recording_read_only; + + // View + QAction* m_show_registers; }; diff --git a/Source/Core/DolphinQt2/Settings.cpp b/Source/Core/DolphinQt2/Settings.cpp index 30dbde00d8..2fb765c1aa 100644 --- a/Source/Core/DolphinQt2/Settings.cpp +++ b/Source/Core/DolphinQt2/Settings.cpp @@ -210,3 +210,18 @@ bool Settings::IsDebugModeEnabled() const { return SConfig::GetInstance().bEnableDebugging; } + +void Settings::SetRegistersVisible(bool enabled) +{ + if (IsRegistersVisible() != enabled) + { + QSettings().setValue(QStringLiteral("debugger/showregisters"), enabled); + + emit RegistersVisibilityChanged(enabled); + } +} + +bool Settings::IsRegistersVisible() const +{ + return QSettings().value(QStringLiteral("debugger/showregisters")).toBool(); +} diff --git a/Source/Core/DolphinQt2/Settings.h b/Source/Core/DolphinQt2/Settings.h index 4ab31ad2cc..ebee8cb788 100644 --- a/Source/Core/DolphinQt2/Settings.h +++ b/Source/Core/DolphinQt2/Settings.h @@ -81,6 +81,8 @@ public: // Debug void SetDebugModeEnabled(bool enabled); bool IsDebugModeEnabled() const; + void SetRegistersVisible(bool enabled); + bool IsRegistersVisible() const; // Other GameListModel* GetGameListModel() const; @@ -93,13 +95,13 @@ signals: void HideCursorChanged(); void VolumeChanged(int volume); void NANDRefresh(); + void RegistersVisibilityChanged(bool visible); void LogVisibilityChanged(bool visible); void LogConfigVisibilityChanged(bool visible); void EnableCheatsChanged(bool enabled); void DebugModeToggled(bool enabled); private: - bool m_registers_visible = false; std::unique_ptr m_client; std::unique_ptr m_server; Settings(); From 08716be43bbefe29ec6fe2a08a88439278746006 Mon Sep 17 00:00:00 2001 From: spycrab Date: Wed, 27 Sep 2017 08:53:05 +0200 Subject: [PATCH 4/6] Qt/Debugger: Implement "Watch" window --- Source/Core/DolphinQt2/CMakeLists.txt | 1 + .../Core/DolphinQt2/Debugger/WatchWidget.cpp | 308 ++++++++++++++++++ Source/Core/DolphinQt2/Debugger/WatchWidget.h | 51 +++ Source/Core/DolphinQt2/DolphinQt2.vcxproj | 3 + Source/Core/DolphinQt2/MainWindow.cpp | 5 +- Source/Core/DolphinQt2/MainWindow.h | 2 + Source/Core/DolphinQt2/MenuBar.cpp | 9 + Source/Core/DolphinQt2/MenuBar.h | 1 + Source/Core/DolphinQt2/Settings.cpp | 15 + Source/Core/DolphinQt2/Settings.h | 3 + 10 files changed, 397 insertions(+), 1 deletion(-) create mode 100644 Source/Core/DolphinQt2/Debugger/WatchWidget.cpp create mode 100644 Source/Core/DolphinQt2/Debugger/WatchWidget.h diff --git a/Source/Core/DolphinQt2/CMakeLists.txt b/Source/Core/DolphinQt2/CMakeLists.txt index 793bda50cd..3200b3f7ae 100644 --- a/Source/Core/DolphinQt2/CMakeLists.txt +++ b/Source/Core/DolphinQt2/CMakeLists.txt @@ -72,6 +72,7 @@ set(SRCS Config/SettingsWindow.cpp Debugger/RegisterColumn.cpp Debugger/RegisterWidget.cpp + Debugger/WatchWidget.cpp GameList/GameFileCache.cpp GameList/GameFile.cpp GameList/GameList.cpp diff --git a/Source/Core/DolphinQt2/Debugger/WatchWidget.cpp b/Source/Core/DolphinQt2/Debugger/WatchWidget.cpp new file mode 100644 index 0000000000..957388be8d --- /dev/null +++ b/Source/Core/DolphinQt2/Debugger/WatchWidget.cpp @@ -0,0 +1,308 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt2/Debugger/WatchWidget.h" + +#include "Common/FileUtil.h" +#include "Common/IniFile.h" +#include "Core/ConfigManager.h" +#include "Core/Core.h" +#include "Core/PowerPC/PowerPC.h" +#include "DolphinQt2/QtUtils/ActionHelper.h" +#include "DolphinQt2/Settings.h" + +#include +#include +#include +#include +#include +#include +#include + +WatchWidget::WatchWidget(QWidget* parent) : QDockWidget(parent) +{ + setWindowTitle(tr("Watch")); + setAllowedAreas(Qt::AllDockWidgetAreas); + + QSettings settings; + + restoreGeometry(settings.value(QStringLiteral("watchwidget/geometry")).toByteArray()); + setFloating(settings.value(QStringLiteral("watchwidget/floating")).toBool()); + + CreateWidgets(); + ConnectWidgets(); + + connect(&Settings::Instance(), &Settings::EmulationStateChanged, [this](Core::State state) { + if (!Settings::Instance().IsDebugModeEnabled()) + return; + + m_load->setEnabled(Core::IsRunning()); + m_save->setEnabled(Core::IsRunning()); + + if (state != Core::State::Starting) + Update(); + }); + + connect(&Settings::Instance(), &Settings::WatchVisibilityChanged, + [this](bool visible) { setHidden(!visible); }); + + connect(&Settings::Instance(), &Settings::DebugModeToggled, + [this](bool enabled) { setHidden(!enabled || !Settings::Instance().IsWatchVisible()); }); + + setHidden(!Settings::Instance().IsWatchVisible() || !Settings::Instance().IsDebugModeEnabled()); + + Update(); +} + +WatchWidget::~WatchWidget() +{ + QSettings settings; + + settings.setValue(QStringLiteral("watchwidget/geometry"), saveGeometry()); + settings.setValue(QStringLiteral("watchwidget/floating"), isFloating()); +} + +void WatchWidget::CreateWidgets() +{ + m_toolbar = new QToolBar; + m_table = new QTableWidget; + + m_table->setColumnCount(5); + m_table->verticalHeader()->setHidden(true); + m_table->setContextMenuPolicy(Qt::CustomContextMenu); + m_table->setSelectionMode(QAbstractItemView::SingleSelection); + + m_load = AddAction(m_toolbar, tr("Load"), this, &WatchWidget::OnLoad); + m_save = AddAction(m_toolbar, tr("Save"), this, &WatchWidget::OnSave); + + m_load->setEnabled(false); + m_save->setEnabled(false); + + auto* layout = new QVBoxLayout; + layout->addWidget(m_toolbar); + layout->addWidget(m_table); + + QWidget* widget = new QWidget; + widget->setLayout(layout); + + setWidget(widget); +} + +void WatchWidget::ConnectWidgets() +{ + connect(m_table, &QTableWidget::customContextMenuRequested, this, &WatchWidget::ShowContextMenu); + connect(m_table, &QTableWidget::itemChanged, this, &WatchWidget::OnItemChanged); +} + +void WatchWidget::Update() +{ + m_updating = true; + + m_table->clear(); + + int size = static_cast(PowerPC::watches.GetWatches().size()); + + m_table->setRowCount(size + 1); + + m_table->setHorizontalHeaderLabels( + {tr("Label"), tr("Address"), tr("Hexadecimal"), tr("Decimal"), tr("String")}); + + for (int i = 0; i < size; i++) + { + auto entry = PowerPC::watches.GetWatches().at(i); + + auto* label = new QTableWidgetItem(QString::fromStdString(entry.name)); + auto* address = + new QTableWidgetItem(QStringLiteral("%1").arg(entry.address, 8, 16, QLatin1Char('0'))); + auto* hex = new QTableWidgetItem; + auto* decimal = new QTableWidgetItem; + auto* string = new QTableWidgetItem; + + QBrush brush = QPalette().brush(QPalette::Text); + + if (!Core::IsRunning() || !PowerPC::HostIsRAMAddress(entry.address)) + brush.setColor(Qt::red); + + if (Core::IsRunning()) + { + if (PowerPC::HostIsRAMAddress(entry.address)) + { + hex->setText(QStringLiteral("%1").arg(PowerPC::HostRead_U32(entry.address), 8, 16, + QLatin1Char('0'))); + decimal->setText(QString::number(PowerPC::HostRead_U32(entry.address))); + string->setText(QString::fromStdString(PowerPC::HostGetString(entry.address, 32))); + } + } + + address->setForeground(brush); + + int column = 0; + + for (auto* item : {label, address, hex, decimal, string}) + { + item->setData(Qt::UserRole, i); + item->setData(Qt::UserRole + 1, column++); + } + + string->setFlags(Qt::ItemIsEnabled); + + m_table->setItem(i, 0, label); + m_table->setItem(i, 1, address); + m_table->setItem(i, 2, hex); + m_table->setItem(i, 3, decimal); + m_table->setItem(i, 4, string); + } + + auto* label = new QTableWidgetItem; + label->setData(Qt::UserRole, -1); + + m_table->setItem(size, 0, label); + + for (int i = 1; i < 5; i++) + { + auto* no_edit = new QTableWidgetItem; + no_edit->setFlags(Qt::ItemIsEnabled); + m_table->setItem(size, i, no_edit); + } + + m_updating = false; +} + +void WatchWidget::closeEvent(QCloseEvent*) +{ + Settings::Instance().SetWatchVisible(false); +} +void WatchWidget::OnLoad() +{ + IniFile ini; + + Watches::TWatchesStr watches; + + if (!ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini", + false)) + { + return; + } + + if (ini.GetLines("Watches", &watches, false)) + { + PowerPC::watches.Clear(); + PowerPC::watches.AddFromStrings(watches); + } + + Update(); +} + +void WatchWidget::OnSave() +{ + IniFile ini; + ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini", + false); + ini.SetLines("Watches", PowerPC::watches.GetStrings()); + ini.Save(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini"); +} + +void WatchWidget::ShowContextMenu() +{ + QMenu* menu = new QMenu(this); + + if (m_table->selectedItems().size()) + { + auto row_variant = m_table->selectedItems()[0]->data(Qt::UserRole); + + if (!row_variant.isNull()) + { + int row = row_variant.toInt(); + + if (row >= 0) + { + AddAction(menu, tr("&Delete Watch"), this, [this, row] { DeleteWatch(row); }); + AddAction(menu, tr("&Add Memory Breakpoint"), this, + [this, row] { AddWatchBreakpoint(row); }); + } + } + } + + menu->addSeparator(); + + AddAction(menu, tr("Update"), this, &WatchWidget::Update); + + menu->exec(QCursor::pos()); +} + +void WatchWidget::OnItemChanged(QTableWidgetItem* item) +{ + if (m_updating || item->data(Qt::UserRole).isNull()) + return; + + int row = item->data(Qt::UserRole).toInt(); + int column = item->data(Qt::UserRole + 1).toInt(); + + if (row == -1) + { + if (!item->text().isEmpty()) + { + AddWatch(item->text(), 0); + + Update(); + return; + } + } + else + { + switch (column) + { + // Label + case 0: + if (item->text().isEmpty()) + DeleteWatch(row); + else + PowerPC::watches.UpdateName(row, item->text().toStdString()); + break; + // Address + // Hexadecimal + // Decimal + case 1: + case 2: + case 3: + { + bool good; + quint32 value = item->text().toUInt(&good, column < 3 ? 16 : 10); + + if (good) + { + if (column == 1) + PowerPC::watches.Update(row, value); + else + PowerPC::HostWrite_U32(value, PowerPC::watches.GetWatches().at(row).address); + } + else + { + QMessageBox::critical(this, tr("Error"), tr("Bad input provided")); + } + break; + } + } + + Update(); + } +} + +void WatchWidget::DeleteWatch(int row) +{ + PowerPC::watches.Remove(PowerPC::watches.GetWatches().at(row).address); + Update(); +} + +void WatchWidget::AddWatchBreakpoint(int row) +{ + emit RequestMemoryBreakpoint(PowerPC::watches.GetWatches().at(row).address); +} + +void WatchWidget::AddWatch(QString name, u32 addr) +{ + PowerPC::watches.Add(addr); + PowerPC::watches.UpdateName(static_cast(PowerPC::watches.GetWatches().size()) - 1, + name.toStdString()); +} diff --git a/Source/Core/DolphinQt2/Debugger/WatchWidget.h b/Source/Core/DolphinQt2/Debugger/WatchWidget.h new file mode 100644 index 0000000000..26cb76378f --- /dev/null +++ b/Source/Core/DolphinQt2/Debugger/WatchWidget.h @@ -0,0 +1,51 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "Common/CommonTypes.h" + +class QAction; +class QTableWidget; +class QTableWidgetItem; +class QToolBar; +class QCloseEvent; + +class WatchWidget : public QDockWidget +{ + Q_OBJECT +public: + explicit WatchWidget(QWidget* parent = nullptr); + ~WatchWidget(); + + void AddWatch(QString name, u32 addr); +signals: + void RequestMemoryBreakpoint(u32 addr); + +protected: + void closeEvent(QCloseEvent*) override; + +private: + void CreateWidgets(); + void ConnectWidgets(); + + void OnLoad(); + void OnSave(); + + void Update(); + + void ShowContextMenu(); + void OnItemChanged(QTableWidgetItem* item); + void DeleteWatch(int row); + void AddWatchBreakpoint(int row); + + QAction* m_load; + QAction* m_save; + QToolBar* m_toolbar; + QTableWidget* m_table; + + bool m_updating = false; +}; diff --git a/Source/Core/DolphinQt2/DolphinQt2.vcxproj b/Source/Core/DolphinQt2/DolphinQt2.vcxproj index 966234241a..d43f142cae 100644 --- a/Source/Core/DolphinQt2/DolphinQt2.vcxproj +++ b/Source/Core/DolphinQt2/DolphinQt2.vcxproj @@ -85,6 +85,7 @@ + @@ -163,6 +164,7 @@ + @@ -205,6 +207,7 @@ + diff --git a/Source/Core/DolphinQt2/MainWindow.cpp b/Source/Core/DolphinQt2/MainWindow.cpp index 99278149ca..4d460f9735 100644 --- a/Source/Core/DolphinQt2/MainWindow.cpp +++ b/Source/Core/DolphinQt2/MainWindow.cpp @@ -47,7 +47,7 @@ #include "DolphinQt2/Config/Mapping/MappingWindow.h" #include "DolphinQt2/Config/SettingsWindow.h" #include "DolphinQt2/Debugger/RegisterWidget.h" -#include "DolphinQt2/FIFOPlayerWindow.h" +#include "DolphinQt2/Debugger/WatchWidget.h" #include "DolphinQt2/Host.h" #include "DolphinQt2/HotkeyScheduler.h" #include "DolphinQt2/MainWindow.h" @@ -170,6 +170,7 @@ void MainWindow::CreateComponents() connect(m_fifo_window, &FIFOPlayerWindow::LoadFIFORequested, this, [this](const QString& path) { StartGame(path); }); m_register_widget = new RegisterWidget(this); + m_watch_widget = new WatchWidget(this); #if defined(HAVE_XRANDR) && HAVE_XRANDR m_graphics_window = new GraphicsWindow( @@ -319,9 +320,11 @@ void MainWindow::ConnectStack() addDockWidget(Qt::RightDockWidgetArea, m_log_widget); addDockWidget(Qt::RightDockWidgetArea, m_log_config_widget); addDockWidget(Qt::RightDockWidgetArea, m_register_widget); + addDockWidget(Qt::RightDockWidgetArea, m_watch_widget); tabifyDockWidget(m_log_widget, m_log_config_widget); tabifyDockWidget(m_log_widget, m_register_widget); + tabifyDockWidget(m_log_widget, m_watch_widget); } void MainWindow::Open() diff --git a/Source/Core/DolphinQt2/MainWindow.h b/Source/Core/DolphinQt2/MainWindow.h index 1eb3c8c008..8cbf8108b9 100644 --- a/Source/Core/DolphinQt2/MainWindow.h +++ b/Source/Core/DolphinQt2/MainWindow.h @@ -32,6 +32,7 @@ class ControllersWindow; class DragEnterEvent; class GraphicsWindow; class RegisterWidget; +class WatchWidget; class MainWindow final : public QMainWindow { @@ -144,4 +145,5 @@ private: LogConfigWidget* m_log_config_widget; FIFOPlayerWindow* m_fifo_window; RegisterWidget* m_register_widget; + WatchWidget* m_watch_widget; }; diff --git a/Source/Core/DolphinQt2/MenuBar.cpp b/Source/Core/DolphinQt2/MenuBar.cpp index d0143eb9f9..0ef13e32e6 100644 --- a/Source/Core/DolphinQt2/MenuBar.cpp +++ b/Source/Core/DolphinQt2/MenuBar.cpp @@ -91,6 +91,7 @@ void MenuBar::OnEmulationStateChanged(Core::State state) void MenuBar::OnDebugModeToggled(bool enabled) { m_show_registers->setVisible(enabled); + m_show_watch->setVisible(enabled); } void MenuBar::AddFileMenu() @@ -272,6 +273,14 @@ void MenuBar::AddViewMenu() connect(&Settings::Instance(), &Settings::RegistersVisibilityChanged, m_show_registers, &QAction::setChecked); + m_show_watch = view_menu->addAction(tr("&Watch")); + m_show_watch->setCheckable(true); + m_show_watch->setChecked(Settings::Instance().IsWatchVisible()); + + connect(m_show_watch, &QAction::toggled, &Settings::Instance(), &Settings::SetWatchVisible); + connect(&Settings::Instance(), &Settings::WatchVisibilityChanged, m_show_watch, + &QAction::setChecked); + view_menu->addSeparator(); AddGameListTypeSection(view_menu); diff --git a/Source/Core/DolphinQt2/MenuBar.h b/Source/Core/DolphinQt2/MenuBar.h index c8af98d00b..385c8c3ca4 100644 --- a/Source/Core/DolphinQt2/MenuBar.h +++ b/Source/Core/DolphinQt2/MenuBar.h @@ -161,4 +161,5 @@ private: // View QAction* m_show_registers; + QAction* m_show_watch; }; diff --git a/Source/Core/DolphinQt2/Settings.cpp b/Source/Core/DolphinQt2/Settings.cpp index 2fb765c1aa..d737f0893b 100644 --- a/Source/Core/DolphinQt2/Settings.cpp +++ b/Source/Core/DolphinQt2/Settings.cpp @@ -225,3 +225,18 @@ bool Settings::IsRegistersVisible() const { return QSettings().value(QStringLiteral("debugger/showregisters")).toBool(); } + +void Settings::SetWatchVisible(bool enabled) +{ + if (IsWatchVisible() != enabled) + { + QSettings().setValue(QStringLiteral("debugger/showwatch"), enabled); + + emit WatchVisibilityChanged(enabled); + } +} + +bool Settings::IsWatchVisible() const +{ + return QSettings().value(QStringLiteral("debugger/showwatch")).toBool(); +} diff --git a/Source/Core/DolphinQt2/Settings.h b/Source/Core/DolphinQt2/Settings.h index ebee8cb788..a654c21222 100644 --- a/Source/Core/DolphinQt2/Settings.h +++ b/Source/Core/DolphinQt2/Settings.h @@ -83,6 +83,8 @@ public: bool IsDebugModeEnabled() const; void SetRegistersVisible(bool enabled); bool IsRegistersVisible() const; + void SetWatchVisible(bool enabled); + bool IsWatchVisible() const; // Other GameListModel* GetGameListModel() const; @@ -99,6 +101,7 @@ signals: void LogVisibilityChanged(bool visible); void LogConfigVisibilityChanged(bool visible); void EnableCheatsChanged(bool enabled); + void WatchVisibilityChanged(bool visible); void DebugModeToggled(bool enabled); private: From 8795b342d101a370d314a8b600d38ff1512ec1d4 Mon Sep 17 00:00:00 2001 From: spycrab Date: Tue, 3 Oct 2017 18:43:44 +0200 Subject: [PATCH 5/6] Qt/Debugger: Implement "Breakpoints" window --- Source/Core/DolphinQt2/CMakeLists.txt | 2 + .../DolphinQt2/Debugger/BreakpointWidget.cpp | 284 ++++++++++++++++++ .../DolphinQt2/Debugger/BreakpointWidget.h | 47 +++ .../Debugger/NewBreakpointDialog.cpp | 182 +++++++++++ .../DolphinQt2/Debugger/NewBreakpointDialog.h | 57 ++++ .../Core/DolphinQt2/Debugger/WatchWidget.cpp | 1 + Source/Core/DolphinQt2/DolphinQt2.vcxproj | 6 + Source/Core/DolphinQt2/MainWindow.cpp | 5 + Source/Core/DolphinQt2/MainWindow.h | 2 + Source/Core/DolphinQt2/MenuBar.cpp | 10 + Source/Core/DolphinQt2/MenuBar.h | 1 + Source/Core/DolphinQt2/Settings.cpp | 15 + Source/Core/DolphinQt2/Settings.h | 3 + 13 files changed, 615 insertions(+) create mode 100644 Source/Core/DolphinQt2/Debugger/BreakpointWidget.cpp create mode 100644 Source/Core/DolphinQt2/Debugger/BreakpointWidget.h create mode 100644 Source/Core/DolphinQt2/Debugger/NewBreakpointDialog.cpp create mode 100644 Source/Core/DolphinQt2/Debugger/NewBreakpointDialog.h diff --git a/Source/Core/DolphinQt2/CMakeLists.txt b/Source/Core/DolphinQt2/CMakeLists.txt index 3200b3f7ae..3ed0ffb1c8 100644 --- a/Source/Core/DolphinQt2/CMakeLists.txt +++ b/Source/Core/DolphinQt2/CMakeLists.txt @@ -70,6 +70,8 @@ set(SRCS Config/Mapping/WiimoteEmuMotionControl.cpp Config/PropertiesDialog.cpp Config/SettingsWindow.cpp + Debugger/BreakpointWidget.cpp + Debugger/NewBreakpointDialog.cpp Debugger/RegisterColumn.cpp Debugger/RegisterWidget.cpp Debugger/WatchWidget.cpp diff --git a/Source/Core/DolphinQt2/Debugger/BreakpointWidget.cpp b/Source/Core/DolphinQt2/Debugger/BreakpointWidget.cpp new file mode 100644 index 0000000000..80ac714607 --- /dev/null +++ b/Source/Core/DolphinQt2/Debugger/BreakpointWidget.cpp @@ -0,0 +1,284 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt2/Debugger/BreakpointWidget.h" + +#include +#include +#include +#include +#include + +#include "Common/FileUtil.h" +#include "Common/IniFile.h" +#include "Core/ConfigManager.h" +#include "Core/Core.h" +#include "Core/PowerPC/BreakPoints.h" +#include "Core/PowerPC/PPCSymbolDB.h" +#include "Core/PowerPC/PowerPC.h" +#include "DolphinQt2/Debugger/NewBreakpointDialog.h" +#include "DolphinQt2/QtUtils/ActionHelper.h" +#include "DolphinQt2/Settings.h" + +BreakpointWidget::BreakpointWidget(QWidget* parent) : QDockWidget(parent) +{ + setWindowTitle(tr("Breakpoints")); + setAllowedAreas(Qt::AllDockWidgetAreas); + + QSettings settings; + + restoreGeometry(settings.value(QStringLiteral("breakpointwidget/geometry")).toByteArray()); + setFloating(settings.value(QStringLiteral("breakpointwidget/floating")).toBool()); + + CreateWidgets(); + + connect(&Settings::Instance(), &Settings::EmulationStateChanged, [this](Core::State state) { + if (!Settings::Instance().IsDebugModeEnabled()) + return; + + m_load->setEnabled(Core::IsRunning()); + m_save->setEnabled(Core::IsRunning()); + }); + + connect(&Settings::Instance(), &Settings::BreakpointsVisibilityChanged, + [this](bool visible) { setHidden(!visible); }); + + connect(&Settings::Instance(), &Settings::DebugModeToggled, [this](bool enabled) { + setHidden(!enabled || !Settings::Instance().IsBreakpointsVisible()); + }); + + setHidden(!Settings::Instance().IsBreakpointsVisible() || + !Settings::Instance().IsDebugModeEnabled()); + + Update(); +} + +BreakpointWidget::~BreakpointWidget() +{ + QSettings settings; + + settings.setValue(QStringLiteral("breakpointwidget/geometry"), saveGeometry()); + settings.setValue(QStringLiteral("breakpointwidget/floating"), isFloating()); +} + +void BreakpointWidget::CreateWidgets() +{ + m_toolbar = new QToolBar; + m_table = new QTableWidget; + m_table->setColumnCount(5); + m_table->setSelectionMode(QAbstractItemView::SingleSelection); + m_table->setSelectionBehavior(QAbstractItemView::SelectRows); + m_table->verticalHeader()->hide(); + + auto* layout = new QVBoxLayout; + + layout->addWidget(m_toolbar); + layout->addWidget(m_table); + + AddAction(m_toolbar, tr("New"), this, &BreakpointWidget::OnNewBreakpoint); + AddAction(m_toolbar, tr("Delete"), this, &BreakpointWidget::OnDelete); + AddAction(m_toolbar, tr("Clear"), this, &BreakpointWidget::OnClear); + + m_load = AddAction(m_toolbar, tr("Load"), this, &BreakpointWidget::OnLoad); + m_save = AddAction(m_toolbar, tr("Save"), this, &BreakpointWidget::OnSave); + + m_load->setEnabled(false); + m_save->setEnabled(false); + + QWidget* widget = new QWidget; + widget->setLayout(layout); + + setWidget(widget); +} + +void BreakpointWidget::closeEvent(QCloseEvent*) +{ + Settings::Instance().SetBreakpointsVisible(false); +} + +void BreakpointWidget::Update() +{ + m_table->clear(); + + m_table->setHorizontalHeaderLabels( + {tr("Active"), tr("Type"), tr("Function"), tr("Address"), tr("Flags")}); + + int i = 0; + + auto create_item = [this](const QString string = QStringLiteral("")) { + QTableWidgetItem* item = new QTableWidgetItem(string); + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + return item; + }; + + // Breakpoints + for (const auto& bp : PowerPC::breakpoints.GetBreakPoints()) + { + m_table->setRowCount(i + 1); + + auto* active = create_item(bp.is_enabled ? tr("on") : QString()); + + active->setData(Qt::UserRole, bp.address); + + m_table->setItem(i, 0, active); + m_table->setItem(i, 1, create_item(QStringLiteral("BP"))); + + if (g_symbolDB.GetSymbolFromAddr(bp.address)) + { + m_table->setItem(i, 2, + create_item(QString::fromStdString(g_symbolDB.GetDescription(bp.address)))); + } + + m_table->setItem(i, 3, + create_item(QStringLiteral("%1").arg(bp.address, 8, 16, QLatin1Char('0')))); + + m_table->setItem(i, 4, create_item()); + + i++; + } + + // Memory Breakpoints + for (const auto& mbp : PowerPC::memchecks.GetMemChecks()) + { + m_table->setRowCount(i + 1); + m_table->setItem(i, 0, create_item(mbp.break_on_hit || mbp.log_on_hit ? tr("on") : QString())); + m_table->setItem(i, 1, create_item(QStringLiteral("MBP"))); + + if (g_symbolDB.GetSymbolFromAddr(mbp.start_address)) + { + m_table->setItem( + i, 2, create_item(QString::fromStdString(g_symbolDB.GetDescription(mbp.start_address)))); + } + + if (mbp.is_ranged) + { + m_table->setItem(i, 3, create_item(QStringLiteral("%1 - %2") + .arg(mbp.start_address, 8, 16, QLatin1Char('0')) + .arg(mbp.end_address, 8, 16, QLatin1Char('0')))); + } + else + { + m_table->setItem( + i, 3, create_item(QStringLiteral("%1").arg(mbp.start_address, 8, 16, QLatin1Char('0')))); + } + + QString flags; + + if (mbp.is_break_on_read) + flags.append(QStringLiteral("r")); + + if (mbp.is_break_on_write) + flags.append(QStringLiteral("w")); + + m_table->setItem(i, 4, create_item(flags)); + + i++; + } +} + +void BreakpointWidget::OnDelete() +{ + if (m_table->selectedItems().size() == 0) + return; + + auto address = m_table->selectedItems()[0]->data(Qt::UserRole).toUInt(); + + PowerPC::breakpoints.Remove(address); + PowerPC::memchecks.Remove(address); + + Update(); +} + +void BreakpointWidget::OnClear() +{ + PowerPC::debug_interface.ClearAllBreakpoints(); + PowerPC::debug_interface.ClearAllMemChecks(); + + m_table->setRowCount(0); + Update(); +} + +void BreakpointWidget::OnNewBreakpoint() +{ + NewBreakpointDialog* dialog = new NewBreakpointDialog(this); + dialog->exec(); +} + +void BreakpointWidget::OnLoad() +{ + IniFile ini; + BreakPoints::TBreakPointsStr newbps; + MemChecks::TMemChecksStr newmcs; + + if (!ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini", + false)) + { + return; + } + + if (ini.GetLines("BreakPoints", &newbps, false)) + { + PowerPC::breakpoints.Clear(); + PowerPC::breakpoints.AddFromStrings(newbps); + } + + if (ini.GetLines("MemoryBreakPoints", &newmcs, false)) + { + PowerPC::memchecks.Clear(); + PowerPC::memchecks.AddFromStrings(newmcs); + } + + Update(); +} + +void BreakpointWidget::OnSave() +{ + IniFile ini; + ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini", + false); + ini.SetLines("BreakPoints", PowerPC::breakpoints.GetStrings()); + ini.SetLines("MemoryBreakPoints", PowerPC::memchecks.GetStrings()); + ini.Save(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini"); +} + +void BreakpointWidget::AddBP(u32 addr) +{ + PowerPC::breakpoints.Add(addr); + + Update(); +} + +void BreakpointWidget::AddAddressMBP(u32 addr, bool on_read, bool on_write, bool do_log, + bool do_break) +{ + TMemCheck check; + + check.start_address = addr; + 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; + + PowerPC::memchecks.Add(check); + + Update(); +} + +void BreakpointWidget::AddRangedMBP(u32 from, u32 to, bool on_read, bool on_write, bool do_log, + bool do_break) +{ + 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; + + PowerPC::memchecks.Add(check); + + Update(); +} diff --git a/Source/Core/DolphinQt2/Debugger/BreakpointWidget.h b/Source/Core/DolphinQt2/Debugger/BreakpointWidget.h new file mode 100644 index 0000000000..0a2761f0a5 --- /dev/null +++ b/Source/Core/DolphinQt2/Debugger/BreakpointWidget.h @@ -0,0 +1,47 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "Common/CommonTypes.h" + +class QAction; +class QTableWidget; +class QToolBar; +class QCloseEvent; + +class BreakpointWidget : public QDockWidget +{ + Q_OBJECT +public: + explicit BreakpointWidget(QWidget* parent = nullptr); + ~BreakpointWidget(); + + void AddBP(u32 addr); + void AddAddressMBP(u32 addr, bool on_read = true, bool on_write = true, bool do_log = true, + bool do_break = true); + void AddRangedMBP(u32 from, u32 to, bool do_read = true, bool do_write = true, bool do_log = true, + bool do_break = true); + +protected: + void closeEvent(QCloseEvent*) override; + +private: + void CreateWidgets(); + + void OnDelete(); + void OnClear(); + void OnNewBreakpoint(); + void OnLoad(); + void OnSave(); + + void Update(); + + QToolBar* m_toolbar; + QTableWidget* m_table; + QAction* m_load; + QAction* m_save; +}; diff --git a/Source/Core/DolphinQt2/Debugger/NewBreakpointDialog.cpp b/Source/Core/DolphinQt2/Debugger/NewBreakpointDialog.cpp new file mode 100644 index 0000000000..723ea5d6c9 --- /dev/null +++ b/Source/Core/DolphinQt2/Debugger/NewBreakpointDialog.cpp @@ -0,0 +1,182 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt2/Debugger/NewBreakpointDialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DolphinQt2/Debugger/BreakpointWidget.h" + +NewBreakpointDialog::NewBreakpointDialog(BreakpointWidget* parent) + : QDialog(parent), m_parent(parent) +{ + setWindowTitle(tr("New Breakpoint")); + CreateWidgets(); + ConnectWidgets(); + + OnBPTypeChanged(); + OnAddressTypeChanged(); +} + +void NewBreakpointDialog::CreateWidgets() +{ + m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + // Instruction BP + m_instruction_bp = new QRadioButton(tr("Instruction Breakpoint")); + m_instruction_bp->setChecked(true); + m_instruction_box = new QGroupBox; + m_instruction_address = new QLineEdit; + + auto* instruction_layout = new QHBoxLayout; + m_instruction_box->setLayout(instruction_layout); + instruction_layout->addWidget(new QLabel(tr("Address:"))); + instruction_layout->addWidget(m_instruction_address); + + // Memory BP + m_memory_bp = new QRadioButton(tr("Memory Breakpoint")); + m_memory_box = new QGroupBox; + m_memory_use_address = new QRadioButton(tr("Address")); + m_memory_use_address->setChecked(true); + m_memory_use_range = new QRadioButton(tr("Range")); + m_memory_address_from = new QLineEdit; + m_memory_address_to = new QLineEdit; + m_memory_address_from_label = new QLabel; // Set by OnAddressTypeChanged + m_memory_address_to_label = new QLabel(tr("To:")); + m_memory_on_read = new QRadioButton(tr("Read")); + m_memory_on_write = new QRadioButton(tr("Write")); + m_memory_on_read_and_write = new QRadioButton(tr("Read or Write")); + m_memory_on_write->setChecked(true); + m_memory_do_log = new QRadioButton(tr("Log")); + m_memory_do_break = new QRadioButton(tr("Break")); + m_memory_do_log_and_break = new QRadioButton(tr("Log and Break")); + m_memory_do_log_and_break->setChecked(true); + + auto* memory_layout = new QGridLayout; + m_memory_box->setLayout(memory_layout); + memory_layout->addWidget(m_memory_use_address, 0, 0); + memory_layout->addWidget(m_memory_use_range, 0, 3); + memory_layout->addWidget(m_memory_address_from_label, 1, 0); + memory_layout->addWidget(m_memory_address_from, 1, 1); + memory_layout->addWidget(m_memory_address_to_label, 1, 2); + memory_layout->addWidget(m_memory_address_to, 1, 3); + memory_layout->addWidget(new QLabel(tr("On...")), 2, 0); + memory_layout->addWidget(m_memory_on_read, 2, 1); + memory_layout->addWidget(m_memory_on_write, 2, 2); + memory_layout->addWidget(m_memory_on_read_and_write, 2, 3); + memory_layout->addWidget(new QLabel(tr("Do...")), 3, 0); + memory_layout->addWidget(m_memory_do_log, 3, 1); + memory_layout->addWidget(m_memory_do_break, 3, 2); + memory_layout->addWidget(m_memory_do_log_and_break, 3, 3); + + auto* layout = new QVBoxLayout; + + layout->addWidget(m_instruction_bp); + layout->addWidget(m_instruction_box); + layout->addWidget(m_memory_bp); + layout->addWidget(m_memory_box); + layout->addWidget(m_buttons); + + setLayout(layout); +} + +void NewBreakpointDialog::ConnectWidgets() +{ + connect(m_buttons, &QDialogButtonBox::accepted, this, &NewBreakpointDialog::accept); + connect(m_buttons, &QDialogButtonBox::rejected, this, &NewBreakpointDialog::reject); + + connect(m_instruction_bp, &QRadioButton::toggled, this, &NewBreakpointDialog::OnBPTypeChanged); + connect(m_memory_bp, &QRadioButton::toggled, this, &NewBreakpointDialog::OnBPTypeChanged); + + connect(m_memory_use_address, &QRadioButton::toggled, this, + &NewBreakpointDialog::OnAddressTypeChanged); + connect(m_memory_use_range, &QRadioButton::toggled, this, + &NewBreakpointDialog::OnAddressTypeChanged); +} + +void NewBreakpointDialog::OnBPTypeChanged() +{ + m_instruction_box->setEnabled(m_instruction_bp->isChecked()); + m_memory_box->setEnabled(m_memory_bp->isChecked()); +} + +void NewBreakpointDialog::OnAddressTypeChanged() +{ + bool ranged = m_memory_use_range->isChecked(); + + m_memory_address_to->setHidden(!ranged); + m_memory_address_to_label->setHidden(!ranged); + + m_memory_address_from_label->setText(ranged ? tr("From:") : tr("Address:")); +} + +void NewBreakpointDialog::accept() +{ + auto invalid_input = [this](QString field) { + QMessageBox::critical(this, tr("Error"), tr("Bad input provided for %1 field").arg(field)); + }; + + bool instruction = m_instruction_bp->isChecked(); + bool ranged = m_memory_use_range->isChecked(); + + // Triggers + bool on_read = m_memory_on_read->isChecked() || m_memory_on_read_and_write->isChecked(); + bool on_write = m_memory_on_write->isChecked() || m_memory_on_read_and_write->isChecked(); + + // Actions + bool do_log = m_memory_do_log->isChecked() || m_memory_do_log_and_break->isChecked(); + bool do_break = m_memory_do_break->isChecked() || m_memory_do_log_and_break->isChecked(); + + bool good; + + if (instruction) + { + u32 address = m_instruction_address->text().toUInt(&good, 16); + + if (!good) + { + invalid_input(tr("address")); + return; + } + + m_parent->AddBP(address); + } + else + { + u32 from = m_memory_address_from->text().toUInt(&good, 16); + + if (!good) + { + invalid_input(ranged ? tr("from") : tr("address")); + return; + } + + if (ranged) + { + u32 to = m_memory_address_to->text().toUInt(&good, 16); + if (!good) + { + invalid_input(tr("to")); + return; + } + + m_parent->AddRangedMBP(from, to, on_read, on_write, do_log, do_break); + } + else + { + m_parent->AddAddressMBP(from, on_read, on_write, do_log, do_break); + } + } + + QDialog::accept(); +} diff --git a/Source/Core/DolphinQt2/Debugger/NewBreakpointDialog.h b/Source/Core/DolphinQt2/Debugger/NewBreakpointDialog.h new file mode 100644 index 0000000000..7dd1d34837 --- /dev/null +++ b/Source/Core/DolphinQt2/Debugger/NewBreakpointDialog.h @@ -0,0 +1,57 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "Common/CommonTypes.h" + +class BreakpointWidget; +class QCheckBox; +class QDialogButtonBox; +class QGroupBox; +class QLabel; +class QLineEdit; +class QRadioButton; + +class NewBreakpointDialog : public QDialog +{ + Q_OBJECT +public: + explicit NewBreakpointDialog(BreakpointWidget* parent); + + void accept(); + +private: + void CreateWidgets(); + void ConnectWidgets(); + + void OnBPTypeChanged(); + void OnAddressTypeChanged(); + + // Instruction BPs + QRadioButton* m_instruction_bp; + QGroupBox* m_instruction_box; + QLineEdit* m_instruction_address; + + // Memory BPs + QRadioButton* m_memory_bp; + QRadioButton* m_memory_use_address; + QRadioButton* m_memory_use_range; + QGroupBox* m_memory_box; + QLabel* m_memory_address_from_label; + QLineEdit* m_memory_address_from; + QLabel* m_memory_address_to_label; + QLineEdit* m_memory_address_to; + QRadioButton* m_memory_on_read; + QRadioButton* m_memory_on_read_and_write; + QRadioButton* m_memory_on_write; + QRadioButton* m_memory_do_log; + QRadioButton* m_memory_do_break; + QRadioButton* m_memory_do_log_and_break; + + QDialogButtonBox* m_buttons; + BreakpointWidget* m_parent; +}; diff --git a/Source/Core/DolphinQt2/Debugger/WatchWidget.cpp b/Source/Core/DolphinQt2/Debugger/WatchWidget.cpp index 957388be8d..efdb94d466 100644 --- a/Source/Core/DolphinQt2/Debugger/WatchWidget.cpp +++ b/Source/Core/DolphinQt2/Debugger/WatchWidget.cpp @@ -173,6 +173,7 @@ void WatchWidget::closeEvent(QCloseEvent*) { Settings::Instance().SetWatchVisible(false); } + void WatchWidget::OnLoad() { IniFile ini; diff --git a/Source/Core/DolphinQt2/DolphinQt2.vcxproj b/Source/Core/DolphinQt2/DolphinQt2.vcxproj index d43f142cae..800825824b 100644 --- a/Source/Core/DolphinQt2/DolphinQt2.vcxproj +++ b/Source/Core/DolphinQt2/DolphinQt2.vcxproj @@ -84,6 +84,8 @@ + + @@ -118,6 +120,7 @@ + @@ -155,6 +158,7 @@ + @@ -205,6 +209,8 @@ + + diff --git a/Source/Core/DolphinQt2/MainWindow.cpp b/Source/Core/DolphinQt2/MainWindow.cpp index 4d460f9735..6e20544770 100644 --- a/Source/Core/DolphinQt2/MainWindow.cpp +++ b/Source/Core/DolphinQt2/MainWindow.cpp @@ -46,6 +46,8 @@ #include "DolphinQt2/Config/LogWidget.h" #include "DolphinQt2/Config/Mapping/MappingWindow.h" #include "DolphinQt2/Config/SettingsWindow.h" +#include "DolphinQt2/FIFOPlayerWindow.h" +#include "DolphinQt2/Debugger/BreakpointWidget.h" #include "DolphinQt2/Debugger/RegisterWidget.h" #include "DolphinQt2/Debugger/WatchWidget.h" #include "DolphinQt2/Host.h" @@ -171,6 +173,7 @@ void MainWindow::CreateComponents() [this](const QString& path) { StartGame(path); }); m_register_widget = new RegisterWidget(this); m_watch_widget = new WatchWidget(this); + m_breakpoint_widget = new BreakpointWidget(this); #if defined(HAVE_XRANDR) && HAVE_XRANDR m_graphics_window = new GraphicsWindow( @@ -321,10 +324,12 @@ void MainWindow::ConnectStack() addDockWidget(Qt::RightDockWidgetArea, m_log_config_widget); addDockWidget(Qt::RightDockWidgetArea, m_register_widget); addDockWidget(Qt::RightDockWidgetArea, m_watch_widget); + addDockWidget(Qt::RightDockWidgetArea, m_breakpoint_widget); tabifyDockWidget(m_log_widget, m_log_config_widget); tabifyDockWidget(m_log_widget, m_register_widget); tabifyDockWidget(m_log_widget, m_watch_widget); + tabifyDockWidget(m_log_widget, m_breakpoint_widget); } void MainWindow::Open() diff --git a/Source/Core/DolphinQt2/MainWindow.h b/Source/Core/DolphinQt2/MainWindow.h index 8cbf8108b9..6584f4e2d1 100644 --- a/Source/Core/DolphinQt2/MainWindow.h +++ b/Source/Core/DolphinQt2/MainWindow.h @@ -17,6 +17,7 @@ #include "DolphinQt2/RenderWidget.h" #include "DolphinQt2/ToolBar.h" +class BreakpointWidget; struct BootParameters; class FIFOPlayerWindow; class HotkeyScheduler; @@ -141,6 +142,7 @@ private: NetPlaySetupDialog* m_netplay_setup_dialog; GraphicsWindow* m_graphics_window; + BreakpointWidget* m_breakpoint_widget; LogWidget* m_log_widget; LogConfigWidget* m_log_config_widget; FIFOPlayerWindow* m_fifo_window; diff --git a/Source/Core/DolphinQt2/MenuBar.cpp b/Source/Core/DolphinQt2/MenuBar.cpp index 0ef13e32e6..326d35a1b4 100644 --- a/Source/Core/DolphinQt2/MenuBar.cpp +++ b/Source/Core/DolphinQt2/MenuBar.cpp @@ -92,6 +92,7 @@ void MenuBar::OnDebugModeToggled(bool enabled) { m_show_registers->setVisible(enabled); m_show_watch->setVisible(enabled); + m_show_breakpoints->setVisible(enabled); } void MenuBar::AddFileMenu() @@ -281,6 +282,15 @@ void MenuBar::AddViewMenu() connect(&Settings::Instance(), &Settings::WatchVisibilityChanged, m_show_watch, &QAction::setChecked); + m_show_breakpoints = view_menu->addAction(tr("&Breakpoints")); + m_show_breakpoints->setCheckable(true); + m_show_breakpoints->setChecked(Settings::Instance().IsBreakpointsVisible()); + + connect(m_show_breakpoints, &QAction::toggled, &Settings::Instance(), + &Settings::SetBreakpointsVisible); + connect(&Settings::Instance(), &Settings::BreakpointsVisibilityChanged, m_show_breakpoints, + &QAction::setChecked); + view_menu->addSeparator(); AddGameListTypeSection(view_menu); diff --git a/Source/Core/DolphinQt2/MenuBar.h b/Source/Core/DolphinQt2/MenuBar.h index 385c8c3ca4..ef73d8623f 100644 --- a/Source/Core/DolphinQt2/MenuBar.h +++ b/Source/Core/DolphinQt2/MenuBar.h @@ -162,4 +162,5 @@ private: // View QAction* m_show_registers; QAction* m_show_watch; + QAction* m_show_breakpoints; }; diff --git a/Source/Core/DolphinQt2/Settings.cpp b/Source/Core/DolphinQt2/Settings.cpp index d737f0893b..433dd3ab95 100644 --- a/Source/Core/DolphinQt2/Settings.cpp +++ b/Source/Core/DolphinQt2/Settings.cpp @@ -240,3 +240,18 @@ bool Settings::IsWatchVisible() const { return QSettings().value(QStringLiteral("debugger/showwatch")).toBool(); } + +void Settings::SetBreakpointsVisible(bool enabled) +{ + if (IsBreakpointsVisible() != enabled) + { + QSettings().setValue(QStringLiteral("debugger/showbreakpoints"), enabled); + + emit BreakpointsVisibilityChanged(enabled); + } +} + +bool Settings::IsBreakpointsVisible() const +{ + return QSettings().value(QStringLiteral("debugger/showbreakpoints")).toBool(); +} diff --git a/Source/Core/DolphinQt2/Settings.h b/Source/Core/DolphinQt2/Settings.h index a654c21222..7ae4340f7b 100644 --- a/Source/Core/DolphinQt2/Settings.h +++ b/Source/Core/DolphinQt2/Settings.h @@ -85,6 +85,8 @@ public: bool IsRegistersVisible() const; void SetWatchVisible(bool enabled); bool IsWatchVisible() const; + void SetBreakpointsVisible(bool enabled); + bool IsBreakpointsVisible() const; // Other GameListModel* GetGameListModel() const; @@ -102,6 +104,7 @@ signals: void LogConfigVisibilityChanged(bool visible); void EnableCheatsChanged(bool enabled); void WatchVisibilityChanged(bool visible); + void BreakpointsVisibilityChanged(bool visible); void DebugModeToggled(bool enabled); private: From b63ec57c1e5ace830ab67f0d4779b782d08b7b21 Mon Sep 17 00:00:00 2001 From: spycrab Date: Sun, 19 Nov 2017 18:50:38 +0100 Subject: [PATCH 6/6] Qt: Connect debugging widgets --- Source/Core/DolphinQt2/Debugger/RegisterWidget.cpp | 3 ++- Source/Core/DolphinQt2/Debugger/RegisterWidget.h | 1 + Source/Core/DolphinQt2/MainWindow.cpp | 8 +++++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Source/Core/DolphinQt2/Debugger/RegisterWidget.cpp b/Source/Core/DolphinQt2/Debugger/RegisterWidget.cpp index e9817b84ff..08e5ba3168 100644 --- a/Source/Core/DolphinQt2/Debugger/RegisterWidget.cpp +++ b/Source/Core/DolphinQt2/Debugger/RegisterWidget.cpp @@ -117,7 +117,8 @@ void RegisterWidget::ShowContextMenu() auto type = static_cast(item->data(DATA_TYPE).toInt()); auto display = item->GetDisplay(); - menu->addAction(tr("Add to &watch")); + AddAction(menu, tr("Add to &watch"), this, + [this, item] { emit RequestMemoryBreakpoint(item->GetValue()); }); menu->addAction(tr("View &memory")); menu->addAction(tr("View &code")); diff --git a/Source/Core/DolphinQt2/Debugger/RegisterWidget.h b/Source/Core/DolphinQt2/Debugger/RegisterWidget.h index 3793e12e6e..f5c1a4c1a1 100644 --- a/Source/Core/DolphinQt2/Debugger/RegisterWidget.h +++ b/Source/Core/DolphinQt2/Debugger/RegisterWidget.h @@ -23,6 +23,7 @@ public: signals: void RequestTableUpdate(); + void RequestMemoryBreakpoint(u32 addr); void UpdateTable(); void UpdateValue(QTableWidgetItem* item); void UpdateValueType(QTableWidgetItem* item); diff --git a/Source/Core/DolphinQt2/MainWindow.cpp b/Source/Core/DolphinQt2/MainWindow.cpp index 6e20544770..a425d8891e 100644 --- a/Source/Core/DolphinQt2/MainWindow.cpp +++ b/Source/Core/DolphinQt2/MainWindow.cpp @@ -1,3 +1,4 @@ + // Copyright 2015 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. @@ -46,10 +47,10 @@ #include "DolphinQt2/Config/LogWidget.h" #include "DolphinQt2/Config/Mapping/MappingWindow.h" #include "DolphinQt2/Config/SettingsWindow.h" -#include "DolphinQt2/FIFOPlayerWindow.h" #include "DolphinQt2/Debugger/BreakpointWidget.h" #include "DolphinQt2/Debugger/RegisterWidget.h" #include "DolphinQt2/Debugger/WatchWidget.h" +#include "DolphinQt2/FIFOPlayerWindow.h" #include "DolphinQt2/Host.h" #include "DolphinQt2/HotkeyScheduler.h" #include "DolphinQt2/MainWindow.h" @@ -175,6 +176,11 @@ void MainWindow::CreateComponents() m_watch_widget = new WatchWidget(this); m_breakpoint_widget = new BreakpointWidget(this); + connect(m_watch_widget, &WatchWidget::RequestMemoryBreakpoint, + [this](u32 addr) { m_breakpoint_widget->AddAddressMBP(addr); }); + connect(m_register_widget, &RegisterWidget::RequestMemoryBreakpoint, + [this](u32 addr) { m_breakpoint_widget->AddAddressMBP(addr); }); + #if defined(HAVE_XRANDR) && HAVE_XRANDR m_graphics_window = new GraphicsWindow( new X11Utils::XRRConfiguration(