diff --git a/src/citra_qt/cheats.cpp b/src/citra_qt/cheats.cpp
index 720609205..fc6f102a2 100644
--- a/src/citra_qt/cheats.cpp
+++ b/src/citra_qt/cheats.cpp
@@ -3,10 +3,12 @@
 // Refer to the license.txt file included.
 
 #include <QCheckBox>
+#include <QMessageBox>
 #include <QTableWidgetItem>
 #include "citra_qt/cheats.h"
 #include "core/cheats/cheat_base.h"
 #include "core/cheats/cheats.h"
+#include "core/cheats/gateway_cheat.h"
 #include "core/core.h"
 #include "core/hle/kernel/process.h"
 #include "ui_cheats.h"
@@ -21,14 +23,23 @@ CheatDialog::CheatDialog(QWidget* parent)
     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->lineName->setEnabled(false);
+    ui->textCode->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->buttonAddCheat, &QPushButton::released, this, &CheatDialog::OnAddCheat);
     connect(ui->tableCheats, &QTableWidget::cellClicked, this, &CheatDialog::OnRowSelected);
+    connect(ui->lineName, &QLineEdit::textEdited, this, &CheatDialog::OnTextEdited);
+    connect(ui->textNotes, &QPlainTextEdit::textChanged, this, &CheatDialog::OnTextEdited);
+    connect(ui->textCode, &QPlainTextEdit::textChanged, this, &CheatDialog::OnTextEdited);
+
+    connect(ui->buttonSave, &QPushButton::released,
+            [this] { SaveCheat(ui->tableCheats->currentRow()); });
+    connect(ui->buttonDelete, &QPushButton::released, this, &CheatDialog::OnDeleteCheat);
 
     LoadCheats();
 }
@@ -36,7 +47,7 @@ CheatDialog::CheatDialog(QWidget* parent)
 CheatDialog::~CheatDialog() = default;
 
 void CheatDialog::LoadCheats() {
-    const auto& cheats = Core::System::GetInstance().CheatEngine().GetCheats();
+    cheats = Core::System::GetInstance().CheatEngine().GetCheats();
 
     ui->tableCheats->setRowCount(cheats.size());
 
@@ -56,20 +67,184 @@ void CheatDialog::LoadCheats() {
     }
 }
 
+bool CheatDialog::CheckSaveCheat() {
+    auto answer = QMessageBox::warning(
+        this, tr("Cheats"), tr("Would you like to save the current cheat?"),
+        QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Cancel);
+
+    if (answer == QMessageBox::Yes) {
+        return SaveCheat(last_row);
+    } else {
+        return answer != QMessageBox::Cancel;
+    }
+}
+
+bool CheatDialog::SaveCheat(int row) {
+    if (ui->lineName->text().isEmpty()) {
+        QMessageBox::critical(this, tr("Save Cheat"), tr("Please enter a cheat name."));
+        return false;
+    }
+    if (ui->textCode->toPlainText().isEmpty()) {
+        QMessageBox::critical(this, tr("Save Cheat"), tr("Please enter the cheat code."));
+        return false;
+    }
+
+    // Check if the cheat lines are valid
+    auto code_lines = ui->textCode->toPlainText().split("\n", QString::SkipEmptyParts);
+    for (int i = 0; i < code_lines.size(); ++i) {
+        Cheats::GatewayCheat::CheatLine cheat_line(code_lines[i].toStdString());
+        if (cheat_line.valid)
+            continue;
+
+        auto answer = QMessageBox::warning(
+            this, tr("Save Cheat"),
+            tr("Cheat code line %1 is not valid.\nWould you like to ignore the error and continue?")
+                .arg(i + 1),
+            QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
+        if (answer == QMessageBox::No)
+            return false;
+    }
+
+    auto cheat = std::make_shared<Cheats::GatewayCheat>(ui->lineName->text().toStdString(),
+                                                        ui->textCode->toPlainText().toStdString(),
+                                                        ui->textNotes->toPlainText().toStdString());
+
+    if (newly_created) {
+        Core::System::GetInstance().CheatEngine().AddCheat(cheat);
+        newly_created = false;
+    } else {
+        Core::System::GetInstance().CheatEngine().UpdateCheat(row, cheat);
+    }
+    Core::System::GetInstance().CheatEngine().SaveCheatFile();
+
+    int previous_row = ui->tableCheats->currentRow();
+    int previous_col = ui->tableCheats->currentColumn();
+    LoadCheats();
+    ui->tableCheats->setCurrentCell(previous_row, previous_col);
+
+    edited = false;
+    ui->buttonSave->setEnabled(false);
+    ui->buttonAddCheat->setEnabled(true);
+    return true;
+}
+
+void CheatDialog::closeEvent(QCloseEvent* event) {
+    if (edited && !CheckSaveCheat()) {
+        event->ignore();
+        return;
+    }
+    event->accept();
+}
+
 void CheatDialog::OnCancel() {
     close();
 }
 
 void CheatDialog::OnRowSelected(int row, int column) {
-    ui->textDetails->setEnabled(true);
+    if (row == last_row) {
+        return;
+    }
+    if (edited && !CheckSaveCheat()) {
+        ui->tableCheats->setCurrentCell(last_row, last_col);
+        return;
+    }
+    if (row < cheats.size()) {
+        if (newly_created) {
+            // Remove the newly created dummy item
+            newly_created = false;
+            ui->tableCheats->setRowCount(ui->tableCheats->rowCount() - 1);
+        }
+
+        const auto& current_cheat = cheats[row];
+        ui->lineName->setText(QString::fromStdString(current_cheat->GetName()));
+        ui->textNotes->setPlainText(QString::fromStdString(current_cheat->GetComments()));
+        ui->textCode->setPlainText(QString::fromStdString(current_cheat->GetCode()));
+    }
+
+    edited = false;
+    ui->buttonSave->setEnabled(false);
+    ui->buttonDelete->setEnabled(true);
+    ui->buttonAddCheat->setEnabled(true);
+    ui->lineName->setEnabled(true);
+    ui->textCode->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()));
+
+    last_row = row;
+    last_col = column;
 }
 
 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);
