Use message queues

This commit is contained in:
offtkp 2024-01-27 18:20:42 +02:00
parent 63f54478f0
commit a473a34794
7 changed files with 266 additions and 269 deletions

View file

@ -14,7 +14,7 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION
endif() endif()
if(NOT CMAKE_BUILD_TYPE) if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE RelWithDebInfo) set(CMAKE_BUILD_TYPE Release)
endif() endif()
project(Alber) project(Alber)

View file

@ -9,6 +9,8 @@
// Forward-declare this since it's just passed and we don't want to include memory.hpp and increase compile time // Forward-declare this since it's just passed and we don't want to include memory.hpp and increase compile time
class Memory; class Memory;
constexpr u32 badCheatHandle = 0xFFFFFFFF;
class Cheats { class Cheats {
public: public:
enum class CheatType { enum class CheatType {
@ -24,6 +26,7 @@ class Cheats {
Cheats(Memory& mem, HIDService& hid); Cheats(Memory& mem, HIDService& hid);
u32 addCheat(const Cheat& cheat); u32 addCheat(const Cheat& cheat);
u32 addCheat(const u8* data, size_t size);
void removeCheat(u32 id); void removeCheat(u32 id);
void enableCheat(u32 id); void enableCheat(u32 id);
void disableCheat(u32 id); void disableCheat(u32 id);

View file

@ -1,15 +1,15 @@
#pragma once #pragma once
#include <filesystem>
#include <memory>
#include <QAction> #include <QAction>
#include <QWidget> #include <QWidget>
#include <filesystem>
#include <memory>
#include "emulator.hpp" #include "emulator.hpp"
class QListWidget; class QListWidget;
class CheatsWindow final : public QWidget class CheatsWindow final : public QWidget {
{
Q_OBJECT Q_OBJECT
public: public:

View file

@ -17,12 +17,19 @@
#include "panda_qt/text_editor.hpp" #include "panda_qt/text_editor.hpp"
#include "services/hid.hpp" #include "services/hid.hpp"
struct CheatMessage
{
u32 handle;
std::vector<uint8_t> cheat;
std::function<void(u32)> callback;
};
class MainWindow : public QMainWindow { class MainWindow : public QMainWindow {
Q_OBJECT Q_OBJECT
private: private:
// Types of messages we might send from the GUI thread to the emulator thread // 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, LoadLuaScript, EditCheat };
// Tagged union representing our message queue messages // Tagged union representing our message queue messages
struct EmulatorMessage { struct EmulatorMessage {
@ -40,6 +47,10 @@ class MainWindow : public QMainWindow {
struct { struct {
std::string* str; std::string* str;
} string; } string;
struct {
CheatMessage* c;
} cheat;
}; };
}; };
@ -59,8 +70,6 @@ class MainWindow : public QMainWindow {
TextEditorWindow* luaEditor; TextEditorWindow* luaEditor;
QMenuBar* menuBar = nullptr; QMenuBar* menuBar = nullptr;
QAction* cheatsEditorAction = nullptr;
void swapEmuBuffer(); void swapEmuBuffer();
void emuThreadMainLoop(); void emuThreadMainLoop();
void selectLuaFile(); void selectLuaFile();
@ -83,4 +92,5 @@ class MainWindow : public QMainWindow {
void keyPressEvent(QKeyEvent* event) override; void keyPressEvent(QKeyEvent* event) override;
void keyReleaseEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override;
void loadLuaScript(const std::string& code); void loadLuaScript(const std::string& code);
void editCheat(u32 handle, const std::vector<uint8_t>& cheat, const std::function<void(u32)>& callback);
}; };

View file

@ -1,4 +1,5 @@
#include "cheats.hpp" #include "cheats.hpp"
#include "swap.hpp"
Cheats::Cheats(Memory& mem, HIDService& hid) : ar(mem, hid) { reset(); } Cheats::Cheats(Memory& mem, HIDService& hid) : ar(mem, hid) { reset(); }
@ -23,6 +24,27 @@ u32 Cheats::addCheat(const Cheat& cheat) {
return cheats.size() - 1; 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) { void Cheats::removeCheat(u32 id) {
if (id >= cheats.size()) { if (id >= cheats.size()) {
return; return;

View file

@ -1,79 +1,47 @@
#include "panda_qt/cheats_window.hpp" #include "panda_qt/cheats_window.hpp"
#include <qlistwidget.h>
#include <QVBoxLayout>
#include <QListWidget>
#include <QPushButton>
#include <QLabel>
#include <QCheckBox> #include <QCheckBox>
#include <QDialog> #include <QDialog>
#include <QLineEdit>
#include <QTextEdit>
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QLabel>
#include <QLineEdit>
#include <QListWidget>
#include <QPushButton>
#include <QTextEdit>
#include <QVBoxLayout>
#include "cheats.hpp" #include "cheats.hpp"
#include "emulator.hpp" #include "emulator.hpp"
#include "panda_qt/main_window.hpp"
using CheatHandle = u32; MainWindow* mainWindow = nullptr;
CheatHandle BAD_CHEAT = 0xFFFFFFFF; struct CheatMetadata {
u32 handle = badCheatHandle;
struct CheatMetadata
{
CheatHandle handle = BAD_CHEAT;
std::string name = "New cheat"; std::string name = "New cheat";
std::string code; std::string code;
bool enabled = true; bool enabled = true;
}; };
u32 addCheat(Emulator* emu, u8* data, size_t size) class CheatEntryWidget : public QWidget {
{
if ((size % 8) != 0) {
return BAD_CHEAT;
}
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 emu->getCheats().addCheat(cheat);
}
class CheatEntryWidget : public QWidget
{
public: public:
CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListWidget* parent); CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListWidget* parent);
void Update() void Update() {
{
name->setText(metadata.name.c_str()); name->setText(metadata.name.c_str());
enabled->setChecked(metadata.enabled); enabled->setChecked(metadata.enabled);
update(); update();
} }
void Remove() void Remove() {
{
emu->getCheats().removeCheat(metadata.handle); emu->getCheats().removeCheat(metadata.handle);
cheatList->takeItem(cheatList->row(listItem)); cheatList->takeItem(cheatList->row(listItem));
deleteLater(); deleteLater();
} }
const CheatMetadata& GetMetadata() const CheatMetadata& GetMetadata() { return metadata; }
{
return metadata;
}
void SetMetadata(const CheatMetadata& metadata) void SetMetadata(const CheatMetadata& metadata) { this->metadata = metadata; }
{
this->metadata = metadata;
}
private: private:
void checkboxChanged(int state); void checkboxChanged(int state);
@ -88,13 +56,13 @@ private:
QListWidgetItem* listItem; QListWidgetItem* listItem;
}; };
class CheatEditDialog : public QDialog class CheatEditDialog : public QDialog {
{
public: public:
CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry); CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry);
void accepted(); void accepted();
void rejected(); void rejected();
private: private:
Emulator* emu; Emulator* emu;
CheatEntryWidget& cheatEntry; CheatEntryWidget& cheatEntry;
@ -103,8 +71,7 @@ private:
}; };
CheatEntryWidget::CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListWidget* parent) CheatEntryWidget::CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListWidget* parent)
: QWidget(), emu(emu), metadata(metadata), cheatList(parent) : QWidget(), emu(emu), metadata(metadata), cheatList(parent) {
{
QHBoxLayout* layout = new QHBoxLayout; QHBoxLayout* layout = new QHBoxLayout;
enabled = new QCheckBox; enabled = new QCheckBox;
@ -127,35 +94,28 @@ CheatEntryWidget::CheatEntryWidget(Emulator* emu, CheatMetadata metadata, QListW
parent->setItemWidget(listItem, this); parent->setItemWidget(listItem, this);
} }
void CheatEntryWidget::checkboxChanged(int state) void CheatEntryWidget::checkboxChanged(int state) {
{
bool enabled = state == Qt::Checked; bool enabled = state == Qt::Checked;
if (metadata.handle == BAD_CHEAT) if (metadata.handle == badCheatHandle) {
{
printf("Cheat handle is bad, this shouldn't happen\n"); printf("Cheat handle is bad, this shouldn't happen\n");
return; return;
} }
if (enabled) if (enabled) {
{
emu->getCheats().enableCheat(metadata.handle); emu->getCheats().enableCheat(metadata.handle);
metadata.enabled = true; metadata.enabled = true;
} } else {
else
{
emu->getCheats().disableCheat(metadata.handle); emu->getCheats().disableCheat(metadata.handle);
metadata.enabled = false; metadata.enabled = false;
} }
} }
void CheatEntryWidget::editClicked() void CheatEntryWidget::editClicked() {
{
CheatEditDialog* dialog = new CheatEditDialog(emu, *this); CheatEditDialog* dialog = new CheatEditDialog(emu, *this);
dialog->show(); dialog->show();
} }
CheatEditDialog::CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry) : QDialog(), emu(emu), cheatEntry(cheatEntry) CheatEditDialog::CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry) : QDialog(), emu(emu), cheatEntry(cheatEntry) {
{
setAttribute(Qt::WA_DeleteOnClose); setAttribute(Qt::WA_DeleteOnClose);
setModal(true); setModal(true);
@ -173,21 +133,16 @@ CheatEditDialog::CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry) :
font.setPointSize(10); font.setPointSize(10);
codeEdit->setFont(font); codeEdit->setFont(font);
if (metadata.code.size() != 0) if (metadata.code.size() != 0) {
{
// Nicely format it like so: // Nicely format it like so:
// 01234567 89ABCDEF // 01234567 89ABCDEF
// 01234567 89ABCDEF // 01234567 89ABCDEF
std::string formattedCode; std::string formattedCode;
for (size_t i = 0; i < metadata.code.size(); i += 2) for (size_t i = 0; i < metadata.code.size(); i += 2) {
{
if (i != 0) { if (i != 0) {
if (i % 8 == 0 && i % 16 != 0) if (i % 8 == 0 && i % 16 != 0) {
{
formattedCode += " "; formattedCode += " ";
} } else if (i % 16 == 0) {
else if (i % 16 == 0)
{
formattedCode += "\n"; formattedCode += "\n";
} }
} }
@ -211,58 +166,41 @@ CheatEditDialog::CheatEditDialog(Emulator* emu, CheatEntryWidget& cheatEntry) :
connect(this, &QDialog::accepted, this, &CheatEditDialog::accepted); connect(this, &QDialog::accepted, this, &CheatEditDialog::accepted);
} }
void CheatEditDialog::accepted() void CheatEditDialog::accepted() {
{
QString code = codeEdit->toPlainText(); QString code = codeEdit->toPlainText();
code.replace(QRegularExpression("[^0-9a-fA-F]"), ""); code.replace(QRegularExpression("[^0-9a-fA-F]"), "");
CheatMetadata metadata = cheatEntry.GetMetadata(); CheatMetadata metadata = cheatEntry.GetMetadata();
bool isEditing = metadata.handle != BAD_CHEAT;
metadata.name = nameEdit->text().toStdString(); metadata.name = nameEdit->text().toStdString();
metadata.code = code.toStdString(); metadata.code = code.toStdString();
std::vector<uint8_t> bytes; std::vector<uint8_t> bytes;
for (size_t i = 0; i < metadata.code.size(); i += 2) for (size_t i = 0; i < metadata.code.size(); i += 2) {
{
std::string hex = metadata.code.substr(i, 2); std::string hex = metadata.code.substr(i, 2);
bytes.push_back((uint8_t)std::stoul(hex, nullptr, 16)); bytes.push_back((uint8_t)std::stoul(hex, nullptr, 16));
} }
if (isEditing) mainWindow->editCheat(cheatEntry.GetMetadata().handle, bytes, [this](u32 handle) {
{ CheatMetadata metadata = cheatEntry.GetMetadata();
emu->getCheats().removeCheat(metadata.handle);
u32 handle = addCheat(emu, bytes.data(), bytes.size());
metadata.handle = handle; metadata.handle = handle;
cheatEntry.SetMetadata(metadata); cheatEntry.SetMetadata(metadata);
}
else
{
if (metadata.name.empty())
{
metadata.name = tr("Cheat code").toStdString();
}
u32 handle = addCheat(emu, bytes.data(), bytes.size());
metadata.handle = handle;
cheatEntry.SetMetadata(metadata);
}
cheatEntry.Update(); cheatEntry.Update();
});
} }
void CheatEditDialog::rejected() void CheatEditDialog::rejected() {
{ bool isEditing = cheatEntry.GetMetadata().handle != badCheatHandle;
bool isEditing = cheatEntry.GetMetadata().handle != BAD_CHEAT;
if (!isEditing) if (!isEditing) {
{
// Was adding a cheat but pressed cancel // Was adding a cheat but pressed cancel
cheatEntry.Remove(); cheatEntry.Remove();
} }
} }
CheatsWindow::CheatsWindow(Emulator* emu, const std::filesystem::path& cheatPath, QWidget* parent) CheatsWindow::CheatsWindow(Emulator* emu, const std::filesystem::path& cheatPath, QWidget* parent)
: QWidget(parent, Qt::Window), emu(emu), cheatPath(cheatPath) : QWidget(parent, Qt::Window), emu(emu), cheatPath(cheatPath) {
{ mainWindow = static_cast<MainWindow*>(parent);
QVBoxLayout* layout = new QVBoxLayout; QVBoxLayout* layout = new QVBoxLayout;
layout->setContentsMargins(6, 6, 6, 6); layout->setContentsMargins(6, 6, 6, 6);
setLayout(layout); setLayout(layout);
@ -292,19 +230,16 @@ CheatsWindow::CheatsWindow(Emulator* emu, const std::filesystem::path& cheatPath
// } // }
} }
void CheatsWindow::addEntry() void CheatsWindow::addEntry() {
{
// CheatEntryWidget is added to the list when it's created // CheatEntryWidget is added to the list when it's created
CheatEntryWidget* entry = new CheatEntryWidget(emu, {BAD_CHEAT, "New cheat", "", true}, cheatList); CheatEntryWidget* entry = new CheatEntryWidget(emu, {badCheatHandle, "New cheat", "", true}, cheatList);
CheatEditDialog* dialog = new CheatEditDialog(emu, *entry); CheatEditDialog* dialog = new CheatEditDialog(emu, *entry);
dialog->show(); dialog->show();
} }
void CheatsWindow::removeClicked() void CheatsWindow::removeClicked() {
{
QListWidgetItem* item = cheatList->currentItem(); QListWidgetItem* item = cheatList->currentItem();
if (item == nullptr) if (item == nullptr) {
{
return; return;
} }

View file

@ -6,6 +6,8 @@
#include <cstdio> #include <cstdio>
#include <fstream> #include <fstream>
#include "cheats.hpp"
MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent), screen(this) { MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent), screen(this) {
setWindowTitle("Alber"); setWindowTitle("Alber");
// Enable drop events for loading ROMs // Enable drop events for loading ROMs
@ -48,8 +50,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
auto dumpRomFSAction = toolsMenu->addAction(tr("Dump RomFS")); auto dumpRomFSAction = toolsMenu->addAction(tr("Dump RomFS"));
auto luaEditorAction = toolsMenu->addAction(tr("Open Lua Editor")); auto luaEditorAction = toolsMenu->addAction(tr("Open Lua Editor"));
cheatsEditorAction = toolsMenu->addAction(tr("Open Cheats Editor")); auto cheatsEditorAction = toolsMenu->addAction(tr("Open Cheats Editor"));
cheatsEditorAction->setEnabled(false);
connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS); connect(dumpRomFSAction, &QAction::triggered, this, &MainWindow::dumpRomFS);
connect(luaEditorAction, &QAction::triggered, this, &MainWindow::openLuaEditor); connect(luaEditorAction, &QAction::triggered, this, &MainWindow::openLuaEditor);
connect(cheatsEditorAction, &QAction::triggered, this, &MainWindow::openCheatsEditor); connect(cheatsEditorAction, &QAction::triggered, this, &MainWindow::openCheatsEditor);
@ -63,7 +64,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent)
// Set up misc objects // Set up misc objects
aboutWindow = new AboutWindow(nullptr); aboutWindow = new AboutWindow(nullptr);
configWindow = new ConfigWindow(this); configWindow = new ConfigWindow(this);
cheatsEditor = new CheatsWindow(emu, {}); cheatsEditor = new CheatsWindow(emu, {}, this);
luaEditor = new TextEditorWindow(this, "script.lua", ""); luaEditor = new TextEditorWindow(this, "script.lua", "");
auto args = QCoreApplication::arguments(); auto args = QCoreApplication::arguments();
@ -245,7 +246,6 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) {
switch (message.type) { switch (message.type) {
case MessageType::LoadROM: case MessageType::LoadROM:
emu->loadROM(*message.path.p); emu->loadROM(*message.path.p);
cheatsEditorAction->setEnabled(true);
// Clean up the allocated path // Clean up the allocated path
delete message.path.p; delete message.path.p;
break; break;
@ -255,6 +255,21 @@ void MainWindow::dispatchMessage(const EmulatorMessage& message) {
delete message.string.str; delete message.string.str;
break; 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 != 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::Pause: emu->pause(); break;
case MessageType::Resume: emu->resume(); break; case MessageType::Resume: emu->resume(); break;
case MessageType::TogglePause: emu->togglePause(); break; case MessageType::TogglePause: emu->togglePause(); break;
@ -327,3 +342,15 @@ void MainWindow::loadLuaScript(const std::string& code) {
message.string.str = new std::string(code); message.string.str = new std::string(code);
sendMessage(message); 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);
}