From c5091bfe00c241b6432367aeaea020d4e3d40d28 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Sat, 22 Dec 2018 21:28:56 -0500 Subject: patch_manager: Add support for loading cheats lists Uses load///cheats as root dir, file name is all upper or lower hex first 8 bytes build ID. --- src/core/file_sys/patch_manager.cpp | 52 +++++++++++++++++++++++++++++++++++++ src/core/file_sys/patch_manager.h | 4 +++ 2 files changed, 56 insertions(+) (limited to 'src/core/file_sys') diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 61706966e..f0fb28e2f 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -7,6 +7,7 @@ #include #include +#include "common/file_util.h" #include "common/hex_util.h" #include "common/logging/log.h" #include "core/file_sys/content_archive.h" @@ -232,6 +233,57 @@ bool PatchManager::HasNSOPatch(const std::array& build_id_) const { return !CollectPatches(patch_dirs, build_id).empty(); } +static std::optional ReadCheatFileFromFolder(u64 title_id, + const std::array& build_id_, + const VirtualDir& base_path, bool upper) { + const auto build_id_raw = Common::HexArrayToString(build_id_, upper); + const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2); + const auto file = base_path->GetFile(fmt::format("{}.txt", build_id)); + + if (file == nullptr) { + LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}", + title_id, build_id); + return std::nullopt; + } + + std::vector data(file->GetSize()); + if (file->Read(data.data(), data.size()) != data.size()) { + LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}", + title_id, build_id); + return std::nullopt; + } + + TextCheatParser parser; + return parser.Parse(data); +} + +std::vector PatchManager::CreateCheatList(const std::array& build_id_) const { + std::vector out; + + const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); + auto patch_dirs = load_dir->GetSubdirectories(); + std::sort(patch_dirs.begin(), patch_dirs.end(), + [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); + + out.reserve(patch_dirs.size()); + for (const auto& subdir : patch_dirs) { + auto cheats_dir = subdir->GetSubdirectory("cheats"); + if (cheats_dir != nullptr) { + auto res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true); + if (res.has_value()) { + out.push_back(std::move(*res)); + continue; + } + + res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false); + if (res.has_value()) + out.push_back(std::move(*res)); + } + } + + return out; +} + static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) { const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index b8a1652fd..3e3ac6aca 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -8,6 +8,7 @@ #include #include #include "common/common_types.h" +#include "core/file_sys/cheat_engine.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/vfs.h" @@ -45,6 +46,9 @@ public: // Used to prevent expensive copies in NSO loader. bool HasNSOPatch(const std::array& build_id) const; + // Creates a CheatList object with all + std::vector CreateCheatList(const std::array& build_id) const; + // Currently tracked RomFS patches: // - Game Updates // - LayeredFS -- cgit v1.2.3 From 4495bf5706d989e2dddaa80c3435b20698e6c11a Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Sat, 22 Dec 2018 21:29:15 -0500 Subject: patch_manager: Display cheats in game list add-ons --- src/core/file_sys/patch_manager.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/core/file_sys') diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index f0fb28e2f..2b09e5d35 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -455,6 +455,8 @@ std::map> PatchManager::GetPatchVersionNam } if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs"))) AppendCommaIfNotEmpty(types, "LayeredFS"); + if (IsDirValidAndNonEmpty(mod->GetSubdirectory("cheats"))) + AppendCommaIfNotEmpty(types, "Cheats"); if (types.empty()) continue; -- cgit v1.2.3 From 769b3466823d988d262b13325c6eb926770d0868 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Sat, 22 Dec 2018 21:31:38 -0500 Subject: cheat_engine: Add parser and interpreter for game cheats --- src/core/file_sys/cheat_engine.cpp | 487 +++++++++++++++++++++++++++++++++++++ src/core/file_sys/cheat_engine.h | 226 +++++++++++++++++ 2 files changed, 713 insertions(+) create mode 100644 src/core/file_sys/cheat_engine.cpp create mode 100644 src/core/file_sys/cheat_engine.h (limited to 'src/core/file_sys') diff --git a/src/core/file_sys/cheat_engine.cpp b/src/core/file_sys/cheat_engine.cpp new file mode 100644 index 000000000..e4383d8c1 --- /dev/null +++ b/src/core/file_sys/cheat_engine.cpp @@ -0,0 +1,487 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "common/hex_util.h" +#include "common/microprofile.h" +#include "common/swap.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/core_timing_util.h" +#include "core/file_sys/cheat_engine.h" +#include "core/hle/kernel/process.h" +#include "core/hle/service/hid/controllers/controller_base.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/hid/hid.h" +#include "core/hle/service/sm/sm.h" + +namespace FileSys { + +constexpr u64 CHEAT_ENGINE_TICKS = CoreTiming::BASE_CLOCK_RATE / 60; +constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF; + +u64 Cheat::Address() const { + u64 out; + std::memcpy(&out, raw.data(), sizeof(u64)); + return Common::swap64(out) & 0xFFFFFFFFFF; +} + +u64 Cheat::ValueWidth(u64 offset) const { + return Value(offset, width); +} + +u64 Cheat::Value(u64 offset, u64 width) const { + u64 out; + std::memcpy(&out, raw.data() + offset, sizeof(u64)); + out = Common::swap64(out); + if (width == 8) + return out; + return out & ((1ull << (width * CHAR_BIT)) - 1); +} + +u32 Cheat::KeypadValue() const { + u32 out; + std::memcpy(&out, raw.data(), sizeof(u32)); + return Common::swap32(out) & 0x0FFFFFFF; +} + +void CheatList::SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, + VAddr heap_end, MemoryWriter writer, MemoryReader reader) { + this->main_region_begin = main_begin; + this->main_region_end = main_end; + this->heap_region_begin = heap_begin; + this->heap_region_end = heap_end; + this->writer = writer; + this->reader = reader; +} + +MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70)); + +void CheatList::Execute() { + MICROPROFILE_SCOPE(Cheat_Engine); + + std::fill(scratch.begin(), scratch.end(), 0); + in_standard = false; + for (std::size_t i = 0; i < master_list.size(); ++i) { + LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, master_list[i].first); + current_block = i; + ExecuteBlock(master_list[i].second); + } + + in_standard = true; + for (std::size_t i = 0; i < standard_list.size(); ++i) { + LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, standard_list[i].first); + current_block = i; + ExecuteBlock(standard_list[i].second); + } +} + +CheatList::CheatList(ProgramSegment master, ProgramSegment standard) + : master_list(master), standard_list(standard) {} + +bool CheatList::EvaluateConditional(const Cheat& cheat) const { + using ComparisonFunction = bool (*)(u64, u64); + constexpr ComparisonFunction comparison_functions[] = { + [](u64 a, u64 b) { return a > b; }, [](u64 a, u64 b) { return a >= b; }, + [](u64 a, u64 b) { return a < b; }, [](u64 a, u64 b) { return a <= b; }, + [](u64 a, u64 b) { return a == b; }, [](u64 a, u64 b) { return a != b; }, + }; + + if (cheat.type == CodeType::ConditionalInput) { + const auto applet_resource = Core::System::GetInstance() + .ServiceManager() + .GetService("hid") + ->GetAppletResource(); + if (applet_resource == nullptr) { + LOG_WARNING( + Common_Filesystem, + "Attempted to evaluate input conditional, but applet resource is not initialized!"); + return false; + } + + const auto press_state = + applet_resource + ->GetController(Service::HID::HidController::NPad) + .GetPressState(); + return ((press_state & cheat.KeypadValue()) & KEYPAD_BITMASK) != 0; + } + + ASSERT(cheat.type == CodeType::Conditional); + + const auto offset = + cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; + ASSERT(static_cast(cheat.comparison_op.Value()) < 6); + const auto* function = comparison_functions[static_cast(cheat.comparison_op.Value())]; + const auto addr = cheat.Address() + offset; + + return function(reader(cheat.width, SanitizeAddress(addr)), cheat.ValueWidth(8)); +} + +void CheatList::ProcessBlockPairs(const Block& block) { + block_pairs.clear(); + + u64 scope = 0; + std::map pairs; + + for (std::size_t i = 0; i < block.size(); ++i) { + const auto& cheat = block[i]; + + switch (cheat.type) { + case CodeType::Conditional: + case CodeType::ConditionalInput: + pairs.insert_or_assign(scope, i); + ++scope; + break; + case CodeType::EndConditional: { + --scope; + const auto idx = pairs.at(scope); + block_pairs.insert_or_assign(idx, i); + break; + } + case CodeType::Loop: { + if (cheat.end_of_loop) { + --scope; + const auto idx = pairs.at(scope); + block_pairs.insert_or_assign(idx, i); + } else { + pairs.insert_or_assign(scope, i); + ++scope; + } + break; + } + } + } +} + +void CheatList::WriteImmediate(const Cheat& cheat) { + const auto offset = + cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; + auto& register_3 = scratch.at(cheat.register_3); + + const auto addr = cheat.Address() + offset + register_3; + LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", addr, + cheat.Value(8, cheat.width)); + writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(8)); +} + +void CheatList::BeginConditional(const Cheat& cheat) { + if (!EvaluateConditional(cheat)) { + const auto iter = block_pairs.find(current_index); + ASSERT(iter != block_pairs.end()); + current_index = iter->second - 1; + } +} + +void CheatList::EndConditional(const Cheat& cheat) { + LOG_DEBUG(Common_Filesystem, "Ending conditional block."); +} + +void CheatList::Loop(const Cheat& cheat) { + if (cheat.end_of_loop.Value()) + ASSERT(!cheat.end_of_loop.Value()); + + auto& register_3 = scratch.at(cheat.register_3); + const auto iter = block_pairs.find(current_index); + ASSERT(iter != block_pairs.end()); + ASSERT(iter->first < iter->second); + + for (int i = cheat.Value(4, 4); i >= 0; --i) { + register_3 = i; + for (std::size_t c = iter->first + 1; c < iter->second; ++c) { + current_index = c; + ExecuteSingleCheat( + (in_standard ? standard_list : master_list)[current_block].second[c]); + } + } + + current_index = iter->second; +} + +void CheatList::LoadImmediate(const Cheat& cheat) { + auto& register_3 = scratch.at(cheat.register_3); + + LOG_DEBUG(Common_Filesystem, "setting register={:01X} equal to value={:016X}", cheat.register_3, + cheat.Value(4, 8)); + register_3 = cheat.Value(4, 8); +} + +void CheatList::LoadIndexed(const Cheat& cheat) { + const auto offset = + cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; + auto& register_3 = scratch.at(cheat.register_3); + + const auto addr = (cheat.load_from_register.Value() ? register_3 : offset) + cheat.Address(); + LOG_DEBUG(Common_Filesystem, "writing indexed value to register={:01X}, addr={:016X}", + cheat.register_3, addr); + register_3 = reader(cheat.width, SanitizeAddress(addr)); +} + +void CheatList::StoreIndexed(const Cheat& cheat) { + auto& register_3 = scratch.at(cheat.register_3); + + const auto addr = + register_3 + (cheat.add_additional_register.Value() ? scratch.at(cheat.register_6) : 0); + LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", + cheat.Value(4, cheat.width), addr); + writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(4)); +} + +void CheatList::RegisterArithmetic(const Cheat& cheat) { + using ArithmeticFunction = u64 (*)(u64, u64); + constexpr ArithmeticFunction arithmetic_functions[] = { + [](u64 a, u64 b) { return a + b; }, [](u64 a, u64 b) { return a - b; }, + [](u64 a, u64 b) { return a * b; }, [](u64 a, u64 b) { return a << b; }, + [](u64 a, u64 b) { return a >> b; }, + }; + + using ArithmeticOverflowCheck = bool (*)(u64, u64); + constexpr ArithmeticOverflowCheck arithmetic_overflow_checks[] = { + [](u64 a, u64 b) { return a > (std::numeric_limits::max() - b); }, // a + b + [](u64 a, u64 b) { return a > (std::numeric_limits::max() + b); }, // a - b + [](u64 a, u64 b) { return a > (std::numeric_limits::max() / b); }, // a * b + [](u64 a, u64 b) { return b >= 64 || (a & ~((1ull << (64 - b)) - 1)) != 0; }, // a << b + [](u64 a, u64 b) { return b >= 64 || (a & ((1ull << b) - 1)) != 0; }, // a >> b + }; + + static_assert(sizeof(arithmetic_functions) == sizeof(arithmetic_overflow_checks), + "Missing or have extra arithmetic overflow checks compared to functions!"); + + auto& register_3 = scratch.at(cheat.register_3); + + ASSERT(static_cast(cheat.arithmetic_op.Value()) < 5); + const auto* function = arithmetic_functions[static_cast(cheat.arithmetic_op.Value())]; + const auto* overflow_function = + arithmetic_overflow_checks[static_cast(cheat.arithmetic_op.Value())]; + LOG_DEBUG(Common_Filesystem, "performing arithmetic with register={:01X}, value={:016X}", + cheat.register_3, cheat.ValueWidth(4)); + + if (overflow_function(register_3, cheat.ValueWidth(4))) { + LOG_WARNING(Common_Filesystem, + "overflow will occur when performing arithmetic operation={:02X} with operands " + "a={:016X}, b={:016X}!", + static_cast(cheat.arithmetic_op.Value()), register_3, cheat.ValueWidth(4)); + } + + register_3 = function(register_3, cheat.ValueWidth(4)); +} + +void CheatList::BeginConditionalInput(const Cheat& cheat) { + if (!EvaluateConditional(cheat)) { + const auto iter = block_pairs.find(current_index); + ASSERT(iter != block_pairs.end()); + current_index = iter->second - 1; + } +} + +VAddr CheatList::SanitizeAddress(VAddr in) const { + if ((in < main_region_begin || in >= main_region_end) && + (in < heap_region_begin || in >= heap_region_end)) { + LOG_ERROR(Common_Filesystem, + "Cheat attempting to access memory at invalid address={:016X}, if this persists, " + "the cheat may be incorrect. However, this may be normal early in execution if " + "the game has not properly set up yet.", + in); + return 0; ///< Invalid addresses will hard crash + } + + return in; +} + +void CheatList::ExecuteSingleCheat(const Cheat& cheat) { + using CheatOperationFunction = void (CheatList::*)(const Cheat&); + constexpr CheatOperationFunction cheat_operation_functions[] = { + &CheatList::WriteImmediate, &CheatList::BeginConditional, + &CheatList::EndConditional, &CheatList::Loop, + &CheatList::LoadImmediate, &CheatList::LoadIndexed, + &CheatList::StoreIndexed, &CheatList::RegisterArithmetic, + &CheatList::BeginConditionalInput, + }; + + const auto index = static_cast(cheat.type.Value()); + ASSERT(index < sizeof(cheat_operation_functions)); + const auto op = cheat_operation_functions[index]; + (this->*op)(cheat); +} + +void CheatList::ExecuteBlock(const Block& block) { + encountered_loops.clear(); + + ProcessBlockPairs(block); + for (std::size_t i = 0; i < block.size(); ++i) { + current_index = i; + ExecuteSingleCheat(block[i]); + i = current_index; + } +} + +CheatParser::~CheatParser() = default; + +CheatList CheatParser::MakeCheatList(CheatList::ProgramSegment master, + CheatList::ProgramSegment standard) const { + return {master, standard}; +} + +TextCheatParser::~TextCheatParser() = default; + +CheatList TextCheatParser::Parse(const std::vector& data) const { + std::stringstream ss; + ss.write(reinterpret_cast(data.data()), data.size()); + + std::vector lines; + std::string stream_line; + while (std::getline(ss, stream_line)) { + // Remove a trailing \r + if (!stream_line.empty() && stream_line.back() == '\r') + stream_line.pop_back(); + lines.push_back(std::move(stream_line)); + } + + CheatList::ProgramSegment master_list; + CheatList::ProgramSegment standard_list; + + for (std::size_t i = 0; i < lines.size(); ++i) { + auto line = lines[i]; + + if (!line.empty() && (line[0] == '[' || line[0] == '{')) { + const auto master = line[0] == '{'; + const auto begin = master ? line.find('{') : line.find('['); + const auto end = master ? line.find_last_of('}') : line.find_last_of(']'); + + ASSERT(begin != std::string::npos && end != std::string::npos); + + const std::string patch_name{line.begin() + begin + 1, line.begin() + end}; + CheatList::Block block{}; + + while (i < lines.size() - 1) { + line = lines[++i]; + if (!line.empty() && (line[0] == '[' || line[0] == '{')) { + --i; + break; + } + + if (line.size() < 8) + continue; + + Cheat out{}; + out.raw = ParseSingleLineCheat(line); + block.push_back(out); + } + + (master ? master_list : standard_list).emplace_back(patch_name, block); + } + } + + return MakeCheatList(master_list, standard_list); +} + +std::array TextCheatParser::ParseSingleLineCheat(const std::string& line) const { + std::array out{}; + + if (line.size() < 8) + return out; + + const auto word1 = Common::HexStringToArray(std::string_view{line.data(), 8}); + std::memcpy(out.data(), word1.data(), sizeof(u32)); + + if (line.size() < 17 || line[8] != ' ') + return out; + + const auto word2 = Common::HexStringToArray(std::string_view{line.data() + 9, 8}); + std::memcpy(out.data() + sizeof(u32), word2.data(), sizeof(u32)); + + if (line.size() < 26 || line[17] != ' ') { + // Perform shifting in case value is truncated early. + const auto type = static_cast((out[0] & 0xF0) >> 4); + if (type == CodeType::Loop || type == CodeType::LoadImmediate || + type == CodeType::StoreIndexed || type == CodeType::RegisterArithmetic) { + std::memcpy(out.data() + 8, out.data() + 4, sizeof(u32)); + std::memset(out.data() + 4, 0, sizeof(u32)); + } + + return out; + } + + const auto word3 = Common::HexStringToArray(std::string_view{line.data() + 18, 8}); + std::memcpy(out.data() + 2 * sizeof(u32), word3.data(), sizeof(u32)); + + if (line.size() < 35 || line[26] != ' ') { + // Perform shifting in case value is truncated early. + const auto type = static_cast((out[0] & 0xF0) >> 4); + if (type == CodeType::WriteImmediate || type == CodeType::Conditional) { + std::memcpy(out.data() + 12, out.data() + 8, sizeof(u32)); + std::memset(out.data() + 8, 0, sizeof(u32)); + } + + return out; + } + + const auto word4 = Common::HexStringToArray(std::string_view{line.data() + 27, 8}); + std::memcpy(out.data() + 3 * sizeof(u32), word4.data(), sizeof(u32)); + + return out; +} + +u64 MemoryReadImpl(u8 width, VAddr addr) { + switch (width) { + case 1: + return Memory::Read8(addr); + case 2: + return Memory::Read16(addr); + case 4: + return Memory::Read32(addr); + case 8: + return Memory::Read64(addr); + default: + UNREACHABLE(); + return 0; + } +} + +void MemoryWriteImpl(u8 width, VAddr addr, u64 value) { + switch (width) { + case 1: + Memory::Write8(addr, static_cast(value)); + break; + case 2: + Memory::Write16(addr, static_cast(value)); + break; + case 4: + Memory::Write32(addr, static_cast(value)); + break; + case 8: + Memory::Write64(addr, value); + break; + default: + UNREACHABLE(); + } +} + +CheatEngine::CheatEngine(std::vector cheats, const std::string& build_id) + : cheats(std::move(cheats)) { + event = CoreTiming::RegisterEvent( + "CheatEngine::FrameCallback::" + build_id, + [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); }); + CoreTiming::ScheduleEvent(CHEAT_ENGINE_TICKS, event); + + const auto& vm_manager = Core::System::GetInstance().CurrentProcess()->VMManager(); + for (auto& list : this->cheats) { + list.SetMemoryParameters( + vm_manager.GetMainCodeRegionBaseAddress(), vm_manager.GetHeapRegionBaseAddress(), + vm_manager.GetMainCodeRegionEndAddress(), vm_manager.GetHeapRegionEndAddress(), + &MemoryWriteImpl, &MemoryReadImpl); + } +} + +CheatEngine::~CheatEngine() { + CoreTiming::UnscheduleEvent(event, 0); +} + +void CheatEngine::FrameCallback(u64 userdata, int cycles_late) { + for (auto& list : cheats) + list.Execute(); + + CoreTiming::ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event); +} + +} // namespace FileSys diff --git a/src/core/file_sys/cheat_engine.h b/src/core/file_sys/cheat_engine.h new file mode 100644 index 000000000..d7a8654b7 --- /dev/null +++ b/src/core/file_sys/cheat_engine.h @@ -0,0 +1,226 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include "common/bit_field.h" +#include "common/common_types.h" + +namespace CoreTiming { +struct EventType; +} + +namespace FileSys { + +enum class CodeType : u32 { + // 0TMR00AA AAAAAAAA YYYYYYYY YYYYYYYY + // Writes a T sized value Y to the address A added to the value of register R in memory domain M + WriteImmediate = 0, + + // 1TMC00AA AAAAAAAA YYYYYYYY YYYYYYYY + // Compares the T sized value Y to the value at address A in memory domain M using the + // conditional function C. If success, continues execution. If failure, jumps to the matching + // EndConditional statement. + Conditional = 1, + + // 20000000 + // Terminates a Conditional or ConditionalInput block. + EndConditional = 2, + + // 300R0000 VVVVVVVV + // Starts looping V times, storing the current count in register R. + // Loop block is terminated with a matching 310R0000. + Loop = 3, + + // 400R0000 VVVVVVVV VVVVVVVV + // Sets the value of register R to the value V. + LoadImmediate = 4, + + // 5TMRI0AA AAAAAAAA + // Sets the value of register R to the value of width T at address A in memory domain M, with + // the current value of R added to the address if I == 1. + LoadIndexed = 5, + + // 6T0RIFG0 VVVVVVVV VVVVVVVV + // Writes the value V of width T to the memory address stored in register R. Adds the value of + // register G to the final calculation if F is nonzero. Increments the value of register R by T + // after operation if I is nonzero. + StoreIndexed = 6, + + // 7T0RA000 VVVVVVVV + // Performs the arithmetic operation A on the value in register R and the value V of width T, + // storing the result in register R. + RegisterArithmetic = 7, + + // 8KKKKKKK + // Checks to see if any of the buttons defined by the bitmask K are pressed. If any are, + // execution continues. If none are, execution skips to the next EndConditional command. + ConditionalInput = 8, +}; + +enum class MemoryType : u32 { + // Addressed relative to start of main NSO + MainNSO = 0, + + // Addressed relative to start of heap + Heap = 1, +}; + +enum class ArithmeticOp : u32 { + Add = 0, + Sub = 1, + Mult = 2, + LShift = 3, + RShift = 4, +}; + +enum class ComparisonOp : u32 { + GreaterThan = 1, + GreaterThanEqual = 2, + LessThan = 3, + LessThanEqual = 4, + Equal = 5, + Inequal = 6, +}; + +union Cheat { + std::array raw; + + BitField<4, 4, CodeType> type; + BitField<0, 4, u32> width; // Can be 1, 2, 4, or 8. Measured in bytes. + BitField<0, 4, u32> end_of_loop; + BitField<12, 4, MemoryType> memory_type; + BitField<8, 4, u32> register_3; + BitField<8, 4, ComparisonOp> comparison_op; + BitField<20, 4, u32> load_from_register; + BitField<20, 4, u32> increment_register; + BitField<20, 4, ArithmeticOp> arithmetic_op; + BitField<16, 4, u32> add_additional_register; + BitField<28, 4, u32> register_6; + + u64 Address() const; + u64 ValueWidth(u64 offset) const; + u64 Value(u64 offset, u64 width) const; + u32 KeypadValue() const; +}; + +class CheatParser; + +// Represents a full collection of cheats for a game. The Execute function should be called every +// interval that all cheats should be executed. Clients should not directly instantiate this class +// (hence private constructor), they should instead receive an instance from CheatParser, which +// guarantees the list is always in an acceptable state. +class CheatList { +public: + friend class CheatParser; + + using Block = std::vector; + using ProgramSegment = std::vector>; + + // (width in bytes, address, value) + using MemoryWriter = void (*)(u8, VAddr, u64); + // (width in bytes, address) -> value + using MemoryReader = u64 (*)(u8, VAddr); + + void SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, VAddr heap_end, + MemoryWriter writer, MemoryReader reader); + + void Execute(); + +private: + CheatList(ProgramSegment master, ProgramSegment standard); + + void ProcessBlockPairs(const Block& block); + void ExecuteSingleCheat(const Cheat& cheat); + + void ExecuteBlock(const Block& block); + + bool EvaluateConditional(const Cheat& cheat) const; + + // Individual cheat operations + void WriteImmediate(const Cheat& cheat); + void BeginConditional(const Cheat& cheat); + void EndConditional(const Cheat& cheat); + void Loop(const Cheat& cheat); + void LoadImmediate(const Cheat& cheat); + void LoadIndexed(const Cheat& cheat); + void StoreIndexed(const Cheat& cheat); + void RegisterArithmetic(const Cheat& cheat); + void BeginConditionalInput(const Cheat& cheat); + + VAddr SanitizeAddress(VAddr in) const; + + // Master Codes are defined as codes that cannot be disabled and are run prior to all + // others. + ProgramSegment master_list; + // All other codes + ProgramSegment standard_list; + + bool in_standard = false; + + // 16 (0x0-0xF) scratch registers that can be used by cheats + std::array scratch{}; + + MemoryWriter writer = nullptr; + MemoryReader reader = nullptr; + + u64 main_region_begin{}; + u64 heap_region_begin{}; + u64 main_region_end{}; + u64 heap_region_end{}; + + u64 current_block{}; + // The current index of the cheat within the current Block + u64 current_index{}; + + // The 'stack' of the program. When a conditional or loop statement is encountered, its index is + // pushed onto this queue. When a end block is encountered, the condition is checked. + std::map block_pairs; + + std::set encountered_loops; +}; + +// Intermediary class that parses a text file or other disk format for storing cheats into a +// CheatList object, that can be used for execution. +class CheatParser { +public: + virtual ~CheatParser(); + + virtual CheatList Parse(const std::vector& data) const = 0; + +protected: + CheatList MakeCheatList(CheatList::ProgramSegment master, + CheatList::ProgramSegment standard) const; +}; + +// CheatParser implementation that parses text files +class TextCheatParser final : public CheatParser { +public: + ~TextCheatParser() override; + + CheatList Parse(const std::vector& data) const override; + +private: + std::array ParseSingleLineCheat(const std::string& line) const; +}; + +// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming +class CheatEngine final { +public: + CheatEngine(std::vector cheats, const std::string& build_id); + ~CheatEngine(); + +private: + void FrameCallback(u64 userdata, int cycles_late); + + CoreTiming::EventType* event; + + std::vector cheats; +}; + +} // namespace FileSys -- cgit v1.2.3 From 52ac6419dafb84b10369226d3746b3b5b761d33b Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Tue, 5 Mar 2019 10:09:27 -0500 Subject: vm_manager: Remove cheat-specific ranges from VMManager --- src/core/file_sys/cheat_engine.cpp | 70 +++++++++++++++++++++----------------- src/core/file_sys/cheat_engine.h | 11 +++--- 2 files changed, 44 insertions(+), 37 deletions(-) (limited to 'src/core/file_sys') diff --git a/src/core/file_sys/cheat_engine.cpp b/src/core/file_sys/cheat_engine.cpp index e4383d8c1..09ca9d705 100644 --- a/src/core/file_sys/cheat_engine.cpp +++ b/src/core/file_sys/cheat_engine.cpp @@ -18,7 +18,7 @@ namespace FileSys { -constexpr u64 CHEAT_ENGINE_TICKS = CoreTiming::BASE_CLOCK_RATE / 60; +constexpr u64 CHEAT_ENGINE_TICKS = Core::Timing::BASE_CLOCK_RATE / 60; constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF; u64 Cheat::Address() const { @@ -82,7 +82,7 @@ CheatList::CheatList(ProgramSegment master, ProgramSegment standard) bool CheatList::EvaluateConditional(const Cheat& cheat) const { using ComparisonFunction = bool (*)(u64, u64); - constexpr ComparisonFunction comparison_functions[] = { + constexpr std::array comparison_functions{ [](u64 a, u64 b) { return a > b; }, [](u64 a, u64 b) { return a >= b; }, [](u64 a, u64 b) { return a < b; }, [](u64 a, u64 b) { return a <= b; }, [](u64 a, u64 b) { return a == b; }, [](u64 a, u64 b) { return a != b; }, @@ -103,7 +103,7 @@ bool CheatList::EvaluateConditional(const Cheat& cheat) const { const auto press_state = applet_resource ->GetController(Service::HID::HidController::NPad) - .GetPressState(); + .GetAndResetPressState(); return ((press_state & cheat.KeypadValue()) & KEYPAD_BITMASK) != 0; } @@ -112,7 +112,7 @@ bool CheatList::EvaluateConditional(const Cheat& cheat) const { const auto offset = cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; ASSERT(static_cast(cheat.comparison_op.Value()) < 6); - const auto* function = comparison_functions[static_cast(cheat.comparison_op.Value())]; + auto* function = comparison_functions[static_cast(cheat.comparison_op.Value())]; const auto addr = cheat.Address() + offset; return function(reader(cheat.width, SanitizeAddress(addr)), cheat.ValueWidth(8)); @@ -157,7 +157,7 @@ void CheatList::ProcessBlockPairs(const Block& block) { void CheatList::WriteImmediate(const Cheat& cheat) { const auto offset = cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; - auto& register_3 = scratch.at(cheat.register_3); + const auto& register_3 = scratch.at(cheat.register_3); const auto addr = cheat.Address() + offset + register_3; LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", addr, @@ -166,11 +166,13 @@ void CheatList::WriteImmediate(const Cheat& cheat) { } void CheatList::BeginConditional(const Cheat& cheat) { - if (!EvaluateConditional(cheat)) { - const auto iter = block_pairs.find(current_index); - ASSERT(iter != block_pairs.end()); - current_index = iter->second - 1; + if (EvaluateConditional(cheat)) { + return; } + + const auto iter = block_pairs.find(current_index); + ASSERT(iter != block_pairs.end()); + current_index = iter->second - 1; } void CheatList::EndConditional(const Cheat& cheat) { @@ -218,7 +220,7 @@ void CheatList::LoadIndexed(const Cheat& cheat) { } void CheatList::StoreIndexed(const Cheat& cheat) { - auto& register_3 = scratch.at(cheat.register_3); + const auto& register_3 = scratch.at(cheat.register_3); const auto addr = register_3 + (cheat.add_additional_register.Value() ? scratch.at(cheat.register_6) : 0); @@ -229,14 +231,14 @@ void CheatList::StoreIndexed(const Cheat& cheat) { void CheatList::RegisterArithmetic(const Cheat& cheat) { using ArithmeticFunction = u64 (*)(u64, u64); - constexpr ArithmeticFunction arithmetic_functions[] = { + constexpr std::array arithmetic_functions{ [](u64 a, u64 b) { return a + b; }, [](u64 a, u64 b) { return a - b; }, [](u64 a, u64 b) { return a * b; }, [](u64 a, u64 b) { return a << b; }, [](u64 a, u64 b) { return a >> b; }, }; using ArithmeticOverflowCheck = bool (*)(u64, u64); - constexpr ArithmeticOverflowCheck arithmetic_overflow_checks[] = { + constexpr std::array arithmetic_overflow_checks{ [](u64 a, u64 b) { return a > (std::numeric_limits::max() - b); }, // a + b [](u64 a, u64 b) { return a > (std::numeric_limits::max() + b); }, // a - b [](u64 a, u64 b) { return a > (std::numeric_limits::max() / b); }, // a * b @@ -250,8 +252,8 @@ void CheatList::RegisterArithmetic(const Cheat& cheat) { auto& register_3 = scratch.at(cheat.register_3); ASSERT(static_cast(cheat.arithmetic_op.Value()) < 5); - const auto* function = arithmetic_functions[static_cast(cheat.arithmetic_op.Value())]; - const auto* overflow_function = + auto* function = arithmetic_functions[static_cast(cheat.arithmetic_op.Value())]; + auto* overflow_function = arithmetic_overflow_checks[static_cast(cheat.arithmetic_op.Value())]; LOG_DEBUG(Common_Filesystem, "performing arithmetic with register={:01X}, value={:016X}", cheat.register_3, cheat.ValueWidth(4)); @@ -267,11 +269,12 @@ void CheatList::RegisterArithmetic(const Cheat& cheat) { } void CheatList::BeginConditionalInput(const Cheat& cheat) { - if (!EvaluateConditional(cheat)) { - const auto iter = block_pairs.find(current_index); - ASSERT(iter != block_pairs.end()); - current_index = iter->second - 1; - } + if (EvaluateConditional(cheat)) + return; + + const auto iter = block_pairs.find(current_index); + ASSERT(iter != block_pairs.end()); + current_index = iter->second - 1; } VAddr CheatList::SanitizeAddress(VAddr in) const { @@ -290,7 +293,7 @@ VAddr CheatList::SanitizeAddress(VAddr in) const { void CheatList::ExecuteSingleCheat(const Cheat& cheat) { using CheatOperationFunction = void (CheatList::*)(const Cheat&); - constexpr CheatOperationFunction cheat_operation_functions[] = { + constexpr std::array cheat_operation_functions{ &CheatList::WriteImmediate, &CheatList::BeginConditional, &CheatList::EndConditional, &CheatList::Loop, &CheatList::LoadImmediate, &CheatList::LoadIndexed, @@ -346,7 +349,7 @@ CheatList TextCheatParser::Parse(const std::vector& data) const { if (!line.empty() && (line[0] == '[' || line[0] == '{')) { const auto master = line[0] == '{'; const auto begin = master ? line.find('{') : line.find('['); - const auto end = master ? line.find_last_of('}') : line.find_last_of(']'); + const auto end = master ? line.rfind('}') : line.rfind(']'); ASSERT(begin != std::string::npos && end != std::string::npos); @@ -422,7 +425,7 @@ std::array TextCheatParser::ParseSingleLineCheat(const std::string& line return out; } -u64 MemoryReadImpl(u8 width, VAddr addr) { +u64 MemoryReadImpl(u32 width, VAddr addr) { switch (width) { case 1: return Memory::Read8(addr); @@ -438,7 +441,7 @@ u64 MemoryReadImpl(u8 width, VAddr addr) { } } -void MemoryWriteImpl(u8 width, VAddr addr, u64 value) { +void MemoryWriteImpl(u32 width, VAddr addr, u64 value) { switch (width) { case 1: Memory::Write8(addr, static_cast(value)); @@ -457,31 +460,34 @@ void MemoryWriteImpl(u8 width, VAddr addr, u64 value) { } } -CheatEngine::CheatEngine(std::vector cheats, const std::string& build_id) +CheatEngine::CheatEngine(std::vector cheats, const std::string& build_id, + VAddr code_region_start, VAddr code_region_end) : cheats(std::move(cheats)) { - event = CoreTiming::RegisterEvent( + auto& core_timing{Core::System::GetInstance().CoreTiming()}; + event = core_timing.RegisterEvent( "CheatEngine::FrameCallback::" + build_id, [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); }); - CoreTiming::ScheduleEvent(CHEAT_ENGINE_TICKS, event); + core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event); const auto& vm_manager = Core::System::GetInstance().CurrentProcess()->VMManager(); for (auto& list : this->cheats) { - list.SetMemoryParameters( - vm_manager.GetMainCodeRegionBaseAddress(), vm_manager.GetHeapRegionBaseAddress(), - vm_manager.GetMainCodeRegionEndAddress(), vm_manager.GetHeapRegionEndAddress(), - &MemoryWriteImpl, &MemoryReadImpl); + list.SetMemoryParameters(code_region_start, vm_manager.GetHeapRegionBaseAddress(), + code_region_end, vm_manager.GetHeapRegionEndAddress(), + &MemoryWriteImpl, &MemoryReadImpl); } } CheatEngine::~CheatEngine() { - CoreTiming::UnscheduleEvent(event, 0); + auto& core_timing{Core::System::GetInstance().CoreTiming()}; + core_timing.UnscheduleEvent(event, 0); } void CheatEngine::FrameCallback(u64 userdata, int cycles_late) { for (auto& list : cheats) list.Execute(); - CoreTiming::ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event); + auto& core_timing{Core::System::GetInstance().CoreTiming()}; + core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event); } } // namespace FileSys diff --git a/src/core/file_sys/cheat_engine.h b/src/core/file_sys/cheat_engine.h index d7a8654b7..7ed69a2c8 100644 --- a/src/core/file_sys/cheat_engine.h +++ b/src/core/file_sys/cheat_engine.h @@ -11,7 +11,7 @@ #include "common/bit_field.h" #include "common/common_types.h" -namespace CoreTiming { +namespace Core::Timing { struct EventType; } @@ -123,9 +123,9 @@ public: using ProgramSegment = std::vector>; // (width in bytes, address, value) - using MemoryWriter = void (*)(u8, VAddr, u64); + using MemoryWriter = void (*)(u32, VAddr, u64); // (width in bytes, address) -> value - using MemoryReader = u64 (*)(u8, VAddr); + using MemoryReader = u64 (*)(u32, VAddr); void SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, VAddr heap_end, MemoryWriter writer, MemoryReader reader); @@ -212,13 +212,14 @@ private: // Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming class CheatEngine final { public: - CheatEngine(std::vector cheats, const std::string& build_id); + CheatEngine(std::vector cheats, const std::string& build_id, VAddr code_region_start, + VAddr code_region_end); ~CheatEngine(); private: void FrameCallback(u64 userdata, int cycles_late); - CoreTiming::EventType* event; + Core::Timing::EventType* event; std::vector cheats; }; -- cgit v1.2.3