mirror of
				https://git.zaroz.cloud/nintendo-back-up/yuzu/yuzu-mainline.git
				synced 2025-03-21 01:53:15 +00:00 
			
		
		
		
	Merge pull request #1933 from DarkLordZach/cheat-engine
file_sys: Implement parser and interpreter for game memory cheats
This commit is contained in:
		
						commit
						639f0c524d
					
				| @ -31,6 +31,8 @@ add_library(core STATIC | ||||
|     file_sys/bis_factory.h | ||||
|     file_sys/card_image.cpp | ||||
|     file_sys/card_image.h | ||||
|     file_sys/cheat_engine.cpp | ||||
|     file_sys/cheat_engine.h | ||||
|     file_sys/content_archive.cpp | ||||
|     file_sys/content_archive.h | ||||
|     file_sys/control_metadata.cpp | ||||
|  | ||||
| @ -32,6 +32,7 @@ | ||||
| #include "core/perf_stats.h" | ||||
| #include "core/settings.h" | ||||
| #include "core/telemetry_session.h" | ||||
| #include "file_sys/cheat_engine.h" | ||||
| #include "frontend/applets/profile_select.h" | ||||
| #include "frontend/applets/software_keyboard.h" | ||||
| #include "frontend/applets/web_browser.h" | ||||
| @ -205,6 +206,7 @@ struct System::Impl { | ||||
|         GDBStub::Shutdown(); | ||||
|         Service::Shutdown(); | ||||
|         service_manager.reset(); | ||||
|         cheat_engine.reset(); | ||||
|         telemetry_session.reset(); | ||||
|         gpu_core.reset(); | ||||
| 
 | ||||
| @ -255,6 +257,8 @@ struct System::Impl { | ||||
|     CpuCoreManager cpu_core_manager; | ||||
|     bool is_powered_on = false; | ||||
| 
 | ||||
|     std::unique_ptr<FileSys::CheatEngine> cheat_engine; | ||||
| 
 | ||||
|     /// Frontend applets
 | ||||
|     std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector; | ||||
|     std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard; | ||||
| @ -453,6 +457,13 @@ Tegra::DebugContext* System::GetGPUDebugContext() const { | ||||
|     return impl->debug_context.get(); | ||||
| } | ||||
| 
 | ||||
| void System::RegisterCheatList(const std::vector<FileSys::CheatList>& list, | ||||
|                                const std::string& build_id, VAddr code_region_start, | ||||
|                                VAddr code_region_end) { | ||||
|     impl->cheat_engine = | ||||
|         std::make_unique<FileSys::CheatEngine>(list, build_id, code_region_start, code_region_end); | ||||
| } | ||||
| 
 | ||||
| void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) { | ||||
|     impl->virtual_filesystem = std::move(vfs); | ||||
| } | ||||
|  | ||||
| @ -20,6 +20,7 @@ class WebBrowserApplet; | ||||
| } // namespace Core::Frontend
 | ||||
| 
 | ||||
| namespace FileSys { | ||||
| class CheatList; | ||||
| class VfsFilesystem; | ||||
| } // namespace FileSys
 | ||||
| 
 | ||||
| @ -253,6 +254,9 @@ public: | ||||
| 
 | ||||
|     std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const; | ||||
| 
 | ||||
|     void RegisterCheatList(const std::vector<FileSys::CheatList>& list, const std::string& build_id, | ||||
|                            VAddr code_region_start, VAddr code_region_end); | ||||
| 
 | ||||
|     void SetProfileSelector(std::unique_ptr<Frontend::ProfileSelectApplet> applet); | ||||
| 
 | ||||
|     const Frontend::ProfileSelectApplet& GetProfileSelector() const; | ||||
|  | ||||
							
								
								
									
										493
									
								
								src/core/file_sys/cheat_engine.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										493
									
								
								src/core/file_sys/cheat_engine.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,493 @@ | ||||
| // Copyright 2018 yuzu emulator team
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <locale> | ||||
| #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 = Core::Timing::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 std::array<ComparisonFunction, 6> 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<Service::HID::Hid>("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::Controller_NPad>(Service::HID::HidController::NPad) | ||||
|                 .GetAndResetPressState(); | ||||
|         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<u8>(cheat.comparison_op.Value()) < 6); | ||||
|     auto* function = comparison_functions[static_cast<u8>(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<u64, u64> 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; | ||||
|     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, | ||||
|               cheat.Value(8, cheat.width)); | ||||
|     writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(8)); | ||||
| } | ||||
| 
 | ||||
