From b90ff739a05a63ab9b7c73d5fdab36c9ebd6a25c Mon Sep 17 00:00:00 2001
From: Ben <bene_thomas@web.de>
Date: Sat, 17 Nov 2018 02:01:10 +0100
Subject: [PATCH] Add CheatEngine and support for Gateway cheats (#4406)

* Add CheatEngine; Add support for Gateway cheats; Add Cheat UI

* fix a potential crash on some systems

* fix substr with negative length

* Add Joker to the NonOp comp handling

* Fixup JokerOp

* minor fixup in patchop; add todo for nested loops

* Add comment for PadState member variable in HID

* fix: stol to stoul in parsing cheat file

* fix misplaced parsing of values; fix patchop code

* add missing break

* Make read_func and write_func a template parameter
---
 src/citra_qt/CMakeLists.txt       |   3 +
 src/citra_qt/cheats.cpp           |  75 +++++
 src/citra_qt/cheats.h             |  30 ++
 src/citra_qt/cheats.ui            | 204 +++++++++++++
 src/citra_qt/main.cpp             |  10 +
 src/citra_qt/main.h               |   1 +
 src/citra_qt/main.ui              |   6 +
 src/common/common_paths.h         |   1 +
 src/common/file_util.cpp          |   1 +
 src/common/file_util.h            |   1 +
 src/common/logging/backend.cpp    |   1 +
 src/common/logging/log.h          |   1 +
 src/core/CMakeLists.txt           |   6 +
 src/core/cheats/cheat_base.cpp    |   9 +
 src/core/cheats/cheat_base.h      |  28 ++
 src/core/cheats/cheats.cpp        |  58 ++++
 src/core/cheats/cheats.h          |  37 +++
 src/core/cheats/gateway_cheat.cpp | 463 ++++++++++++++++++++++++++++++
 src/core/cheats/gateway_cheat.h   |  83 ++++++
 src/core/core.cpp                 |  11 +
 src/core/core.h                   |  13 +
 src/core/hle/service/hid/hid.cpp  |   5 +-
 src/core/hle/service/hid/hid.h    |   6 +
 23 files changed, 1052 insertions(+), 1 deletion(-)
 create mode 100644 src/citra_qt/cheats.cpp
 create mode 100644 src/citra_qt/cheats.h
 create mode 100644 src/citra_qt/cheats.ui
 create mode 100644 src/core/cheats/cheat_base.cpp
 create mode 100644 src/core/cheats/cheat_base.h
 create mode 100644 src/core/cheats/cheats.cpp
 create mode 100644 src/core/cheats/cheats.h
 create mode 100644 src/core/cheats/gateway_cheat.cpp
 create mode 100644 src/core/cheats/gateway_cheat.h

diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index 20fefa87c..4bb95a7f2 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -24,6 +24,8 @@ add_executable(citra-qt
     camera/qt_camera_base.h
     camera/qt_multimedia_camera.cpp
     camera/qt_multimedia_camera.h
+    cheats.cpp
+    cheats.h
     citra-qt.rc
     configuration/config.cpp
     configuration/config.h
@@ -134,6 +136,7 @@ set(UIS
     multiplayer/client_room.ui
     multiplayer/host_room.ui
     aboutdialog.ui
+    cheats.ui
     hotkeys.ui
     main.ui
     compatdb.ui
diff --git a/src/citra_qt/cheats.cpp b/src/citra_qt/cheats.cpp
new file mode 100644
index 000000000..720609205
--- /dev/null
+++ b/src/citra_qt/cheats.cpp
@@ -0,0 +1,75 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <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);
+}
diff --git a/src/citra_qt/cheats.h b/src/citra_qt/cheats.h
new file mode 100644
index 000000000..d532175ab
--- /dev/null
+++ b/src/citra_qt/cheats.h
@@ -0,0 +1,30 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <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);
+};
diff --git a/src/citra_qt/cheats.ui b/src/citra_qt/cheats.ui
new file mode 100644
index 000000000..08c0da19f
--- /dev/null
+++ b/src/citra_qt/cheats.ui
@@ -0,0 +1,204 @@
+<?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>
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index db5c3c2ee..e16c08f8e 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -20,6 +20,7 @@
 #include "citra_qt/bootmanager.h"
 #include "citra_qt/camera/qt_multimedia_camera.h"
 #include "citra_qt/camera/still_image_camera.h"
+#include "citra_qt/cheats.h"
 #include "citra_qt/compatdb.h"
 #include "citra_qt/compatibility_list.h"
 #include "citra_qt/configuration/config.h"