+    cheats[row]->SetEnabled(state);
+    Core::System::GetInstance().CheatEngine().SaveCheatFile();
+}
+
+void CheatDialog::OnTextEdited() {
+    edited = true;
+    ui->buttonSave->setEnabled(true);
+}
+
+void CheatDialog::OnDeleteCheat() {
+    if (newly_created) {
+        newly_created = false;
+    } else {
+        Core::System::GetInstance().CheatEngine().RemoveCheat(ui->tableCheats->currentRow());
+        Core::System::GetInstance().CheatEngine().SaveCheatFile();
+    }
+
+    LoadCheats();
+    if (cheats.empty()) {
+        ui->lineName->setText("");
+        ui->textCode->setPlainText("");
+        ui->textNotes->setPlainText("");
+        ui->lineName->setEnabled(false);
+        ui->textCode->setEnabled(false);
+        ui->textNotes->setEnabled(false);
+        ui->buttonDelete->setEnabled(false);
+        last_row = last_col = -1;
+    } else {
+        if (last_row >= ui->tableCheats->rowCount()) {
+            last_row = ui->tableCheats->rowCount() - 1;
+        }
+        ui->tableCheats->setCurrentCell(last_row, last_col);
+
+        const auto& current_cheat = cheats[last_row];
+        ui->lineName->setText(QString::fromStdString(current_cheat->GetName()));
+        ui->textNotes->setPlainText(QString::fromStdString(current_cheat->GetComments()));
+        ui->textCode->setPlainText(QString::fromStdString(current_cheat->GetCode()));
+    }
+
+    edited = false;
+    ui->buttonSave->setEnabled(false);
+    ui->buttonAddCheat->setEnabled(true);
+}
+
+void CheatDialog::OnAddCheat() {
+    if (edited && !CheckSaveCheat()) {
+        return;
+    }
+
+    int row = ui->tableCheats->rowCount();
+    ui->tableCheats->setRowCount(row + 1);
+    ui->tableCheats->setCurrentCell(row, 1);
+
+    // create a dummy item
+    ui->tableCheats->setItem(row, 1, new QTableWidgetItem(tr("[new cheat]")));
+    ui->tableCheats->setItem(row, 2, new QTableWidgetItem(""));
+    ui->lineName->setText("");
+    ui->lineName->setPlaceholderText(tr("[new cheat]"));
+    ui->textCode->setPlainText("");
+    ui->textNotes->setPlainText("");
+    ui->lineName->setEnabled(true);
+    ui->textCode->setEnabled(true);
+    ui->textNotes->setEnabled(true);
+    ui->buttonSave->setEnabled(true);
+    ui->buttonDelete->setEnabled(true);
+    ui->buttonAddCheat->setEnabled(false);
+
+    edited = false;
+    newly_created = true;
+    last_row = row;
+    last_col = 1;
 }
