Merge pull request #11365 from iwubcode/cheat_manager_freeze_value

DolphinQt: add ability to lock / freeze values in the watches window
This commit is contained in:
Admiral H. Curtiss 2023-01-09 18:41:28 +01:00 committed by GitHub
commit 653e0ccf28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 309 additions and 40 deletions

View File

@ -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

View File

@ -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;
};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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();

View File

@ -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);

View File

@ -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());

View File

@ -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:

View File

@ -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);
}

View File

@ -36,6 +36,7 @@ public:
signals:
void OpenGeneralSettings();
void ShowMemory(u32 address);
void RequestWatch(QString name, u32 address);
private:
void CreateWidgets();

View File

@ -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();
}

View File

@ -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;
};

View File

@ -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()