1
0
Fork 0

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
This commit is contained in:
Ben 2018-11-17 02:01:10 +01:00 committed by James Rowe
parent 560df843b1
commit b90ff739a0
23 changed files with 1052 additions and 1 deletions

View File

@ -24,6 +24,8 @@ add_executable(citra-qt
camera/qt_camera_base.h camera/qt_camera_base.h
camera/qt_multimedia_camera.cpp camera/qt_multimedia_camera.cpp
camera/qt_multimedia_camera.h camera/qt_multimedia_camera.h
cheats.cpp
cheats.h
citra-qt.rc citra-qt.rc
configuration/config.cpp configuration/config.cpp
configuration/config.h configuration/config.h
@ -134,6 +136,7 @@ set(UIS
multiplayer/client_room.ui multiplayer/client_room.ui
multiplayer/host_room.ui multiplayer/host_room.ui
aboutdialog.ui aboutdialog.ui
cheats.ui
hotkeys.ui hotkeys.ui
main.ui main.ui
compatdb.ui compatdb.ui

75
src/citra_qt/cheats.cpp Normal file
View File

@ -0,0 +1,75 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QCheckBox>
#include <QTableWidgetItem>
#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<Ui::CheatDialog>()) {
// 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<int>(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<QCheckBox*>(sender());
int row = static_cast<int>(checkbox->property("row").toInt());
Core::System::GetInstance().CheatEngine().GetCheats()[row]->SetEnabled(state);
}

30
src/citra_qt/cheats.h Normal file
View File

@ -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 <memory>
#include <QDialog>
namespace Ui {
class CheatDialog;
} // namespace Ui
class CheatDialog : public QDialog {
Q_OBJECT
public:
explicit CheatDialog(QWidget* parent = nullptr);
~CheatDialog();
private:
std::unique_ptr<Ui::CheatDialog> ui;
void LoadCheats();
private slots:
void OnCancel();
void OnRowSelected(int row, int column);
void OnCheckChanged(int state);
};

204
src/citra_qt/cheats.ui Normal file
View File

@ -0,0 +1,204 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CheatDialog</class>
<widget class="QDialog" name="CheatDialog">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>862</width>
<height>612</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Cheats</string>
</property>
<widget class="QLabel" name="labelTitle">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>300</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>Title ID:</string>
</property>
</widget>
<widget class="QWidget" name="horizontalLayoutWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>570</y>
<width>841</width>
<height>41</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="buttonClose">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="verticalLayoutWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>80</y>
<width>551</width>
<height>471</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTableWidget" name="tableCheats">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="columnCount">
<number>3</number>
</property>
<attribute name="horizontalHeaderVisible">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string/>
</property>
</column>
<column>
<property name="text">
<string>Name</string>
</property>
</column>
<column>
<property name="text">
<string>Type</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QLabel" name="labelAvailableCheats">
<property name="geometry">
<rect>
<x>10</x>
<y>60</y>
<width>121</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Available Cheats:</string>
</property>
</widget>
<widget class="QWidget" name="verticalLayoutWidget_2">
<property name="geometry">
<rect>
<x>580</x>
<y>440</y>
<width>271</width>
<height>111</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPlainTextEdit" name="textNotes">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QLabel" name="labelNotes">
<property name="geometry">
<rect>
<x>580</x>
<y>420</y>
<width>111</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Notes:</string>
</property>
</widget>
<widget class="QWidget" name="verticalLayoutWidget_3">
<property name="geometry">
<rect>
<x>580</x>
<y>80</y>
<width>271</width>
<height>311</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QPlainTextEdit" name="textDetails">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QLabel" name="labelDetails">
<property name="geometry">
<rect>
<x>580</x>
<y>60</y>
<width>55</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Code:</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -20,6 +20,7 @@
#include "citra_qt/bootmanager.h" #include "citra_qt/bootmanager.h"
#include "citra_qt/camera/qt_multimedia_camera.h" #include "citra_qt/camera/qt_multimedia_camera.h"
#include "citra_qt/camera/still_image_camera.h" #include "citra_qt/camera/still_image_camera.h"
#include "citra_qt/cheats.h"
#include "citra_qt/compatdb.h" #include "citra_qt/compatdb.h"
#include "citra_qt/compatibility_list.h" #include "citra_qt/compatibility_list.h"
#include "citra_qt/configuration/config.h" #include "citra_qt/configuration/config.h"
@ -467,6 +468,7 @@ void GMainWindow::RestoreUIState() {
microProfileDialog->restoreGeometry(UISettings::values.microprofile_geometry); microProfileDialog->restoreGeometry(UISettings::values.microprofile_geometry);
microProfileDialog->setVisible(UISettings::values.microprofile_visible); microProfileDialog->setVisible(UISettings::values.microprofile_visible);
#endif #endif
ui.action_Cheats->setEnabled(false);
game_list->LoadInterfaceLayout(); game_list->LoadInterfaceLayout();
@ -527,6 +529,7 @@ void GMainWindow::ConnectMenuEvents() {
connect(ui.action_Report_Compatibility, &QAction::triggered, this, connect(ui.action_Report_Compatibility, &QAction::triggered, this,
&GMainWindow::OnMenuReportCompatibility); &GMainWindow::OnMenuReportCompatibility);
connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure); connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure);
connect(ui.action_Cheats, &QAction::triggered, this, &GMainWindow::OnCheats);
// View // View
connect(ui.action_Single_Window_Mode, &QAction::triggered, this, connect(ui.action_Single_Window_Mode, &QAction::triggered, this,
@ -870,6 +873,7 @@ void GMainWindow::ShutdownGame() {
ui.action_Pause->setEnabled(false); ui.action_Pause->setEnabled(false);
ui.action_Stop->setEnabled(false); ui.action_Stop->setEnabled(false);
ui.action_Restart->setEnabled(false); ui.action_Restart->setEnabled(false);
ui.action_Cheats->setEnabled(false);
ui.action_Load_Amiibo->setEnabled(false); ui.action_Load_Amiibo->setEnabled(false);
ui.action_Remove_Amiibo->setEnabled(false); ui.action_Remove_Amiibo->setEnabled(false);
ui.action_Report_Compatibility->setEnabled(false); ui.action_Report_Compatibility->setEnabled(false);
@ -1159,6 +1163,7 @@ void GMainWindow::OnStartGame() {
ui.action_Pause->setEnabled(true); ui.action_Pause->setEnabled(true);
ui.action_Stop->setEnabled(true); ui.action_Stop->setEnabled(true);
ui.action_Restart->setEnabled(true); ui.action_Restart->setEnabled(true);
ui.action_Cheats->setEnabled(true);
ui.action_Load_Amiibo->setEnabled(true); ui.action_Load_Amiibo->setEnabled(true);
ui.action_Report_Compatibility->setEnabled(true); ui.action_Report_Compatibility->setEnabled(true);
ui.action_Enable_Frame_Advancing->setEnabled(true); ui.action_Enable_Frame_Advancing->setEnabled(true);
@ -1294,6 +1299,11 @@ void GMainWindow::OnSwapScreens() {
Settings::Apply(); Settings::Apply();
} }
void GMainWindow::OnCheats() {
CheatDialog cheat_dialog(this);
cheat_dialog.exec();
}
void GMainWindow::OnConfigure() { void GMainWindow::OnConfigure() {
ConfigureDialog configureDialog(this, hotkey_registry); ConfigureDialog configureDialog(this, hotkey_registry);
connect(&configureDialog, &ConfigureDialog::languageChanged, this, connect(&configureDialog, &ConfigureDialog::languageChanged, this,

View File

@ -174,6 +174,7 @@ private slots:
void ChangeScreenLayout(); void ChangeScreenLayout();
void ToggleScreenLayout(); void ToggleScreenLayout();
void OnSwapScreens(); void OnSwapScreens();
void OnCheats();
void ShowFullscreen(); void ShowFullscreen();
void HideFullscreen(); void HideFullscreen();
void ToggleWindowMode(); void ToggleWindowMode();

View File

@ -85,6 +85,7 @@
<addaction name="action_Report_Compatibility"/> <addaction name="action_Report_Compatibility"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="action_Configure"/> <addaction name="action_Configure"/>
<addaction name="action_Cheats"/>
</widget> </widget>
<widget class="QMenu" name="menu_View"> <widget class="QMenu" name="menu_View">
<property name="title"> <property name="title">
@ -227,6 +228,11 @@
<string>Configure...</string> <string>Configure...</string>
</property> </property>
</action> </action>
<action name="action_Cheats">
<property name="text">
<string>Cheats...</string>
</property>
</action>
<action name="action_Display_Dock_Widget_Headers"> <action name="action_Display_Dock_Widget_Headers">
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>

View File

@ -37,6 +37,7 @@
#define NAND_DIR "nand" #define NAND_DIR "nand"
#define SYSDATA_DIR "sysdata" #define SYSDATA_DIR "sysdata"
#define LOG_DIR "log" #define LOG_DIR "log"
#define CHEATS_DIR "cheats"
// Filenames // Filenames
// Files in the directory returned by GetUserPath(UserPath::LogDir) // Files in the directory returned by GetUserPath(UserPath::LogDir)

View File

@ -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); paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP);
// TODO: Put the logs in a better location for each OS // TODO: Put the logs in a better location for each OS
paths.emplace(UserPath::LogDir, user_path + LOG_DIR DIR_SEP); paths.emplace(UserPath::LogDir, user_path + LOG_DIR DIR_SEP);
paths.emplace(UserPath::CheatsDir, user_path + CHEATS_DIR DIR_SEP);
} }
if (!new_path.empty()) { if (!new_path.empty()) {

View File

@ -22,6 +22,7 @@ namespace FileUtil {
// User paths for GetUserPath // User paths for GetUserPath
enum class UserPath { enum class UserPath {
CacheDir, CacheDir,
CheatsDir,
ConfigDir, ConfigDir,
LogDir, LogDir,
NANDDir, NANDDir,

View File

@ -148,6 +148,7 @@ void FileBackend::Write(const Entry& entry) {
CLS(Core) \ CLS(Core) \
SUB(Core, ARM11) \ SUB(Core, ARM11) \
SUB(Core, Timing) \ SUB(Core, Timing) \
SUB(Core, Cheats) \
CLS(Config) \ CLS(Config) \
CLS(Debug) \ CLS(Debug) \
SUB(Debug, Emulated) \ SUB(Debug, Emulated) \

View File

@ -40,6 +40,7 @@ enum class Class : ClassType {
Core, ///< LLE emulation core Core, ///< LLE emulation core
Core_ARM11, ///< ARM11 CPU core Core_ARM11, ///< ARM11 CPU core
Core_Timing, ///< CoreTiming functions Core_Timing, ///< CoreTiming functions
Core_Cheats, ///< Cheat functions
Config, ///< Emulator configuration (including commandline) Config, ///< Emulator configuration (including commandline)
Debug, ///< Debugging tools Debug, ///< Debugging tools
Debug_Emulated, ///< Debug messages from the emulated programs Debug_Emulated, ///< Debug messages from the emulated programs

View File

@ -26,6 +26,12 @@ add_library(core STATIC
arm/skyeye_common/vfp/vfpdouble.cpp arm/skyeye_common/vfp/vfpdouble.cpp
arm/skyeye_common/vfp/vfpinstr.cpp arm/skyeye_common/vfp/vfpinstr.cpp
arm/skyeye_common/vfp/vfpsingle.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.cpp
core.h core.h
core_timing.cpp core_timing.cpp

View File

@ -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

View File

@ -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 <string>
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

View File

@ -0,0 +1,58 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <functional>
#include <fmt/format.h>
#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<std::unique_ptr<CheatBase>>& 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

37
src/core/cheats/cheats.h Normal file
View File

@ -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 <memory>
#include <vector>
#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<std::unique_ptr<CheatBase>>& GetCheats() const;
private:
void LoadCheatFile();
void RunCallback(u64 userdata, int cycles_late);
std::vector<std::unique_ptr<CheatBase>> cheats_list;
Core::TimingEventType* event;
Core::System& system;
};
} // namespace Cheats

View File

@ -0,0 +1,463 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <cmath>
#include <fstream>
#include <functional>
#include <string>
#include <vector>
#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 <typename T, typename WriteFunction>
static inline std::enable_if_t<std::is_integral_v<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<T>(line.value));
system.CPU().InvalidateCacheRange(addr, sizeof(T));
}
template <typename T, typename ReadFunction, typename CompareFunc>
static inline std::enable_if_t<std::is_integral_v<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 <typename T, typename WriteFunction>
static inline std::enable_if_t<std::is_integral_v<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<T>(state.reg));
system.CPU().InvalidateCacheRange(addr, sizeof(T));
state.offset += sizeof(T);
}
template <typename T, typename ReadFunction>
static inline std::enable_if_t<std::is_integral_v<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<Service::HID::Module::Interface>("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<GatewayCheat::CheatLine>& cheat_lines) {
if (state.if_flag > 0) {
// Skip over the additional patch lines
state.current_line_nr += static_cast<int>(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<CheatType>(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<CheatLine> 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<u32>(line, state, &Memory::Write32, system);
break;
case CheatType::Write16:
// 1XXXXXXX 0000YYYY - half[XXXXXXX+offset] = YYYY
WriteOp<u16>(line, state, &Memory::Write16, system);
break;
case CheatType::Write8:
// 2XXXXXXX 000000YY - byte[XXXXXXX+offset] = YY
WriteOp<u8>(line, state, &Memory::Write8, system);
break;
case CheatType::GreaterThan32:
// 3XXXXXXX YYYYYYYY - Execute next block IF YYYYYYYY > word[XXXXXXX] ;unsigned
CompOp<u32>(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<u32>(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<u32>(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<u32>(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<u16>(line, state, &Memory::Read16, [&line](u16 val) -> bool {
return static_cast<u16>(line.value) > ~(static_cast<u16>(~line.value >> 16) & val);
});
break;
case CheatType::LessThan16WithMask:
// 8XXXXXXX ZZZZYYYY - Execute next block IF YYYY < ((not ZZZZ) AND half[XXXXXXX])
CompOp<u16>(line, state, &Memory::Read16, [&line](u16 val) -> bool {
return static_cast<u16>(line.value) < ~(static_cast<u16>(~line.value >> 16) & val);
});
break;
case CheatType::EqualTo16WithMask:
// 9XXXXXXX ZZZZYYYY - Execute next block IF YYYY = ((not ZZZZ) AND half[XXXXXXX])
CompOp<u16>(line, state, &Memory::Read16, [&line](u16 val) -> bool {
return static_cast<u16>(line.value) == ~(static_cast<u16>(~line.value >> 16) & val);
});
break;
case CheatType::NotEqualTo16WithMask:
// AXXXXXXX ZZZZYYYY - Execute next block IF YYYY <> ((not ZZZZ) AND half[XXXXXXX])
CompOp<u16>(line, state, &Memory::Read16, [&line](u16 val) -> bool {
return static_cast<u16>(line.value) != ~(static_cast<u16>(~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<u32>(line, state, &Memory::Write32, system);
break;
}
case CheatType::IncrementiveWrite16: {
// D7000000 XXXXXXXX (16bit) [XXXXXXXX+offset] = reg & 0xffff ; offset += 2
IncrementiveWriteOp<u16>(line, state, &Memory::Write16, system);
break;
}
case CheatType::IncrementiveWrite8: {
// D8000000 XXXXXXXX (16bit) [XXXXXXXX+offset] = reg & 0xff ; offset++
IncrementiveWriteOp<u8>(line, state, &Memory::Write8, system);
break;
}
case CheatType::Load32: {
// D9000000 XXXXXXXX reg = [XXXXXXXX+offset]
LoadOp<u32>(line, state, &Memory::Read32);
break;
}
case CheatType::Load16: {
// DA000000 XXXXXXXX reg = [XXXXXXXX+offset] & 0xFFFF
LoadOp<u16>(line, state, &Memory::Read16);
break;
}
case CheatType::Load8: {
// DB000000 XXXXXXXX reg = [XXXXXXXX+offset] & 0xFF
LoadOp<u8>(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<std::unique_ptr<CheatBase>> GatewayCheat::LoadFile(const std::string& filepath) {
std::vector<std::unique_ptr<CheatBase>> cheats;
std::ifstream file;
OpenFStream(file, filepath, std::ios_base::in);
if (!file) {
return cheats;
}
std::string comments;
std::vector<CheatLine> 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<GatewayCheat>(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<GatewayCheat>(name, cheat_lines, comments));
}
return cheats;
}
} // namespace Cheats

View File

@ -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 <atomic>
#include <memory>
#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<CheatLine> 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<std::unique_ptr<CheatBase>> LoadFile(const std::string& filepath);
private:
std::atomic<bool> enabled = false;
const std::string name;
const std::vector<CheatLine> cheat_lines;
const std::string comments;
};
} // namespace Cheats

View File

@ -12,6 +12,7 @@
#include "core/arm/dynarmic/arm_dynarmic.h" #include "core/arm/dynarmic/arm_dynarmic.h"
#endif #endif
#include "core/arm/dyncom/arm_dyncom.h" #include "core/arm/dyncom/arm_dyncom.h"
#include "core/cheats/cheats.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/gdbstub/gdbstub.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); Memory::SetCurrentPageTable(&kernel->GetCurrentProcess()->vm_manager.page_table);
cheat_engine = std::make_unique<Cheats::CheatEngine>(*this);
status = ResultStatus::Success; status = ResultStatus::Success;
m_emu_window = &emu_window; m_emu_window = &emu_window;
m_filepath = filepath; m_filepath = filepath;
@ -248,6 +250,14 @@ const Timing& System::CoreTiming() const {
return *timing; return *timing;
} }
Cheats::CheatEngine& System::CheatEngine() {
return *cheat_engine;
}
const Cheats::CheatEngine& System::CheatEngine() const {
return *cheat_engine;
}
void System::RegisterSoftwareKeyboard(std::shared_ptr<Frontend::SoftwareKeyboard> swkbd) { void System::RegisterSoftwareKeyboard(std::shared_ptr<Frontend::SoftwareKeyboard> swkbd) {
registered_swkbd = std::move(swkbd); registered_swkbd = std::move(swkbd);
} }
@ -271,6 +281,7 @@ void System::Shutdown() {
#ifdef ENABLE_SCRIPTING #ifdef ENABLE_SCRIPTING
rpc_server.reset(); rpc_server.reset();
#endif #endif
cheat_engine.reset();
service_manager.reset(); service_manager.reset();
dsp_core.reset(); dsp_core.reset();
cpu_core.reset(); cpu_core.reset();

View File

@ -39,6 +39,10 @@ namespace Kernel {
class KernelSystem; class KernelSystem;
} }
namespace Cheats {
class CheatEngine;
}
namespace Core { namespace Core {
class Timing; class Timing;
@ -184,6 +188,12 @@ public:
/// Gets a const reference to the timing system /// Gets a const reference to the timing system
const Timing& CoreTiming() const; 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; PerfStats perf_stats;
FrameLimiter frame_limiter; FrameLimiter frame_limiter;
@ -244,6 +254,9 @@ private:
/// Frontend applets /// Frontend applets
std::shared_ptr<Frontend::SoftwareKeyboard> registered_swkbd; std::shared_ptr<Frontend::SoftwareKeyboard> registered_swkbd;
/// Cheats manager
std::unique_ptr<Cheats::CheatEngine> cheat_engine;
#ifdef ENABLE_SCRIPTING #ifdef ENABLE_SCRIPTING
/// RPC Server for scripting support /// RPC Server for scripting support
std::unique_ptr<RPC::RPCServer> rpc_server; std::unique_ptr<RPC::RPCServer> rpc_server;

View File

@ -74,7 +74,6 @@ void Module::UpdatePadCallback(u64 userdata, s64 cycles_late) {
if (is_device_reload_pending.exchange(false)) if (is_device_reload_pending.exchange(false))
LoadInputDevices(); LoadInputDevices();
PadState state;
using namespace Settings::NativeButton; using namespace Settings::NativeButton;
state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
state.b.Assign(buttons[B - 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); is_device_reload_pending.store(true);
} }
const PadState& Module::GetState() const {
return state;
}
std::shared_ptr<Module> GetModule(Core::System& system) { std::shared_ptr<Module> GetModule(Core::System& system) {
auto hid = system.ServiceManager().GetService<Service::HID::Module::Interface>("hid:USER"); auto hid = system.ServiceManager().GetService<Service::HID::Module::Interface>("hid:USER");
if (!hid) if (!hid)

View File

@ -299,6 +299,8 @@ public:
void ReloadInputDevices(); void ReloadInputDevices();
const PadState& GetState() const;
private: private:
void LoadInputDevices(); void LoadInputDevices();
void UpdatePadCallback(u64 userdata, s64 cycles_late); void UpdatePadCallback(u64 userdata, s64 cycles_late);
@ -317,6 +319,10 @@ private:
Kernel::SharedPtr<Kernel::Event> event_gyroscope; Kernel::SharedPtr<Kernel::Event> event_gyroscope;
Kernel::SharedPtr<Kernel::Event> event_debug_pad; Kernel::SharedPtr<Kernel::Event> 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_pad_index = 0;
u32 next_touch_index = 0; u32 next_touch_index = 0;
u32 next_accelerometer_index = 0; u32 next_accelerometer_index = 0;