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