mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-01-24 23:11:14 +01:00
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);
|
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
|
const std::vector<MemoryPatch>& MemoryPatches::GetPatches() const
|
||||||
{
|
{
|
||||||
return m_patches;
|
return m_patches;
|
||||||
@ -81,6 +98,7 @@ bool MemoryPatches::HasEnabledPatch(u32 address) const
|
|||||||
void MemoryPatches::RemovePatch(std::size_t index)
|
void MemoryPatches::RemovePatch(std::size_t index)
|
||||||
{
|
{
|
||||||
DisablePatch(index);
|
DisablePatch(index);
|
||||||
|
UnPatch(index);
|
||||||
m_patches.erase(m_patches.begin() + index);
|
m_patches.erase(m_patches.begin() + index);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +106,10 @@ void MemoryPatches::ClearPatches()
|
|||||||
{
|
{
|
||||||
const std::size_t size = m_patches.size();
|
const std::size_t size = m_patches.size();
|
||||||
for (std::size_t index = 0; index < size; ++index)
|
for (std::size_t index = 0; index < size; ++index)
|
||||||
|
{
|
||||||
DisablePatch(index);
|
DisablePatch(index);
|
||||||
|
UnPatch(index);
|
||||||
|
}
|
||||||
m_patches.clear();
|
m_patches.clear();
|
||||||
}
|
}
|
||||||
} // namespace Common::Debug
|
} // namespace Common::Debug
|
||||||
|
@ -19,12 +19,19 @@ struct MemoryPatch
|
|||||||
Disabled
|
Disabled
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class ApplyType
|
||||||
|
{
|
||||||
|
Once,
|
||||||
|
EachFrame
|
||||||
|
};
|
||||||
|
|
||||||
MemoryPatch(u32 address_, std::vector<u8> value_);
|
MemoryPatch(u32 address_, std::vector<u8> value_);
|
||||||
MemoryPatch(u32 address_, u32 value_);
|
MemoryPatch(u32 address_, u32 value_);
|
||||||
|
|
||||||
u32 address;
|
u32 address;
|
||||||
std::vector<u8> value;
|
std::vector<u8> value;
|
||||||
State is_enabled = State::Enabled;
|
State is_enabled = State::Enabled;
|
||||||
|
ApplyType type = ApplyType::Once;
|
||||||
};
|
};
|
||||||
|
|
||||||
class MemoryPatches
|
class MemoryPatches
|
||||||
@ -35,6 +42,8 @@ public:
|
|||||||
|
|
||||||
void SetPatch(u32 address, u32 value);
|
void SetPatch(u32 address, u32 value);
|
||||||
void SetPatch(u32 address, std::vector<u8> 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;
|
const std::vector<MemoryPatch>& GetPatches() const;
|
||||||
void UnsetPatch(u32 address);
|
void UnsetPatch(u32 address);
|
||||||
void EnablePatch(std::size_t index);
|
void EnablePatch(std::size_t index);
|
||||||
@ -42,9 +51,11 @@ public:
|
|||||||
bool HasEnabledPatch(u32 address) const;
|
bool HasEnabledPatch(u32 address) const;
|
||||||
void RemovePatch(std::size_t index);
|
void RemovePatch(std::size_t index);
|
||||||
void ClearPatches();
|
void ClearPatches();
|
||||||
|
virtual void ApplyExistingPatch(std::size_t index) = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void Patch(std::size_t index) = 0;
|
virtual void Patch(std::size_t index) = 0;
|
||||||
|
virtual void UnPatch(std::size_t index) = 0;
|
||||||
|
|
||||||
std::vector<MemoryPatch> m_patches;
|
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);
|
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)
|
void Watches::EnableWatch(std::size_t index)
|
||||||
{
|
{
|
||||||
m_watches[index].is_enabled = Watch::State::Enabled;
|
m_watches[index].is_enabled = Watch::State::Enabled;
|
||||||
|
@ -22,6 +22,7 @@ struct Watch
|
|||||||
u32 address;
|
u32 address;
|
||||||
std::string name;
|
std::string name;
|
||||||
State is_enabled;
|
State is_enabled;
|
||||||
|
bool locked = false;
|
||||||
|
|
||||||
Watch(u32 address, std::string name, State is_enabled);
|
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 UpdateWatch(std::size_t index, u32 address, std::string name);
|
||||||
void UpdateWatchAddress(std::size_t index, u32 address);
|
void UpdateWatchAddress(std::size_t index, u32 address);
|
||||||
void UpdateWatchName(std::size_t index, std::string name);
|
void UpdateWatchName(std::size_t index, std::string name);
|
||||||
|
void UpdateWatchLockedState(std::size_t index, bool locked);
|
||||||
void EnableWatch(std::size_t index);
|
void EnableWatch(std::size_t index);
|
||||||
void DisableWatch(std::size_t index);
|
void DisableWatch(std::size_t index);
|
||||||
bool HasEnabledWatch(u32 address) const;
|
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 UpdateWatch(std::size_t index, u32 address, std::string name) = 0;
|
||||||
virtual void UpdateWatchAddress(std::size_t index, u32 address) = 0;
|
virtual void UpdateWatchAddress(std::size_t index, u32 address) = 0;
|
||||||
virtual void UpdateWatchName(std::size_t index, std::string name) = 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 EnableWatch(std::size_t index) = 0;
|
||||||
virtual void DisableWatch(std::size_t index) = 0;
|
virtual void DisableWatch(std::size_t index) = 0;
|
||||||
virtual bool HasEnabledWatch(u32 address) const = 0;
|
virtual bool HasEnabledWatch(u32 address) const = 0;
|
||||||
@ -43,6 +44,8 @@ public:
|
|||||||
// Memory Patches
|
// Memory Patches
|
||||||
virtual void SetPatch(u32 address, u32 value) = 0;
|
virtual void SetPatch(u32 address, u32 value) = 0;
|
||||||
virtual void SetPatch(u32 address, std::vector<u8> 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 const std::vector<Debug::MemoryPatch>& GetPatches() const = 0;
|
||||||
virtual void UnsetPatch(u32 address) = 0;
|
virtual void UnsetPatch(u32 address) = 0;
|
||||||
virtual void EnablePatch(std::size_t index) = 0;
|
virtual void EnablePatch(std::size_t index) = 0;
|
||||||
@ -50,6 +53,7 @@ public:
|
|||||||
virtual bool HasEnabledPatch(u32 address) const = 0;
|
virtual bool HasEnabledPatch(u32 address) const = 0;
|
||||||
virtual void RemovePatch(std::size_t index) = 0;
|
virtual void RemovePatch(std::size_t index) = 0;
|
||||||
virtual void ClearPatches() = 0;
|
virtual void ClearPatches() = 0;
|
||||||
|
virtual void ApplyExistingPatch(std::size_t index) = 0;
|
||||||
|
|
||||||
// Threads
|
// Threads
|
||||||
virtual Debug::Threads GetThreads() const = 0;
|
virtual Debug::Threads GetThreads() const = 0;
|
||||||
|
@ -19,13 +19,13 @@
|
|||||||
#include "Core/Core.h"
|
#include "Core/Core.h"
|
||||||
#include "Core/Debugger/OSThread.h"
|
#include "Core/Debugger/OSThread.h"
|
||||||
#include "Core/HW/DSP.h"
|
#include "Core/HW/DSP.h"
|
||||||
|
#include "Core/PatchEngine.h"
|
||||||
#include "Core/PowerPC/MMU.h"
|
#include "Core/PowerPC/MMU.h"
|
||||||
#include "Core/PowerPC/PPCSymbolDB.h"
|
#include "Core/PowerPC/PPCSymbolDB.h"
|
||||||
#include "Core/PowerPC/PowerPC.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())
|
if (patch.value.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -36,9 +36,16 @@ void PPCPatches::Patch(std::size_t index)
|
|||||||
|
|
||||||
for (u32 offset = 0; offset < size; ++offset)
|
for (u32 offset = 0; offset < size; ++offset)
|
||||||
{
|
{
|
||||||
const u8 value = PowerPC::HostRead_U8(address + offset);
|
if (store_existing_value)
|
||||||
PowerPC::HostWrite_U8(patch.value[offset], address + offset);
|
{
|
||||||
patch.value[offset] = 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)
|
if (((address + offset) % 4) == 3)
|
||||||
PowerPC::ScheduleInvalidateCacheThreadSafe(Common::AlignDown(address + offset, 4));
|
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;
|
||||||
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));
|
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)
|
void PPCDebugInterface::EnableWatch(std::size_t index)
|
||||||
{
|
{
|
||||||
m_watches.EnableWatch(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));
|
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
|
const std::vector<Common::Debug::MemoryPatch>& PPCDebugInterface::GetPatches() const
|
||||||
{
|
{
|
||||||
return m_patches.GetPatches();
|
return m_patches.GetPatches();
|
||||||
@ -168,6 +214,11 @@ void PPCDebugInterface::ClearPatches()
|
|||||||
m_patches.ClearPatches();
|
m_patches.ClearPatches();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PPCDebugInterface::ApplyExistingPatch(std::size_t index)
|
||||||
|
{
|
||||||
|
m_patches.ApplyExistingPatch(index);
|
||||||
|
}
|
||||||
|
|
||||||
Common::Debug::Threads PPCDebugInterface::GetThreads() const
|
Common::Debug::Threads PPCDebugInterface::GetThreads() const
|
||||||
{
|
{
|
||||||
Common::Debug::Threads threads;
|
Common::Debug::Threads threads;
|
||||||
|
@ -12,10 +12,16 @@
|
|||||||
#include "Common/DebugInterface.h"
|
#include "Common/DebugInterface.h"
|
||||||
#include "Core/NetworkCaptureLogger.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:
|
private:
|
||||||
void Patch(std::size_t index) override;
|
void Patch(std::size_t index) override;
|
||||||
|
void UnPatch(std::size_t index) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
// wrapper between disasm control and Dolphin debugger
|
// 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 UpdateWatch(std::size_t index, u32 address, std::string name) override;
|
||||||
void UpdateWatchAddress(std::size_t index, u32 address) override;
|
void UpdateWatchAddress(std::size_t index, u32 address) override;
|
||||||
void UpdateWatchName(std::size_t index, std::string name) 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 EnableWatch(std::size_t index) override;
|
||||||
void DisableWatch(std::size_t index) override;
|
void DisableWatch(std::size_t index) override;
|
||||||
bool HasEnabledWatch(u32 address) const override;
|
bool HasEnabledWatch(u32 address) const override;
|
||||||
@ -45,6 +52,8 @@ public:
|
|||||||
// Memory Patches
|
// Memory Patches
|
||||||
void SetPatch(u32 address, u32 value) override;
|
void SetPatch(u32 address, u32 value) override;
|
||||||
void SetPatch(u32 address, std::vector<u8> 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;
|
const std::vector<Common::Debug::MemoryPatch>& GetPatches() const override;
|
||||||
void UnsetPatch(u32 address) override;
|
void UnsetPatch(u32 address) override;
|
||||||
void EnablePatch(std::size_t index) override;
|
void EnablePatch(std::size_t index) override;
|
||||||
@ -52,6 +61,7 @@ public:
|
|||||||
bool HasEnabledPatch(u32 address) const override;
|
bool HasEnabledPatch(u32 address) const override;
|
||||||
void RemovePatch(std::size_t index) override;
|
void RemovePatch(std::size_t index) override;
|
||||||
void ClearPatches() override;
|
void ClearPatches() override;
|
||||||
|
void ApplyExistingPatch(std::size_t index) override;
|
||||||
|
|
||||||
// Threads
|
// Threads
|
||||||
Common::Debug::Threads GetThreads() const override;
|
Common::Debug::Threads GetThreads() const override;
|
||||||
|
@ -11,13 +11,16 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <span>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
|
|
||||||
#include "Common/Assert.h"
|
#include "Common/Assert.h"
|
||||||
|
#include "Common/Debug/MemoryPatches.h"
|
||||||
#include "Common/IniFile.h"
|
#include "Common/IniFile.h"
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
|
|
||||||
@ -25,6 +28,7 @@
|
|||||||
#include "Core/CheatCodes.h"
|
#include "Core/CheatCodes.h"
|
||||||
#include "Core/Config/SessionSettings.h"
|
#include "Core/Config/SessionSettings.h"
|
||||||
#include "Core/ConfigManager.h"
|
#include "Core/ConfigManager.h"
|
||||||
|
#include "Core/Debugger/PPCDebugInterface.h"
|
||||||
#include "Core/GeckoCode.h"
|
#include "Core/GeckoCode.h"
|
||||||
#include "Core/GeckoCodeConfig.h"
|
#include "Core/GeckoCodeConfig.h"
|
||||||
#include "Core/PowerPC/MMU.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<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;
|
static std::map<u32, int> s_speed_hacks;
|
||||||
|
|
||||||
const char* PatchTypeAsString(PatchType type)
|
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
|
// Requires MSR.DR, MSR.IR
|
||||||
// There's no perfect way to do this, it's just a heuristic.
|
// 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.
|
// 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);
|
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()
|
bool ApplyFramePatches()
|
||||||
{
|
{
|
||||||
// Because we're using the VI Interrupt to time this instead of patching the game with a
|
// 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);
|
ApplyPatches(s_on_frame);
|
||||||
|
ApplyMemoryPatches(s_on_frame_memory);
|
||||||
|
|
||||||
// Run the Gecko code handler
|
// Run the Gecko code handler
|
||||||
Gecko::RunCodeHandler();
|
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 SavePatchSection(IniFile* local_ini, const std::vector<Patch>& patches);
|
||||||
void LoadPatches();
|
void LoadPatches();
|
||||||
|
|
||||||
|
void AddMemoryPatch(std::size_t index);
|
||||||
|
void RemoveMemoryPatch(std::size_t index);
|
||||||
|
|
||||||
bool ApplyFramePatches();
|
bool ApplyFramePatches();
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
void Reload();
|
void Reload();
|
||||||
|
@ -298,7 +298,7 @@ void Reset()
|
|||||||
|
|
||||||
void ScheduleInvalidateCacheThreadSafe(u32 address)
|
void ScheduleInvalidateCacheThreadSafe(u32 address)
|
||||||
{
|
{
|
||||||
if (CPU::GetState() == CPU::State::Running)
|
if (CPU::GetState() == CPU::State::Running && !Core::IsCPUThread())
|
||||||
{
|
{
|
||||||
Core::System::GetInstance().GetCoreTiming().ScheduleEvent(
|
Core::System::GetInstance().GetCoreTiming().ScheduleEvent(
|
||||||
0, s_invalidate_cache_thread_safe, address, CoreTiming::FromThread::NON_CPU);
|
0, s_invalidate_cache_thread_safe, address, CoreTiming::FromThread::NON_CPU);
|
||||||
|
@ -454,6 +454,10 @@ void CheatSearchWidget::OnAddressTableContextMenu()
|
|||||||
QMenu* menu = new QMenu(this);
|
QMenu* menu = new QMenu(this);
|
||||||
|
|
||||||
menu->addAction(tr("Show in memory"), [this, address] { emit ShowMemory(address); });
|
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->addAction(tr("Generate Action Replay Code"), this, &CheatSearchWidget::GenerateARCode);
|
||||||
|
|
||||||
menu->exec(QCursor::pos());
|
menu->exec(QCursor::pos());
|
||||||
|
@ -41,6 +41,7 @@ public:
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void ActionReplayCodeGenerated(const ActionReplay::ARCode& ar_code);
|
void ActionReplayCodeGenerated(const ActionReplay::ARCode& ar_code);
|
||||||
|
void RequestWatch(QString name, u32 address);
|
||||||
void ShowMemory(const u32 address);
|
void ShowMemory(const u32 address);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -123,6 +123,8 @@ void CheatsManager::OnNewSessionCreated(const Cheats::CheatSearchSessionBase& se
|
|||||||
m_ar_code->AddCode(ar_code);
|
m_ar_code->AddCode(ar_code);
|
||||||
});
|
});
|
||||||
w->connect(w, &CheatSearchWidget::ShowMemory, [this](u32 address) { emit ShowMemory(address); });
|
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);
|
m_tab_widget->setCurrentIndex(tab_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ public:
|
|||||||
signals:
|
signals:
|
||||||
void OpenGeneralSettings();
|
void OpenGeneralSettings();
|
||||||
void ShowMemory(u32 address);
|
void ShowMemory(u32 address);
|
||||||
|
void RequestWatch(QString name, u32 address);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void CreateWidgets();
|
void CreateWidgets();
|
||||||
|
@ -83,7 +83,8 @@ void WatchWidget::CreateWidgets()
|
|||||||
m_table->setColumnCount(NUM_COLUMNS);
|
m_table->setColumnCount(NUM_COLUMNS);
|
||||||
m_table->verticalHeader()->setHidden(true);
|
m_table->verticalHeader()->setHidden(true);
|
||||||
m_table->setContextMenuPolicy(Qt::CustomContextMenu);
|
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_new = m_toolbar->addAction(tr("New"), this, &WatchWidget::OnNewWatch);
|
||||||
m_delete = m_toolbar->addAction(tr("Delete"), this, &WatchWidget::OnDelete);
|
m_delete = m_toolbar->addAction(tr("Delete"), this, &WatchWidget::OnDelete);
|
||||||
@ -158,11 +159,11 @@ void WatchWidget::Update()
|
|||||||
// i18n: Data type used in computing
|
// i18n: Data type used in computing
|
||||||
tr("String"),
|
tr("String"),
|
||||||
// i18n: Floating-point (non-integer) number
|
// i18n: Floating-point (non-integer) number
|
||||||
tr("Float")});
|
tr("Float"), tr("Locked")});
|
||||||
|
|
||||||
for (int i = 0; i < size; i++)
|
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* label = new QTableWidgetItem(QString::fromStdString(entry.name));
|
||||||
auto* address =
|
auto* address =
|
||||||
@ -172,8 +173,11 @@ void WatchWidget::Update()
|
|||||||
auto* string = new QTableWidgetItem;
|
auto* string = new QTableWidgetItem;
|
||||||
auto* floatValue = new QTableWidgetItem;
|
auto* floatValue = new QTableWidgetItem;
|
||||||
|
|
||||||
std::array<QTableWidgetItem*, NUM_COLUMNS> items = {label, address, hex,
|
auto* lockValue = new QTableWidgetItem;
|
||||||
decimal, string, floatValue};
|
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);
|
QBrush brush = QPalette().brush(QPalette::Text);
|
||||||
|
|
||||||
@ -189,6 +193,7 @@ void WatchWidget::Update()
|
|||||||
decimal->setText(QString::number(PowerPC::HostRead_U32(entry.address)));
|
decimal->setText(QString::number(PowerPC::HostRead_U32(entry.address)));
|
||||||
string->setText(QString::fromStdString(PowerPC::HostGetString(entry.address, 32)));
|
string->setText(QString::fromStdString(PowerPC::HostGetString(entry.address, 32)));
|
||||||
floatValue->setText(QString::number(PowerPC::HostRead_F32(entry.address)));
|
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())
|
if (m_table->selectedItems().empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto row_variant = m_table->selectedItems()[0]->data(Qt::UserRole);
|
DeleteSelectedWatches();
|
||||||
if (row_variant.isNull())
|
|
||||||
return;
|
|
||||||
|
|
||||||
DeleteWatch(row_variant.toInt());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WatchWidget::OnClear()
|
void WatchWidget::OnClear()
|
||||||
@ -280,6 +281,10 @@ void WatchWidget::OnLoad()
|
|||||||
|
|
||||||
if (ini.GetLines("Watches", &watches, false))
|
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.ClearWatches();
|
||||||
PowerPC::debug_interface.LoadWatchesFromStrings(watches);
|
PowerPC::debug_interface.LoadWatchesFromStrings(watches);
|
||||||
}
|
}
|
||||||
@ -302,20 +307,30 @@ void WatchWidget::ShowContextMenu()
|
|||||||
|
|
||||||
if (!m_table->selectedItems().empty())
|
if (!m_table->selectedItems().empty())
|
||||||
{
|
{
|
||||||
auto row_variant = m_table->selectedItems()[0]->data(Qt::UserRole);
|
const std::size_t count = m_table->selectionModel()->selectedRows().count();
|
||||||
|
if (count > 1)
|
||||||
if (!row_variant.isNull())
|
|
||||||
{
|
{
|
||||||
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); });
|
int row = row_variant.toInt();
|
||||||
// i18n: This kind of "watch" is used for watching emulated memory.
|
|
||||||
// It's not related to timekeeping devices.
|
if (row >= 0)
|
||||||
menu->addAction(tr("&Delete Watch"), this, [this, row] { DeleteWatch(row); });
|
{
|
||||||
menu->addAction(tr("&Add Memory Breakpoint"), this,
|
menu->addAction(tr("Show in Memory"), this, [this, row] { ShowInMemory(row); });
|
||||||
[this, row] { AddWatchBreakpoint(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)
|
switch (column)
|
||||||
{
|
{
|
||||||
// Label
|
case COLUMN_INDEX_LABEL:
|
||||||
case 0:
|
|
||||||
if (item->text().isEmpty())
|
if (item->text().isEmpty())
|
||||||
DeleteWatch(row);
|
DeleteWatchAndUpdate(row);
|
||||||
else
|
else
|
||||||
PowerPC::debug_interface.UpdateWatchName(row, item->text().toStdString());
|
PowerPC::debug_interface.UpdateWatchName(row, item->text().toStdString());
|
||||||
break;
|
break;
|
||||||
// Address
|
case COLUMN_INDEX_ADDRESS:
|
||||||
// Hexadecimal
|
case COLUMN_INDEX_HEX:
|
||||||
// Decimal
|
case COLUMN_INDEX_DECIMAL:
|
||||||
case 1:
|
|
||||||
case 2:
|
|
||||||
case 3:
|
|
||||||
{
|
{
|
||||||
bool good;
|
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 (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);
|
PowerPC::debug_interface.UpdateWatchAddress(row, value);
|
||||||
|
if (watch.locked)
|
||||||
|
LockWatchAddress(value);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
PowerPC::HostWrite_U32(value, PowerPC::debug_interface.GetWatch(row).address);
|
PowerPC::HostWrite_U32(value, PowerPC::debug_interface.GetWatch(row).address);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -379,15 +400,68 @@ void WatchWidget::OnItemChanged(QTableWidgetItem* item)
|
|||||||
}
|
}
|
||||||
break;
|
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();
|
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)
|
void WatchWidget::DeleteWatch(int row)
|
||||||
{
|
{
|
||||||
|
PowerPC::debug_interface.UnsetPatch(PowerPC::debug_interface.GetWatch(row).address);
|
||||||
PowerPC::debug_interface.RemoveWatch(row);
|
PowerPC::debug_interface.RemoveWatch(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WatchWidget::DeleteWatchAndUpdate(int row)
|
||||||
|
{
|
||||||
|
DeleteWatch(row);
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,3 +480,41 @@ void WatchWidget::AddWatch(QString name, u32 addr)
|
|||||||
PowerPC::debug_interface.SetWatch(addr, name.toStdString());
|
PowerPC::debug_interface.SetWatch(addr, name.toStdString());
|
||||||
Update();
|
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 ShowContextMenu();
|
||||||
void OnItemChanged(QTableWidgetItem* item);
|
void OnItemChanged(QTableWidgetItem* item);
|
||||||
|
void LockWatchAddress(u32 address);
|
||||||
|
void DeleteSelectedWatches();
|
||||||
void DeleteWatch(int row);
|
void DeleteWatch(int row);
|
||||||
|
void DeleteWatchAndUpdate(int row);
|
||||||
void AddWatchBreakpoint(int row);
|
void AddWatchBreakpoint(int row);
|
||||||
void ShowInMemory(int row);
|
void ShowInMemory(int row);
|
||||||
void UpdateIcons();
|
void UpdateIcons();
|
||||||
|
void LockSelectedWatches();
|
||||||
|
void UnlockSelectedWatches();
|
||||||
|
|
||||||
QAction* m_new;
|
QAction* m_new;
|
||||||
QAction* m_delete;
|
QAction* m_delete;
|
||||||
@ -61,5 +66,12 @@ private:
|
|||||||
|
|
||||||
bool m_updating = false;
|
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,
|
connect(m_breakpoint_widget, &BreakpointWidget::ShowMemory, m_memory_widget,
|
||||||
&MemoryWidget::SetAddress);
|
&MemoryWidget::SetAddress);
|
||||||
connect(m_cheats_manager, &CheatsManager::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()
|
void MainWindow::ConnectMenuBar()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user