From 62bfe92069a23a00fd1d801d3a6ee327f6cb0a48 Mon Sep 17 00:00:00 2001 From: Rosalie Date: Tue, 9 Jul 2024 14:47:00 +0200 Subject: [PATCH] WIP netplay --- .github/workflows/build.yml | 3 +- CMakeLists.txt | 1 + Source/3rdParty/CMakeLists.txt | 3 +- Source/RMG-Core/CMakeLists.txt | 7 + Source/RMG-Core/Core.hpp | 1 + Source/RMG-Core/Emulation.cpp | 65 ++- Source/RMG-Core/Emulation.hpp | 2 +- Source/RMG-Core/Netplay.cpp | 102 ++++ Source/RMG-Core/Netplay.hpp | 24 + Source/RMG-Core/Settings/Settings.cpp | 9 + Source/RMG-Core/Settings/SettingsID.hpp | 4 + Source/RMG/CMakeLists.txt | 23 + Source/RMG/Thread/EmulationThread.cpp | 17 +- Source/RMG/Thread/EmulationThread.hpp | 4 + .../Netplay/CreateNetplaySessionDialog.cpp | 302 ++++++++++++ .../Netplay/CreateNetplaySessionDialog.hpp | 69 +++ .../Netplay/CreateNetplaySessionDialog.ui | 147 ++++++ .../Dialog/Netplay/NetplayCommon.cpp | 110 +++++ .../Dialog/Netplay/NetplayCommon.hpp | 35 ++ .../Netplay/NetplaySessionBrowserDialog.cpp | 436 ++++++++++++++++++ .../Netplay/NetplaySessionBrowserDialog.hpp | 73 +++ .../Netplay/NetplaySessionBrowserDialog.ui | 129 ++++++ .../Dialog/Netplay/NetplaySessionDialog.cpp | 149 ++++++ .../Dialog/Netplay/NetplaySessionDialog.hpp | 57 +++ .../Dialog/Netplay/NetplaySessionDialog.ui | 164 +++++++ .../Netplay/NetplaySessionPasswordDialog.cpp | 41 ++ .../Netplay/NetplaySessionPasswordDialog.hpp | 41 ++ .../Netplay/NetplaySessionPasswordDialog.ui | 82 ++++ .../UserInterface/Dialog/SettingsDialog.cpp | 56 ++- .../UserInterface/Dialog/SettingsDialog.hpp | 6 +- .../UserInterface/Dialog/SettingsDialog.ui | 62 +++ Source/RMG/UserInterface/MainWindow.cpp | 117 ++++- Source/RMG/UserInterface/MainWindow.hpp | 29 +- Source/RMG/UserInterface/MainWindow.ui | 235 +++++----- .../Resource/icons/black/svg/plug-line.svg | 1 + .../Resource/icons/black/svg/server-line.svg | 1 + .../Resource/icons/white/svg/plug-line.svg | 1 + .../Resource/icons/white/svg/server-line.svg | 1 + Source/RMG/UserInterface/UIResources.qrc | 4 + .../Widget/RomBrowser/RomBrowserWidget.cpp | 20 + .../Widget/RomBrowser/RomBrowserWidget.hpp | 3 + Source/Script/Build.sh | 2 +- 42 files changed, 2450 insertions(+), 188 deletions(-) create mode 100644 Source/RMG-Core/Netplay.cpp create mode 100644 Source/RMG-Core/Netplay.hpp create mode 100644 Source/RMG/UserInterface/Dialog/Netplay/CreateNetplaySessionDialog.cpp create mode 100644 Source/RMG/UserInterface/Dialog/Netplay/CreateNetplaySessionDialog.hpp create mode 100644 Source/RMG/UserInterface/Dialog/Netplay/CreateNetplaySessionDialog.ui create mode 100644 Source/RMG/UserInterface/Dialog/Netplay/NetplayCommon.cpp create mode 100644 Source/RMG/UserInterface/Dialog/Netplay/NetplayCommon.hpp create mode 100644 Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionBrowserDialog.cpp create mode 100644 Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionBrowserDialog.hpp create mode 100644 Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionBrowserDialog.ui create mode 100644 Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.cpp create mode 100644 Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.hpp create mode 100644 Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.ui create mode 100644 Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionPasswordDialog.cpp create mode 100644 Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionPasswordDialog.hpp create mode 100644 Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionPasswordDialog.ui create mode 100644 Source/RMG/UserInterface/Resource/icons/black/svg/plug-line.svg create mode 100644 Source/RMG/UserInterface/Resource/icons/black/svg/server-line.svg create mode 100644 Source/RMG/UserInterface/Resource/icons/white/svg/plug-line.svg create mode 100644 Source/RMG/UserInterface/Resource/icons/white/svg/server-line.svg diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 941db4bc..427b08c6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ jobs: - name: Install Packages run: | sudo add-apt-repository ppa:okirby/qt6-backports --yes - sudo apt-get -y install cmake ninja-build libhidapi-dev libsamplerate0-dev libspeex-dev libminizip-dev libsdl2-dev libfreetype6-dev libgl1-mesa-dev libglu1-mesa-dev pkg-config zlib1g-dev binutils-dev libspeexdsp-dev qt6-base-dev libqt6svg6-dev libvulkan-dev build-essential nasm git zip appstream + sudo apt-get -y install cmake ninja-build libhidapi-dev libsamplerate0-dev libspeex-dev libminizip-dev libsdl2-dev libsdl2-net-dev libfreetype6-dev libgl1-mesa-dev libglu1-mesa-dev pkg-config zlib1g-dev binutils-dev libspeexdsp-dev qt6-base-dev libqt6svg6-dev libqt6websockets6-dev libvulkan-dev build-essential nasm git zip appstream - name: Prepare Environment run: | echo "GIT_REVISION=$(git describe --tags --always)" >> $GITHUB_ENV @@ -67,6 +67,7 @@ jobs: mingw-w64-x86_64-freetype mingw-w64-x86_64-libpng mingw-w64-x86_64-SDL2 + mingw-w64-x86_64-SDL2_net mingw-w64-x86_64-qt6 mingw-w64-x86_64-SDL2 mingw-w64-x86_64-hidapi diff --git a/CMakeLists.txt b/CMakeLists.txt index dcba26c9..4cee119b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ option(UPDATER "Enables updater" ${WIN32}) option(APPIMAGE_UPDATER "Enables AppImage updater" OFF) option(DISCORD_RPC "Enables Discord Rich Presence" ON) option(DRAG_DROP "Enables drag and drop" ON) +option(NETPLAY "Enables netplay" ON) option(VRU "Enables VRU support in RMG-Input" ON) option(USE_CCACHE "Enables usage of ccache when ccache has been found" ON) option(USE_LTO "Enables building with LTO/IPO when compiler supports it" ON) diff --git a/Source/3rdParty/CMakeLists.txt b/Source/3rdParty/CMakeLists.txt index ed418236..fcf9632d 100644 --- a/Source/3rdParty/CMakeLists.txt +++ b/Source/3rdParty/CMakeLists.txt @@ -71,7 +71,8 @@ ExternalProject_Add(mupen64plus-core BUILD_COMMAND ${MAKE_CMD} all -f ${M64P_CORE_DIR}/projects/unix/Makefile SRCDIR=${CMAKE_CURRENT_SOURCE_DIR}/mupen64plus-core/src SUBDIR=${CMAKE_CURRENT_SOURCE_DIR}/mupen64plus-core/subprojects - OSD=0 NEW_DYNAREC=1 NO_ASM=$ KEYBINDINGS=0 ACCURATE_FPU=1 VULKAN=0 + OSD=0 NEW_DYNAREC=1 NO_ASM=$ + KEYBINDINGS=0 ACCURATE_FPU=1 VULKAN=0 NETPLAY=$ TARGET=${CORE_FILE} DEBUG=${MAKE_DEBUG} CC=${MAKE_CC_COMPILER} CXX=${MAKE_CXX_COMPILER} OPTFLAGS=${MAKE_OPTFLAGS} diff --git a/Source/RMG-Core/CMakeLists.txt b/Source/RMG-Core/CMakeLists.txt index dddd1c79..c3aaba5a 100644 --- a/Source/RMG-Core/CMakeLists.txt +++ b/Source/RMG-Core/CMakeLists.txt @@ -75,6 +75,13 @@ if (DISCORD_RPC) add_definitions(-DDISCORD_RPC) endif(DISCORD_RPC) +if (NETPLAY) + list(APPEND RMG_CORE_SOURCES + Netplay.cpp + ) + add_definitions(-DNETPLAY) +endif(NETPLAY) + if (PORTABLE_INSTALL) add_definitions(-DPORTABLE_INSTALL) endif(PORTABLE_INSTALL) diff --git a/Source/RMG-Core/Core.hpp b/Source/RMG-Core/Core.hpp index 4ed56552..e1261a65 100644 --- a/Source/RMG-Core/Core.hpp +++ b/Source/RMG-Core/Core.hpp @@ -22,6 +22,7 @@ #include "SaveState.hpp" #include "RomHeader.hpp" #include "Callback.hpp" +#include "Netplay.hpp" #include "Plugins.hpp" #include "Version.hpp" #include "Cheats.hpp" diff --git a/Source/RMG-Core/Emulation.cpp b/Source/RMG-Core/Emulation.cpp index 35cde80c..56743748 100644 --- a/Source/RMG-Core/Emulation.cpp +++ b/Source/RMG-Core/Emulation.cpp @@ -16,6 +16,7 @@ #include "RomHeader.hpp" #include "m64p/Api.hpp" #include "Plugins.hpp" +#include "Netplay.hpp" #include "Cheats.hpp" #include "Error.hpp" #include "Rom.hpp" @@ -145,10 +146,12 @@ static void apply_pif_rom_settings(void) // Exported Functions // -bool CoreStartEmulation(std::filesystem::path n64rom, std::filesystem::path n64ddrom) +bool CoreStartEmulation(std::filesystem::path n64rom, std::filesystem::path n64ddrom, + std::string address, int port, int player) { std::string error; - m64p_error ret; + m64p_error m64p_ret; + bool netplay_ret = false; CoreRomType type; if (!CoreOpenRom(n64rom)) @@ -177,12 +180,16 @@ bool CoreStartEmulation(std::filesystem::path n64rom, std::filesystem::path n64d return false; } - if (!CoreApplyCheats()) + // TODO: add support for cheats during netplay + if (!address.empty()) { - CoreDetachPlugins(); - CoreApplyPluginSettings(); - CoreCloseRom(); - return false; + if (!CoreApplyCheats()) + { + CoreDetachPlugins(); + CoreApplyPluginSettings(); + CoreCloseRom(); + return false; + } } if (!CoreGetRomType(type)) @@ -213,12 +220,35 @@ bool CoreStartEmulation(std::filesystem::path n64rom, std::filesystem::path n64d CoreDiscordRpcUpdate(true); #endif // DISCORD_RPC - ret = m64p::Core.DoCommand(M64CMD_EXECUTE, 0, nullptr); - if (ret != M64ERR_SUCCESS) +#ifdef NETPLAY + if (!address.empty()) { - error = "CoreStartEmulation m64p::Core.DoCommand(M64CMD_EXECUTE) Failed: "; - error += m64p::Core.ErrorMessage(ret); + netplay_ret = CoreInitNetplay(address, port, player); + if (!netplay_ret) + { + m64p_ret = M64ERR_SYSTEM_FAIL; + } + } +#endif // NETPLAY + + // only start emulation when initializing netplay + // is successful or if there's no netplay requested + if (address.empty() || netplay_ret) + { + m64p_ret = m64p::Core.DoCommand(M64CMD_EXECUTE, 0, nullptr); + if (m64p_ret != M64ERR_SUCCESS) + { + error = "CoreStartEmulation m64p::Core.DoCommand(M64CMD_EXECUTE) Failed: "; + error += m64p::Core.ErrorMessage(m64p_ret); + } + } + +#ifdef NETPLAY + if (!address.empty() && netplay_ret) + { + CoreShutdownNetplay(); } +#endif // NETPLAY CoreClearCheats(); CoreDetachPlugins(); @@ -234,12 +264,15 @@ bool CoreStartEmulation(std::filesystem::path n64rom, std::filesystem::path n64d CoreDiscordRpcUpdate(false); #endif // DISCORD_RPC - // we need to set the emulation error last, - // to prevent the other functions from - // overriding the emulation error - CoreSetError(error); + if (address.empty() || netplay_ret) + { + // we need to set the emulation error last, + // to prevent the other functions from + // overriding the emulation error + CoreSetError(error); + } - return ret == M64ERR_SUCCESS; + return m64p_ret == M64ERR_SUCCESS; } bool CoreStopEmulation(void) diff --git a/Source/RMG-Core/Emulation.hpp b/Source/RMG-Core/Emulation.hpp index 3c5f02b4..1b7610c9 100644 --- a/Source/RMG-Core/Emulation.hpp +++ b/Source/RMG-Core/Emulation.hpp @@ -20,7 +20,7 @@ enum class CoreEmulationState }; // starts emulation with given ROM -bool CoreStartEmulation(std::filesystem::path n64rom, std::filesystem::path n64ddrom); +bool CoreStartEmulation(std::filesystem::path n64rom, std::filesystem::path n64ddrom, std::string address = "", int port = -1, int player = -1); // stops emulation bool CoreStopEmulation(void); diff --git a/Source/RMG-Core/Netplay.cpp b/Source/RMG-Core/Netplay.cpp new file mode 100644 index 00000000..aec2a484 --- /dev/null +++ b/Source/RMG-Core/Netplay.cpp @@ -0,0 +1,102 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifdef _WIN32 +#define _CRT_RAND_S +#include +#endif // _WIN32 +#include "Netplay.hpp" +#include "Error.hpp" +#include "m64p/Api.hpp" + + + +// +// Local Variables +// + +static bool l_HasInitNetplay = false; + +// +// Exported Functions +// + +bool CoreInitNetplay(std::string address, int port, int player) +{ + std::string error; + m64p_error ret; + uint32_t id = 0; + + // initialize random ID + while (id == 0) + { +#ifdef _WIN32 + rand_s(&id); +#else + id = rand(); +#endif + id &= ~0x7; + id |= player; + } + + uint32_t version; + ret = m64p::Core.DoCommand(M64CMD_NETPLAY_GET_VERSION, 0x010001, &version); + if (ret != M64ERR_SUCCESS) + { + error = "CoreInitNetplay m64p::Core.DoCommand(M64CMD_NETPLAY_GET_VERSION) Failed: "; + error += m64p::Core.ErrorMessage(ret); + CoreSetError(error); + return false; + } + + ret = m64p::Core.DoCommand(M64CMD_NETPLAY_INIT, port, (void*)address.c_str()); + if (ret != M64ERR_SUCCESS) + { + error = "CoreInitNetplay m64p::Core.DoCommand(M64CMD_NETPLAY_INIT) Failed: "; + error += m64p::Core.ErrorMessage(ret); + CoreSetError(error); + return false; + } + + ret = m64p::Core.DoCommand(M64CMD_NETPLAY_CONTROL_PLAYER, player, &id); + if (ret != M64ERR_SUCCESS) + { + error = "CoreInitNetplay m64p::Core.DoCommand(M64CMD_NETPLAY_CONTROL_PLAYER) Failed: "; + error += m64p::Core.ErrorMessage(ret); + CoreSetError(error); + CoreShutdownNetplay(); + return false; + } + + l_HasInitNetplay = true; + return true; +} + +bool CoreHasInitNetplay(void) +{ + return l_HasInitNetplay; +} + +bool CoreShutdownNetplay(void) +{ + std::string error; + m64p_error ret; + + ret = m64p::Core.DoCommand(M64CMD_NETPLAY_CLOSE, 0, nullptr); + if (ret != M64ERR_SUCCESS) + { + error = "CoreShutdownNetplay m64p::Core.DoCommand(M64CMD_NETPLAY_CLOSE) Failed: "; + error += m64p::Core.ErrorMessage(ret); + CoreSetError(error); + return false; + } + + l_HasInitNetplay = false; + return true; +} \ No newline at end of file diff --git a/Source/RMG-Core/Netplay.hpp b/Source/RMG-Core/Netplay.hpp new file mode 100644 index 00000000..4bc57444 --- /dev/null +++ b/Source/RMG-Core/Netplay.hpp @@ -0,0 +1,24 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef CORE_NETPLAY_HPP +#define CORE_NETPLAY_HPP + +#include + +// attempts to initialize netplay +bool CoreInitNetplay(std::string address, int port, int player); + +// returns whether netplay has been initialized +bool CoreHasInitNetplay(void); + +// attempts to shutdown netplay +bool CoreShutdownNetplay(void); + +#endif // CORE_NETPLAY_HPP \ No newline at end of file diff --git a/Source/RMG-Core/Settings/Settings.cpp b/Source/RMG-Core/Settings/Settings.cpp index 03bbaf2e..0295ff0f 100644 --- a/Source/RMG-Core/Settings/Settings.cpp +++ b/Source/RMG-Core/Settings/Settings.cpp @@ -52,6 +52,7 @@ static std::vector l_keyList; // #define SETTING_SECTION_GUI "Rosalie's Mupen GUI" +#define SETTING_SECTION_NETPLAY SETTING_SECTION_GUI " Netplay" #define SETTING_SECTION_CORE SETTING_SECTION_GUI " Core" #define SETTING_SECTION_OVERLAY SETTING_SECTION_CORE " Overlay" #define SETTING_SECTION_KEYBIND SETTING_SECTION_GUI " KeyBindings" @@ -158,6 +159,14 @@ static l_Setting get_setting(SettingsID settingId) setting = {SETTING_SECTION_GUI, "Version", CoreGetVersion()}; break; + case SettingsID::Netplay_Nickname: + setting = {SETTING_SECTION_NETPLAY, "Nickname", "NetplayUser"}; + break; + + case SettingsID::Netplay_ServerJsonUrl: + setting = {SETTING_SECTION_NETPLAY, "ServerJsonUrl", "https://m64p.s3.amazonaws.com/servers.json"}; + break; + case SettingsID::Core_GFX_Plugin: setting = {SETTING_SECTION_CORE, "GFX_Plugin", #ifdef _WIN32 diff --git a/Source/RMG-Core/Settings/SettingsID.hpp b/Source/RMG-Core/Settings/SettingsID.hpp index d4af2598..84cf1c1e 100644 --- a/Source/RMG-Core/Settings/SettingsID.hpp +++ b/Source/RMG-Core/Settings/SettingsID.hpp @@ -30,6 +30,10 @@ enum class SettingsID GUI_DiscordRpc, GUI_Version, + // Netplay Settings + Netplay_Nickname, + Netplay_ServerJsonUrl, + // Core Plugin Settings Core_GFX_Plugin, Core_AUDIO_Plugin, diff --git a/Source/RMG/CMakeLists.txt b/Source/RMG/CMakeLists.txt index 4017ce01..50e1c6ef 100644 --- a/Source/RMG/CMakeLists.txt +++ b/Source/RMG/CMakeLists.txt @@ -28,6 +28,11 @@ if (DRAG_DROP) add_definitions(-DDRAG_DROP) endif(DRAG_DROP) +if (NETPLAY) + find_package(Qt6 COMPONENTS WebSockets REQUIRED) + add_definitions(-DNETPLAY) +endif(NETPLAY) + find_package(PkgConfig REQUIRED) pkg_check_modules(SDL2 REQUIRED sdl2) @@ -82,6 +87,20 @@ if (UPDATER) ) endif(UPDATER) +if (NETPLAY) + list(APPEND RMG_SOURCES + UserInterface/Dialog/Netplay/NetplayCommon.cpp + UserInterface/Dialog/Netplay/CreateNetplaySessionDialog.cpp + UserInterface/Dialog/Netplay/CreateNetplaySessionDialog.ui + UserInterface/Dialog/Netplay/NetplaySessionBrowserDialog.cpp + UserInterface/Dialog/Netplay/NetplaySessionBrowserDialog.ui + UserInterface/Dialog/Netplay/NetplaySessionDialog.cpp + UserInterface/Dialog/Netplay/NetplaySessionDialog.ui + UserInterface/Dialog/Netplay/NetplaySessionPasswordDialog.cpp + UserInterface/Dialog/Netplay/NetplaySessionPasswordDialog.ui + ) +endif(NETPLAY) + set_source_files_properties(${IMGUI_SOURCES} PROPERTIES GENERATED TRUE) list(APPEND RMG_SOURCES ${IMGUI_SOURCES}) @@ -120,3 +139,7 @@ if (UPDATER) target_link_libraries(RMG Qt6::Network) endif(UPDATER) +if (NETPLAY) + target_link_libraries(RMG Qt6::WebSockets) +endif(NETPLAY) + diff --git a/Source/RMG/Thread/EmulationThread.cpp b/Source/RMG/Thread/EmulationThread.cpp index 43470bfa..2d59f14b 100644 --- a/Source/RMG/Thread/EmulationThread.cpp +++ b/Source/RMG/Thread/EmulationThread.cpp @@ -38,16 +38,31 @@ void EmulationThread::SetDiskFile(QString file) this->disk = file; } +void EmulationThread::SetNetplay(QString address, int port, int player) +{ + this->address = address; + this->port = port; + this->player = player; +} + void EmulationThread::run(void) { emit this->on_Emulation_Started(); - bool ret = CoreStartEmulation(this->rom.toStdU32String(), this->disk.toStdU32String()); + bool ret = CoreStartEmulation(this->rom.toStdU32String(), this->disk.toStdU32String(), + this->address.toStdString(), this->port, this->player); if (!ret) { this->errorMessage = QString::fromStdString(CoreGetError()); } + // reset state + this->rom.clear(); + this->disk.clear(); + this->address.clear(); + this->port = -1; + this->player = -1; + emit this->on_Emulation_Finished(ret); } diff --git a/Source/RMG/Thread/EmulationThread.hpp b/Source/RMG/Thread/EmulationThread.hpp index e87461d2..bc036eb7 100644 --- a/Source/RMG/Thread/EmulationThread.hpp +++ b/Source/RMG/Thread/EmulationThread.hpp @@ -33,6 +33,7 @@ class EmulationThread : public QThread void SetRomFile(QString); void SetDiskFile(QString); + void SetNetplay(QString address, int port, int player); void run(void) override; @@ -41,6 +42,9 @@ class EmulationThread : public QThread private: QString rom; QString disk; + QString address; + int port = -1; + int player = -1; QString errorMessage; signals: diff --git a/Source/RMG/UserInterface/Dialog/Netplay/CreateNetplaySessionDialog.cpp b/Source/RMG/UserInterface/Dialog/Netplay/CreateNetplaySessionDialog.cpp new file mode 100644 index 00000000..bb19b1ff --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/Netplay/CreateNetplaySessionDialog.cpp @@ -0,0 +1,302 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "CreateNetplaySessionDialog.hpp" +#include "NetplayCommon.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace UserInterface::Dialog; + +// +// Local Structs +// + +struct NetplayRomData_t +{ + QString GoodName; + QString MD5; + QString File; +}; + +Q_DECLARE_METATYPE(NetplayRomData_t); + +// +// Exported Functions +// + + +CreateNetplaySessionDialog::CreateNetplaySessionDialog(QWidget *parent, QWebSocket* webSocket, QMap modelData) : QDialog(parent) +{ + qRegisterMetaType(); + + this->setupUi(this); + + // prepare web socket + this->webSocket = webSocket; + connect(this->webSocket, &QWebSocket::textMessageReceived, this, &CreateNetplaySessionDialog::on_webSocket_textMessageReceived); + + // prepare broadcast + broadcastSocket.bind(QHostAddress(QHostAddress::AnyIPv4), 0); + connect(&this->broadcastSocket, &QUdpSocket::readyRead, this, &CreateNetplaySessionDialog::on_broadcastSocket_readyRead); + QByteArray multirequest; + multirequest.append(1); + broadcastSocket.writeDatagram(multirequest, QHostAddress::Broadcast, 45000); + + // change ok button name + QPushButton* createButton = this->buttonBox->button(QDialogButtonBox::Ok); + createButton->setText("Create"); + createButton->setEnabled(false); + + // set validator for nickname + QRegularExpression nicknameRe(NETPLAYCOMMON_NICKNAME_REGEX); + this->nickNameLineEdit->setValidator(new QRegularExpressionValidator(nicknameRe, this)); + this->nickNameLineEdit->setText(QString::fromStdString(CoreSettingsGetStringValue(SettingsID::Netplay_Nickname))); + + // set validator for session + QRegularExpression sessionRe(NETPLAYCOMMON_SESSION_REGEX); + this->sessionNameLineEdit->setValidator(new QRegularExpressionValidator(sessionRe, this)); + + // set validator for password + QRegularExpression passwordRe(NETPLAYCOMMON_PASSWORD_REGEX); + this->passwordLineEdit->setValidator(new QRegularExpressionValidator(passwordRe, this)); + + // transform model data to data we can use + QList romData; + romData.reserve(modelData.size()); + for (auto it = modelData.begin(); it != modelData.end(); it++) + { + romData.append( + { + QString::fromStdString(it.value().GoodName), + QString::fromStdString(it.value().MD5), + it.key() + }); + } + // add data to list widget + for (const NetplayRomData_t& data : romData) + { + QListWidgetItem* item = new QListWidgetItem(); + item->setData(Qt::UserRole, QVariant::fromValue(data)); + item->setText(data.GoodName); + this->listWidget->addItem(item); + } + this->listWidget->sortItems(); + + this->validateCreateButton(); + + // request server list + QString serverUrl = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::Netplay_ServerJsonUrl)); + if (!serverUrl.isEmpty() && QUrl(serverUrl).isValid()) + { + QNetworkAccessManager* networkAccessManager = new QNetworkAccessManager(this); + connect(networkAccessManager, &QNetworkAccessManager::finished, this, &CreateNetplaySessionDialog::on_networkAccessManager_Finished); + networkAccessManager->get(QNetworkRequest(QUrl(serverUrl))); + } +} + +CreateNetplaySessionDialog::~CreateNetplaySessionDialog(void) +{ + QString nickname = this->nickNameLineEdit->text(); + if (!nickname.isEmpty()) + { + CoreSettingsSetValue(SettingsID::Netplay_Nickname, nickname.toStdString()); + } +} + +QJsonObject CreateNetplaySessionDialog::GetSessionJson(void) +{ + return this->sessionJson; +} + +QString CreateNetplaySessionDialog::GetSessionFile(void) +{ + return this->sessionFile; +} + +void CreateNetplaySessionDialog::showErrorMessage(QString error, QString details) +{ + QMessageBox msgBox(this); + msgBox.setIcon(QMessageBox::Icon::Critical); + msgBox.setWindowTitle("Error"); + msgBox.setText(error); + msgBox.setDetailedText(details); + msgBox.addButton(QMessageBox::Ok); + msgBox.exec(); +} + +bool CreateNetplaySessionDialog::validate(void) +{ + if (this->nickNameLineEdit->text().isEmpty() || + this->nickNameLineEdit->text().size() > 128) + { + return false; + } + + if (this->sessionNameLineEdit->text().isEmpty() || + this->sessionNameLineEdit->text().size() > 128) + { + return false; + } + + if (this->listWidget->count() == 0 || + this->serverComboBox->count() == 0) + { + return false; + } + + if (this->listWidget->currentItem() == nullptr) + { + return false; + } + + return true; +} + +void CreateNetplaySessionDialog::validateCreateButton(void) +{ + QPushButton* createButton = this->buttonBox->button(QDialogButtonBox::Ok); + createButton->setEnabled(this->validate()); +} + +void CreateNetplaySessionDialog::on_webSocket_textMessageReceived(QString message) +{ + QJsonDocument jsonDocument = QJsonDocument::fromJson(message.toUtf8()); + QJsonObject json = jsonDocument.object(); + + if (json.value("type").toString() == "reply_create_room") + { + if (json.value("accept").toInt() == 0) + { + this->sessionJson = json; + QDialog::accept(); + } + else + { + this->showErrorMessage("Server Error", json.value("message").toString()); + this->validateCreateButton(); + } + } +} + +void CreateNetplaySessionDialog::on_broadcastSocket_readyRead() +{ + while (this->broadcastSocket.hasPendingDatagrams()) + { + QNetworkDatagram datagram = this->broadcastSocket.receiveDatagram(); + QByteArray incomingData = datagram.data(); + QJsonDocument json_doc = QJsonDocument::fromJson(incomingData); + QJsonObject json = json_doc.object(); + QStringList servers = json.keys(); + + for (int i = 0; i < servers.size(); i++) + { + this->serverComboBox->addItem(servers.at(i), json.value(servers.at(i)).toString()); + } + } +} + +void CreateNetplaySessionDialog::on_networkAccessManager_Finished(QNetworkReply* reply) +{ + if (reply->error()) + { + reply->deleteLater(); + return; + } + + QJsonDocument jsonDocument = QJsonDocument::fromJson(reply->readAll()); + QJsonObject jsonObject = jsonDocument.object(); + QStringList jsonServers = jsonObject.keys(); + + for (int i = 0; i < jsonServers.size(); i++) + { + this->serverComboBox->addItem(jsonServers.at(i), jsonObject.value(jsonServers.at(i)).toString()); + } + + this->serverComboBox->addItem(QString("Custom"), QString("Custom")); + reply->deleteLater(); +} + +void CreateNetplaySessionDialog::on_serverComboBox_currentIndexChanged(int index) +{ + if (index == -1) + { + return; + } + + QString address = this->serverComboBox->itemData(index).toString(); + this->webSocket->open(QUrl(address)); +} + +void CreateNetplaySessionDialog::on_nickNameLineEdit_textChanged(void) +{ + this->validateCreateButton(); +} + +void CreateNetplaySessionDialog::on_sessionNameLineEdit_textChanged(void) +{ + this->validateCreateButton(); +} + +void CreateNetplaySessionDialog::on_passwordLineEdit_textChanged(void) +{ + this->validateCreateButton(); +} + +void CreateNetplaySessionDialog::on_listWidget_currentRowChanged(int index) +{ + this->validateCreateButton(); +} + +#include +void CreateNetplaySessionDialog::accept() +{ + if (!this->webSocket->isValid()) + { + this->showErrorMessage("Server Error", "Connection Failed"); + return; + } + + // disable create button while we're processing the request + QPushButton* createButton = this->buttonBox->button(QDialogButtonBox::Ok); + createButton->setEnabled(false); + + QListWidgetItem* item = this->listWidget->currentItem(); + NetplayRomData_t romData = item->data(Qt::UserRole).value(); + + this->sessionFile = romData.File; + + QList plugins = NetplayCommon::GetPluginNames(romData.MD5); + + QJsonObject jsonFeatures; + jsonFeatures.insert("cpu_emulator", NetplayCommon::GetCpuEmulator(romData.MD5)); + jsonFeatures.insert("rsp_plugin", plugins[0]); + jsonFeatures.insert("gfx_plugin", plugins[1]); + + QJsonObject json; + json.insert("type", "request_create_room"); + json.insert("room_name", this->sessionNameLineEdit->text()); + json.insert("player_name", this->nickNameLineEdit->text()); + json.insert("password", this->passwordLineEdit->text()); + json.insert("MD5", romData.MD5); + json.insert("game_name", romData.GoodName); + json.insert("features", jsonFeatures); + NetplayCommon::AddCommonJson(json); + + this->webSocket->sendTextMessage(QJsonDocument(json).toJson()); +} diff --git a/Source/RMG/UserInterface/Dialog/Netplay/CreateNetplaySessionDialog.hpp b/Source/RMG/UserInterface/Dialog/Netplay/CreateNetplaySessionDialog.hpp new file mode 100644 index 00000000..2d27d4ec --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/Netplay/CreateNetplaySessionDialog.hpp @@ -0,0 +1,69 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef CREATENETPLAYSESSIONDIALOG_HPP +#define CREATENETPLAYSESSIONDIALOG_HPP + +#include +#include +#include +#include +#include +#include + +#include "ui_CreateNetplaySessionDialog.h" + +#include + +namespace UserInterface +{ +namespace Dialog +{ +class CreateNetplaySessionDialog : public QDialog, private Ui::CreateNetplaySessionDialog +{ + Q_OBJECT + + public: + CreateNetplaySessionDialog(QWidget *parent, QWebSocket* webSocket, QMap data); + ~CreateNetplaySessionDialog(void); + + QJsonObject GetSessionJson(void); + QString GetSessionFile(void); + + private: + QWebSocket* webSocket; + QUdpSocket broadcastSocket; + + QJsonObject sessionJson; + QString sessionFile; + + void showErrorMessage(QString error, QString details); + + bool validate(void); + void validateCreateButton(void); + + private slots: + void on_webSocket_textMessageReceived(QString message); + void on_broadcastSocket_readyRead(void); + void on_networkAccessManager_Finished(QNetworkReply* reply); + + void on_serverComboBox_currentIndexChanged(int index); + + void on_nickNameLineEdit_textChanged(void); + void on_sessionNameLineEdit_textChanged(void); + void on_passwordLineEdit_textChanged(void); + + void on_listWidget_currentRowChanged(int index); + + void accept(void) Q_DECL_OVERRIDE; +}; +} // namespace Dialog +} // namespace UserInterface + +#endif // CREATENETPLAYSESSIONDIALOG_HPP diff --git a/Source/RMG/UserInterface/Dialog/Netplay/CreateNetplaySessionDialog.ui b/Source/RMG/UserInterface/Dialog/Netplay/CreateNetplaySessionDialog.ui new file mode 100644 index 00000000..d5b2d6cd --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/Netplay/CreateNetplaySessionDialog.ui @@ -0,0 +1,147 @@ + + + CreateNetplaySessionDialog + + + + 0 + 0 + 582 + 440 + + + + Create Netplay Session + + + + + + + + Server + + + + + + + + + + + + + + Nickname + + + + + + + + 0 + 0 + + + + + + + + + + + + Session name + + + + + + + + 0 + 0 + + + + + + + + + + + + Password + + + + + + + + 0 + 0 + + + + QLineEdit::EchoMode::Password + + + + + + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + + + + + + + buttonBox + accepted() + CreateNetplaySessionDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CreateNetplaySessionDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Source/RMG/UserInterface/Dialog/Netplay/NetplayCommon.cpp b/Source/RMG/UserInterface/Dialog/Netplay/NetplayCommon.cpp new file mode 100644 index 00000000..ef93b2a4 --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/Netplay/NetplayCommon.cpp @@ -0,0 +1,110 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "NetplayCommon.hpp" + +#include +#include +#include +#include + +#include + +using namespace NetplayCommon; + +void NetplayCommon::AddCommonJson(QJsonObject& json) +{ + QCryptographicHash hash = QCryptographicHash(QCryptographicHash::Sha256); + QByteArray currentTime = QByteArray::number(QDateTime::currentMSecsSinceEpoch()); + hash.addData(currentTime); + hash.addData(QStringLiteral("RMG").toUtf8()); + + json.insert("client_sha", QString::fromStdString(CoreGetVersion())); + json.insert("emulator", "RMG"); + json.insert("auth", QString(hash.result().toHex())); + json.insert("authTime", QString(currentTime)); + json.insert("netplay_version", 16); +} + +QString NetplayCommon::GetCpuEmulator(QString md5QString) +{ + const std::string md5 = md5QString.toStdString(); + int cpuEmulator = -1; + bool overrideCoreSettings = CoreSettingsGetBoolValue(SettingsID::Game_OverrideCoreSettings, md5); + if (overrideCoreSettings) + { + cpuEmulator = CoreSettingsGetIntValue(SettingsID::Game_CPU_Emulator, md5); + } + else + { + cpuEmulator = CoreSettingsGetIntValue(SettingsID::CoreOverlay_CPU_Emulator); + } + return QString::number(cpuEmulator); +} + +QString NetplayCommon::GetCpuEmulatorName(QString cpuEmulator) +{ + switch (cpuEmulator.toInt()) + { + default: + case 0: + return "Pure Interpreter"; + case 1: + return "Cached Interpreter"; + case 2: + return "Dynamic Recompiler"; + } +} + +QList NetplayCommon::GetPluginNames(QString md5QString) +{ + QList pluginNames(2); + const std::string md5 = md5QString.toStdString(); + const std::vector pluginList = CoreGetAllPlugins(); + const SettingsID settingsId[] = + { + SettingsID::Core_RSP_Plugin, SettingsID::Core_GFX_Plugin, + }; + const SettingsID gameSettingsId[] = + { + SettingsID::Game_RSP_Plugin, SettingsID::Game_GFX_Plugin, + }; + QString pluginFileNames[4]; + QString pluginFileName; + + int index = 0; + + for (int i = 0; i < 2; i++) + { + pluginFileName = QString::fromStdString(CoreSettingsGetStringValue(gameSettingsId[i], md5)); + if (pluginFileName.isEmpty()) + { // fallback to main plugin settings + pluginFileName = QString::fromStdString(CoreSettingsGetStringValue(settingsId[i])); + } + + if (!pluginFileName.isEmpty()) + { + // account for full path ( + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef NETPLAYCOMMON_HPP +#define NETPLAYCOMMON_HPP + +#include +#include + +namespace NetplayCommon +{ + #define NETPLAYCOMMON_SESSION_REGEX "[a-zA-Z0-9 ]+" + #define NETPLAYCOMMON_NICKNAME_REGEX "[a-zA-Z0-9]+" + #define NETPLAYCOMMON_PASSWORD_REGEX "[a-zA-Z0-9,.\\/<>?;:[\\]{}\\-=_+`~!@#$%^&*()]+" + + // Adds common json emulator and auth info + void AddCommonJson(QJsonObject& json); + + // Retrieves CPU emulator for MD5 + QString GetCpuEmulator(QString md5QString); + + // Retrieves CPU emulator name + QString GetCpuEmulatorName(QString cpuEmulator); + + // Retrieves RSP and GFX plugin names + QList GetPluginNames(QString md5QString); +} + +#endif // NETPLAYCOMMON_HPP diff --git a/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionBrowserDialog.cpp b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionBrowserDialog.cpp new file mode 100644 index 00000000..62a9e0d1 --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionBrowserDialog.cpp @@ -0,0 +1,436 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "NetplaySessionBrowserDialog.hpp" +#include "UserInterface/NoFocusDelegate.hpp" +#include "NetplaySessionPasswordDialog.hpp" +#include "NetplayCommon.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace UserInterface::Dialog; + +// +// Local Structs +// + +struct NetplaySessionData_t +{ + QString SessionName; + QString GameName; + QString MD5; + bool PasswordProtected = false; + int Port = 0; + QString CpuEmulator; + QString RspPlugin; + QString GfxPlugin; +}; + +Q_DECLARE_METATYPE(NetplaySessionData_t); + +// +// Exported Functions +// + + +NetplaySessionBrowserDialog::NetplaySessionBrowserDialog(QWidget *parent, QWebSocket* webSocket, QMap modelData) : QDialog(parent) +{ + qRegisterMetaType(); + + this->setupUi(this); + + // prepare web socket + this->webSocket = webSocket; + connect(this->webSocket, &QWebSocket::connected, this, &NetplaySessionBrowserDialog::on_webSocket_connected); + connect(this->webSocket, &QWebSocket::textMessageReceived, this, &NetplaySessionBrowserDialog::on_webSocket_textMessageReceived); + + // copy rom data for later + this->romData = modelData; + + // prepare broadcast + broadcastSocket.bind(QHostAddress(QHostAddress::AnyIPv4), 0); + connect(&this->broadcastSocket, &QUdpSocket::readyRead, this, &NetplaySessionBrowserDialog::on_broadcastSocket_readyRead); + QByteArray multirequest; + multirequest.append(1); + broadcastSocket.writeDatagram(multirequest, QHostAddress::Broadcast, 45000); + + // change ok button name + QPushButton* joinButton = this->buttonBox->button(QDialogButtonBox::Ok); + joinButton->setText("Join"); + joinButton->setEnabled(false); + + // set validator for nickname + QRegularExpression re(NETPLAYCOMMON_NICKNAME_REGEX); + this->nickNameLineEdit->setValidator(new QRegularExpressionValidator(re, this)); + this->nickNameLineEdit->setText(QString::fromStdString(CoreSettingsGetStringValue(SettingsID::Netplay_Nickname))); + + // prepare session browser + this->tableWidget->setFrameStyle(QFrame::NoFrame); + this->tableWidget->setItemDelegate(new NoFocusDelegate(this)); + this->tableWidget->setWordWrap(false); + this->tableWidget->setShowGrid(false); + this->tableWidget->setSortingEnabled(true); + this->tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); + this->tableWidget->setSelectionBehavior(QTableView::SelectRows); + this->tableWidget->setSelectionMode(QAbstractItemView::SingleSelection); + this->tableWidget->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel); + this->tableWidget->verticalHeader()->hide(); + this->tableWidget->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + this->tableWidget->horizontalHeader()->setSectionsMovable(true); + this->tableWidget->horizontalHeader()->setFirstSectionMovable(true); + this->tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + this->tableWidget->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); + this->tableWidget->horizontalHeader()->setSortIndicatorShown(false); + this->tableWidget->horizontalHeader()->setHighlightSections(false); + + // request server list + QString serverUrl = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::Netplay_ServerJsonUrl)); + if (!serverUrl.isEmpty() && QUrl(serverUrl).isValid()) + { + QNetworkAccessManager* networkAccessManager = new QNetworkAccessManager(this); + connect(networkAccessManager, &QNetworkAccessManager::finished, this, &NetplaySessionBrowserDialog::on_networkAccessManager_Finished); + networkAccessManager->get(QNetworkRequest(QUrl(serverUrl))); + } + + this->validateJoinButton(); +} + +NetplaySessionBrowserDialog::~NetplaySessionBrowserDialog(void) +{ + QString nickname = this->nickNameLineEdit->text(); + if (!nickname.isEmpty()) + { + CoreSettingsSetValue(SettingsID::Netplay_Nickname, nickname.toStdString()); + } +} + +QJsonObject NetplaySessionBrowserDialog::GetSessionJson(void) +{ + return this->sessionJson; +} + +QString NetplaySessionBrowserDialog::GetSessionFile(void) +{ + return this->sessionFile; +} + +void NetplaySessionBrowserDialog::showErrorMessage(QString error, QString details) +{ + QMessageBox msgBox(this); + msgBox.setIcon(QMessageBox::Icon::Critical); + msgBox.setWindowTitle("Error"); + msgBox.setText(error); + msgBox.setDetailedText(details); + msgBox.addButton(QMessageBox::Ok); + msgBox.exec(); +} + +QString NetplaySessionBrowserDialog::showROMDialog(QString name, QString md5) +{ + QString title = "Open " + name; + QString file = QFileDialog::getOpenFileName(this, title, "", "N64 ROMs (*.n64 *.z64 *.v64 *.zip *.7z)"); + CoreRomSettings romSettings; + + if (!file.isEmpty()) + { + if (!CoreOpenRom(file.toStdU32String()) || + !CoreGetCurrentRomSettings(romSettings)) + { + CoreCloseRom(); + return ""; + } + + CoreCloseRom(); + + if (md5.toStdString() != romSettings.MD5) + { + QString details = "Expected MD5: " + md5 + "\n"; + details += "Received MD5: " + QString::fromStdString(romSettings.MD5); + this->showErrorMessage("Incorrect ROM Selected", details); + return ""; + } + } + + return file; +} + +bool NetplaySessionBrowserDialog::validate(void) +{ + if (this->nickNameLineEdit->text().isEmpty() || + this->nickNameLineEdit->text().contains(' ') || + this->nickNameLineEdit->text().size() > 256) + { + return false; + } + + if (this->tableWidget->currentItem() == nullptr || + this->serverComboBox->count() == 0) + { + return false; + } + + return true; +} + +void NetplaySessionBrowserDialog::validateJoinButton(void) +{ + QPushButton* joinButton = this->buttonBox->button(QDialogButtonBox::Ok); + joinButton->setEnabled(this->validate()); +} + +void NetplaySessionBrowserDialog::addSessionData(QString name, QString game, QString md5, bool password, int port, + QString cpuEmulator, QString rspPlugin, QString gfxPlugin) +{ + const NetplaySessionData_t sessionData = + { + name, + game, + md5, + password, + port, + cpuEmulator, + rspPlugin, + gfxPlugin + }; + + int row = this->tableWidget->rowCount(); + this->tableWidget->insertRow(row); + + // Session name + QTableWidgetItem* tableWidgetItem1 = new QTableWidgetItem(name); + tableWidgetItem1->setData(Qt::UserRole, QVariant::fromValue(sessionData)); + this->tableWidget->setItem(row, 0, tableWidgetItem1); + + // Game + QTableWidgetItem* tableWidgetItem2 = new QTableWidgetItem(game); + this->tableWidget->setItem(row, 1, tableWidgetItem2); + + // MD5 + QTableWidgetItem* tableWidgetItem3 = new QTableWidgetItem(md5); + this->tableWidget->setItem(row, 2, tableWidgetItem3); + + // Password + QTableWidgetItem* tableWidgetItem4 = new QTableWidgetItem(password ? "Yes" : "No"); + this->tableWidget->setItem(row, 3, tableWidgetItem4); +} + +void NetplaySessionBrowserDialog::on_webSocket_connected(void) +{ + QJsonObject json; + json.insert("type", "request_get_rooms"); + NetplayCommon::AddCommonJson(json); + + this->webSocket->sendTextMessage(QJsonDocument(json).toJson()); +} + +void NetplaySessionBrowserDialog::on_webSocket_textMessageReceived(QString message) +{ + QJsonDocument jsonDocument = QJsonDocument::fromJson(message.toUtf8()); + QJsonObject json = jsonDocument.object(); + + QString type = json.value("type").toString(); + + if (type == "reply_get_rooms") + { + if (json.value("accept").toInt() == 0) + { + this->addSessionData(json.value("room_name").toString(), + json.value("game_name").toString(), + json.value("MD5").toString(), + json.value("protected").toBool(), + json.value("port").toInt(), + json.value("features").toObject().value("cpu_emulator").toString(), + json.value("features").toObject().value("rsp_plugin").toString(), + json.value("features").toObject().value("gfx_plugin").toString()); + } + else + { + this->showErrorMessage("Server Error", json.value("message").toString()); + } + } + else if (type == "reply_join_room") + { + if (json.value("accept").toInt() == 0) + { + this->sessionJson = json; + QDialog::accept(); + } + else + { + this->showErrorMessage("Server Error", json.value("message").toString()); + this->validateJoinButton(); + } + } +} + +void NetplaySessionBrowserDialog::on_broadcastSocket_readyRead(void) +{ + while (this->broadcastSocket.hasPendingDatagrams()) + { + QNetworkDatagram datagram = this->broadcastSocket.receiveDatagram(); + QByteArray incomingData = datagram.data(); + QJsonDocument json_doc = QJsonDocument::fromJson(incomingData); + QJsonObject json = json_doc.object(); + QStringList servers = json.keys(); + + for (int i = 0; i < servers.size(); i++) + { + this->serverComboBox->addItem(servers.at(i), json.value(servers.at(i)).toString()); + } + } +} + +void NetplaySessionBrowserDialog::on_networkAccessManager_Finished(QNetworkReply* reply) +{ + if (reply->error()) + { + reply->deleteLater(); + return; + } + + QJsonDocument jsonDocument = QJsonDocument::fromJson(reply->readAll()); + QJsonObject jsonObject = jsonDocument.object(); + QStringList jsonServers = jsonObject.keys(); + + for (int i = 0; i < jsonServers.size(); i++) + { + this->serverComboBox->addItem(jsonServers.at(i), jsonObject.value(jsonServers.at(i)).toString()); + } + + reply->deleteLater(); +} + +void NetplaySessionBrowserDialog::on_serverComboBox_currentIndexChanged(int index) +{ + if (index == -1) + { + return; + } + + // clear sessions from the table + this->tableWidget->setRowCount(0); + + QString address = this->serverComboBox->itemData(index).toString(); + this->webSocket->open(QUrl(address)); +} + +void NetplaySessionBrowserDialog::on_tableWidget_currentItemChanged(QTableWidgetItem* current, QTableWidgetItem* previous) +{ + this->validateJoinButton(); +} + +void NetplaySessionBrowserDialog::on_nickNameLineEdit_textChanged() +{ + this->validateJoinButton(); +} + +void NetplaySessionBrowserDialog::accept() +{ + if (!this->webSocket->isValid()) + { + this->showErrorMessage("Server Error", "Connection Failed"); + return; + } + + // disable join button while we're processing the request + QPushButton* joinButton = this->buttonBox->button(QDialogButtonBox::Ok); + joinButton->setEnabled(false); + + // retrieve information from row + QTableWidgetItem* item = this->tableWidget->item(this->tableWidget->currentRow(), 0); + NetplaySessionData_t sessionData = item->data(Qt::UserRole).value(); + + // request password when needed + QString password; + if (sessionData.PasswordProtected) + { + NetplaySessionPasswordDialog dialog(this); + dialog.exec(); + password = dialog.GetPassword(); + + // do nothing if password is empty + if (password.isEmpty()) + { + joinButton->setEnabled(true); + return; + } + } + + // ensure features match to what we currently have + QList pluginNames = NetplayCommon::GetPluginNames(sessionData.MD5); + QString cpuEmulator = NetplayCommon::GetCpuEmulator(sessionData.MD5); + QString details; + if (sessionData.CpuEmulator != cpuEmulator) + { + details = "Expected CPU Emulator: " + NetplayCommon::GetCpuEmulatorName(sessionData.CpuEmulator) + "\n"; + details += "Current CPU Emulator: " + NetplayCommon::GetCpuEmulatorName(cpuEmulator); + this->showErrorMessage("CPU Emulator Mismatch", details); + joinButton->setEnabled(true); + return; + } + if (sessionData.RspPlugin != pluginNames[0]) + { + details = "Expected RSP Plugin: " + sessionData.RspPlugin + "\n"; + details += "Current RSP Plugin: " + pluginNames[0]; + this->showErrorMessage("RSP Plugin Mismatch", details); + joinButton->setEnabled(true); + return; + } + if (sessionData.GfxPlugin != pluginNames[1]) + { + details = "Expected GFX Plugin: " + sessionData.GfxPlugin + "\n"; + details += "Current GFX Plugin: " + pluginNames[1]; + this->showErrorMessage("GFX Plugin Mismatch", details); + joinButton->setEnabled(true); + return; + } + + + // attempt to find ROM from the ROM browser data + std::string md5 = sessionData.MD5.toStdString(); + for (auto it = this->romData.begin(); it != this->romData.end(); it++) + { + if (it.value().MD5 == md5) + { + this->sessionFile = it.key(); + break; + } + } + + // show ROM dialog when we haven't found the ROM + if (this->sessionFile.isEmpty()) + { + this->sessionFile = this->showROMDialog(sessionData.GameName, sessionData.MD5); + if (this->sessionFile.isEmpty()) + { + joinButton->setEnabled(true); + return; + } + } + + QJsonObject json; + json.insert("type", "request_join_room"); + json.insert("port", sessionData.Port); + json.insert("player_name", this->nickNameLineEdit->text()); + json.insert("password", password); + json.insert("MD5", QString::fromStdString(md5)); + NetplayCommon::AddCommonJson(json); + + this->webSocket->sendTextMessage(QJsonDocument(json).toJson()); +} diff --git a/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionBrowserDialog.hpp b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionBrowserDialog.hpp new file mode 100644 index 00000000..2f7e2742 --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionBrowserDialog.hpp @@ -0,0 +1,73 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef NETPLAYSESSIONBROWSERDIALOG_HPP +#define NETPLAYSESSIONBROWSERDIALOG_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include "ui_NetplaySessionBrowserDialog.h" + +#include + +namespace UserInterface +{ +namespace Dialog +{ +class NetplaySessionBrowserDialog : public QDialog, private Ui::NetplaySessionBrowserDialog +{ + Q_OBJECT + + public: + NetplaySessionBrowserDialog(QWidget *parent, QWebSocket* webSocket, QMap data); + ~NetplaySessionBrowserDialog(void); + + QJsonObject GetSessionJson(void); + QString GetSessionFile(void); + + private: + QWebSocket* webSocket; + QUdpSocket broadcastSocket; + QJsonObject sessionJson; + QString sessionFile; + QMap romData; + + void showErrorMessage(QString error, QString details); + QString showROMDialog(QString name, QString md5); + + bool validate(void); + void validateJoinButton(void); + + void addSessionData(QString name, QString game, QString md5, bool password, int port, + QString cpuEmulator, QString rspPlugin, QString gfxPlugin); + + private slots: + void on_webSocket_connected(void); + void on_webSocket_textMessageReceived(QString message); + void on_broadcastSocket_readyRead(void); + void on_networkAccessManager_Finished(QNetworkReply* reply); + + void on_serverComboBox_currentIndexChanged(int index); + void on_tableWidget_currentItemChanged(QTableWidgetItem* current, QTableWidgetItem* previous); + + void on_nickNameLineEdit_textChanged(void); + + void accept(void) Q_DECL_OVERRIDE; + +}; +} // namespace Dialog +} // namespace UserInterface + +#endif // NETPLAYSESSIONBROWSERDIALOG_HPP diff --git a/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionBrowserDialog.ui b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionBrowserDialog.ui new file mode 100644 index 00000000..f06bd194 --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionBrowserDialog.ui @@ -0,0 +1,129 @@ + + + NetplaySessionBrowserDialog + + + + 0 + 0 + 723 + 511 + + + + Netplay Session Browser + + + + + + + + Nickname + + + + + + + + 0 + 0 + + + + + + + + + + + + Server + + + + + + + + + + + + true + + + false + + + + Name + + + + + Game + + + + + Game MD5 + + + + + Password? + + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + + + + + + + buttonBox + accepted() + NetplaySessionBrowserDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + NetplaySessionBrowserDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.cpp b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.cpp new file mode 100644 index 00000000..cc26d7ab --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.cpp @@ -0,0 +1,149 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "NetplaySessionDialog.hpp" +#include "NetplayCommon.hpp" + +#include +#include +#include +#include +#include + +#include + +using namespace UserInterface::Dialog; + +NetplaySessionDialog::NetplaySessionDialog(QWidget *parent, QWebSocket* webSocket, QJsonObject sessionJson, QString sessionFile) : QDialog(parent) +{ + this->setupUi(this); + + this->webSocket = webSocket; + + this->nickName = sessionJson.value("player_name").toString(); + this->sessionPort = sessionJson.value("port").toInt(); + this->sessionName = sessionJson.value("room_name").toString(); + this->sessionFile = sessionFile; + + this->sessionNameLineEdit->setText(this->sessionName); + this->gameNameLineEdit->setText(sessionJson.value("game_name").toString()); + + connect(this->webSocket, &QWebSocket::textMessageReceived, this, &NetplaySessionDialog::on_webSocket_textMessageReceived); + + // request server motd + QJsonObject json; + json.insert("type", "request_motd"); + json.insert("room_name", this->sessionName); + NetplayCommon::AddCommonJson(json); + webSocket->sendTextMessage(QJsonDocument(json).toJson()); + + // request players + json.insert("type", "request_players"); + json.insert("port", this->sessionPort); + NetplayCommon::AddCommonJson(json); + webSocket->sendTextMessage(QJsonDocument(json).toJson()); + + QPushButton* startButton = this->buttonBox->button(QDialogButtonBox::Ok); + startButton->setText("Start"); + startButton->setEnabled(false); +} + +NetplaySessionDialog::~NetplaySessionDialog(void) +{ +} + +void NetplaySessionDialog::showErrorMessage(QString error, QString details) +{ + QMessageBox msgBox(this); + msgBox.setIcon(QMessageBox::Icon::Critical); + msgBox.setWindowTitle("Error"); + msgBox.setText(error); + msgBox.setDetailedText(details); + msgBox.addButton(QMessageBox::Ok); + msgBox.exec(); +} + +void NetplaySessionDialog::on_webSocket_textMessageReceived(QString message) +{ + QJsonDocument jsonDocument = QJsonDocument::fromJson(message.toUtf8()); + QJsonObject json = jsonDocument.object(); + QString type = json.value("type").toString(); + + if (type == "reply_players") + { + if (json.contains("player_names")) + { + this->listWidget->clear(); + QString name; + for (int i = 0; i < 4; ++i) + { + name = json.value("player_names").toArray().at(i).toString(); + if (!name.isEmpty()) + { + this->listWidget->addItem(name); + if (this->nickName == name) + { + this->sessionNumber = i + 1; + } + } + if (i == 0) + { + QPushButton* startButton = this->buttonBox->button(QDialogButtonBox::Ok); + startButton->setEnabled(this->nickName == name); + } + } + } + } + else if (type == "reply_chat_message") + { + this->chatPlainTextEdit->appendPlainText(json.value("message").toString()); + } + else if (type == "reply_begin_game") + { + emit OnPlayGame(this->sessionFile, this->webSocket->peerAddress().toString(), this->sessionPort, this->sessionNumber); + QDialog::accept(); + } + else if (type == "reply_motd") + { + QString message = "MOTD: " + json.value("message").toString(); + this->chatPlainTextEdit->appendHtml(message); + this->chatPlainTextEdit->setTextInteractionFlags(Qt::TextBrowserInteraction); + } +} + +void NetplaySessionDialog::on_chatLineEdit_textChanged(QString text) +{ + this->sendPushButton->setEnabled(!text.isEmpty() && text.size() <= 256); +} + +void NetplaySessionDialog::on_sendPushButton_clicked(void) +{ + QJsonObject json; + json.insert("type", "request_chat_message"); + json.insert("port", this->sessionPort); + json.insert("player_name", this->nickName); + json.insert("message", this->chatLineEdit->text()); + NetplayCommon::AddCommonJson(json); + + webSocket->sendTextMessage(QJsonDocument(json).toJson()); + this->chatLineEdit->clear(); +} + +void NetplaySessionDialog::accept() +{ + QPushButton* startButton = this->buttonBox->button(QDialogButtonBox::Ok); + startButton->setEnabled(false); + + QJsonObject json; + json.insert("type", "request_begin_game"); + json.insert("port", this->sessionPort); + NetplayCommon::AddCommonJson(json); + + webSocket->sendTextMessage(QJsonDocument(json).toJson()); +} diff --git a/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.hpp b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.hpp new file mode 100644 index 00000000..19eb8e31 --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.hpp @@ -0,0 +1,57 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef NETPLAYSESSIONDIALOG_HPP +#define NETPLAYSESSIONDIALOG_HPP + +#include +#include +#include +#include + +#include "ui_NetplaySessionDialog.h" + +namespace UserInterface +{ +namespace Dialog +{ +class NetplaySessionDialog : public QDialog, private Ui::NetplaySessionDialog +{ + Q_OBJECT + + public: + NetplaySessionDialog(QWidget *parent, QWebSocket* webSocket, QJsonObject sessionJson, QString sessionFile); + ~NetplaySessionDialog(void); + + private: + QString sessionFile; + QString nickName; + QString sessionName; + int sessionPort = -1; + int sessionNumber = -1; + + QWebSocket* webSocket; + + void showErrorMessage(QString error, QString details); + + private slots: + void on_webSocket_textMessageReceived(QString message); + + void on_chatLineEdit_textChanged(QString text); + void on_sendPushButton_clicked(void); + + void accept(void) Q_DECL_OVERRIDE; + + signals: + void OnPlayGame(QString file, QString address, int port, int player); +}; +} // namespace Dialog +} // namespace UserInterface + +#endif // NETPLAYSESSIONDIALOG_HPP diff --git a/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.ui b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.ui new file mode 100644 index 00000000..f741f92a --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.ui @@ -0,0 +1,164 @@ + + + NetplaySessionDialog + + + + 0 + 0 + 603 + 470 + + + + Netplay Session + + + + + + + + Session name + + + + + + + + 0 + 0 + + + + true + + + + + + + + + + + Game name + + + + + + + + 0 + 0 + + + + true + + + + + + + + + + + Chat + + + + + + true + + + + + + + + + + + + false + + + Send + + + + + + + + + + + + Players + + + + + + + + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + false + + + + + + + + + buttonBox + accepted() + NetplaySessionDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + NetplaySessionDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionPasswordDialog.cpp b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionPasswordDialog.cpp new file mode 100644 index 00000000..b1bf0639 --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionPasswordDialog.cpp @@ -0,0 +1,41 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "NetplaySessionPasswordDialog.hpp" +#include "NetplayCommon.hpp" + +#include +#include + +#include + +using namespace UserInterface::Dialog; + +NetplaySessionPasswordDialog::NetplaySessionPasswordDialog(QWidget *parent) : QDialog(parent) +{ + this->setupUi(this); + + QRegularExpression re(NETPLAYCOMMON_PASSWORD_REGEX); + this->lineEdit->setValidator(new QRegularExpressionValidator(re, this)); +} + +NetplaySessionPasswordDialog::~NetplaySessionPasswordDialog(void) +{ +} + +QString NetplaySessionPasswordDialog::GetPassword(void) +{ + return this->password; +} + +void NetplaySessionPasswordDialog::accept() +{ + this->password = this->lineEdit->text(); + QDialog::accept(); +} diff --git a/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionPasswordDialog.hpp b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionPasswordDialog.hpp new file mode 100644 index 00000000..9e2c7aff --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionPasswordDialog.hpp @@ -0,0 +1,41 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef NETPLAYSESSIONPASSWORDDIALOG_HPP +#define NETPLAYSESSIONPASSWORDDIALOG_HPP + +#include +#include + +#include "ui_NetplaySessionPasswordDialog.h" + +namespace UserInterface +{ +namespace Dialog +{ +class NetplaySessionPasswordDialog : public QDialog, private Ui::NetplaySessionPasswordDialog +{ + Q_OBJECT + + public: + NetplaySessionPasswordDialog(QWidget *parent); + ~NetplaySessionPasswordDialog(void); + + QString GetPassword(void); + + private: + QString password; + + private slots: + void accept(void) Q_DECL_OVERRIDE; +}; +} // namespace Dialog +} // namespace UserInterface + +#endif // NETPLAYSESSIONPASSWORDDIALOG_HPP diff --git a/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionPasswordDialog.ui b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionPasswordDialog.ui new file mode 100644 index 00000000..60a2ae27 --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionPasswordDialog.ui @@ -0,0 +1,82 @@ + + + NetplaySessionPasswordDialog + + + + 0 + 0 + 229 + 86 + + + + Enter Password + + + + + + + + Password: + + + + + + + QLineEdit::EchoMode::Password + + + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + + + + + + + buttonBox + accepted() + NetplaySessionPasswordDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + NetplaySessionPasswordDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Source/RMG/UserInterface/Dialog/SettingsDialog.cpp b/Source/RMG/UserInterface/Dialog/SettingsDialog.cpp index 5ac69bd2..826a47b6 100644 --- a/Source/RMG/UserInterface/Dialog/SettingsDialog.cpp +++ b/Source/RMG/UserInterface/Dialog/SettingsDialog.cpp @@ -12,7 +12,10 @@ #include "RMG-Core/DiscordRpc.hpp" #include "RMG-Core/Settings/Settings.hpp" #include "UserInterface/Widget/KeybindButton.hpp" +#include "UserInterface/Dialog/Netplay/NetplayCommon.hpp" +#include +#include #include #include #include @@ -33,15 +36,16 @@ enum class SettingsDialogTab InterfaceRomBrowser = 2, InterfaceLog = 3, InterfaceOSD = 4, - Hotkey = 5, - Core = 6, - Game = 7, - GameCore = 8, - GamePlugin = 9, - Plugin = 10, - Directory = 11, - N64DD = 12, - Invalid = 13 + InterfaceNetplay = 5, + Hotkey = 6, + Core = 7, + Game = 8, + GameCore = 9, + GamePlugin = 10, + Plugin = 11, + Directory = 12, + N64DD = 13, + Invalid = 14 }; @@ -77,7 +81,7 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent) pluginList = CoreGetAllPlugins(); - for (int i = 0; i < 14; i++) + for (int i = 0; i < (int)SettingsDialogTab::Invalid; i++) { this->loadSettings(i); } @@ -167,6 +171,9 @@ void SettingsDialog::restoreDefaults(int stackedWidgetIndex) case SettingsDialogTab::InterfaceOSD: this->loadDefaultInterfaceOSDSettings(); break; + case SettingsDialogTab::InterfaceNetplay: + this->loadDefaultInterfaceNetplaySettings(); + break; case SettingsDialogTab::Hotkey: this->loadDefaultHotkeySettings(); break; @@ -221,6 +228,9 @@ void SettingsDialog::loadSettings(int stackedWidgetIndex) case SettingsDialogTab::InterfaceOSD: this->loadInterfaceOSDSettings(); break; + case SettingsDialogTab::InterfaceNetplay: + this->loadInterfaceNetplaySettings(); + break; case SettingsDialogTab::Hotkey: this->loadHotkeySettings(); break; @@ -513,6 +523,20 @@ void SettingsDialog::loadInterfaceOSDSettings(void) } } +void SettingsDialog::loadInterfaceNetplaySettings(void) +{ + // TODO: maybe add initialize functions for each tab? + static bool initialized = false; + if (!initialized) + { // set regexp for nickname + QRegularExpression re(NETPLAYCOMMON_NICKNAME_REGEX); + this->netplayNicknameLineEdit->setValidator(new QRegularExpressionValidator(re, this)); + initialized = true; + } + + this->netplayNicknameLineEdit->setText(QString::fromStdString(CoreSettingsGetStringValue(SettingsID::Netplay_Nickname))); + this->netplayServerUrlLineEdit->setText(QString::fromStdString(CoreSettingsGetStringValue(SettingsID::Netplay_ServerJsonUrl))); +} void SettingsDialog::loadDefaultCoreSettings(void) { @@ -683,6 +707,11 @@ void SettingsDialog::loadDefaultInterfaceOSDSettings(void) } } +void SettingsDialog::loadDefaultInterfaceNetplaySettings(void) +{ + this->netplayNicknameLineEdit->setText(QString::fromStdString(CoreSettingsGetDefaultStringValue(SettingsID::Netplay_Nickname))); + this->netplayServerUrlLineEdit->setText(QString::fromStdString(CoreSettingsGetDefaultStringValue(SettingsID::Netplay_ServerJsonUrl))); +} void SettingsDialog::saveSettings(void) { @@ -704,6 +733,7 @@ void SettingsDialog::saveSettings(void) this->saveInterfaceRomBrowserSettings(); this->saveInterfaceLogSettings(); this->saveInterfaceOSDSettings(); + this->saveInterfaceNetplaySettings(); CoreSettingsSave(); } @@ -910,6 +940,12 @@ void SettingsDialog::saveInterfaceOSDSettings(void) })); } +void SettingsDialog::saveInterfaceNetplaySettings(void) +{ + CoreSettingsSetValue(SettingsID::Netplay_Nickname, this->netplayNicknameLineEdit->text().toStdString()); + CoreSettingsSetValue(SettingsID::Netplay_ServerJsonUrl, this->netplayServerUrlLineEdit->text().toStdString()); +} + void SettingsDialog::commonHotkeySettings(SettingsDialogAction action) { struct keybinding diff --git a/Source/RMG/UserInterface/Dialog/SettingsDialog.hpp b/Source/RMG/UserInterface/Dialog/SettingsDialog.hpp index b6bdd824..5a355c5e 100644 --- a/Source/RMG/UserInterface/Dialog/SettingsDialog.hpp +++ b/Source/RMG/UserInterface/Dialog/SettingsDialog.hpp @@ -83,7 +83,7 @@ class SettingsDialog : public QDialog, private Ui::SettingsDialog void loadInterfaceRomBrowserSettings(void); void loadInterfaceLogSettings(void); void loadInterfaceOSDSettings(void); - void loadInterfaceStyleSettings(void); + void loadInterfaceNetplaySettings(void); void loadDefaultCoreSettings(void); void loadDefaultGameSettings(void); @@ -98,7 +98,7 @@ class SettingsDialog : public QDialog, private Ui::SettingsDialog void loadDefaultInterfaceRomBrowserSettings(void); void loadDefaultInterfaceLogSettings(void); void loadDefaultInterfaceOSDSettings(void); - void loadDefaultInterfaceStyleSettings(void); + void loadDefaultInterfaceNetplaySettings(void); void saveSettings(void); void saveCoreSettings(void); @@ -114,7 +114,7 @@ class SettingsDialog : public QDialog, private Ui::SettingsDialog void saveInterfaceRomBrowserSettings(void); void saveInterfaceLogSettings(void); void saveInterfaceOSDSettings(void); - void saveInterfaceStyleSettings(void); + void saveInterfaceNetplaySettings(void); void commonHotkeySettings(SettingsDialogAction); void commonPluginSettings(SettingsDialogAction); diff --git a/Source/RMG/UserInterface/Dialog/SettingsDialog.ui b/Source/RMG/UserInterface/Dialog/SettingsDialog.ui index 926a9d5d..b8add6e2 100644 --- a/Source/RMG/UserInterface/Dialog/SettingsDialog.ui +++ b/Source/RMG/UserInterface/Dialog/SettingsDialog.ui @@ -625,6 +625,68 @@ + + + Netplay + + + + + + + + Nickname + + + + + + + + 0 + 0 + + + + + + + + + + + + Server URL + + + + + + + + 0 + 0 + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 435 + + + + + + diff --git a/Source/RMG/UserInterface/MainWindow.cpp b/Source/RMG/UserInterface/MainWindow.cpp index 5f7c3e29..89cdb05e 100644 --- a/Source/RMG/UserInterface/MainWindow.cpp +++ b/Source/RMG/UserInterface/MainWindow.cpp @@ -9,35 +9,51 @@ */ #include "MainWindow.hpp" +#include + #include "UserInterface/Dialog/AboutDialog.hpp" +#include "Dialog/Cheats/CheatsDialog.hpp" +#include "Dialog/SettingsDialog.hpp" +#include "Dialog/RomInfoDialog.hpp" #ifdef UPDATER -#include "UserInterface/Dialog/Update/UpdateDialog.hpp" #include "UserInterface/Dialog/Update/DownloadUpdateDialog.hpp" #include "UserInterface/Dialog/Update/InstallUpdateDialog.hpp" +#include "UserInterface/Dialog/Update/UpdateDialog.hpp" #endif // UPDATER +#ifdef NETPLAY +#include "Dialog/Netplay/NetplaySessionBrowserDialog.hpp" +#include "Dialog/Netplay/CreateNetplaySessionDialog.hpp" +#include "Dialog/Netplay/NetplaySessionDialog.hpp" +#endif // NETPLAY #include "UserInterface/EventFilter.hpp" #include "Utilities/QtKeyToSdl2Key.hpp" #include "OnScreenDisplay.hpp" #include "Callbacks.hpp" #include "VidExt.hpp" -#include - +#ifdef UPDATER +#include +#include +#include +#endif // UPDATER +#ifdef NETPLAY +#include +#endif // NETPLAY #include #include #include #include +#include #include -#include #include #include #include #include +#include #include -#include -#include #include #include +#include using namespace UserInterface; @@ -78,6 +94,10 @@ bool MainWindow::Init(QApplication* app, bool showUI, bool launchROM) this->action_Help_Update->setVisible(false); #endif // UPDATER +#ifndef NETPLAY + this->menuNetplay->menuAction()->setVisible(false); +#endif // NETPLAY + this->initializeEmulationThread(); this->connectEmulationThreadSignals(); @@ -575,6 +595,25 @@ void MainWindow::connectEmulationThreadSignals(void) Qt::BlockingQueuedConnection); } +void MainWindow::launchEmulationThread(QString cartRom, QString address, int port, int player) +{ + // TODO: update UI... + CoreSettingsSave(); + + if (this->emulationThread->isRunning()) + { + this->on_Action_System_Shutdown(); + + while (this->emulationThread->isRunning()) + { + QCoreApplication::processEvents(); + } + } + + this->emulationThread->SetNetplay(address, port, player); + this->launchEmulationThread(cartRom); +} + void MainWindow::launchEmulationThread(QString cartRom, QString diskRom, bool refreshRomListAfterEmulation, int slot) { CoreSettingsSave(); @@ -649,23 +688,23 @@ void MainWindow::updateActions(bool inEmulation, bool isPaused) this->action_System_Shutdown->setEnabled(inEmulation); this->menuReset->setEnabled(inEmulation); keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_SoftReset)); - this->action_System_SoftReset->setEnabled(inEmulation); + this->action_System_SoftReset->setEnabled(inEmulation && !CoreHasInitNetplay()); this->action_System_SoftReset->setShortcut(QKeySequence(keyBinding)); keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_HardReset)); - this->action_System_HardReset->setEnabled(inEmulation); + this->action_System_HardReset->setEnabled(inEmulation && !CoreHasInitNetplay()); this->action_System_HardReset->setShortcut(QKeySequence(keyBinding)); keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_Resume)); this->action_System_Pause->setChecked(isPaused); - this->action_System_Pause->setEnabled(inEmulation); + this->action_System_Pause->setEnabled(inEmulation && !CoreHasInitNetplay()); this->action_System_Pause->setShortcut(QKeySequence(keyBinding)); keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_Screenshot)); this->action_System_Screenshot->setEnabled(inEmulation); this->action_System_Screenshot->setShortcut(QKeySequence(keyBinding)); keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_LimitFPS)); - this->action_System_LimitFPS->setEnabled(inEmulation); + this->action_System_LimitFPS->setEnabled(inEmulation && !CoreHasInitNetplay()); this->action_System_LimitFPS->setShortcut(QKeySequence(keyBinding)); this->action_System_LimitFPS->setChecked(CoreIsSpeedLimiterEnabled()); - this->menuSpeedFactor->setEnabled(inEmulation); + this->menuSpeedFactor->setEnabled(inEmulation && !CoreHasInitNetplay()); keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_SaveState)); this->action_System_SaveState->setEnabled(inEmulation); this->action_System_SaveState->setShortcut(QKeySequence(keyBinding)); @@ -673,17 +712,17 @@ void MainWindow::updateActions(bool inEmulation, bool isPaused) this->action_System_SaveAs->setEnabled(inEmulation); this->action_System_SaveAs->setShortcut(QKeySequence(keyBinding)); keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_LoadState)); - this->action_System_LoadState->setEnabled(inEmulation); + this->action_System_LoadState->setEnabled(inEmulation && !CoreHasInitNetplay()); this->action_System_LoadState->setShortcut(QKeySequence(keyBinding)); keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_Load)); - this->action_System_Load->setEnabled(inEmulation); + this->action_System_Load->setEnabled(inEmulation && !CoreHasInitNetplay()); this->action_System_Load->setShortcut(QKeySequence(keyBinding)); this->menuCurrent_Save_State->setEnabled(inEmulation); keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_Cheats)); - this->action_System_Cheats->setEnabled(inEmulation); + this->action_System_Cheats->setEnabled(inEmulation && !CoreHasInitNetplay()); this->action_System_Cheats->setShortcut(QKeySequence(keyBinding)); keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_GSButton)); - this->action_System_GSButton->setEnabled(inEmulation); + this->action_System_GSButton->setEnabled(inEmulation && !CoreHasInitNetplay()); this->action_System_GSButton->setShortcut(QKeySequence(keyBinding)); keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_Exit)); this->action_System_Exit->setShortcut(QKeySequence(keyBinding)); @@ -757,6 +796,9 @@ void MainWindow::updateActions(bool inEmulation, bool isPaused) this->action_View_Log->setShortcut(QKeySequence(keyBinding)); this->action_View_ClearRomCache->setEnabled(!inEmulation); + this->action_Netplay_CreateSession->setEnabled(!inEmulation); + this->action_Netplay_JoinSession->setEnabled(!inEmulation); + keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_IncreaseVolume)); this->action_Audio_IncreaseVolume->setShortcut(QKeySequence(keyBinding)); this->action_Audio_IncreaseVolume->setEnabled(inEmulation); @@ -1084,6 +1126,9 @@ void MainWindow::connectActionSignals(void) connect(this->action_View_ClearRomCache, &QAction::triggered, this, &MainWindow::on_Action_View_ClearRomCache); connect(this->action_View_Log, &QAction::triggered, this, &MainWindow::on_Action_View_Log); + connect(this->action_Netplay_CreateSession, &QAction::triggered, this, &MainWindow::on_Action_Netplay_CreateSession); + connect(this->action_Netplay_JoinSession, &QAction::triggered, this, &MainWindow::on_Action_Netplay_JoinSession); + connect(this->action_Help_Github, &QAction::triggered, this, &MainWindow::on_Action_Help_Github); connect(this->action_Help_About, &QAction::triggered, this, &MainWindow::on_Action_Help_About); connect(this->action_Help_Update, &QAction::triggered, this, &MainWindow::on_Action_Help_Update); @@ -1515,6 +1560,11 @@ void MainWindow::on_Action_System_HardReset(void) } void MainWindow::on_Action_System_Pause(void) { + if (!this->action_System_Pause->isEnabled()) + { + return; + } + bool isPaused = CoreIsEmulationPaused(); bool ret; @@ -1838,6 +1888,38 @@ void MainWindow::on_Action_View_Log(void) this->logDialog.show(); } +void MainWindow::on_Action_Netplay_CreateSession(void) +{ +#ifdef NETPLAY + QWebSocket webSocket; + + Dialog::CreateNetplaySessionDialog dialog(this, &webSocket, this->ui_Widget_RomBrowser->GetModelData()); + int ret = dialog.exec(); + if (ret == QDialog::Accepted) + { + Dialog::NetplaySessionDialog sessionDialog(this, &webSocket, dialog.GetSessionJson(), dialog.GetSessionFile()); + connect(&sessionDialog, &Dialog::NetplaySessionDialog::OnPlayGame, this, &MainWindow::on_Netplay_PlayGame); + sessionDialog.exec(); + } +#endif // NETPLAY +} + +void MainWindow::on_Action_Netplay_JoinSession(void) +{ +#ifdef NETPLAY + QWebSocket webSocket; + + Dialog::NetplaySessionBrowserDialog dialog(this, &webSocket, this->ui_Widget_RomBrowser->GetModelData()); + int ret = dialog.exec(); + if (ret == QDialog::Accepted) + { + Dialog::NetplaySessionDialog sessionDialog(this, &webSocket, dialog.GetSessionJson(), dialog.GetSessionFile()); + connect(&sessionDialog, &Dialog::NetplaySessionDialog::OnPlayGame, this, &MainWindow::on_Netplay_PlayGame); + sessionDialog.exec(); + } +#endif // NETPLAY +} + void MainWindow::on_Action_Help_Github(void) { QDesktopServices::openUrl(QUrl("https://github.com/Rosalie241/RMG")); @@ -2117,6 +2199,11 @@ void MainWindow::on_RomBrowser_Cheats(QString file) } } +void MainWindow::on_Netplay_PlayGame(QString file, QString address, int port, int player) +{ + this->launchEmulationThread(file, address, port, player); +} + void MainWindow::on_VidExt_Init(VidExtRenderMode renderMode) { this->ui_VidExtRenderMode = renderMode; diff --git a/Source/RMG/UserInterface/MainWindow.hpp b/Source/RMG/UserInterface/MainWindow.hpp index f4c652fe..e6daca4b 100644 --- a/Source/RMG/UserInterface/MainWindow.hpp +++ b/Source/RMG/UserInterface/MainWindow.hpp @@ -14,31 +14,22 @@ #include "EventFilter.hpp" #include "Callbacks.hpp" +#include "Widget/RomBrowser/RomBrowserWidget.hpp" #include "Widget/Render/DummyWidget.hpp" #include "Widget/Render/OGLWidget.hpp" #include "Widget/Render/VKWidget.hpp" -#include "Widget/RomBrowser/RomBrowserWidget.hpp" -#include "Dialog/SettingsDialog.hpp" -#include "Dialog/Cheats/CheatsDialog.hpp" -#include "Dialog/RomInfoDialog.hpp" #include "Dialog/LogDialog.hpp" -#include -#include -#include -#include -#include -#include -#include -#include - #ifdef UPDATER -#include #include -#include -#include #endif // UPDATER +#include +#include +#include +#include +#include +#include #include "ui_MainWindow.h" @@ -140,6 +131,7 @@ class MainWindow : public QMainWindow, private Ui::MainWindow void initializeEmulationThread(void); void connectEmulationThreadSignals(void); + void launchEmulationThread(QString cartRom, QString address, int port, int player); void launchEmulationThread(QString cartRom, QString diskRom = "", bool refreshRomListAfterEmulation = false, int slot = -1); QString getSaveStateSlotDateTimeText(QAction* action); @@ -209,6 +201,9 @@ class MainWindow : public QMainWindow, private Ui::MainWindow void on_Action_View_ClearRomCache(void); void on_Action_View_Log(void); + void on_Action_Netplay_CreateSession(void); + void on_Action_Netplay_JoinSession(void); + void on_Action_Help_Github(void); void on_Action_Help_About(void); void on_Action_Help_Update(void); @@ -229,6 +224,8 @@ class MainWindow : public QMainWindow, private Ui::MainWindow void on_RomBrowser_EditGameInputSettings(QString file); void on_RomBrowser_Cheats(QString file); + void on_Netplay_PlayGame(QString file, QString address, int port, int player); + public slots: void on_VidExt_Init(VidExtRenderMode renderMode); diff --git a/Source/RMG/UserInterface/MainWindow.ui b/Source/RMG/UserInterface/MainWindow.ui index 50b5cc35..ba6ef75c 100644 --- a/Source/RMG/UserInterface/MainWindow.ui +++ b/Source/RMG/UserInterface/MainWindow.ui @@ -31,26 +31,24 @@ - System + S&ystem - Reset + &Reset - - .. + - Current Save State + &Current Save State - - .. + @@ -68,11 +66,10 @@ true - Speed Factor + Speed &Factor - - .. + @@ -113,7 +110,7 @@ - Settings + Setti&ngs @@ -143,7 +140,7 @@ - Help + He&lp @@ -151,9 +148,17 @@ + + + Netplay + + + + + @@ -165,7 +170,7 @@ true - Qt::ToolButtonTextUnderIcon + Qt::ToolButtonStyle::ToolButtonTextUnderIcon false @@ -190,47 +195,42 @@ - - .. + - Start ROM + &Start ROM - - .. + - Start Combo + Start Co&mbo - - .. + - Shutdown + S&hutdown - - .. + - Soft Reset + &Soft Reset - - .. + - Hard Reset + &Hard Reset @@ -238,20 +238,18 @@ true - - .. + - Pause + &Pause - - .. + - Screenshot + Scree&nshot @@ -259,26 +257,23 @@ true - - .. + - Limit FPS + &Limit FPS - - .. + - Save State + Sa&ve State - - .. + Save As... @@ -286,20 +281,18 @@ - - .. + - Load State + L&oad State - - .. + - Load... + Loa&d... @@ -307,7 +300,7 @@ true - Slot 0 + &Slot 0 @@ -315,7 +308,7 @@ true - Slot 1 + Slot &1 @@ -323,7 +316,7 @@ true - Slot 2 + Slot &2 @@ -331,7 +324,7 @@ true - Slot 3 + Slot &3 @@ -339,7 +332,7 @@ true - Slot 4 + Slot &4 @@ -347,7 +340,7 @@ true - Slot 5 + Slot &5 @@ -355,7 +348,7 @@ true - Slot 6 + Slot &6 @@ -363,7 +356,7 @@ true - Slot 7 + Slot &7 @@ -371,7 +364,7 @@ true - Slot 8 + Slot &8 @@ -379,13 +372,12 @@ true - Slot 9 + Slot &9 - - .. + Cheats... @@ -393,92 +385,82 @@ - - .. + - GS Button + &GS Button - - .. + - Exit + &Exit - - .. + - Graphics + &Graphics - - .. + - Input + &Input - - .. + - RSP + &RSP - - .. + - Audio + &Audio - - .. + - Settings + &Settings - - .. + - Fullscreen + &Fullscreen - - .. + - Github Repository + &Github Repository - - .. + - About RMG + &About RMG @@ -486,11 +468,10 @@ true - - .. + - Toolbar + &Toolbar @@ -498,56 +479,50 @@ true - - .. + - Status Bar + &Status Bar - - .. + - Game List + &Game List - - .. + - Game Grid + Ga&me Grid - - .. + - Refresh ROMs + &Refresh ROMs - - .. + - Log + &Log - - .. + - Clear ROM Cache + &Clear ROM Cache @@ -555,11 +530,10 @@ true - - .. + - Uniform Size (Grid View) + &Uniform Size (Grid View) @@ -567,17 +541,17 @@ false - 25% + &25% - 50% + &50% - 100% + &100% @@ -592,12 +566,12 @@ - 200% + 2&00% - 75% + &75% @@ -622,16 +596,31 @@ - 300% + &300% - - .. + + + + &Check For Updates + + + + + + + + &Create Session + + + + + - Check For Updates + &Join Session diff --git a/Source/RMG/UserInterface/Resource/icons/black/svg/plug-line.svg b/Source/RMG/UserInterface/Resource/icons/black/svg/plug-line.svg new file mode 100644 index 00000000..784ad078 --- /dev/null +++ b/Source/RMG/UserInterface/Resource/icons/black/svg/plug-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Source/RMG/UserInterface/Resource/icons/black/svg/server-line.svg b/Source/RMG/UserInterface/Resource/icons/black/svg/server-line.svg new file mode 100644 index 00000000..42411a81 --- /dev/null +++ b/Source/RMG/UserInterface/Resource/icons/black/svg/server-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Source/RMG/UserInterface/Resource/icons/white/svg/plug-line.svg b/Source/RMG/UserInterface/Resource/icons/white/svg/plug-line.svg new file mode 100644 index 00000000..b1143c3e --- /dev/null +++ b/Source/RMG/UserInterface/Resource/icons/white/svg/plug-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Source/RMG/UserInterface/Resource/icons/white/svg/server-line.svg b/Source/RMG/UserInterface/Resource/icons/white/svg/server-line.svg new file mode 100644 index 00000000..66593c87 --- /dev/null +++ b/Source/RMG/UserInterface/Resource/icons/white/svg/server-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Source/RMG/UserInterface/UIResources.qrc b/Source/RMG/UserInterface/UIResources.qrc index 17bc14d3..6133cbd2 100644 --- a/Source/RMG/UserInterface/UIResources.qrc +++ b/Source/RMG/UserInterface/UIResources.qrc @@ -23,6 +23,7 @@ Resource/icons/black/svg/list-check.svg Resource/icons/black/svg/pause-line.svg Resource/icons/black/svg/play-line.svg + Resource/icons/black/svg/plug-line.svg Resource/icons/black/svg/refresh-line.svg Resource/icons/black/svg/restart-line.svg Resource/icons/black/svg/save-3-line.svg @@ -30,6 +31,7 @@ Resource/icons/black/svg/shut-down-line.svg Resource/icons/black/svg/speed-line.svg Resource/icons/black/svg/tools-line.svg + Resource/icons/black/svg/server-line.svg Resource/icons/black/svg/settings-3-line.svg Resource/icons/black/svg/volume-up-line.svg @@ -53,6 +55,7 @@ Resource/icons/white/svg/list-check.svg Resource/icons/white/svg/pause-line.svg Resource/icons/white/svg/play-line.svg + Resource/icons/white/svg/plug-line.svg Resource/icons/white/svg/refresh-line.svg Resource/icons/white/svg/restart-line.svg Resource/icons/white/svg/save-3-line.svg @@ -60,6 +63,7 @@ Resource/icons/white/svg/shut-down-line.svg Resource/icons/white/svg/speed-line.svg Resource/icons/white/svg/tools-line.svg + Resource/icons/white/svg/server-line.svg Resource/icons/white/svg/settings-3-line.svg Resource/icons/white/svg/volume-up-line.svg diff --git a/Source/RMG/UserInterface/Widget/RomBrowser/RomBrowserWidget.cpp b/Source/RMG/UserInterface/Widget/RomBrowser/RomBrowserWidget.cpp index 134fe464..372e93e2 100644 --- a/Source/RMG/UserInterface/Widget/RomBrowser/RomBrowserWidget.cpp +++ b/Source/RMG/UserInterface/Widget/RomBrowser/RomBrowserWidget.cpp @@ -335,6 +335,26 @@ void RomBrowserWidget::SetGridViewUniformSizes(bool value) this->gridViewWidget->setUniformItemSizes(value); } +QMap RomBrowserWidget::GetModelData(void) +{ + QMap data; + QStandardItemModel* model = this->getCurrentModel(); + RomBrowserModelData modelData; + + if (model == nullptr) + { + return data; + } + + for (int i = 0; i < model->rowCount(); i++) + { + modelData = model->item(i)->data().value(); + data.insert(modelData.file, modelData.settings); + } + + return data; +} + QStandardItemModel* RomBrowserWidget::getCurrentModel(void) { QWidget* currentWidget = this->currentWidget(); diff --git a/Source/RMG/UserInterface/Widget/RomBrowser/RomBrowserWidget.hpp b/Source/RMG/UserInterface/Widget/RomBrowser/RomBrowserWidget.hpp index fdc0a308..c24a6300 100644 --- a/Source/RMG/UserInterface/Widget/RomBrowser/RomBrowserWidget.hpp +++ b/Source/RMG/UserInterface/Widget/RomBrowser/RomBrowserWidget.hpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -53,6 +54,8 @@ class RomBrowserWidget : public QStackedWidget void SetGridViewUniformSizes(bool value); + QMap GetModelData(void); + private: Widget::RomBrowserEmptyWidget* emptyWidget = nullptr; Widget::RomBrowserLoadingWidget* loadingWidget = nullptr; diff --git a/Source/Script/Build.sh b/Source/Script/Build.sh index 0f9e382b..66177990 100755 --- a/Source/Script/Build.sh +++ b/Source/Script/Build.sh @@ -15,7 +15,7 @@ fi mkdir -p "$build_dir" -cmake -S "$toplvl_dir" -B "$build_dir" -DCMAKE_BUILD_TYPE="$build_config" -DPORTABLE_INSTALL=ON -DUSE_ANGRYLION=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -G "Ninja" +cmake -S "$toplvl_dir" -B "$build_dir" -DCMAKE_BUILD_TYPE="$build_config" -DNETPLAY=ON -DPORTABLE_INSTALL=ON -DUSE_ANGRYLION=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -G "Ninja" cmake --build "$build_dir" --parallel "$threads"