| void CheatList::BeginConditional(const Cheat& cheat) { | ||||
|     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) { | ||||
|     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) { | ||||
|     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); | ||||
|     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 std::array<ArithmeticFunction, 5> 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 std::array<ArithmeticOverflowCheck, 5> arithmetic_overflow_checks{ | ||||
|         [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() - b); },       // a + b
 | ||||
|         [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() + b); },       // a - b
 | ||||
|         [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::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<u8>(cheat.arithmetic_op.Value()) < 5); | ||||
|     auto* function = arithmetic_functions[static_cast<u8>(cheat.arithmetic_op.Value())]; | ||||
|     auto* overflow_function = | ||||
|         arithmetic_overflow_checks[static_cast<u8>(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<u8>(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)) | ||||
|         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 { | ||||
|     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 std::array<CheatOperationFunction, 9> 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<u8>(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<u8>& data) const { | ||||
|     std::stringstream ss; | ||||
|     ss.write(reinterpret_cast<const char*>(data.data()), data.size()); | ||||
| 
 | ||||
|     std::vector<std::string> 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.rfind('}') : line.rfind(']'); | ||||
| 
 | ||||
|             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<u8, 16> TextCheatParser::ParseSingleLineCheat(const std::string& line) const { | ||||
|     std::array<u8, 16> out{}; | ||||
| 
 | ||||
|     if (line.size() < 8) | ||||
|         return out; | ||||
| 
 | ||||
|     const auto word1 = Common::HexStringToArray<sizeof(u32)>(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<sizeof(u32)>(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<CodeType>((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<sizeof(u32)>(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<CodeType>((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<sizeof(u32)>(std::string_view{line.data() + 27, 8}); | ||||
|     std::memcpy(out.data() + 3 * sizeof(u32), word4.data(), sizeof(u32)); | ||||
| 
 | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| u64 MemoryReadImpl(u32 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(u32 width, VAddr addr, u64 value) { | ||||
|     switch (width) { | ||||
|     case 1: | ||||
|         Memory::Write8(addr, static_cast<u8>(value)); | ||||
|         break; | ||||
|     case 2: | ||||
|         Memory::Write16(addr, static_cast<u16>(value)); | ||||
|         break; | ||||
|     case 4: | ||||
|         Memory::Write32(addr, static_cast<u32>(value)); | ||||
|         break; | ||||
|     case 8: | ||||
|         Memory::Write64(addr, value); | ||||
|         break; | ||||
|     default: | ||||
|         UNREACHABLE(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| CheatEngine::CheatEngine(std::vector<CheatList> cheats, const std::string& build_id, | ||||
|                          VAddr code_region_start, VAddr code_region_end) | ||||
|     : cheats(std::move(cheats)) { | ||||
|     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); }); | ||||
|     core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event); | ||||
| 
 | ||||
|     const auto& vm_manager = Core::System::GetInstance().CurrentProcess()->VMManager(); | ||||
|     for (auto& list : this->cheats) { | ||||
|         list.SetMemoryParameters(code_region_start, vm_manager.GetHeapRegionBaseAddress(), | ||||
|                                  code_region_end, vm_manager.GetHeapRegionEndAddress(), | ||||
|                                  &MemoryWriteImpl, &MemoryReadImpl); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| CheatEngine::~CheatEngine() { | ||||
|     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(); | ||||
| 
 | ||||
|     auto& core_timing{Core::System::GetInstance().CoreTiming()}; | ||||
|     core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event); | ||||
| } | ||||
| 
 | ||||
| } // namespace FileSys
 | ||||
							
								
								
									
										227
									
								
								src/core/file_sys/cheat_engine.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								src/core/file_sys/cheat_engine.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,227 @@ | ||||
| // Copyright 2018 yuzu emulator team
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <map> | ||||
| #include <set> | ||||
| #include <vector> | ||||
| #include <queue> | ||||
| #include "common/bit_field.h" | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| namespace Core::Timing { | ||||
| 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<u8, 16> 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<Cheat>; | ||||
|     using ProgramSegment = std::vector<std::pair<std::string, Block>>; | ||||
| 
 | ||||
|     // (width in bytes, address, value)
 | ||||
|     using MemoryWriter = void (*)(u32, VAddr, u64); | ||||
|     // (width in bytes, address) -> value
 | ||||
|     using MemoryReader = u64 (*)(u32, 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<u64, 16> 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<u64, u64> block_pairs; | ||||
| 
 | ||||
|     std::set<u64> 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<u8>& 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<u8>& data) const override; | ||||
| 
 | ||||
| private: | ||||
|     std::array<u8, 16> 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<CheatList> cheats, const std::string& build_id, VAddr code_region_start, | ||||
|                 VAddr code_region_end); | ||||
|     ~CheatEngine(); | ||||
| 
 | ||||
| private: | ||||
|     void FrameCallback(u64 userdata, int cycles_late); | ||||
| 
 | ||||
|     Core::Timing::EventType* event; | ||||
| 
 | ||||
|     std::vector<CheatList> cheats; | ||||
| }; | ||||
| 
 | ||||
| } // namespace FileSys
 | ||||
| @ -7,6 +7,7 @@ | ||||
| #include <cstddef> | ||||
| #include <cstring> | ||||
| 
 | ||||
| #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<u8, 32>& build_id_) const { | ||||
|     return !CollectPatches(patch_dirs, build_id).empty(); | ||||
| } | ||||
| 
 | ||||
| static std::optional<CheatList> ReadCheatFileFromFolder(u64 title_id, | ||||
|                                                         const std::array<u8, 0x20>& 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<u8> 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<CheatList> PatchManager::CreateCheatList(const std::array<u8, 32>& build_id_) const { | ||||
|     std::vector<CheatList> 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) || | ||||
| @ -403,6 +455,8 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam | ||||
|             } | ||||
|             if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs"))) | ||||
|                 AppendCommaIfNotEmpty(types, "LayeredFS"); | ||||
|             if (IsDirValidAndNonEmpty(mod->GetSubdirectory("cheats"))) | ||||
|                 AppendCommaIfNotEmpty(types, "Cheats"); | ||||
| 
 | ||||
|             if (types.empty()) | ||||
|                 continue; | ||||
|  | ||||
| @ -8,6 +8,7 @@ | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #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<u8, 0x20>& build_id) const; | ||||
| 
 | ||||
|     // Creates a CheatList object with all
 | ||||
|     std::vector<CheatList> CreateCheatList(const std::array<u8, 0x20>& build_id) const; | ||||
| 
 | ||||
|     // Currently tracked RomFS patches:
 | ||||
|     // - Game Updates
 | ||||
|     // - LayeredFS
 | ||||
|  | ||||
| @ -617,6 +617,9 @@ private: | ||||
|     VAddr new_map_region_base = 0; | ||||
|     VAddr new_map_region_end = 0; | ||||
| 
 | ||||
|     VAddr main_code_region_base = 0; | ||||
|     VAddr main_code_region_end = 0; | ||||
| 
 | ||||
|     VAddr tls_io_region_base = 0; | ||||
|     VAddr tls_io_region_end = 0; | ||||
| 
 | ||||
|  | ||||
| @ -4,6 +4,9 @@ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include "core/hle/service/hid/controllers/controller_base.h" | ||||
| #include "core/hle/service/service.h" | ||||
| 
 | ||||
| #include "controllers/controller_base.h" | ||||
| #include "core/hle/service/service.h" | ||||
| 
 | ||||
|  | ||||
| @ -7,8 +7,10 @@ | ||||
| #include <lz4.h> | ||||
| #include "common/common_funcs.h" | ||||
| #include "common/file_util.h" | ||||
| #include "common/hex_util.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/swap.h" | ||||
| #include "core/core.h" | ||||
| #include "core/file_sys/patch_manager.h" | ||||
| #include "core/gdbstub/gdbstub.h" | ||||
| #include "core/hle/kernel/code_set.h" | ||||
| @ -165,6 +167,16 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, | ||||
|         std::memcpy(program_image.data(), pi_header.data() + 0x100, program_image.size()); | ||||
|     } | ||||
| 
 | ||||
|     // Apply cheats if they exist and the program has a valid title ID
 | ||||
|     if (pm) { | ||||
|         const auto cheats = pm->CreateCheatList(nso_header.build_id); | ||||
|         if (!cheats.empty()) { | ||||
|             Core::System::GetInstance().RegisterCheatList( | ||||
|                 cheats, Common::HexArrayToString(nso_header.build_id), load_base, | ||||
|                 load_base + program_image.size()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Load codeset for current process
 | ||||
|     codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image)); | ||||
|     process.LoadModule(std::move(codeset), load_base); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 bunnei
						bunnei