Merge pull request #90 from gizmo98/patch-2
[libretro-ppsspp.git] / Core / CwCheat.cpp
blob1c2bc1994a00a00c406047e9ea9d6a768eaaa223
1 #include "i18n/i18n.h"
2 #include "UI/OnScreenDisplay.h"
3 #include "Common/StringUtils.h"
4 #include "Common/ChunkFile.h"
5 #include "Common/FileUtil.h"
6 #include "Core/CoreTiming.h"
7 #include "Core/CoreParameter.h"
8 #include "Core/CwCheat.h"
9 #include "Core/Config.h"
10 #include "Core/MIPS/MIPS.h"
11 #include "Core/ELF/ParamSFO.h"
12 #include "Core/System.h"
13 #include "Core/HLE/sceCtrl.h"
14 #include "Core/MIPS/JitCommon/NativeJit.h"
16 #if defined(_WIN32) && !defined(__MINGW32__)
17 #include "util/text/utf8.h"
18 #endif
20 static int CheatEvent = -1;
21 std::string gameTitle;
22 std::string activeCheatFile;
23 static CWCheatEngine *cheatEngine;
24 static bool cheatsEnabled;
25 void hleCheat(u64 userdata, int cyclesLate);
26 void trim2(std::string& str);
28 static void __CheatStop() {
29 if (cheatEngine != 0) {
30 cheatEngine->Exit();
31 delete cheatEngine;
32 cheatEngine = 0;
34 cheatsEnabled = false;
37 static void __CheatStart() {
38 __CheatStop();
40 gameTitle = g_paramSFO.GetValueString("DISC_ID");
42 activeCheatFile = GetSysDirectory(DIRECTORY_CHEATS) + gameTitle + ".ini";
43 File::CreateFullPath(GetSysDirectory(DIRECTORY_CHEATS));
45 if (!File::Exists(activeCheatFile)) {
46 FILE *f = File::OpenCFile(activeCheatFile, "wb");
47 if (f) {
48 fwrite("\xEF\xBB\xBF", 1, 3, f);
49 fclose(f);
51 if (!File::Exists(activeCheatFile)) {
52 I18NCategory *err = GetI18NCategory("Error");
53 osm.Show(err->T("Unable to create cheat file, disk may be full"));
58 cheatEngine = new CWCheatEngine();
59 cheatEngine->CreateCodeList();
60 g_Config.bReloadCheats = false;
61 cheatsEnabled = true;
64 void __CheatInit() {
65 // Always register the event, want savestates to be compatible whether cheats on or off.
66 CheatEvent = CoreTiming::RegisterEvent("CheatEvent", &hleCheat);
68 if (g_Config.bEnableCheats) {
69 __CheatStart();
72 int refresh = g_Config.iCwCheatRefreshRate;
74 // Only check once a second for cheats to be enabled.
75 CoreTiming::ScheduleEvent(msToCycles(cheatsEnabled ? refresh : 1000), CheatEvent, 0);
78 void __CheatShutdown() {
79 __CheatStop();
82 void __CheatDoState(PointerWrap &p) {
83 auto s = p.Section("CwCheat", 0, 2);
84 if (!s) {
85 return;
88 p.Do(CheatEvent);
89 CoreTiming::RestoreRegisterEvent(CheatEvent, "CheatEvent", &hleCheat);
91 int refresh = g_Config.iCwCheatRefreshRate;
93 if (s < 2) {
94 // Before this we didn't have a checkpoint, so reset didn't work.
95 // Let's just force one in.
96 CoreTiming::RemoveEvent(CheatEvent);
97 CoreTiming::ScheduleEvent(msToCycles(cheatsEnabled ? refresh : 1000), CheatEvent, 0);
101 void hleCheat(u64 userdata, int cyclesLate) {
102 if (cheatsEnabled != g_Config.bEnableCheats) {
103 // Okay, let's move to the desired state, then.
104 if (g_Config.bEnableCheats) {
105 __CheatStart();
106 } else {
107 __CheatStop();
111 int refresh = g_Config.iCwCheatRefreshRate;
113 // Only check once a second for cheats to be enabled.
114 CoreTiming::ScheduleEvent(msToCycles(cheatsEnabled ? refresh : 1000), CheatEvent, 0);
116 if (!cheatEngine || !cheatsEnabled)
117 return;
119 if (g_Config.bReloadCheats) { //Checks if the "reload cheats" button has been pressed.
120 cheatEngine->CreateCodeList();
121 g_Config.bReloadCheats = false;
123 cheatEngine->Run();
126 CWCheatEngine::CWCheatEngine() {
130 void CWCheatEngine::Exit() {
131 exit2 = true;
134 // Takes a single code line and creates a two-part vector for each code. Feeds to CreateCodeList
135 static inline std::vector<std::string> makeCodeParts(const std::vector<std::string>& CodesList) {
136 std::string currentcode;
137 std::vector<std::string> finalList;
138 char split_char = '\n';
139 char empty = ' ';
140 for (size_t i = 0; i < CodesList.size(); i++) {
141 currentcode = CodesList[i];
142 for (size_t j=0; j < currentcode.length(); j++) {
143 if (currentcode[j] == empty) {
144 currentcode[j] = '\n';
147 trim2(currentcode);
148 std::istringstream iss(currentcode);
149 std::string each;
150 while (std::getline(iss, each, split_char)) {
151 finalList.push_back(each);
154 return finalList;
157 void CWCheatEngine::CreateCodeList() { //Creates code list to be used in function GetNextCode
158 initialCodesList = GetCodesList();
159 std::string currentcode, codename;
160 std::vector<std::string> codelist;
161 for (size_t i = 0; i < initialCodesList.size(); i ++) {
162 if (initialCodesList[i].substr(0,2) == "_S") {
163 continue; //Line indicates Disc ID, not needed for cheats
165 if (initialCodesList[i].substr(0,2) == "_G") {
166 continue; //Line indicates game Title, also not needed for cheats
168 if (initialCodesList[i].substr(0,2) == "//") {
169 continue; //Line indicates comment, also not needed for cheats.
171 if (initialCodesList[i].substr(0,3) == "_C1") {
172 cheatEnabled = true;
173 codename = initialCodesList[i];
174 codename.erase (codename.begin(), codename.begin()+4);
175 codeNameList.push_back(codename); //Import names for GUI, will be implemented later.
176 continue;
178 if (initialCodesList[i].substr(0,2) == "_L") {
179 if (cheatEnabled == true) {
180 currentcode = initialCodesList[i];
181 currentcode.erase(currentcode.begin(), currentcode.begin() + 3);
182 codelist.push_back(currentcode);
184 continue;
186 if (initialCodesList[i].substr(0,3) == "_C0") {
187 cheatEnabled = false;
188 codename = initialCodesList[i];
189 codename.erase (codename.begin(), codename.begin()+4);
190 codeNameList.push_back(codename); //Import names for GUI, will be implemented later.
191 continue;
194 parts = makeCodeParts(codelist);
197 std::vector<int> CWCheatEngine::GetNextCode() { // Feeds a size-2 vector of ints to Run() which contains the address and value of one cheat.
198 std::string code1;
199 std::string code2;
200 std::vector<std::string> splitCode;
201 std::vector<int> finalCode;
202 std::string modifier2 = "0";
203 while (true) {
204 if (currentCode >= parts.size()) {
205 code1.clear();
206 code2.clear();
207 break;
209 code1 = parts[currentCode++];
210 trim2(code1);
211 code2 = parts[currentCode++];
212 trim2(code2);
213 splitCode.push_back(code1);
214 splitCode.push_back(code2);
216 int var1 = (int) parseHexLong(splitCode[0]);
217 int var2 = (int) parseHexLong(splitCode[1]);
218 finalCode.push_back(var1);
219 finalCode.push_back(var2);
220 if (splitCode[0].substr(0,1) == modifier2) {
221 break;
224 return finalCode;
227 void CWCheatEngine::SkipCodes(int count) {
228 for (int i = 0; i < count; i ++) {
229 auto code = GetNextCode();
230 if (code.empty())
232 WARN_LOG(COMMON, "CWCHEAT: Tried to skip more codes than there are, the cheat is most likely wrong");
233 break;
235 if (code[0] == 0) {
236 break;
241 void CWCheatEngine::SkipAllCodes() {
242 currentCode = codes.size() - 1;
245 int CWCheatEngine::GetAddress(int value) { //Returns static address used by ppsspp. Some games may not like this, and causes cheats to not work without offset
246 int address = (value + 0x08800000) & 0x3FFFFFFF;
247 if (gameTitle == "ULUS10563" || gameTitle == "ULJS-00351" || gameTitle == "NPJH50352" ) //Offset to make God Eater Burst codes work
248 address -= 0x7EF00;
249 return address;
253 inline void trim2(std::string& str) {
254 size_t pos = str.find_last_not_of(' ');
255 if(pos != std::string::npos) {
256 str.erase(pos + 1);
257 pos = str.find_first_not_of(' ');
258 if(pos != std::string::npos) str.erase(0, pos);
260 else str.erase(str.begin(), str.end());
263 std::vector<std::string> CWCheatEngine::GetCodesList() { //Reads the entire cheat list from the appropriate .ini.
264 std::string line;
265 std::vector<std::string> codesList; // Read from INI here
266 #if defined(_WIN32) && !defined(__MINGW32__)
267 std::ifstream list(ConvertUTF8ToWString(activeCheatFile));
268 #else
269 std::ifstream list(activeCheatFile.c_str());
270 #endif
271 if (!list) {
272 return codesList;
274 for (int i = 0; !list.eof(); i ++) {
275 getline(list, line, '\n');
276 if (line.length() > 3 && (line.substr(0,1) == "_"||line.substr(0,2) == "//")){
277 codesList.push_back(line);
280 for(size_t i = 0; i < codesList.size(); i++) {
281 trim2(codesList[i]);
283 return codesList;
286 void CWCheatEngine::InvalidateICache(u32 addr, int size) {
287 currentMIPS->InvalidateICache(addr & ~3, size);
290 void CWCheatEngine::Run() {
291 exit2 = false;
292 while (!exit2) {
293 currentCode = 0;
295 while (true) {
296 std::vector<int> code = GetNextCode();
297 if (code.size() < 2) {
298 Exit();
299 break;
302 int value;
303 unsigned int comm = code[0];
304 u32 arg = code[1];
305 int addr = GetAddress(comm & 0x0FFFFFFF);
307 switch (comm >> 28) {
308 case 0: // 8-bit write.But need more check
309 if (Memory::IsValidAddress(addr)) {
310 InvalidateICache(addr & ~3, 4);
311 if (arg < 0x00000100) // 8-bit
312 Memory::Write_U8((u8) arg, addr);
313 else if (arg < 0x00010000) // 16-bit
314 Memory::Write_U16((u16) arg, addr);
315 else // 32-bit
316 Memory::Write_U32((u32) arg, addr);
318 break;
319 case 0x1: // 16-bit write
320 if (Memory::IsValidAddress(addr)) {
321 InvalidateICache(addr & ~3, 4);
322 Memory::Write_U16((u16) arg, addr);
324 break;
325 case 0x2: // 32-bit write
326 if (Memory::IsValidAddress(addr)){
327 InvalidateICache(addr & ~3, 4);
328 Memory::Write_U32((u32) arg, addr);
330 break;
331 case 0x3: // Increment/Decrement
333 addr = GetAddress(arg & 0x0FFFFFFF);
334 InvalidateICache(addr & ~3, 4);
335 value = 0;
336 int increment = 0;
337 // Read value from memory
338 switch ((comm >> 20) & 0xF) {
339 case 1:
340 case 2: // 8-bit
341 value = Memory::Read_U8(addr);
342 increment = comm & 0xFF;
343 break;
344 case 3:
345 case 4: // 16-bit
346 value = Memory::Read_U16(addr);
347 increment = comm & 0xFFFF;
348 break;
349 case 5:
350 case 6: // 32-bit
351 value = Memory::Read_U32(addr);
352 code = GetNextCode();
353 if (code[0] != 0) {
354 increment = code[0];
356 break;
358 // Increment/Decrement value
359 switch ((comm >> 20) & 0xF) {
360 case 1:
361 case 3:
362 case 5: // increment
363 value += increment;
364 break;
365 case 2:
366 case 4:
367 case 6: // Decrement
368 value -= increment;
369 break;
371 // Write value back to memory
372 switch ((comm >> 20) & 0xF) {
373 case 1:
374 case 2: // 8-bit
375 Memory::Write_U8((u8) value, addr);
376 break;
377 case 3:
378 case 4: // 16-bit
379 Memory::Write_U16((u16) value, addr);
380 break;
381 case 5:
382 case 6: // 32-bit
383 Memory::Write_U32((u32) value, addr);
384 break;
386 break;
388 case 0x4: // 32-bit patch code
389 code = GetNextCode();
390 if (true) {
391 int data = code[0];
392 int dataAdd = code[1];
394 int count = (arg >> 16) & 0xFFFF;
395 int stepAddr = (arg & 0xFFFF) * 4;
397 InvalidateICache(addr, count * stepAddr);
398 for (int a = 0; a < count; a++) {
399 if (Memory::IsValidAddress(addr)) {
400 Memory::Write_U32((u32) data, addr);
402 addr += stepAddr;
403 data += dataAdd;
406 break;
407 case 0x5: // Memcpy command
408 code = GetNextCode();
409 if (true) {
410 int destAddr = GetAddress(code[0]);
411 int len = arg;
412 InvalidateICache(destAddr, len);
413 if (Memory::IsValidAddress(addr) && Memory::IsValidAddress(destAddr)) {
414 Memory::MemcpyUnchecked(destAddr, addr, len);
417 break;
418 case 0x6: // Pointer commands
419 code = GetNextCode();
420 if (code.size() >= 2) {
421 int arg2 = code[0];
422 int offset = code[1];
423 int baseOffset = (arg2 >> 20) * 4;
424 InvalidateICache(addr + baseOffset, 4);
425 int base = Memory::Read_U32(addr + baseOffset);
426 int count = arg2 & 0xFFFF;
427 int type = (arg2 >> 16) & 0xF;
428 for (int i = 1; i < count; i ++ ) {
429 if (i+1 < count) {
430 code = GetNextCode();
431 if (code.size() < 2) {
432 // Code broken. Should warn but would be very spammy...
433 break;
435 int arg3 = code[0];
436 int arg4 = code[1];
437 int comm3 = arg3 >> 28;
438 switch (comm3) {
439 case 0x1: // type copy byte
441 int srcAddr = Memory::Read_U32(addr) + offset;
442 int dstAddr = Memory::Read_U16(addr + baseOffset) + (arg3 & 0x0FFFFFFF);
443 if (Memory::IsValidAddress(dstAddr) && Memory::IsValidAddress(srcAddr)) {
444 Memory::MemcpyUnchecked(dstAddr, srcAddr, arg);
446 type = -1; //Done
447 break; }
448 case 0x2:
449 case 0x3: // type pointer walk
451 int walkOffset = arg3 & 0x0FFFFFFF;
452 if (comm3 == 0x3) {
453 walkOffset = -walkOffset;
455 base = Memory::Read_U32(base + walkOffset);
456 int comm4 = arg4 >> 28;
457 switch (comm4) {
458 case 0x2:
459 case 0x3: // type pointer walk
460 walkOffset = arg4 & 0x0FFFFFFF;
461 if (comm4 == 0x3) {
462 walkOffset = -walkOffset;
464 base = Memory::Read_U32(base + walkOffset);
465 break;
467 break; }
468 case 0x9: // type multi address write
469 base += arg3 & 0x0FFFFFFF;
470 arg += arg4;
471 break;
472 default:
473 break;
479 switch (type) {
480 case 0: // 8 bit write
481 Memory::Write_U8((u8) arg, base + offset);
482 break;
483 case 1: // 16-bit write
484 Memory::Write_U16((u16) arg, base + offset);
485 break;
486 case 2: // 32-bit write
487 Memory::Write_U32((u32) arg, base + offset);
488 break;
489 case 3: // 8 bit inverse write
490 Memory::Write_U8((u8) arg, base - offset);
491 break;
492 case 4: // 16-bit inverse write
493 Memory::Write_U16((u16) arg, base - offset);
494 break;
495 case 5: // 32-bit inverse write
496 Memory::Write_U32((u32) arg, base - offset);
497 break;
498 case -1: // Operation already performed, nothing to do
499 break;
502 break;
503 case 0x7: // Boolean commands.
504 switch (arg >> 16) {
505 case 0x0000: // 8-bit OR.
506 if (Memory::IsValidAddress(addr)) {
507 InvalidateICache(addr & ~3, 4);
508 int val1 = (int) (arg & 0xFF);
509 int val2 = (int) Memory::Read_U8(addr);
510 Memory::Write_U8((u8) (val1 | val2), addr);
512 break;
513 case 0x0002: // 8-bit AND.
514 if (Memory::IsValidAddress(addr)) {
515 InvalidateICache(addr & ~3, 4);
516 int val1 = (int) (arg & 0xFF);
517 int val2 = (int) Memory::Read_U8(addr);
518 Memory::Write_U8((u8) (val1 & val2), addr);
520 break;
521 case 0x0004: // 8-bit XOR.
522 if (Memory::IsValidAddress(addr)) {
523 InvalidateICache(addr & ~3, 4);
524 int val1 = (int) (arg & 0xFF);
525 int val2 = (int) Memory::Read_U8(addr);
526 Memory::Write_U8((u8) (val1 ^ val2), addr);
528 break;
529 case 0x0001: // 16-bit OR.
530 if (Memory::IsValidAddress(addr)) {
531 InvalidateICache(addr & ~3, 4);
532 short val1 = (short) (arg & 0xFFFF);
533 short val2 = (short) Memory::Read_U16(addr);
534 Memory::Write_U16((u16) (val1 | val2), addr);
536 break;
537 case 0x0003: // 16-bit AND.
538 if (Memory::IsValidAddress(addr)) {
539 InvalidateICache(addr & ~3, 4);
540 short val1 = (short) (arg & 0xFFFF);
541 short val2 = (short) Memory::Read_U16(addr);
542 Memory::Write_U16((u16) (val1 & val2), addr);
544 break;
545 case 0x0005: // 16-bit OR.
546 if (Memory::IsValidAddress(addr)) {
547 InvalidateICache(addr & ~3, 4);
548 short val1 = (short) (arg & 0xFFFF);
549 short val2 = (short) Memory::Read_U16(addr);
550 Memory::Write_U16((u16) (val1 ^ val2), addr);
552 break;
554 break;
555 case 0x8: // 8-bit and 16-bit patch code
556 code = GetNextCode();
557 if (code.size() >= 2) {
558 int data = code[0];
559 int dataAdd = code[1];
561 bool is8Bit = (data >> 16) == 0x0000;
562 int count = (arg >> 16) & 0xFFFF;
563 int stepAddr = (arg & 0xFFFF) * (is8Bit ? 1 : 2);
564 InvalidateICache(addr, count * stepAddr);
565 for (int a = 0; a < count; a++) {
566 if (Memory::IsValidAddress(addr)) {
567 if (is8Bit) {
568 Memory::Write_U8((u8) (data & 0xFF), addr);
570 else {
571 Memory::Write_U16((u16) (data & 0xFFFF), addr);
574 addr += stepAddr;
575 data += dataAdd;
578 break;
579 case 0xB: // Time command (not sure what to do?)
580 break;
581 case 0xC: // Code stopper
582 if (Memory::IsValidAddress(addr)) {
583 InvalidateICache(addr, 4);
584 value = Memory::Read_U32(addr);
585 if ((u32)value != arg) {
586 SkipAllCodes();
589 break;
590 case 0xD: // Test commands & Jocker codes
591 if ((arg >> 28) == 0x0 || (arg >> 28) == 0x2) { // 8Bit & 16Bit ignore next line cheat code
592 bool is8Bit = (arg >> 28) == 0x2;
593 addr = GetAddress(comm & 0x0FFFFFFF);
594 if (Memory::IsValidAddress(addr)) {
595 int memoryValue = is8Bit ? Memory::Read_U8(addr) : Memory::Read_U16(addr);
596 int testValue = arg & (is8Bit ? 0xFF : 0xFFFF);
597 bool executeNextLines = false;
598 switch ((arg >> 20) & 0xF) {
599 case 0x0: // Equal
600 executeNextLines = memoryValue == testValue;
601 break;
602 case 0x1: // Not Equal
603 executeNextLines = memoryValue != testValue;
604 break;
605 case 0x2: // Less Than
606 executeNextLines = memoryValue < testValue;
607 break;
608 case 0x3: // Greater Than
609 executeNextLines = memoryValue > testValue;
610 break;
611 default:
612 break;
614 if (!executeNextLines)
615 SkipCodes(1);
617 break;
619 else if ((arg >> 28) == 0x1 || (arg >> 28) == 0x3) { // Buttons dependent ignore cheat code
620 // Button Code
621 // SELECT 0x00000001
622 // START 0x00000008
623 // DPAD UP 0x00000010
624 // DPAD RIGHT 0x00000020
625 // DPAD DOWN 0x00000040
626 // DPAD LEFT 0x00000080
627 // L TRIGGER 0x00000100
628 // R TRIGGER 0x00000200
629 // TRIANGLE 0x00001000
630 // CIRCLE 0x00002000
631 // CROSS 0x00004000
632 // SQUARE 0x00008000
633 // HOME 0x00010000
634 // HOLD 0x00020000
635 // WLAN 0x00040000
636 // REMOTE HOLD 0x00080000
637 // VOLUME UP 0x00100000
638 // VOLUME DOWN 0x00200000
639 // SCREEN 0x00400000
640 // NOTE 0x00800000
641 u32 buttonStatus = __CtrlPeekButtons();
642 int skip = (comm & 0xFF) + 1;
643 u32 mask = arg & 0x0FFFFFFF;
644 if ((arg >> 28) == 0x1)
645 // Old, too specific check: if (buttonStatus == (arg & 0x0FFFFFFF)) // cheat code likes: 0xD00000nn 0x1bbbbbbb;
646 if ((buttonStatus & mask) == mask) // cheat code likes: 0xD00000nn 0x1bbbbbbb;
647 break;
648 else
649 SkipCodes(skip);
650 else // (arg >> 28) == 2?
651 // Old, too specific check: if (buttonStatus != (arg & 0x0FFFFFFF)) // cheat code likes: 0xD00000nn 0x3bbbbbbb;
652 if ((buttonStatus & mask) == mask) // cheat code likes: 0xD00000nn 0x3bbbbbbb;
653 SkipCodes(skip);
654 else
655 break;
656 break;
658 else if ((arg >> 28) == 0x4 || (arg >> 28) == 0x5 || (arg >> 28) == 0x6 || (arg >> 28) == 0x7) {
659 int addr1 = GetAddress(comm & 0x0FFFFFFF);
660 int addr2 = GetAddress(arg & 0x0FFFFFFF);
661 code = GetNextCode();
662 if (code.size() >= 2)
663 if (Memory::IsValidAddress(addr1) && Memory::IsValidAddress(addr2)) {
664 int comm2 = code[0];
665 int arg2 = code[1];
666 int skip = (comm2 & 0xFFFFFFFF);
667 int memoryValue1 = 0;
668 int memoryValue2 = 0;
669 switch (arg2 & 0xF) {
670 case 0x0: // 8Bit
671 memoryValue1 = Memory::Read_U8(addr1);
672 memoryValue2 = Memory::Read_U8(addr2);
673 break;
674 case 0x1: // 16Bit
675 memoryValue1 = Memory::Read_U16(addr1);
676 memoryValue2 = Memory::Read_U16(addr2);
677 break;
678 case 0x2: // 32Bit
679 memoryValue1 = Memory::Read_U32(addr1);
680 memoryValue2 = Memory::Read_U32(addr2);
681 break;
682 default:
683 break;
685 switch (arg >> 28) {
686 case 0x4: // Equal
687 if (memoryValue1 != memoryValue2)
688 SkipCodes(skip);
689 break;
690 case 0x5: // Not Equal
691 if (memoryValue1 == memoryValue2)
692 SkipCodes(skip);
693 break;
694 case 0x6: // Less Than
695 if (memoryValue1 >= memoryValue2)
696 SkipCodes(skip);
697 break;
698 case 0x7: // Greater Than
699 if (memoryValue1 <= memoryValue2)
700 SkipCodes(skip);
701 break;
702 default:
703 break;
707 else
708 break;
709 case 0xE: // Test commands, multiple skip
711 bool is8Bit = (comm >> 24) == 0xE1;
712 addr = GetAddress(arg & 0x0FFFFFFF);
713 if (Memory::IsValidAddress(addr)) {
714 int memoryValue = is8Bit ? Memory::Read_U8(addr) : Memory::Read_U16(addr);
715 int testValue = comm & (is8Bit ? 0xFF : 0xFFFF);
716 bool executeNextLines = false;
717 switch ( arg >> 28) {
718 case 0x0: // Equal
719 executeNextLines = memoryValue == testValue;
720 break;
721 case 0x1: // Not Equal
722 executeNextLines = memoryValue != testValue;
723 break;
724 case 0x2: // Less Than
725 executeNextLines = memoryValue < testValue;
726 break;
727 case 0x3: // Greater Than
728 executeNextLines = memoryValue > testValue;
729 break;
731 if (!executeNextLines) {
732 int skip = (comm >> 16) & (is8Bit ? 0xFF : 0xFFF);
733 SkipCodes(skip);
736 break;
738 default:
740 break;
745 // exiting...
746 Exit();
749 bool CWCheatEngine::HasCheats() {
750 return !parts.empty();
753 bool CheatsInEffect() {
754 if (!cheatEngine || !cheatsEnabled)
755 return false;
756 return cheatEngine->HasCheats();