From 352d1eaedefeaf79cf1aac422f88d3838a06cf43 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Thu, 11 May 2023 02:13:45 +1000 Subject: [PATCH] Qt: Add open/join session UI --- src/core/netplay.cpp | 103 ++++++++--- src/core/netplay.h | 7 +- .../createnetplaysessiondialog.ui | 164 ++++++++++++++++++ src/duckstation-qt/duckstation-qt.vcxproj | 9 + .../duckstation-qt.vcxproj.filters | 9 + .../joinnetplaysessiondialog.ui | 161 +++++++++++++++++ src/duckstation-qt/mainwindow.cpp | 22 ++- src/duckstation-qt/mainwindow.h | 4 +- src/duckstation-qt/mainwindow.ui | 17 +- src/duckstation-qt/netplaydialogs.cpp | 101 +++++++++++ src/duckstation-qt/netplaydialogs.h | 50 ++++++ src/duckstation-qt/qthost.cpp | 44 ++++- src/duckstation-qt/qthost.h | 2 + 13 files changed, 663 insertions(+), 30 deletions(-) create mode 100644 src/duckstation-qt/createnetplaysessiondialog.ui create mode 100644 src/duckstation-qt/joinnetplaysessiondialog.ui create mode 100644 src/duckstation-qt/netplaydialogs.cpp create mode 100644 src/duckstation-qt/netplaydialogs.h diff --git a/src/core/netplay.cpp b/src/core/netplay.cpp index dac1c0e3b..94c9ccf48 100644 --- a/src/core/netplay.cpp +++ b/src/core/netplay.cpp @@ -97,8 +97,7 @@ static void HandleChatMessage(s32 player_id, const ENetPacket* pkt); // l = local, r = remote static void CreateGGPOSession(); static void DestroyGGPOSession(); -static bool Start(s32 lhandle, u16 lport, const std::string& raddr, u16 rport, s32 ldelay, u32 pred, - std::string game_path); +static bool Start(bool is_hosting, std::string nickname, const std::string& remote_addr, s32 port, s32 ldelay); static void CloseSession(); // Host functions. @@ -275,20 +274,40 @@ static const T* CheckReceivedPacket(s32 player_id, const ENetPacket* pkt) // Netplay Impl -bool Netplay::Start(s32 lhandle, u16 lport, const std::string& raddr, u16 rport, s32 ldelay, u32 pred, - std::string game_path) +bool Netplay::Start(bool is_hosting, std::string nickname, const std::string& remote_addr, s32 port, s32 ldelay) { - const bool is_hosting = (lhandle == 1); + if (IsActive()) + { + Log_ErrorPrintf("Netplay session already active"); + return false; + } + + // Port should be valid regardless of hosting/joining. + if (port < 0 || port >= 65535) + { + Log_ErrorPrintf("Invalid port %d", port); + return false; + } + + // Need a system if we're hosting. + if (!System::IsValid()) + { + if (is_hosting) + { + Log_ErrorPrintf("Can't host a netplay session without a valid VM"); + return false; + } + else if (!is_hosting && !CreateSystem(std::string(), false)) + { + Log_ErrorPrintf("Failed to create VM for joining session"); + return false; + } + } + s_state = SessionState::Initializing; SetSettings(); - if (!CreateSystem(std::move(game_path), is_hosting)) - { - Log_ErrorPrintf("Failed to create system."); - return false; - } - if (!InitializeEnet()) { Log_ErrorPrintf("Failed to initialize Enet."); @@ -298,7 +317,7 @@ bool Netplay::Start(s32 lhandle, u16 lport, const std::string& raddr, u16 rport, // Create our "host" (which is basically just our port). ENetAddress server_address; server_address.host = ENET_HOST_ANY; - server_address.port = lport; + server_address.port = is_hosting ? static_cast(port) : ENET_PORT_ANY; s_enet_host = enet_host_create(&server_address, MAX_PLAYERS - 1, NUM_ENET_CHANNELS, 0, 0); if (!s_enet_host) { @@ -307,7 +326,7 @@ bool Netplay::Start(s32 lhandle, u16 lport, const std::string& raddr, u16 rport, } s_host_player_id = 0; - s_local_nickname = fmt::format("NICKNAME{}", lhandle); + s_local_nickname = std::move(nickname); s_local_delay = ldelay; s_reset_cookie = 0; s_reset_players.reset(); @@ -321,7 +340,8 @@ bool Netplay::Start(s32 lhandle, u16 lport, const std::string& raddr, u16 rport, s_reset_players = 1; CreateGGPOSession(); s_state = SessionState::Running; - Log_InfoPrintf("Netplay session started as host."); + Log_InfoPrintf("Netplay session started as host on port %d.", port); + System::SetState(System::State::Paused); return true; } @@ -330,10 +350,10 @@ bool Netplay::Start(s32 lhandle, u16 lport, const std::string& raddr, u16 rport, // Connect to host. ENetAddress host_address; - host_address.port = rport; - if (enet_address_set_host_ip(&host_address, raddr.c_str()) != 0) + host_address.port = static_cast(port); + if (enet_address_set_host(&host_address, remote_addr.c_str()) != 0) { - Log_ErrorPrintf("Failed to parse host: '%s'", raddr.c_str()); + Log_ErrorPrintf("Failed to parse host: '%s'", remote_addr.c_str()); return false; } @@ -348,6 +368,7 @@ bool Netplay::Start(s32 lhandle, u16 lport, const std::string& raddr, u16 rport, // Wait until we're connected to the main host. They'll send us back state to load and a full player list. s_state = SessionState::Connecting; s_reset_start_time.Reset(); + System::SetState(System::State::Paused); return true; } @@ -1409,6 +1430,8 @@ void Netplay::SetSettings() Settings::GetControllerTypeName(ControllerType::DigitalController)); } + //si.SetStringValue("CPU", "ExecutionMode", "Interpreter"); + // No runahead or rewind, that'd be a disaster. si.SetIntValue("Main", "RunaheadFrameCount", 0); si.SetBoolValue("Main", "RewindEnable", false); @@ -1619,16 +1642,23 @@ void Netplay::SetInputs(Netplay::Input inputs[2]) } } -void Netplay::StartNetplaySession(s32 local_handle, u16 local_port, std::string& remote_addr, u16 remote_port, +void Netplay::StartNetplaySession(s32 local_handle, u16 local_port, const std::string& remote_addr, u16 remote_port, s32 input_delay, std::string game_path) { // dont want to start a session when theres already one going on. if (IsActive()) return; + const bool is_hosting = (local_handle == 1); + if (!CreateSystem(std::move(game_path), is_hosting)) + { + Log_ErrorPrintf("Failed to create system."); + return; + } + // create session - if (!Netplay::Start(local_handle, local_port, remote_addr, remote_port, input_delay, MAX_ROLLBACK_FRAMES, - std::move(game_path))) + std::string nickname = fmt::format("NICKNAME{}", local_handle); + if (!Netplay::Start(is_hosting, std::move(nickname), remote_addr, is_hosting ? local_port : remote_port, input_delay)) { // this'll call back to us to shut everything netplay-related down Log_ErrorPrint("Failed to Create Netplay Session!"); @@ -1652,6 +1682,36 @@ void Netplay::StopNetplaySession() System::ShutdownSystem(false); } +bool Netplay::CreateSession(std::string nickname, s32 port, s32 max_players, std::string password) +{ + // TODO: Password + + const s32 input_delay = 1; + + if (!Netplay::Start(true, std::move(nickname), std::string(), port, input_delay)) + { + CloseSession(); + return false; + } + + return true; +} + +bool Netplay::JoinSession(std::string nickname, const std::string& hostname, s32 port, std::string password) +{ + // TODO: Password + + const s32 input_delay = 1; + + if (!Netplay::Start(false, std::move(nickname), hostname, port, input_delay)) + { + CloseSession(); + return false; + } + + return true; +} + void Netplay::NetplayAdvanceFrame(Netplay::Input inputs[], int disconnect_flags) { Netplay::SetInputs(inputs); @@ -1661,6 +1721,9 @@ void Netplay::NetplayAdvanceFrame(Netplay::Input inputs[], int disconnect_flags) void Netplay::ExecuteNetplay() { + // TODO: Fix this hackery to get out of the CPU loop... + System::SetState(System::State::Running); + while (s_state != SessionState::Inactive) { switch (s_state) diff --git a/src/core/netplay.h b/src/core/netplay.h index 49cd8ed24..b71791e59 100644 --- a/src/core/netplay.h +++ b/src/core/netplay.h @@ -28,10 +28,13 @@ enum : u8 NUM_ENET_CHANNELS, }; -void StartNetplaySession(s32 local_handle, u16 local_port, std::string& remote_addr, u16 remote_port, s32 input_delay, - std::string game_path); +void StartNetplaySession(s32 local_handle, u16 local_port, const std::string& remote_addr, u16 remote_port, + s32 input_delay, std::string game_path); void StopNetplaySession(); +bool CreateSession(std::string nickname, s32 port, s32 max_players, std::string password); +bool JoinSession(std::string nickname, const std::string& hostname, s32 port, std::string password); + bool IsActive(); /// Frees up resources associated with the current netplay session. diff --git a/src/duckstation-qt/createnetplaysessiondialog.ui b/src/duckstation-qt/createnetplaysessiondialog.ui new file mode 100644 index 000000000..c6f8529e0 --- /dev/null +++ b/src/duckstation-qt/createnetplaysessiondialog.ui @@ -0,0 +1,164 @@ + + + CreateNetplaySessionDialog + + + Qt::WindowModal + + + + 0 + 0 + 496 + 235 + + + + Create Netplay Session + + + true + + + + + + + + + + + :/icons/emblem-person-blue.png + + + + + + + + 14 + false + + + + Create Netplay Session + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + + Select a nickname and port to open your current game session to other players via netplay. A password may optionally be supplied to restrict who can join. + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Nickname: + + + + + + + Netplay Host + + + 128 + + + + + + + Port: + + + + + + + 1 + + + 65535 + + + 31200 + + + + + + + Players: + + + + + + + 2 + + + 8 + + + + + + + Password: + + + + + + + 128 + + + QLineEdit::Normal + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + diff --git a/src/duckstation-qt/duckstation-qt.vcxproj b/src/duckstation-qt/duckstation-qt.vcxproj index 6e851e84e..c6f002d71 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj +++ b/src/duckstation-qt/duckstation-qt.vcxproj @@ -41,6 +41,7 @@ + @@ -84,6 +85,7 @@ + @@ -265,6 +267,7 @@ + @@ -331,6 +334,12 @@ Document + + Document + + + Document + diff --git a/src/duckstation-qt/duckstation-qt.vcxproj.filters b/src/duckstation-qt/duckstation-qt.vcxproj.filters index e98ec568c..55e1624f1 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj.filters +++ b/src/duckstation-qt/duckstation-qt.vcxproj.filters @@ -94,6 +94,10 @@ + + + + @@ -155,6 +159,8 @@ + + @@ -196,6 +202,9 @@ + + + diff --git a/src/duckstation-qt/joinnetplaysessiondialog.ui b/src/duckstation-qt/joinnetplaysessiondialog.ui new file mode 100644 index 000000000..603884899 --- /dev/null +++ b/src/duckstation-qt/joinnetplaysessiondialog.ui @@ -0,0 +1,161 @@ + + + JoinNetplaySessionDialog + + + Qt::WindowModal + + + + 0 + 0 + 480 + 220 + + + + Create Netplay Session + + + true + + + + + + + + + + + :/icons/emblem-person-blue.png + + + + + + + + 14 + false + + + + Join Netplay Session + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + + Choose a nickname and enter the address/port of the netplay session you wish to join. + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Nickname: + + + + + + + Netplay Peer + + + 128 + + + + + + + Port: + + + + + + + 1 + + + 65535 + + + 31200 + + + + + + + Hostname: + + + + + + + localhost + + + + + + + Password: + + + + + + + 128 + + + QLineEdit::Normal + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 637192c3b..ce5190277 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -22,6 +22,7 @@ #include "gamelistsettingswidget.h" #include "gamelistwidget.h" #include "memorycardeditordialog.h" +#include "netplaydialogs.h" #include "qthost.h" #include "qtutils.h" #include "settingsdialog.h" @@ -1716,6 +1717,9 @@ void MainWindow::updateEmulationActions(bool starting, bool running, bool cheevo m_ui.actionViewGameProperties->setDisabled(starting || !running); + m_ui.actionCreateNetplaySession->setDisabled(!running || cheevos_challenge_mode); + m_ui.actionJoinNetplaySession->setDisabled(cheevos_challenge_mode); + if (starting || running) { if (!m_ui.toolBar->actions().contains(m_ui.actionPowerOff)) @@ -2100,7 +2104,9 @@ void MainWindow::connectSignals() updateMenuSelectedTheme(); // Netplay UI , TODO - connect(m_ui.actionCreateNetplaySession, &QAction::triggered, this, &MainWindow::onNetplaySessionCreated); + connect(m_ui.actionSetupNetplaySession, &QAction::triggered, this, &MainWindow::onSetupNetplaySessionClicked); + connect(m_ui.actionCreateNetplaySession, &QAction::triggered, this, &MainWindow::onCreateNetplaySessionClicked); + connect(m_ui.actionJoinNetplaySession, &QAction::triggered, this, &MainWindow::onJoinNetplaySessionClicked); } void MainWindow::addThemeToMenu(const QString& name, const QString& key) @@ -2765,7 +2771,7 @@ void MainWindow::onCPUDebuggerClosed() m_debugger_window = nullptr; } -void MainWindow::onNetplaySessionCreated() +void MainWindow::onSetupNetplaySessionClicked() { Assert(!m_netplay_window); @@ -2787,6 +2793,18 @@ void MainWindow::onNetplaySessionCreated() }); } +void MainWindow::onCreateNetplaySessionClicked() +{ + CreateNetplaySessionDialog dlg(this); + dlg.exec(); +} + +void MainWindow::onJoinNetplaySessionClicked() +{ + JoinNetplaySessionDialog dlg(this); + dlg.exec(); +} + void MainWindow::onToolsOpenDataDirectoryTriggered() { QtUtils::OpenURL(this, QUrl::fromLocalFile(QString::fromStdString(EmuFolders::DataRoot))); diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index 20ce67b66..369173072 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -169,7 +169,9 @@ private Q_SLOTS: void openCPUDebugger(); void onCPUDebuggerClosed(); - void onNetplaySessionCreated(); + void onSetupNetplaySessionClicked(); + void onCreateNetplaySessionClicked(); + void onJoinNetplaySessionClicked(); protected: void showEvent(QShowEvent* event) override; diff --git a/src/duckstation-qt/mainwindow.ui b/src/duckstation-qt/mainwindow.ui index 66ff93c02..c472832a1 100644 --- a/src/duckstation-qt/mainwindow.ui +++ b/src/duckstation-qt/mainwindow.ui @@ -27,7 +27,7 @@ 0 0 800 - 21 + 22 @@ -241,7 +241,10 @@ Netplay + + + @@ -983,9 +986,19 @@ Cover Downloader + + + Setup Session + + - Create Session + Create Session... + + + + + Join Session... diff --git a/src/duckstation-qt/netplaydialogs.cpp b/src/duckstation-qt/netplaydialogs.cpp new file mode 100644 index 000000000..3010dc218 --- /dev/null +++ b/src/duckstation-qt/netplaydialogs.cpp @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: 2023 Connor McLaughlin +// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) + +#include "netplaydialogs.h" +#include "qthost.h" + +#include "core/netplay.h" + +#include + +CreateNetplaySessionDialog::CreateNetplaySessionDialog(QWidget* parent) : QDialog(parent) +{ + m_ui.setupUi(this); + + connect(m_ui.maxPlayers, &QSpinBox::valueChanged, this, &CreateNetplaySessionDialog::updateState); + connect(m_ui.port, &QSpinBox::valueChanged, this, &CreateNetplaySessionDialog::updateState); + connect(m_ui.nickname, &QLineEdit::textChanged, this, &CreateNetplaySessionDialog::updateState); + connect(m_ui.password, &QLineEdit::textChanged, this, &CreateNetplaySessionDialog::updateState); + + connect(m_ui.buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, this, + &CreateNetplaySessionDialog::accept); + connect(m_ui.buttonBox->button(QDialogButtonBox::Cancel), &QAbstractButton::clicked, this, + &CreateNetplaySessionDialog::reject); + + updateState(); +} + +CreateNetplaySessionDialog::~CreateNetplaySessionDialog() = default; + +void CreateNetplaySessionDialog::accept() +{ + if (!validate()) + return; + + const int players = m_ui.maxPlayers->value(); + const int port = m_ui.port->value(); + const QString& nickname = m_ui.nickname->text(); + const QString& password = m_ui.password->text(); + QDialog::accept(); + + g_emu_thread->createNetplaySession(nickname.trimmed(), port, players, password); +} + +bool CreateNetplaySessionDialog::validate() +{ + const int players = m_ui.maxPlayers->value(); + const int port = m_ui.port->value(); + const QString& nickname = m_ui.nickname->text(); + return (!nickname.isEmpty() && players >= 2 && players <= Netplay::MAX_PLAYERS && port > 0 && port <= 65535); +} + +void CreateNetplaySessionDialog::updateState() +{ + m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(validate()); +} + +JoinNetplaySessionDialog::JoinNetplaySessionDialog(QWidget* parent) +{ + m_ui.setupUi(this); + + connect(m_ui.port, &QSpinBox::valueChanged, this, &JoinNetplaySessionDialog::updateState); + connect(m_ui.nickname, &QLineEdit::textChanged, this, &JoinNetplaySessionDialog::updateState); + connect(m_ui.password, &QLineEdit::textChanged, this, &JoinNetplaySessionDialog::updateState); + connect(m_ui.hostname, &QLineEdit::textChanged, this, &JoinNetplaySessionDialog::updateState); + + connect(m_ui.buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, this, + &JoinNetplaySessionDialog::accept); + connect(m_ui.buttonBox->button(QDialogButtonBox::Cancel), &QAbstractButton::clicked, this, + &JoinNetplaySessionDialog::reject); + + updateState(); +} + +JoinNetplaySessionDialog::~JoinNetplaySessionDialog() = default; + +void JoinNetplaySessionDialog::accept() +{ + if (!validate()) + return; + + const int port = m_ui.port->value(); + const QString& nickname = m_ui.nickname->text(); + const QString& hostname = m_ui.hostname->text(); + const QString& password = m_ui.password->text(); + QDialog::accept(); + + g_emu_thread->joinNetplaySession(nickname.trimmed(), hostname.trimmed(), port, password); +} + +bool JoinNetplaySessionDialog::validate() +{ + const int port = m_ui.port->value(); + const QString& nickname = m_ui.nickname->text(); + const QString& hostname = m_ui.hostname->text(); + return (!nickname.isEmpty() && !hostname.isEmpty() && port > 0 && port <= 65535); +} + +void JoinNetplaySessionDialog::updateState() +{ + m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(validate()); +} diff --git a/src/duckstation-qt/netplaydialogs.h b/src/duckstation-qt/netplaydialogs.h new file mode 100644 index 000000000..2e5e3f5a7 --- /dev/null +++ b/src/duckstation-qt/netplaydialogs.h @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2023 Connor McLaughlin +// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) + +#pragma once + +#include "ui_createnetplaysessiondialog.h" +#include "ui_joinnetplaysessiondialog.h" + +#include + +class CreateNetplaySessionDialog : public QDialog +{ + Q_OBJECT + +public: + CreateNetplaySessionDialog(QWidget* parent); + ~CreateNetplaySessionDialog(); + +public Q_SLOTS: + void accept() override; + +private Q_SLOTS: + void updateState(); + +private: + bool validate(); + + Ui::CreateNetplaySessionDialog m_ui; +}; + +class JoinNetplaySessionDialog : public QDialog +{ + Q_OBJECT + +public: + JoinNetplaySessionDialog(QWidget* parent); + ~JoinNetplaySessionDialog(); + +public Q_SLOTS: + void accept() override; + +private Q_SLOTS: + void updateState(); + +private: + bool validate(); + +private: + Ui::JoinNetplaySessionDialog m_ui; +}; diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 172f8dba2..acb78640c 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -1099,6 +1099,45 @@ void EmuThread::sendNetplayMessage(const QString& message) // Netplay::SendMsg(message.toStdString().c_str()); } +void EmuThread::createNetplaySession(const QString& nickname, qint32 port, qint32 max_players, const QString& password) +{ + if (!isOnThread()) + { + QMetaObject::invokeMethod(this, "createNetplaySession", Qt::QueuedConnection, Q_ARG(const QString&, nickname), + Q_ARG(qint32, port), Q_ARG(qint32, max_players), Q_ARG(const QString&, password)); + return; + } + + if (!Netplay::CreateSession(nickname.toStdString(), port, max_players, password.toStdString())) + { + errorReported(tr("Netplay Error"), tr("Failed to create netplay session. The log may contain more information.")); + return; + } + + // TODO: Fix this junk.. for some reason, it stays sleeping... + g_emu_thread->wakeThread(); +} + +void EmuThread::joinNetplaySession(const QString& nickname, const QString& hostname, qint32 port, + const QString& password) +{ + if (!isOnThread()) + { + QMetaObject::invokeMethod(this, "joinNetplaySession", Qt::QueuedConnection, Q_ARG(const QString&, nickname), + Q_ARG(const QString&, hostname), Q_ARG(qint32, port), Q_ARG(const QString&, password)); + return; + } + + if (!Netplay::JoinSession(nickname.toStdString(), hostname.toStdString(), port, password.toStdString())) + { + errorReported(tr("Netplay Error"), tr("Failed to join netplay session. The log may contain more information.")); + return; + } + + // TODO: Fix this junk.. for some reason, it stays sleeping... + g_emu_thread->wakeThread(); +} + void EmuThread::stopNetplaySession() { if (!isOnThread()) @@ -1458,7 +1497,7 @@ void EmuThread::run() // main loop while (!m_shutdown_flag) { - if (Netplay::IsActive() && System::IsRunning()) + if (Netplay::IsActive() && System::IsValid()) { Netplay::ExecuteNetplay(); } @@ -2227,8 +2266,7 @@ int main(int argc, char* argv[]) { Host::RunOnCPUThread([]() { const bool first = (s_netplay_test == 0); - if (!first) - QtHost::RunOnUIThread([]() { g_main_window->move(g_main_window->pos() + QPoint(1200, 0)); }); + QtHost::RunOnUIThread([first]() { g_main_window->move(QPoint(first ? 300 : 1400, 500)); }); const int h = first ? 1 : 2; const int nh = first ? 2 : 1; diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h index 5f49c40b6..b6ca31514 100644 --- a/src/duckstation-qt/qthost.h +++ b/src/duckstation-qt/qthost.h @@ -191,6 +191,8 @@ public Q_SLOTS: int input_delay, const QString& game_path); void stopNetplaySession(); void sendNetplayMessage(const QString& message); + void createNetplaySession(const QString& nickname, qint32 port, qint32 max_players, const QString& password); + void joinNetplaySession(const QString& nickname, const QString& hostname, qint32 port, const QString& password); private Q_SLOTS: void stopInThread();