CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Core/CwCheat.cpp
Views: 1401
#include <algorithm>1#include <cctype>2#include <cstdint>3#include <cstdio>45#include "Common/Data/Text/I18n.h"6#include "Common/StringUtils.h"7#include "Common/Serialize/Serializer.h"8#include "Common/Serialize/SerializeFuncs.h"9#include "Common/System/OSD.h"10#include "Common/File/FileUtil.h"11#include "Core/CoreTiming.h"12#include "Core/CoreParameter.h"13#include "Core/CwCheat.h"14#include "Core/Config.h"15#include "Core/MemMapHelpers.h"16#include "Core/MIPS/MIPS.h"17#include "Core/ELF/ParamSFO.h"18#include "Core/System.h"19#include "Core/HLE/sceCtrl.h"20#include "Core/MIPS/JitCommon/JitCommon.h"21#include "Core/RetroAchievements.h"22#include "GPU/Common/PostShader.h"2324#ifdef _WIN3225#include "Common/Data/Encoding/Utf8.h"26#endif2728static int CheatEvent = -1;29static CWCheatEngine *cheatEngine;30static bool cheatsEnabled;31using namespace SceCtrl;3233void hleCheat(u64 userdata, int cyclesLate);3435static inline std::string TrimString(const std::string &s) {36auto wsfront = std::find_if_not(s.begin(), s.end(), [](int c) {37// isspace() expects 0 - 255, so convert any sign-extended value.38return std::isspace(c & 0xFF);39});40auto wsback = std::find_if_not(s.rbegin(), s.rend(), [](int c){41return std::isspace(c & 0xFF);42}).base();43return wsback > wsfront ? std::string(wsfront, wsback) : std::string();44}4546class CheatFileParser {47public:48CheatFileParser(const Path &filename, const std::string &gameID = "") {49fp_ = File::OpenCFile(filename, "rt");50validGameID_ = ReplaceAll(gameID, "-", "");51}52~CheatFileParser() {53if (fp_)54fclose(fp_);55}5657bool Parse();5859std::vector<std::string> GetErrors() const {60return errors_;61}6263std::vector<CheatCode> GetCheats() const {64return cheats_;65}6667std::vector<CheatFileInfo> GetFileInfo() const {68return cheatInfo_;69}7071protected:72void Flush();73void FlushCheatInfo();74void AddError(const std::string &msg);75void ParseLine(const std::string &line);76void ParseDataLine(const std::string &line, CheatCodeFormat format);77bool ValidateGameID(const std::string &gameID);7879FILE *fp_ = nullptr;80std::string validGameID_;8182int line_ = 0;83int games_ = 0;84std::vector<std::string> errors_;85std::vector<CheatFileInfo> cheatInfo_;86std::vector<CheatCode> cheats_;87std::vector<CheatLine> pendingLines_;88CheatCodeFormat codeFormat_ = CheatCodeFormat::UNDEFINED;89CheatFileInfo lastCheatInfo_;90bool gameEnabled_ = true;91bool gameRiskyEnabled_ = false;92bool cheatEnabled_ = false;93};9495bool CheatFileParser::Parse() {96for (line_ = 1; fp_ && !feof(fp_); ++line_) {97char temp[2048];98char *tempLine = fgets(temp, sizeof(temp), fp_);99if (!tempLine)100continue;101102// Detect UTF-8 BOM sequence, and ignore it.103if (line_ == 1 && memcmp(tempLine, "\xEF\xBB\xBF", 3) == 0)104tempLine += 3;105std::string line = TrimString(tempLine);106107// Minimum length 5 is shortest possible _ lines name of the game "_G N+"108// and a minimum of 1 displayable character in cheat name string "_C0 1"109// which both equal to 5 characters.110if (line.length() >= 5 && line[0] == '_') {111ParseLine(line);112} else if (line.length() >= 2 && line[0] == '/' && line[1] == '/') {113// Comment, ignore.114} else if (line.length() >= 1 && line[0] == '#') {115// Comment, ignore.116} else if (line.length() > 0) {117errors_.push_back(StringFromFormat("Unrecognized content on line %d: expecting _", line_));118}119}120121Flush();122123return errors_.empty();124}125126void CheatFileParser::Flush() {127if (!pendingLines_.empty()) {128FlushCheatInfo();129cheats_.push_back({ codeFormat_, pendingLines_ });130pendingLines_.clear();131}132codeFormat_ = CheatCodeFormat::UNDEFINED;133}134135void CheatFileParser::FlushCheatInfo() {136if (lastCheatInfo_.lineNum != 0) {137cheatInfo_.push_back(lastCheatInfo_);138lastCheatInfo_ = { 0 };139}140}141142void CheatFileParser::AddError(const std::string &err) {143errors_.push_back(StringFromFormat("Error on line %d: %s", line_, err.c_str()));144}145146void CheatFileParser::ParseLine(const std::string &line) {147switch (line[1]) {148case 'S':149// Disc ID, validate (for multi-disc cheat files)?150Flush();151++games_;152153if (ValidateGameID(line.substr(2))) {154if (gameRiskyEnabled_) {155// We found the right one, so let's not use this risky stuff.156cheats_.clear();157cheatInfo_.clear();158gameRiskyEnabled_ = false;159}160gameEnabled_ = true;161} else if (games_ == 1) {162// Old behavior was to ignore.163// For BC, let's allow if the game id doesn't match, but there's only one line.164gameRiskyEnabled_ = true;165gameEnabled_ = true;166} else {167if (gameRiskyEnabled_) {168// There are multiple games here, kill the risky stuff.169cheats_.clear();170cheatInfo_.clear();171gameRiskyEnabled_ = false;172}173gameEnabled_ = false;174}175return;176177case 'G':178// Game title.179return;180181case 'C':182Flush();183184// Cheat name and activation status.185if (line.length() >= 3 && line[2] >= '1' && line[2] <= '9') {186lastCheatInfo_ = { line_, line.length() >= 5 ? line.substr(4) : "", true };187cheatEnabled_ = true;188} else if (line.length() >= 3 && line[2] == '0') {189lastCheatInfo_ = { line_, line.length() >= 5 ? line.substr(4) : "", false };190cheatEnabled_ = false;191} else {192AddError("could not parse cheat name line");193cheatEnabled_ = false;194return;195}196return;197198case 'L':199// CwCheat data line.200ParseDataLine(line.substr(2), CheatCodeFormat::CWCHEAT);201return;202203case 'M':204// TempAR data line.205ParseDataLine(line.substr(2), CheatCodeFormat::TEMPAR);206return;207208default:209AddError("unknown line type");210return;211}212}213214void CheatFileParser::ParseDataLine(const std::string &line, CheatCodeFormat format) {215if (codeFormat_ == CheatCodeFormat::UNDEFINED) {216codeFormat_ = format;217} else if (codeFormat_ != format) {218AddError("mixed code format (cwcheat/tempar)");219lastCheatInfo_ = { 0 };220pendingLines_.clear();221cheatEnabled_ = false;222}223224if (!gameEnabled_) {225return;226}227if (!cheatEnabled_) {228FlushCheatInfo();229return;230}231232CheatLine cheatLine;233int len = 0;234if (sscanf(line.c_str(), "%x %x %n", &cheatLine.part1, &cheatLine.part2, &len) == 2) {235if ((size_t)len < line.length()) {236AddError("junk after line data");237}238pendingLines_.push_back(cheatLine);239} else {240AddError("expecting two values");241}242}243244bool CheatFileParser::ValidateGameID(const std::string &gameID) {245return validGameID_.empty() || ReplaceAll(TrimString(gameID), "-", "") == validGameID_;246}247248static void __CheatStop() {249if (cheatEngine) {250delete cheatEngine;251cheatEngine = nullptr;252}253cheatsEnabled = false;254}255256static void __CheatStart() {257__CheatStop();258259cheatEngine = new CWCheatEngine(g_paramSFO.GetDiscID());260// This only generates ini files on boot, let's leave homebrew ini file for UI.261std::string realGameID = g_paramSFO.GetValueString("DISC_ID");262if (!realGameID.empty()) {263cheatEngine->CreateCheatFile();264}265266cheatEngine->ParseCheats();267g_Config.bReloadCheats = false;268cheatsEnabled = true;269}270271static int GetRefreshMs() {272int refresh = g_Config.iCwCheatRefreshIntervalMs;273274if (!cheatsEnabled)275refresh = 1000;276277// Horrible hack for Tony Hawk - Underground 2. See #3854. Avoids crashing somehow278// but still causes regular JIT invalidations which causes stutters.279if (PSP_CoreParameter().compat.flags().JitInvalidationHack) {280refresh = 2;281}282283return refresh;284}285286void __CheatInit() {287// Always register the event, want savestates to be compatible whether cheats on or off.288CheatEvent = CoreTiming::RegisterEvent("CheatEvent", &hleCheat);289290if (g_Config.bEnableCheats) {291__CheatStart();292}293294// Only check once a second for cheats to be enabled.295CoreTiming::ScheduleEvent(msToCycles(GetRefreshMs()), CheatEvent, 0);296}297298void __CheatShutdown() {299__CheatStop();300}301302void __CheatDoState(PointerWrap &p) {303auto s = p.Section("CwCheat", 0, 2);304if (!s) {305CheatEvent = -1;306CoreTiming::RestoreRegisterEvent(CheatEvent, "CheatEvent", &hleCheat);307return;308}309310Do(p, CheatEvent);311CoreTiming::RestoreRegisterEvent(CheatEvent, "CheatEvent", &hleCheat);312313if (s < 2) {314// Before this we didn't have a checkpoint, so reset didn't work.315// Let's just force one in.316CoreTiming::RemoveEvent(CheatEvent);317CoreTiming::ScheduleEvent(msToCycles(GetRefreshMs()), CheatEvent, 0);318}319}320321void hleCheat(u64 userdata, int cyclesLate) {322bool shouldBeEnabled = !Achievements::HardcoreModeActive() && g_Config.bEnableCheats;323324if (cheatsEnabled != shouldBeEnabled) {325// Okay, let's move to the desired state, then.326if (shouldBeEnabled) {327__CheatStart();328} else {329__CheatStop();330}331}332333// Check periodically for cheats.334CoreTiming::ScheduleEvent(msToCycles(GetRefreshMs()), CheatEvent, 0);335336if (PSP_CoreParameter().compat.flags().JitInvalidationHack) {337std::string gameTitle = g_paramSFO.GetValueString("DISC_ID");338339// Horrible hack for Tony Hawk - Underground 2. See #3854. Avoids crashing somehow340// but still causes regular JIT invalidations which causes stutters.341if (gameTitle == "ULUS10014") {342cheatEngine->InvalidateICache(0x08865600, 72);343cheatEngine->InvalidateICache(0x08865690, 4);344} else if (gameTitle == "ULES00033" || gameTitle == "ULES00034" || gameTitle == "ULES00035") { // euro, also 34 and 35345cheatEngine->InvalidateICache(0x088655D8, 72);346cheatEngine->InvalidateICache(0x08865668, 4);347} else if (gameTitle == "ULUS10138") { // MTX MotoTrax US348cheatEngine->InvalidateICache(0x0886DCC0, 72);349cheatEngine->InvalidateICache(0x0886DC20, 4);350cheatEngine->InvalidateICache(0x0886DD40, 4);351} else if (gameTitle == "ULES00581") { // MTX MotoTrax EU (ported from US cwcheat codes)352cheatEngine->InvalidateICache(0x0886E1D8, 72);353cheatEngine->InvalidateICache(0x0886E138, 4);354cheatEngine->InvalidateICache(0x0886E258, 4);355}356}357358if (!cheatEngine || !cheatsEnabled)359return;360361if (g_Config.bReloadCheats) { //Checks if the "reload cheats" button has been pressed.362cheatEngine->ParseCheats();363g_Config.bReloadCheats = false;364}365cheatEngine->Run();366}367368CWCheatEngine::CWCheatEngine(const std::string &gameID) : gameID_(gameID) {369filename_ = GetSysDirectory(DIRECTORY_CHEATS) / (gameID_ + ".ini");370}371372void CWCheatEngine::CreateCheatFile() {373File::CreateFullPath(GetSysDirectory(DIRECTORY_CHEATS));374375if (!File::Exists(filename_)) {376FILE *f = File::OpenCFile(filename_, "wb");377if (f) {378fwrite("\xEF\xBB\xBF\n", 1, 4, f);379fclose(f);380}381if (!File::Exists(filename_)) {382auto err = GetI18NCategory(I18NCat::ERRORS);383g_OSD.Show(OSDType::MESSAGE_ERROR, err->T("Unable to create cheat file, disk may be full"));384}385}386}387388Path CWCheatEngine::CheatFilename() {389return filename_;390}391392void CWCheatEngine::ParseCheats() {393CheatFileParser parser(filename_, gameID_);394395parser.Parse();396// TODO: Report errors.397398cheats_ = parser.GetCheats();399}400401u32 CWCheatEngine::GetAddress(u32 value) {402// Returns static address used by ppsspp. Some games may not like this, and causes cheats to not work without offset403u32 address = (value + 0x08800000) & 0x3FFFFFFF;404return address;405}406407std::vector<CheatFileInfo> CWCheatEngine::FileInfo() {408CheatFileParser parser(filename_, gameID_);409410parser.Parse();411return parser.GetFileInfo();412}413414void CWCheatEngine::InvalidateICache(u32 addr, int size) {415// Round start down and size up to the nearest word.416u32 aligned = addr & ~3;417int alignedSize = (addr + size - aligned + 3) & ~3;418currentMIPS->InvalidateICache(aligned, alignedSize);419}420421enum class CheatOp {422Invalid,423Noop,424425Write,426Add,427Subtract,428Or,429And,430Xor,431432MultiWrite,433434CopyBytesFrom,435Vibration,436VibrationFromMemory,437PostShader,438PostShaderFromMemory,439Delay,440441Assert,442443IfEqual,444IfNotEqual,445IfLess,446IfGreater,447448IfAddrEqual,449IfAddrNotEqual,450IfAddrLess,451IfAddrGreater,452453IfPressed,454IfNotPressed,455456CwCheatPointerCommands,457};458459struct CheatOperation {460CheatOp op;461uint32_t addr;462int sz;463uint32_t val;464465union {466struct {467uint32_t count;468uint32_t step;469uint32_t add;470} multiWrite;471struct {472uint32_t destAddr;473} copyBytesFrom;474struct {475uint32_t skip;476} ifTypes;477struct {478uint32_t skip;479uint32_t compareAddr;480} ifAddrTypes;481struct {482int offset;483int baseOffset;484int count;485int type;486} pointerCommands;487struct {488uint16_t vibrL;489uint16_t vibrR;490uint8_t vibrLTime;491uint8_t vibrRTime;492} vibrationValues;493struct {494union {495float f;496uint32_t u;497} value;498uint8_t shader;499uint8_t uniform;500uint8_t format;501} PostShaderUniform;502};503};504505CheatOperation CWCheatEngine::InterpretNextCwCheat(const CheatCode &cheat, size_t &i) {506const CheatLine &line1 = cheat.lines[i++];507const uint32_t &arg = line1.part2;508509// Filled as needed.510u32 addr;511512int type = line1.part1 >> 28;513switch (type) {514case 0x0: // Write 8-bit data (up to 4 bytes.)515addr = GetAddress(line1.part1 & 0x0FFFFFFF);516if (arg & 0xFFFF0000)517return { CheatOp::Write, addr, 4, arg };518else if (arg & 0x0000FF00)519return { CheatOp::Write, addr, 2, arg };520else521return { CheatOp::Write, addr, 1, arg };522523case 0x1: // Write 16-bit data.524addr = GetAddress(line1.part1 & 0x0FFFFFFF);525return { CheatOp::Write, addr, 2, arg };526527case 0x2: // Write 32-bit data.528addr = GetAddress(line1.part1 & 0x0FFFFFFF);529return { CheatOp::Write, addr, 4, arg };530531case 0x3: // Increment/decrement data.532addr = GetAddress(arg & 0x0FFFFFFF);533switch ((line1.part1 >> 20) & 0xF) {534case 1:535return { CheatOp::Add, addr, 1, line1.part1 & 0xFF };536case 2:537return { CheatOp::Subtract, addr, 1, line1.part1 & 0xFF };538case 3:539return { CheatOp::Add, addr, 2, line1.part1 & 0xFFFF };540case 4:541return { CheatOp::Subtract, addr, 2, line1.part1 & 0xFFFF };542case 5:543if (i < cheat.lines.size())544return { CheatOp::Add, addr, 4, cheat.lines[i++].part1 };545return { CheatOp::Invalid };546case 6:547if (i < cheat.lines.size())548return { CheatOp::Subtract, addr, 4, cheat.lines[i++].part1 };549return { CheatOp::Invalid };550551default:552return { CheatOp::Invalid };553}554break;555556case 0x4: // 32-bit multi-write patch data.557addr = GetAddress(line1.part1 & 0x0FFFFFFF);558if (i < cheat.lines.size()) {559const CheatLine &line2 = cheat.lines[i++];560561CheatOperation op = { CheatOp::MultiWrite, addr, 4, line2.part1 };562op.multiWrite.count = arg >> 16;563op.multiWrite.step = (arg & 0xFFFF) * 4;564op.multiWrite.add = line2.part2;565return op;566}567return { CheatOp::Invalid };568569case 0x5: // Memcpy command.570addr = GetAddress(line1.part1 & 0x0FFFFFFF);571if (i < cheat.lines.size()) {572const CheatLine &line2 = cheat.lines[i++];573574CheatOperation op = { CheatOp::CopyBytesFrom, addr, 0, arg };575op.copyBytesFrom.destAddr = GetAddress(line2.part1 & 0x0FFFFFFF);576return op;577}578return { CheatOp::Invalid };579580case 0x6: // Pointer commands.581addr = GetAddress(line1.part1 & 0x0FFFFFFF);582if (i < cheat.lines.size()) {583const CheatLine &line2 = cheat.lines[i++];584int count = (line2.part1 & 0xFFFF) - 1;585586// Validate lines to process - make sure we stay inside cheat.lines.587if (i + count > cheat.lines.size())588return { CheatOp::Invalid };589590CheatOperation op = { CheatOp::CwCheatPointerCommands, addr, 0, arg };591op.pointerCommands.offset = (int)line2.part2;592// TODO: Verify sign handling. Is this really supposed to sign extend?593op.pointerCommands.baseOffset = ((int)line2.part1 >> 20) * 4;594op.pointerCommands.count = count;595op.pointerCommands.type = (line2.part1 >> 16) & 0xF;596return op;597}598return { CheatOp::Invalid };599600case 0x7: // Boolean data operations.601addr = GetAddress(line1.part1 & 0x0FFFFFFF);602switch (arg >> 16) {603case 0x0000: // 8-bit OR.604return { CheatOp::Or, addr, 1, arg & 0xFF };605case 0x0001: // 16-bit OR.606return { CheatOp::Or, addr, 2, arg & 0xFFFF };607case 0x0002: // 8-bit AND.608return { CheatOp::And, addr, 1, arg & 0xFF };609case 0x0003: // 16-bit AND.610return { CheatOp::And, addr, 2, arg & 0xFFFF };611case 0x0004: // 8-bit XOR.612return { CheatOp::Xor, addr, 1, arg & 0xFF };613case 0x0005: // 16-bit XOR.614return { CheatOp::Xor, addr, 2, arg & 0xFFFF };615}616return { CheatOp::Invalid };617618case 0x8: // 8-bit or 16-bit multi-write patch data.619addr = GetAddress(line1.part1 & 0x0FFFFFFF);620if (i < cheat.lines.size()) {621const CheatLine &line2 = cheat.lines[i++];622const bool is8Bit = (line2.part1 & 0xFFFF0000) == 0;623const uint32_t val = is8Bit ? (line2.part1 & 0xFF) : (line2.part1 & 0xFFFF);624625CheatOperation op = { CheatOp::MultiWrite, addr, is8Bit ? 1 : 2, val };626op.multiWrite.count = arg >> 16;627op.multiWrite.step = (arg & 0xFFFF) * (is8Bit ? 1 : 2);628op.multiWrite.add = line2.part2;629return op;630}631return { CheatOp::Invalid };632633case 0xA: // PPSSPP specific cheats634switch (line1.part1 >> 24 & 0xF) {635case 0x0: // 0x0 sets gamepad vibration by cheat parameters636{637CheatOperation op = { CheatOp::Vibration };638op.vibrationValues.vibrL = line1.part1 & 0x0000FFFF;639op.vibrationValues.vibrR = line1.part2 & 0x0000FFFF;640op.vibrationValues.vibrLTime = (line1.part1 >> 16) & 0x000000FF;641op.vibrationValues.vibrRTime = (line1.part2 >> 16) & 0x000000FF;642return op;643}644case 0x1: // 0x1 reads value for gamepad vibration from memory645addr = line1.part2;646return { CheatOp::VibrationFromMemory, addr };647case 0x2: // 0x2 sets postprocessing shader uniform648{649CheatOperation op = { CheatOp::PostShader };650op.PostShaderUniform.uniform = line1.part1 & 0x000000FF;651op.PostShaderUniform.shader = (line1.part1 >> 16) & 0x000000FF;652op.PostShaderUniform.value.u = line1.part2;653return op;654}655case 0x3: // 0x3 sets postprocessing shader uniform from memory656{657addr = line1.part2;658CheatOperation op = { CheatOp::PostShaderFromMemory, addr };659op.PostShaderUniform.uniform = line1.part1 & 0x000000FF;660op.PostShaderUniform.format = (line1.part1 >> 8) & 0x000000FF;661op.PostShaderUniform.shader = (line1.part1 >> 16) & 0x000000FF;662return op;663}664// Place for other PPSSPP specific cheats665default:666return { CheatOp::Invalid };667}668669case 0xB: // Delay command.670return { CheatOp::Delay, 0, 0, arg };671672case 0xC: // 32-bit equal check / code stopper.673addr = GetAddress(line1.part1 & 0x0FFFFFFF);674return { CheatOp::Assert, addr, 4, arg };675676case 0xD: // Line skip tests & joker codes.677switch (arg >> 28) {678case 0x0: // 16-bit next line skip test.679case 0x2: // 8-bit next line skip test.680addr = GetAddress(line1.part1 & 0x0FFFFFFF);681{682const bool is8Bit = (arg >> 28) == 0x2;683const uint32_t val = is8Bit ? (arg & 0xFF) : (arg & 0xFFFF);684685CheatOp opcode;686switch ((arg >> 20) & 0xF) {687case 0x0:688opcode = CheatOp::IfEqual;689break;690case 0x1:691opcode = CheatOp::IfNotEqual;692break;693case 0x2:694opcode = CheatOp::IfLess;695break;696case 0x3:697opcode = CheatOp::IfGreater;698break;699default:700return { CheatOp::Invalid };701}702703CheatOperation op = { opcode, addr, is8Bit ? 1 : 2, val };704op.ifTypes.skip = 1;705return op;706}707708case 0x1: // Joker code - button pressed.709case 0x3: // Inverse joker code - button not pressed.710{711bool pressed = (arg >> 28) == 0x1;712CheatOperation op = { pressed ? CheatOp::IfPressed : CheatOp::IfNotPressed, 0, 0, arg & 0x0FFFFFFF };713op.ifTypes.skip = (line1.part1 & 0xFF) + 1;714return op;715}716717case 0x4: // Adress equal test.718case 0x5: // Address not equal test.719case 0x6: // Address less than test.720case 0x7: // Address greater than test.721addr = GetAddress(line1.part1 & 0x0FFFFFFF);722if (i < cheat.lines.size()) {723const CheatLine &line2 = cheat.lines[i++];724const int sz = 1 << (line2.part2 & 0xF);725726CheatOp opcode;727switch (arg >> 28) {728case 0x4:729opcode = CheatOp::IfAddrEqual;730break;731case 0x5:732opcode = CheatOp::IfAddrNotEqual;733break;734case 0x6:735opcode = CheatOp::IfAddrLess;736break;737case 0x7:738opcode = CheatOp::IfAddrGreater;739break;740default:741return { CheatOp::Invalid };742}743744CheatOperation op = { opcode, addr, sz, 0 };745op.ifAddrTypes.skip = line2.part1;746op.ifAddrTypes.compareAddr = GetAddress(arg & 0x0FFFFFFF);747return op;748}749return { CheatOp::Invalid };750751default:752return { CheatOp::Invalid };753}754755case 0xE: // Multiple line skip tests.756addr = GetAddress(arg & 0x0FFFFFFF);757{758const bool is8Bit = (line1.part1 >> 24) == 0xE1;759const uint32_t val = is8Bit ? (line1.part1 & 0xFF) : (line1.part1 & 0xFFFF);760761CheatOp opcode;762switch (arg >> 28) {763case 0x0:764opcode = CheatOp::IfEqual;765break;766case 0x1:767opcode = CheatOp::IfNotEqual;768break;769case 0x2:770opcode = CheatOp::IfLess;771break;772case 0x3:773opcode = CheatOp::IfGreater;774break;775default:776return { CheatOp::Invalid };777}778779CheatOperation op = { opcode, addr, is8Bit ? 1 : 2, val };780op.ifTypes.skip = (line1.part1 >> 16) & (is8Bit ? 0xFF : 0xFFF);781return op;782}783784default:785return { CheatOp::Invalid };786}787}788789CheatOperation CWCheatEngine::InterpretNextTempAR(const CheatCode &cheat, size_t &i) {790// TODO791return { CheatOp::Invalid };792}793794CheatOperation CWCheatEngine::InterpretNextOp(const CheatCode &cheat, size_t &i) {795if (cheat.fmt == CheatCodeFormat::CWCHEAT)796return InterpretNextCwCheat(cheat, i);797else if (cheat.fmt == CheatCodeFormat::TEMPAR)798return InterpretNextTempAR(cheat, i);799else {800// This shouldn't happen, but apparently does: #14082801// Either I'm missing a path or we have memory corruption.802// Not sure whether to log here though, feels like we could end up with a803// ton of logspam...804return { CheatOp::Invalid };805}806}807808void CWCheatEngine::ApplyMemoryOperator(const CheatOperation &op, uint32_t(*oper)(uint32_t, uint32_t)) {809if (Memory::IsValidRange(op.addr, op.sz)) {810InvalidateICache(op.addr, op.sz);811if (op.sz == 1)812Memory::Write_U8((u8)oper(Memory::Read_U8(op.addr), op.val), op.addr);813else if (op.sz == 2)814Memory::Write_U16((u16)oper(Memory::Read_U16(op.addr), op.val),op. addr);815else if (op.sz == 4)816Memory::Write_U32((u32)oper(Memory::Read_U32(op.addr), op.val), op.addr);817}818}819820bool CWCheatEngine::TestIf(const CheatOperation &op, bool(*oper)(int, int)) {821if (Memory::IsValidRange(op.addr, op.sz)) {822InvalidateICache(op.addr, op.sz);823824int memoryValue = 0;825if (op.sz == 1)826memoryValue = (int)Memory::Read_U8(op.addr);827else if (op.sz == 2)828memoryValue = (int)Memory::Read_U16(op.addr);829else if (op.sz == 4)830memoryValue = (int)Memory::Read_U32(op.addr);831832return oper(memoryValue, (int)op.val);833}834return false;835}836837bool CWCheatEngine::TestIfAddr(const CheatOperation &op, bool(*oper)(int, int)) {838if (Memory::IsValidRange(op.addr, op.sz) && Memory::IsValidRange(op.ifAddrTypes.compareAddr, op.sz)) {839InvalidateICache(op.addr, op.sz);840InvalidateICache(op.addr, op.ifAddrTypes.compareAddr);841842int memoryValue1 = 0;843int memoryValue2 = 0;844if (op.sz == 1) {845memoryValue1 = (int)Memory::Read_U8(op.addr);846memoryValue2 = (int)Memory::Read_U8(op.ifAddrTypes.compareAddr);847} else if (op.sz == 2) {848memoryValue1 = (int)Memory::Read_U16(op.addr);849memoryValue2 = (int)Memory::Read_U16(op.ifAddrTypes.compareAddr);850} else if (op.sz == 4) {851memoryValue1 = (int)Memory::Read_U32(op.addr);852memoryValue2 = (int)Memory::Read_U32(op.ifAddrTypes.compareAddr);853}854855return oper(memoryValue1, memoryValue2);856}857return false;858}859860void CWCheatEngine::ExecuteOp(const CheatOperation &op, const CheatCode &cheat, size_t &i) {861switch (op.op) {862case CheatOp::Invalid:863i = cheat.lines.size();864break;865866case CheatOp::Noop:867break;868869case CheatOp::Write:870if (Memory::IsValidRange(op.addr, op.sz)) {871InvalidateICache(op.addr, op.sz);872if (op.sz == 1)873Memory::Write_U8((u8)op.val, op.addr);874else if (op.sz == 2)875Memory::Write_U16((u16)op.val, op.addr);876else if (op.sz == 4)877Memory::Write_U32((u32)op.val, op.addr);878}879break;880881case CheatOp::Add:882ApplyMemoryOperator(op, [](uint32_t a, uint32_t b) {883return a + b;884});885break;886887case CheatOp::Subtract:888ApplyMemoryOperator(op, [](uint32_t a, uint32_t b) {889return a - b;890});891break;892893case CheatOp::Or:894ApplyMemoryOperator(op, [](uint32_t a, uint32_t b) {895return a | b;896});897break;898899case CheatOp::And:900ApplyMemoryOperator(op, [](uint32_t a, uint32_t b) {901return a & b;902});903break;904905case CheatOp::Xor:906ApplyMemoryOperator(op, [](uint32_t a, uint32_t b) {907return a ^ b;908});909break;910911case CheatOp::MultiWrite:912if (Memory::IsValidAddress(op.addr)) {913InvalidateICache(op.addr, op.multiWrite.count * op.multiWrite.step + op.sz);914915uint32_t data = op.val;916uint32_t addr = op.addr;917for (uint32_t a = 0; a < op.multiWrite.count; a++) {918if (Memory::IsValidAddress(addr)) {919if (op.sz == 1)920Memory::Write_U8((u8)data, addr);921else if (op.sz == 2)922Memory::Write_U16((u16)data, addr);923else if (op.sz == 4)924Memory::Write_U32((u32)data, addr);925}926addr += op.multiWrite.step;927data += op.multiWrite.add;928}929}930break;931932case CheatOp::CopyBytesFrom:933if (Memory::IsValidRange(op.addr, op.val) && Memory::IsValidRange(op.copyBytesFrom.destAddr, op.val)) {934InvalidateICache(op.addr, op.val);935InvalidateICache(op.copyBytesFrom.destAddr, op.val);936937Memory::Memcpy(op.copyBytesFrom.destAddr, op.addr, op.val, "CwCheat");938}939break;940941case CheatOp::Vibration:942if (op.vibrationValues.vibrL > 0) {943SetLeftVibration(op.vibrationValues.vibrL);944SetVibrationLeftDropout(op.vibrationValues.vibrLTime);945}946if (op.vibrationValues.vibrR > 0) {947SetRightVibration(op.vibrationValues.vibrR);948SetVibrationRightDropout(op.vibrationValues.vibrRTime);949}950break;951952case CheatOp::VibrationFromMemory:953if (Memory::IsValidRange(op.addr, 8)) {954uint16_t checkLeftVibration = Memory::Read_U16(op.addr);955uint16_t checkRightVibration = Memory::Read_U16(op.addr + 0x2);956if (checkLeftVibration > 0) {957SetLeftVibration(checkLeftVibration);958SetVibrationLeftDropout(Memory::Read_U8(op.addr + 0x4));959}960if (checkRightVibration > 0) {961SetRightVibration(checkRightVibration);962SetVibrationRightDropout(Memory::Read_U8(op.addr + 0x6));963}964}965break;966967case CheatOp::PostShader:968{969auto shaderChain = GetFullPostShadersChain(g_Config.vPostShaderNames);970if (op.PostShaderUniform.shader < shaderChain.size()) {971std::string shaderName = shaderChain[op.PostShaderUniform.shader]->section;972g_Config.mPostShaderSetting[StringFromFormat("%sSettingCurrentValue%d", shaderName.c_str(), op.PostShaderUniform.uniform + 1)] = op.PostShaderUniform.value.f;973}974}975break;976977case CheatOp::PostShaderFromMemory:978{979auto shaderChain = GetFullPostShadersChain(g_Config.vPostShaderNames);980if (Memory::IsValidRange(op.addr, 4) && op.PostShaderUniform.shader < shaderChain.size()) {981union {982float f;983uint32_t u;984} value;985value.u = Memory::Read_U32(op.addr);986std::string shaderName = shaderChain[op.PostShaderUniform.shader]->section;987switch (op.PostShaderUniform.format) {988case 0:989g_Config.mPostShaderSetting[StringFromFormat("%sSettingCurrentValue%d", shaderName.c_str(), op.PostShaderUniform.uniform + 1)] = value.u & 0x000000FF;990break;991case 1:992g_Config.mPostShaderSetting[StringFromFormat("%sSettingCurrentValue%d", shaderName.c_str(), op.PostShaderUniform.uniform + 1)] = value.u & 0x0000FFFF;993break;994case 2:995g_Config.mPostShaderSetting[StringFromFormat("%sSettingCurrentValue%d", shaderName.c_str(), op.PostShaderUniform.uniform + 1)] = value.u;996break;997case 3:998g_Config.mPostShaderSetting[StringFromFormat("%sSettingCurrentValue%d", shaderName.c_str(), op.PostShaderUniform.uniform + 1)] = value.f;999break;1000}1001}1002}1003break;10041005case CheatOp::Delay:1006// TODO: Not supported.1007break;10081009case CheatOp::Assert:1010if (Memory::IsValidRange(op.addr, 4)) {1011InvalidateICache(op.addr, 4);1012if (Memory::Read_U32(op.addr) != op.val) {1013i = cheat.lines.size();1014}1015}1016break;10171018case CheatOp::IfEqual:1019if (!TestIf(op, [](int a, int b) { return a == b; })) {1020i += (size_t)op.ifTypes.skip;1021}1022break;10231024case CheatOp::IfNotEqual:1025if (!TestIf(op, [](int a, int b) { return a != b; })) {1026i += (size_t)op.ifTypes.skip;1027}1028break;10291030case CheatOp::IfLess:1031if (!TestIf(op, [](int a, int b) { return a < b; })) {1032i += (size_t)op.ifTypes.skip;1033}1034break;10351036case CheatOp::IfGreater:1037if (!TestIf(op, [](int a, int b) { return a > b; })) {1038i += (size_t)op.ifTypes.skip;1039}1040break;10411042case CheatOp::IfAddrEqual:1043if (!TestIfAddr(op, [](int a, int b) { return a == b; })) {1044i += (size_t)op.ifAddrTypes.skip;1045}1046break;10471048case CheatOp::IfAddrNotEqual:1049if (!TestIfAddr(op, [](int a, int b) { return a != b; })) {1050i += (size_t)op.ifAddrTypes.skip;1051}1052break;10531054case CheatOp::IfAddrLess:1055if (!TestIfAddr(op, [](int a, int b) { return a < b; })) {1056i += (size_t)op.ifAddrTypes.skip;1057}1058break;10591060case CheatOp::IfAddrGreater:1061if (!TestIfAddr(op, [](int a, int b) { return a > b; })) {1062i += (size_t)op.ifAddrTypes.skip;1063}1064break;10651066case CheatOp::IfPressed:1067// Button Code1068// SELECT 0x000000011069// START 0x000000081070// DPAD UP 0x000000101071// DPAD RIGHT 0x000000201072// DPAD DOWN 0x000000401073// DPAD LEFT 0x000000801074// L TRIGGER 0x000001001075// R TRIGGER 0x000002001076// TRIANGLE 0x000010001077// CIRCLE 0x000020001078// CROSS 0x000040001079// SQUARE 0x000080001080// HOME 0x000100001081// HOLD 0x000200001082// WLAN 0x000400001083// REMOTE HOLD 0x000800001084// VOLUME UP 0x001000001085// VOLUME DOWN 0x002000001086// SCREEN 0x004000001087// NOTE 0x008000001088if ((__CtrlPeekButtons() & op.val) != op.val) {1089i += (size_t)op.ifTypes.skip;1090}1091break;10921093case CheatOp::IfNotPressed:1094if ((__CtrlPeekButtons() & op.val) == op.val) {1095i += (size_t)op.ifTypes.skip;1096}1097break;10981099case CheatOp::CwCheatPointerCommands:1100{1101InvalidateICache(op.addr + op.pointerCommands.baseOffset, 4);1102u32 base = Memory::Read_U32(op.addr + op.pointerCommands.baseOffset);1103u32 val = op.val;1104int type = op.pointerCommands.type;1105for (int a = 0; a < op.pointerCommands.count; ++a) {1106const CheatLine &line = cheat.lines[i++];1107switch (line.part1 >> 28) {1108case 0x1: // type copy byte1109{1110InvalidateICache(op.addr, 4);1111u32 srcAddr = Memory::Read_U32(op.addr) + op.pointerCommands.offset;1112u32 dstAddr = Memory::Read_U32(op.addr + op.pointerCommands.baseOffset) + (line.part1 & 0x0FFFFFFF);1113if (Memory::IsValidRange(dstAddr, val) && Memory::IsValidRange(srcAddr, val)) {1114InvalidateICache(dstAddr, val);1115InvalidateICache(srcAddr, val);1116Memory::Memcpy(dstAddr, srcAddr, val, "CwCheat");1117}1118// Don't perform any further action.1119type = -1;1120}1121break;11221123case 0x2:1124case 0x3: // type pointer walk1125{1126int walkOffset = (int)line.part1 & 0x0FFFFFFF;1127if ((line.part1 >> 28) == 0x3) {1128walkOffset = -walkOffset;1129}1130InvalidateICache(base + walkOffset, 4);1131base = Memory::Read_U32(base + walkOffset);1132switch (line.part2 >> 28) {1133case 0x2:1134case 0x3: // type pointer walk1135walkOffset = line.part2 & 0x0FFFFFFF;1136if ((line.part2 >> 28) == 0x3) {1137walkOffset = -walkOffset;1138}1139InvalidateICache(base + walkOffset, 4);1140base = Memory::Read_U32(base + walkOffset);1141break;11421143default:1144// Unexpected value in cheat line?1145break;1146}1147}1148break;11491150case 0x9: // type multi address write1151base += line.part1 & 0x0FFFFFFF;1152val += line.part2;1153break;11541155default:1156// Unexpected value in cheat line?1157break;1158}1159}11601161switch (type) {1162case 0: // 8 bit write1163InvalidateICache(base + op.pointerCommands.offset, 1);1164Memory::Write_U8((u8)val, base + op.pointerCommands.offset);1165break;1166case 1: // 16-bit write1167InvalidateICache(base + op.pointerCommands.offset, 2);1168Memory::Write_U16((u16)val, base + op.pointerCommands.offset);1169break;1170case 2: // 32-bit write1171InvalidateICache(base + op.pointerCommands.offset, 4);1172Memory::Write_U32((u32)val, base + op.pointerCommands.offset);1173break;1174case 3: // 8 bit inverse write1175InvalidateICache(base - op.pointerCommands.offset, 1);1176Memory::Write_U8((u8)val, base - op.pointerCommands.offset);1177break;1178case 4: // 16-bit inverse write1179InvalidateICache(base - op.pointerCommands.offset, 2);1180Memory::Write_U16((u16)val, base - op.pointerCommands.offset);1181break;1182case 5: // 32-bit inverse write1183InvalidateICache(base - op.pointerCommands.offset, 4);1184Memory::Write_U32((u32)val, base - op.pointerCommands.offset);1185break;1186case -1: // Operation already performed, nothing to do1187break;1188}1189}1190break;11911192default:1193_assert_(false);1194}1195}11961197void CWCheatEngine::Run() {1198if (Achievements::HardcoreModeActive()) {1199return;1200}12011202for (CheatCode cheat : cheats_) {1203// InterpretNextOp and ExecuteOp move i.1204for (size_t i = 0; i < cheat.lines.size(); ) {1205CheatOperation op = InterpretNextOp(cheat, i);1206ExecuteOp(op, cheat, i);1207}1208}1209}12101211bool CWCheatEngine::HasCheats() {1212return !cheats_.empty();1213}12141215bool CheatsInEffect() {1216if (!cheatEngine || !cheatsEnabled || Achievements::HardcoreModeActive())1217return false;1218return cheatEngine->HasCheats();1219}122012211222