1 // Copyright 2018 yuzu emulator team
2 // Licensed under GPLv2 or any later version
3 // Refer to the license.txt file included.
4 
5 #include <locale>
6 #include "common/hex_util.h"
7 #include "common/microprofile.h"
8 #include "common/swap.h"
9 #include "core/core.h"
10 #include "core/core_timing.h"
11 #include "core/core_timing_util.h"
12 #include "core/hardware_properties.h"
13 #include "core/hle/kernel/memory/page_table.h"
14 #include "core/hle/kernel/process.h"
15 #include "core/hle/service/hid/controllers/npad.h"
16 #include "core/hle/service/hid/hid.h"
17 #include "core/hle/service/sm/sm.h"
18 #include "core/memory.h"
19 #include "core/memory/cheat_engine.h"
20 
21 namespace Core::Memory {
22 namespace {
23 constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12};
24 constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
25 
ExtractName(std::string_view data,std::size_t start_index,char match)26 std::string_view ExtractName(std::string_view data, std::size_t start_index, char match) {
27     auto end_index = start_index;
28     while (data[end_index] != match) {
29         ++end_index;
30         if (end_index > data.size() ||
31             (end_index - start_index - 1) > sizeof(CheatDefinition::readable_name)) {
32             return {};
33         }
34     }
35 
36     return data.substr(start_index, end_index - start_index);
37 }
38 } // Anonymous namespace
39 
StandardVmCallbacks(Core::System & system,const CheatProcessMetadata & metadata)40 StandardVmCallbacks::StandardVmCallbacks(Core::System& system, const CheatProcessMetadata& metadata)
41     : metadata(metadata), system(system) {}
42 
43 StandardVmCallbacks::~StandardVmCallbacks() = default;
44 
MemoryRead(VAddr address,void * data,u64 size)45 void StandardVmCallbacks::MemoryRead(VAddr address, void* data, u64 size) {
46     system.Memory().ReadBlock(SanitizeAddress(address), data, size);
47 }
48 
MemoryWrite(VAddr address,const void * data,u64 size)49 void StandardVmCallbacks::MemoryWrite(VAddr address, const void* data, u64 size) {
50     system.Memory().WriteBlock(SanitizeAddress(address), data, size);
51 }
52 
HidKeysDown()53 u64 StandardVmCallbacks::HidKeysDown() {
54     const auto applet_resource =
55         system.ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource();
56     if (applet_resource == nullptr) {
57         LOG_WARNING(CheatEngine,
58                     "Attempted to read input state, but applet resource is not initialized!");
59         return 0;
60     }
61 
62     const auto press_state =
63         applet_resource
64             ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)
65             .GetAndResetPressState();
66     return press_state & KEYPAD_BITMASK;
67 }
68 
DebugLog(u8 id,u64 value)69 void StandardVmCallbacks::DebugLog(u8 id, u64 value) {
70     LOG_INFO(CheatEngine, "Cheat triggered DebugLog: ID '{:01X}' Value '{:016X}'", id, value);
71 }
72 
CommandLog(std::string_view data)73 void StandardVmCallbacks::CommandLog(std::string_view data) {
74     LOG_DEBUG(CheatEngine, "[DmntCheatVm]: {}",
75               data.back() == '\n' ? data.substr(0, data.size() - 1) : data);
76 }
77 
SanitizeAddress(VAddr in) const78 VAddr StandardVmCallbacks::SanitizeAddress(VAddr in) const {
79     if ((in < metadata.main_nso_extents.base ||
80          in >= metadata.main_nso_extents.base + metadata.main_nso_extents.size) &&
81         (in < metadata.heap_extents.base ||
82          in >= metadata.heap_extents.base + metadata.heap_extents.size)) {
83         LOG_ERROR(CheatEngine,
84                   "Cheat attempting to access memory at invalid address={:016X}, if this "
85                   "persists, "
86                   "the cheat may be incorrect. However, this may be normal early in execution if "
87                   "the game has not properly set up yet.",
88                   in);
89         return 0; ///< Invalid addresses will hard crash
90     }
91 
92     return in;
93 }
94 
95 CheatParser::~CheatParser() = default;
96 
97 TextCheatParser::~TextCheatParser() = default;
98 
Parse(std::string_view data) const99 std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
100     std::vector<CheatEntry> out(1);
101     std::optional<u64> current_entry;
102 
103     for (std::size_t i = 0; i < data.size(); ++i) {
104         if (::isspace(data[i])) {
105             continue;
106         }
107 
108         if (data[i] == '{') {
109             current_entry = 0;
110 
111             if (out[*current_entry].definition.num_opcodes > 0) {
112                 return {};
113             }
114 
115             const auto name = ExtractName(data, i + 1, '}');
116             if (name.empty()) {
117                 return {};
118             }
119 
120             std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
121                         std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
122                                               name.size()));
123             out[*current_entry]
124                 .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
125                 '\0';
126 
127             i += name.length() + 1;
128         } else if (data[i] == '[') {
129             current_entry = out.size();
130             out.emplace_back();
131 
132             const auto name = ExtractName(data, i + 1, ']');
133             if (name.empty()) {
134                 return {};
135             }
136 
137             std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
138                         std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
139                                               name.size()));
140             out[*current_entry]
141                 .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
142                 '\0';
143 
144             i += name.length() + 1;
145         } else if (::isxdigit(data[i])) {
146             if (!current_entry || out[*current_entry].definition.num_opcodes >=
147                                       out[*current_entry].definition.opcodes.size()) {
148                 return {};
149             }
150 
151             const auto hex = std::string(data.substr(i, 8));
152             if (!std::all_of(hex.begin(), hex.end(), ::isxdigit)) {
153                 return {};
154             }
155 
156             const auto value = static_cast<u32>(std::stoul(hex, nullptr, 0x10));
157             out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
158                 value;
159 
160             i += 8;
161         } else {
162             return {};
163         }
164     }
165 
166     out[0].enabled = out[0].definition.num_opcodes > 0;
167     out[0].cheat_id = 0;
168 
169     for (u32 i = 1; i < out.size(); ++i) {
170         out[i].enabled = out[i].definition.num_opcodes > 0;
171         out[i].cheat_id = i;
172     }
173 
174     return out;
175 }
176 
CheatEngine(Core::System & system,std::vector<CheatEntry> cheats,const std::array<u8,0x20> & build_id)177 CheatEngine::CheatEngine(Core::System& system, std::vector<CheatEntry> cheats,
178                          const std::array<u8, 0x20>& build_id)
179     : vm{std::make_unique<StandardVmCallbacks>(system, metadata)},
180       cheats(std::move(cheats)), core_timing{system.CoreTiming()}, system{system} {
181     metadata.main_nso_build_id = build_id;
182 }
183 
~CheatEngine()184 CheatEngine::~CheatEngine() {
185     core_timing.UnscheduleEvent(event, 0);
186 }
187 
Initialize()188 void CheatEngine::Initialize() {
189     event = Core::Timing::CreateEvent(
190         "CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id),
191         [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
192             FrameCallback(user_data, ns_late);
193         });
194     core_timing.ScheduleEvent(CHEAT_ENGINE_NS, event);
195 
196     metadata.process_id = system.CurrentProcess()->GetProcessID();
197     metadata.title_id = system.CurrentProcess()->GetTitleID();
198 
199     const auto& page_table = system.CurrentProcess()->PageTable();
200     metadata.heap_extents = {
201         .base = page_table.GetHeapRegionStart(),
202         .size = page_table.GetHeapRegionSize(),
203     };
204 
205     metadata.address_space_extents = {
206         .base = page_table.GetAddressSpaceStart(),
207         .size = page_table.GetAddressSpaceSize(),
208     };
209 
210     metadata.alias_extents = {
211         .base = page_table.GetAliasCodeRegionStart(),
212         .size = page_table.GetAliasCodeRegionSize(),
213     };
214 
215     is_pending_reload.exchange(true);
216 }
217 
SetMainMemoryParameters(VAddr main_region_begin,u64 main_region_size)218 void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size) {
219     metadata.main_nso_extents = {
220         .base = main_region_begin,
221         .size = main_region_size,
222     };
223 }
224 
Reload(std::vector<CheatEntry> cheats)225 void CheatEngine::Reload(std::vector<CheatEntry> cheats) {
226     this->cheats = std::move(cheats);
227     is_pending_reload.exchange(true);
228 }
229 
230 MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
231 
FrameCallback(std::uintptr_t,std::chrono::nanoseconds ns_late)232 void CheatEngine::FrameCallback(std::uintptr_t, std::chrono::nanoseconds ns_late) {
233     if (is_pending_reload.exchange(false)) {
234         vm.LoadProgram(cheats);
235     }
236 
237     if (vm.GetProgramSize() == 0) {
238         return;
239     }
240 
241     MICROPROFILE_SCOPE(Cheat_Engine);
242 
243     vm.Execute(metadata);
244 
245     core_timing.ScheduleEvent(CHEAT_ENGINE_NS - ns_late, event);
246 }
247 
248 } // namespace Core::Memory
249