Skip to content
Permalink
Browse files
Merge pull request #11365 from iwubcode/cheat_manager_freeze_value
DolphinQt: add ability to lock / freeze values in the watches window
  • Loading branch information
AdmiralCurtiss committed Jan 9, 2023
2 parents 21c29ba + 993d2ab commit 653e0cc
Show file tree
Hide file tree
Showing 17 changed files with 309 additions and 40 deletions.
@@ -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,14 +98,18 @@ bool MemoryPatches::HasEnabledPatch(u32 address) const
void MemoryPatches::RemovePatch(std::size_t index)
{
DisablePatch(index);
UnPatch(index);
m_patches.erase(m_patches.begin() + index);
}

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,16 +42,20 @@ class MemoryPatches

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);
void DisablePatch(std::size_t index);
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 @@ class Watches
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 @@ class DebugInterface
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,13 +44,16 @@ class DebugInterface
// 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;
virtual void DisablePatch(std::size_t index) = 0;
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 @@ class PPCDebugInterface final : public Common::DebugInterface
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,13 +52,16 @@ class PPCDebugInterface final : public Common::DebugInterface
// 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;
void DisablePatch(std::size_t index) override;
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,20 +11,24 @@
#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"

#include "Core/ActionReplay.h"
#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());

0 comments on commit 653e0cc

Please sign in to comment.