RMG: implement dispatcher server for netplay

This commit is contained in:
Rosalie Wanders
2025-09-10 10:52:45 +02:00
parent 7270e0dfbe
commit 81f98505ca
14 changed files with 572 additions and 108 deletions
@@ -17,6 +17,8 @@
#include <QJsonDocument>
#include <QPushButton>
#include <QJsonObject>
#include <QJsonArray>
#include <QUrlQuery>
#include <QFileInfo>
#include <QFile>
@@ -39,6 +41,7 @@ CreateNetplaySessionDialog::CreateNetplaySessionDialog(QWidget *parent, QWebSock
this->webSocket = webSocket;
connect(this->webSocket, &QWebSocket::textMessageReceived, this, &CreateNetplaySessionDialog::on_webSocket_textMessageReceived);
connect(this->webSocket, &QWebSocket::pong, this, &CreateNetplaySessionDialog::on_webSocket_pong);
connect(this->webSocket, &QWebSocket::connected, this, &CreateNetplaySessionDialog::on_webSocket_connected);
// prepare broadcast
broadcastSocket.bind(QHostAddress(QHostAddress::AnyIPv4), 0);
@@ -96,13 +99,26 @@ CreateNetplaySessionDialog::CreateNetplaySessionDialog(QWidget *parent, QWebSock
else if (QUrl(serverUrl).isValid())
{
QNetworkAccessManager* networkAccessManager = new QNetworkAccessManager(this);
connect(networkAccessManager, &QNetworkAccessManager::finished, this, &CreateNetplaySessionDialog::on_networkAccessManager_Finished);
connect(networkAccessManager, &QNetworkAccessManager::finished, this, &CreateNetplaySessionDialog::on_jsonServerListDownload_Finished);
networkAccessManager->setTransferTimeout(15000);
networkAccessManager->get(QNetworkRequest(QUrl(serverUrl)));
}
}
this->pingTimerId = this->startTimer(2000);
QString dispatcherUrl = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::Netplay_DispatcherUrl));
if (!dispatcherUrl.isEmpty() && QUrl(dispatcherUrl).isValid())
{
this->dispatcherUrl = dispatcherUrl;
QNetworkAccessManager* networkAccessManager = new QNetworkAccessManager(this);
connect(networkAccessManager, &QNetworkAccessManager::finished, this, &CreateNetplaySessionDialog::on_dispatcherRegionListDownload_Finished);
networkAccessManager->setTransferTimeout(15000);
QNetworkRequest networkRequest(QUrl(this->dispatcherUrl + "/getRegions"));
networkRequest.setRawHeader("netplay-id", "RMG");
networkAccessManager->get(networkRequest);
}
}
CreateNetplaySessionDialog::~CreateNetplaySessionDialog(void)
@@ -173,6 +189,41 @@ void CreateNetplaySessionDialog::validateCreateButton(void)
createButton->setEnabled(this->validate());
}
void CreateNetplaySessionDialog::createSession(void)
{
QList<QString> plugins = NetplayCommon::GetPluginNames(this->sessionMD5);
QJsonObject jsonFeatures;
jsonFeatures.insert("rsp_plugin", plugins[0]);
jsonFeatures.insert("gfx_plugin", plugins[1]);
QJsonObject json;
QJsonObject session;
session.insert("room_name", this->sessionNameLineEdit->text());
session.insert("password", this->passwordLineEdit->text());
session.insert("MD5", this->sessionMD5);
session.insert("game_name", this->getGameName(this->sessionGoodName, this->sessionFile));
session.insert("features", jsonFeatures);
json.insert("type", "request_create_room");
json.insert("player_name", this->nickNameLineEdit->text());
json.insert("room", session);
NetplayCommon::AddCommonJson(json);
this->webSocket->sendTextMessage(QJsonDocument(json).toJson());
}
void CreateNetplaySessionDialog::toggleUI(bool enable, bool enableCreateButton)
{
QPushButton* createButton = this->buttonBox->button(QDialogButtonBox::Ok);
createButton->setEnabled(enableCreateButton);
this->serverComboBox->setEnabled(enable);
this->sessionNameLineEdit->setReadOnly(!enable);
this->passwordLineEdit->setReadOnly(!enable);
this->sessionNameLineEdit->setReadOnly(!enable);
this->nickNameLineEdit->setReadOnly(!enable);
}
void CreateNetplaySessionDialog::timerEvent(QTimerEvent* event)
{
if (event->timerId() == this->pingTimerId)
@@ -199,7 +250,7 @@ void CreateNetplaySessionDialog::on_webSocket_textMessageReceived(QString messag
else
{
QtMessageBox::Error(this, "Server Error", json.value("message").toString());
this->validateCreateButton();
this->toggleUI(true, this->validate());
}
}
}
@@ -209,26 +260,28 @@ void CreateNetplaySessionDialog::on_webSocket_pong(quint64 elapsedTime, const QB
this->pingLineEdit->setText(QString::number(elapsedTime) + " ms");
}
void CreateNetplaySessionDialog::on_webSocket_connected()
{
if (NetplayCommon::IsServerDispatcher(this->serverComboBox))
{
this->createSession();
}
}
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());
}
NetplayCommon::AddServers(this->serverComboBox, QJsonDocument::fromJson(incomingData));
}
NetplayCommon::RestoreSelectedServer(this->serverComboBox);
}
void CreateNetplaySessionDialog::on_networkAccessManager_Finished(QNetworkReply* reply)
void CreateNetplaySessionDialog::on_jsonServerListDownload_Finished(QNetworkReply* reply)
{
if (reply->error())
{
@@ -243,6 +296,55 @@ void CreateNetplaySessionDialog::on_networkAccessManager_Finished(QNetworkReply*
reply->deleteLater();
}
void CreateNetplaySessionDialog::on_dispatcherRegionListDownload_Finished(QNetworkReply* reply)
{
if (reply->error())
{
QtMessageBox::Error(this, "Server Error", "Failed to retrieve region list: " + reply->errorString());
reply->deleteLater();
return;
}
NetplayCommon::AddServers(this->serverComboBox,
QJsonDocument::fromJson(reply->readAll()), true);
reply->deleteLater();
}
void CreateNetplaySessionDialog::on_dispatcherServerCreate_Finished(QNetworkReply* reply)
{
if (reply->error())
{
QtMessageBox::Error(this, "Server Error", "Failed to create server: " + reply->errorString());
reply->deleteLater();
this->toggleUI(true, this->validate());
return;
}
QJsonDocument jsonDocument = QJsonDocument::fromJson(reply->readAll());
QJsonObject jsonObject = jsonDocument.object();
if (jsonObject.empty() || jsonObject.keys().empty())
{
QtMessageBox::Error(this, "Server Error", "Failed to create server: " + jsonDocument.toJson());
reply->deleteLater();
this->toggleUI(true, this->validate());
return;
}
// first item should be an address
QString address = jsonObject[jsonObject.keys().at(0)].toString();
if (!NetplayCommon::ConnectToIPv4Server(address, this->webSocket))
{
QtMessageBox::Error(this, "Failed to find IPv4 address of server");
reply->deleteLater();
this->toggleUI(true, this->validate());
return;
}
reply->deleteLater();
}
void CreateNetplaySessionDialog::on_serverComboBox_currentIndexChanged(int index)
{
if (index == -1)
@@ -250,12 +352,25 @@ void CreateNetplaySessionDialog::on_serverComboBox_currentIndexChanged(int index
return;
}
this->pingLineEdit->setText("Calculating...");
QString address = this->serverComboBox->itemData(index).toString();
if (!NetplayCommon::ConnectToIPv4Server(address, this->webSocket))
bool dispatcher = NetplayCommon::IsServerDispatcher(this->serverComboBox, index);
if (this->pingTimerId != -1)
{
QtMessageBox::Error(this, "Failed to find IPv4 address of server");
this->killTimer(this->pingTimerId);
this->pingTimerId = -1;
}
this->pingLineEdit->setText(dispatcher ? "N/A" : "Calculating...");
if (!dispatcher)
{
this->pingTimerId = this->startTimer(2000);
QString address = NetplayCommon::GetServerData(this->serverComboBox, index);
if (!NetplayCommon::ConnectToIPv4Server(address, this->webSocket))
{
QtMessageBox::Error(this, "Failed to find IPv4 address of server");
}
}
}
@@ -281,7 +396,7 @@ void CreateNetplaySessionDialog::on_romListWidget_OnRomChanged(bool valid)
void CreateNetplaySessionDialog::accept()
{
if (!this->webSocket->isValid())
if (!NetplayCommon::IsServerDispatcher(this->serverComboBox) && !this->webSocket->isValid())
{
QtMessageBox::Error(this, "Server Error", "Connection Failed");
return;
@@ -293,29 +408,36 @@ void CreateNetplaySessionDialog::accept()
return;
}
// store these for use in createSession()
this->sessionFile = romData.File;
this->sessionMD5 = romData.MD5;
this->sessionGoodName = romData.GoodName;
// disable create button while we're processing the request
QPushButton* createButton = this->buttonBox->button(QDialogButtonBox::Ok);
createButton->setEnabled(false);
this->toggleUI(false, false);
this->sessionFile = romData.File;
if (NetplayCommon::IsServerDispatcher(this->serverComboBox))
{
QNetworkAccessManager* networkAccessManager = new QNetworkAccessManager(this);
connect(networkAccessManager, &QNetworkAccessManager::finished, this, &CreateNetplaySessionDialog::on_dispatcherServerCreate_Finished);
networkAccessManager->setTransferTimeout(120000);
QList<QString> plugins = NetplayCommon::GetPluginNames(romData.MD5);
QUrl url(this->dispatcherUrl + "/createServer");
QJsonObject jsonFeatures;
jsonFeatures.insert("rsp_plugin", plugins[0]);
jsonFeatures.insert("gfx_plugin", plugins[1]);
QUrlQuery urlQuery;
urlQuery.addQueryItem("region", NetplayCommon::GetServerData(this->serverComboBox));
url.setQuery(urlQuery);
QJsonObject json;
QJsonObject session;
session.insert("room_name", this->sessionNameLineEdit->text());
session.insert("password", this->passwordLineEdit->text());
session.insert("MD5", romData.MD5);
session.insert("game_name", this->getGameName(romData.GoodName, romData.File));
session.insert("features", jsonFeatures);
json.insert("type", "request_create_room");
json.insert("player_name", this->nickNameLineEdit->text());
json.insert("room", session);
NetplayCommon::AddCommonJson(json);
QNetworkRequest networkRequest(url);
networkRequest.setRawHeader("netplay-id", "RMG");
networkAccessManager->get(networkRequest);
this->webSocket->sendTextMessage(QJsonDocument(json).toJson());
// creating a server can take a while,
// so show a loading screen
this->romListWidget->ShowLoading();
}
else
{
this->createSession();
}
}
@@ -45,20 +45,32 @@ class CreateNetplaySessionDialog : public QDialog, private Ui::CreateNetplaySess
QJsonObject sessionJson;
QString sessionFile;
QString sessionMD5;
QString sessionGoodName;
QString dispatcherUrl;
QString getGameName(QString goodName, QString file);
bool validate(void);
void validateCreateButton(void);
void createSession(void);
void toggleUI(bool enable, bool enableCreateButton);
protected:
void timerEvent(QTimerEvent *event) Q_DECL_OVERRIDE;
private slots:
void on_webSocket_textMessageReceived(QString message);
void on_webSocket_pong(quint64 elapsedTime, const QByteArray&);
void on_webSocket_connected(void);
void on_broadcastSocket_readyRead(void);
void on_networkAccessManager_Finished(QNetworkReply* reply);
void on_jsonServerListDownload_Finished(QNetworkReply* reply);
void on_dispatcherRegionListDownload_Finished(QNetworkReply* reply);
void on_dispatcherServerCreate_Finished(QNetworkReply* reply);
void on_serverComboBox_currentIndexChanged(int index);
@@ -31,7 +31,7 @@
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="label_5">
<widget class="QLabel" name="pingLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
@@ -11,6 +11,7 @@
#include <QCryptographicHash>
#include <QByteArray>
#include <QJsonArray>
#include <QFileInfo>
#include <QDateTime>
#include <QHostInfo>
@@ -22,6 +23,9 @@
using namespace NetplayCommon;
// register type with qt
Q_DECLARE_METATYPE(NetplayServerData);
void NetplayCommon::AddCommonJson(QJsonObject& json)
{
QCryptographicHash hash = QCryptographicHash(QCryptographicHash::Sha256);
@@ -83,14 +87,36 @@ QList<QString> NetplayCommon::GetPluginNames(QString md5QString)
return pluginNames;
}
void NetplayCommon::AddServers(QComboBox* comboBox, QJsonDocument document)
void NetplayCommon::AddServers(QComboBox* comboBox, QJsonDocument document, bool dispatcher)
{
QJsonObject jsonObject = document.object();
QStringList jsonServers = jsonObject.keys();
for (int i = 0; i < jsonServers.size(); i++)
if (dispatcher)
{
comboBox->addItem(jsonServers.at(i), jsonObject.value(jsonServers.at(i)).toString());
QJsonArray jsonServers = document.array();
for (int i = 0; i < jsonServers.size(); i++)
{
comboBox->addItem(jsonServers.at(i).toString(),
QVariant::fromValue<NetplayServerData>(
{
dispatcher,
jsonServers.at(i).toString()
}));
}
}
else
{
QJsonObject jsonObject = document.object();
QStringList jsonServers = jsonObject.keys();
for (int i = 0; i < jsonServers.size(); i++)
{
comboBox->addItem(jsonServers.at(i),
QVariant::fromValue<NetplayServerData>(
{
dispatcher,
jsonObject.value(jsonServers.at(i)).toString()
}));
}
}
NetplayCommon::RestoreSelectedServer(comboBox);
@@ -111,6 +137,26 @@ void NetplayCommon::RestoreSelectedServer(QComboBox* comboBox)
}
}
bool NetplayCommon::IsServerDispatcher(QComboBox* comboBox, int index)
{
if (index == -1)
{
index = comboBox->currentIndex();
}
return comboBox->itemData(index).value<NetplayServerData>().Dispatcher;
}
QString NetplayCommon::GetServerData(QComboBox* comboBox, int index)
{
if (index == -1)
{
index = comboBox->currentIndex();
}
return comboBox->itemData(index).value<NetplayServerData>().Data;
}
bool NetplayCommon::ConnectToIPv4Server(QString address, QWebSocket* webSocket)
{
QUrl addressUrl(address);
@@ -20,6 +20,12 @@
namespace NetplayCommon
{
struct NetplayServerData
{
bool Dispatcher;
QString Data;
};
#define NETPLAYCOMMON_SESSION_REGEX "[a-zA-Z0-9 ]+"
#define NETPLAYCOMMON_NICKNAME_REGEX "[a-zA-Z0-9]+"
#define NETPLAYCOMMON_PASSWORD_REGEX "[a-zA-Z0-9,.\\/<>?;:[\\]{}\\-=_+`~!@#$%^&*()]+"
@@ -31,11 +37,17 @@ namespace NetplayCommon
QList<QString> GetPluginNames(QString md5QString);
// Adds servers from json to combobox
void AddServers(QComboBox* comboBox, QJsonDocument document);
void AddServers(QComboBox* comboBox, QJsonDocument document, bool dispatcher = false);
// Restores previously selected server
void RestoreSelectedServer(QComboBox* comboBox);
// Returns whether server from comboBox is dispatcher
bool IsServerDispatcher(QComboBox* comboBox, int index = -1);
// Returns server data from comboBox
QString GetServerData(QComboBox* comboBox, int index = -1);
#ifdef NETPLAY
// Attempts to connect the web socket to the given address using IPv4
bool ConnectToIPv4Server(QString address, QWebSocket* webSocket);
@@ -21,6 +21,7 @@
#include <QFileDialog>
#include <QJsonObject>
#include <QJsonArray>
#include <QUrlQuery>
#include <QFile>
#include <RMG-Core/Settings.hpp>
@@ -70,6 +71,11 @@ NetplaySessionBrowserDialog::NetplaySessionBrowserDialog(QWidget *parent, QWebSo
this->nickNameLineEdit->setValidator(new QRegularExpressionValidator(re, this));
this->nickNameLineEdit->setText(QString::fromStdString(CoreSettingsGetStringValue(SettingsID::Netplay_Nickname)));
// configure dispatcher network access manager
this->dispatcherNetworkAccessManager = new QNetworkAccessManager(this);
this->dispatcherNetworkAccessManager->setTransferTimeout(15000);
connect(this->dispatcherNetworkAccessManager, &QNetworkAccessManager::finished, this, &NetplaySessionBrowserDialog::on_dispatcherRetrieveServers_Finished);
// request server list
QString serverUrl = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::Netplay_ServerJsonUrl));
if (!serverUrl.isEmpty())
@@ -90,15 +96,28 @@ NetplaySessionBrowserDialog::NetplaySessionBrowserDialog(QWidget *parent, QWebSo
else if (QUrl(serverUrl).isValid())
{
QNetworkAccessManager* networkAccessManager = new QNetworkAccessManager(this);
connect(networkAccessManager, &QNetworkAccessManager::finished, this, &NetplaySessionBrowserDialog::on_networkAccessManager_Finished);
connect(networkAccessManager, &QNetworkAccessManager::finished, this, &NetplaySessionBrowserDialog::on_jsonServerListDownload_Finished);
networkAccessManager->setTransferTimeout(15000);
networkAccessManager->get(QNetworkRequest(QUrl(serverUrl)));
}
}
this->validateJoinButton();
QString dispatcherUrl = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::Netplay_DispatcherUrl));
if (!dispatcherUrl.isEmpty() && QUrl(dispatcherUrl).isValid())
{
this->dispatcherUrl = dispatcherUrl;
this->pingTimerId = this->startTimer(2000);
QNetworkAccessManager* networkAccessManager = new QNetworkAccessManager(this);
connect(networkAccessManager, &QNetworkAccessManager::finished, this, &NetplaySessionBrowserDialog::on_dispatcherRegionListDownload_Finished);
networkAccessManager->setTransferTimeout(15000);
QNetworkRequest networkRequest(QUrl(this->dispatcherUrl + "/getRegions"));
networkRequest.setRawHeader("netplay-id", "RMG");
networkAccessManager->get(networkRequest);
}
this->validateJoinButton();
}
NetplaySessionBrowserDialog::~NetplaySessionBrowserDialog(void)
@@ -179,6 +198,79 @@ void NetplaySessionBrowserDialog::validateJoinButton(void)
joinButton->setEnabled(this->validate());
}
void NetplaySessionBrowserDialog::toggleUI(bool enable, bool enableJoinButton)
{
QPushButton* joinButton = this->buttonBox->button(QDialogButtonBox::Ok);
joinButton->setEnabled(enableJoinButton);
QPushButton* refreshButton = this->buttonBox->button(QDialogButtonBox::RestoreDefaults);
refreshButton->setEnabled(enable);
this->serverComboBox->setEnabled(enable);
this->nickNameLineEdit->setReadOnly(!enable);
}
void NetplaySessionBrowserDialog::refreshSessions(void)
{
// disable refresh button while refreshing
QPushButton* refreshButton = this->buttonBox->button(QDialogButtonBox::RestoreDefaults);
refreshButton->setEnabled(false);
// disable join button while refreshing
QPushButton* joinButton = this->buttonBox->button(QDialogButtonBox::Ok);
joinButton->setEnabled(false);
if (NetplayCommon::IsServerDispatcher(this->serverComboBox))
{
this->on_serverComboBox_currentIndexChanged(this->serverComboBox->currentIndex());
}
else
{
this->on_webSocket_connected();
}
}
void NetplaySessionBrowserDialog::joinSession(void)
{
QJsonObject json;
QJsonObject session;
session.insert("port", sessionData.Port);
session.insert("password", this->sessionPassword);
session.insert("MD5", this->sessionData.MD5);
json.insert("type", "request_join_room");
json.insert("player_name", this->nickNameLineEdit->text());
json.insert("room", session);
NetplayCommon::AddCommonJson(json);
this->webSocket->sendTextMessage(QJsonDocument(json).toJson());
}
void NetplaySessionBrowserDialog::resetDispatcherState(void)
{
if (this->dispatcherTimerId != -1)
{
this->killTimer(this->dispatcherTimerId);
this->dispatcherTimerId = -1;
}
if (this->dispatcherTimeoutTimerId != -1)
{
this->killTimer(this->dispatcherTimeoutTimerId);
this->dispatcherTimeoutTimerId = -1;
}
if (this->dispatcherNetworkReply != nullptr)
{
this->dispatcherNetworkReply->abort();
this->dispatcherNetworkReply = nullptr;
}
this->dispatcherMoveThroughList = false;
this->dispatcherJoinSession = false;
this->dispatcherAddressListIndex = false;
}
void NetplaySessionBrowserDialog::timerEvent(QTimerEvent *event)
{
if (event->timerId() == this->pingTimerId)
@@ -188,6 +280,49 @@ void NetplaySessionBrowserDialog::timerEvent(QTimerEvent *event)
this->webSocket->ping();
}
}
else if (event->timerId() == this->dispatcherTimerId)
{
if (this->dispatcherMoveThroughList)
{
this->dispatcherAddressListIndex++;
if (this->dispatcherAddressListIndex < this->dispatcherAddressList.size())
{
QString address = this->dispatcherAddressList.at(this->dispatcherAddressListIndex);
if (!NetplayCommon::ConnectToIPv4Server(address, this->webSocket))
{
this->sessionBrowserWidget->Reset();
QtMessageBox::Error(this, "Failed to find IPv4 address of server");
}
this->dispatcherMoveThroughList = false;
// start socket timeout
if (this->dispatcherTimeoutTimerId != -1)
{
this->killTimer(this->dispatcherTimeoutTimerId);
this->dispatcherTimeoutTimerId = -1;
}
this->dispatcherTimeoutTimerId = this->startTimer(15000);
}
else
{
this->sessionBrowserWidget->RefreshDone();
this->resetDispatcherState();
}
}
}
else if (event->timerId() == this->dispatcherTimeoutTimerId)
{
this->webSocket->close();
this->dispatcherMoveThroughList = true;
this->killTimer(this->dispatcherTimeoutTimerId);
this->dispatcherTimeoutTimerId = -1;
}
}
void NetplaySessionBrowserDialog::on_webSocket_connected(void)
@@ -198,16 +333,20 @@ void NetplaySessionBrowserDialog::on_webSocket_connected(void)
return;
}
// disable refresh button while refreshing
QPushButton* refreshButton = this->buttonBox->button(QDialogButtonBox::RestoreDefaults);
refreshButton->setEnabled(false);
bool dispatcher = NetplayCommon::IsServerDispatcher(this->serverComboBox);
// disable join button while refreshing
QPushButton* joinButton = this->buttonBox->button(QDialogButtonBox::Ok);
joinButton->setEnabled(false);
if (dispatcher && this->dispatcherJoinSession)
{
this->joinSession();
this->dispatcherJoinSession = false;
return;
}
// clear sessions
this->sessionBrowserWidget->StartRefresh();
// clear sessions when we're not using the dispatcher server
if (!dispatcher)
{
this->sessionBrowserWidget->StartRefresh();
}
// request session list from server
QJsonObject json;
@@ -241,11 +380,15 @@ void NetplaySessionBrowserDialog::on_webSocket_textMessageReceived(QString messa
session.value("port").toInt(),
session.value("features").toObject().value("cpu_emulator").toString(),
session.value("features").toObject().value("rsp_plugin").toString(),
session.value("features").toObject().value("gfx_plugin").toString());
session.value("features").toObject().value("gfx_plugin").toString(),
this->webSocket->requestUrl().toString());
}
// we're done refreshing the sessions
this->sessionBrowserWidget->RefreshDone();
if (!NetplayCommon::IsServerDispatcher(this->serverComboBox))
{
this->sessionBrowserWidget->RefreshDone();
}
}
else
{
@@ -263,9 +406,11 @@ void NetplaySessionBrowserDialog::on_webSocket_textMessageReceived(QString messa
else
{
QtMessageBox::Error(this, "Server Error", json.value("message").toString());
this->validateJoinButton();
this->toggleUI(true, this->validate());
}
}
this->dispatcherMoveThroughList = true;
}
void NetplaySessionBrowserDialog::on_webSocket_pong(quint64 elapsedTime, const QByteArray&)
@@ -275,7 +420,10 @@ void NetplaySessionBrowserDialog::on_webSocket_pong(quint64 elapsedTime, const Q
void NetplaySessionBrowserDialog::on_webSocket_disconnected()
{
this->sessionBrowserWidget->Reset();
if (!NetplayCommon::IsServerDispatcher(this->serverComboBox))
{
this->sessionBrowserWidget->Reset();
}
}
void NetplaySessionBrowserDialog::on_broadcastSocket_readyRead(void)
@@ -284,20 +432,14 @@ void NetplaySessionBrowserDialog::on_broadcastSocket_readyRead(void)
{
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());
}
NetplayCommon::AddServers(this->serverComboBox, QJsonDocument::fromJson(incomingData));
}
NetplayCommon::RestoreSelectedServer(this->serverComboBox);
}
void NetplaySessionBrowserDialog::on_networkAccessManager_Finished(QNetworkReply* reply)
void NetplaySessionBrowserDialog::on_jsonServerListDownload_Finished(QNetworkReply* reply)
{
if (reply->error())
{
@@ -313,6 +455,61 @@ void NetplaySessionBrowserDialog::on_networkAccessManager_Finished(QNetworkReply
reply->deleteLater();
}
void NetplaySessionBrowserDialog::on_dispatcherRegionListDownload_Finished(QNetworkReply* reply)
{
if (reply->error())
{
this->sessionBrowserWidget->Reset();
QtMessageBox::Error(this, "Server Error", "Failed to retrieve region list: " + reply->errorString());
reply->deleteLater();
return;
}
NetplayCommon::AddServers(this->serverComboBox,
QJsonDocument::fromJson(reply->readAll()), true);
reply->deleteLater();
}
void NetplaySessionBrowserDialog::on_dispatcherRetrieveServers_Finished(QNetworkReply* reply)
{
// don't show an error when the request has been aborted
if (reply->error() == QNetworkReply::OperationCanceledError)
{
reply->deleteLater();
return;
}
if (reply->error())
{
this->sessionBrowserWidget->Reset();
QtMessageBox::Error(this, "Server Error", "Failed to retrieve server list: " + reply->errorString());
reply->deleteLater();
return;
}
QJsonDocument jsonDocument = QJsonDocument::fromJson(reply->readAll());
QJsonObject jsonObject = jsonDocument.object();
QStringList jsonKeys = jsonObject.keys();
this->sessionBrowserWidget->StartRefresh();
this->resetDispatcherState();
// we only care about the addresses
this->dispatcherAddressList.clear();
for (int i = 0; i < jsonKeys.size(); i++)
{
QString address = jsonObject[jsonKeys.at(i)].toString();
this->dispatcherAddressList.append(address);
}
this->dispatcherAddressListIndex = -1;
this->dispatcherMoveThroughList = true;
this->dispatcherTimerId = this->startTimer(250);
reply->deleteLater();
}
void NetplaySessionBrowserDialog::on_serverComboBox_currentIndexChanged(int index)
{
QPushButton* refreshButton = this->buttonBox->button(QDialogButtonBox::RestoreDefaults);
@@ -323,14 +520,51 @@ void NetplaySessionBrowserDialog::on_serverComboBox_currentIndexChanged(int inde
return;
}
this->pingLineEdit->setText("Calculating...");
bool dispatcher = NetplayCommon::IsServerDispatcher(this->serverComboBox, index);
if (this->pingTimerId != -1)
{
this->killTimer(this->pingTimerId);
this->pingTimerId = -1;
}
this->resetDispatcherState();
this->pingLineEdit->setText(dispatcher ? "N/A" : "Calculating...");
this->sessionBrowserWidget->StartRefresh();
QString address = this->serverComboBox->itemData(index).toString();
if (!NetplayCommon::ConnectToIPv4Server(address, this->webSocket))
if (dispatcher)
{
this->sessionBrowserWidget->Reset();
QtMessageBox::Error(this, "Failed to find IPv4 address of server");
QString region = NetplayCommon::GetServerData(this->serverComboBox, index);
QUrl url(this->dispatcherUrl + "/getServers");
QUrlQuery urlQuery;
urlQuery.addQueryItem("region", region);
url.setQuery(urlQuery);
QNetworkRequest networkRequest(url);
networkRequest.setRawHeader("netplay-id", "RMG");
// sadly we have to force HTTP/1 here due to a Qt bug,
// we abort the connection which sadly in Qt6 causes an error and eventually segfault,
// so to workaround this, force HTTP/1 until it's fixed in Qt6
//
// thank you MapLibre devs for figuring out this issue:
// https://github.com/maplibre/maplibre-native/issues/3644
networkRequest.setAttribute(QNetworkRequest::Http2AllowedAttribute, false);
this->dispatcherNetworkReply = this->dispatcherNetworkAccessManager->get(networkRequest);
}
else
{
this->pingTimerId = this->startTimer(2000);
QString address = NetplayCommon::GetServerData(this->serverComboBox, index);
if (!NetplayCommon::ConnectToIPv4Server(address, this->webSocket))
{
this->sessionBrowserWidget->Reset();
QtMessageBox::Error(this, "Failed to find IPv4 address of server");
}
}
}
@@ -356,13 +590,14 @@ void NetplaySessionBrowserDialog::on_buttonBox_clicked(QAbstractButton* button)
// refresh session list when refresh button has been pressed
if (button == this->buttonBox->button(QDialogButtonBox::RestoreDefaults))
{
this->on_webSocket_connected();
this->refreshSessions();
}
}
void NetplaySessionBrowserDialog::accept()
{
if (!this->webSocket->isValid())
if (!NetplayCommon::IsServerDispatcher(this->serverComboBox) &&
!this->webSocket->isValid())
{
QtMessageBox::Error(this, "Server Error", "Connection Failed");
return;
@@ -376,21 +611,19 @@ void NetplaySessionBrowserDialog::accept()
}
// disable join button while we're processing the request
QPushButton* joinButton = this->buttonBox->button(QDialogButtonBox::Ok);
joinButton->setEnabled(false);
this->toggleUI(false, false);
// request password when needed
QString password;
if (sessionData.PasswordProtected)
{
NetplaySessionPasswordDialog dialog(this);
dialog.exec();
password = dialog.GetPassword();
this->sessionPassword = dialog.GetPassword();
// do nothing if password is empty
if (password.isEmpty())
if (this->sessionPassword.isEmpty())
{
joinButton->setEnabled(true);
this->toggleUI(true, true);
return;
}
}
@@ -403,7 +636,7 @@ void NetplaySessionBrowserDialog::accept()
details = "Expected RSP Plugin: " + sessionData.RspPlugin + "\n";
details += "Current RSP Plugin: " + pluginNames[0];
QtMessageBox::Error(this, "RSP Plugin Mismatch", details);
joinButton->setEnabled(true);
this->toggleUI(true, true);
return;
}
if (sessionData.GfxPlugin != pluginNames[1])
@@ -411,7 +644,7 @@ void NetplaySessionBrowserDialog::accept()
details = "Expected GFX Plugin: " + sessionData.GfxPlugin + "\n";
details += "Current GFX Plugin: " + pluginNames[1];
QtMessageBox::Error(this, "GFX Plugin Mismatch", details);
joinButton->setEnabled(true);
this->toggleUI(true, true);
return;
}
@@ -433,21 +666,20 @@ void NetplaySessionBrowserDialog::accept()
this->sessionFile = this->showROMDialog(sessionData.GameName, sessionData.MD5);
if (this->sessionFile.isEmpty())
{
joinButton->setEnabled(true);
this->toggleUI(true, true);
return;
}
}
QJsonObject json;
QJsonObject session;
session.insert("port", sessionData.Port);
session.insert("password", password);
session.insert("MD5", QString::fromStdString(md5));
this->sessionData = sessionData;
json.insert("type", "request_join_room");
json.insert("player_name", this->nickNameLineEdit->text());
json.insert("room", session);
NetplayCommon::AddCommonJson(json);
this->webSocket->sendTextMessage(QJsonDocument(json).toJson());
if (NetplayCommon::IsServerDispatcher(this->serverComboBox))
{
this->dispatcherJoinSession = true;
this->webSocket->open(sessionData.Address);
}
else
{
this->joinSession();
}
}
@@ -43,15 +43,35 @@ class NetplaySessionBrowserDialog : public QDialog, private Ui::NetplaySessionBr
QUdpSocket broadcastSocket;
QJsonObject sessionJson;
QString sessionFile;
QString sessionPassword;
NetplaySessionData sessionData;
QMap<QString, CoreRomSettings> romData;
int pingTimerId = -1;
int dispatcherTimerId = -1;
int dispatcherTimeoutTimerId = -1;
bool dispatcherMoveThroughList = false;
bool dispatcherJoinSession = false;
QNetworkAccessManager* dispatcherNetworkAccessManager = nullptr;
QNetworkReply* dispatcherNetworkReply = nullptr;
QString dispatcherUrl;
QStringList dispatcherAddressList;
int dispatcherAddressListIndex = 0;
QString showROMDialog(QString name, QString md5);
bool validate(void);
void validateJoinButton(void);
void toggleUI(bool enable, bool enableJoinButton);
void refreshSessions(void);
void joinSession(void);
void resetDispatcherState(void);
protected:
void timerEvent(QTimerEvent *event) Q_DECL_OVERRIDE;
@@ -62,7 +82,10 @@ class NetplaySessionBrowserDialog : public QDialog, private Ui::NetplaySessionBr
void on_webSocket_disconnected(void);
void on_broadcastSocket_readyRead(void);
void on_networkAccessManager_Finished(QNetworkReply* reply);
void on_jsonServerListDownload_Finished(QNetworkReply* reply);
void on_dispatcherRegionListDownload_Finished(QNetworkReply* reply);
void on_dispatcherRetrieveServers_Finished(QNetworkReply* reply);
void on_serverComboBox_currentIndexChanged(int index);
void on_sessionBrowserWidget_OnSessionChanged(bool valid);
@@ -52,7 +52,7 @@
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_2">
<widget class="QLabel" name="pingLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
@@ -26,6 +26,11 @@ CreateNetplaySessionWidget::CreateNetplaySessionWidget(QWidget* parent) : QStack
this->emptyWidget = new Widget::CreateNetplaySessionEmptyWidget(this);
this->addWidget(this->emptyWidget);
// configure loading widget
this->loadingWidget = new Widget::NetplaySessionBrowserLoadingWidget(this, "Creating server");
this->loadingWidget->SetWidgetIndex(this->addWidget(this->loadingWidget));
connect(this, &QStackedWidget::currentChanged, this->loadingWidget, &NetplaySessionBrowserLoadingWidget::on_NetplaySessionBrowserWidget_currentChanged);
// configure list widget
this->listWidget = new QListWidget(this);
this->addWidget(this->listWidget);
@@ -66,6 +71,11 @@ void CreateNetplaySessionWidget::RefreshDone(void)
}
}
void CreateNetplaySessionWidget::ShowLoading(void)
{
this->setCurrentWidget(this->loadingWidget);
}
bool CreateNetplaySessionWidget::IsCurrentRomValid()
{
return this->currentWidget() == this->listWidget &&
@@ -10,6 +10,7 @@
#ifndef CREATENETPLAYSESSIONWIDGET_HPP
#define CREATENETPLAYSESSIONWIDGET_HPP
#include "NetplaySessionBrowserLoadingWidget.hpp"
#include "CreateNetplaySessionEmptyWidget.hpp"
#include <QStackedWidget>
@@ -36,14 +37,15 @@ class CreateNetplaySessionWidget : public QStackedWidget
~CreateNetplaySessionWidget(void);
void AddRomData(QString goodName, QString md5, QString file);
void RefreshDone(void);
void ShowLoading(void);
bool IsCurrentRomValid(void);
bool GetCurrentRom(NetplayRomData& data);
private:
Widget::CreateNetplaySessionEmptyWidget* emptyWidget = nullptr;
Widget::NetplaySessionBrowserLoadingWidget* loadingWidget = nullptr;
QListWidget* listWidget = nullptr;
@@ -15,12 +15,13 @@
using namespace UserInterface::Widget;
NetplaySessionBrowserLoadingWidget::NetplaySessionBrowserLoadingWidget(QWidget* parent) : QWidget(parent)
NetplaySessionBrowserLoadingWidget::NetplaySessionBrowserLoadingWidget(QWidget* parent, QString loadingText) : QWidget(parent)
{
QHBoxLayout* layout = new QHBoxLayout(this);
this->baseLoadingText = loadingText;
this->loadingLabel = new QLabel(this);
this->loadingLabel->setText("Loading");
this->loadingLabel->setText(this->baseLoadingText);
this->loadingLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
layout->addWidget(loadingLabel);
@@ -66,7 +67,7 @@ void NetplaySessionBrowserLoadingWidget::on_NetplaySessionBrowserWidget_currentC
void NetplaySessionBrowserLoadingWidget::updateLoadingText()
{
QString loadingText = "Loading";
QString loadingText = this->baseLoadingText;
if (dotCount <= 3)
{
@@ -22,7 +22,7 @@ class NetplaySessionBrowserLoadingWidget : public QWidget
{
Q_OBJECT
public:
NetplaySessionBrowserLoadingWidget(QWidget* parent);
NetplaySessionBrowserLoadingWidget(QWidget* parent, QString loadingText = "Loading");
~NetplaySessionBrowserLoadingWidget();
void SetWidgetIndex(int index);
@@ -31,6 +31,7 @@ public slots:
void on_NetplaySessionBrowserWidget_currentChanged(int index);
private:
QString baseLoadingText;
QLabel* loadingLabel;
int loadingLabelTimerId = -1;
int widgetIndex = -1;
@@ -87,7 +87,7 @@ void NetplaySessionBrowserWidget::StartRefresh(void)
}
void NetplaySessionBrowserWidget::AddSessionData(QString name, QString game, QString md5, bool password, int port,
QString cpuEmulator, QString rspPlugin, QString gfxPlugin)
QString cpuEmulator, QString rspPlugin, QString gfxPlugin, QUrl address)
{
const NetplaySessionData sessionData =
{
@@ -98,7 +98,8 @@ void NetplaySessionBrowserWidget::AddSessionData(QString name, QString game, QSt
port,
cpuEmulator,
rspPlugin,
gfxPlugin
gfxPlugin,
address
};
int row = this->tableWidget->rowCount();
@@ -16,6 +16,7 @@
#include <QTableWidgetItem>
#include <QStackedWidget>
#include <QTableWidget>
#include <QUrl>
// session data
struct NetplaySessionData
@@ -28,6 +29,7 @@ struct NetplaySessionData
QString CpuEmulator;
QString RspPlugin;
QString GfxPlugin;
QUrl Address;
};
namespace UserInterface
@@ -47,7 +49,7 @@ class NetplaySessionBrowserWidget : public QStackedWidget
void StartRefresh(void);
void AddSessionData(QString name, QString game, QString md5, bool password, int port,
QString cpuEmulator, QString rspPlugin, QString gfxPlugin);
QString cpuEmulator, QString rspPlugin, QString gfxPlugin, QUrl address);
void RefreshDone(void);