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:
commit
653e0ccf28
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ public:
|
|||
signals:
|
||||
void OpenGeneralSettings();
|
||||
void ShowMemory(u32 address);
|
||||
void RequestWatch(QString name, u32 address);
|
||||
|
||||
private:
|
||||
void CreateWidgets();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue