diff --git a/Source/Core/Common/Debug/MemoryPatches.cpp b/Source/Core/Common/Debug/MemoryPatches.cpp index cbe7258bed68..ed96b20f2f19 100644 --- a/Source/Core/Common/Debug/MemoryPatches.cpp +++ b/Source/Core/Common/Debug/MemoryPatches.cpp @@ -4,19 +4,21 @@ #include "Common/Debug/MemoryPatches.h" #include +#include +#include #include #include namespace Common::Debug { -MemoryPatch::MemoryPatch(u32 address_, std::vector value_) - : address(address_), value(std::move(value_)) +MemoryPatch::MemoryPatch(u32 address_, std::vector value) + : address(address_), patch_value(std::move(value)), original_value() { } -MemoryPatch::MemoryPatch(u32 address_, u32 value_) - : MemoryPatch(address_, {static_cast(value_ >> 24), static_cast(value_ >> 16), - static_cast(value_ >> 8), static_cast(value_)}) +MemoryPatch::MemoryPatch(u32 address_, u32 value) + : MemoryPatch(address_, {static_cast(value >> 24), static_cast(value >> 16), + static_cast(value >> 8), static_cast(value)}) { } @@ -37,6 +39,11 @@ void MemoryPatches::SetPatch(u32 address, std::vector value) Patch(index); } +const MemoryPatch& MemoryPatches::GetPatch(std::size_t index) const +{ + return m_patches.at(index); +} + const std::vector& MemoryPatches::GetPatches() const { return m_patches; @@ -74,6 +81,7 @@ void MemoryPatches::DisablePatch(std::size_t index) return; m_patches[index].is_enabled = MemoryPatch::State::Disabled; Patch(index); + m_patches[index].original_value.clear(); } bool MemoryPatches::HasEnabledPatch(u32 address) const @@ -89,6 +97,87 @@ void MemoryPatches::RemovePatch(std::size_t index) m_patches.erase(m_patches.begin() + index); } +void MemoryPatches::LoadFromStrings(const std::vector& patches) +{ + for (const std::string& patch : patches) + { + std::stringstream ss; + ss.imbue(std::locale::classic()); + + // Get the patch state (on, off) + std::string state; + ss << patch; + ss >> state; + const bool is_enabled = state == "on"; + if (!ss) + continue; + + // Get the patch address + u32 address; + ss >> std::hex >> address; + ss >> std::ws; + if (!ss) + continue; + + // Get the patch value + std::string hexstring; + ss >> hexstring; + if (!ss) + continue; + + // Check the end of line + std::string is_not_eol; + if (ss >> std::ws >> is_not_eol) + continue; + + const bool is_hex_valid = + hexstring.find_first_not_of("0123456789abcdefABCDEF") == hexstring.npos && + (hexstring.size() % 2) == 0; + if (!is_hex_valid) + continue; + + // Convert the patch value to bytes + std::vector value; + const std::size_t len = hexstring.length(); + for (std::size_t i = 0; i < len; i += 2) + { + std::size_t size; + u32 hex = std::stoi(hexstring.substr(i, 2), &size, 16); + if (size != 2) + { + value.clear(); + break; + } + value.push_back(static_cast(hex)); + } + + if (value.empty()) + continue; + + const std::size_t index = m_patches.size(); + SetPatch(address, value); + if (!is_enabled) + DisablePatch(index); + } +} + +std::vector MemoryPatches::SaveToStrings() const +{ + std::vector patches; + for (const auto& patch : m_patches) + { + std::ostringstream oss; + oss.imbue(std::locale::classic()); + const bool is_enabled = patch.is_enabled == MemoryPatch::State::Enabled; + oss << std::hex << std::setfill('0') << (is_enabled ? "on" : "off") << " "; + oss << std::setw(8) << patch.address << " "; + for (u8 b : patch.patch_value) + oss << std::setw(2) << static_cast(b); + patches.push_back(oss.str()); + } + return patches; +} + void MemoryPatches::ClearPatches() { const std::size_t size = m_patches.size(); diff --git a/Source/Core/Common/Debug/MemoryPatches.h b/Source/Core/Common/Debug/MemoryPatches.h index abf88b04995f..bfd83c866a30 100644 --- a/Source/Core/Common/Debug/MemoryPatches.h +++ b/Source/Core/Common/Debug/MemoryPatches.h @@ -23,7 +23,8 @@ struct MemoryPatch MemoryPatch(u32 address_, u32 value_); u32 address; - std::vector value; + std::vector patch_value; + std::vector original_value; State is_enabled = State::Enabled; }; @@ -35,12 +36,15 @@ class MemoryPatches void SetPatch(u32 address, u32 value); void SetPatch(u32 address, std::vector value); + const MemoryPatch& GetPatch(std::size_t index) const; const std::vector& GetPatches() const; void UnsetPatch(u32 address); void EnablePatch(std::size_t index); void DisablePatch(std::size_t index); bool HasEnabledPatch(u32 address) const; void RemovePatch(std::size_t index); + void LoadFromStrings(const std::vector& patches); + std::vector SaveToStrings() const; void ClearPatches(); protected: diff --git a/Source/Core/Common/DebugInterface.h b/Source/Core/Common/DebugInterface.h index a59ac79f69ea..6f464e219170 100644 --- a/Source/Core/Common/DebugInterface.h +++ b/Source/Core/Common/DebugInterface.h @@ -43,12 +43,15 @@ class DebugInterface // Memory Patches virtual void SetPatch(u32 address, u32 value) = 0; virtual void SetPatch(u32 address, std::vector value) = 0; + virtual const Debug::MemoryPatch& GetPatch(std::size_t index) const = 0; virtual const std::vector& GetPatches() const = 0; virtual void UnsetPatch(u32 address) = 0; virtual void EnablePatch(std::size_t index) = 0; virtual void DisablePatch(std::size_t index) = 0; virtual bool HasEnabledPatch(u32 address) const = 0; virtual void RemovePatch(std::size_t index) = 0; + virtual void LoadPatchesFromStrings(const std::vector& watches) = 0; + virtual std::vector SavePatchesToStrings() const = 0; virtual void ClearPatches() = 0; // Threads diff --git a/Source/Core/Core/Debugger/PPCDebugInterface.cpp b/Source/Core/Core/Debugger/PPCDebugInterface.cpp index 03d8331a11d7..b9a846edc95f 100644 --- a/Source/Core/Core/Debugger/PPCDebugInterface.cpp +++ b/Source/Core/Core/Debugger/PPCDebugInterface.cpp @@ -26,19 +26,27 @@ void PPCPatches::Patch(std::size_t index) { auto& patch = m_patches[index]; - if (patch.value.empty()) + if (patch.patch_value.empty()) return; const u32 address = patch.address; - const std::size_t size = patch.value.size(); + const std::size_t size = patch.patch_value.size(); if (!PowerPC::HostIsRAMAddress(address)) return; + const bool is_enabled = patch.is_enabled == Common::Debug::MemoryPatch::State::Enabled; for (u32 offset = 0; offset < size; ++offset) { - const u8 value = PowerPC::HostRead_U8(address + offset); - PowerPC::HostWrite_U8(patch.value[offset], address + offset); - patch.value[offset] = value; + if (is_enabled) + { + const u8 original_value = PowerPC::HostRead_U8(address + offset); + PowerPC::HostWrite_U8(patch.patch_value[offset], address + offset); + patch.original_value.push_back(original_value); + } + else + { + PowerPC::HostWrite_U8(patch.original_value[offset], address + offset); + } if (((address + offset) % 4) == 3) PowerPC::ScheduleInvalidateCacheThreadSafe(Common::AlignDown(address + offset, 4)); @@ -133,6 +141,11 @@ void PPCDebugInterface::SetPatch(u32 address, std::vector value) m_patches.SetPatch(address, std::move(value)); } +const Common::Debug::MemoryPatch& PPCDebugInterface::GetPatch(std::size_t index) const +{ + return m_patches.GetPatch(index); +} + const std::vector& PPCDebugInterface::GetPatches() const { return m_patches.GetPatches(); @@ -163,6 +176,16 @@ void PPCDebugInterface::RemovePatch(std::size_t index) m_patches.RemovePatch(index); } +void PPCDebugInterface::LoadPatchesFromStrings(const std::vector& patches) +{ + m_patches.LoadFromStrings(patches); +} + +std::vector PPCDebugInterface::SavePatchesToStrings() const +{ + return m_patches.SaveToStrings(); +} + void PPCDebugInterface::ClearPatches() { m_patches.ClearPatches(); diff --git a/Source/Core/Core/Debugger/PPCDebugInterface.h b/Source/Core/Core/Debugger/PPCDebugInterface.h index 4c5dfb0feccb..35a23c7fde2d 100644 --- a/Source/Core/Core/Debugger/PPCDebugInterface.h +++ b/Source/Core/Core/Debugger/PPCDebugInterface.h @@ -45,12 +45,15 @@ class PPCDebugInterface final : public Common::DebugInterface // Memory Patches void SetPatch(u32 address, u32 value) override; void SetPatch(u32 address, std::vector value) override; + const Common::Debug::MemoryPatch& GetPatch(std::size_t index) const override; const std::vector& GetPatches() const override; void UnsetPatch(u32 address) override; void EnablePatch(std::size_t index) override; void DisablePatch(std::size_t index) override; bool HasEnabledPatch(u32 address) const override; void RemovePatch(std::size_t index) override; + void LoadPatchesFromStrings(const std::vector& patches) override; + std::vector SavePatchesToStrings() const override; void ClearPatches() override; // Threads diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index e08af734cc31..78f6c5e278ae 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -216,6 +216,8 @@ add_executable(dolphin-emu Debugger/CodeWidget.h Debugger/JITWidget.cpp Debugger/JITWidget.h + Debugger/MemoryPatchWidget.cpp + Debugger/MemoryPatchWidget.h Debugger/MemoryViewWidget.cpp Debugger/MemoryViewWidget.h Debugger/MemoryWidget.cpp diff --git a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp index 76e43d50ad72..22897c23ff26 100644 --- a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp @@ -513,6 +513,8 @@ void CodeViewWidget::ReplaceAddress(u32 address, ReplaceWith replace) { PowerPC::debug_interface.UnsetPatch(address); PowerPC::debug_interface.SetPatch(address, replace == ReplaceWith::BLR ? 0x4e800020 : 0x60000000); + + emit MemoryPatchesChanged(); Update(); } @@ -823,6 +825,8 @@ void CodeViewWidget::OnReplaceInstruction() { PowerPC::debug_interface.UnsetPatch(addr); PowerPC::debug_interface.SetPatch(addr, dialog.GetCode()); + + emit MemoryPatchesChanged(); Update(); } } @@ -832,6 +836,8 @@ void CodeViewWidget::OnRestoreInstruction() const u32 addr = GetContextAddress(); PowerPC::debug_interface.UnsetPatch(addr); + + emit MemoryPatchesChanged(); Update(); } diff --git a/Source/Core/DolphinQt/Debugger/CodeViewWidget.h b/Source/Core/DolphinQt/Debugger/CodeViewWidget.h index c962971cb8a4..ed74ef68c408 100644 --- a/Source/Core/DolphinQt/Debugger/CodeViewWidget.h +++ b/Source/Core/DolphinQt/Debugger/CodeViewWidget.h @@ -49,6 +49,7 @@ class CodeViewWidget : public QTableWidget void ShowMemory(u32 address); void SymbolsChanged(); void BreakpointsChanged(); + void MemoryPatchesChanged(); void UpdateCodeWidget(); private: diff --git a/Source/Core/DolphinQt/Debugger/CodeWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeWidget.cpp index 3be5a75776b7..436cddd914b7 100644 --- a/Source/Core/DolphinQt/Debugger/CodeWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/CodeWidget.cpp @@ -173,6 +173,8 @@ void CodeWidget::ConnectWidgets() connect(m_code_view, &CodeViewWidget::SymbolsChanged, this, &CodeWidget::UpdateSymbols); connect(m_code_view, &CodeViewWidget::BreakpointsChanged, this, [this] { emit BreakpointsChanged(); }); + connect(m_code_view, &CodeViewWidget::MemoryPatchesChanged, this, + [this] { emit MemoryPatchesChanged(); }); connect(m_code_view, &CodeViewWidget::UpdateCodeWidget, this, &CodeWidget::Update); connect(m_code_view, &CodeViewWidget::RequestPPCComparison, this, diff --git a/Source/Core/DolphinQt/Debugger/CodeWidget.h b/Source/Core/DolphinQt/Debugger/CodeWidget.h index fcf67d89a657..2f1b3169f21a 100644 --- a/Source/Core/DolphinQt/Debugger/CodeWidget.h +++ b/Source/Core/DolphinQt/Debugger/CodeWidget.h @@ -46,6 +46,7 @@ class CodeWidget : public QDockWidget void UpdateSymbols(); signals: void BreakpointsChanged(); + void MemoryPatchesChanged(); void RequestPPCComparison(u32 addr); void ShowMemory(u32 address); diff --git a/Source/Core/DolphinQt/Debugger/MemoryPatchWidget.cpp b/Source/Core/DolphinQt/Debugger/MemoryPatchWidget.cpp new file mode 100644 index 000000000000..f3c1ba55e4d4 --- /dev/null +++ b/Source/Core/DolphinQt/Debugger/MemoryPatchWidget.cpp @@ -0,0 +1,273 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt/Debugger/MemoryPatchWidget.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include "Common/FileUtil.h" +#include "Core/ConfigManager.h" +#include "Core/Core.h" +#include "Core/PowerPC/PowerPC.h" + +#include "DolphinQt/QtUtils/ModalMessageBox.h" +#include "DolphinQt/Resources.h" +#include "DolphinQt/Settings.h" + +MemoryPatchWidget::MemoryPatchWidget(QWidget* parent) : QDockWidget(parent) +{ + setWindowTitle(tr("Patches")); + setObjectName(QStringLiteral("patches")); + + setAllowedAreas(Qt::AllDockWidgetAreas); + + auto& settings = Settings::GetQSettings(); + + restoreGeometry(settings.value(QStringLiteral("memorypatchwidget/geometry")).toByteArray()); + setFloating(settings.value(QStringLiteral("memorypatchwidget/floating")).toBool()); + + CreateWidgets(); + + connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this](Core::State state) { + if (!Settings::Instance().IsDebugModeEnabled()) + return; + + UpdateButtonsEnabled(); + + if (state == Core::State::Uninitialized) + { + PowerPC::debug_interface.ClearPatches(); + Update(); + } + }); + + connect(&Settings::Instance(), &Settings::MemoryPatchesVisibilityChanged, this, + [this](bool visible) { setHidden(!visible); }); + + connect(&Settings::Instance(), &Settings::DebugModeToggled, this, [this](bool enabled) { + setHidden(!enabled || !Settings::Instance().IsMemoryPatchesVisible()); + }); + + connect(&Settings::Instance(), &Settings::ThemeChanged, this, &MemoryPatchWidget::UpdateIcons); + UpdateIcons(); + + setHidden(!Settings::Instance().IsMemoryPatchesVisible() || + !Settings::Instance().IsDebugModeEnabled()); + + Update(); +} + +MemoryPatchWidget::~MemoryPatchWidget() +{ + auto& settings = Settings::GetQSettings(); + + settings.setValue(QStringLiteral("memorypatchwidget/geometry"), saveGeometry()); + settings.setValue(QStringLiteral("memorypatchwidget/floating"), isFloating()); +} + +void MemoryPatchWidget::CreateWidgets() +{ + m_toolbar = new QToolBar; + m_toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + + m_table = new QTableWidget; + m_table->setSelectionMode(QAbstractItemView::SingleSelection); + m_table->setSelectionBehavior(QAbstractItemView::SelectRows); + m_table->setEditTriggers(QAbstractItemView::NoEditTriggers); + + const QStringList header{{tr("Active"), tr("Address"), tr("Patch value"), tr("Original value")}}; + m_table->setColumnCount(header.size()); + m_table->setHorizontalHeaderLabels(header); + m_table->verticalHeader()->hide(); + + auto* layout = new QVBoxLayout; + + layout->addWidget(m_toolbar); + layout->addWidget(m_table); + + m_toggle_on_off = m_toolbar->addAction(tr("On/Off"), this, &MemoryPatchWidget::OnToggleOnOff); + m_delete = m_toolbar->addAction(tr("Delete"), this, &MemoryPatchWidget::OnDelete); + m_clear = m_toolbar->addAction(tr("Clear"), this, &MemoryPatchWidget::OnClear); + m_load = m_toolbar->addAction(tr("Load from file"), this, &MemoryPatchWidget::OnLoadFromFile); + m_save = m_toolbar->addAction(tr("Save to file"), this, &MemoryPatchWidget::OnSaveToFile); + + QWidget* widget = new QWidget; + widget->setLayout(layout); + + setWidget(widget); +} + +void MemoryPatchWidget::UpdateIcons() +{ + // TODO: Create a "debugger_toggle_on_off" icon + m_toggle_on_off->setIcon(Resources::GetScaledThemeIcon("debugger_breakpoint")); + m_delete->setIcon(Resources::GetScaledThemeIcon("debugger_delete")); + m_clear->setIcon(Resources::GetScaledThemeIcon("debugger_clear")); + m_load->setIcon(Resources::GetScaledThemeIcon("debugger_load")); + m_save->setIcon(Resources::GetScaledThemeIcon("debugger_save")); +} + +void MemoryPatchWidget::UpdateButtonsEnabled() +{ + if (!isVisible()) + return; + + const bool is_enabled = Core::IsRunning(); + m_toggle_on_off->setEnabled(is_enabled); + m_delete->setEnabled(is_enabled); + m_clear->setEnabled(is_enabled); + m_load->setEnabled(is_enabled); + m_save->setEnabled(is_enabled); +} + +void MemoryPatchWidget::closeEvent(QCloseEvent*) +{ + Settings::Instance().SetMemoryPatchesVisible(false); +} + +void MemoryPatchWidget::showEvent(QShowEvent* event) +{ + UpdateButtonsEnabled(); +} + +void MemoryPatchWidget::Update() +{ + m_table->clearContents(); + + int patch_id = 0; + m_table->setRowCount(patch_id); + + auto create_item = [](const QString& string = QString()) { + QTableWidgetItem* item = new QTableWidgetItem(string); + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + return item; + }; + + for (const auto& patch : PowerPC::debug_interface.GetPatches()) + { + m_table->setRowCount(patch_id + 1); + + const QString is_enabled = + (patch.is_enabled == Common::Debug::MemoryPatch::State::Enabled) ? tr("on") : tr("off"); + auto* active = create_item(is_enabled); + active->setData(Qt::UserRole, patch_id); + m_table->setItem(patch_id, 0, active); + + const QString address = QStringLiteral("%1").arg(patch.address, 8, 16, QLatin1Char('0')); + m_table->setItem(patch_id, 1, create_item(address)); + + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + for (u8 b : patch.patch_value) + { + oss << std::setw(2) << static_cast(b); + } + m_table->setItem(patch_id, 2, create_item(QString::fromStdString(oss.str()))); + + oss.str(""); + for (u8 b : patch.original_value) + { + oss << std::setw(2) << static_cast(b); + } + m_table->setItem(patch_id, 3, create_item(QString::fromStdString(oss.str()))); + + patch_id += 1; + } +} + +void MemoryPatchWidget::OnDelete() +{ + if (m_table->selectedItems().empty()) + return; + + const auto patch_id = m_table->selectedItems()[0]->data(Qt::UserRole).toUInt(); + + PowerPC::debug_interface.RemovePatch(patch_id); + + emit MemoryPatchesChanged(); + Update(); +} + +void MemoryPatchWidget::OnClear() +{ + PowerPC::debug_interface.ClearPatches(); + + m_table->setRowCount(0); + + emit MemoryPatchesChanged(); + Update(); +} + +void MemoryPatchWidget::OnToggleOnOff() +{ + if (m_table->selectedItems().empty()) + return; + + const auto patch_id = m_table->selectedItems()[0]->data(Qt::UserRole).toUInt(); + const auto& patch = PowerPC::debug_interface.GetPatch(patch_id); + if (patch.is_enabled == Common::Debug::MemoryPatch::State::Enabled) + PowerPC::debug_interface.DisablePatch(patch_id); + else + PowerPC::debug_interface.EnablePatch(patch_id); + + emit MemoryPatchesChanged(); + Update(); +} + +void MemoryPatchWidget::OnLoadFromFile() +{ + const QString path = QFileDialog::getOpenFileName(this, tr("Load patch file"), QString(), + tr("Dolphin Patch File (*.patch)")); + + if (path.isEmpty()) + return; + + std::ifstream ifs; + File::OpenFStream(ifs, path.toStdString(), std::ios_base::in); + if (!ifs) + { + ModalMessageBox::warning(this, tr("Error"), tr("Failed to load patch file '%1'").arg(path)); + return; + } + + std::string line; + std::vector patches; + while (std::getline(ifs, line)) + patches.push_back(line); + ifs.close(); + + PowerPC::debug_interface.ClearPatches(); + PowerPC::debug_interface.LoadPatchesFromStrings(patches); + + emit MemoryPatchesChanged(); + Update(); +} + +void MemoryPatchWidget::OnSaveToFile() +{ + const QString path = QFileDialog::getSaveFileName(this, tr("Save patch file"), QString(), + tr("Dolphin Patch File (*.patch)")); + + if (path.isEmpty()) + return; + + std::ofstream out; + File::OpenFStream(out, path.toStdString(), std::ios::out); + if (!out) + { + ModalMessageBox::warning(this, tr("Error"), + tr("Failed to save patch file to path '%1'").arg(path)); + } + + for (auto& patch : PowerPC::debug_interface.SavePatchesToStrings()) + out << patch << std::endl; + out.close(); +} diff --git a/Source/Core/DolphinQt/Debugger/MemoryPatchWidget.h b/Source/Core/DolphinQt/Debugger/MemoryPatchWidget.h new file mode 100644 index 000000000000..1c4e4388d033 --- /dev/null +++ b/Source/Core/DolphinQt/Debugger/MemoryPatchWidget.h @@ -0,0 +1,49 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +class QAction; +class QTableWidget; +class QToolBar; +class QCloseEvent; + +class MemoryPatchWidget : public QDockWidget +{ + Q_OBJECT +public: + explicit MemoryPatchWidget(QWidget* parent = nullptr); + ~MemoryPatchWidget(); + + void Update(); + +signals: + void MemoryPatchesChanged(); + +protected: + void closeEvent(QCloseEvent*) override; + void showEvent(QShowEvent* event) override; + +private: + void CreateWidgets(); + + void OnDelete(); + void OnClear(); + void OnToggleOnOff(); + void OnLoadFromFile(); + void OnSaveToFile(); + + void UpdateIcons(); + void UpdateButtonsEnabled(); + + QToolBar* m_toolbar; + QTableWidget* m_table; + QAction* m_delete; + QAction* m_clear; + QAction* m_toggle_on_off; + QAction* m_load; + QAction* m_save; +}; diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index 4763f2af5d46..b1997ce8cd4b 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -123,6 +123,7 @@ + @@ -310,6 +311,7 @@ + diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 3be8f9d6a0d4..03a156be77ad 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -78,6 +78,7 @@ #include "DolphinQt/Debugger/CodeViewWidget.h" #include "DolphinQt/Debugger/CodeWidget.h" #include "DolphinQt/Debugger/JITWidget.h" +#include "DolphinQt/Debugger/MemoryPatchWidget.h" #include "DolphinQt/Debugger/MemoryWidget.h" #include "DolphinQt/Debugger/NetworkWidget.h" #include "DolphinQt/Debugger/RegisterWidget.h" @@ -439,6 +440,7 @@ void MainWindow::CreateComponents() m_thread_widget = new ThreadWidget(this); m_watch_widget = new WatchWidget(this); m_breakpoint_widget = new BreakpointWidget(this); + m_memory_patch_widget = new MemoryPatchWidget(this); m_code_widget = new CodeWidget(this); m_cheats_manager = new CheatsManager(this); @@ -469,6 +471,9 @@ void MainWindow::CreateComponents() &BreakpointWidget::Update); connect(m_code_widget, &CodeWidget::RequestPPCComparison, m_jit_widget, &JITWidget::Compare); connect(m_code_widget, &CodeWidget::ShowMemory, m_memory_widget, &MemoryWidget::SetAddress); + connect(m_code_widget, &CodeWidget::MemoryPatchesChanged, m_memory_patch_widget, + &MemoryPatchWidget::Update); + connect(m_memory_widget, &MemoryWidget::BreakpointsChanged, m_breakpoint_widget, &BreakpointWidget::Update); connect(m_memory_widget, &MemoryWidget::ShowCode, m_code_widget, [this](u32 address) { @@ -484,6 +489,9 @@ void MainWindow::CreateComponents() if (Core::GetState() == Core::State::Paused) m_code_widget->SetAddress(address, CodeViewWidget::SetAddressUpdate::WithDetailedUpdate); }); + + connect(m_memory_patch_widget, &MemoryPatchWidget::MemoryPatchesChanged, m_code_widget, + &CodeWidget::Update); } void MainWindow::ConnectMenuBar() @@ -709,6 +717,7 @@ void MainWindow::ConnectStack() addDockWidget(Qt::LeftDockWidgetArea, m_thread_widget); addDockWidget(Qt::LeftDockWidgetArea, m_watch_widget); addDockWidget(Qt::LeftDockWidgetArea, m_breakpoint_widget); + addDockWidget(Qt::LeftDockWidgetArea, m_memory_patch_widget); addDockWidget(Qt::LeftDockWidgetArea, m_memory_widget); addDockWidget(Qt::LeftDockWidgetArea, m_network_widget); addDockWidget(Qt::LeftDockWidgetArea, m_jit_widget); @@ -719,6 +728,7 @@ void MainWindow::ConnectStack() tabifyDockWidget(m_log_widget, m_thread_widget); tabifyDockWidget(m_log_widget, m_watch_widget); tabifyDockWidget(m_log_widget, m_breakpoint_widget); + tabifyDockWidget(m_log_widget, m_memory_patch_widget); tabifyDockWidget(m_log_widget, m_memory_widget); tabifyDockWidget(m_log_widget, m_network_widget); tabifyDockWidget(m_log_widget, m_jit_widget); diff --git a/Source/Core/DolphinQt/MainWindow.h b/Source/Core/DolphinQt/MainWindow.h index 6e53f2df107b..ee7a674f24a3 100644 --- a/Source/Core/DolphinQt/MainWindow.h +++ b/Source/Core/DolphinQt/MainWindow.h @@ -32,6 +32,7 @@ class JITWidget; class LogConfigWidget; class LogWidget; class MappingWindow; +class MemoryPatchWidget; class MemoryWidget; class MenuBar; class NetPlayDialog; @@ -233,6 +234,7 @@ class MainWindow final : public QMainWindow JITWidget* m_jit_widget; LogWidget* m_log_widget; LogConfigWidget* m_log_config_widget; + MemoryPatchWidget* m_memory_patch_widget; MemoryWidget* m_memory_widget; NetworkWidget* m_network_widget; RegisterWidget* m_register_widget; diff --git a/Source/Core/DolphinQt/MenuBar.cpp b/Source/Core/DolphinQt/MenuBar.cpp index a353cb573411..b76ebcd89057 100644 --- a/Source/Core/DolphinQt/MenuBar.cpp +++ b/Source/Core/DolphinQt/MenuBar.cpp @@ -173,6 +173,7 @@ void MenuBar::OnDebugModeToggled(bool enabled) m_show_registers->setVisible(enabled); m_show_threads->setVisible(enabled); m_show_watch->setVisible(enabled); + m_show_memory_patches->setVisible(enabled); m_show_breakpoints->setVisible(enabled); m_show_memory->setVisible(enabled); m_show_network->setVisible(enabled); @@ -471,6 +472,15 @@ void MenuBar::AddViewMenu() connect(&Settings::Instance(), &Settings::BreakpointsVisibilityChanged, m_show_breakpoints, &QAction::setChecked); + m_show_memory_patches = view_menu->addAction(tr("&Patches")); + m_show_memory_patches->setCheckable(true); + m_show_memory_patches->setChecked(Settings::Instance().IsMemoryPatchesVisible()); + + connect(m_show_memory_patches, &QAction::toggled, &Settings::Instance(), + &Settings::SetMemoryPatchesVisible); + connect(&Settings::Instance(), &Settings::MemoryPatchesVisibilityChanged, m_show_memory_patches, + &QAction::setChecked); + m_show_memory = view_menu->addAction(tr("&Memory")); m_show_memory->setCheckable(true); m_show_memory->setChecked(Settings::Instance().IsMemoryVisible()); diff --git a/Source/Core/DolphinQt/MenuBar.h b/Source/Core/DolphinQt/MenuBar.h index 0841bdff0709..a6d89bb53ad1 100644 --- a/Source/Core/DolphinQt/MenuBar.h +++ b/Source/Core/DolphinQt/MenuBar.h @@ -243,6 +243,7 @@ class MenuBar final : public QMenuBar QAction* m_show_registers; QAction* m_show_threads; QAction* m_show_watch; + QAction* m_show_memory_patches; QAction* m_show_breakpoints; QAction* m_show_memory; QAction* m_show_network; diff --git a/Source/Core/DolphinQt/Settings.cpp b/Source/Core/DolphinQt/Settings.cpp index 1efe4c4e42da..79b83b6b03fd 100644 --- a/Source/Core/DolphinQt/Settings.cpp +++ b/Source/Core/DolphinQt/Settings.cpp @@ -514,6 +514,21 @@ bool Settings::IsWatchVisible() const return GetQSettings().value(QStringLiteral("debugger/showwatch")).toBool(); } +void Settings::SetMemoryPatchesVisible(bool enabled) +{ + if (IsMemoryPatchesVisible() == enabled) + return; + + GetQSettings().setValue(QStringLiteral("debugger/showmemorypatches"), enabled); + + emit MemoryPatchesVisibilityChanged(enabled); +} + +bool Settings::IsMemoryPatchesVisible() const +{ + return GetQSettings().value(QStringLiteral("debugger/showmemorypatches")).toBool(); +} + void Settings::SetBreakpointsVisible(bool enabled) { if (IsBreakpointsVisible() != enabled) diff --git a/Source/Core/DolphinQt/Settings.h b/Source/Core/DolphinQt/Settings.h index 1471daddec7a..cb2dcfeee923 100644 --- a/Source/Core/DolphinQt/Settings.h +++ b/Source/Core/DolphinQt/Settings.h @@ -131,6 +131,8 @@ class Settings final : public QObject bool IsThreadsVisible() const; void SetWatchVisible(bool enabled); bool IsWatchVisible() const; + void SetMemoryPatchesVisible(bool enabled); + bool IsMemoryPatchesVisible() const; void SetBreakpointsVisible(bool enabled); bool IsBreakpointsVisible() const; void SetCodeVisible(bool enabled); @@ -183,6 +185,7 @@ class Settings final : public QObject void WidgetLockChanged(bool locked); void EnableCheatsChanged(bool enabled); void WatchVisibilityChanged(bool visible); + void MemoryPatchesVisibilityChanged(bool visible); void BreakpointsVisibilityChanged(bool visible); void CodeVisibilityChanged(bool visible); void MemoryVisibilityChanged(bool visible);