diff --git a/CMakeLists.txt b/CMakeLists.txt index baa37466..ac6cdfb8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -197,10 +197,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 +#include +#include +#include + +#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 #include #include +#include #include #include #include @@ -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 cheat; + std::function 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& cheat, const std::function& 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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 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(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(cheatList->itemWidget(item)); + entry->Remove(); +} diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 5c661119..1d7118bc 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -6,6 +6,8 @@ #include #include +#include "cheats.hpp" + MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent), screen(this) { setWindowTitle("Alber"); // Enable drop events for loading ROMs @@ -48,20 +50,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(); @@ -184,6 +189,7 @@ MainWindow::~MainWindow() { delete menuBar; delete aboutWindow; delete configWindow; + delete cheatsEditor; delete luaEditor; } @@ -233,6 +239,7 @@ void MainWindow::showAboutMenu() { } void MainWindow::openLuaEditor() { luaEditor->show(); } +void MainWindow::openCheatsEditor() { cheatsEditor->show(); } void MainWindow::dispatchMessage(const EmulatorMessage& message) { switch (message.type) { @@ -247,12 +254,29 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) { delete message.string.str; break; + case MessageType::EditCheat: { + u32 handle = message.cheat.c->handle; + const std::vector& cheat = message.cheat.c->cheat; + const std::function& 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; } } @@ -260,7 +284,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); }; @@ -273,6 +302,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; @@ -289,7 +323,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); }; @@ -302,6 +341,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; @@ -318,4 +363,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& cheat, const std::function& 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