// 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/Assert.h" #include "Common/StringUtil.h" #include "Core/AchievementManager.h" #include "Core/Core.h" #include "Core/HW/Memmap.h" #include "Core/PowerPC/MMU.h" #include "Core/PowerPC/PowerPC.h" #include "Core/System.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 {std::bit_cast(std::get(value.m_value))}; case Cheats::DataType::S16: return ToByteVector(Common::swap16(std::bit_cast(std::get(value.m_value)))); case Cheats::DataType::S32: return ToByteVector(Common::swap32(std::bit_cast(std::get(value.m_value)))); case Cheats::DataType::S64: return ToByteVector(Common::swap64(std::bit_cast(std::get(value.m_value)))); case Cheats::DataType::F32: return ToByteVector(Common::swap32(std::bit_cast(std::get(value.m_value)))); case Cheats::DataType::F64: return ToByteVector(Common::swap64(std::bit_cast(std::get(value.m_value)))); default: DEBUG_ASSERT(false); return {}; } } namespace { template static std::optional> TryReadValueFromEmulatedMemory(const Core::CPUThreadGuard& guard, u32 addr, PowerPC::RequestedAddressSpace space); template <> std::optional> TryReadValueFromEmulatedMemory(const Core::CPUThreadGuard& guard, u32 addr, PowerPC::RequestedAddressSpace space) { return PowerPC::MMU::HostTryReadU8(guard, addr, space); } template <> std::optional> TryReadValueFromEmulatedMemory(const Core::CPUThreadGuard& guard, u32 addr, PowerPC::RequestedAddressSpace space) { return PowerPC::MMU::HostTryReadU16(guard, addr, space); } template <> std::optional> TryReadValueFromEmulatedMemory(const Core::CPUThreadGuard& guard, u32 addr, PowerPC::RequestedAddressSpace space) { return PowerPC::MMU::HostTryReadU32(guard, addr, space); } template <> std::optional> TryReadValueFromEmulatedMemory(const Core::CPUThreadGuard& guard, u32 addr, PowerPC::RequestedAddressSpace space) { return PowerPC::MMU::HostTryReadU64(guard, addr, space); } template <> std::optional> TryReadValueFromEmulatedMemory(const Core::CPUThreadGuard& guard, u32 addr, PowerPC::RequestedAddressSpace space) { auto tmp = PowerPC::MMU::HostTryReadU8(guard, addr, space); if (!tmp) return std::nullopt; return PowerPC::ReadResult(tmp->translated, std::bit_cast(tmp->value)); } template <> std::optional> TryReadValueFromEmulatedMemory(const Core::CPUThreadGuard& guard, u32 addr, PowerPC::RequestedAddressSpace space) { auto tmp = PowerPC::MMU::HostTryReadU16(guard, addr, space); if (!tmp) return std::nullopt; return PowerPC::ReadResult(tmp->translated, std::bit_cast(tmp->value)); } template <> std::optional> TryReadValueFromEmulatedMemory(const Core::CPUThreadGuard& guard, u32 addr, PowerPC::RequestedAddressSpace space) { auto tmp = PowerPC::MMU::HostTryReadU32(guard, addr, space); if (!tmp) return std::nullopt; return PowerPC::ReadResult(tmp->translated, std::bit_cast(tmp->value)); } template <> std::optional> TryReadValueFromEmulatedMemory(const Core::CPUThreadGuard& guard, u32 addr, PowerPC::RequestedAddressSpace space) { auto tmp = PowerPC::MMU::HostTryReadU64(guard, addr, space); if (!tmp) return std::nullopt; return PowerPC::ReadResult(tmp->translated, std::bit_cast(tmp->value)); } template <> std::optional> TryReadValueFromEmulatedMemory(const Core::CPUThreadGuard& guard, u32 addr, PowerPC::RequestedAddressSpace space) { return PowerPC::MMU::HostTryReadF32(guard, addr, space); } template <> std::optional> TryReadValueFromEmulatedMemory(const Core::CPUThreadGuard& guard, u32 addr, PowerPC::RequestedAddressSpace space) { return PowerPC::MMU::HostTryReadF64(guard, addr, space); } } // namespace template Common::Result>> Cheats::NewSearch(const Core::CPUThreadGuard& guard, const std::vector& memory_ranges, PowerPC::RequestedAddressSpace address_space, bool aligned, const std::function& validator) { if (AchievementManager::GetInstance().IsHardcoreModeActive()) return Cheats::SearchErrorCode::DisabledInHardcoreMode; auto& system = guard.GetSystem(); std::vector> results; const Core::State core_state = Core::GetState(system); if (core_state != Core::State::Running && core_state != Core::State::Paused) return Cheats::SearchErrorCode::NoEmulationActive; const auto& ppc_state = system.GetPPCState(); if (address_space == PowerPC::RequestedAddressSpace::Virtual && !ppc_state.msr.DR) return Cheats::SearchErrorCode::VirtualAddressesCurrentlyNotAccessible; for (const Cheats::MemoryRange& range : memory_ranges) { if (range.m_length < sizeof(T)) continue; const u32 increment_per_loop = aligned ? sizeof(T) : 1; const u32 start_address = aligned ? Common::AlignUp(range.m_start, sizeof(T)) : range.m_start; const u64 aligned_length = range.m_length - (start_address - range.m_start); if (aligned_length < sizeof(T)) continue; const u64 length = aligned_length - (sizeof(T) - 1); for (u64 i = 0; i < length; i += increment_per_loop) { const u32 addr = start_address + i; const auto current_value = TryReadValueFromEmulatedMemory(guard, 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; } } } return results; } template Common::Result>> Cheats::NextSearch(const Core::CPUThreadGuard& guard, const std::vector>& previous_results, PowerPC::RequestedAddressSpace address_space, const std::function& validator) { if (AchievementManager::GetInstance().IsHardcoreModeActive()) return Cheats::SearchErrorCode::DisabledInHardcoreMode; auto& system = guard.GetSystem(); std::vector> results; const Core::State core_state = Core::GetState(system); if (core_state != Core::State::Running && core_state != Core::State::Paused) return Cheats::SearchErrorCode::NoEmulationActive; const auto& ppc_state = system.GetPPCState(); if (address_space == PowerPC::RequestedAddressSpace::Virtual && !ppc_state.msr.DR) return Cheats::SearchErrorCode::VirtualAddressesCurrentlyNotAccessible; for (const auto& previous_result : previous_results) { const u32 addr = previous_result.m_address; const auto current_value = TryReadValueFromEmulatedMemory(guard, 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; } } return results; } 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, bool force_parse_as_hex) { if (str.empty()) return std::nullopt; T tmp; if constexpr (std::is_integral_v) { if (TryParse(str, &tmp, force_parse_as_hex ? 16 : 0)) return tmp; } else { if (TryParse(str, &tmp)) return tmp; } return std::nullopt; } template bool Cheats::CheatSearchSession::SetValueFromString(const std::string& value_as_string, bool force_parse_as_hex) { m_value = ParseValue(value_as_string, force_parse_as_hex); 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: DEBUG_ASSERT(false); return nullptr; } } template static std::function MakeCompareFunctionForLastValue(Cheats::CompareType op) { switch (op) { case Cheats::CompareType::Equal: return std::equal_to(); case Cheats::CompareType::NotEqual: return std::not_equal_to(); case Cheats::CompareType::Less: return std::less(); case Cheats::CompareType::LessOrEqual: return std::less_equal(); case Cheats::CompareType::Greater: return std::greater(); case Cheats::CompareType::GreaterOrEqual: return std::greater_equal(); default: DEBUG_ASSERT(false); return nullptr; } } template Cheats::SearchErrorCode Cheats::CheatSearchSession::RunSearch(const Core::CPUThreadGuard& guard) { if (AchievementManager::GetInstance().IsHardcoreModeActive()) return Cheats::SearchErrorCode::DisabledInHardcoreMode; 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( guard, m_search_results, m_address_space, [&func](const T& new_value, const T& old_value) { return func(new_value); }); } else { result = Cheats::NewSearch(guard, 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(guard, 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(guard, m_search_results, m_address_space, [](const T& v1, const T& v2) { return true; }); } else { result = Cheats::NewSearch(guard, 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() const { return m_memory_ranges.size(); } template Cheats::MemoryRange Cheats::CheatSearchSession::GetMemoryRange(size_t index) const { return m_memory_ranges[index]; } template PowerPC::RequestedAddressSpace Cheats::CheatSearchSession::GetAddressSpace() const { return m_address_space; } template Cheats::DataType Cheats::CheatSearchSession::GetDataType() const { return Cheats::GetDataType(Cheats::SearchValue{T(0)}); } template bool Cheats::CheatSearchSession::GetAligned() const { return m_aligned; } template bool Cheats::CheatSearchSession::IsIntegerType() const { return std::is_integral_v; } template bool Cheats::CheatSearchSession::IsFloatingType() const { return std::is_floating_point_v; } 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}", std::bit_cast(m_search_results[index].m_value)); } else if constexpr (std::is_same_v) { return fmt::format("0x{0:016x}", std::bit_cast(m_search_results[index].m_value)); } else { return fmt::format("0x{0:0{1}x}", std::bit_cast>(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 size_t begin_index, const size_t end_index) const { if (begin_index == 0 && end_index >= m_search_results.size()) return Clone(); auto c = std::make_unique>(m_memory_ranges, m_address_space, m_aligned); c->m_search_results.assign(m_search_results.begin() + begin_index, m_search_results.begin() + end_index); 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: DEBUG_ASSERT(false); return nullptr; } }