diff --git a/Source/Core/Common/HttpRequest.cpp b/Source/Core/Common/HttpRequest.cpp index 82fed65598..ab3fd11c34 100644 --- a/Source/Core/Common/HttpRequest.cpp +++ b/Source/Core/Common/HttpRequest.cpp @@ -36,7 +36,7 @@ public: static int CurlProgressCallback(Impl* impl, double dlnow, double dltotal, double ulnow, double ultotal); - const std::string EscapeComponent(const std::string& string); + std::string EscapeComponent(const std::string& string); private: static std::mutex s_curl_was_inited_mutex; @@ -75,7 +75,7 @@ void HttpRequest::FollowRedirects(long max) m_impl->FollowRedirects(max); } -const std::string HttpRequest::EscapeComponent(const std::string& string) +std::string HttpRequest::EscapeComponent(const std::string& string) { return m_impl->EscapeComponent(string); } @@ -165,7 +165,7 @@ void HttpRequest::Impl::FollowRedirects(long max) curl_easy_setopt(m_curl.get(), CURLOPT_MAXREDIRS, max); } -const std::string HttpRequest::Impl::EscapeComponent(const std::string& string) +std::string HttpRequest::Impl::EscapeComponent(const std::string& string) { return curl_easy_escape(m_curl.get(), string.c_str(), static_cast(string.size())); } diff --git a/Source/Core/Common/HttpRequest.h b/Source/Core/Common/HttpRequest.h index a557ce1ec0..54f13ff73b 100644 --- a/Source/Core/Common/HttpRequest.h +++ b/Source/Core/Common/HttpRequest.h @@ -34,7 +34,7 @@ public: void SetCookies(const std::string& cookies); void UseIPv4(); void FollowRedirects(long max = 1); - const std::string EscapeComponent(const std::string& string); + std::string EscapeComponent(const std::string& string); Response Get(const std::string& url, const Headers& headers = {}); Response Post(const std::string& url, const std::vector& payload, const Headers& headers = {}); diff --git a/Source/Core/Core/Config/NetplaySettings.cpp b/Source/Core/Core/Config/NetplaySettings.cpp index 2109300586..2fd2609606 100644 --- a/Source/Core/Core/Config/NetplaySettings.cpp +++ b/Source/Core/Core/Config/NetplaySettings.cpp @@ -20,7 +20,7 @@ const ConfigInfo NETPLAY_TRAVERSAL_PORT{{System::Main, "NetPlay", "Traversa const ConfigInfo NETPLAY_TRAVERSAL_CHOICE{{System::Main, "NetPlay", "TraversalChoice"}, "direct"}; const ConfigInfo NETPLAY_INDEX_URL{{System::Main, "NetPlay", "IndexServer"}, - "https://dolphin-emu.org/lobby"}; + "https://lobby.dolphin-emu.org"}; const ConfigInfo NETPLAY_USE_INDEX{{System::Main, "NetPlay", "UseIndex"}, false}; const ConfigInfo NETPLAY_INDEX_NAME{{System::Main, "NetPlay", "IndexName"}, ""}; diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index 85bc526ae6..61e587feb9 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -98,6 +98,7 @@ add_executable(dolphin-emu NetPlay/ChunkedProgressDialog.cpp NetPlay/GameListDialog.cpp NetPlay/MD5Dialog.cpp + NetPlay/NetPlayBrowser.cpp NetPlay/NetPlayDialog.cpp NetPlay/NetPlaySetupDialog.cpp NetPlay/PadMappingDialog.cpp diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index e2fefa340c..3d9b7ebf04 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -146,6 +146,7 @@ + @@ -247,6 +248,7 @@ + @@ -364,6 +366,7 @@ + diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 9814644f78..7e2e2809b3 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -80,6 +80,7 @@ #include "DolphinQt/HotkeyScheduler.h" #include "DolphinQt/MainWindow.h" #include "DolphinQt/MenuBar.h" +#include "DolphinQt/NetPlay/NetPlayBrowser.h" #include "DolphinQt/NetPlay/NetPlayDialog.h" #include "DolphinQt/NetPlay/NetPlaySetupDialog.h" #include "DolphinQt/QtUtils/ModalMessageBox.h" @@ -449,6 +450,7 @@ void MainWindow::ConnectMenuBar() connect(m_menu_bar, &MenuBar::PerformOnlineUpdate, this, &MainWindow::PerformOnlineUpdate); connect(m_menu_bar, &MenuBar::BootWiiSystemMenu, this, &MainWindow::BootWiiSystemMenu); connect(m_menu_bar, &MenuBar::StartNetPlay, this, &MainWindow::ShowNetPlaySetupDialog); + connect(m_menu_bar, &MenuBar::BrowseNetPlay, this, &MainWindow::ShowNetPlayBrowser); connect(m_menu_bar, &MenuBar::ShowFIFOPlayer, this, &MainWindow::ShowFIFOPlayer); connect(m_menu_bar, &MenuBar::ConnectWiiRemote, this, &MainWindow::OnConnectWiiRemote); @@ -1125,6 +1127,13 @@ void MainWindow::ShowNetPlaySetupDialog() m_netplay_setup_dialog->activateWindow(); } +void MainWindow::ShowNetPlayBrowser() +{ + auto* browser = new NetPlayBrowser(this); + connect(browser, &NetPlayBrowser::Join, this, &MainWindow::NetPlayJoin); + browser->exec(); +} + void MainWindow::ShowFIFOPlayer() { if (!m_fifo_window) diff --git a/Source/Core/DolphinQt/MainWindow.h b/Source/Core/DolphinQt/MainWindow.h index edc6fc479b..ceecdf49e4 100644 --- a/Source/Core/DolphinQt/MainWindow.h +++ b/Source/Core/DolphinQt/MainWindow.h @@ -148,6 +148,7 @@ private: void ShowAboutDialog(); void ShowHotkeyDialog(); void ShowNetPlaySetupDialog(); + void ShowNetPlayBrowser(); void ShowFIFOPlayer(); void ShowMemcardManager(); void ShowResourcePackManager(); diff --git a/Source/Core/DolphinQt/MenuBar.cpp b/Source/Core/DolphinQt/MenuBar.cpp index 2ff5dfcc5c..a486905a63 100644 --- a/Source/Core/DolphinQt/MenuBar.cpp +++ b/Source/Core/DolphinQt/MenuBar.cpp @@ -241,6 +241,7 @@ void MenuBar::AddToolsMenu() gc_ipl->addAction(tr("PAL"), this, [this] { emit BootGameCubeIPL(DiscIO::Region::PAL); }); tools_menu->addAction(tr("Start &NetPlay..."), this, &MenuBar::StartNetPlay); + tools_menu->addAction(tr("Browse &NetPlay Sessions...."), this, &MenuBar::BrowseNetPlay); tools_menu->addAction(tr("FIFO Player"), this, &MenuBar::ShowFIFOPlayer); tools_menu->addSeparator(); diff --git a/Source/Core/DolphinQt/MenuBar.h b/Source/Core/DolphinQt/MenuBar.h index dbb962e2ac..a6febece35 100644 --- a/Source/Core/DolphinQt/MenuBar.h +++ b/Source/Core/DolphinQt/MenuBar.h @@ -61,6 +61,7 @@ signals: void FrameAdvance(); void Screenshot(); void StartNetPlay(); + void BrowseNetPlay(); void StateLoad(); void StateSave(); void StateLoadSlot(); diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayBrowser.cpp b/Source/Core/DolphinQt/NetPlay/NetPlayBrowser.cpp new file mode 100644 index 0000000000..8097a0a48a --- /dev/null +++ b/Source/Core/DolphinQt/NetPlay/NetPlayBrowser.cpp @@ -0,0 +1,243 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt/NetPlay/NetPlayBrowser.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Common/Version.h" + +#include "Core/Config/NetplaySettings.h" +#include "Core/ConfigManager.h" + +#include "DolphinQt/QtUtils/ModalMessageBox.h" + +NetPlayBrowser::NetPlayBrowser(QWidget* parent) : QDialog(parent) +{ + setWindowTitle(tr("NetPlay Session Browser")); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + CreateWidgets(); + ConnectWidgets(); + + resize(750, 500); + + m_table_widget->verticalHeader()->setHidden(true); + m_table_widget->setAlternatingRowColors(true); + + Refresh(); +} + +void NetPlayBrowser::CreateWidgets() +{ + auto* layout = new QVBoxLayout; + + m_table_widget = new QTableWidget; + + m_table_widget->setSelectionBehavior(QAbstractItemView::SelectRows); + m_table_widget->setSelectionMode(QAbstractItemView::SingleSelection); + + m_region_combo = new QComboBox; + + m_region_combo->addItem(tr("Any Region")); + + for (const auto& region : NetPlayIndex::GetRegions()) + { + m_region_combo->addItem( + tr("%1 (%2)").arg(tr(region.second.c_str())).arg(QString::fromStdString(region.first)), + QString::fromStdString(region.first)); + } + + m_status_label = new QLabel; + m_button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + m_button_refresh = new QPushButton(tr("Refresh")); + m_edit_name = new QLineEdit; + m_edit_game_id = new QLineEdit; + + m_radio_all = new QRadioButton(tr("Private and Public")); + m_radio_private = new QRadioButton(tr("Private")); + m_radio_public = new QRadioButton(tr("Public")); + + m_radio_all->setChecked(true); + + auto* filter_box = new QGroupBox(tr("Filters")); + auto* filter_layout = new QGridLayout; + filter_box->setLayout(filter_layout); + + filter_layout->addWidget(new QLabel(tr("Region:")), 0, 0); + filter_layout->addWidget(m_region_combo, 0, 1); + filter_layout->addWidget(new QLabel(tr("Name:")), 1, 0); + filter_layout->addWidget(m_edit_name, 1, 1, 1, -1); + filter_layout->addWidget(new QLabel(tr("Game ID:")), 2, 0); + filter_layout->addWidget(m_edit_game_id, 2, 1, 1, -1); + filter_layout->addWidget(m_radio_all, 3, 1); + filter_layout->addWidget(m_radio_public, 3, 2); + filter_layout->addWidget(m_radio_private, 3, 3); + filter_layout->addItem(new QSpacerItem(4, 1, QSizePolicy::Expanding), 2, 4); + + layout->addWidget(m_table_widget); + layout->addWidget(filter_box); + layout->addWidget(m_status_label); + layout->addWidget(m_button_box); + + m_button_box->addButton(m_button_refresh, QDialogButtonBox::ResetRole); + m_button_box->button(QDialogButtonBox::Ok)->setEnabled(false); + + setLayout(layout); +} + +void NetPlayBrowser::ConnectWidgets() +{ + connect(m_button_box, &QDialogButtonBox::accepted, this, &NetPlayBrowser::accept); + connect(m_button_box, &QDialogButtonBox::rejected, this, &NetPlayBrowser::reject); + connect(m_button_refresh, &QPushButton::pressed, this, &NetPlayBrowser::Refresh); + + connect(m_radio_all, &QRadioButton::toggled, this, &NetPlayBrowser::Refresh); + connect(m_radio_private, &QRadioButton::toggled, this, &NetPlayBrowser::Refresh); + + connect(m_edit_name, &QLineEdit::textChanged, this, &NetPlayBrowser::Refresh); + connect(m_edit_game_id, &QLineEdit::textChanged, this, &NetPlayBrowser::Refresh); + connect(m_table_widget, &QTableWidget::itemSelectionChanged, this, + &NetPlayBrowser::OnSelectionChanged); +} + +void NetPlayBrowser::Refresh() +{ + m_status_label->setText(tr("Refreshing...")); + + m_table_widget->clear(); + m_table_widget->setColumnCount(6); + m_table_widget->setHorizontalHeaderLabels( + {tr("Region"), tr("Name"), tr("Password?"), tr("In-Game?"), tr("Game"), tr("Players")}); + m_table_widget->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + m_table_widget->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + m_table_widget->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents); + m_table_widget->horizontalHeader()->setSectionResizeMode(3, QHeaderView::ResizeToContents); + m_table_widget->horizontalHeader()->setSectionResizeMode(4, QHeaderView::Stretch); + + NetPlayIndex client; + + std::map filters; + + filters["version"] = Common::scm_desc_str; + + if (!m_edit_name->text().isEmpty()) + filters["name"] = m_edit_name->text().toStdString(); + + if (!m_edit_game_id->text().isEmpty()) + filters["game"] = m_edit_game_id->text().toStdString(); + + if (!m_radio_all->isChecked()) + filters["password"] = std::to_string(m_radio_private->isChecked()); + + if (m_region_combo->currentIndex() != 0) + filters["region"] = m_region_combo->currentData().toString().toStdString(); + + auto entries = client.List(filters); + + if (!entries) + { + m_status_label->setText( + tr("Error obtaining session list: %1").arg(QString::fromStdString(client.GetLastError()))); + return; + } + + const int session_count = static_cast(entries.value().size()); + + m_table_widget->setRowCount(session_count); + + for (int i = 0; i < session_count; i++) + { + const auto& entry = entries.value()[i]; + + auto* region = new QTableWidgetItem(QString::fromStdString(entry.region)); + auto* name = new QTableWidgetItem(QString::fromStdString(entry.name)); + auto* password = new QTableWidgetItem(entry.has_password ? tr("Yes") : tr("No")); + auto* in_game = new QTableWidgetItem(entry.in_game ? tr("Yes") : tr("No")); + auto* game_id = new QTableWidgetItem(QString::fromStdString(entry.game_id)); + auto* player_count = new QTableWidgetItem(QStringLiteral("%1").arg(entry.player_count)); + + for (const auto& item : {region, name, password, game_id, player_count}) + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + + m_table_widget->setItem(i, 0, region); + m_table_widget->setItem(i, 1, name); + m_table_widget->setItem(i, 2, password); + m_table_widget->setItem(i, 3, in_game); + m_table_widget->setItem(i, 4, game_id); + m_table_widget->setItem(i, 5, player_count); + } + + m_status_label->setText( + (session_count == 1 ? tr("%1 session found") : tr("%1 sessions found")).arg(session_count)); + + m_sessions = entries.value(); +} + +void NetPlayBrowser::OnSelectionChanged() +{ + m_button_box->button(QDialogButtonBox::Ok) + ->setEnabled(!m_table_widget->selectedItems().isEmpty()); +} + +void NetPlayBrowser::accept() +{ + const int index = m_table_widget->selectedItems()[0]->row(); + + NetPlaySession& session = m_sessions[index]; + + std::string server_id = session.server_id; + + if (m_sessions[index].has_password) + { + auto* dialog = new QInputDialog(this); + + dialog->setWindowFlags(dialog->windowFlags() & ~Qt::WindowContextHelpButtonHint); + dialog->setWindowTitle(tr("Enter password")); + dialog->setLabelText(tr("This session requires a password:")); + dialog->setWindowModality(Qt::WindowModal); + dialog->setTextEchoMode(QLineEdit::Password); + + if (dialog->exec() != QDialog::Accepted) + return; + + const std::string password = dialog->textValue().toStdString(); + + auto decrypted_id = session.DecryptID(password); + + if (!decrypted_id) + { + ModalMessageBox::warning(this, tr("Error"), tr("Invalid password provided.")); + return; + } + + server_id = decrypted_id.value(); + } + + QDialog::accept(); + + Config::SetBaseOrCurrent(Config::NETPLAY_TRAVERSAL_CHOICE, session.method); + + Config::SetBaseOrCurrent(Config::NETPLAY_CONNECT_PORT, session.port); + + if (session.method == "traversal") + Config::SetBaseOrCurrent(Config::NETPLAY_HOST_CODE, server_id); + else + Config::SetBaseOrCurrent(Config::NETPLAY_ADDRESS, server_id); + + emit Join(); +} diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayBrowser.h b/Source/Core/DolphinQt/NetPlay/NetPlayBrowser.h new file mode 100644 index 0000000000..f6cdfdfc5a --- /dev/null +++ b/Source/Core/DolphinQt/NetPlay/NetPlayBrowser.h @@ -0,0 +1,52 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include + +#include "UICommon/NetPlayIndex.h" + +class QComboBox; +class QDialogButtonBox; +class QLabel; +class QLineEdit; +class QPushButton; +class QRadioButton; +class QTableWidget; + +class NetPlayBrowser : public QDialog +{ + Q_OBJECT +public: + explicit NetPlayBrowser(QWidget* parent = nullptr); + + void accept() override; +signals: + void Join(); + +private: + void CreateWidgets(); + void ConnectWidgets(); + + void Refresh(); + + void OnSelectionChanged(); + + QComboBox* m_region_combo; + QLabel* m_status_label; + QPushButton* m_button_refresh; + QTableWidget* m_table_widget; + QDialogButtonBox* m_button_box; + QLineEdit* m_edit_name; + QLineEdit* m_edit_game_id; + + QRadioButton* m_radio_all; + QRadioButton* m_radio_private; + QRadioButton* m_radio_public; + + std::vector m_sessions; +}; diff --git a/Source/Core/DolphinQt/NetPlay/NetPlaySetupDialog.cpp b/Source/Core/DolphinQt/NetPlay/NetPlaySetupDialog.cpp index b960a99dd5..6cc078a421 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlaySetupDialog.cpp +++ b/Source/Core/DolphinQt/NetPlay/NetPlaySetupDialog.cpp @@ -22,6 +22,8 @@ #include "DolphinQt/QtUtils/ModalMessageBox.h" #include "DolphinQt/Settings.h" +#include "UICommon/NetPlayIndex.h" + NetPlaySetupDialog::NetPlaySetupDialog(QWidget* parent) : QDialog(parent), m_game_list_model(Settings::Instance().GetGameListModel()) { @@ -30,6 +32,10 @@ NetPlaySetupDialog::NetPlaySetupDialog(QWidget* parent) CreateMainLayout(); + bool use_index = Config::Get(Config::NETPLAY_USE_INDEX); + std::string index_region = Config::Get(Config::NETPLAY_INDEX_REGION); + std::string index_name = Config::Get(Config::NETPLAY_INDEX_NAME); + std::string index_password = Config::Get(Config::NETPLAY_INDEX_PASSWORD); std::string nickname = Config::Get(Config::NETPLAY_NICKNAME); std::string traversal_choice = Config::Get(Config::NETPLAY_TRAVERSAL_CHOICE); int connect_port = Config::Get(Config::NETPLAY_CONNECT_PORT); @@ -48,10 +54,21 @@ NetPlaySetupDialog::NetPlaySetupDialog(QWidget* parent) m_connect_port_box->setValue(connect_port); m_host_port_box->setValue(host_port); - m_host_force_port_check->setChecked(false); m_host_force_port_box->setValue(host_listen_port); m_host_force_port_box->setEnabled(false); + m_host_server_browser->setChecked(use_index); + + m_host_server_region->setEnabled(use_index); + m_host_server_region->setCurrentIndex( + m_host_server_region->findData(QString::fromStdString(index_region))); + + m_host_server_name->setEnabled(use_index); + m_host_server_name->setText(QString::fromStdString(index_name)); + + m_host_server_password->setEnabled(use_index); + m_host_server_password->setText(QString::fromStdString(index_password)); + m_host_chunked_upload_limit_check->setChecked(enable_chunked_upload_limit); m_host_chunked_upload_limit_box->setValue(chunked_upload_limit); m_host_chunked_upload_limit_box->setEnabled(enable_chunked_upload_limit); @@ -112,6 +129,10 @@ void NetPlaySetupDialog::CreateMainLayout() m_host_force_port_box = new QSpinBox; m_host_chunked_upload_limit_check = new QCheckBox(tr("Limit Chunked Upload Speed:")); m_host_chunked_upload_limit_box = new QSpinBox; + m_host_server_browser = new QCheckBox(tr("Show in server browser")); + m_host_server_name = new QLineEdit; + m_host_server_password = new QLineEdit; + m_host_server_region = new QComboBox; #ifdef USE_UPNP m_host_upnp = new QCheckBox(tr("Forward port (UPnP)")); @@ -128,17 +149,33 @@ void NetPlaySetupDialog::CreateMainLayout() m_host_chunked_upload_limit_check->setToolTip(tr( "This will limit the speed of chunked uploading per client, which is used for save sync.")); + m_host_server_name->setToolTip(tr("Name of your session shown in the server browser")); + m_host_server_name->setPlaceholderText(tr("Name")); + m_host_server_password->setToolTip(tr("Password for joining your game (leave empty for none)")); + m_host_server_password->setPlaceholderText(tr("Password")); + + for (const auto& region : NetPlayIndex::GetRegions()) + { + m_host_server_region->addItem( + tr("%1 (%2)").arg(tr(region.second.c_str())).arg(QString::fromStdString(region.first)), + QString::fromStdString(region.first)); + } + host_layout->addWidget(m_host_port_label, 0, 0); host_layout->addWidget(m_host_port_box, 0, 1); #ifdef USE_UPNP host_layout->addWidget(m_host_upnp, 0, 2); #endif - host_layout->addWidget(m_host_games, 1, 0, 1, -1); - host_layout->addWidget(m_host_force_port_check, 2, 0); - host_layout->addWidget(m_host_force_port_box, 2, 1, Qt::AlignLeft); - host_layout->addWidget(m_host_chunked_upload_limit_check, 3, 0); - host_layout->addWidget(m_host_chunked_upload_limit_box, 3, 1, Qt::AlignLeft); - host_layout->addWidget(m_host_button, 2, 2, 2, 1, Qt::AlignRight); + host_layout->addWidget(m_host_server_browser, 1, 0); + host_layout->addWidget(m_host_server_region, 1, 1); + host_layout->addWidget(m_host_server_name, 1, 2); + host_layout->addWidget(m_host_server_password, 1, 3); + host_layout->addWidget(m_host_games, 2, 0, 1, -1); + host_layout->addWidget(m_host_force_port_check, 3, 0); + host_layout->addWidget(m_host_force_port_box, 3, 1, Qt::AlignLeft); + host_layout->addWidget(m_host_chunked_upload_limit_check, 4, 0); + host_layout->addWidget(m_host_chunked_upload_limit_box, 4, 1, Qt::AlignLeft); + host_layout->addWidget(m_host_button, 4, 3, 2, 1, Qt::AlignRight); host_widget->setLayout(host_layout); @@ -199,6 +236,11 @@ void NetPlaySetupDialog::ConnectWidgets() connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(m_reset_traversal_button, &QPushButton::clicked, this, &NetPlaySetupDialog::ResetTraversalHost); + connect(m_host_server_browser, &QCheckBox::toggled, this, [this](bool value) { + m_host_server_region->setEnabled(value); + m_host_server_name->setEnabled(value); + m_host_server_password->setEnabled(value); + }); } void NetPlaySetupDialog::SaveSettings() @@ -224,6 +266,13 @@ void NetPlaySetupDialog::SaveSettings() m_host_chunked_upload_limit_check->isChecked()); Config::SetBaseOrCurrent(Config::NETPLAY_CHUNKED_UPLOAD_LIMIT, m_host_chunked_upload_limit_box->value()); + + Config::SetBaseOrCurrent(Config::NETPLAY_USE_INDEX, m_host_server_browser->isChecked()); + Config::SetBaseOrCurrent(Config::NETPLAY_INDEX_REGION, + m_host_server_region->currentData().toString().toStdString()); + Config::SetBaseOrCurrent(Config::NETPLAY_INDEX_NAME, m_host_server_name->text().toStdString()); + Config::SetBaseOrCurrent(Config::NETPLAY_INDEX_PASSWORD, + m_host_server_password->text().toStdString()); } void NetPlaySetupDialog::OnConnectionTypeChanged(int index) @@ -273,6 +322,12 @@ void NetPlaySetupDialog::accept() return; } + if (m_host_server_browser->isChecked() && m_host_server_name->text().isEmpty()) + { + ModalMessageBox::critical(this, tr("Error"), tr("You must provide a name for your session!")); + return; + } + emit Host(items[0]->text()); } } diff --git a/Source/Core/DolphinQt/NetPlay/NetPlaySetupDialog.h b/Source/Core/DolphinQt/NetPlay/NetPlaySetupDialog.h index 7f5456848b..ec0d4b3c5d 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlaySetupDialog.h +++ b/Source/Core/DolphinQt/NetPlay/NetPlaySetupDialog.h @@ -65,6 +65,10 @@ private: QSpinBox* m_host_force_port_box; QCheckBox* m_host_chunked_upload_limit_check; QSpinBox* m_host_chunked_upload_limit_box; + QCheckBox* m_host_server_browser; + QLineEdit* m_host_server_name; + QLineEdit* m_host_server_password; + QComboBox* m_host_server_region; #ifdef USE_UPNP QCheckBox* m_host_upnp; diff --git a/Source/Core/UICommon/NetPlayIndex.cpp b/Source/Core/UICommon/NetPlayIndex.cpp index 9092a96350..b41920c647 100644 --- a/Source/Core/UICommon/NetPlayIndex.cpp +++ b/Source/Core/UICommon/NetPlayIndex.cpp @@ -4,17 +4,19 @@ #include "UICommon/NetPlayIndex.h" +#include +#include + #include +#include "Common/Common.h" #include "Common/HttpRequest.h" #include "Common/Thread.h" #include "Common/Version.h" #include "Core/Config/NetplaySettings.h" -NetPlayIndex::NetPlayIndex() -{ -} +NetPlayIndex::NetPlayIndex() = default; NetPlayIndex::~NetPlayIndex() { @@ -45,16 +47,15 @@ NetPlayIndex::List(const std::map& filters) if (!filters.empty()) { - list_url += "?"; + list_url += '?'; for (const auto& filter : filters) { - list_url += filter.first + "=" + request.EscapeComponent(filter.second) + "&"; + list_url += filter.first + '=' + request.EscapeComponent(filter.second) + '&'; } - list_url = list_url.substr(0, list_url.size() - 1); + list_url.pop_back(); } - auto response = request.Get(list_url); - + auto response = request.Get(list_url, {{"X-Is-Dolphin", "1"}}); if (!response) { m_last_error = "NO_RESPONSE"; @@ -98,7 +99,9 @@ NetPlayIndex::List(const std::map& filters) if (!name.is() || !region.is() || !method.is() || !server_id.is() || !game_id.is() || !has_password.is() || !player_count.is() || !port.is() || !in_game.is()) + { continue; + } session.name = name.to_str(); session.region = region.to_str(); @@ -110,7 +113,7 @@ NetPlayIndex::List(const std::map& filters) session.port = static_cast(port.get()); session.in_game = in_game.get(); - sessions.push_back(session); + sessions.push_back(std::move(session)); } return sessions; @@ -123,8 +126,9 @@ void NetPlayIndex::NotificationLoop() Common::HttpRequest request; auto response = request.Get( Config::Get(Config::NETPLAY_INDEX_URL) + "/v0/session/active?secret=" + m_secret + - "&player_count=" + std::to_string(m_player_count) + - "&game=" + request.EscapeComponent(m_game) + "&in_game=" + std::to_string(m_in_game)); + "&player_count=" + std::to_string(m_player_count) + + "&game=" + request.EscapeComponent(m_game) + "&in_game=" + std::to_string(m_in_game), + {{"X-Is-Dolphin", "1"}}); if (!response) continue; @@ -142,7 +146,7 @@ void NetPlayIndex::NotificationLoop() if (status != "OK") { - m_last_error = status; + m_last_error = std::move(status); m_running.Set(false); return; } @@ -156,14 +160,17 @@ bool NetPlayIndex::Add(NetPlaySession session) m_running.Set(true); Common::HttpRequest request; - auto response = request.Get( - Config::Get(Config::NETPLAY_INDEX_URL) + "/v0/session/add?name=" + - request.EscapeComponent(session.name) + "®ion=" + request.EscapeComponent(session.region) + - "&game=" + request.EscapeComponent(session.game_id) + - "&password=" + std::to_string(session.has_password) + "&method=" + session.method + - "&server_id=" + session.server_id + "&in_game=" + std::to_string(session.in_game) + - "&port=" + std::to_string(session.port) + - "&player_count=" + std::to_string(session.player_count) + "&version=" + Common::scm_desc_str); + auto response = request.Get(Config::Get(Config::NETPLAY_INDEX_URL) + + "/v0/session/add?name=" + request.EscapeComponent(session.name) + + "®ion=" + request.EscapeComponent(session.region) + + "&game=" + request.EscapeComponent(session.game_id) + + "&password=" + std::to_string(session.has_password) + + "&method=" + session.method + "&server_id=" + session.server_id + + "&in_game=" + std::to_string(session.in_game) + + "&port=" + std::to_string(session.port) + + "&player_count=" + std::to_string(session.player_count) + + "&version=" + Common::scm_desc_str, + {{"X-Is-Dolphin", "1"}}); if (!response.has_value()) { @@ -183,7 +190,7 @@ bool NetPlayIndex::Add(NetPlaySession session) if (status != "OK") { - m_last_error = status; + m_last_error = std::move(status); return false; } @@ -209,9 +216,9 @@ void NetPlayIndex::SetPlayerCount(int player_count) m_player_count = player_count; } -void NetPlayIndex::SetGame(const std::string& game) +void NetPlayIndex::SetGame(const std::string game) { - m_game = game; + m_game = std::move(game); } void NetPlayIndex::Remove() @@ -226,16 +233,19 @@ void NetPlayIndex::Remove() // We don't really care whether this fails or not Common::HttpRequest request; - request.Get(Config::Get(Config::NETPLAY_INDEX_URL) + "/v0/session/remove?secret=" + m_secret); + request.Get(Config::Get(Config::NETPLAY_INDEX_URL) + "/v0/session/remove?secret=" + m_secret, + {{"X-Is-Dolphin", "1"}}); - m_secret = ""; + m_secret.clear(); } -std::vector NetPlayIndex::GetRegions() +std::vector> NetPlayIndex::GetRegions() { - static std::vector regions{"AF", "CN", "EA", "EU", "NA", "OC"}; - - return regions; + return { + {"EA", _trans("East Asia")}, {"CN", _trans("China")}, {"EU", _trans("Europe")}, + {"NA", _trans("North America")}, {"SA", _trans("South America")}, {"OC", _trans("Oceania")}, + {"AF", _trans("Africa")}, + }; } // This encryption system uses simple XOR operations and a checksum @@ -247,21 +257,16 @@ bool NetPlaySession::EncryptID(const std::string& password) if (password.empty()) return false; - u8 i = 0; - std::string to_encrypt = server_id; // Calculate and append checksum to ID - u8 sum = 0; - - for (char c : to_encrypt) - sum += c; - + const u8 sum = std::accumulate(to_encrypt.begin(), to_encrypt.end(), u8{0}); to_encrypt += sum; std::string encrypted_id; - for (const char& byte : to_encrypt) + u8 i = 0; + for (const char byte : to_encrypt) { char c = byte ^ password[i % password.size()]; c += i; @@ -270,7 +275,7 @@ bool NetPlaySession::EncryptID(const std::string& password) ++i; } - server_id = encrypted_id; + server_id = std::move(encrypted_id); return true; } @@ -301,13 +306,11 @@ std::optional NetPlaySession::DecryptID(const std::string& password } // Verify checksum - u8 expected_sum = decoded[decoded.size() - 1]; + const u8 expected_sum = decoded[decoded.size() - 1]; - decoded = decoded.substr(0, decoded.size() - 1); + decoded.pop_back(); - u8 sum = 0; - for (char c : decoded) - sum += c; + const u8 sum = std::accumulate(decoded.begin(), decoded.end(), u8{0}); if (sum != expected_sum) return {}; diff --git a/Source/Core/UICommon/NetPlayIndex.h b/Source/Core/UICommon/NetPlayIndex.h index 929e163eb2..2bc778efa0 100644 --- a/Source/Core/UICommon/NetPlayIndex.h +++ b/Source/Core/UICommon/NetPlayIndex.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "Common/Flag.h" @@ -39,14 +40,14 @@ public: std::optional> List(const std::map& filters = {}); - static std::vector GetRegions(); + static std::vector> GetRegions(); bool Add(NetPlaySession session); void Remove(); void SetPlayerCount(int player_count); void SetInGame(bool in_game); - void SetGame(const std::string& game); + void SetGame(std::string game); const std::string& GetLastError() const;