diff --git a/src/citra_qt/cheats.h b/src/citra_qt/cheats.h
index d532175ab..89c08b1c5 100644
--- a/src/citra_qt/cheats.h
+++ b/src/citra_qt/cheats.h
@@ -7,6 +7,10 @@
 #include <memory>
 #include <QDialog>
 
+namespace Cheats {
+class CheatBase;
+}
+
 namespace Ui {
 class CheatDialog;
 } // namespace Ui
@@ -19,12 +23,38 @@ public:
     ~CheatDialog();
 
 private:
-    std::unique_ptr<Ui::CheatDialog> ui;
-
+    /**
+     * Loads the cheats from the CheatEngine, and populates the table.
+     */
     void LoadCheats();
 
+    /**
+     * Pops up a message box asking if the user wants to save the current cheat.
+     * If the user selected Yes, attempts to save the current cheat.
+     * @return true if the user selected No, or if the cheat was saved successfully
+     *         false if the user selected Cancel, or if the user selected Yes but saving failed
+     */
+    bool CheckSaveCheat();
+
+    /**
+     * Saves the current cheat as the row-th cheat in the cheat list.
+     * @return true if the cheat is saved successfully, false otherwise
+     */
+    bool SaveCheat(int row);
+
+    void closeEvent(QCloseEvent* event) override;
+
 private slots:
     void OnCancel();
     void OnRowSelected(int row, int column);
     void OnCheckChanged(int state);
+    void OnTextEdited();
+    void OnDeleteCheat();
+    void OnAddCheat();
+
+private:
+    std::unique_ptr<Ui::CheatDialog> ui;
+    std::vector<std::shared_ptr<Cheats::CheatBase>> cheats;
+    bool edited = false, newly_created = false;
+    int last_row = -1, last_col = -1;
 };
diff --git a/src/citra_qt/cheats.ui b/src/citra_qt/cheats.ui
index 08c0da19f..b6bb35b9e 100644
--- a/src/citra_qt/cheats.ui
+++ b/src/citra_qt/cheats.ui
@@ -22,182 +22,192 @@
   <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/>
+  <layout class="QVBoxLayout">
+   <item>
+    <layout class="QHBoxLayout">
+     <item>
+      <widget class="QLabel" name="labelTitle">
+       <property name="font">
+        <font>
+         <pointsize>10</pointsize>
+        </font>
        </property>
-      </column>
-      <column>
        <property name="text">
-        <string>Name</string>
+        <string>Title ID:</string>
        </property>
-      </column>
-      <column>
+      </widget>
+     </item>
+     <item>
+      <spacer>
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="buttonAddCheat">
        <property name="text">
-        <string>Type</string>
+        <string>Add Cheat</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>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <layout class="QHBoxLayout">
+     <item>
+      <layout class="QVBoxLayout">
+       <item>
+        <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>
+       </item>
+       <item>
+        <widget class="QTableWidget" name="tableCheats">
+         <property name="editTriggers">
+          <set>QAbstractItemView::NoEditTriggers</set>
+         </property>
+         <property name="selectionBehavior">
+          <enum>QAbstractItemView::SelectRows</enum>
+         </property>
+         <property name="selectionMode">
+          <enum>QAbstractItemView::SingleSelection</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>
+     </item>
+     <item>
+      <layout class="QVBoxLayout">
+       <item>
+        <layout class="QHBoxLayout">
+         <item>
+          <spacer>
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+          </spacer>
+         </item>
+         <item>
+          <widget class="QPushButton" name="buttonSave">
+           <property name="text">
+            <string>Save</string>
+           </property>
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="buttonDelete">
+           <property name="text">
+            <string>Delete</string>
+           </property>
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <layout class="QVBoxLayout">
+         <item>
+          <layout class="QHBoxLayout">
+           <item>
+            <widget class="QLabel" name="labelName">
+             <property name="text">
+              <string>Name:</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QLineEdit" name="lineName"/>
+           </item>
+          </layout>
+         </item>
+         <item>
+          <widget class="QLabel" name="labelNotes">
+           <property name="text">
+            <string>Notes:</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPlainTextEdit" name="textNotes"/>
+         </item>
+         <item>
+          <widget class="QLabel" name="labelCode">
+           <property name="text">
+            <string>Code:</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPlainTextEdit" name="textCode"/>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <layout class="QHBoxLayout">
+     <item>
+      <spacer>
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="buttonClose">
+       <property name="text">
+        <string>Close</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
  </widget>
  <resources/>
  <connections/>