From b90ff739a05a63ab9b7c73d5fdab36c9ebd6a25c Mon Sep 17 00:00:00 2001 From: Ben Date: Sat, 17 Nov 2018 02:01:10 +0100 Subject: [PATCH] Add CheatEngine and support for Gateway cheats (#4406) * Add CheatEngine; Add support for Gateway cheats; Add Cheat UI * fix a potential crash on some systems * fix substr with negative length * Add Joker to the NonOp comp handling * Fixup JokerOp * minor fixup in patchop; add todo for nested loops * Add comment for PadState member variable in HID * fix: stol to stoul in parsing cheat file * fix misplaced parsing of values; fix patchop code * add missing break * Make read_func and write_func a template parameter --- src/citra_qt/CMakeLists.txt | 3 + src/citra_qt/cheats.cpp | 75 +++++ src/citra_qt/cheats.h | 30 ++ src/citra_qt/cheats.ui | 204 +++++++++++++ src/citra_qt/main.cpp | 10 + src/citra_qt/main.h | 1 + src/citra_qt/main.ui | 6 + src/common/common_paths.h | 1 + src/common/file_util.cpp | 1 + src/common/file_util.h | 1 + src/common/logging/backend.cpp | 1 + src/common/logging/log.h | 1 + src/core/CMakeLists.txt | 6 + src/core/cheats/cheat_base.cpp | 9 + src/core/cheats/cheat_base.h | 28 ++ src/core/cheats/cheats.cpp | 58 ++++ src/core/cheats/cheats.h | 37 +++ src/core/cheats/gateway_cheat.cpp | 463 ++++++++++++++++++++++++++++++ src/core/cheats/gateway_cheat.h | 83 ++++++ src/core/core.cpp | 11 + src/core/core.h | 13 + src/core/hle/service/hid/hid.cpp | 5 +- src/core/hle/service/hid/hid.h | 6 + 23 files changed, 1052 insertions(+), 1 deletion(-) create mode 100644 src/citra_qt/cheats.cpp create mode 100644 src/citra_qt/cheats.h create mode 100644 src/citra_qt/cheats.ui create mode 100644 src/core/cheats/cheat_base.cpp create mode 100644 src/core/cheats/cheat_base.h create mode 100644 src/core/cheats/cheats.cpp create mode 100644 src/core/cheats/cheats.h create mode 100644 src/core/cheats/gateway_cheat.cpp create mode 100644 src/core/cheats/gateway_cheat.h diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 20fefa87c..4bb95a7f2 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -24,6 +24,8 @@ add_executable(citra-qt camera/qt_camera_base.h camera/qt_multimedia_camera.cpp camera/qt_multimedia_camera.h + cheats.cpp + cheats.h citra-qt.rc configuration/config.cpp configuration/config.h @@ -134,6 +136,7 @@ set(UIS multiplayer/client_room.ui multiplayer/host_room.ui aboutdialog.ui + cheats.ui hotkeys.ui main.ui compatdb.ui diff --git a/src/citra_qt/cheats.cpp b/src/citra_qt/cheats.cpp new file mode 100644 index 000000000..720609205 --- /dev/null +++ b/src/citra_qt/cheats.cpp @@ -0,0 +1,75 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "citra_qt/cheats.h" +#include "core/cheats/cheat_base.h" +#include "core/cheats/cheats.h" +#include "core/core.h" +#include "core/hle/kernel/process.h" +#include "ui_cheats.h" + +CheatDialog::CheatDialog(QWidget* parent) + : QDialog(parent), ui(std::make_unique()) { + // Setup gui control settings + ui->setupUi(this); + setWindowFlags(Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint); + ui->tableCheats->setColumnWidth(0, 30); + ui->tableCheats->setColumnWidth(2, 85); + ui->tableCheats->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); + ui->tableCheats->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + ui->tableCheats->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed); + ui->textDetails->setEnabled(false); + ui->textNotes->setEnabled(false); + const auto game_id = fmt::format( + "{:016X}", Core::System::GetInstance().Kernel().GetCurrentProcess()->codeset->program_id); + ui->labelTitle->setText(tr("Title ID: %1").arg(QString::fromStdString(game_id))); + + connect(ui->buttonClose, &QPushButton::released, this, &CheatDialog::OnCancel); + connect(ui->tableCheats, &QTableWidget::cellClicked, this, &CheatDialog::OnRowSelected); + + LoadCheats(); +} + +CheatDialog::~CheatDialog() = default; + +void CheatDialog::LoadCheats() { + const auto& cheats = Core::System::GetInstance().CheatEngine().GetCheats(); + + ui->tableCheats->setRowCount(cheats.size()); + + for (size_t i = 0; i < cheats.size(); i++) { + QCheckBox* enabled = new QCheckBox(); + enabled->setChecked(cheats[i]->IsEnabled()); + enabled->setStyleSheet("margin-left:7px;"); + ui->tableCheats->setItem(i, 0, new QTableWidgetItem()); + ui->tableCheats->setCellWidget(i, 0, enabled); + ui->tableCheats->setItem( + i, 1, new QTableWidgetItem(QString::fromStdString(cheats[i]->GetName()))); + ui->tableCheats->setItem( + i, 2, new QTableWidgetItem(QString::fromStdString(cheats[i]->GetType()))); + enabled->setProperty("row", static_cast(i)); + + connect(enabled, &QCheckBox::stateChanged, this, &CheatDialog::OnCheckChanged); + } +} + +void CheatDialog::OnCancel() { + close(); +} + +void CheatDialog::OnRowSelected(int row, int column) { + ui->textDetails->setEnabled(true); + ui->textNotes->setEnabled(true); + const auto& current_cheat = Core::System::GetInstance().CheatEngine().GetCheats()[row]; + ui->textNotes->setPlainText(QString::fromStdString(current_cheat->GetComments())); + ui->textDetails->setPlainText(QString::fromStdString(current_cheat->ToString())); +} + +void CheatDialog::OnCheckChanged(int state) { + const QCheckBox* checkbox = qobject_cast(sender()); + int row = static_cast(checkbox->property("row").toInt()); + Core::System::GetInstance().CheatEngine().GetCheats()[row]->SetEnabled(state); +} diff --git a/src/citra_qt/cheats.h b/src/citra_qt/cheats.h new file mode 100644 index 000000000..d532175ab --- /dev/null +++ b/src/citra_qt/cheats.h @@ -0,0 +1,30 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +namespace Ui { +class CheatDialog; +} // namespace Ui + +class CheatDialog : public QDialog { + Q_OBJECT + +public: + explicit CheatDialog(QWidget* parent = nullptr); + ~CheatDialog(); + +private: + std::unique_ptr ui; + + void LoadCheats(); + +private slots: + void OnCancel(); + void OnRowSelected(int row, int column); + void OnCheckChanged(int state); +}; diff --git a/src/citra_qt/cheats.ui b/src/citra_qt/cheats.ui new file mode 100644 index 000000000..08c0da19f --- /dev/null +++ b/src/citra_qt/cheats.ui @@ -0,0 +1,204 @@ + + + CheatDialog + + + Qt::ApplicationModal + + + + 0 + 0 + 862 + 612 + + + + + 0 + 0 + + + + Cheats + + + + + 10 + 10 + 300 + 31 + + + + + 10 + + + + Title ID: + + + + + + 10 + 570 + 841 + 41 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Close + + + + + + + + + 10 + 80 + 551 + 471 + + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SelectRows + + + false + + + 3 + + + true + + + false + + + + + + + + + Name + + + + + Type + + + + + + + + + + 10 + 60 + 121 + 16 + + + + Available Cheats: + + + + + + 580 + 440 + 271 + 111 + + + + + + + true + + + + + + + + + 580 + 420 + 111 + 16 + + + + Notes: + + + + + + 580 + 80 + 271 + 311 + + + + + + + true + + + + + + + + + 580 + 60 + 55 + 16 + + + + Code: + + + + + + diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index db5c3c2ee..e16c08f8e 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -20,6 +20,7 @@ #include "citra_qt/bootmanager.h" #include "citra_qt/camera/qt_multimedia_camera.h" #include "citra_qt/camera/still_image_camera.h" +#include "citra_qt/cheats.h" #include "citra_qt/compatdb.h" #include "citra_qt/compatibility_list.h" #include "citra_qt/configuration/config.h" @@ -467,6 +468,7 @@ void GMainWindow::RestoreUIState() { microProfileDialog->restoreGeometry(UISettings::values.microprofile_geometry); microProfileDialog->setVisible(UISettings::values.microprofile_visible); #endif + ui.action_Cheats->setEnabled(false); game_list->LoadInterfaceLayout(); @@ -527,6 +529,7 @@ void GMainWindow::ConnectMenuEvents() { connect(ui.action_Report_Compatibility, &QAction::triggered, this, &GMainWindow::OnMenuReportCompatibility); connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure); + connect(ui.action_Cheats, &QAction::triggered, this, &GMainWindow::OnCheats); // View connect(ui.action_Single_Window_Mode, &QAction::triggered, this, @@ -870,6 +873,7 @@ void GMainWindow::ShutdownGame() { ui.action_Pause->setEnabled(false); ui.action_Stop->setEnabled(false); ui.action_Restart->setEnabled(false); + ui.action_Cheats->setEnabled(false); ui.action_Load_Amiibo->setEnabled(false); ui.action_Remove_Amiibo->setEnabled(false); ui.action_Report_Compatibility->setEnabled(false); @@ -1159,6 +1163,7 @@ void GMainWindow::OnStartGame() { ui.action_Pause->setEnabled(true); ui.action_Stop->setEnabled(true); ui.action_Restart->setEnabled(true); + ui.action_Cheats->setEnabled(true); ui.action_Load_Amiibo->setEnabled(true); ui.action_Report_Compatibility->setEnabled(true); ui.action_Enable_Frame_Advancing->setEnabled(true); @@ -1294,6 +1299,11 @@ void GMainWindow::OnSwapScreens() { Settings::Apply(); } +void GMainWindow::OnCheats() { + CheatDialog cheat_dialog(this); + cheat_dialog.exec(); +} + void GMainWindow::OnConfigure() { ConfigureDialog configureDialog(this, hotkey_registry); connect(&configureDialog, &ConfigureDialog::languageChanged, this, diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index 684a9e332..94657a88d 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -174,6 +174,7 @@ private slots: void ChangeScreenLayout(); void ToggleScreenLayout(); void OnSwapScreens(); + void OnCheats(); void ShowFullscreen(); void HideFullscreen(); void ToggleWindowMode(); diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui index 77ac7e3ac..adfacb1d8 100644 --- a/src/citra_qt/main.ui +++ b/src/citra_qt/main.ui @@ -85,6 +85,7 @@ + @@ -227,6 +228,11 @@ Configure... + + + Cheats... + + true diff --git a/src/common/common_paths.h b/src/common/common_paths.h index 80aa4b5a0..908225ac8 100644 --- a/src/common/common_paths.h +++ b/src/common/common_paths.h @@ -37,6 +37,7 @@ #define NAND_DIR "nand" #define SYSDATA_DIR "sysdata" #define LOG_DIR "log" +#define CHEATS_DIR "cheats" // Filenames // Files in the directory returned by GetUserPath(UserPath::LogDir) diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index b2f9b8d7e..4b95174d6 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -710,6 +710,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) { paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP); // TODO: Put the logs in a better location for each OS paths.emplace(UserPath::LogDir, user_path + LOG_DIR DIR_SEP); + paths.emplace(UserPath::CheatsDir, user_path + CHEATS_DIR DIR_SEP); } if (!new_path.empty()) { diff --git a/src/common/file_util.h b/src/common/file_util.h index 5f29b0514..96a30675c 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -22,6 +22,7 @@ namespace FileUtil { // User paths for GetUserPath enum class UserPath { CacheDir, + CheatsDir, ConfigDir, LogDir, NANDDir, diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 2915efd7b..722799fad 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -148,6 +148,7 @@ void FileBackend::Write(const Entry& entry) { CLS(Core) \ SUB(Core, ARM11) \ SUB(Core, Timing) \ + SUB(Core, Cheats) \ CLS(Config) \ CLS(Debug) \ SUB(Debug, Emulated) \ diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 59f3fae7d..fe529f415 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -40,6 +40,7 @@ enum class Class : ClassType { Core, ///< LLE emulation core Core_ARM11, ///< ARM11 CPU core Core_Timing, ///< CoreTiming functions + Core_Cheats, ///< Cheat functions Config, ///< Emulator configuration (including commandline) Debug, ///< Debugging tools Debug_Emulated, ///< Debug messages from the emulated programs diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 8cfb7f35a..175276354 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -26,6 +26,12 @@ add_library(core STATIC arm/skyeye_common/vfp/vfpdouble.cpp arm/skyeye_common/vfp/vfpinstr.cpp arm/skyeye_common/vfp/vfpsingle.cpp + cheats/cheat_base.cpp + cheats/cheat_base.h + cheats/cheats.cpp + cheats/cheats.h + cheats/gateway_cheat.cpp + cheats/gateway_cheat.h core.cpp core.h core_timing.cpp diff --git a/src/core/cheats/cheat_base.cpp b/src/core/cheats/cheat_base.cpp new file mode 100644 index 000000000..d6373ba76 --- /dev/null +++ b/src/core/cheats/cheat_base.cpp @@ -0,0 +1,9 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/cheats/cheat_base.h" + +namespace Cheats { +CheatBase::~CheatBase() = default; +} // namespace Cheats diff --git a/src/core/cheats/cheat_base.h b/src/core/cheats/cheat_base.h new file mode 100644 index 000000000..ee64a047f --- /dev/null +++ b/src/core/cheats/cheat_base.h @@ -0,0 +1,28 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +namespace Core { +class System; +} + +namespace Cheats { +class CheatBase { +public: + virtual ~CheatBase(); + virtual void Execute(Core::System& system) = 0; + + virtual bool IsEnabled() const = 0; + virtual void SetEnabled(bool enabled) = 0; + + virtual std::string GetComments() const = 0; + virtual std::string GetName() const = 0; + virtual std::string GetType() const = 0; + + virtual std::string ToString() const = 0; +}; +} // namespace Cheats diff --git a/src/core/cheats/cheats.cpp b/src/core/cheats/cheats.cpp new file mode 100644 index 000000000..612d1b5d9 --- /dev/null +++ b/src/core/cheats/cheats.cpp @@ -0,0 +1,58 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "core/cheats/cheats.h" +#include "core/cheats/gateway_cheat.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/hle/kernel/process.h" + +namespace Cheats { + +constexpr u64 run_interval_ticks = BASE_CLOCK_RATE_ARM11 / 60; + +CheatEngine::CheatEngine(Core::System& system_) : system(system_) { + LoadCheatFile(); + event = system.CoreTiming().RegisterEvent( + "CheatCore::run_event", + [this](u64 thread_id, s64 cycle_late) { RunCallback(thread_id, cycle_late); }); + system.CoreTiming().ScheduleEvent(run_interval_ticks, event); +} + +CheatEngine::~CheatEngine() { + system.CoreTiming().UnscheduleEvent(event, 0); +} + +const std::vector>& CheatEngine::GetCheats() const { + return cheats_list; +} + +void CheatEngine::LoadCheatFile() { + const std::string cheat_dir = FileUtil::GetUserPath(FileUtil::UserPath::CheatsDir); + const std::string filepath = fmt::format( + "{}{:016X}.txt", cheat_dir, system.Kernel().GetCurrentProcess()->codeset->program_id); + + if (!FileUtil::IsDirectory(cheat_dir)) { + FileUtil::CreateDir(cheat_dir); + } + + if (!FileUtil::Exists(filepath)) + return; + + auto gateway_cheats = GatewayCheat::LoadFile(filepath); + std::move(gateway_cheats.begin(), gateway_cheats.end(), std::back_inserter(cheats_list)); +} + +void CheatEngine::RunCallback([[maybe_unused]] u64 userdata, int cycles_late) { + for (auto& cheat : cheats_list) { + if (cheat->IsEnabled()) { + cheat->Execute(system); + } + } + system.CoreTiming().ScheduleEvent(run_interval_ticks - cycles_late, event); +} + +} // namespace Cheats diff --git a/src/core/cheats/cheats.h b/src/core/cheats/cheats.h new file mode 100644 index 000000000..7e838de29 --- /dev/null +++ b/src/core/cheats/cheats.h @@ -0,0 +1,37 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "common/common_types.h" + +namespace Core { +class System; +struct TimingEventType; +} // namespace Core + +namespace CoreTiming { +struct EventType; +} + +namespace Cheats { + +class CheatBase; + +class CheatEngine { +public: + explicit CheatEngine(Core::System& system); + ~CheatEngine(); + const std::vector>& GetCheats() const; + +private: + void LoadCheatFile(); + void RunCallback(u64 userdata, int cycles_late); + std::vector> cheats_list; + Core::TimingEventType* event; + Core::System& system; +}; +} // namespace Cheats diff --git a/src/core/cheats/gateway_cheat.cpp b/src/core/cheats/gateway_cheat.cpp new file mode 100644 index 000000000..18b15e747 --- /dev/null +++ b/src/core/cheats/gateway_cheat.cpp @@ -0,0 +1,463 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include "common/file_util.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "core/cheats/gateway_cheat.h" +#include "core/core.h" +#include "core/hle/service/hid/hid.h" +#include "core/memory.h" + +namespace Cheats { + +struct State { + u32 reg = 0; + u32 offset = 0; + u32 if_flag = 0; + u32 loop_count = 0; + std::size_t loop_back_line = 0; + std::size_t current_line_nr = 0; + bool loop_flag = false; +}; + +template +static inline std::enable_if_t> WriteOp(const GatewayCheat::CheatLine& line, + const State& state, + WriteFunction write_func, + Core::System& system) { + u32 addr = line.address + state.offset; + write_func(addr, static_cast(line.value)); + system.CPU().InvalidateCacheRange(addr, sizeof(T)); +} + +template +static inline std::enable_if_t> CompOp(const GatewayCheat::CheatLine& line, + State& state, ReadFunction read_func, + CompareFunc comp) { + u32 addr = line.address + state.offset; + T val = read_func(addr); + if (!comp(val)) { + state.if_flag++; + } +} + +static inline void LoadOffsetOp(const GatewayCheat::CheatLine& line, State& state) { + u32 addr = line.address + state.offset; + state.offset = Memory::Read32(addr); +} + +static inline void LoopOp(const GatewayCheat::CheatLine& line, State& state) { + state.loop_flag = state.loop_count < line.value; + state.loop_count++; + state.loop_back_line = state.current_line_nr; +} + +static inline void TerminateOp(State& state) { + if (state.if_flag > 0) { + state.if_flag--; + } +} + +static inline void LoopExecuteVariantOp(State& state) { + if (state.loop_flag) { + state.current_line_nr = state.loop_back_line - 1; + } else { + state.loop_count = 0; + } +} + +static inline void FullTerminateOp(State& state) { + if (state.loop_flag) { + state.current_line_nr = state.loop_back_line - 1; + } else { + state.offset = 0; + state.reg = 0; + state.loop_count = 0; + state.if_flag = 0; + state.loop_flag = false; + } +} + +static inline void SetOffsetOp(const GatewayCheat::CheatLine& line, State& state) { + state.offset = line.value; +} + +static inline void AddValueOp(const GatewayCheat::CheatLine& line, State& state) { + state.reg += line.value; +} + +static inline void SetValueOp(const GatewayCheat::CheatLine& line, State& state) { + state.reg = line.value; +} + +template +static inline std::enable_if_t> IncrementiveWriteOp( + const GatewayCheat::CheatLine& line, State& state, WriteFunction write_func, + Core::System& system) { + u32 addr = line.value + state.offset; + write_func(addr, static_cast(state.reg)); + system.CPU().InvalidateCacheRange(addr, sizeof(T)); + state.offset += sizeof(T); +} + +template +static inline std::enable_if_t> LoadOp(const GatewayCheat::CheatLine& line, + State& state, ReadFunction read_func) { + + u32 addr = line.value + state.offset; + state.reg = read_func(addr); +} + +static inline void AddOffsetOp(const GatewayCheat::CheatLine& line, State& state) { + state.offset += line.value; +} + +static inline void JokerOp(const GatewayCheat::CheatLine& line, State& state, + const Core::System& system) { + u32 pad_state = system.ServiceManager() + .GetService("hid:USER") + ->GetModule() + ->GetState() + .hex; + bool pressed = (pad_state & line.value) == line.value; + if (!pressed) { + state.if_flag++; + } +} + +static inline void PatchOp(const GatewayCheat::CheatLine& line, State& state, Core::System& system, + const std::vector& cheat_lines) { + if (state.if_flag > 0) { + // Skip over the additional patch lines + state.current_line_nr += static_cast(std::ceil(line.value / 8.0)); + return; + } + u32 num_bytes = line.value; + u32 addr = line.address + state.offset; + system.CPU().InvalidateCacheRange(addr, num_bytes); + bool first = true; + u32 bit_offset = 0; + if (num_bytes > 0) + state.current_line_nr++; // skip over the current code + while (num_bytes >= 4) { + u32 tmp = first ? cheat_lines[state.current_line_nr].first + : cheat_lines[state.current_line_nr].value; + if (!first && num_bytes > 4) { + state.current_line_nr++; + } + first = !first; + Memory::Write32(addr, tmp); + addr += 4; + num_bytes -= 4; + } + while (num_bytes > 0) { + u32 tmp = (first ? cheat_lines[state.current_line_nr].first + : cheat_lines[state.current_line_nr].value) >> + bit_offset; + Memory::Write8(addr, tmp); + addr += 1; + num_bytes -= 1; + bit_offset += 8; + } +} + +GatewayCheat::CheatLine::CheatLine(const std::string& line) { + constexpr std::size_t cheat_length = 17; + if (line.length() != cheat_length) { + type = CheatType::Null; + cheat_line = line; + LOG_ERROR(Core_Cheats, "Cheat contains invalid line: {}", line); + return; + } + try { + std::string type_temp = line.substr(0, 1); + // 0xD types have extra subtype value, i.e. 0xDA + std::string sub_type_temp; + if (type_temp == "D" || type_temp == "d") + sub_type_temp = line.substr(1, 1); + type = static_cast(std::stoi(type_temp + sub_type_temp, 0, 16)); + first = std::stoul(line.substr(0, 8), 0, 16); + address = first & 0x0FFFFFFF; + value = std::stoul(line.substr(9, 8), 0, 16); + cheat_line = line; + } catch (const std::logic_error& e) { + type = CheatType::Null; + cheat_line = line; + LOG_ERROR(Core_Cheats, "Cheat contains invalid line: {}", line); + } +} + +GatewayCheat::GatewayCheat(std::string name_, std::vector cheat_lines_, + std::string comments_) + : name(std::move(name_)), cheat_lines(std::move(cheat_lines_)), comments(std::move(comments_)) { +} + +GatewayCheat::~GatewayCheat() = default; + +void GatewayCheat::Execute(Core::System& system) { + State state; + + for (state.current_line_nr = 0; state.current_line_nr < cheat_lines.size(); + state.current_line_nr++) { + auto line = cheat_lines[state.current_line_nr]; + if (state.if_flag > 0) { + switch (line.type) { + case CheatType::GreaterThan32: + case CheatType::LessThan32: + case CheatType::EqualTo32: + case CheatType::NotEqualTo32: + case CheatType::GreaterThan16WithMask: + case CheatType::LessThan16WithMask: + case CheatType::EqualTo16WithMask: + case CheatType::NotEqualTo16WithMask: + case CheatType::Joker: + // Increment the if_flag to handle the end if correctly + state.if_flag++; + break; + case CheatType::Patch: + // EXXXXXXX YYYYYYYY + // Copies YYYYYYYY bytes from (current code location + 8) to [XXXXXXXX + offset]. + // We need to call this here to skip the additional patch lines + PatchOp(line, state, system, cheat_lines); + break; + case CheatType::Terminator: + // D0000000 00000000 - ENDIF + TerminateOp(state); + break; + case CheatType::FullTerminator: + // D2000000 00000000 - END; offset = 0; reg = 0; + FullTerminateOp(state); + break; + default: + break; + } + // Do not execute any other op code + continue; + } + switch (line.type) { + case CheatType::Null: + break; + case CheatType::Write32: + // 0XXXXXXX YYYYYYYY - word[XXXXXXX+offset] = YYYYYYYY + WriteOp(line, state, &Memory::Write32, system); + break; + case CheatType::Write16: + // 1XXXXXXX 0000YYYY - half[XXXXXXX+offset] = YYYY + WriteOp(line, state, &Memory::Write16, system); + break; + case CheatType::Write8: + // 2XXXXXXX 000000YY - byte[XXXXXXX+offset] = YY + WriteOp(line, state, &Memory::Write8, system); + break; + case CheatType::GreaterThan32: + // 3XXXXXXX YYYYYYYY - Execute next block IF YYYYYYYY > word[XXXXXXX] ;unsigned + CompOp(line, state, &Memory::Read32, + [&line](u32 val) -> bool { return line.value > val; }); + break; + case CheatType::LessThan32: + // 4XXXXXXX YYYYYYYY - Execute next block IF YYYYYYYY < word[XXXXXXX] ;unsigned + CompOp(line, state, &Memory::Read32, + [&line](u32 val) -> bool { return line.value < val; }); + break; + case CheatType::EqualTo32: + // 5XXXXXXX YYYYYYYY - Execute next block IF YYYYYYYY == word[XXXXXXX] ;unsigned + CompOp(line, state, &Memory::Read32, + [&line](u32 val) -> bool { return line.value == val; }); + break; + case CheatType::NotEqualTo32: + // 6XXXXXXX YYYYYYYY - Execute next block IF YYYYYYYY != word[XXXXXXX] ;unsigned + CompOp(line, state, &Memory::Read32, + [&line](u32 val) -> bool { return line.value != val; }); + break; + case CheatType::GreaterThan16WithMask: + // 7XXXXXXX ZZZZYYYY - Execute next block IF YYYY > ((not ZZZZ) AND half[XXXXXXX]) + CompOp(line, state, &Memory::Read16, [&line](u16 val) -> bool { + return static_cast(line.value) > ~(static_cast(~line.value >> 16) & val); + }); + break; + case CheatType::LessThan16WithMask: + // 8XXXXXXX ZZZZYYYY - Execute next block IF YYYY < ((not ZZZZ) AND half[XXXXXXX]) + CompOp(line, state, &Memory::Read16, [&line](u16 val) -> bool { + return static_cast(line.value) < ~(static_cast(~line.value >> 16) & val); + }); + break; + case CheatType::EqualTo16WithMask: + // 9XXXXXXX ZZZZYYYY - Execute next block IF YYYY = ((not ZZZZ) AND half[XXXXXXX]) + CompOp(line, state, &Memory::Read16, [&line](u16 val) -> bool { + return static_cast(line.value) == ~(static_cast(~line.value >> 16) & val); + }); + break; + case CheatType::NotEqualTo16WithMask: + // AXXXXXXX ZZZZYYYY - Execute next block IF YYYY <> ((not ZZZZ) AND half[XXXXXXX]) + CompOp(line, state, &Memory::Read16, [&line](u16 val) -> bool { + return static_cast(line.value) != ~(static_cast(~line.value >> 16) & val); + }); + break; + case CheatType::LoadOffset: + // BXXXXXXX 00000000 - offset = word[XXXXXXX+offset] + LoadOffsetOp(line, state); + break; + case CheatType::Loop: { + // C0000000 YYYYYYYY - LOOP next block YYYYYYYY times + // TODO(B3N30): Support nested loops if necessary + LoopOp(line, state); + break; + } + case CheatType::Terminator: { + // D0000000 00000000 - END IF + TerminateOp(state); + break; + } + case CheatType::LoopExecuteVariant: { + // D1000000 00000000 - END LOOP + LoopExecuteVariantOp(state); + break; + } + case CheatType::FullTerminator: { + // D2000000 00000000 - NEXT & Flush + FullTerminateOp(state); + break; + } + case CheatType::SetOffset: { + // D3000000 XXXXXXXX – Sets the offset to XXXXXXXX + SetOffsetOp(line, state); + break; + } + case CheatType::AddValue: { + // D4000000 XXXXXXXX – reg += XXXXXXXX + AddValueOp(line, state); + break; + } + case CheatType::SetValue: { + // D5000000 XXXXXXXX – reg = XXXXXXXX + SetValueOp(line, state); + break; + } + case CheatType::IncrementiveWrite32: { + // D6000000 XXXXXXXX – (32bit) [XXXXXXXX+offset] = reg ; offset += 4 + IncrementiveWriteOp(line, state, &Memory::Write32, system); + break; + } + case CheatType::IncrementiveWrite16: { + // D7000000 XXXXXXXX – (16bit) [XXXXXXXX+offset] = reg & 0xffff ; offset += 2 + IncrementiveWriteOp(line, state, &Memory::Write16, system); + break; + } + case CheatType::IncrementiveWrite8: { + // D8000000 XXXXXXXX – (16bit) [XXXXXXXX+offset] = reg & 0xff ; offset++ + IncrementiveWriteOp(line, state, &Memory::Write8, system); + break; + } + case CheatType::Load32: { + // D9000000 XXXXXXXX – reg = [XXXXXXXX+offset] + LoadOp(line, state, &Memory::Read32); + break; + } + case CheatType::Load16: { + // DA000000 XXXXXXXX – reg = [XXXXXXXX+offset] & 0xFFFF + LoadOp(line, state, &Memory::Read16); + break; + } + case CheatType::Load8: { + // DB000000 XXXXXXXX – reg = [XXXXXXXX+offset] & 0xFF + LoadOp(line, state, &Memory::Read8); + break; + } + case CheatType::AddOffset: { + // DC000000 XXXXXXXX – offset + XXXXXXXX + AddOffsetOp(line, state); + break; + } + case CheatType::Joker: { + // DD000000 XXXXXXXX – if KEYPAD has value XXXXXXXX execute next block + JokerOp(line, state, system); + break; + } + case CheatType::Patch: { + // EXXXXXXX YYYYYYYY + // Copies YYYYYYYY bytes from (current code location + 8) to [XXXXXXXX + offset]. + PatchOp(line, state, system, cheat_lines); + break; + } + } + } +} + +bool GatewayCheat::IsEnabled() const { + return enabled; +} + +void GatewayCheat::SetEnabled(bool enabled_) { + enabled = enabled_; + if (enabled) { + LOG_WARNING(Core_Cheats, "Cheats enabled. This might lead to weird behaviour or crashes"); + } +} + +std::string GatewayCheat::GetComments() const { + return comments; +} + +std::string GatewayCheat::GetName() const { + return name; +} + +std::string GatewayCheat::GetType() const { + return "Gateway"; +} + +std::string GatewayCheat::ToString() const { + std::string result; + result += '[' + name + "]\n"; + result += comments + '\n'; + for (const auto& line : cheat_lines) + result += line.cheat_line + '\n'; + result += '\n'; + return result; +} + +std::vector> GatewayCheat::LoadFile(const std::string& filepath) { + std::vector> cheats; + + std::ifstream file; + OpenFStream(file, filepath, std::ios_base::in); + if (!file) { + return cheats; + } + + std::string comments; + std::vector cheat_lines; + std::string name; + + while (!file.eof()) { + std::string line; + std::getline(file, line); + line.erase(std::remove(line.begin(), line.end(), '\0'), line.end()); + line = Common::StripSpaces(line); // remove spaces at front and end + if (line.length() >= 2 && line.front() == '[') { + if (!cheat_lines.empty()) { + cheats.push_back(std::make_unique(name, cheat_lines, comments)); + } + name = line.substr(1, line.length() - 2); + cheat_lines.clear(); + comments.erase(); + } else if (!line.empty() && line.front() == '*') { + comments += line.substr(1, line.length() - 1) + '\n'; + } else if (!line.empty()) { + cheat_lines.emplace_back(std::move(line)); + } + } + if (!cheat_lines.empty()) { + cheats.push_back(std::make_unique(name, cheat_lines, comments)); + } + return cheats; +} +} // namespace Cheats diff --git a/src/core/cheats/gateway_cheat.h b/src/core/cheats/gateway_cheat.h new file mode 100644 index 000000000..5d7a8fcf9 --- /dev/null +++ b/src/core/cheats/gateway_cheat.h @@ -0,0 +1,83 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "core/cheats/cheat_base.h" + +namespace Cheats { +class GatewayCheat final : public CheatBase { +public: + enum class CheatType { + Null = -0x1, + Write32 = 0x00, + Write16 = 0x01, + Write8 = 0x02, + GreaterThan32 = 0x03, + LessThan32 = 0x04, + EqualTo32 = 0x05, + NotEqualTo32 = 0x06, + GreaterThan16WithMask = 0x07, + LessThan16WithMask = 0x08, + EqualTo16WithMask = 0x09, + NotEqualTo16WithMask = 0x0A, + LoadOffset = 0x0B, + Loop = 0x0C, + Terminator = 0xD0, + LoopExecuteVariant = 0xD1, + FullTerminator = 0xD2, + SetOffset = 0xD3, + AddValue = 0xD4, + SetValue = 0xD5, + IncrementiveWrite32 = 0xD6, + IncrementiveWrite16 = 0xD7, + IncrementiveWrite8 = 0xD8, + Load32 = 0xD9, + Load16 = 0xDA, + Load8 = 0xDB, + AddOffset = 0xDC, + Joker = 0xDD, + Patch = 0xE, + }; + + struct CheatLine { + explicit CheatLine(const std::string& line); + CheatType type; + u32 address; + u32 value; + u32 first; + std::string cheat_line; + }; + + GatewayCheat(std::string name, std::vector cheat_lines, std::string comments); + ~GatewayCheat(); + + void Execute(Core::System& system) override; + + bool IsEnabled() const override; + void SetEnabled(bool enabled) override; + + std::string GetComments() const override; + std::string GetName() const override; + std::string GetType() const override; + std::string ToString() const override; + + /// Gateway cheats look like: + /// [Name] + /// 12345678 90ABCDEF + /// 12345678 90ABCDEF + /// (there might be multiple lines of those hex numbers) + /// Comment lines start with a '*' + /// This function will pares the file for such structures + static std::vector> LoadFile(const std::string& filepath); + +private: + std::atomic enabled = false; + const std::string name; + const std::vector cheat_lines; + const std::string comments; +}; +} // namespace Cheats diff --git a/src/core/core.cpp b/src/core/core.cpp index 40cedd2d7..295a2d7a8 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -12,6 +12,7 @@ #include "core/arm/dynarmic/arm_dynarmic.h" #endif #include "core/arm/dyncom/arm_dyncom.h" +#include "core/cheats/cheats.h" #include "core/core.h" #include "core/core_timing.h" #include "core/gdbstub/gdbstub.h" @@ -143,6 +144,7 @@ System::ResultStatus System::Load(EmuWindow& emu_window, const std::string& file } } Memory::SetCurrentPageTable(&kernel->GetCurrentProcess()->vm_manager.page_table); + cheat_engine = std::make_unique(*this); status = ResultStatus::Success; m_emu_window = &emu_window; m_filepath = filepath; @@ -248,6 +250,14 @@ const Timing& System::CoreTiming() const { return *timing; } +Cheats::CheatEngine& System::CheatEngine() { + return *cheat_engine; +} + +const Cheats::CheatEngine& System::CheatEngine() const { + return *cheat_engine; +} + void System::RegisterSoftwareKeyboard(std::shared_ptr swkbd) { registered_swkbd = std::move(swkbd); } @@ -271,6 +281,7 @@ void System::Shutdown() { #ifdef ENABLE_SCRIPTING rpc_server.reset(); #endif + cheat_engine.reset(); service_manager.reset(); dsp_core.reset(); cpu_core.reset(); diff --git a/src/core/core.h b/src/core/core.h index af1291856..e3b58b620 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -39,6 +39,10 @@ namespace Kernel { class KernelSystem; } +namespace Cheats { +class CheatEngine; +} + namespace Core { class Timing; @@ -184,6 +188,12 @@ public: /// Gets a const reference to the timing system const Timing& CoreTiming() const; + /// Gets a reference to the cheat engine + Cheats::CheatEngine& CheatEngine(); + + /// Gets a const reference to the cheat engine + const Cheats::CheatEngine& CheatEngine() const; + PerfStats perf_stats; FrameLimiter frame_limiter; @@ -244,6 +254,9 @@ private: /// Frontend applets std::shared_ptr registered_swkbd; + /// Cheats manager + std::unique_ptr cheat_engine; + #ifdef ENABLE_SCRIPTING /// RPC Server for scripting support std::unique_ptr rpc_server; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 805cc6c91..70c5cd9b1 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -74,7 +74,6 @@ void Module::UpdatePadCallback(u64 userdata, s64 cycles_late) { if (is_device_reload_pending.exchange(false)) LoadInputDevices(); - PadState state; using namespace Settings::NativeButton; state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); @@ -394,6 +393,10 @@ void Module::ReloadInputDevices() { is_device_reload_pending.store(true); } +const PadState& Module::GetState() const { + return state; +} + std::shared_ptr GetModule(Core::System& system) { auto hid = system.ServiceManager().GetService("hid:USER"); if (!hid) diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index de0b6f0a9..07bd18230 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -299,6 +299,8 @@ public: void ReloadInputDevices(); + const PadState& GetState() const; + private: void LoadInputDevices(); void UpdatePadCallback(u64 userdata, s64 cycles_late); @@ -317,6 +319,10 @@ private: Kernel::SharedPtr event_gyroscope; Kernel::SharedPtr event_debug_pad; + // The HID module of a 3DS does not store the PadState. + // Storing this here was necessary for emulation specific tasks like cheats or scripting. + PadState state; + u32 next_pad_index = 0; u32 next_touch_index = 0; u32 next_accelerometer_index = 0;