@@ -467,6 +468,7 @@ void GMainWindow::RestoreUIState() {
     microProfileDialog->restoreGeometry(UISettings::values.microprofile_geometry);
     microProfileDialog->setVisible(UISettings::values.microprofile_visible);
 #endif
+    ui.action_Cheats->setEnabled(false);
 
     game_list->LoadInterfaceLayout();
 
@@ -527,6 +529,7 @@ void GMainWindow::ConnectMenuEvents() {
     connect(ui.action_Report_Compatibility, &QAction::triggered, this,
             &GMainWindow::OnMenuReportCompatibility);
     connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure);
+    connect(ui.action_Cheats, &QAction::triggered, this, &GMainWindow::OnCheats);
 
     // View
     connect(ui.action_Single_Window_Mode, &QAction::triggered, this,
@@ -870,6 +873,7 @@ void GMainWindow::ShutdownGame() {
     ui.action_Pause->setEnabled(false);
     ui.action_Stop->setEnabled(false);
     ui.action_Restart->setEnabled(false);
+    ui.action_Cheats->setEnabled(false);
     ui.action_Load_Amiibo->setEnabled(false);
     ui.action_Remove_Amiibo->setEnabled(false);
     ui.action_Report_Compatibility->setEnabled(false);
@@ -1159,6 +1163,7 @@ void GMainWindow::OnStartGame() {
     ui.action_Pause->setEnabled(true);
     ui.action_Stop->setEnabled(true);
     ui.action_Restart->setEnabled(true);
+    ui.action_Cheats->setEnabled(true);
     ui.action_Load_Amiibo->setEnabled(true);
     ui.action_Report_Compatibility->setEnabled(true);
     ui.action_Enable_Frame_Advancing->setEnabled(true);
@@ -1294,6 +1299,11 @@ void GMainWindow::OnSwapScreens() {
     Settings::Apply();
 }
 
+void GMainWindow::OnCheats() {
+    CheatDialog cheat_dialog(this);
+    cheat_dialog.exec();
+}
+
 void GMainWindow::OnConfigure() {
     ConfigureDialog configureDialog(this, hotkey_registry);
     connect(&configureDialog, &ConfigureDialog::languageChanged, this,
diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h
index 684a9e332..94657a88d 100644
--- a/src/citra_qt/main.h
+++ b/src/citra_qt/main.h
@@ -174,6 +174,7 @@ private slots:
     void ChangeScreenLayout();
     void ToggleScreenLayout();
     void OnSwapScreens();
+    void OnCheats();
     void ShowFullscreen();
     void HideFullscreen();
     void ToggleWindowMode();
diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui
index 77ac7e3ac..adfacb1d8 100644
--- a/src/citra_qt/main.ui
+++ b/src/citra_qt/main.ui
@@ -85,6 +85,7 @@
     <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>
diff --git a/src/common/common_paths.h b/src/common/common_paths.h
index 80aa4b5a0..908225ac8 100644
--- a/src/common/common_paths.h
+++ b/src/common/common_paths.h
@@ -37,6 +37,7 @@
 #define NAND_DIR "nand"
 #define SYSDATA_DIR "sysdata"
 #define LOG_DIR "log"
+#define CHEATS_DIR "cheats"
 
 // Filenames
 // Files in the directory returned by GetUserPath(UserPath::LogDir)
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index b2f9b8d7e..4b95174d6 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -710,6 +710,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
         paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP);
         // TODO: Put the logs in a better location for each OS
         paths.emplace(UserPath::LogDir, user_path + LOG_DIR DIR_SEP);
+        paths.emplace(UserPath::CheatsDir, user_path + CHEATS_DIR DIR_SEP);
     }
 
     if (!new_path.empty()) {
diff --git a/src/common/file_util.h b/src/common/file_util.h
index 5f29b0514..96a30675c 100644
--- a/src/common/file_util.h
+++ b/src/common/file_util.h
@@ -22,6 +22,7 @@ namespace FileUtil {
 // User paths for GetUserPath
 enum class UserPath {
     CacheDir,
+    CheatsDir,
     ConfigDir,
     LogDir,
     NANDDir,
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 2915efd7b..722799fad 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -148,6 +148,7 @@ void FileBackend::Write(const Entry& entry) {
     CLS(Core)                                                                                      \
     SUB(Core, ARM11)                                                                               \
     SUB(Core, Timing)                                                                              \
+    SUB(Core, Cheats)                                                                              \
     CLS(Config)                                                                                    \
     CLS(Debug)                                                                                     \
     SUB(Debug, Emulated)                                                                           \
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 59f3fae7d..fe529f415 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -40,6 +40,7 @@ enum class Class : ClassType {
     Core,              ///< LLE emulation core
     Core_ARM11,        ///< ARM11 CPU core
     Core_Timing,       ///< CoreTiming functions
+    Core_Cheats,       ///< Cheat functions
     Config,            ///< Emulator configuration (including commandline)
     Debug,             ///< Debugging tools
     Debug_Emulated,    ///< Debug messages from the emulated programs
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 8cfb7f35a..175276354 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -26,6 +26,12 @@ add_library(core STATIC
     arm/skyeye_common/vfp/vfpdouble.cpp
     arm/skyeye_common/vfp/vfpinstr.cpp
     arm/skyeye_common/vfp/vfpsingle.cpp
+    cheats/cheat_base.cpp
+    cheats/cheat_base.h
+    cheats/cheats.cpp
+    cheats/cheats.h
+    cheats/gateway_cheat.cpp
+    cheats/gateway_cheat.h
     core.cpp
     core.h
     core_timing.cpp
diff --git a/src/core/cheats/cheat_base.cpp b/src/core/cheats/cheat_base.cpp
new file mode 100644
index 000000000..d6373ba76
--- /dev/null
+++ b/src/core/cheats/cheat_base.cpp
@@ -0,0 +1,9 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/cheats/cheat_base.h"
+
+namespace Cheats {
+CheatBase::~CheatBase() = default;
+} // namespace Cheats
diff --git a/src/core/cheats/cheat_base.h b/src/core/cheats/cheat_base.h
new file mode 100644
index 000000000..ee64a047f
--- /dev/null
+++ b/src/core/cheats/cheat_base.h
@@ -0,0 +1,28 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <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
diff --git a/src/core/cheats/cheats.cpp b/src/core/cheats/cheats.cpp
new file mode 100644
index 000000000..612d1b5d9
--- /dev/null
+++ b/src/core/cheats/cheats.cpp
@@ -0,0 +1,58 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <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
diff --git a/src/core/cheats/cheats.h b/src/core/cheats/cheats.h
new file mode 100644
index 000000000..7e838de29
--- /dev/null
+++ b/src/core/cheats/cheats.h
@@ -0,0 +1,37 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <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
diff --git a/src/core/cheats/gateway_cheat.cpp b/src/core/cheats/gateway_cheat.cpp
new file mode 100644
index 000000000..18b15e747
--- /dev/null
+++ b/src/core/cheats/gateway_cheat.cpp
@@ -0,0 +1,463 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <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
diff --git a/src/core/cheats/gateway_cheat.h b/src/core/cheats/gateway_cheat.h
new file mode 100644
index 000000000..5d7a8fcf9
--- /dev/null
+++ b/src/core/cheats/gateway_cheat.h
@@ -0,0 +1,83 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <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
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 40cedd2d7..295a2d7a8 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -12,6 +12,7 @@
 #include "core/arm/dynarmic/arm_dynarmic.h"
 #endif
 #include "core/arm/dyncom/arm_dyncom.h"
+#include "core/cheats/cheats.h"
 #include "core/core.h"
 #include "core/core_timing.h"
 #include "core/gdbstub/gdbstub.h"
@@ -143,6 +144,7 @@ System::ResultStatus System::Load(EmuWindow& emu_window, const std::string& file
         }
     }
     Memory::SetCurrentPageTable(&kernel->GetCurrentProcess()->vm_manager.page_table);
+    cheat_engine = std::make_unique<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();
diff --git a/src/core/core.h b/src/core/core.h
index af1291856..e3b58b620 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -39,6 +39,10 @@ namespace Kernel {
 class KernelSystem;
 }
 
+namespace Cheats {
+class CheatEngine;
+}
+
 namespace Core {
 
 class Timing;
@@ -184,6 +188,12 @@ public:
     /// Gets a const reference to the timing system
     const Timing& CoreTiming() const;
 
+    /// Gets a reference to the cheat engine
+    Cheats::CheatEngine& CheatEngine();
+
+    /// Gets a const reference to the cheat engine
+    const Cheats::CheatEngine& CheatEngine() const;
+
     PerfStats perf_stats;
     FrameLimiter frame_limiter;
 
@@ -244,6 +254,9 @@ private:
     /// Frontend applets
     std::shared_ptr<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;
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 805cc6c91..70c5cd9b1 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -74,7 +74,6 @@ void Module::UpdatePadCallback(u64 userdata, s64 cycles_late) {
     if (is_device_reload_pending.exchange(false))
         LoadInputDevices();
 
-    PadState state;
     using namespace Settings::NativeButton;
     state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
     state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
@@ -394,6 +393,10 @@ void Module::ReloadInputDevices() {
     is_device_reload_pending.store(true);
 }
 
+const PadState& Module::GetState() const {
+    return state;
+}
+
 std::shared_ptr<Module> GetModule(Core::System& system) {
     auto hid = system.ServiceManager().GetService<Service::HID::Module::Interface>("hid:USER");
     if (!hid)
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index de0b6f0a9..07bd18230 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -299,6 +299,8 @@ public:
 
     void ReloadInputDevices();
 
+    const PadState& GetState() const;
+
 private:
     void LoadInputDevices();
     void UpdatePadCallback(u64 userdata, s64 cycles_late);
@@ -317,6 +319,10 @@ private:
     Kernel::SharedPtr<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;