1 // Copyright 2018 Citra Emulator Project
2 // Licensed under GPLv2 or any later version
3 // Refer to the license.txt file included.
4 
5 #include <algorithm>
6 #include <cmath>
7 #include <fstream>
8 #include <functional>
9 #include <string>
10 #include <vector>
11 #include "common/file_util.h"
12 #include "common/logging/log.h"
13 #include "common/string_util.h"
14 #include "core/cheats/gateway_cheat.h"
15 #include "core/core.h"
16 #include "core/hle/service/hid/hid.h"
17 #include "core/memory.h"
18 
19 namespace Cheats {
20 
21 struct State {
22     u32 reg = 0;
23     u32 offset = 0;
24     u32 if_flag = 0;
25     u32 loop_count = 0;
26     std::size_t loop_back_line = 0;
27     std::size_t current_line_nr = 0;
28     bool loop_flag = false;
29 };
30 
31 template <typename T, typename ReadFunction, typename WriteFunction>
WriteOp(const GatewayCheat::CheatLine & line,const State & state,ReadFunction read_func,WriteFunction write_func,Core::System & system)32 static inline std::enable_if_t<std::is_integral_v<T>> WriteOp(const GatewayCheat::CheatLine& line,
33                                                               const State& state,
34                                                               ReadFunction read_func,
35                                                               WriteFunction write_func,
36                                                               Core::System& system) {
37     u32 addr = line.address + state.offset;
38     T val = read_func(addr);
39     if (val != static_cast<T>(line.value)) {
40         write_func(addr, static_cast<T>(line.value));
41         system.InvalidateCacheRange(addr, sizeof(T));
42     }
43 }
44 
45 template <typename T, typename ReadFunction, typename CompareFunc>
CompOp(const GatewayCheat::CheatLine & line,State & state,ReadFunction read_func,CompareFunc comp)46 static inline std::enable_if_t<std::is_integral_v<T>> CompOp(const GatewayCheat::CheatLine& line,
47                                                              State& state, ReadFunction read_func,
48                                                              CompareFunc comp) {
49     u32 addr = line.address + state.offset;
50     T val = read_func(addr);
51     if (!comp(val)) {
52         state.if_flag++;
53     }
54 }
55 
LoadOffsetOp(Memory::MemorySystem & memory,const GatewayCheat::CheatLine & line,State & state)56 static inline void LoadOffsetOp(Memory::MemorySystem& memory, const GatewayCheat::CheatLine& line,
57                                 State& state) {
58     u32 addr = line.address + state.offset;
59     state.offset = memory.Read32(addr);
60 }
61 
LoopOp(const GatewayCheat::CheatLine & line,State & state)62 static inline void LoopOp(const GatewayCheat::CheatLine& line, State& state) {
63     state.loop_flag = state.loop_count < line.value;
64     state.loop_count++;
65     state.loop_back_line = state.current_line_nr;
66 }
67 
TerminateOp(State & state)68 static inline void TerminateOp(State& state) {
69     if (state.if_flag > 0) {
70         state.if_flag--;
71     }
72 }
73 
LoopExecuteVariantOp(State & state)74 static inline void LoopExecuteVariantOp(State& state) {
75     if (state.loop_flag) {
76         state.current_line_nr = state.loop_back_line - 1;
77     } else {
78         state.loop_count = 0;
79     }
80 }
81 
FullTerminateOp(State & state)82 static inline void FullTerminateOp(State& state) {
83     if (state.loop_flag) {
84         state.current_line_nr = state.loop_back_line - 1;
85     } else {
86         state.offset = 0;
87         state.reg = 0;
88         state.loop_count = 0;
89         state.if_flag = 0;
90         state.loop_flag = false;
91     }
92 }
93 
SetOffsetOp(const GatewayCheat::CheatLine & line,State & state)94 static inline void SetOffsetOp(const GatewayCheat::CheatLine& line, State& state) {
95     state.offset = line.value;
96 }
97 
AddValueOp(const GatewayCheat::CheatLine & line,State & state)98 static inline void AddValueOp(const GatewayCheat::CheatLine& line, State& state) {
99     state.reg += line.value;
100 }
101 
SetValueOp(const GatewayCheat::CheatLine & line,State & state)102 static inline void SetValueOp(const GatewayCheat::CheatLine& line, State& state) {
103     state.reg = line.value;
104 }
105 
106 template <typename T, typename ReadFunction, typename WriteFunction>
IncrementiveWriteOp(const GatewayCheat::CheatLine & line,State & state,ReadFunction read_func,WriteFunction write_func,Core::System & system)107 static inline std::enable_if_t<std::is_integral_v<T>> IncrementiveWriteOp(
108     const GatewayCheat::CheatLine& line, State& state, ReadFunction read_func,
109     WriteFunction write_func, Core::System& system) {
110     u32 addr = line.value + state.offset;
111     T val = read_func(addr);
112     if (val != static_cast<T>(state.reg)) {
113         write_func(addr, static_cast<T>(state.reg));
114         system.InvalidateCacheRange(addr, sizeof(T));
115     }
116     state.offset += sizeof(T);
117 }
118 
119 template <typename T, typename ReadFunction>
LoadOp(const GatewayCheat::CheatLine & line,State & state,ReadFunction read_func)120 static inline std::enable_if_t<std::is_integral_v<T>> LoadOp(const GatewayCheat::CheatLine& line,
121                                                              State& state, ReadFunction read_func) {
122 
123     u32 addr = line.value + state.offset;
124     state.reg = read_func(addr);
125 }
126 
AddOffsetOp(const GatewayCheat::CheatLine & line,State & state)127 static inline void AddOffsetOp(const GatewayCheat::CheatLine& line, State& state) {
128     state.offset += line.value;
129 }
130 
JokerOp(const GatewayCheat::CheatLine & line,State & state,const Core::System & system)131 static inline void JokerOp(const GatewayCheat::CheatLine& line, State& state,
132                            const Core::System& system) {
133     u32 pad_state = system.ServiceManager()
134                         .GetService<Service::HID::Module::Interface>("hid:USER")
135                         ->GetModule()
136                         ->GetState()
137                         .hex;
138     bool pressed = (pad_state & line.value) == line.value;
139     if (!pressed) {
140         state.if_flag++;
141     }
142 }
143 
PatchOp(const GatewayCheat::CheatLine & line,State & state,Core::System & system,const std::vector<GatewayCheat::CheatLine> & cheat_lines)144 static inline void PatchOp(const GatewayCheat::CheatLine& line, State& state, Core::System& system,
145                            const std::vector<GatewayCheat::CheatLine>& cheat_lines) {
146     if (state.if_flag > 0) {
147         // Skip over the additional patch lines
148         state.current_line_nr += static_cast<int>(std::ceil(line.value / 8.0));
149         return;
150     }
151     u32 num_bytes = line.value;
152     u32 addr = line.address + state.offset;
153     system.InvalidateCacheRange(addr, num_bytes);
154 
155     bool first = true;
156     u32 bit_offset = 0;
157     if (num_bytes > 0)
158         state.current_line_nr++; // skip over the current code
159     while (num_bytes >= 4) {
160         u32 tmp = first ? cheat_lines[state.current_line_nr].first
161                         : cheat_lines[state.current_line_nr].value;
162         if (!first && num_bytes > 4) {
163             state.current_line_nr++;
164         }
165         first = !first;
166         system.Memory().Write32(addr, tmp);
167         addr += 4;
168         num_bytes -= 4;
169     }
170     while (num_bytes > 0) {
171         u32 tmp = (first ? cheat_lines[state.current_line_nr].first
172                          : cheat_lines[state.current_line_nr].value) >>
173                   bit_offset;
174         system.Memory().Write8(addr, tmp);
175         addr += 1;
176         num_bytes -= 1;
177         bit_offset += 8;
178     }
179 }
180 
CheatLine(const std::string & line)181 GatewayCheat::CheatLine::CheatLine(const std::string& line) {
182     constexpr std::size_t cheat_length = 17;
183     if (line.length() != cheat_length) {
184         type = CheatType::Null;
185         cheat_line = line;
186         LOG_ERROR(Core_Cheats, "Cheat contains invalid line: {}", line);
187         valid = false;
188         return;
189     }
190     try {
191         std::string type_temp = line.substr(0, 1);
192         // 0xD types have extra subtype value, i.e. 0xDA
193         std::string sub_type_temp;
194         if (type_temp == "D" || type_temp == "d")
195             sub_type_temp = line.substr(1, 1);
196         type = static_cast<CheatType>(std::stoi(type_temp + sub_type_temp, 0, 16));
197         first = std::stoul(line.substr(0, 8), 0, 16);
198         address = first & 0x0FFFFFFF;
199         value = std::stoul(line.substr(9, 8), 0, 16);
200         cheat_line = line;
201     } catch (const std::logic_error&) {
202         type = CheatType::Null;
203         cheat_line = line;
204         LOG_ERROR(Core_Cheats, "Cheat contains invalid line: {}", line);
205         valid = false;
206     }
207 }
208 
GatewayCheat(std::string name_,std::vector<CheatLine> cheat_lines_,std::string comments_)209 GatewayCheat::GatewayCheat(std::string name_, std::vector<CheatLine> cheat_lines_,
210                            std::string comments_)
211     : name(std::move(name_)), cheat_lines(std::move(cheat_lines_)), comments(std::move(comments_)) {
212 }
213 
GatewayCheat(std::string name_,std::string code,std::string comments_)214 GatewayCheat::GatewayCheat(std::string name_, std::string code, std::string comments_)
215     : name(std::move(name_)), comments(std::move(comments_)) {
216 
217     std::vector<std::string> code_lines;
218     Common::SplitString(code, '\n', code_lines);
219 
220     std::vector<CheatLine> temp_cheat_lines;
221     for (std::size_t i = 0; i < code_lines.size(); ++i) {
222         if (!code_lines[i].empty())
223             temp_cheat_lines.emplace_back(code_lines[i]);
224     }
225     cheat_lines = std::move(temp_cheat_lines);
226 }
227 
228 GatewayCheat::~GatewayCheat() = default;
229 
Execute(Core::System & system) const230 void GatewayCheat::Execute(Core::System& system) const {
231     State state;
232 
233     Memory::MemorySystem& memory = system.Memory();
234     auto Read8 = [&memory](VAddr addr) { return memory.Read8(addr); };
235     auto Read16 = [&memory](VAddr addr) { return memory.Read16(addr); };
236     auto Read32 = [&memory](VAddr addr) { return memory.Read32(addr); };
237     auto Write8 = [&memory](VAddr addr, u8 value) { memory.Write8(addr, value); };
238     auto Write16 = [&memory](VAddr addr, u16 value) { memory.Write16(addr, value); };
239     auto Write32 = [&memory](VAddr addr, u32 value) { memory.Write32(addr, value); };
240 
241     for (state.current_line_nr = 0; state.current_line_nr < cheat_lines.size();
242          state.current_line_nr++) {
243         auto line = cheat_lines[state.current_line_nr];
244         if (state.if_flag > 0) {
245             switch (line.type) {
246             case CheatType::GreaterThan32:
247             case CheatType::LessThan32:
248             case CheatType::EqualTo32:
249             case CheatType::NotEqualTo32:
250             case CheatType::GreaterThan16WithMask:
251             case CheatType::LessThan16WithMask:
252             case CheatType::EqualTo16WithMask:
253             case CheatType::NotEqualTo16WithMask:
254             case CheatType::Joker:
255                 // Increment the if_flag to handle the end if correctly
256                 state.if_flag++;
257                 break;
258             case CheatType::Patch:
259                 // EXXXXXXX YYYYYYYY
260                 // Copies YYYYYYYY bytes from (current code location + 8) to [XXXXXXXX + offset].
261                 // We need to call this here to skip the additional patch lines
262                 PatchOp(line, state, system, cheat_lines);
263                 break;
264             case CheatType::Terminator:
265                 // D0000000 00000000 - ENDIF
266                 TerminateOp(state);
267                 break;
268             case CheatType::FullTerminator:
269                 // D2000000 00000000 - END; offset = 0; reg = 0;
270                 FullTerminateOp(state);
271                 break;
272             default:
273                 break;
274             }
275             // Do not execute any other op code
276             continue;
277         }
278         switch (line.type) {
279         case CheatType::Null:
280             break;
281         case CheatType::Write32:
282             // 0XXXXXXX YYYYYYYY - word[XXXXXXX+offset] = YYYYYYYY
283             WriteOp<u32>(line, state, Read32, Write32, system);
284             break;
285         case CheatType::Write16:
286             // 1XXXXXXX 0000YYYY - half[XXXXXXX+offset] = YYYY
287             WriteOp<u16>(line, state, Read16, Write16, system);
288             break;
289         case CheatType::Write8:
290             // 2XXXXXXX 000000YY - byte[XXXXXXX+offset] = YY
291             WriteOp<u8>(line, state, Read8, Write8, system);
292             break;
293         case CheatType::GreaterThan32:
294             // 3XXXXXXX YYYYYYYY - Execute next block IF YYYYYYYY > word[XXXXXXX]   ;unsigned
295             CompOp<u32>(line, state, Read32, [&line](u32 val) -> bool { return line.value > val; });
296             break;
297         case CheatType::LessThan32:
298             // 4XXXXXXX YYYYYYYY - Execute next block IF YYYYYYYY < word[XXXXXXX]   ;unsigned
299             CompOp<u32>(line, state, Read32, [&line](u32 val) -> bool { return line.value < val; });
300             break;
301         case CheatType::EqualTo32:
302             // 5XXXXXXX YYYYYYYY - Execute next block IF YYYYYYYY == word[XXXXXXX]   ;unsigned
303             CompOp<u32>(line, state, Read32,
304                         [&line](u32 val) -> bool { return line.value == val; });
305             break;
306         case CheatType::NotEqualTo32:
307             // 6XXXXXXX YYYYYYYY - Execute next block IF YYYYYYYY != word[XXXXXXX]   ;unsigned
308             CompOp<u32>(line, state, Read32,
309                         [&line](u32 val) -> bool { return line.value != val; });
310             break;
311         case CheatType::GreaterThan16WithMask:
312             // 7XXXXXXX ZZZZYYYY - Execute next block IF YYYY > ((not ZZZZ) AND half[XXXXXXX])
313             CompOp<u16>(line, state, Read16, [&line](u16 val) -> bool {
314                 return static_cast<u16>(line.value) > (static_cast<u16>(~line.value >> 16) & val);
315             });
316             break;
317         case CheatType::LessThan16WithMask:
318             // 8XXXXXXX ZZZZYYYY - Execute next block IF YYYY < ((not ZZZZ) AND half[XXXXXXX])
319             CompOp<u16>(line, state, Read16, [&line](u16 val) -> bool {
320                 return static_cast<u16>(line.value) < (static_cast<u16>(~line.value >> 16) & val);
321             });
322             break;
323         case CheatType::EqualTo16WithMask:
324             // 9XXXXXXX ZZZZYYYY - Execute next block IF YYYY = ((not ZZZZ) AND half[XXXXXXX])
325             CompOp<u16>(line, state, Read16, [&line](u16 val) -> bool {
326                 return static_cast<u16>(line.value) == (static_cast<u16>(~line.value >> 16) & val);
327             });
328             break;
329         case CheatType::NotEqualTo16WithMask:
330             // AXXXXXXX ZZZZYYYY - Execute next block IF YYYY <> ((not ZZZZ) AND half[XXXXXXX])
331             CompOp<u16>(line, state, Read16, [&line](u16 val) -> bool {
332                 return static_cast<u16>(line.value) != (static_cast<u16>(~line.value >> 16) & val);
333             });
334             break;
335         case CheatType::LoadOffset:
336             // BXXXXXXX 00000000 - offset = word[XXXXXXX+offset]
337             LoadOffsetOp(system.Memory(), line, state);
338             break;
339         case CheatType::Loop: {
340             // C0000000 YYYYYYYY - LOOP next block YYYYYYYY times
341             // TODO(B3N30): Support nested loops if necessary
342             LoopOp(line, state);
343             break;
344         }
345         case CheatType::Terminator: {
346             // D0000000 00000000 - END IF
347             TerminateOp(state);
348             break;
349         }
350         case CheatType::LoopExecuteVariant: {
351             // D1000000 00000000 - END LOOP
352             LoopExecuteVariantOp(state);
353             break;
354         }
355         case CheatType::FullTerminator: {
356             // D2000000 00000000 - NEXT & Flush
357             FullTerminateOp(state);
358             break;
359         }
360         case CheatType::SetOffset: {
361             // D3000000 XXXXXXXX – Sets the offset to XXXXXXXX
362             SetOffsetOp(line, state);
363             break;
364         }
365         case CheatType::AddValue: {
366             // D4000000 XXXXXXXX – reg += XXXXXXXX
367             AddValueOp(line, state);
368             break;
369         }
370         case CheatType::SetValue: {
371             // D5000000 XXXXXXXX – reg = XXXXXXXX
372             SetValueOp(line, state);
373             break;
374         }
375         case CheatType::IncrementiveWrite32: {
376             // D6000000 XXXXXXXX – (32bit) [XXXXXXXX+offset] = reg ; offset += 4
377             IncrementiveWriteOp<u32>(line, state, Read32, Write32, system);
378             break;
379         }
380         case CheatType::IncrementiveWrite16: {
381             // D7000000 XXXXXXXX – (16bit) [XXXXXXXX+offset] = reg & 0xffff ; offset += 2
382             IncrementiveWriteOp<u16>(line, state, Read16, Write16, system);
383             break;
384         }
385         case CheatType::IncrementiveWrite8: {
386             // D8000000 XXXXXXXX – (16bit) [XXXXXXXX+offset] = reg & 0xff ; offset++
387             IncrementiveWriteOp<u8>(line, state, Read8, Write8, system);
388             break;
389         }
390         case CheatType::Load32: {
391             // D9000000 XXXXXXXX – reg = [XXXXXXXX+offset]
392             LoadOp<u32>(line, state, Read32);
393             break;
394         }
395         case CheatType::Load16: {
396             // DA000000 XXXXXXXX – reg = [XXXXXXXX+offset] & 0xFFFF
397             LoadOp<u16>(line, state, Read16);
398             break;
399         }
400         case CheatType::Load8: {
401             // DB000000 XXXXXXXX – reg = [XXXXXXXX+offset] & 0xFF
402             LoadOp<u8>(line, state, Read8);
403             break;
404         }
405         case CheatType::AddOffset: {
406             // DC000000 XXXXXXXX – offset + XXXXXXXX
407             AddOffsetOp(line, state);
408             break;
409         }
410         case CheatType::Joker: {
411             // DD000000 XXXXXXXX – if KEYPAD has value XXXXXXXX execute next block
412             JokerOp(line, state, system);
413             break;
414         }
415         case CheatType::Patch: {
416             // EXXXXXXX YYYYYYYY
417             // Copies YYYYYYYY bytes from (current code location + 8) to [XXXXXXXX + offset].
418             PatchOp(line, state, system, cheat_lines);
419             break;
420         }
421         }
422     }
423 }
424 
IsEnabled() const425 bool GatewayCheat::IsEnabled() const {
426     return enabled;
427 }
428 
SetEnabled(bool enabled_)429 void GatewayCheat::SetEnabled(bool enabled_) {
430     enabled = enabled_;
431     if (enabled) {
432         LOG_WARNING(Core_Cheats, "Cheats enabled. This might lead to weird behaviour or crashes");
433     }
434 }
435 
GetComments() const436 std::string GatewayCheat::GetComments() const {
437     return comments;
438 }
439 
GetName() const440 std::string GatewayCheat::GetName() const {
441     return name;
442 }
443 
GetType() const444 std::string GatewayCheat::GetType() const {
445     return "Gateway";
446 }
447 
GetCode() const448 std::string GatewayCheat::GetCode() const {
449     std::string result;
450     for (const auto& line : cheat_lines)
451         result += line.cheat_line + '\n';
452     return result;
453 }
454 
455 /// A special marker used to keep track of enabled cheats
456 static constexpr char EnabledText[] = "*citra_enabled";
457 
ToString() const458 std::string GatewayCheat::ToString() const {
459     std::string result;
460     result += '[' + name + "]\n";
461     if (enabled) {
462         result += EnabledText;
463         result += '\n';
464     }
465     std::vector<std::string> comment_lines;
466     Common::SplitString(comments, '\n', comment_lines);
467     for (const auto& comment_line : comment_lines)
468         result += "*" + comment_line + '\n';
469     result += GetCode() + '\n';
470     return result;
471 }
472 
LoadFile(const std::string & filepath)473 std::vector<std::unique_ptr<CheatBase>> GatewayCheat::LoadFile(const std::string& filepath) {
474     std::vector<std::unique_ptr<CheatBase>> cheats;
475 
476     std::ifstream file;
477     OpenFStream(file, filepath, std::ios_base::in);
478     if (!file) {
479         return cheats;
480     }
481 
482     std::string comments;
483     std::vector<CheatLine> cheat_lines;
484     std::string name;
485     bool enabled = false;
486 
487     while (!file.eof()) {
488         std::string line;
489         std::getline(file, line);
490         line.erase(std::remove(line.begin(), line.end(), '\0'), line.end());
491         line = Common::StripSpaces(line); // remove spaces at front and end
492         if (line.length() >= 2 && line.front() == '[') {
493             if (!cheat_lines.empty()) {
494                 cheats.push_back(std::make_unique<GatewayCheat>(name, cheat_lines, comments));
495                 cheats.back()->SetEnabled(enabled);
496                 enabled = false;
497             }
498             name = line.substr(1, line.length() - 2);
499             cheat_lines.clear();
500             comments.erase();
501         } else if (!line.empty() && line.front() == '*') {
502             if (line == EnabledText) {
503                 enabled = true;
504             } else {
505                 comments += line.substr(1, line.length() - 1) + '\n';
506             }
507         } else if (!line.empty()) {
508             cheat_lines.emplace_back(std::move(line));
509         }
510     }
511     if (!cheat_lines.empty()) {
512         cheats.push_back(std::make_unique<GatewayCheat>(name, cheat_lines, comments));
513         cheats.back()->SetEnabled(enabled);
514     }
515     return cheats;
516 }
517 } // namespace Cheats
518