diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 7f6e6cc838..b22f523811 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -15,6 +15,10 @@ add_library(core BootManager.cpp BootManager.h CheatCodes.h + CheatGeneration.cpp + CheatGeneration.h + CheatSearch.cpp + CheatSearch.h CommonTitles.h Config/DefaultLocale.cpp Config/DefaultLocale.h diff --git a/Source/Core/Core/CheatGeneration.cpp b/Source/Core/Core/CheatGeneration.cpp new file mode 100644 index 0000000000..8011a879ad --- /dev/null +++ b/Source/Core/Core/CheatGeneration.cpp @@ -0,0 +1,77 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include + +#include "Core/CheatGeneration.h" + +#include "Common/Align.h" +#include "Common/CommonTypes.h" +#include "Common/Result.h" +#include "Common/Swap.h" + +#include "Core/ActionReplay.h" +#include "Core/CheatSearch.h" + +constexpr int AR_SET_BYTE_CMD = 0x00; +constexpr int AR_SET_SHORT_CMD = 0x02; +constexpr int AR_SET_INT_CMD = 0x04; + +static std::vector ResultToAREntries(u32 addr, const Cheats::SearchValue& sv) +{ + std::vector codes; + std::vector data = Cheats::GetValueAsByteVector(sv); + + for (size_t i = 0; i < data.size(); ++i) + { + const u32 address = (addr + i) & 0x01ff'ffffu; + if (Common::AlignUp(address, 4) == address && i + 3 < data.size()) + { + const u8 cmd = AR_SET_INT_CMD; + const u32 val = Common::swap32(&data[i]); + codes.emplace_back((cmd << 24) | address, val); + i += 3; + } + else if (Common::AlignUp(address, 2) == address && i + 1 < data.size()) + { + const u8 cmd = AR_SET_SHORT_CMD; + const u32 val = Common::swap16(&data[i]); + codes.emplace_back((cmd << 24) | address, val); + ++i; + } + else + { + const u8 cmd = AR_SET_BYTE_CMD; + const u32 val = data[i]; + codes.emplace_back((cmd << 24) | address, val); + } + } + + return codes; +} + +Common::Result +Cheats::GenerateActionReplayCode(const Cheats::CheatSearchSessionBase& session, size_t index) +{ + if (index >= session.GetResultCount()) + return Cheats::GenerateActionReplayCodeErrorCode::IndexOutOfRange; + + if (session.GetResultValueState(index) != Cheats::SearchResultValueState::ValueFromVirtualMemory) + return Cheats::GenerateActionReplayCodeErrorCode::NotVirtualMemory; + + u32 address = session.GetResultAddress(index); + + // check if the address is actually addressable by the ActionReplay system + if (((address & 0x01ff'ffffu) | 0x8000'0000u) != address) + return Cheats::GenerateActionReplayCodeErrorCode::InvalidAddress; + + ActionReplay::ARCode ar_code; + ar_code.enabled = true; + ar_code.user_defined = true; + ar_code.name = fmt::format("Generated by Cheat Search (Address 0x{:08x})", address); + ar_code.ops = ResultToAREntries(address, session.GetResultValueAsSearchValue(index)); + + return ar_code; +} diff --git a/Source/Core/Core/CheatGeneration.h b/Source/Core/Core/CheatGeneration.h new file mode 100644 index 0000000000..f6fda3a221 --- /dev/null +++ b/Source/Core/Core/CheatGeneration.h @@ -0,0 +1,23 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Common/Result.h" + +#include "Core/ActionReplay.h" + +namespace Cheats +{ +class CheatSearchSessionBase; + +enum class GenerateActionReplayCodeErrorCode +{ + IndexOutOfRange, + NotVirtualMemory, + InvalidAddress, +}; + +Common::Result +GenerateActionReplayCode(const Cheats::CheatSearchSessionBase& session, size_t index); +} // namespace Cheats diff --git a/Source/Core/Core/CheatSearch.cpp b/Source/Core/Core/CheatSearch.cpp new file mode 100644 index 0000000000..f83b7f8b9d --- /dev/null +++ b/Source/Core/Core/CheatSearch.cpp @@ -0,0 +1,640 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/CheatSearch.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Common/Align.h" +#include "Common/BitUtils.h" +#include "Common/StringUtil.h" + +#include "Core/Core.h" +#include "Core/HW/Memmap.h" +#include "Core/PowerPC/MMU.h" +#include "Core/PowerPC/PowerPC.h" + +Cheats::DataType Cheats::GetDataType(const Cheats::SearchValue& value) +{ + // sanity checks that our enum matches with our std::variant + using should_be_u8 = std::remove_cv_t< + std::remove_reference_t(DataType::U8)>(value.m_value))>>; + using should_be_u16 = std::remove_cv_t< + std::remove_reference_t(DataType::U16)>(value.m_value))>>; + using should_be_u32 = std::remove_cv_t< + std::remove_reference_t(DataType::U32)>(value.m_value))>>; + using should_be_u64 = std::remove_cv_t< + std::remove_reference_t(DataType::U64)>(value.m_value))>>; + using should_be_s8 = std::remove_cv_t< + std::remove_reference_t(DataType::S8)>(value.m_value))>>; + using should_be_s16 = std::remove_cv_t< + std::remove_reference_t(DataType::S16)>(value.m_value))>>; + using should_be_s32 = std::remove_cv_t< + std::remove_reference_t(DataType::S32)>(value.m_value))>>; + using should_be_s64 = std::remove_cv_t< + std::remove_reference_t(DataType::S64)>(value.m_value))>>; + using should_be_f32 = std::remove_cv_t< + std::remove_reference_t(DataType::F32)>(value.m_value))>>; + using should_be_f64 = std::remove_cv_t< + std::remove_reference_t(DataType::F64)>(value.m_value))>>; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + return static_cast(value.m_value.index()); +} + +template +static std::vector ToByteVector(const T& val) +{ + static_assert(std::is_trivially_copyable_v); + const auto* const begin = reinterpret_cast(&val); + const auto* const end = begin + sizeof(T); + return {begin, end}; +} + +std::vector Cheats::GetValueAsByteVector(const Cheats::SearchValue& value) +{ + DataType type = GetDataType(value); + switch (type) + { + case Cheats::DataType::U8: + return {std::get(value.m_value)}; + case Cheats::DataType::U16: + return ToByteVector(Common::swap16(std::get(value.m_value))); + case Cheats::DataType::U32: + return ToByteVector(Common::swap32(std::get(value.m_value))); + case Cheats::DataType::U64: + return ToByteVector(Common::swap64(std::get(value.m_value))); + case Cheats::DataType::S8: + return {Common::BitCast(std::get(value.m_value))}; + case Cheats::DataType::S16: + return ToByteVector(Common::swap16(Common::BitCast(std::get(value.m_value)))); + case Cheats::DataType::S32: + return ToByteVector(Common::swap32(Common::BitCast(std::get(value.m_value)))); + case Cheats::DataType::S64: + return ToByteVector(Common::swap64(Common::BitCast(std::get(value.m_value)))); + case Cheats::DataType::F32: + return ToByteVector(Common::swap32(Common::BitCast(std::get(value.m_value)))); + case Cheats::DataType::F64: + return ToByteVector(Common::swap64(Common::BitCast(std::get(value.m_value)))); + default: + assert(0); + return {}; + } +} + +namespace +{ +template +static PowerPC::TryReadResult +TryReadValueFromEmulatedMemory(u32 addr, PowerPC::RequestedAddressSpace space); + +template <> +PowerPC::TryReadResult TryReadValueFromEmulatedMemory(u32 addr, + PowerPC::RequestedAddressSpace space) +{ + return PowerPC::HostTryReadU8(addr, space); +} + +template <> +PowerPC::TryReadResult TryReadValueFromEmulatedMemory(u32 addr, + PowerPC::RequestedAddressSpace space) +{ + return PowerPC::HostTryReadU16(addr, space); +} + +template <> +PowerPC::TryReadResult TryReadValueFromEmulatedMemory(u32 addr, + PowerPC::RequestedAddressSpace space) +{ + return PowerPC::HostTryReadU32(addr, space); +} + +template <> +PowerPC::TryReadResult TryReadValueFromEmulatedMemory(u32 addr, + PowerPC::RequestedAddressSpace space) +{ + return PowerPC::HostTryReadU64(addr, space); +} + +template <> +PowerPC::TryReadResult TryReadValueFromEmulatedMemory(u32 addr, + PowerPC::RequestedAddressSpace space) +{ + auto tmp = PowerPC::HostTryReadU8(addr, space); + if (!tmp) + return PowerPC::TryReadResult(); + return PowerPC::TryReadResult(tmp.translated, Common::BitCast(tmp.value)); +} + +template <> +PowerPC::TryReadResult TryReadValueFromEmulatedMemory(u32 addr, + PowerPC::RequestedAddressSpace space) +{ + auto tmp = PowerPC::HostTryReadU16(addr, space); + if (!tmp) + return PowerPC::TryReadResult(); + return PowerPC::TryReadResult(tmp.translated, Common::BitCast(tmp.value)); +} + +template <> +PowerPC::TryReadResult TryReadValueFromEmulatedMemory(u32 addr, + PowerPC::RequestedAddressSpace space) +{ + auto tmp = PowerPC::HostTryReadU32(addr, space); + if (!tmp) + return PowerPC::TryReadResult(); + return PowerPC::TryReadResult(tmp.translated, Common::BitCast(tmp.value)); +} + +template <> +PowerPC::TryReadResult TryReadValueFromEmulatedMemory(u32 addr, + PowerPC::RequestedAddressSpace space) +{ + auto tmp = PowerPC::HostTryReadU64(addr, space); + if (!tmp) + return PowerPC::TryReadResult(); + return PowerPC::TryReadResult(tmp.translated, Common::BitCast(tmp.value)); +} + +template <> +PowerPC::TryReadResult TryReadValueFromEmulatedMemory(u32 addr, + PowerPC::RequestedAddressSpace space) +{ + return PowerPC::HostTryReadF32(addr, space); +} + +template <> +PowerPC::TryReadResult TryReadValueFromEmulatedMemory(u32 addr, + PowerPC::RequestedAddressSpace space) +{ + return PowerPC::HostTryReadF64(addr, space); +} +} // namespace + +template +Common::Result>> +Cheats::NewSearch(const std::vector& memory_ranges, + PowerPC::RequestedAddressSpace address_space, bool aligned, + const std::function& validator) +{ + const u32 data_size = sizeof(T); + std::vector> results; + Cheats::SearchErrorCode error_code = Cheats::SearchErrorCode::Success; + Core::RunAsCPUThread([&] { + const Core::State core_state = Core::GetState(); + if (core_state != Core::State::Running && core_state != Core::State::Paused) + { + error_code = Cheats::SearchErrorCode::NoEmulationActive; + return; + } + + if (address_space == PowerPC::RequestedAddressSpace::Virtual && !MSR.DR) + { + error_code = Cheats::SearchErrorCode::VirtualAddressesCurrentlyNotAccessible; + return; + } + + for (const Cheats::MemoryRange& range : memory_ranges) + { + const u32 increment_per_loop = aligned ? data_size : 1; + const u32 start_address = aligned ? Common::AlignUp(range.m_start, data_size) : range.m_start; + const u64 aligned_length = range.m_length - (start_address - range.m_start); + const u64 length = aligned_length - (data_size - 1); + for (u64 i = 0; i < length; i += increment_per_loop) + { + const u32 addr = start_address + i; + const auto current_value = TryReadValueFromEmulatedMemory(addr, address_space); + if (!current_value) + continue; + + if (validator(current_value.value)) + { + auto& r = results.emplace_back(); + r.m_value = current_value.value; + r.m_value_state = current_value.translated ? + Cheats::SearchResultValueState::ValueFromVirtualMemory : + Cheats::SearchResultValueState::ValueFromPhysicalMemory; + r.m_address = addr; + } + } + } + }); + if (error_code == Cheats::SearchErrorCode::Success) + return results; + return error_code; +} + +template +Common::Result>> +Cheats::NextSearch(const std::vector>& previous_results, + PowerPC::RequestedAddressSpace address_space, + const std::function& validator) +{ + std::vector> results; + Cheats::SearchErrorCode error_code = Cheats::SearchErrorCode::Success; + Core::RunAsCPUThread([&] { + const Core::State core_state = Core::GetState(); + if (core_state != Core::State::Running && core_state != Core::State::Paused) + { + error_code = Cheats::SearchErrorCode::NoEmulationActive; + return; + } + + if (address_space == PowerPC::RequestedAddressSpace::Virtual && !MSR.DR) + { + error_code = Cheats::SearchErrorCode::VirtualAddressesCurrentlyNotAccessible; + return; + } + + for (const auto& previous_result : previous_results) + { + const u32 addr = previous_result.m_address; + const auto current_value = TryReadValueFromEmulatedMemory(addr, address_space); + if (!current_value) + { + auto& r = results.emplace_back(); + r.m_address = addr; + r.m_value_state = Cheats::SearchResultValueState::AddressNotAccessible; + continue; + } + + // if the previous state was invalid we always update the value to avoid getting stuck in an + // invalid state + if (!previous_result.IsValueValid() || + validator(current_value.value, previous_result.m_value)) + { + auto& r = results.emplace_back(); + r.m_value = current_value.value; + r.m_value_state = current_value.translated ? + Cheats::SearchResultValueState::ValueFromVirtualMemory : + Cheats::SearchResultValueState::ValueFromPhysicalMemory; + r.m_address = addr; + } + } + }); + if (error_code == Cheats::SearchErrorCode::Success) + return results; + return error_code; +} + +Cheats::CheatSearchSessionBase::~CheatSearchSessionBase() = default; + +template +Cheats::CheatSearchSession::CheatSearchSession(std::vector memory_ranges, + PowerPC::RequestedAddressSpace address_space, + bool aligned) + : m_memory_ranges(std::move(memory_ranges)), m_address_space(address_space), m_aligned(aligned) +{ +} + +template +Cheats::CheatSearchSession::CheatSearchSession(const CheatSearchSession& session) = default; + +template +Cheats::CheatSearchSession::CheatSearchSession(CheatSearchSession&& session) = default; + +template +Cheats::CheatSearchSession& +Cheats::CheatSearchSession::operator=(const CheatSearchSession& session) = default; + +template +Cheats::CheatSearchSession& +Cheats::CheatSearchSession::operator=(CheatSearchSession&& session) = default; + +template +Cheats::CheatSearchSession::~CheatSearchSession() = default; + +template +void Cheats::CheatSearchSession::SetCompareType(CompareType compare_type) +{ + m_compare_type = compare_type; +} + +template +void Cheats::CheatSearchSession::SetFilterType(FilterType filter_type) +{ + m_filter_type = filter_type; +} + +template +static std::optional ParseValue(const std::string& str) +{ + if (str.empty()) + return std::nullopt; + + T tmp; + if (TryParse(str, &tmp)) + return tmp; + + return std::nullopt; +} + +template +bool Cheats::CheatSearchSession::SetValueFromString(const std::string& value_as_string) +{ + m_value = ParseValue(value_as_string); + return m_value.has_value(); +} + +template +void Cheats::CheatSearchSession::ResetResults() +{ + m_first_search_done = false; + m_search_results.clear(); +} + +template +static std::function +MakeCompareFunctionForSpecificValue(Cheats::CompareType op, const T& old_value) +{ + switch (op) + { + case Cheats::CompareType::Equal: + return [&](const T& new_value) { return new_value == old_value; }; + case Cheats::CompareType::NotEqual: + return [&](const T& new_value) { return new_value != old_value; }; + case Cheats::CompareType::Less: + return [&](const T& new_value) { return new_value < old_value; }; + case Cheats::CompareType::LessOrEqual: + return [&](const T& new_value) { return new_value <= old_value; }; + case Cheats::CompareType::Greater: + return [&](const T& new_value) { return new_value > old_value; }; + case Cheats::CompareType::GreaterOrEqual: + return [&](const T& new_value) { return new_value >= old_value; }; + default: + assert(0); + return nullptr; + } +} + +template +static std::function +MakeCompareFunctionForLastValue(Cheats::CompareType op) +{ + switch (op) + { + case Cheats::CompareType::Equal: + return [](const T& new_value, const T& old_value) { return new_value == old_value; }; + case Cheats::CompareType::NotEqual: + return [](const T& new_value, const T& old_value) { return new_value != old_value; }; + case Cheats::CompareType::Less: + return [](const T& new_value, const T& old_value) { return new_value < old_value; }; + case Cheats::CompareType::LessOrEqual: + return [](const T& new_value, const T& old_value) { return new_value <= old_value; }; + case Cheats::CompareType::Greater: + return [](const T& new_value, const T& old_value) { return new_value > old_value; }; + case Cheats::CompareType::GreaterOrEqual: + return [](const T& new_value, const T& old_value) { return new_value >= old_value; }; + default: + assert(0); + return nullptr; + } +} + +template +Cheats::SearchErrorCode Cheats::CheatSearchSession::RunSearch() +{ + Common::Result>> result = + Cheats::SearchErrorCode::InvalidParameters; + if (m_filter_type == FilterType::CompareAgainstSpecificValue) + { + if (!m_value) + return Cheats::SearchErrorCode::InvalidParameters; + + auto func = MakeCompareFunctionForSpecificValue(m_compare_type, *m_value); + if (m_first_search_done) + { + result = Cheats::NextSearch( + m_search_results, m_address_space, + [&func](const T& new_value, const T& old_value) { return func(new_value); }); + } + else + { + result = Cheats::NewSearch(m_memory_ranges, m_address_space, m_aligned, func); + } + } + else if (m_filter_type == FilterType::CompareAgainstLastValue) + { + if (!m_first_search_done) + return Cheats::SearchErrorCode::InvalidParameters; + + result = Cheats::NextSearch(m_search_results, m_address_space, + MakeCompareFunctionForLastValue(m_compare_type)); + } + else if (m_filter_type == FilterType::DoNotFilter) + { + if (m_first_search_done) + { + result = Cheats::NextSearch(m_search_results, m_address_space, + [](const T& v1, const T& v2) { return true; }); + } + else + { + result = Cheats::NewSearch(m_memory_ranges, m_address_space, m_aligned, + [](const T& v) { return true; }); + } + } + + if (result.Succeeded()) + { + m_search_results = std::move(*result); + m_first_search_done = true; + return Cheats::SearchErrorCode::Success; + } + + return result.Error(); +} + +template +size_t Cheats::CheatSearchSession::GetMemoryRangeCount() +{ + return m_memory_ranges.size(); +} + +template +Cheats::MemoryRange Cheats::CheatSearchSession::GetMemoryRange(size_t index) +{ + return m_memory_ranges[index]; +} + +template +PowerPC::RequestedAddressSpace Cheats::CheatSearchSession::GetAddressSpace() +{ + return m_address_space; +} + +template +Cheats::DataType Cheats::CheatSearchSession::GetDataType() +{ + return Cheats::GetDataType(Cheats::SearchValue{T(0)}); +} + +template +bool Cheats::CheatSearchSession::GetAligned() +{ + return m_aligned; +} + +template +size_t Cheats::CheatSearchSession::GetResultCount() const +{ + return m_search_results.size(); +} + +template +size_t Cheats::CheatSearchSession::GetValidValueCount() const +{ + const auto& results = m_search_results; + size_t count = 0; + for (const auto& r : results) + { + if (r.IsValueValid()) + ++count; + } + return count; +} + +template +u32 Cheats::CheatSearchSession::GetResultAddress(size_t index) const +{ + return m_search_results[index].m_address; +} + +template +T Cheats::CheatSearchSession::GetResultValue(size_t index) const +{ + return m_search_results[index].m_value; +} + +template +Cheats::SearchValue Cheats::CheatSearchSession::GetResultValueAsSearchValue(size_t index) const +{ + return Cheats::SearchValue{m_search_results[index].m_value}; +} + +template +std::string Cheats::CheatSearchSession::GetResultValueAsString(size_t index, bool hex) const +{ + if (GetResultValueState(index) == Cheats::SearchResultValueState::AddressNotAccessible) + return "(inaccessible)"; + + if (hex) + { + if constexpr (std::is_same_v) + return fmt::format("0x{0:08x}", Common::BitCast(m_search_results[index].m_value)); + else if constexpr (std::is_same_v) + return fmt::format("0x{0:016x}", Common::BitCast(m_search_results[index].m_value)); + else + return fmt::format("0x{0:0{1}x}", m_search_results[index].m_value, sizeof(T) * 2); + } + + return fmt::format("{}", m_search_results[index].m_value); +} + +template +Cheats::SearchResultValueState +Cheats::CheatSearchSession::GetResultValueState(size_t index) const +{ + return m_search_results[index].m_value_state; +} + +template +bool Cheats::CheatSearchSession::WasFirstSearchDone() const +{ + return m_first_search_done; +} + +template +std::unique_ptr Cheats::CheatSearchSession::Clone() const +{ + return std::make_unique>(*this); +} + +template +std::unique_ptr +Cheats::CheatSearchSession::ClonePartial(const std::vector& result_indices) const +{ + const auto& results = m_search_results; + std::vector> partial_results; + partial_results.reserve(result_indices.size()); + for (size_t idx : result_indices) + partial_results.push_back(results[idx]); + + auto c = + std::make_unique>(m_memory_ranges, m_address_space, m_aligned); + c->m_search_results = std::move(partial_results); + c->m_compare_type = this->m_compare_type; + c->m_filter_type = this->m_filter_type; + c->m_value = this->m_value; + c->m_first_search_done = this->m_first_search_done; + return c; +} + +template class Cheats::CheatSearchSession; +template class Cheats::CheatSearchSession; +template class Cheats::CheatSearchSession; +template class Cheats::CheatSearchSession; +template class Cheats::CheatSearchSession; +template class Cheats::CheatSearchSession; +template class Cheats::CheatSearchSession; +template class Cheats::CheatSearchSession; +template class Cheats::CheatSearchSession; +template class Cheats::CheatSearchSession; + +std::unique_ptr +Cheats::MakeSession(std::vector memory_ranges, + PowerPC::RequestedAddressSpace address_space, bool aligned, DataType data_type) +{ + switch (data_type) + { + case Cheats::DataType::U8: + return std::make_unique>(std::move(memory_ranges), address_space, + aligned); + case Cheats::DataType::U16: + return std::make_unique>(std::move(memory_ranges), address_space, + aligned); + case Cheats::DataType::U32: + return std::make_unique>(std::move(memory_ranges), address_space, + aligned); + case Cheats::DataType::U64: + return std::make_unique>(std::move(memory_ranges), address_space, + aligned); + case Cheats::DataType::S8: + return std::make_unique>(std::move(memory_ranges), address_space, + aligned); + case Cheats::DataType::S16: + return std::make_unique>(std::move(memory_ranges), address_space, + aligned); + case Cheats::DataType::S32: + return std::make_unique>(std::move(memory_ranges), address_space, + aligned); + case Cheats::DataType::S64: + return std::make_unique>(std::move(memory_ranges), address_space, + aligned); + case Cheats::DataType::F32: + return std::make_unique>(std::move(memory_ranges), address_space, + aligned); + case Cheats::DataType::F64: + return std::make_unique>(std::move(memory_ranges), address_space, + aligned); + default: + assert(0); + return nullptr; + } +} diff --git a/Source/Core/Core/CheatSearch.h b/Source/Core/Core/CheatSearch.h new file mode 100644 index 0000000000..5b777f50f8 --- /dev/null +++ b/Source/Core/Core/CheatSearch.h @@ -0,0 +1,219 @@ +// Copyright 2021 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "Common/Result.h" +#include "Core/PowerPC/MMU.h" + +namespace Cheats +{ +enum class CompareType +{ + Equal, + NotEqual, + Less, + LessOrEqual, + Greater, + GreaterOrEqual, +}; + +enum class FilterType +{ + CompareAgainstSpecificValue, + CompareAgainstLastValue, + DoNotFilter, +}; + +enum class DataType +{ + U8, + U16, + U32, + U64, + S8, + S16, + S32, + S64, + F32, + F64, +}; + +struct SearchValue +{ + std::variant m_value; +}; + +enum class SearchResultValueState : u8 +{ + ValueFromPhysicalMemory, + ValueFromVirtualMemory, + AddressNotAccessible, +}; + +template +struct SearchResult +{ + T m_value; + SearchResultValueState m_value_state; + u32 m_address; + + bool IsValueValid() const + { + return m_value_state == SearchResultValueState::ValueFromPhysicalMemory || + m_value_state == SearchResultValueState::ValueFromVirtualMemory; + } +}; + +struct MemoryRange +{ + u32 m_start; + u64 m_length; + + MemoryRange(u32 start, u64 length) : m_start(start), m_length(length) {} +}; + +enum class SearchErrorCode +{ + Success, + + // No emulation is currently active. + NoEmulationActive, + + // The parameter set given to the search function is bogus. + InvalidParameters, + + // This is returned if PowerPC::RequestedAddressSpace::Virtual is given but the MSR.DR flag is + // currently off in the emulated game. + VirtualAddressesCurrentlyNotAccessible, +}; + +// Returns the corresponding DataType enum for the value currently held by the given SearchValue. +DataType GetDataType(const SearchValue& value); + +// Converts the given value to a std::vector, regardless of the actual stored value type. +// Returned array is in big endian byte order, so it can be copied directly into emulated memory for +// patches or action replay codes. +std::vector GetValueAsByteVector(const SearchValue& value); + +// Do a new search across the given memory region in the given address space, only keeping values +// for which the given validator returns true. +template +Common::Result>> +NewSearch(const std::vector& memory_ranges, + PowerPC::RequestedAddressSpace address_space, bool aligned, + const std::function& validator); + +// Refresh the values for the given results in the given address space, only keeping values for +// which the given validator returns true. +template +Common::Result>> +NextSearch(const std::vector>& previous_results, + PowerPC::RequestedAddressSpace address_space, + const std::function& validator); + +class CheatSearchSessionBase +{ +public: + virtual ~CheatSearchSessionBase(); + + // Set the value comparison operation used by subsequent searches. + virtual void SetCompareType(CompareType compare_type) = 0; + + // Set the filtering value target used by subsequent searches. + virtual void SetFilterType(FilterType filter_type) = 0; + + // Set the value of the CompareAgainstSpecificValue filter used by subsequent searches. + virtual bool SetValueFromString(const std::string& value_as_string) = 0; + + // Resets the search results, causing the next search to act as a new search. + virtual void ResetResults() = 0; + + // Run either a new search or a next search based on the current state of this session. + virtual SearchErrorCode RunSearch() = 0; + + virtual size_t GetMemoryRangeCount() = 0; + virtual MemoryRange GetMemoryRange(size_t index) = 0; + virtual PowerPC::RequestedAddressSpace GetAddressSpace() = 0; + virtual DataType GetDataType() = 0; + virtual bool GetAligned() = 0; + + virtual size_t GetResultCount() const = 0; + virtual size_t GetValidValueCount() const = 0; + virtual u32 GetResultAddress(size_t index) const = 0; + virtual SearchValue GetResultValueAsSearchValue(size_t index) const = 0; + virtual std::string GetResultValueAsString(size_t index, bool hex) const = 0; + virtual SearchResultValueState GetResultValueState(size_t index) const = 0; + virtual bool WasFirstSearchDone() const = 0; + + // Create a complete copy of this search session. + virtual std::unique_ptr Clone() const = 0; + + // Create a partial copy of this search session. Only the results with the passed indices are + // copied. This is useful if you want to run a next search on only partial result data of a + // previous search. + virtual std::unique_ptr + ClonePartial(const std::vector& result_indices) const = 0; +}; + +template +class CheatSearchSession : public CheatSearchSessionBase +{ +public: + CheatSearchSession(std::vector memory_ranges, + PowerPC::RequestedAddressSpace address_space, bool aligned); + CheatSearchSession(const CheatSearchSession& session); + CheatSearchSession(CheatSearchSession&& session); + CheatSearchSession& operator=(const CheatSearchSession& session); + CheatSearchSession& operator=(CheatSearchSession&& session); + ~CheatSearchSession() override; + + void SetCompareType(CompareType compare_type) override; + void SetFilterType(FilterType filter_type) override; + bool SetValueFromString(const std::string& value_as_string) override; + + void ResetResults() override; + SearchErrorCode RunSearch() override; + + size_t GetMemoryRangeCount() override; + MemoryRange GetMemoryRange(size_t index) override; + PowerPC::RequestedAddressSpace GetAddressSpace() override; + DataType GetDataType() override; + bool GetAligned() override; + + size_t GetResultCount() const override; + size_t GetValidValueCount() const override; + u32 GetResultAddress(size_t index) const override; + T GetResultValue(size_t index) const; + SearchValue GetResultValueAsSearchValue(size_t index) const override; + std::string GetResultValueAsString(size_t index, bool hex) const override; + SearchResultValueState GetResultValueState(size_t index) const override; + bool WasFirstSearchDone() const override; + + std::unique_ptr Clone() const override; + std::unique_ptr + ClonePartial(const std::vector& result_indices) const override; + +private: + std::vector> m_search_results; + std::vector m_memory_ranges; + PowerPC::RequestedAddressSpace m_address_space; + CompareType m_compare_type = CompareType::Equal; + FilterType m_filter_type = FilterType::DoNotFilter; + std::optional m_value = std::nullopt; + bool m_aligned; + bool m_first_search_done = false; +}; + +std::unique_ptr MakeSession(std::vector memory_ranges, + PowerPC::RequestedAddressSpace address_space, + bool aligned, DataType data_type); +} // namespace Cheats diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 02c58f2193..6b83842839 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -163,6 +163,8 @@ + + @@ -743,6 +745,8 @@ + +