summaryrefslogtreecommitdiff
path: root/Source/Core
diff options
context:
space:
mode:
authorAdmiral H. Curtiss <pikachu025@gmail.com>2023-01-09 18:41:28 +0100
committerGitHub <noreply@github.com>2023-01-09 18:41:28 +0100
commit653e0ccf280768e8474952767ee4da06b7e9b607 (patch)
tree1a73b75401b7d663306159314d1bdf023736e094 /Source/Core
parent21c29bad6bcb7b3750818e0a3417b96def28c4f0 (diff)
parent993d2ab173f0e262465b4420e5746bb55a2c9ff1 (diff)
Merge pull request #11365 from iwubcode/cheat_manager_freeze_value
DolphinQt: add ability to lock / freeze values in the watches window
Diffstat (limited to 'Source/Core')
-rw-r--r--Source/Core/Common/Debug/MemoryPatches.cpp21
-rw-r--r--Source/Core/Common/Debug/MemoryPatches.h11
-rw-r--r--Source/Core/Common/Debug/Watches.cpp5
-rw-r--r--Source/Core/Common/Debug/Watches.h2
-rw-r--r--Source/Core/Common/DebugInterface.h4
-rw-r--r--Source/Core/Core/Debugger/PPCDebugInterface.cpp61
-rw-r--r--Source/Core/Core/Debugger/PPCDebugInterface.h12
-rw-r--r--Source/Core/Core/PatchEngine.cpp29
-rw-r--r--Source/Core/Core/PatchEngine.h3
-rw-r--r--Source/Core/Core/PowerPC/PowerPC.cpp2
-rw-r--r--Source/Core/DolphinQt/CheatSearchWidget.cpp4
-rw-r--r--Source/Core/DolphinQt/CheatSearchWidget.h1
-rw-r--r--Source/Core/DolphinQt/CheatsManager.cpp2
-rw-r--r--Source/Core/DolphinQt/CheatsManager.h1
-rw-r--r--Source/Core/DolphinQt/Debugger/WatchWidget.cpp176
-rw-r--r--Source/Core/DolphinQt/Debugger/WatchWidget.h14
-rw-r--r--Source/Core/DolphinQt/MainWindow.cpp1
17 files changed, 309 insertions, 40 deletions
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<u8> 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<u8> 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<MemoryPatch>& 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<u8> value_);
MemoryPatch(u32 address_, u32 value_);
u32 address;
std::vector<u8> 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<u8> value);
+ void SetFramePatch(u32 address, u32 value);
+ void SetFramePatch(u32 address, std::vector<u8> value);
const std::vector<MemoryPatch>& 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<MemoryPatch> 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<u8> value) = 0;
+ virtual void SetFramePatch(u32 address, u32 value) = 0;
+ virtual void SetFramePatch(u32 address, std::vector<u8> value) = 0;
virtual const std::vector<Debug::MemoryPatch>& 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<u8> 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<u8> value)
+{
+ m_patches.SetFramePatch(address, std::move(value));
+}
+
const std::vector<Common::Debug::MemoryPatch>& 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<u8> value) override;
+ void SetFramePatch(u32 address, u32 value) override;
+ void SetFramePatch(u32 address, std::vector<u8> value) override;
const std::vector<Common::Debug::MemoryPatch>& 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 <array>
#include <iterator>
#include <map>
+#include <mutex>
#include <optional>
+#include <span>
#include <string>
#include <vector>
#include <fmt/format.h>
#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<const char*, 3> s_patch_type_strings{{
}};
static std::vector<Patch> s_on_frame;
+static std::vector<std::size_t> s_on_frame_memory;
+static std::mutex s_on_frame_memory_mutex;
static std::map<u32, int> s_speed_hacks;
const char* PatchTypeAsString(PatchType type)
@@ -257,6 +263,15 @@ static void ApplyPatches(const std::vector<Patch>& patches)
}
}
+static void ApplyMemoryPatches(std::span<const std::size_t> 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<Patch>* patches,
void SavePatchSection(IniFile* local_ini, const std::vector<Patch>& 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<QTableWidgetItem*, NUM_COLUMNS> items = {label, address, hex,
- decimal, string, floatValue};
+ auto* lockValue = new QTableWidgetItem;
+ lockValue->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable);
+
+ std::array<QTableWidgetItem*, NUM_COLUMNS> 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<u8> bytes;
+ for (const char c : memory_data_as_string)
+ {
+ bytes.push_back(static_cast<u8>(c));
+ }
+
+ PowerPC::debug_interface.SetFramePatch(address, bytes);
+}
+
+void WatchWidget::DeleteSelectedWatches()
+{
+ std::vector<int> 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()