diff --git a/Source/Core/Common/Debug/MemoryPatches.cpp b/Source/Core/Common/Debug/MemoryPatches.cpp index 8dd9b5716d..f48e6e5bb5 100644 --- a/Source/Core/Common/Debug/MemoryPatches.cpp +++ b/Source/Core/Common/Debug/MemoryPatches.cpp @@ -38,6 +38,23 @@ void MemoryPatches::SetPatch(u32 address, std::vector value) Patch(index); } +void MemoryPatches::SetFramePatch(u32 address, u32 value) +{ + const std::size_t index = m_patches.size(); + m_patches.emplace_back(address, value); + m_patches.back().type = MemoryPatch::ApplyType::EachFrame; + Patch(index); +} + +void MemoryPatches::SetFramePatch(u32 address, std::vector value) +{ + UnsetPatch(address); + const std::size_t index = m_patches.size(); + m_patches.emplace_back(address, std::move(value)); + m_patches.back().type = MemoryPatch::ApplyType::EachFrame; + Patch(index); +} + const std::vector& MemoryPatches::GetPatches() const { return m_patches; @@ -81,6 +98,7 @@ bool MemoryPatches::HasEnabledPatch(u32 address) const void MemoryPatches::RemovePatch(std::size_t index) { DisablePatch(index); + UnPatch(index); m_patches.erase(m_patches.begin() + index); } @@ -88,7 +106,10 @@ void MemoryPatches::ClearPatches() { const std::size_t size = m_patches.size(); for (std::size_t index = 0; index < size; ++index) + { DisablePatch(index); + UnPatch(index); + } m_patches.clear(); } } // namespace Common::Debug diff --git a/Source/Core/Common/Debug/MemoryPatches.h b/Source/Core/Common/Debug/MemoryPatches.h index abf88b0499..2a4f902dcd 100644 --- a/Source/Core/Common/Debug/MemoryPatches.h +++ b/Source/Core/Common/Debug/MemoryPatches.h @@ -19,12 +19,19 @@ struct MemoryPatch Disabled }; + enum class ApplyType + { + Once, + EachFrame + }; + MemoryPatch(u32 address_, std::vector value_); MemoryPatch(u32 address_, u32 value_); u32 address; std::vector value; State is_enabled = State::Enabled; + ApplyType type = ApplyType::Once; }; class MemoryPatches @@ -35,6 +42,8 @@ public: void SetPatch(u32 address, u32 value); void SetPatch(u32 address, std::vector value); + void SetFramePatch(u32 address, u32 value); + void SetFramePatch(u32 address, std::vector value); const std::vector& GetPatches() const; void UnsetPatch(u32 address); void EnablePatch(std::size_t index); @@ -42,9 +51,11 @@ public: bool HasEnabledPatch(u32 address) const; void RemovePatch(std::size_t index); void ClearPatches(); + virtual void ApplyExistingPatch(std::size_t index) = 0; protected: virtual void Patch(std::size_t index) = 0; + virtual void UnPatch(std::size_t index) = 0; std::vector m_patches; }; diff --git a/Source/Core/Common/Debug/Watches.cpp b/Source/Core/Common/Debug/Watches.cpp index 0088c8f26a..dddb3f3435 100644 --- a/Source/Core/Common/Debug/Watches.cpp +++ b/Source/Core/Common/Debug/Watches.cpp @@ -62,6 +62,11 @@ void Watches::UpdateWatchName(std::size_t index, std::string name) m_watches[index].name = std::move(name); } +void Watches::UpdateWatchLockedState(std::size_t index, bool locked) +{ + m_watches[index].locked = locked; +} + void Watches::EnableWatch(std::size_t index) { m_watches[index].is_enabled = Watch::State::Enabled; diff --git a/Source/Core/Common/Debug/Watches.h b/Source/Core/Common/Debug/Watches.h index 9cb7e3e76d..fc16adb826 100644 --- a/Source/Core/Common/Debug/Watches.h +++ b/Source/Core/Common/Debug/Watches.h @@ -22,6 +22,7 @@ struct Watch u32 address; std::string name; State is_enabled; + bool locked = false; Watch(u32 address, std::string name, State is_enabled); }; @@ -36,6 +37,7 @@ public: void UpdateWatch(std::size_t index, u32 address, std::string name); void UpdateWatchAddress(std::size_t index, u32 address); void UpdateWatchName(std::size_t index, std::string name); + void UpdateWatchLockedState(std::size_t index, bool locked); void EnableWatch(std::size_t index); void DisableWatch(std::size_t index); bool HasEnabledWatch(u32 address) const; diff --git a/Source/Core/Common/DebugInterface.h b/Source/Core/Common/DebugInterface.h index a59ac79f69..17c10a1894 100644 --- a/Source/Core/Common/DebugInterface.h +++ b/Source/Core/Common/DebugInterface.h @@ -32,6 +32,7 @@ public: virtual void UpdateWatch(std::size_t index, u32 address, std::string name) = 0; virtual void UpdateWatchAddress(std::size_t index, u32 address) = 0; virtual void UpdateWatchName(std::size_t index, std::string name) = 0; + virtual void UpdateWatchLockedState(std::size_t index, bool locked) = 0; virtual void EnableWatch(std::size_t index) = 0; virtual void DisableWatch(std::size_t index) = 0; virtual bool HasEnabledWatch(u32 address) const = 0; @@ -43,6 +44,8 @@ public: // Memory Patches virtual void SetPatch(u32 address, u32 value) = 0; virtual void SetPatch(u32 address, std::vector value) = 0; + virtual void SetFramePatch(u32 address, u32 value) = 0; + virtual void SetFramePatch(u32 address, std::vector value) = 0; virtual const std::vector& GetPatches() const = 0; virtual void UnsetPatch(u32 address) = 0; virtual void EnablePatch(std::size_t index) = 0; @@ -50,6 +53,7 @@ public: virtual bool HasEnabledPatch(u32 address) const = 0; virtual void RemovePatch(std::size_t index) = 0; virtual void ClearPatches() = 0; + virtual void ApplyExistingPatch(std::size_t index) = 0; // Threads virtual Debug::Threads GetThreads() const = 0; diff --git a/Source/Core/Core/Debugger/PPCDebugInterface.cpp b/Source/Core/Core/Debugger/PPCDebugInterface.cpp index 2cbeae41fb..75488237ab 100644 --- a/Source/Core/Core/Debugger/PPCDebugInterface.cpp +++ b/Source/Core/Core/Debugger/PPCDebugInterface.cpp @@ -19,13 +19,13 @@ #include "Core/Core.h" #include "Core/Debugger/OSThread.h" #include "Core/HW/DSP.h" +#include "Core/PatchEngine.h" #include "Core/PowerPC/MMU.h" #include "Core/PowerPC/PPCSymbolDB.h" #include "Core/PowerPC/PowerPC.h" -void PPCPatches::Patch(std::size_t index) +void ApplyMemoryPatch(Common::Debug::MemoryPatch& patch, bool store_existing_value) { - auto& patch = m_patches[index]; if (patch.value.empty()) return; @@ -36,9 +36,16 @@ void PPCPatches::Patch(std::size_t index) 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 (store_existing_value) + { + const u8 value = PowerPC::HostRead_U8(address + offset); + PowerPC::HostWrite_U8(patch.value[offset], address + offset); + patch.value[offset] = value; + } + else + { + PowerPC::HostWrite_U8(patch.value[offset], address + offset); + } if (((address + offset) % 4) == 3) PowerPC::ScheduleInvalidateCacheThreadSafe(Common::AlignDown(address + offset, 4)); @@ -50,6 +57,30 @@ void PPCPatches::Patch(std::size_t index) } } +void PPCPatches::ApplyExistingPatch(std::size_t index) +{ + auto& patch = m_patches[index]; + ApplyMemoryPatch(patch, false); +} + +void PPCPatches::Patch(std::size_t index) +{ + auto& patch = m_patches[index]; + if (patch.type == Common::Debug::MemoryPatch::ApplyType::Once) + ApplyMemoryPatch(patch); + else + PatchEngine::AddMemoryPatch(index); +} + +void PPCPatches::UnPatch(std::size_t index) +{ + auto& patch = m_patches[index]; + if (patch.type == Common::Debug::MemoryPatch::ApplyType::Once) + return; + + PatchEngine::RemoveMemoryPatch(index); +} + PPCDebugInterface::PPCDebugInterface() = default; PPCDebugInterface::~PPCDebugInterface() = default; @@ -88,6 +119,11 @@ void PPCDebugInterface::UpdateWatchName(std::size_t index, std::string name) return m_watches.UpdateWatchName(index, std::move(name)); } +void PPCDebugInterface::UpdateWatchLockedState(std::size_t index, bool locked) +{ + return m_watches.UpdateWatchLockedState(index, locked); +} + void PPCDebugInterface::EnableWatch(std::size_t index) { m_watches.EnableWatch(index); @@ -133,6 +169,16 @@ void PPCDebugInterface::SetPatch(u32 address, std::vector value) m_patches.SetPatch(address, std::move(value)); } +void PPCDebugInterface::SetFramePatch(u32 address, u32 value) +{ + m_patches.SetFramePatch(address, value); +} + +void PPCDebugInterface::SetFramePatch(u32 address, std::vector value) +{ + m_patches.SetFramePatch(address, std::move(value)); +} + const std::vector& PPCDebugInterface::GetPatches() const { return m_patches.GetPatches(); @@ -168,6 +214,11 @@ void PPCDebugInterface::ClearPatches() m_patches.ClearPatches(); } +void PPCDebugInterface::ApplyExistingPatch(std::size_t index) +{ + m_patches.ApplyExistingPatch(index); +} + Common::Debug::Threads PPCDebugInterface::GetThreads() const { Common::Debug::Threads threads; diff --git a/Source/Core/Core/Debugger/PPCDebugInterface.h b/Source/Core/Core/Debugger/PPCDebugInterface.h index 4c5dfb0fec..4d44481a8b 100644 --- a/Source/Core/Core/Debugger/PPCDebugInterface.h +++ b/Source/Core/Core/Debugger/PPCDebugInterface.h @@ -12,10 +12,16 @@ #include "Common/DebugInterface.h" #include "Core/NetworkCaptureLogger.h" -class PPCPatches : public Common::Debug::MemoryPatches +void ApplyMemoryPatch(Common::Debug::MemoryPatch& patch, bool store_existing_value = true); + +class PPCPatches final : public Common::Debug::MemoryPatches { +public: + void ApplyExistingPatch(std::size_t index) override; + private: void Patch(std::size_t index) override; + void UnPatch(std::size_t index) override; }; // wrapper between disasm control and Dolphin debugger @@ -34,6 +40,7 @@ public: void UpdateWatch(std::size_t index, u32 address, std::string name) override; void UpdateWatchAddress(std::size_t index, u32 address) override; void UpdateWatchName(std::size_t index, std::string name) override; + void UpdateWatchLockedState(std::size_t index, bool locked) override; void EnableWatch(std::size_t index) override; void DisableWatch(std::size_t index) override; bool HasEnabledWatch(u32 address) const override; @@ -45,6 +52,8 @@ public: // Memory Patches void SetPatch(u32 address, u32 value) override; void SetPatch(u32 address, std::vector value) override; + void SetFramePatch(u32 address, u32 value) override; + void SetFramePatch(u32 address, std::vector value) override; const std::vector& GetPatches() const override; void UnsetPatch(u32 address) override; void EnablePatch(std::size_t index) override; @@ -52,6 +61,7 @@ public: bool HasEnabledPatch(u32 address) const override; void RemovePatch(std::size_t index) override; void ClearPatches() override; + void ApplyExistingPatch(std::size_t index) override; // Threads Common::Debug::Threads GetThreads() const override; diff --git a/Source/Core/Core/PatchEngine.cpp b/Source/Core/Core/PatchEngine.cpp index f5fe19b7ef..a525e67261 100644 --- a/Source/Core/Core/PatchEngine.cpp +++ b/Source/Core/Core/PatchEngine.cpp @@ -11,13 +11,16 @@ #include #include #include +#include #include +#include #include #include #include #include "Common/Assert.h" +#include "Common/Debug/MemoryPatches.h" #include "Common/IniFile.h" #include "Common/StringUtil.h" @@ -25,6 +28,7 @@ #include "Core/CheatCodes.h" #include "Core/Config/SessionSettings.h" #include "Core/ConfigManager.h" +#include "Core/Debugger/PPCDebugInterface.h" #include "Core/GeckoCode.h" #include "Core/GeckoCodeConfig.h" #include "Core/PowerPC/MMU.h" @@ -39,6 +43,8 @@ constexpr std::array s_patch_type_strings{{ }}; static std::vector s_on_frame; +static std::vector s_on_frame_memory; +static std::mutex s_on_frame_memory_mutex; static std::map s_speed_hacks; const char* PatchTypeAsString(PatchType type) @@ -257,6 +263,15 @@ static void ApplyPatches(const std::vector& patches) } } +static void ApplyMemoryPatches(std::span memory_patch_indices) +{ + std::lock_guard lock(s_on_frame_memory_mutex); + for (std::size_t index : memory_patch_indices) + { + PowerPC::debug_interface.ApplyExistingPatch(index); + } +} + // Requires MSR.DR, MSR.IR // There's no perfect way to do this, it's just a heuristic. // We require at least 2 stack frames, if the stack is shallower than that then it won't work. @@ -281,6 +296,19 @@ static bool IsStackSane() 0 != PowerPC::HostRead_Instruction(address); } +void AddMemoryPatch(std::size_t index) +{ + std::lock_guard lock(s_on_frame_memory_mutex); + s_on_frame_memory.push_back(index); +} + +void RemoveMemoryPatch(std::size_t index) +{ + std::lock_guard lock(s_on_frame_memory_mutex); + s_on_frame_memory.erase(std::remove(s_on_frame_memory.begin(), s_on_frame_memory.end(), index), + s_on_frame_memory.end()); +} + bool ApplyFramePatches() { // Because we're using the VI Interrupt to time this instead of patching the game with a @@ -297,6 +325,7 @@ bool ApplyFramePatches() } ApplyPatches(s_on_frame); + ApplyMemoryPatches(s_on_frame_memory); // Run the Gecko code handler Gecko::RunCodeHandler(); diff --git a/Source/Core/Core/PatchEngine.h b/Source/Core/Core/PatchEngine.h index 9c0a7ed9ee..4218e65fcb 100644 --- a/Source/Core/Core/PatchEngine.h +++ b/Source/Core/Core/PatchEngine.h @@ -51,6 +51,9 @@ void LoadPatchSection(const std::string& section, std::vector* patches, void SavePatchSection(IniFile* local_ini, const std::vector& patches); void LoadPatches(); +void AddMemoryPatch(std::size_t index); +void RemoveMemoryPatch(std::size_t index); + bool ApplyFramePatches(); void Shutdown(); void Reload(); diff --git a/Source/Core/Core/PowerPC/PowerPC.cpp b/Source/Core/Core/PowerPC/PowerPC.cpp index 411705d833..5110788645 100644 --- a/Source/Core/Core/PowerPC/PowerPC.cpp +++ b/Source/Core/Core/PowerPC/PowerPC.cpp @@ -298,7 +298,7 @@ void Reset() void ScheduleInvalidateCacheThreadSafe(u32 address) { - if (CPU::GetState() == CPU::State::Running) + if (CPU::GetState() == CPU::State::Running && !Core::IsCPUThread()) { Core::System::GetInstance().GetCoreTiming().ScheduleEvent( 0, s_invalidate_cache_thread_safe, address, CoreTiming::FromThread::NON_CPU); diff --git a/Source/Core/DolphinQt/CheatSearchWidget.cpp b/Source/Core/DolphinQt/CheatSearchWidget.cpp index 33594f7d1d..e4e81b3820 100644 --- a/Source/Core/DolphinQt/CheatSearchWidget.cpp +++ b/Source/Core/DolphinQt/CheatSearchWidget.cpp @@ -454,6 +454,10 @@ void CheatSearchWidget::OnAddressTableContextMenu() QMenu* menu = new QMenu(this); menu->addAction(tr("Show in memory"), [this, address] { emit ShowMemory(address); }); + menu->addAction(tr("Add to watch"), this, [this, address] { + const QString name = QStringLiteral("mem_%1").arg(address, 8, 16, QLatin1Char('0')); + emit RequestWatch(name, address); + }); menu->addAction(tr("Generate Action Replay Code"), this, &CheatSearchWidget::GenerateARCode); menu->exec(QCursor::pos()); diff --git a/Source/Core/DolphinQt/CheatSearchWidget.h b/Source/Core/DolphinQt/CheatSearchWidget.h index 88aeeb9fcb..e885f7a181 100644 --- a/Source/Core/DolphinQt/CheatSearchWidget.h +++ b/Source/Core/DolphinQt/CheatSearchWidget.h @@ -41,6 +41,7 @@ public: signals: void ActionReplayCodeGenerated(const ActionReplay::ARCode& ar_code); + void RequestWatch(QString name, u32 address); void ShowMemory(const u32 address); private: diff --git a/Source/Core/DolphinQt/CheatsManager.cpp b/Source/Core/DolphinQt/CheatsManager.cpp index f4b7154b6f..743126f574 100644 --- a/Source/Core/DolphinQt/CheatsManager.cpp +++ b/Source/Core/DolphinQt/CheatsManager.cpp @@ -123,6 +123,8 @@ void CheatsManager::OnNewSessionCreated(const Cheats::CheatSearchSessionBase& se m_ar_code->AddCode(ar_code); }); w->connect(w, &CheatSearchWidget::ShowMemory, [this](u32 address) { emit ShowMemory(address); }); + w->connect(w, &CheatSearchWidget::RequestWatch, + [this](QString name, u32 address) { emit RequestWatch(name, address); }); m_tab_widget->setCurrentIndex(tab_index); } diff --git a/Source/Core/DolphinQt/CheatsManager.h b/Source/Core/DolphinQt/CheatsManager.h index 8cd478e81c..5e7ddb8bb2 100644 --- a/Source/Core/DolphinQt/CheatsManager.h +++ b/Source/Core/DolphinQt/CheatsManager.h @@ -36,6 +36,7 @@ public: signals: void OpenGeneralSettings(); void ShowMemory(u32 address); + void RequestWatch(QString name, u32 address); private: void CreateWidgets(); diff --git a/Source/Core/DolphinQt/Debugger/WatchWidget.cpp b/Source/Core/DolphinQt/Debugger/WatchWidget.cpp index e0573f39d4..2f4ea27598 100644 --- a/Source/Core/DolphinQt/Debugger/WatchWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/WatchWidget.cpp @@ -83,7 +83,8 @@ void WatchWidget::CreateWidgets() m_table->setColumnCount(NUM_COLUMNS); m_table->verticalHeader()->setHidden(true); m_table->setContextMenuPolicy(Qt::CustomContextMenu); - m_table->setSelectionMode(QAbstractItemView::SingleSelection); + m_table->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_table->setSelectionBehavior(QAbstractItemView::SelectRows); m_new = m_toolbar->addAction(tr("New"), this, &WatchWidget::OnNewWatch); m_delete = m_toolbar->addAction(tr("Delete"), this, &WatchWidget::OnDelete); @@ -158,11 +159,11 @@ void WatchWidget::Update() // i18n: Data type used in computing tr("String"), // i18n: Floating-point (non-integer) number - tr("Float")}); + tr("Float"), tr("Locked")}); for (int i = 0; i < size; i++) { - auto entry = PowerPC::debug_interface.GetWatch(i); + const auto& entry = PowerPC::debug_interface.GetWatch(i); auto* label = new QTableWidgetItem(QString::fromStdString(entry.name)); auto* address = @@ -172,8 +173,11 @@ void WatchWidget::Update() auto* string = new QTableWidgetItem; auto* floatValue = new QTableWidgetItem; - std::array items = {label, address, hex, - decimal, string, floatValue}; + auto* lockValue = new QTableWidgetItem; + lockValue->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable); + + std::array items = {label, address, hex, decimal, + string, floatValue, lockValue}; QBrush brush = QPalette().brush(QPalette::Text); @@ -189,6 +193,7 @@ void WatchWidget::Update() decimal->setText(QString::number(PowerPC::HostRead_U32(entry.address))); string->setText(QString::fromStdString(PowerPC::HostGetString(entry.address, 32))); floatValue->setText(QString::number(PowerPC::HostRead_F32(entry.address))); + lockValue->setCheckState(entry.locked ? Qt::Checked : Qt::Unchecked); } } @@ -235,11 +240,7 @@ void WatchWidget::OnDelete() if (m_table->selectedItems().empty()) return; - auto row_variant = m_table->selectedItems()[0]->data(Qt::UserRole); - if (row_variant.isNull()) - return; - - DeleteWatch(row_variant.toInt()); + DeleteSelectedWatches(); } void WatchWidget::OnClear() @@ -280,6 +281,10 @@ void WatchWidget::OnLoad() if (ini.GetLines("Watches", &watches, false)) { + for (const auto& watch : PowerPC::debug_interface.GetWatches()) + { + PowerPC::debug_interface.UnsetPatch(watch.address); + } PowerPC::debug_interface.ClearWatches(); PowerPC::debug_interface.LoadWatchesFromStrings(watches); } @@ -302,20 +307,30 @@ void WatchWidget::ShowContextMenu() if (!m_table->selectedItems().empty()) { - auto row_variant = m_table->selectedItems()[0]->data(Qt::UserRole); - - if (!row_variant.isNull()) + const std::size_t count = m_table->selectionModel()->selectedRows().count(); + if (count > 1) { - int row = row_variant.toInt(); + menu->addAction(tr("&Delete Watches"), this, [this] { DeleteSelectedWatches(); }); + menu->addAction(tr("&Lock Watches"), this, [this] { LockSelectedWatches(); }); + menu->addAction(tr("&Unlock Watches"), this, [this] { UnlockSelectedWatches(); }); + } + else if (count == 1) + { + auto row_variant = m_table->selectedItems()[0]->data(Qt::UserRole); - if (row >= 0) + if (!row_variant.isNull()) { - menu->addAction(tr("Show in Memory"), this, [this, row] { ShowInMemory(row); }); - // i18n: This kind of "watch" is used for watching emulated memory. - // It's not related to timekeeping devices. - menu->addAction(tr("&Delete Watch"), this, [this, row] { DeleteWatch(row); }); - menu->addAction(tr("&Add Memory Breakpoint"), this, - [this, row] { AddWatchBreakpoint(row); }); + int row = row_variant.toInt(); + + if (row >= 0) + { + menu->addAction(tr("Show in Memory"), this, [this, row] { ShowInMemory(row); }); + // i18n: This kind of "watch" is used for watching emulated memory. + // It's not related to timekeeping devices. + menu->addAction(tr("&Delete Watch"), this, [this, row] { DeleteWatchAndUpdate(row); }); + menu->addAction(tr("&Add Memory Breakpoint"), this, + [this, row] { AddWatchBreakpoint(row); }); + } } } } @@ -349,29 +364,35 @@ void WatchWidget::OnItemChanged(QTableWidgetItem* item) { switch (column) { - // Label - case 0: + case COLUMN_INDEX_LABEL: if (item->text().isEmpty()) - DeleteWatch(row); + DeleteWatchAndUpdate(row); else PowerPC::debug_interface.UpdateWatchName(row, item->text().toStdString()); break; - // Address - // Hexadecimal - // Decimal - case 1: - case 2: - case 3: + case COLUMN_INDEX_ADDRESS: + case COLUMN_INDEX_HEX: + case COLUMN_INDEX_DECIMAL: { bool good; - quint32 value = item->text().toUInt(&good, column < 3 ? 16 : 10); + const bool column_uses_hex_formatting = + column == COLUMN_INDEX_ADDRESS || column == COLUMN_INDEX_HEX; + quint32 value = item->text().toUInt(&good, column_uses_hex_formatting ? 16 : 10); if (good) { - if (column == 1) + if (column == COLUMN_INDEX_ADDRESS) + { + const auto& watch = PowerPC::debug_interface.GetWatch(row); + PowerPC::debug_interface.UnsetPatch(watch.address); PowerPC::debug_interface.UpdateWatchAddress(row, value); + if (watch.locked) + LockWatchAddress(value); + } else + { PowerPC::HostWrite_U32(value, PowerPC::debug_interface.GetWatch(row).address); + } } else { @@ -379,15 +400,68 @@ void WatchWidget::OnItemChanged(QTableWidgetItem* item) } break; } + case COLUMN_INDEX_LOCK: + { + PowerPC::debug_interface.UpdateWatchLockedState(row, item->checkState() == Qt::Checked); + const auto& watch = PowerPC::debug_interface.GetWatch(row); + if (watch.locked) + LockWatchAddress(watch.address); + else + PowerPC::debug_interface.UnsetPatch(watch.address); + break; + } } Update(); } } +void WatchWidget::LockWatchAddress(u32 address) +{ + const std::string memory_data_as_string = PowerPC::HostGetString(address, 4); + + std::vector bytes; + for (const char c : memory_data_as_string) + { + bytes.push_back(static_cast(c)); + } + + PowerPC::debug_interface.SetFramePatch(address, bytes); +} + +void WatchWidget::DeleteSelectedWatches() +{ + std::vector row_indices; + for (const auto& index : m_table->selectionModel()->selectedRows()) + { + const auto* item = m_table->item(index.row(), index.column()); + const auto row_variant = item->data(Qt::UserRole); + if (row_variant.isNull()) + continue; + + row_indices.push_back(row_variant.toInt()); + } + + // Sort greatest to smallest, so we + // don't stomp on existing indices + std::sort(row_indices.begin(), row_indices.end(), std::greater{}); + for (const int row : row_indices) + { + DeleteWatch(row); + } + + Update(); +} + void WatchWidget::DeleteWatch(int row) { + PowerPC::debug_interface.UnsetPatch(PowerPC::debug_interface.GetWatch(row).address); PowerPC::debug_interface.RemoveWatch(row); +} + +void WatchWidget::DeleteWatchAndUpdate(int row) +{ + DeleteWatch(row); Update(); } @@ -406,3 +480,41 @@ void WatchWidget::AddWatch(QString name, u32 addr) PowerPC::debug_interface.SetWatch(addr, name.toStdString()); Update(); } + +void WatchWidget::LockSelectedWatches() +{ + for (const auto& index : m_table->selectionModel()->selectedRows()) + { + const auto* item = m_table->item(index.row(), index.column()); + const auto row_variant = item->data(Qt::UserRole); + if (row_variant.isNull()) + continue; + const int row = row_variant.toInt(); + const auto& watch = PowerPC::debug_interface.GetWatch(row); + if (watch.locked) + continue; + PowerPC::debug_interface.UpdateWatchLockedState(row, true); + LockWatchAddress(watch.address); + } + + Update(); +} + +void WatchWidget::UnlockSelectedWatches() +{ + for (const auto& index : m_table->selectionModel()->selectedRows()) + { + const auto* item = m_table->item(index.row(), index.column()); + const auto row_variant = item->data(Qt::UserRole); + if (row_variant.isNull()) + continue; + const int row = row_variant.toInt(); + const auto& watch = PowerPC::debug_interface.GetWatch(row); + if (!watch.locked) + continue; + PowerPC::debug_interface.UpdateWatchLockedState(row, false); + PowerPC::debug_interface.UnsetPatch(watch.address); + } + + Update(); +} diff --git a/Source/Core/DolphinQt/Debugger/WatchWidget.h b/Source/Core/DolphinQt/Debugger/WatchWidget.h index 6529bba4d3..22aefb9b0e 100644 --- a/Source/Core/DolphinQt/Debugger/WatchWidget.h +++ b/Source/Core/DolphinQt/Debugger/WatchWidget.h @@ -46,10 +46,15 @@ private: void ShowContextMenu(); void OnItemChanged(QTableWidgetItem* item); + void LockWatchAddress(u32 address); + void DeleteSelectedWatches(); void DeleteWatch(int row); + void DeleteWatchAndUpdate(int row); void AddWatchBreakpoint(int row); void ShowInMemory(int row); void UpdateIcons(); + void LockSelectedWatches(); + void UnlockSelectedWatches(); QAction* m_new; QAction* m_delete; @@ -61,5 +66,12 @@ private: bool m_updating = false; - static constexpr int NUM_COLUMNS = 6; + static constexpr int NUM_COLUMNS = 7; + static constexpr int COLUMN_INDEX_LABEL = 0; + static constexpr int COLUMN_INDEX_ADDRESS = 1; + static constexpr int COLUMN_INDEX_HEX = 2; + static constexpr int COLUMN_INDEX_DECIMAL = 3; + static constexpr int COLUMN_INDEX_STRING = 4; + static constexpr int COLUMN_INDEX_FLOAT = 5; + static constexpr int COLUMN_INDEX_LOCK = 6; }; diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 5c13da946c..210dc895c4 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -467,6 +467,7 @@ void MainWindow::CreateComponents() connect(m_breakpoint_widget, &BreakpointWidget::ShowMemory, m_memory_widget, &MemoryWidget::SetAddress); connect(m_cheats_manager, &CheatsManager::ShowMemory, m_memory_widget, &MemoryWidget::SetAddress); + connect(m_cheats_manager, &CheatsManager::RequestWatch, request_watch); } void MainWindow::ConnectMenuBar()