mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	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:
		
							parent
							
								
									560df843b1
								
							
						
					
					
						commit
						b90ff739a0
					
				
					 23 changed files with 1052 additions and 1 deletions
				
			
		|  | @ -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 | ||||
|  |  | |||
							
								
								
									
										75
									
								
								src/citra_qt/cheats.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/citra_qt/cheats.cpp
									
										
									
									
									
										Normal 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
									
								
							
							
						
						
									
										30
									
								
								src/citra_qt/cheats.h
									
										
									
									
									
										Normal 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
									
								
							
							
						
						
									
										204
									
								
								src/citra_qt/cheats.ui
									
										
									
									
									
										Normal 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> | ||||
|  | @ -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, | ||||
|  |  | |||
|  | @ -174,6 +174,7 @@ private slots: | |||
|     void ChangeScreenLayout(); | ||||
|     void ToggleScreenLayout(); | ||||
|     void OnSwapScreens(); | ||||
|     void OnCheats(); | ||||
|     void ShowFullscreen(); | ||||
|     void HideFullscreen(); | ||||
|     void ToggleWindowMode(); | ||||
|  |  | |||
|  | @ -85,6 +85,7 @@ | |||
|     <addaction name="action_Report_Compatibility"/> | ||||
|     <addaction name="separator"/> | ||||
|     <addaction name="action_Configure"/> | ||||
|     <addaction name="action_Cheats"/> | ||||
|    </widget> | ||||
|    <widget class="QMenu" name="menu_View"> | ||||
|     <property name="title"> | ||||
|  | @ -227,6 +228,11 @@ | |||
|     <string>Configure...</string> | ||||
|    </property> | ||||
|   </action> | ||||
|   <action name="action_Cheats"> | ||||
|    <property name="text"> | ||||
|     <string>Cheats...</string> | ||||
|    </property> | ||||
|   </action> | ||||
|   <action name="action_Display_Dock_Widget_Headers"> | ||||
|    <property name="checkable"> | ||||
|     <bool>true</bool> | ||||
|  |  | |||
|  | @ -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)
 | ||||
|  |  | |||
|  | @ -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()) { | ||||
|  |  | |||
|  | @ -22,6 +22,7 @@ namespace FileUtil { | |||
| // User paths for GetUserPath
 | ||||
| enum class UserPath { | ||||
|     CacheDir, | ||||
|     CheatsDir, | ||||
|     ConfigDir, | ||||
|     LogDir, | ||||
|     NANDDir, | ||||
|  |  | |||
|  | @ -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)                                                                           \ | ||||
|  |  | |||
|  | @ -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
 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
							
								
								
									
										9
									
								
								src/core/cheats/cheat_base.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/core/cheats/cheat_base.cpp
									
										
									
									
									
										Normal 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
 | ||||
							
								
								
									
										28
									
								
								src/core/cheats/cheat_base.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/core/cheats/cheat_base.h
									
										
									
									
									
										Normal 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
 | ||||
							
								
								
									
										58
									
								
								src/core/cheats/cheats.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/core/cheats/cheats.cpp
									
										
									
									
									
										Normal 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
									
								
							
							
						
						
									
										37
									
								
								src/core/cheats/cheats.h
									
										
									
									
									
										Normal 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
 | ||||
							
								
								
									
										463
									
								
								src/core/cheats/gateway_cheat.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										463
									
								
								src/core/cheats/gateway_cheat.cpp
									
										
									
									
									
										Normal 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
 | ||||
							
								
								
									
										83
									
								
								src/core/cheats/gateway_cheat.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/core/cheats/gateway_cheat.h
									
										
									
									
									
										Normal 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
 | ||||
|  | @ -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<Cheats::CheatEngine>(*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<Frontend::SoftwareKeyboard> 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(); | ||||
|  |  | |||
|  | @ -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<Frontend::SoftwareKeyboard> registered_swkbd; | ||||
| 
 | ||||
|     /// Cheats manager
 | ||||
|     std::unique_ptr<Cheats::CheatEngine> cheat_engine; | ||||
| 
 | ||||
| #ifdef ENABLE_SCRIPTING | ||||
|     /// RPC Server for scripting support
 | ||||
|     std::unique_ptr<RPC::RPCServer> rpc_server; | ||||
|  |  | |||
|  | @ -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<Module> GetModule(Core::System& system) { | ||||
|     auto hid = system.ServiceManager().GetService<Service::HID::Module::Interface>("hid:USER"); | ||||
|     if (!hid) | ||||
|  |  | |||
|  | @ -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<Kernel::Event> event_gyroscope; | ||||
|     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_touch_index = 0; | ||||
|     u32 next_accelerometer_index = 0; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue