diff --git a/CMakeLists.txt b/CMakeLists.txt index d7883392b80..127159a25ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -216,6 +216,8 @@ add_library(${PROJECT_NAME} STATIC src/logo.h src/main_data.cpp src/main_data.h + src/maniac_patch.cpp + src/maniac_patch.h src/map_data.h src/memory_management.h src/message_overlay.cpp diff --git a/Makefile.am b/Makefile.am index b71c4191fd4..57144b3a7aa 100644 --- a/Makefile.am +++ b/Makefile.am @@ -202,6 +202,8 @@ libeasyrpg_player_a_SOURCES = \ src/logo.h \ src/main_data.cpp \ src/main_data.h \ + src/maniac_patch.cpp \ + src/maniac_patch.h \ src/map_data.h \ src/memory_management.h \ src/message_overlay.cpp \ diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index f842603ebf3..f9e99be61e6 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -40,6 +40,7 @@ #include "game_message.h" #include "game_pictures.h" #include "game_screen.h" +#include "maniac_patch.h" #include "spriteset_map.h" #include "sprite_character.h" #include "scene_gameover.h" @@ -1499,8 +1500,8 @@ bool Game_Interpreter::CommandControlVariables(lcf::rpg::EventCommand const& com } case 21: // Expression (Maniac) - Output::Warning("ControlVariables: Maniac Patch expressions not supported"); - return true; + value = ManiacPatch::ParseExpression(MakeSpan(com.parameters).subspan(6, com.parameters[5])); + break; default: Output::Warning("ControlVariables: Unsupported operand {}", operand); return true; @@ -1529,8 +1530,9 @@ bool Game_Interpreter::CommandControlVariables(lcf::rpg::EventCommand const& com end = Main_Data::game_variables->Get(com.parameters[2]); } else if (target == 4 && Player::IsPatchManiac()) { // Expression (Maniac) - Output::Warning("ControlVariables: Maniac Patch expressions not supported"); - return true; + int idx = com.parameters[1]; + start = ManiacPatch::ParseExpression(MakeSpan(com.parameters).subspan(idx + 1, com.parameters[idx])); + end = start; } else { return true; } diff --git a/src/maniac_patch.cpp b/src/maniac_patch.cpp new file mode 100644 index 00000000000..59ab7fece57 --- /dev/null +++ b/src/maniac_patch.cpp @@ -0,0 +1,235 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#include "maniac_patch.h" +#include "main_data.h" +#include "game_switches.h" +#include "game_variables.h" +#include "output.h" + +#include + +namespace { + enum class Op { + Null = 0, + U8, + U16, + S32, + UX8, + UX16, + SX32, + Var = 8, + Switch, + VarIndirect = 13, + SwitchIndirect, + Array = 19, + Negate = 24, + Not, + Flip, + AssignInplace = 34, + AddInplace, + SubInplace, + MulInplace, + DivInplace, + ModInplace, + BitOrInplace, + BitAndInplace, + BitXorInplace, + BitShiftLeftInplace, + BitShiftRightInplace, + Add = 48, + Sub, + Mul, + Div, + Mod, + BitOr, + BitAnd, + BitXor, + BitShiftLeft, + BitShiftRight, + Equal, + GreaterEqual, + LessEqual, + Greater, + Less, + NotEqual, + Or, + And, + Range, + Subscript, + Ternary = 72, + Function = 78 + }; + + using Result = std::tuple::iterator, int>; +} + +Result process(std::vector::iterator it, std::vector::iterator end) { + int value = 0; + int imm = 0; + int imm2 = 0; + int imm3 = 0; + + auto op = static_cast(*it); + ++it; + + // When entering the switch it is on the first argument + switch (op) { + case Op::Null: + return {++it, 0}; + case Op::U8: + case Op::UX8: + value = *it++; + return {it, static_cast(value)}; + case Op::U16: + case Op::UX16: + imm = *it++; + imm2 = *it++; + value = (imm2 << 8) + imm; + return {it, static_cast(value)}; + case Op::S32: + case Op::SX32: + imm = *it++; + imm2 = *it++; + imm3 = *it++; + value = *it++; + value = (value << 24) + (imm3 << 16) + (imm2 << 8) + imm; + return {it, value}; + case Op::Var: + std::tie(it, imm) = process(it, end); + return {it, Main_Data::game_variables->Get(imm)}; + case Op::Switch: + std::tie(it, imm) = process(it, end); + return {it, Main_Data::game_switches->GetInt(imm)}; + case Op::VarIndirect: + std::tie(it, imm) = process(it, end); + return {it, Main_Data::game_variables->GetIndirect(imm)}; + case Op::SwitchIndirect: + std::tie(it, imm) = process(it, end); + return {it, Main_Data::game_switches->GetInt(Main_Data::game_variables->Get(imm))}; + case Op::Negate: + std::tie(it, imm) = process(it, end); + return {it, -imm}; + case Op::Not: + std::tie(it, imm) = process(it, end); + return {it, !imm ? 0 : 1}; + case Op::Flip: + std::tie(it, imm) = process(it, end); + return {it, ~imm}; + case Op::Add: + std::tie(it, imm) = process(it, end); + std::tie(it, imm2) = process(it, end); + return {it, imm + imm2}; + case Op::Sub: + std::tie(it, imm) = process(it, end); + std::tie(it, imm2) = process(it, end); + return {it, imm - imm2}; + case Op::Mul: + std::tie(it, imm) = process(it, end); + std::tie(it, imm2) = process(it, end); + return {it, imm * imm2}; + case Op::Div: + std::tie(it, imm) = process(it, end); + std::tie(it, imm2) = process(it, end); + if (imm2 == 0) { + return {it, 0}; + } + return {it, imm / imm2}; + case Op::Mod: + std::tie(it, imm) = process(it, end); + std::tie(it, imm2) = process(it, end); + if (imm2 == 0) { + return {it, 0}; + } + return {it, imm % imm2}; + case Op::BitOr: + std::tie(it, imm) = process(it, end); + std::tie(it, imm2) = process(it, end); + return {it, imm | imm2}; + case Op::BitAnd: + std::tie(it, imm) = process(it, end); + std::tie(it, imm2) = process(it, end); + return {it, imm & imm2}; + case Op::BitXor: + std::tie(it, imm) = process(it, end); + std::tie(it, imm2) = process(it, end); + return {it, imm ^ imm2}; + case Op::BitShiftLeft: + std::tie(it, imm) = process(it, end); + std::tie(it, imm2) = process(it, end); + return {it, imm << imm2}; + case Op::BitShiftRight: + std::tie(it, imm) = process(it, end); + std::tie(it, imm2) = process(it, end); + return {it, imm >> imm2}; + case Op::Equal: + std::tie(it, imm) = process(it, end); + std::tie(it, imm2) = process(it, end); + return {it, imm == imm2 ? 1 : 0}; + case Op::GreaterEqual: + std::tie(it, imm) = process(it, end); + std::tie(it, imm2) = process(it, end); + return {it, imm >= imm2 ? 1 : 0}; + case Op::LessEqual: + std::tie(it, imm) = process(it, end); + std::tie(it, imm2) = process(it, end); + return {it, imm <= imm2 ? 1 : 0}; + case Op::Greater: + std::tie(it, imm) = process(it, end); + std::tie(it, imm2) = process(it, end); + return {it, imm > imm2 ? 1 : 0}; + case Op::Less: + std::tie(it, imm) = process(it, end); + std::tie(it, imm2) = process(it, end); + return {it, imm < imm2 ? 1 : 0}; + case Op::NotEqual: + std::tie(it, imm) = process(it, end); + std::tie(it, imm2) = process(it, end); + return {it, imm != imm2 ? 1 : 0}; + case Op::Or: + std::tie(it, imm) = process(it, end); + std::tie(it, imm2) = process(it, end); + return {it, !!imm || !!imm2 ? 1 : 0}; + case Op::And: + std::tie(it, imm) = process(it, end); + std::tie(it, imm2) = process(it, end); + return {it, !!imm && !!imm2 ? 1 : 0}; + case Op::Subscript: + std::tie(it, imm) = process(it, end); + std::tie(it, imm2) = process(it, end); + return {it, 0}; + case Op::Ternary: + std::tie(it, imm) = process(it, end); + std::tie(it, imm2) = process(it, end); + std::tie(it, imm3) = process(it, end); + return {it, imm != 0 ? imm2 : imm3}; + default: + Output::Warning("Maniac: Expression contains unsupported operation {}", static_cast(op)); + return {end, 0}; + } +} + +int32_t ManiacPatch::ParseExpression(Span op_codes) { + std::vector ops; + for (auto &o: op_codes) { + ops.push_back((o & 0x000000FF)); + ops.push_back((o & 0x0000FF00) >> 8); + ops.push_back((o & 0x00FF0000) >> 16); + ops.push_back((o & 0xFF000000) >> 24); + } + return std::get(process(ops.begin(), ops.end())); +} \ No newline at end of file diff --git a/src/maniac_patch.h b/src/maniac_patch.h new file mode 100644 index 00000000000..6e7a5a8c009 --- /dev/null +++ b/src/maniac_patch.h @@ -0,0 +1,28 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + + +#ifndef EP_MANIAC_PATCH +#define EP_MANIAC_PATCH + +#include "span.h" + +namespace ManiacPatch { + int32_t ParseExpression(Span op_codes); +} + +#endif \ No newline at end of file