// Copyright 2016 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include #include "common/common_paths.h" #include "common/file_util.h" #include "core/cheat_core.h" #include "core/hle/kernel/process.h" #include "core/memory.h" namespace CheatCore { constexpr u64 frame_ticks = 268123480ull / 60; static int tick_event; static std::unique_ptr cheat_engine; static void CheatTickCallback(u64, int cycles_late) { if (cheat_engine == nullptr) cheat_engine = std::make_unique(); cheat_engine->Run(); CoreTiming::ScheduleEvent(frame_ticks - cycles_late, tick_event); } void Init() { tick_event = CoreTiming::RegisterEvent("CheatCore::tick_event", CheatTickCallback); CoreTiming::ScheduleEvent(frame_ticks, tick_event); } void Shutdown() { CoreTiming::UnscheduleEvent(tick_event, 0); } void RefreshCheats() { cheat_engine->RefreshCheats(); } } // namespace CheatCore namespace CheatEngine { static std::string GetFilePath() { const auto program_id = Common::StringFromFormat("%016llX", Kernel::g_current_process->codeset->program_id); return FileUtil::GetUserPath(D_USER_IDX) + "cheats" + DIR_SEP + program_id + ".txt"; } CheatEngine::CheatEngine() { const auto file_path = GetFilePath(); if (!FileUtil::Exists(file_path)) FileUtil::CreateEmptyFile(file_path); cheats_list = ReadFileContents(); } std::vector> CheatEngine::ReadFileContents() { std::string file_path = GetFilePath(); std::string contents; FileUtil::ReadFileToString(true, file_path.c_str(), contents); std::vector lines; Common::SplitString(contents, '\n', lines); std::string code_type = "Gateway"; // If more cheat types added, need to specify which type in parsing. std::vector notes; std::vector cheat_lines; std::vector> cheats; std::string name; bool enabled = false; for (size_t i = 0; i < lines.size(); i++) { std::string current_line = std::string(lines[i].c_str()); current_line = Common::Trim(current_line); if (current_line.compare(0, 2, "+[") == 0) { // Enabled code if (!cheat_lines.empty()) { if (code_type == "Gateway") cheats.push_back( std::make_shared(cheat_lines, notes, enabled, name)); } name = current_line.substr(2, current_line.length() - 3); cheat_lines.clear(); notes.clear(); enabled = true; continue; } else if (current_line.front() == '[') { // Disabled code if (!cheat_lines.empty()) { if (code_type == "Gateway") cheats.push_back( std::make_shared(cheat_lines, notes, enabled, name)); } name = current_line.substr(1, current_line.length() - 2); cheat_lines.clear(); notes.clear(); enabled = false; continue; } else if (current_line.front() == '*') { // Comment notes.push_back(std::move(current_line)); } else if (!current_line.empty()) { cheat_lines.emplace_back(std::move(current_line)); } if (i == lines.size() - 1) { // End of file if (!cheat_lines.empty()) { if (code_type == "Gateway") cheats.push_back( std::make_shared(cheat_lines, notes, enabled, name)); } } } return cheats; } void CheatEngine::Save(std::vector> cheats) { const auto file_path = GetFilePath(); FileUtil::IOFile file = FileUtil::IOFile(file_path, "w+"); for (auto& cheat : cheats) { if (cheat->GetType() == "Gateway") { auto cheatString = cheat->ToString(); file.WriteBytes(cheatString.c_str(), cheatString.length()); } } } void CheatEngine::RefreshCheats() { const auto file_path = GetFilePath(); if (!FileUtil::Exists(file_path)) FileUtil::CreateEmptyFile(file_path); cheats_list = ReadFileContents(); } void CheatEngine::Run() { for (auto& cheat : cheats_list) { cheat->Execute(); } } void GatewayCheat::Execute() { if (enabled == false) return; u32 addr = 0; u32 reg = 0; u32 offset = 0; u32 val = 0; int if_flag = 0; int loop_count = 0; s32 loopbackline = 0; u32 counter = 0; bool loop_flag = false; for (size_t i = 0; i < cheat_lines.size(); i++) { auto line = cheat_lines[i]; if (line.type == -1) continue; addr = line.address; val = line.value; if (if_flag > 0) { if (line.type == 0x0E) i += (line.value + 7) / 8; if ((line.type == 0x0D) && (line.sub_type == 0)) if_flag--; // ENDIF if ((line.type == 0x0D) && (line.sub_type == 2)) // NEXT & Flush { if (loop_flag) i = loopbackline - 1; else { offset = 0; reg = 0; loop_count = 0; counter = 0; if_flag = 0; loop_flag = 0; } } continue; } switch (line.type) { case 0x00: { // 0XXXXXXX YYYYYYYY word[XXXXXXX+offset] = YYYYYYYY addr = line.address + offset; Memory::Write32(addr, val); break; } case 0x01: { // 1XXXXXXX 0000YYYY half[XXXXXXX+offset] = YYYY addr = line.address + offset; Memory::Write16(addr, static_cast(val)); break; } case 0x02: { // 2XXXXXXX 000000YY byte[XXXXXXX+offset] = YY addr = line.address + offset; Memory::Write8(addr, static_cast(val)); break; } case 0x03: { // 3XXXXXXX YYYYYYYY IF YYYYYYYY > word[XXXXXXX] ;unsigned if (line.address == 0) line.address = offset; val = Memory::Read32(line.address); if (line.value > val) { if (if_flag > 0) if_flag--; } else { if_flag++; } break; } case 0x04: { // 4XXXXXXX YYYYYYYY IF YYYYYYYY < word[XXXXXXX] ;unsigned if (line.address == 0) line.address = offset; val = Memory::Read32(line.address); if (line.value < val) { if (if_flag > 0) if_flag--; } else { if_flag++; } break; } case 0x05: { // 5XXXXXXX YYYYYYYY IF YYYYYYYY = word[XXXXXXX] if (line.address == 0) line.address = offset; val = Memory::Read32(line.address); if (line.value == val) { if (if_flag > 0) if_flag--; } else { if_flag++; } break; } case 0x06: { // 6XXXXXXX YYYYYYYY IF YYYYYYYY <> word[XXXXXXX] if (line.address == 0) line.address = offset; val = Memory::Read32(line.address); if (line.value != val) { if (if_flag > 0) if_flag--; } else { if_flag++; } break; } case 0x07: { // 7XXXXXXX ZZZZYYYY IF YYYY > ((not ZZZZ) AND half[XXXXXXX]) if (line.address == 0) line.address = offset; val = Memory::Read16(line.address); if (line.value > val) { if (if_flag > 0) if_flag--; } else { if_flag++; } break; } case 0x08: { // 8XXXXXXX ZZZZYYYY IF YYYY < ((not ZZZZ) AND half[XXXXXXX]) if (line.address == 0) line.address = offset; val = Memory::Read16(line.address); if (static_cast(line.value) < val) { if (if_flag > 0) if_flag--; } else { if_flag++; } break; } case 0x09: { // 9XXXXXXX ZZZZYYYY IF YYYY = ((not ZZZZ) AND half[XXXXXXX]) if (line.address == 0) line.address = offset; val = Memory::Read16(line.address); if (static_cast(line.value) == (!static_cast(line.value >> 16) & val)) { if (if_flag > 0) if_flag--; } else { if_flag++; } break; } case 0x0A: { // AXXXXXXX ZZZZYYYY IF YYYY <> ((not ZZZZ) AND half[XXXXXXX]) if (line.address == 0) line.address = offset; val = Memory::Read16(line.address); if (static_cast(line.value) != val) { if (if_flag > 0) if_flag--; } else { if_flag++; } break; } case 0x0B: { // BXXXXXXX 00000000 offset = word[XXXXXXX+offset] addr = line.address + offset; offset = Memory::Read32(addr); break; } case 0x0C: { if (loop_count < (line.value + 1)) loop_flag = 1; else loop_flag = 0; loop_count++; loopbackline = i; break; } case 0x0D: { switch (line.sub_type) { case 0x00: break; case 0x01: { if (loop_flag) i = loopbackline - 1; break; } case 0x02: { if (loop_flag) i = loopbackline - 1; else { offset = 0; reg = 0; loop_count = 0; counter = 0; if_flag = 0; loop_flag = 0; } break; } case 0x03: { offset = line.value; break; } case 0x04: { reg += line.value; break; } case 0x05: { reg = line.value; break; } case 0x06: { addr = line.value + offset; Memory::Write32(addr, reg); offset += 4; break; } case 0x07: { addr = line.value + offset; Memory::Write16(addr, static_cast(reg)); offset += 2; break; } case 0x08: { addr = line.value + offset; Memory::Write8(addr, static_cast(reg)); offset += 1; break; } case 0x09: { addr = line.value + offset; reg = Memory::Read32(addr); break; } case 0x0A: { addr = line.value + offset; reg = Memory::Read16(addr); break; } case 0x0B: { addr = line.value + offset; reg = Memory::Read8(addr); break; } case 0x0C: { offset += line.value; break; } case 0x0D: { // TODO: Implement Joker codes break; } } } case 0x0E: { // EXXXXXXX YYYYYYYY Copy YYYYYYYY parameter bytes to [XXXXXXXX+offset...] // TODO: Implement whatever this is... break; } } } } std::string GatewayCheat::ToString() { std::string result; if (cheat_lines.empty()) return result; if (enabled) result += '+'; result += '[' + name + "]\n"; for (auto& str : notes) { if (str.front() == '*') str.insert(0, 1, '*'); } result += Common::Join(notes, "\n"); if (!notes.empty()) result += '\n'; for (const auto& line : cheat_lines) result += line.cheat_line + '\n'; result += '\n'; return result; } }