Merge pull request #7945 from spycrab/np_browser
Qt/NetPlay: Implement session/server browser
This commit is contained in:
commit
23986d48f7
|
@ -36,6 +36,7 @@ public:
|
|||
|
||||
static int CurlProgressCallback(Impl* impl, double dlnow, double dltotal, double ulnow,
|
||||
double ultotal);
|
||||
std::string EscapeComponent(const std::string& string);
|
||||
|
||||
private:
|
||||
static std::mutex s_curl_was_inited_mutex;
|
||||
|
@ -74,6 +75,11 @@ void HttpRequest::FollowRedirects(long max)
|
|||
m_impl->FollowRedirects(max);
|
||||
}
|
||||
|
||||
std::string HttpRequest::EscapeComponent(const std::string& string)
|
||||
{
|
||||
return m_impl->EscapeComponent(string);
|
||||
}
|
||||
|
||||
HttpRequest::Response HttpRequest::Get(const std::string& url, const Headers& headers)
|
||||
{
|
||||
return m_impl->Fetch(url, Impl::Method::GET, headers, nullptr, 0);
|
||||
|
@ -159,6 +165,11 @@ void HttpRequest::Impl::FollowRedirects(long max)
|
|||
curl_easy_setopt(m_curl.get(), CURLOPT_MAXREDIRS, max);
|
||||
}
|
||||
|
||||
std::string HttpRequest::Impl::EscapeComponent(const std::string& string)
|
||||
{
|
||||
return curl_easy_escape(m_curl.get(), string.c_str(), static_cast<int>(string.size()));
|
||||
}
|
||||
|
||||
static size_t CurlWriteCallback(char* data, size_t size, size_t nmemb, void* userdata)
|
||||
{
|
||||
auto* buffer = static_cast<std::vector<u8>*>(userdata);
|
||||
|
|
|
@ -34,6 +34,7 @@ public:
|
|||
void SetCookies(const std::string& cookies);
|
||||
void UseIPv4();
|
||||
void FollowRedirects(long max = 1);
|
||||
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<u8>& payload,
|
||||
const Headers& headers = {});
|
||||
|
|
|
@ -19,6 +19,15 @@ const ConfigInfo<std::string> NETPLAY_TRAVERSAL_SERVER{{System::Main, "NetPlay",
|
|||
const ConfigInfo<u16> NETPLAY_TRAVERSAL_PORT{{System::Main, "NetPlay", "TraversalPort"}, 6262};
|
||||
const ConfigInfo<std::string> NETPLAY_TRAVERSAL_CHOICE{{System::Main, "NetPlay", "TraversalChoice"},
|
||||
"direct"};
|
||||
const ConfigInfo<std::string> NETPLAY_INDEX_URL{{System::Main, "NetPlay", "IndexServer"},
|
||||
"https://lobby.dolphin-emu.org"};
|
||||
|
||||
const ConfigInfo<bool> NETPLAY_USE_INDEX{{System::Main, "NetPlay", "UseIndex"}, false};
|
||||
const ConfigInfo<std::string> NETPLAY_INDEX_NAME{{System::Main, "NetPlay", "IndexName"}, ""};
|
||||
const ConfigInfo<std::string> NETPLAY_INDEX_REGION{{System::Main, "NetPlay", "IndexRegion"}, ""};
|
||||
const ConfigInfo<std::string> NETPLAY_INDEX_PASSWORD{{System::Main, "NetPlay", "IndexPassword"},
|
||||
""};
|
||||
|
||||
const ConfigInfo<std::string> NETPLAY_HOST_CODE{{System::Main, "NetPlay", "HostCode"}, "00000000"};
|
||||
|
||||
const ConfigInfo<u16> NETPLAY_HOST_PORT{{System::Main, "NetPlay", "HostPort"}, DEFAULT_LISTEN_PORT};
|
||||
|
|
|
@ -19,6 +19,7 @@ extern const ConfigInfo<std::string> NETPLAY_TRAVERSAL_SERVER;
|
|||
extern const ConfigInfo<u16> NETPLAY_TRAVERSAL_PORT;
|
||||
extern const ConfigInfo<std::string> NETPLAY_TRAVERSAL_CHOICE;
|
||||
extern const ConfigInfo<std::string> NETPLAY_HOST_CODE;
|
||||
extern const ConfigInfo<std::string> NETPLAY_INDEX_URL;
|
||||
|
||||
extern const ConfigInfo<u16> NETPLAY_HOST_PORT;
|
||||
extern const ConfigInfo<std::string> NETPLAY_ADDRESS;
|
||||
|
@ -30,6 +31,11 @@ extern const ConfigInfo<bool> NETPLAY_USE_UPNP;
|
|||
|
||||
extern const ConfigInfo<bool> NETPLAY_ENABLE_QOS;
|
||||
|
||||
extern const ConfigInfo<bool> NETPLAY_USE_INDEX;
|
||||
extern const ConfigInfo<std::string> NETPLAY_INDEX_REGION;
|
||||
extern const ConfigInfo<std::string> NETPLAY_INDEX_NAME;
|
||||
extern const ConfigInfo<std::string> NETPLAY_INDEX_PASSWORD;
|
||||
|
||||
extern const ConfigInfo<bool> NETPLAY_ENABLE_CHUNKED_UPLOAD_LIMIT;
|
||||
extern const ConfigInfo<u32> NETPLAY_CHUNKED_UPLOAD_LIMIT;
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "Common/ENetUtil.h"
|
||||
#include "Common/File.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/HttpRequest.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/SFMLHelper.h"
|
||||
|
@ -132,6 +133,8 @@ NetPlayServer::NetPlayServer(const u16 port, const bool forward_port,
|
|||
m_server = enet_host_create(&serverAddr, 10, CHANNEL_COUNT, 0, 0);
|
||||
if (m_server != nullptr)
|
||||
m_server->intercept = ENetUtil::InterceptCallback;
|
||||
|
||||
SetupIndex();
|
||||
}
|
||||
if (m_server != nullptr)
|
||||
{
|
||||
|
@ -162,6 +165,45 @@ static void ClearPeerPlayerId(ENetPeer* peer)
|
|||
}
|
||||
}
|
||||
|
||||
void NetPlayServer::SetupIndex()
|
||||
{
|
||||
if (!Config::Get(Config::NETPLAY_USE_INDEX))
|
||||
return;
|
||||
|
||||
NetPlaySession session;
|
||||
|
||||
session.name = Config::Get(Config::NETPLAY_INDEX_NAME);
|
||||
session.region = Config::Get(Config::NETPLAY_INDEX_REGION);
|
||||
session.has_password = !Config::Get(Config::NETPLAY_INDEX_PASSWORD).empty();
|
||||
session.method = m_traversal_client ? "traversal" : "direct";
|
||||
session.game_id = m_selected_game.empty() ? "UNKNOWN" : m_selected_game;
|
||||
session.player_count = static_cast<int>(m_players.size());
|
||||
session.in_game = m_is_running;
|
||||
session.port = GetPort();
|
||||
|
||||
if (m_traversal_client)
|
||||
{
|
||||
session.server_id = std::string(g_TraversalClient->GetHostID().data(), 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
Common::HttpRequest request;
|
||||
// ENet does not support IPv6, so IPv4 has to be used
|
||||
request.UseIPv4();
|
||||
Common::HttpRequest::Response response =
|
||||
request.Get("https://ip.dolphin-emu.org/", {{"X-Is-Dolphin", "1"}});
|
||||
|
||||
if (!response.has_value())
|
||||
return;
|
||||
|
||||
session.server_id = std::string(response->begin(), response->end());
|
||||
}
|
||||
|
||||
session.EncryptID(Config::Get(Config::NETPLAY_INDEX_PASSWORD));
|
||||
|
||||
m_index.Add(session);
|
||||
}
|
||||
|
||||
// called from ---NETPLAY--- thread
|
||||
void NetPlayServer::ThreadFunc()
|
||||
{
|
||||
|
@ -178,6 +220,11 @@ void NetPlayServer::ThreadFunc()
|
|||
|
||||
m_ping_timer.Start();
|
||||
SendToClients(spac);
|
||||
|
||||
m_index.SetPlayerCount(static_cast<int>(m_players.size()));
|
||||
m_index.SetGame(m_selected_game);
|
||||
m_index.SetInGame(m_is_running);
|
||||
|
||||
m_update_pings = false;
|
||||
}
|
||||
|
||||
|
@ -283,7 +330,7 @@ void NetPlayServer::ThreadFunc()
|
|||
ClearPeerPlayerId(player_entry.second.socket);
|
||||
enet_peer_disconnect(player_entry.second.socket, 0);
|
||||
}
|
||||
}
|
||||
} // namespace NetPlay
|
||||
|
||||
// called from ---NETPLAY--- thread
|
||||
unsigned int NetPlayServer::OnConnect(ENetPeer* socket, sf::Packet& rpac)
|
||||
|
@ -1067,11 +1114,14 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
|
|||
|
||||
void NetPlayServer::OnTraversalStateChanged()
|
||||
{
|
||||
const TraversalClient::State state = m_traversal_client->GetState();
|
||||
|
||||
if (g_TraversalClient->GetHostID()[0] != '\0')
|
||||
SetupIndex();
|
||||
|
||||
if (!m_dialog)
|
||||
return;
|
||||
|
||||
const TraversalClient::State state = m_traversal_client->GetState();
|
||||
|
||||
if (state == TraversalClient::Failure)
|
||||
m_dialog->OnTraversalError(m_traversal_client->GetFailureReason());
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "Common/TraversalClient.h"
|
||||
#include "Core/NetPlayProto.h"
|
||||
#include "InputCommon/GCPadStatus.h"
|
||||
#include "UICommon/NetPlayIndex.h"
|
||||
|
||||
namespace NetPlay
|
||||
{
|
||||
|
@ -136,6 +137,7 @@ private:
|
|||
std::vector<std::pair<std::string, std::string>> GetInterfaceListInternal() const;
|
||||
void ChunkedDataThreadFunc();
|
||||
void ChunkedDataSend(sf::Packet&& packet, PlayerId pid, const TargetMode target_mode);
|
||||
void SetupIndex();
|
||||
|
||||
bool PlayerHasControllerMapped(PlayerId pid) const;
|
||||
|
||||
|
@ -187,5 +189,6 @@ private:
|
|||
ENetHost* m_server = nullptr;
|
||||
TraversalClient* m_traversal_client = nullptr;
|
||||
NetPlayUI* m_dialog = nullptr;
|
||||
NetPlayIndex m_index;
|
||||
};
|
||||
} // namespace NetPlay
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -147,6 +147,7 @@
|
|||
<QtMoc Include="NetPlay\ChunkedProgressDialog.h" />
|
||||
<QtMoc Include="NetPlay\GameListDialog.h" />
|
||||
<QtMoc Include="NetPlay\MD5Dialog.h" />
|
||||
<QtMoc Include="NetPlay\NetPlayBrowser.h" />
|
||||
<QtMoc Include="NetPlay\NetPlayDialog.h" />
|
||||
<QtMoc Include="NetPlay\NetPlaySetupDialog.h" />
|
||||
<QtMoc Include="NetPlay\PadMappingDialog.h" />
|
||||
|
@ -248,6 +249,7 @@
|
|||
<ClCompile Include="$(QtMocOutPrefix)MemoryWidget.cpp" />
|
||||
<ClCompile Include="$(QtMocOutPrefix)MenuBar.cpp" />
|
||||
<ClCompile Include="$(QtMocOutPrefix)ModalMessageBox.cpp" />
|
||||
<ClCompile Include="$(QtMocOutPrefix)NetPlayBrowser.cpp" />
|
||||
<ClCompile Include="$(QtMocOutPrefix)NetPlayDialog.cpp" />
|
||||
<ClCompile Include="$(QtMocOutPrefix)NetPlaySetupDialog.cpp" />
|
||||
<ClCompile Include="$(QtMocOutPrefix)NewBreakpointDialog.cpp" />
|
||||
|
@ -367,6 +369,7 @@
|
|||
<ClCompile Include="NetPlay\ChunkedProgressDialog.cpp" />
|
||||
<ClCompile Include="NetPlay\GameListDialog.cpp" />
|
||||
<ClCompile Include="NetPlay\MD5Dialog.cpp" />
|
||||
<ClCompile Include="NetPlay\NetPlayBrowser.cpp" />
|
||||
<ClCompile Include="NetPlay\NetPlayDialog.cpp" />
|
||||
<ClCompile Include="NetPlay\NetPlaySetupDialog.cpp" />
|
||||
<ClCompile Include="NetPlay\PadMappingDialog.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"
|
||||
|
@ -453,6 +454,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);
|
||||
|
||||
|
@ -1131,6 +1133,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)
|
||||
|
|
|
@ -148,6 +148,7 @@ private:
|
|||
void ShowAboutDialog();
|
||||
void ShowHotkeyDialog();
|
||||
void ShowNetPlaySetupDialog();
|
||||
void ShowNetPlayBrowser();
|
||||
void ShowFIFOPlayer();
|
||||
void ShowMemcardManager();
|
||||
void ShowResourcePackManager();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -61,6 +61,7 @@ signals:
|
|||
void FrameAdvance();
|
||||
void Screenshot();
|
||||
void StartNetPlay();
|
||||
void BrowseNetPlay();
|
||||
void StateLoad();
|
||||
void StateSave();
|
||||
void StateLoadSlot();
|
||||
|
|
|
@ -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 <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QHeaderView>
|
||||
#include <QInputDialog>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include <QSpacerItem>
|
||||
#include <QTableWidget>
|
||||
#include <QTableWidgetItem>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#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<std::string, std::string> 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<int>(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();
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright 2019 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#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<NetPlaySession> m_sessions;
|
||||
};
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -5,6 +5,7 @@ add_library(uicommon
|
|||
DiscordPresence.cpp
|
||||
GameFile.cpp
|
||||
GameFileCache.cpp
|
||||
NetPlayIndex.cpp
|
||||
ResourcePack/Manager.cpp
|
||||
ResourcePack/Manifest.cpp
|
||||
ResourcePack/ResourcePack.cpp
|
||||
|
|
|
@ -0,0 +1,324 @@
|
|||
// Copyright 2019 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "UICommon/NetPlayIndex.h"
|
||||
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
|
||||
#include <picojson/picojson.h>
|
||||
|
||||
#include "Common/Common.h"
|
||||
#include "Common/HttpRequest.h"
|
||||
#include "Common/Thread.h"
|
||||
#include "Common/Version.h"
|
||||
|
||||
#include "Core/Config/NetplaySettings.h"
|
||||
|
||||
NetPlayIndex::NetPlayIndex() = default;
|
||||
|
||||
NetPlayIndex::~NetPlayIndex()
|
||||
{
|
||||
if (!m_secret.empty())
|
||||
Remove();
|
||||
}
|
||||
|
||||
static std::optional<picojson::value> ParseResponse(std::vector<u8> response)
|
||||
{
|
||||
std::string response_string(reinterpret_cast<char*>(response.data()), response.size());
|
||||
|
||||
picojson::value json;
|
||||
|
||||
auto error = picojson::parse(json, response_string);
|
||||
|
||||
if (!error.empty())
|
||||
return {};
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
std::optional<std::vector<NetPlaySession>>
|
||||
NetPlayIndex::List(const std::map<std::string, std::string>& filters)
|
||||
{
|
||||
Common::HttpRequest request;
|
||||
|
||||
std::string list_url = Config::Get(Config::NETPLAY_INDEX_URL) + "/v0/list";
|
||||
|
||||
if (!filters.empty())
|
||||
{
|
||||
list_url += '?';
|
||||
for (const auto& filter : filters)
|
||||
{
|
||||
list_url += filter.first + '=' + request.EscapeComponent(filter.second) + '&';
|
||||
}
|
||||
list_url.pop_back();
|
||||
}
|
||||
|
||||
auto response = request.Get(list_url, {{"X-Is-Dolphin", "1"}});
|
||||
if (!response)
|
||||
{
|
||||
m_last_error = "NO_RESPONSE";
|
||||
return {};
|
||||
}
|
||||
|
||||
auto json = ParseResponse(response.value());
|
||||
|
||||
if (!json)
|
||||
{
|
||||
m_last_error = "BAD_JSON";
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto& status = json->get("status");
|
||||
|
||||
if (status.to_str() != "OK")
|
||||
{
|
||||
m_last_error = status.to_str();
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto& entries = json->get("sessions");
|
||||
|
||||
std::vector<NetPlaySession> sessions;
|
||||
|
||||
for (const auto& entry : entries.get<picojson::array>())
|
||||
{
|
||||
NetPlaySession session;
|
||||
|
||||
const auto& name = entry.get("name");
|
||||
const auto& region = entry.get("region");
|
||||
const auto& method = entry.get("method");
|
||||
const auto& game_id = entry.get("game");
|
||||
const auto& server_id = entry.get("server_id");
|
||||
const auto& has_password = entry.get("password");
|
||||
const auto& player_count = entry.get("player_count");
|
||||
const auto& port = entry.get("port");
|
||||
const auto& in_game = entry.get("in_game");
|
||||
|
||||
if (!name.is<std::string>() || !region.is<std::string>() || !method.is<std::string>() ||
|
||||
!server_id.is<std::string>() || !game_id.is<std::string>() || !has_password.is<bool>() ||
|
||||
!player_count.is<double>() || !port.is<double>() || !in_game.is<bool>())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
session.name = name.to_str();
|
||||
session.region = region.to_str();
|
||||
session.game_id = game_id.to_str();
|
||||
session.server_id = server_id.to_str();
|
||||
session.method = method.to_str();
|
||||
session.has_password = has_password.get<bool>();
|
||||
session.player_count = static_cast<int>(player_count.get<double>());
|
||||
session.port = static_cast<int>(port.get<double>());
|
||||
session.in_game = in_game.get<bool>();
|
||||
|
||||
sessions.push_back(std::move(session));
|
||||
}
|
||||
|
||||
return sessions;
|
||||
}
|
||||
|
||||
void NetPlayIndex::NotificationLoop()
|
||||
{
|
||||
while (m_running.IsSet())
|
||||
{
|
||||
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),
|
||||
{{"X-Is-Dolphin", "1"}});
|
||||
|
||||
if (!response)
|
||||
continue;
|
||||
|
||||
auto json = ParseResponse(response.value());
|
||||
|
||||
if (!json)
|
||||
{
|
||||
m_last_error = "BAD_JSON";
|
||||
m_running.Set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string status = json->get("status").to_str();
|
||||
|
||||
if (status != "OK")
|
||||
{
|
||||
m_last_error = std::move(status);
|
||||
m_running.Set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
Common::SleepCurrentThread(1000 * 5);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
{{"X-Is-Dolphin", "1"}});
|
||||
|
||||
if (!response.has_value())
|
||||
{
|
||||
m_last_error = "NO_RESPONSE";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto json = ParseResponse(response.value());
|
||||
|
||||
if (!json)
|
||||
{
|
||||
m_last_error = "BAD_JSON";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string status = json->get("status").to_str();
|
||||
|
||||
if (status != "OK")
|
||||
{
|
||||
m_last_error = std::move(status);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_secret = json->get("secret").to_str();
|
||||
m_in_game = session.in_game;
|
||||
m_player_count = session.player_count;
|
||||
m_game = session.game_id;
|
||||
|
||||
m_session_thread = std::thread([this] { NotificationLoop(); });
|
||||
|
||||
m_session_thread.detach();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NetPlayIndex::SetInGame(bool in_game)
|
||||
{
|
||||
m_in_game = in_game;
|
||||
}
|
||||
|
||||
void NetPlayIndex::SetPlayerCount(int player_count)
|
||||
{
|
||||
m_player_count = player_count;
|
||||
}
|
||||
|
||||
void NetPlayIndex::SetGame(const std::string game)
|
||||
{
|
||||
m_game = std::move(game);
|
||||
}
|
||||
|
||||
void NetPlayIndex::Remove()
|
||||
{
|
||||
if (m_secret.empty())
|
||||
return;
|
||||
|
||||
m_running.Set(false);
|
||||
|
||||
if (m_session_thread.joinable())
|
||||
m_session_thread.join();
|
||||
|
||||
// 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,
|
||||
{{"X-Is-Dolphin", "1"}});
|
||||
|
||||
m_secret.clear();
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> NetPlayIndex::GetRegions()
|
||||
{
|
||||
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
|
||||
// It isn't very secure but is preferable to adding another dependency on mbedtls
|
||||
// The encrypted data is encoded as nibbles with the character 'A' as the base offset
|
||||
|
||||
bool NetPlaySession::EncryptID(const std::string& password)
|
||||
{
|
||||
if (password.empty())
|
||||
return false;
|
||||
|
||||
std::string to_encrypt = server_id;
|
||||
|
||||
// Calculate and append checksum to ID
|
||||
const u8 sum = std::accumulate(to_encrypt.begin(), to_encrypt.end(), u8{0});
|
||||
to_encrypt += sum;
|
||||
|
||||
std::string encrypted_id;
|
||||
|
||||
u8 i = 0;
|
||||
for (const char byte : to_encrypt)
|
||||
{
|
||||
char c = byte ^ password[i % password.size()];
|
||||
c += i;
|
||||
encrypted_id += 'A' + ((c & 0xF0) >> 4);
|
||||
encrypted_id += 'A' + (c & 0x0F);
|
||||
++i;
|
||||
}
|
||||
|
||||
server_id = std::move(encrypted_id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<std::string> NetPlaySession::DecryptID(const std::string& password) const
|
||||
{
|
||||
if (password.empty())
|
||||
return {};
|
||||
|
||||
// If the length of an encrypted session id is not divisble by two, it's invalid
|
||||
if (server_id.empty() || server_id.size() % 2 != 0)
|
||||
return {};
|
||||
|
||||
std::string decoded;
|
||||
|
||||
for (size_t i = 0; i < server_id.size(); i += 2)
|
||||
{
|
||||
char c = (server_id[i] - 'A') << 4 | (server_id[i + 1] - 'A');
|
||||
decoded.push_back(c);
|
||||
}
|
||||
|
||||
u8 i = 0;
|
||||
for (auto& c : decoded)
|
||||
{
|
||||
c -= i;
|
||||
c ^= password[i % password.size()];
|
||||
++i;
|
||||
}
|
||||
|
||||
// Verify checksum
|
||||
const u8 expected_sum = decoded[decoded.size() - 1];
|
||||
|
||||
decoded.pop_back();
|
||||
|
||||
const u8 sum = std::accumulate(decoded.begin(), decoded.end(), u8{0});
|
||||
|
||||
if (sum != expected_sum)
|
||||
return {};
|
||||
|
||||
return decoded;
|
||||
}
|
||||
|
||||
const std::string& NetPlayIndex::GetLastError() const
|
||||
{
|
||||
return m_last_error;
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright 2019 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/Flag.h"
|
||||
|
||||
struct NetPlaySession
|
||||
{
|
||||
std::string name;
|
||||
std::string region;
|
||||
std::string method;
|
||||
std::string server_id;
|
||||
std::string game_id;
|
||||
|
||||
int player_count;
|
||||
int port;
|
||||
|
||||
bool has_password;
|
||||
bool in_game;
|
||||
|
||||
bool EncryptID(const std::string& password);
|
||||
std::optional<std::string> DecryptID(const std::string& password) const;
|
||||
};
|
||||
|
||||
class NetPlayIndex
|
||||
{
|
||||
public:
|
||||
explicit NetPlayIndex();
|
||||
~NetPlayIndex();
|
||||
|
||||
std::optional<std::vector<NetPlaySession>>
|
||||
List(const std::map<std::string, std::string>& filters = {});
|
||||
|
||||
static std::vector<std::pair<std::string, std::string>> GetRegions();
|
||||
|
||||
bool Add(NetPlaySession session);
|
||||
void Remove();
|
||||
|
||||
void SetPlayerCount(int player_count);
|
||||
void SetInGame(bool in_game);
|
||||
void SetGame(std::string game);
|
||||
|
||||
const std::string& GetLastError() const;
|
||||
|
||||
private:
|
||||
void NotificationLoop();
|
||||
|
||||
Common::Flag m_running;
|
||||
|
||||
std::string m_secret;
|
||||
std::string m_game;
|
||||
int m_player_count = 0;
|
||||
bool m_in_game = false;
|
||||
|
||||
std::string m_last_error;
|
||||
std::thread m_session_thread;
|
||||
};
|
|
@ -53,6 +53,7 @@
|
|||
<ClCompile Include="AutoUpdate.cpp" />
|
||||
<ClCompile Include="CommandLineParse.cpp" />
|
||||
<ClCompile Include="DiscordPresence.cpp" />
|
||||
<ClCompile Include="NetPlayIndex.cpp" />
|
||||
<ClCompile Include="ResourcePack\Manager.cpp" />
|
||||
<ClCompile Include="ResourcePack\Manifest.cpp" />
|
||||
<ClCompile Include="ResourcePack\ResourcePack.cpp" />
|
||||
|
@ -69,6 +70,7 @@
|
|||
<ClInclude Include="AutoUpdate.h" />
|
||||
<ClInclude Include="CommandLineParse.h" />
|
||||
<ClInclude Include="DiscordPresence.h" />
|
||||
<ClInclude Include="NetPlayIndex.h" />
|
||||
<ClInclude Include="ResourcePack\Manager.h" />
|
||||
<ClInclude Include="ResourcePack\Manifest.h" />
|
||||
<ClInclude Include="ResourcePack\ResourcePack.h" />
|
||||
|
@ -86,4 +88,4 @@
|
|||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
Loading…
Reference in New Issue