diff --git a/CMakeLists.txt b/CMakeLists.txt
index 57b6db2f..05b74ce3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -198,10 +198,10 @@ set(RENDERER_SW_SOURCE_FILES src/core/renderer_sw/renderer_sw.cpp)
 if(NOT ANDROID)
     if(ENABLE_QT_GUI)
         set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp src/panda_qt/main_window.cpp src/panda_qt/about_window.cpp
-            src/panda_qt/config_window.cpp src/panda_qt/zep.cpp src/panda_qt/text_editor.cpp
+            src/panda_qt/config_window.cpp src/panda_qt/zep.cpp src/panda_qt/text_editor.cpp src/panda_qt/cheats_window.cpp
         )   
         set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp include/panda_qt/main_window.hpp include/panda_qt/about_window.hpp
-            include/panda_qt/config_window.hpp include/panda_qt/text_editor.hpp
+            include/panda_qt/config_window.hpp include/panda_qt/text_editor.hpp include/panda_qt/cheats_window.hpp
         )
 
         source_group("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES})
diff --git a/include/cheats.hpp b/include/cheats.hpp
index 2be25827..b90c080b 100644
--- a/include/cheats.hpp
+++ b/include/cheats.hpp
@@ -24,6 +24,7 @@ class Cheats {
 
 	Cheats(Memory& mem, HIDService& hid);
 	u32 addCheat(const Cheat& cheat);
+	u32 addCheat(const u8* data, size_t size);
 	void removeCheat(u32 id);
 	void enableCheat(u32 id);
 	void disableCheat(u32 id);
@@ -32,6 +33,7 @@ class Cheats {
 
 	void clear();
 	bool haveCheats() const { return cheatsLoaded; }
+	static constexpr u32 badCheatHandle = 0xFFFFFFFF;
 
   private:
 	ActionReplay ar;  // An ActionReplay cheat machine for executing CTRPF codes
diff --git a/include/panda_qt/cheats_window.hpp b/include/panda_qt/cheats_window.hpp
new file mode 100644
index 00000000..c82b2bd8
--- /dev/null
+++ b/include/panda_qt/cheats_window.hpp
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <QAction>
+#include <QWidget>
+#include <filesystem>
+#include <memory>
+
+#include "emulator.hpp"
+
+class QListWidget;
+
+class CheatsWindow final : public QWidget {
+	Q_OBJECT
+
+  public:
+	CheatsWindow(Emulator* emu, const std::filesystem::path& path, QWidget* parent = nullptr);
+	~CheatsWindow() = default;
+
+  private:
+	void addEntry();
+	void removeClicked();
+
+	QListWidget* cheatList;
+	std::filesystem::path cheatPath;
+	Emulator* emu;
+};
diff --git a/include/panda_qt/main_window.hpp b/include/panda_qt/main_window.hpp
index 7dfb91b7..c03cffbb 100644
--- a/include/panda_qt/main_window.hpp
+++ b/include/panda_qt/main_window.hpp
@@ -5,6 +5,7 @@
 #include <QtWidgets>
 #include <atomic>
 #include <filesystem>
+#include <functional>
 #include <mutex>
 #include <thread>
 #include <vector>
@@ -12,16 +13,36 @@
 #include "emulator.hpp"
 #include "panda_qt/about_window.hpp"
 #include "panda_qt/config_window.hpp"
+#include "panda_qt/cheats_window.hpp"
 #include "panda_qt/screen.hpp"
 #include "panda_qt/text_editor.hpp"
 #include "services/hid.hpp"
 
+struct CheatMessage {
+	u32 handle;
+	std::vector<uint8_t> cheat;
+	std::function<void(u32)> callback;
+};
+
 class MainWindow : public QMainWindow {
 	Q_OBJECT
 
   private:
 	// Types of messages we might send from the GUI thread to the emulator thread
-	enum class MessageType { LoadROM, Reset, Pause, Resume, TogglePause, DumpRomFS, PressKey, ReleaseKey, LoadLuaScript };
+	enum class MessageType {
+		LoadROM,
+		Reset,
+		Pause,
+		Resume,
+		TogglePause,
+		DumpRomFS,
+		PressKey,
+		ReleaseKey,
+		SetCirclePadX,
+		SetCirclePadY,
+		LoadLuaScript,
+		EditCheat,
+	};
 
 	// Tagged union representing our message queue messages
 	struct EmulatorMessage {
@@ -36,9 +57,17 @@ class MainWindow : public QMainWindow {
 				u32 key;
 			} key;
 
+			struct {
+				s16 value;
+			} circlepad;
+
 			struct {
 				std::string* str;
 			} string;
+
+			struct {
+				CheatMessage* c;
+			} cheat;
 		};
 	};
 
@@ -54,6 +83,7 @@ class MainWindow : public QMainWindow {
 	ScreenWidget screen;
 	AboutWindow* aboutWindow;
 	ConfigWindow* configWindow;
+	CheatsWindow* cheatsEditor;
 	TextEditorWindow* luaEditor;
 	QMenuBar* menuBar = nullptr;
 
@@ -63,6 +93,7 @@ class MainWindow : public QMainWindow {
 	void selectROM();
 	void dumpRomFS();
 	void openLuaEditor();
+	void openCheatsEditor();
 	void showAboutMenu();
 	void sendMessage(const EmulatorMessage& message);
 	void dispatchMessage(const EmulatorMessage& message);
@@ -78,4 +109,5 @@ class MainWindow : public QMainWindow {
 	void keyPressEvent(QKeyEvent* event) override;
 	void keyReleaseEvent(QKeyEvent* event) override;
 	void loadLuaScript(const std::string& code);
+	void editCheat(u32 handle, const std::vector<uint8_t>& cheat, const std::function<void(u32)>& callback);
 };
\ No newline at end of file
diff --git a/src/core/action_replay.cpp b/src/core/action_replay.cpp
index e8467425..1ef494a2 100644
--- a/src/core/action_replay.cpp
+++ b/src/core/action_replay.cpp
@@ -139,6 +139,65 @@ void ActionReplay::executeDType(const Cheat& cheat, u32 instruction) {
 	switch (instruction) {
 		case 0xD3000000: offset1 = cheat[pc++]; break;
 		case 0xD3000001: offset2 = cheat[pc++]; break;
+
+		case 0xD6000000:
+			write32(*activeOffset + cheat[pc++], u32(*activeData));
+			*activeOffset += 4;
+			break;
+
+		case 0xD6000001:
+			write32(*activeOffset + cheat[pc++], u32(data1));
+			*activeOffset += 4;
+			break;
+
+		case 0xD6000002:
+			write32(*activeOffset + cheat[pc++], u32(data2));
+			*activeOffset += 4;
+			break;
+
+		case 0xD7000000:
+			write16(*activeOffset + cheat[pc++], u16(*activeData));
+			*activeOffset += 2;
+			break;
+
+		case 0xD7000001:
+			write16(*activeOffset + cheat[pc++], u16(data1));
+			*activeOffset += 2;
+			break;
+
+		case 0xD7000002:
+			write16(*activeOffset + cheat[pc++], u16(data2));
+			*activeOffset += 2;
+			break;
+
+		case 0xD8000000:
+			write8(*activeOffset + cheat[pc++], u8(*activeData));
+			*activeOffset += 1;
+			break;
+
+		case 0xD8000001:
+			write8(*activeOffset + cheat[pc++], u8(data1));
+			*activeOffset += 1;
+			break;
+
+		case 0xD8000002:
+			write8(*activeOffset + cheat[pc++], u8(data2));
+			*activeOffset += 1;
+			break;
+
+
+		case 0xD9000000: *activeData = read32(cheat[pc++] + *activeOffset); break;
+		case 0xD9000001: data1 = read32(cheat[pc++] + *activeOffset); break;
+		case 0xD9000002: data2 = read32(cheat[pc++] + *activeOffset); break;
+
+		case 0xDA000000: *activeData = read16(cheat[pc++] + *activeOffset); break;
+		case 0xDA000001: data1 = read16(cheat[pc++] + *activeOffset); break;
+		case 0xDA000002: data2 = read16(cheat[pc++] + *activeOffset); break;
+
+		case 0xDB000000: *activeData = read8(cheat[pc++] + *activeOffset); break;
+		case 0xDB000001: data1 = read8(cheat[pc++] + *activeOffset); break;
+		case 0xDB000002: data2 = read8(cheat[pc++] + *activeOffset); break;
+
 		case 0xDC000000: *activeOffset += cheat[pc++]; break;
 
 		// DD000000 XXXXXXXX - if KEYPAD has value XXXXXXXX execute next block
diff --git a/src/core/cheats.cpp b/src/core/cheats.cpp
index 83e7cdc4..7b8b71c2 100644
--- a/src/core/cheats.cpp
+++ b/src/core/cheats.cpp
@@ -1,4 +1,5 @@
 #include "cheats.hpp"
+#include "swap.hpp"
 
 Cheats::Cheats(Memory& mem, HIDService& hid) : ar(mem, hid) { reset(); }
 
@@ -23,6 +24,27 @@ u32 Cheats::addCheat(const Cheat& cheat) {
 	return cheats.size() - 1;
 }
 
+u32 Cheats::addCheat(const u8* data, size_t size) {
+	if ((size % 8) != 0) {
+		return badCheatHandle;
+	}
+
+	Cheats::Cheat cheat;
+	cheat.enabled = true;
+	cheat.type = Cheats::CheatType::ActionReplay;
+
+	for (size_t i = 0; i < size; i += 8) {
+		auto read32 = [](const u8* ptr) { return (u32(ptr[3]) << 24) | (u32(ptr[2]) << 16) | (u32(ptr[1]) << 8) | u32(ptr[0]); };
+
+		// Data is passed to us in big endian so we bswap
+		u32 firstWord = Common::swap32(read32(data + i));
+		u32 secondWord = Common::swap32(read32(data + i + 4));
+		cheat.instructions.insert(cheat.instructions.end(), {firstWord, secondWord});
+	}
+
+	return addCheat(cheat);
+}
+
 void Cheats::removeCheat(u32 id) {
 	if (id >= cheats.size()) {
 		return;
diff --git a/src/hydra_core.cpp b/src/hydra_core.cpp
index d67ffe2f..acbf30a8 100644
--- a/src/hydra_core.cpp
+++ b/src/hydra_core.cpp
@@ -134,25 +134,7 @@ void HydraCore::setPollInputCallback(void (*callback)()) { pollInputCallback = c
 void HydraCore::setCheckButtonCallback(s32 (*callback)(u32 player, hydra::ButtonType button)) { checkButtonCallback = callback; }
 
 u32 HydraCore::addCheat(const u8* data, u32 size) {
-	// Every 3DS cheat is a multiple of 64 bits == 8 bytes
-	if ((size % 8) != 0) {
-		return hydra::BAD_CHEAT;
-	}
-
-	Cheats::Cheat cheat;
-	cheat.enabled = true;
-	cheat.type = Cheats::CheatType::ActionReplay;
-
-	for (u32 i = 0; i < size; i += 8) {
-		auto read32 = [](const u8* ptr) { return (u32(ptr[3]) << 24) | (u32(ptr[2]) << 16) | (u32(ptr[1]) << 8) | u32(ptr[0]); };
-
-		// Data is passed to us in big endian so we bswap
-		u32 firstWord = Common::swap32(read32(data + i));
-		u32 secondWord = Common::swap32(read32(data + i + 4));
-		cheat.instructions.insert(cheat.instructions.end(), {firstWord, secondWord});
-	}
-
-	return emulator->getCheats().addCheat(cheat);
+	return emulator->getCheats().addCheat(data, size);
 };
 
 void HydraCore::removeCheat(u32 id) { emulator->getCheats().removeCheat(id); }
diff --git a/src/panda_qt/cheats_window.cpp b/src/panda_qt/cheats_window.cpp
new file mode 100644
index 00000000..dbd251cc
--- /dev/null
+++ b/src/panda_qt/cheats_window.cpp
@@ -0,0 +1,268 @@
+#include "panda_qt/cheats_window.hpp"
+
+#include <QCheckBox>
+#include <QDialog>
+#include <QDialogButtonBox>
+#include <QLabel>
+#include <QLineEdit>
+#include <QListWidget>
+#include <QPushButton>
+#include <QTextEdit>
+#include <QVBoxLayout>
+#include <QTimer>
+#include <functional>
+
+#include "cheats.hpp"
+#include "emulator.hpp"
+#include "panda_qt/main_window.hpp"
+
+MainWindow* mainWindow = nullptr;
+
+struct CheatMetadata {
+	u32 handle = Cheats::badCheatHandle;
+	std::string name = "New cheat";
+	std::string code;
+	bool enabled = true;
+};
+
+void dispatchToMainThread(std::function<void()> callback) {
+    QTimer* timer = new QTimer();
+    timer->moveToThread(qApp->thread());
+    timer->setSingleShot(true);
+    QObject::connect(timer, &QTimer::timeout, [=]()
+    {
+        callback();
+        timer->deleteLater();
+    });
+    QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, 0));
+}
+
+class CheatEntryWidget : public QWidget {
+  public:
+	CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListWidget* parent);
+
+	void Update() {
+		name->setText(metadata.name.c_str());
+		enabled->setChecked(metadata.enabled);
+		update();
+	}
+
+	void Remove() {
+		emu->getCheats().removeCheat(metadata.handle);
+		cheatList->takeItem(cheatList->row(listItem));
+		deleteLater();
+	}
+
+	const CheatMetadata& getMetadata() { return metadata; }
+	void setMetadata(const CheatMetadata& metadata) { this->metadata = metadata; }
+
+  private:
+	void checkboxChanged(int state);
+	void editClicked();
+
+	Emulator* emu;
+	CheatMetadata metadata;
+	u32 handle;
+	QLabel* name;
+	QCheckBox* enabled;
+	QListWidget* cheatList;
+	QListWidgetItem* listItem;
+};
+
+class CheatEditDialog : public QDialog {
+  public:
+	CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry);
+
+	void accepted();
+	void rejected();
+
+  private:
+	Emulator* emu;
+	CheatEntryWidget& cheatEntry;
+	QTextEdit* codeEdit;
+	QLineEdit* nameEdit;
+};
+
+CheatEntryWidget::CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListWidget* parent)
+	: QWidget(), emu(emu), metadata(metadata), cheatList(parent) {
+	QHBoxLayout* layout = new QHBoxLayout;
+
+	enabled = new QCheckBox;
+	enabled->setChecked(metadata.enabled);
+
+	name = new QLabel(metadata.name.c_str());
+	QPushButton* buttonEdit = new QPushButton(tr("Edit"));
+
+	connect(enabled, &QCheckBox::stateChanged, this, &CheatEntryWidget::checkboxChanged);
+	connect(buttonEdit, &QPushButton::clicked, this, &CheatEntryWidget::editClicked);
+
+	layout->addWidget(enabled);
+	layout->addWidget(name);
+	layout->addWidget(buttonEdit);
+	setLayout(layout);
+
+	listItem = new QListWidgetItem;
+	listItem->setSizeHint(sizeHint());
+	parent->addItem(listItem);
+	parent->setItemWidget(listItem, this);
+}
+
+void CheatEntryWidget::checkboxChanged(int state) {
+	bool enabled = state == Qt::Checked;
+	if (metadata.handle == Cheats::badCheatHandle) {
+		printf("Cheat handle is bad, this shouldn't happen\n");
+		return;
+	}
+
+	if (enabled) {
+		emu->getCheats().enableCheat(metadata.handle);
+		metadata.enabled = true;
+	} else {
+		emu->getCheats().disableCheat(metadata.handle);
+		metadata.enabled = false;
+	}
+}
+
+void CheatEntryWidget::editClicked() {
+	CheatEditDialog* dialog = new CheatEditDialog(emu, *this);
+	dialog->show();
+}
+
+CheatEditDialog::CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry) : QDialog(), emu(emu), cheatEntry(cheatEntry) {
+	setAttribute(Qt::WA_DeleteOnClose);
+	setModal(true);
+
+	QVBoxLayout* layout = new QVBoxLayout;
+	const CheatMetadata& metadata = cheatEntry.getMetadata();
+	codeEdit = new QTextEdit;
+	nameEdit = new QLineEdit;
+	nameEdit->setText(metadata.name.c_str());
+	nameEdit->setPlaceholderText(tr("Cheat name"));
+	layout->addWidget(nameEdit);
+
+	QFont font;
+	font.setFamily("Courier");
+	font.setFixedPitch(true);
+	font.setPointSize(10);
+	codeEdit->setFont(font);
+
+	if (metadata.code.size() != 0) {
+		// Nicely format it like so:
+		// 01234567 89ABCDEF
+		// 01234567 89ABCDEF
+		std::string formattedCode;
+		for (size_t i = 0; i < metadata.code.size(); i += 2) {
+			if (i != 0) {
+				if (i % 8 == 0 && i % 16 != 0) {
+					formattedCode += " ";
+				} else if (i % 16 == 0) {
+					formattedCode += "\n";
+				}
+			}
+
+			formattedCode += metadata.code[i];
+			formattedCode += metadata.code[i + 1];
+		}
+		codeEdit->setText(formattedCode.c_str());
+	}
+
+	layout->addWidget(codeEdit);
+	setLayout(layout);
+
+	auto buttons = QDialogButtonBox::Ok | QDialogButtonBox::Cancel;
+	QDialogButtonBox* buttonBox = new QDialogButtonBox(buttons);
+	layout->addWidget(buttonBox);
+
+	connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
+	connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
+	connect(this, &QDialog::rejected, this, &CheatEditDialog::rejected);
+	connect(this, &QDialog::accepted, this, &CheatEditDialog::accepted);
+}
+
+void CheatEditDialog::accepted() {
+	QString code = codeEdit->toPlainText();
+	code.replace(QRegularExpression("[^0-9a-fA-F]"), "");
+
+	CheatMetadata metadata = cheatEntry.getMetadata();
+	metadata.name = nameEdit->text().toStdString();
+	metadata.code = code.toStdString();
+	cheatEntry.setMetadata(metadata);
+
+	std::vector<u8> bytes;
+	for (size_t i = 0; i < metadata.code.size(); i += 2) {
+		std::string hex = metadata.code.substr(i, 2);
+		bytes.push_back((u8)std::stoul(hex, nullptr, 16));
+	}
+
+	mainWindow->editCheat(cheatEntry.getMetadata().handle, bytes, [this](u32 handle) {
+		dispatchToMainThread([this, handle]() {
+			if (handle == Cheats::badCheatHandle) {
+				cheatEntry.Remove();
+				return;
+			} else {
+				CheatMetadata metadata = cheatEntry.getMetadata();
+				metadata.handle = handle;
+				cheatEntry.setMetadata(metadata);
+				cheatEntry.Update();
+			}
+		});
+	});
+}
+
+void CheatEditDialog::rejected() {
+	bool isEditing = cheatEntry.getMetadata().handle != Cheats::badCheatHandle;
+	if (!isEditing) {
+		// Was adding a cheat but user pressed cancel
+		cheatEntry.Remove();
+	}
+}
+
+CheatsWindow::CheatsWindow(Emulator* emu, const std::filesystem::path& cheatPath, QWidget* parent)
+	: QWidget(parent, Qt::Window), emu(emu), cheatPath(cheatPath) {
+    mainWindow = static_cast<MainWindow*>(parent);
+
+	QVBoxLayout* layout = new QVBoxLayout;
+	layout->setContentsMargins(6, 6, 6, 6);
+	setLayout(layout);
+
+	cheatList = new QListWidget;
+	layout->addWidget(cheatList);
+
+	QWidget* buttonBox = new QWidget;
+	QHBoxLayout* buttonLayout = new QHBoxLayout;
+
+	QPushButton* buttonAdd = new QPushButton(tr("Add"));
+	QPushButton* buttonRemove = new QPushButton(tr("Remove"));
+
+	connect(buttonAdd, &QPushButton::clicked, this, &CheatsWindow::addEntry);
+	connect(buttonRemove, &QPushButton::clicked, this, &CheatsWindow::removeClicked);
+
+	buttonLayout->addWidget(buttonAdd);
+	buttonLayout->addWidget(buttonRemove);
+	buttonBox->setLayout(buttonLayout);
+
+	layout->addWidget(buttonBox);
+
+	// TODO: load cheats from saved cheats per game
+	// for (const CheatMetadata& metadata : getSavedCheats())
+	// {
+	//     new CheatEntryWidget(emu, metadata, cheatList);
+	// }
+}
+
+void CheatsWindow::addEntry() {
+	// CheatEntryWidget is added to the list when it's created
+	CheatEntryWidget* entry = new CheatEntryWidget(emu, {Cheats::badCheatHandle, "New cheat", "", true}, cheatList);
+	CheatEditDialog* dialog = new CheatEditDialog(emu, *entry);
+	dialog->show();
+}
+
+void CheatsWindow::removeClicked() {
+	QListWidgetItem* item = cheatList->currentItem();
+	if (item == nullptr) {
+		return;
+	}
+
+	CheatEntryWidget* entry = static_cast<CheatEntryWidget*>(cheatList->itemWidget(item));
+	entry->Remove();
+}
diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp
index 216904bc..19827419 100644
--- a/src/panda_qt/main_window.cpp
+++ b/src/panda_qt/main_window.cpp
@@ -6,6 +6,7 @@
 #include <cstdio>
 #include <fstream>
 
+#include "cheats.hpp"
 #include "imgui.h"
 #include "backends/imgui_impl_sdl2.h"
 #include "backends/imgui_impl_opengl3.h"
@@ -56,20 +57,23 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
 
 	auto dumpRomFSAction = toolsMenu->addAction(tr("Dump RomFS"));
 	auto luaEditorAction = toolsMenu->addAction(tr("Open Lua Editor"));
+	auto cheatsEditorAction = toolsMenu->addAction(tr("Open Cheats Editor"));
 	connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS);
 	connect(luaEditorAction, &QAction::triggered, this, &MainWindow::openLuaEditor);
+	connect(cheatsEditorAction, &QAction::triggered, this, &MainWindow::openCheatsEditor);
 
 	auto aboutAction = aboutMenu->addAction(tr("About Panda3DS"));
 	connect(aboutAction, &QAction::triggered, this, &MainWindow::showAboutMenu);
 
+	emu = new Emulator();
+	emu->setOutputSize(screen.surfaceWidth, screen.surfaceHeight);
+
 	// Set up misc objects
 	aboutWindow = new AboutWindow(nullptr);
 	configWindow = new ConfigWindow(this);
+	cheatsEditor = new CheatsWindow(emu, {}, this);
 	luaEditor = new TextEditorWindow(this, "script.lua", "");
 
-	emu = new Emulator();
-	emu->setOutputSize(screen.surfaceWidth, screen.surfaceHeight);
-
 	auto args = QCoreApplication::arguments();
 	if (args.size() > 1) {
 		auto romPath = std::filesystem::current_path() / args.at(1).toStdU16String();
@@ -293,6 +297,7 @@ MainWindow::~MainWindow() {
 	delete menuBar;
 	delete aboutWindow;
 	delete configWindow;
+	delete cheatsEditor;
 	delete luaEditor;
 }
 
@@ -342,6 +347,7 @@ void MainWindow::showAboutMenu() {
 }
 
 void MainWindow::openLuaEditor() { luaEditor->show(); }
+void MainWindow::openCheatsEditor() { cheatsEditor->show(); }
 
 void MainWindow::dispatchMessage(const EmulatorMessage& message) {
 	switch (message.type) {
@@ -356,12 +362,29 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) {
 			delete message.string.str;
 			break;
 
+		case MessageType::EditCheat: {
+			u32 handle = message.cheat.c->handle;
+			const std::vector<uint8_t>& cheat = message.cheat.c->cheat;
+			const std::function<void(u32)>& callback = message.cheat.c->callback;
+			bool isEditing = handle != Cheats::badCheatHandle;
+			if (isEditing) {
+				emu->getCheats().removeCheat(handle);
+				u32 handle = emu->getCheats().addCheat(cheat.data(), cheat.size());
+			} else {
+				u32 handle = emu->getCheats().addCheat(cheat.data(), cheat.size());
+				callback(handle);
+			}
+			delete message.cheat.c;
+		} break;
+
 		case MessageType::Pause: emu->pause(); break;
 		case MessageType::Resume: emu->resume(); break;
 		case MessageType::TogglePause: emu->togglePause(); break;
 		case MessageType::Reset: emu->reset(Emulator::ReloadOption::Reload); break;
 		case MessageType::PressKey: emu->getServiceManager().getHID().pressKey(message.key.key); break;
 		case MessageType::ReleaseKey: emu->getServiceManager().getHID().releaseKey(message.key.key); break;
+		case MessageType::SetCirclePadX: emu->getServiceManager().getHID().setCirclepadX(message.circlepad.value); break;
+		case MessageType::SetCirclePadY: emu->getServiceManager().getHID().setCirclepadY(message.circlepad.value); break;
 	}
 }
 
@@ -369,7 +392,12 @@ void MainWindow::keyPressEvent(QKeyEvent* event) {
 	auto pressKey = [this](u32 key) {
 		EmulatorMessage message{.type = MessageType::PressKey};
 		message.key.key = key;
+		sendMessage(message);
+	};
 
+	auto setCirclePad = [this](MessageType type, s16 value) {
+		EmulatorMessage message{.type = type};
+		message.circlepad.value = value;
 		sendMessage(message);
 	};
 
@@ -382,6 +410,11 @@ void MainWindow::keyPressEvent(QKeyEvent* event) {
 		case Qt::Key_Q: pressKey(HID::Keys::L); break;
 		case Qt::Key_P: pressKey(HID::Keys::R); break;
 
+		case Qt::Key_W: setCirclePad(MessageType::SetCirclePadY, 0x9C); break;
+		case Qt::Key_A: setCirclePad(MessageType::SetCirclePadX, -0x9C); break;
+		case Qt::Key_S: setCirclePad(MessageType::SetCirclePadY, -0x9C); break;
+		case Qt::Key_D: setCirclePad(MessageType::SetCirclePadX, 0x9C); break;
+
 		case Qt::Key_Right: pressKey(HID::Keys::Right); break;
 		case Qt::Key_Left: pressKey(HID::Keys::Left); break;
 		case Qt::Key_Up: pressKey(HID::Keys::Up); break;
@@ -398,7 +431,12 @@ void MainWindow::keyReleaseEvent(QKeyEvent* event) {
 	auto releaseKey = [this](u32 key) {
 		EmulatorMessage message{.type = MessageType::ReleaseKey};
 		message.key.key = key;
+		sendMessage(message);
+	};
 
+	auto releaseCirclePad = [this](MessageType type) {
+		EmulatorMessage message{.type = type};
+		message.circlepad.value = 0;
 		sendMessage(message);
 	};
 
@@ -411,6 +449,12 @@ void MainWindow::keyReleaseEvent(QKeyEvent* event) {
 		case Qt::Key_Q: releaseKey(HID::Keys::L); break;
 		case Qt::Key_P: releaseKey(HID::Keys::R); break;
 
+		case Qt::Key_W:
+		case Qt::Key_S: releaseCirclePad(MessageType::SetCirclePadY); break;
+
+		case Qt::Key_A:
+		case Qt::Key_D: releaseCirclePad(MessageType::SetCirclePadX); break;
+
 		case Qt::Key_Right: releaseKey(HID::Keys::Right); break;
 		case Qt::Key_Left: releaseKey(HID::Keys::Left); break;
 		case Qt::Key_Up: releaseKey(HID::Keys::Up); break;
@@ -427,4 +471,16 @@ void MainWindow::loadLuaScript(const std::string& code) {
 	// Make a copy of the code on the heap to send via the message queue
 	message.string.str = new std::string(code);
 	sendMessage(message);
+}
+
+void MainWindow::editCheat(u32 handle, const std::vector<uint8_t>& cheat, const std::function<void(u32)>& callback) {
+	EmulatorMessage message{.type = MessageType::EditCheat};
+
+	CheatMessage* c = new CheatMessage();
+	c->handle = handle;
+	c->cheat = cheat;
+	c->callback = callback;
+
+	message.cheat.c = c;
+	sendMessage(message);
 }
\ No newline at end of file
diff --git a/src/panda_qt/text_editor.cpp b/src/panda_qt/text_editor.cpp
index c189c2ce..a31a829f 100644
--- a/src/panda_qt/text_editor.cpp
+++ b/src/panda_qt/text_editor.cpp
@@ -16,8 +16,8 @@ TextEditorWindow::TextEditorWindow(QWidget* parent, const std::string& filename,
 	ZepReplExCommand::Register(zepWidget.GetEditor(), &replProvider);
 
 	// Default to standard mode instead of vim mode, initialize text box
-	zepWidget.GetEditor().SetGlobalMode(Zep::ZepMode_Standard::StaticName());
 	zepWidget.GetEditor().InitWithText(filename, initialText);
+	zepWidget.GetEditor().SetGlobalMode(Zep::ZepMode_Standard::StaticName());
 
 	// Layout for widgets
 	QVBoxLayout* mainLayout = new QVBoxLayout();
@@ -41,4 +41,4 @@ TextEditorWindow::TextEditorWindow(QWidget* parent, const std::string& filename,
 
 	mainLayout->addWidget(button);
 	mainLayout->addWidget(&zepWidget);
-}
\ No newline at end of file
+}