@@ -79,6 +79,7 @@ struct NetSettings
bool m_SyncSaveData;
bool m_SyncCodes;
std::string m_SaveDataRegion;
bool m_SyncAllWiiSaves;
bool m_IsHosting;
bool m_HostInputAuthority;
};
@@ -110,6 +111,12 @@ enum

NP_MSG_CHAT_MESSAGE = 0x30,

NP_MSG_CHUNKED_DATA_START = 0x40,
NP_MSG_CHUNKED_DATA_END = 0x41,
NP_MSG_CHUNKED_DATA_PAYLOAD = 0x42,
NP_MSG_CHUNKED_DATA_PROGRESS = 0x43,
NP_MSG_CHUNKED_DATA_COMPLETE = 0x44,

NP_MSG_PAD_DATA = 0x60,
NP_MSG_PAD_MAPPING = 0x61,
NP_MSG_PAD_BUFFER = 0x62,
@@ -179,6 +186,10 @@ enum

constexpr u32 NETPLAY_LZO_IN_LEN = 1024 * 64;
constexpr u32 NETPLAY_LZO_OUT_LEN = NETPLAY_LZO_IN_LEN + (NETPLAY_LZO_IN_LEN / 16) + 64 + 3;
constexpr size_t CHUNKED_DATA_UNIT_SIZE = 16384;
constexpr u8 CHANNEL_COUNT = 2;
constexpr u8 DEFAULT_CHANNEL = 0;
constexpr u8 CHUNKED_DATA_CHANNEL = 1;

using NetWiimote = std::vector<u8>;
using MessageId = u8;
@@ -192,8 +203,10 @@ bool IsNetPlayRunning();
// IsNetPlayRunning() must be true before calling this.
const NetSettings& GetNetSettings();
IOS::HLE::FS::FileSystem* GetWiiSyncFS();
void SetWiiSyncFS(std::unique_ptr<IOS::HLE::FS::FileSystem> fs);
void ClearWiiSyncFS();
const std::vector<u64>& GetWiiSyncTitles();
void SetWiiSyncData(std::unique_ptr<IOS::HLE::FS::FileSystem> fs, const std::vector<u64>& titles);
void ClearWiiSyncData();
void SetSIPollBatching(bool state);
void SendPowerButtonEvent();
bool IsSyncingAllWiiSaves();
} // namespace NetPlay

Large diffs are not rendered by default.

@@ -13,6 +13,8 @@
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include "Common/Event.h"
#include "Common/QoSSession.h"
#include "Common/SPSCQueue.h"
#include "Common/Timer.h"
@@ -29,7 +31,12 @@ class NetPlayServer : public TraversalClientClient
{
public:
void ThreadFunc();
void SendAsyncToClients(sf::Packet&& packet);
void SendAsync(sf::Packet&& packet, PlayerId pid, u8 channel_id = DEFAULT_CHANNEL);
void SendAsyncToClients(sf::Packet&& packet, PlayerId skip_pid = 0,
u8 channel_id = DEFAULT_CHANNEL);
void SendChunked(sf::Packet&& packet, PlayerId pid, const std::string& title = "");
void SendChunkedToClients(sf::Packet&& packet, PlayerId skip_pid = 0,
const std::string& title = "");

NetPlayServer(u16 port, bool forward_port, const NetTraversalConfig& traversal_config);
~NetPlayServer();
@@ -84,6 +91,28 @@ class NetPlayServer : public TraversalClientClient
bool IsHost() const { return pid == 1; }
};

enum class TargetMode
{
Only,
AllExcept
};

struct AsyncQueueEntry
{
sf::Packet packet;
PlayerId target_pid;
TargetMode target_mode;
u8 channel_id;
};

struct ChunkedDataQueueEntry
{
sf::Packet packet;
PlayerId target_pid;
TargetMode target_mode;
std::string title;
};

bool SyncSaveData();
bool SyncCodes();
void CheckSyncAndStartGame();
@@ -93,8 +122,9 @@ class NetPlayServer : public TraversalClientClient

u64 GetInitialNetPlayRTC() const;

void SendToClients(const sf::Packet& packet, const PlayerId skip_pid = 0);
void Send(ENetPeer* socket, const sf::Packet& packet);
void SendToClients(const sf::Packet& packet, PlayerId skip_pid = 0,
u8 channel_id = DEFAULT_CHANNEL);
void Send(ENetPeer* socket, const sf::Packet& packet, u8 channel_id = DEFAULT_CHANNEL);
unsigned int OnConnect(ENetPeer* socket);
unsigned int OnDisconnect(const Client& player);
unsigned int OnData(sf::Packet& packet, Client& player);
@@ -105,6 +135,8 @@ class NetPlayServer : public TraversalClientClient
void UpdatePadMapping();
void UpdateWiimoteMapping();
std::vector<std::pair<std::string, std::string>> GetInterfaceListInternal() const;
void ChunkedDataThreadFunc();
void ChunkedDataSend(sf::Packet&& packet, PlayerId pid, const TargetMode target_mode);

NetSettings m_settings;

@@ -138,11 +170,19 @@ class NetPlayServer : public TraversalClientClient
// lock order
std::recursive_mutex players;
std::recursive_mutex async_queue_write;
std::recursive_mutex chunked_data_queue_write;
} m_crit;

Common::SPSCQueue<AsyncQueueEntry, false> m_async_queue;
Common::SPSCQueue<ChunkedDataQueueEntry, false> m_chunked_data_queue;

std::string m_selected_game;
std::thread m_thread;
Common::SPSCQueue<sf::Packet, false> m_async_queue;
Common::Event m_chunked_data_event;
Common::Event m_chunked_data_complete_event;
std::thread m_chunked_data_thread;
u32 m_next_chunked_data_id;
std::unordered_map<u32, unsigned int> m_chunked_data_complete_count;

ENetHost* m_server = nullptr;
TraversalClient* m_traversal_client = nullptr;
@@ -17,6 +17,7 @@
#include "Common/StringUtil.h"
#include "Core/ConfigManager.h"
#include "Core/HW/WiiSave.h"
#include "Core/IOS/ES/ES.h"
#include "Core/IOS/FS/FileSystem.h"
#include "Core/IOS/IOS.h"
#include "Core/IOS/Uids.h"
@@ -30,6 +31,16 @@ namespace FS = IOS::HLE::FS;

static std::string s_temp_wii_root;

static void CopySave(FS::FileSystem* source, FS::FileSystem* dest, const u64 title_id)
{
dest->CreateDirectory(IOS::PID_KERNEL, IOS::PID_KERNEL, Common::GetTitleDataPath(title_id), 0,
{IOS::HLE::FS::Mode::ReadWrite, IOS::HLE::FS::Mode::ReadWrite,
IOS::HLE::FS::Mode::ReadWrite});
const auto source_save = WiiSave::MakeNandStorage(source, title_id);
const auto dest_save = WiiSave::MakeNandStorage(dest, title_id);
WiiSave::Copy(source_save.get(), dest_save.get());
}

static void InitializeDeterministicWiiSaves(FS::FileSystem* session_fs)
{
const u64 title_id = SConfig::GetInstance().GetTitleID();
@@ -53,10 +64,28 @@ static void InitializeDeterministicWiiSaves(FS::FileSystem* session_fs)
{
// Copy the current user's save to the Blank NAND
auto* sync_fs = NetPlay::GetWiiSyncFS();
const auto user_save =
WiiSave::MakeNandStorage(sync_fs ? sync_fs : configured_fs.get(), title_id);
const auto session_save = WiiSave::MakeNandStorage(session_fs, title_id);
WiiSave::Copy(user_save.get(), session_save.get());
auto& sync_titles = NetPlay::GetWiiSyncTitles();
if (sync_fs)
{
for (const u64 title : sync_titles)
{
CopySave(sync_fs, session_fs, title);
}
}
else
{
if (NetPlay::IsSyncingAllWiiSaves())
{
for (const u64 title : sync_titles)
{
CopySave(configured_fs.get(), session_fs, title);
}
}
else
{
CopySave(configured_fs.get(), session_fs, title_id);
}
}
}
}

@@ -93,6 +93,7 @@ add_executable(dolphin-emu
GameList/ListProxyModel.cpp
GCMemcardManager.cpp
QtUtils/BlockUserInputFilter.cpp
NetPlay/ChunkedProgressDialog.cpp
NetPlay/GameListDialog.cpp
NetPlay/MD5Dialog.cpp
NetPlay/NetPlayDialog.cpp
@@ -141,6 +141,7 @@
<QtMoc Include="Settings\WiiPane.h" />
<QtMoc Include="MainWindow.h" />
<QtMoc Include="MenuBar.h" />
<QtMoc Include="NetPlay\ChunkedProgressDialog.h" />
<QtMoc Include="NetPlay\GameListDialog.h" />
<QtMoc Include="NetPlay\MD5Dialog.h" />
<QtMoc Include="NetPlay\NetPlayDialog.h" />
@@ -174,6 +175,7 @@
<ClCompile Include="$(QtMocOutPrefix)CheatCodeEditor.cpp" />
<ClCompile Include="$(QtMocOutPrefix)CheatWarningWidget.cpp" />
<ClCompile Include="$(QtMocOutPrefix)CheatsManager.cpp" />
<ClCompile Include="$(QtMocOutPrefix)ChunkedProgressDialog.cpp" />
<ClCompile Include="$(QtMocOutPrefix)CodeViewWidget.cpp" />
<ClCompile Include="$(QtMocOutPrefix)CodeWidget.cpp" />
<ClCompile Include="$(QtMocOutPrefix)ControllersWindow.cpp" />
@@ -350,6 +352,7 @@
<ClCompile Include="Main.cpp" />
<ClCompile Include="MainWindow.cpp" />
<ClCompile Include="MenuBar.cpp" />
<ClCompile Include="NetPlay\ChunkedProgressDialog.cpp" />
<ClCompile Include="NetPlay\GameListDialog.cpp" />
<ClCompile Include="NetPlay\MD5Dialog.cpp" />
<ClCompile Include="NetPlay\NetPlayDialog.cpp" />
@@ -0,0 +1,123 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include "DolphinQt/NetPlay/ChunkedProgressDialog.h"

#include <algorithm>
#include <cmath>
#include <functional>

#include <QDialogButtonBox>
#include <QGroupBox>
#include <QLabel>
#include <QProgressBar>
#include <QPushButton>
#include <QVBoxLayout>

#include "Common/StringUtil.h"

#include "Core/NetPlayClient.h"
#include "Core/NetPlayServer.h"

#include "DolphinQt/Settings.h"

static QString GetPlayerNameFromPID(int pid)
{
QString player_name = QObject::tr("Invalid Player ID");
auto client = Settings::Instance().GetNetPlayClient();
if (!client)
return player_name;

for (const auto* player : client->GetPlayers())
{
if (player->pid == pid)
{
player_name = QString::fromStdString(player->name);
break;
}
}
return player_name;
}

ChunkedProgressDialog::ChunkedProgressDialog(QWidget* parent) : QDialog(parent)
{
CreateWidgets();
ConnectWidgets();
setWindowTitle(tr("Data Transfer"));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
}

void ChunkedProgressDialog::CreateWidgets()
{
m_main_layout = new QVBoxLayout;
m_progress_box = new QGroupBox;
m_progress_layout = new QVBoxLayout;

m_progress_box->setLayout(m_progress_layout);

m_main_layout->addWidget(m_progress_box);
setLayout(m_main_layout);
}

void ChunkedProgressDialog::ConnectWidgets()
{
}

void ChunkedProgressDialog::show(const QString& title, const u64 data_size,
const std::vector<int>& players)
{
m_progress_box->setTitle(title);
m_data_size = data_size;

for (auto& pair : m_progress_bars)
{
m_progress_layout->removeWidget(pair.second);
pair.second->deleteLater();
}

for (auto& pair : m_status_labels)
{
m_progress_layout->removeWidget(pair.second);
pair.second->deleteLater();
}

m_progress_bars.clear();
m_status_labels.clear();

auto client = Settings::Instance().GetNetPlayClient();
if (!client)
return;

for (const auto* player : client->GetPlayers())
{
if (std::find(players.begin(), players.end(), player->pid) == players.end())
continue;

m_progress_bars[player->pid] = new QProgressBar;
m_status_labels[player->pid] = new QLabel;

m_progress_layout->addWidget(m_progress_bars[player->pid]);
m_progress_layout->addWidget(m_status_labels[player->pid]);
}

QDialog::show();
}

void ChunkedProgressDialog::SetProgress(const int pid, const u64 progress)
{
QString player_name = GetPlayerNameFromPID(pid);

if (!m_status_labels.count(pid))
return;

const float acquired = progress / 1024.0f / 1024.0f;
const float total = m_data_size / 1024.0f / 1024.0f;
const int prog = std::lround((static_cast<float>(progress) / m_data_size) * 100.0f);

m_status_labels[pid]->setText(tr("%1[%2]: %3/%4 MiB")
.arg(player_name, QString::number(pid),
QString::fromStdString(StringFromFormat("%.2f", acquired)),
QString::fromStdString(StringFromFormat("%.2f", total))));
m_progress_bars[pid]->setValue(prog);
}
@@ -0,0 +1,41 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#pragma once

#include <map>
#include <string>
#include <vector>

#include <QDialog>

#include "Common/CommonTypes.h"

class QGroupBox;
class QLabel;
class QProgressBar;
class QVBoxLayout;
class QWidget;

class ChunkedProgressDialog : public QDialog
{
Q_OBJECT
public:
explicit ChunkedProgressDialog(QWidget* parent);

void show(const QString& title, u64 data_size, const std::vector<int>& players);
void SetProgress(int pid, u64 progress);

private:
void CreateWidgets();
void ConnectWidgets();

std::map<int, QProgressBar*> m_progress_bars;
std::map<int, QLabel*> m_status_labels;
u64 m_data_size = 0;

QGroupBox* m_progress_box;
QVBoxLayout* m_progress_layout;
QVBoxLayout* m_main_layout;
};
@@ -43,6 +43,7 @@
#include "Core/NetPlayServer.h"

#include "DolphinQt/GameList/GameListModel.h"
#include "DolphinQt/NetPlay/ChunkedProgressDialog.h"
#include "DolphinQt/NetPlay/GameListDialog.h"
#include "DolphinQt/NetPlay/MD5Dialog.h"
#include "DolphinQt/NetPlay/PadMappingDialog.h"
@@ -68,6 +69,7 @@ NetPlayDialog::NetPlayDialog(QWidget* parent)

m_pad_mapping = new PadMappingDialog(this);
m_md5_dialog = new MD5Dialog(this);
m_chunked_progress_dialog = new ChunkedProgressDialog(this);

ResetExternalIP();
CreateChatLayout();
@@ -83,6 +85,7 @@ NetPlayDialog::NetPlayDialog(QWidget* parent)
const bool reduce_polling_rate = Config::Get(Config::NETPLAY_REDUCE_POLLING_RATE);
const bool strict_settings_sync = Config::Get(Config::NETPLAY_STRICT_SETTINGS_SYNC);
const bool host_input_authority = Config::Get(Config::NETPLAY_HOST_INPUT_AUTHORITY);
const bool sync_all_wii_saves = Config::Get(Config::NETPLAY_SYNC_ALL_WII_SAVES);

m_buffer_size_box->setValue(buffer_size);
m_save_sd_box->setChecked(write_save_sdcard_data);
@@ -93,6 +96,7 @@ NetPlayDialog::NetPlayDialog(QWidget* parent)
m_reduce_polling_rate_box->setChecked(reduce_polling_rate);
m_strict_settings_sync_box->setChecked(strict_settings_sync);
m_host_input_authority_box->setChecked(host_input_authority);
m_sync_all_wii_saves_box->setChecked(sync_all_wii_saves);

ConnectWidgets();

@@ -125,6 +129,7 @@ void NetPlayDialog::CreateMainLayout()
m_strict_settings_sync_box = new QCheckBox(tr("Strict Settings Sync"));
m_host_input_authority_box = new QCheckBox(tr("Host Input Authority"));
m_sync_codes_box = new QCheckBox(tr("Sync Codes"));
m_sync_all_wii_saves_box = new QCheckBox(tr("Sync All Wii Saves"));
m_buffer_label = new QLabel(tr("Buffer:"));
m_quit_button = new QPushButton(tr("Quit"));
m_splitter = new QSplitter(Qt::Horizontal);
@@ -198,6 +203,7 @@ void NetPlayDialog::CreateMainLayout()
options_boxes->addWidget(m_save_sd_box);
options_boxes->addWidget(m_load_wii_box);
options_boxes->addWidget(m_sync_save_data_box);
options_boxes->addWidget(m_sync_all_wii_saves_box);
options_boxes->addWidget(m_sync_codes_box);
options_boxes->addWidget(m_record_input_box);
options_boxes->addWidget(m_reduce_polling_rate_box);
@@ -337,6 +343,9 @@ void NetPlayDialog::ConnectWidgets()
}
});

connect(m_sync_save_data_box, &QCheckBox::stateChanged, this,
[this](bool checked) { m_sync_all_wii_saves_box->setEnabled(checked); });

// SaveSettings() - Save Hosting-Dialog Settings

connect(m_buffer_size_box, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
@@ -349,6 +358,7 @@ void NetPlayDialog::ConnectWidgets()
connect(m_reduce_polling_rate_box, &QCheckBox::stateChanged, this, &NetPlayDialog::SaveSettings);
connect(m_strict_settings_sync_box, &QCheckBox::stateChanged, this, &NetPlayDialog::SaveSettings);
connect(m_host_input_authority_box, &QCheckBox::stateChanged, this, &NetPlayDialog::SaveSettings);
connect(m_sync_all_wii_saves_box, &QCheckBox::stateChanged, this, &NetPlayDialog::SaveSettings);
}

void NetPlayDialog::OnChat()
@@ -462,6 +472,8 @@ void NetPlayDialog::OnStart()
settings.m_StrictSettingsSync = m_strict_settings_sync_box->isChecked();
settings.m_SyncSaveData = m_sync_save_data_box->isChecked();
settings.m_SyncCodes = m_sync_codes_box->isChecked();
settings.m_SyncAllWiiSaves =
m_sync_all_wii_saves_box->isChecked() && m_sync_save_data_box->isChecked();

// Unload GameINI to restore things to normal
Config::RemoveLayer(Config::LayerType::GlobalGame);
@@ -515,6 +527,7 @@ void NetPlayDialog::show(std::string nickname, bool use_traversal)
m_reduce_polling_rate_box->setHidden(!is_hosting);
m_strict_settings_sync_box->setHidden(!is_hosting);
m_host_input_authority_box->setHidden(!is_hosting);
m_sync_all_wii_saves_box->setHidden(!is_hosting);
m_kick_button->setHidden(!is_hosting);
m_assign_ports_button->setHidden(!is_hosting);
m_md5_button->setHidden(!is_hosting);
@@ -815,6 +828,7 @@ void NetPlayDialog::SetOptionsEnabled(bool enabled)
m_reduce_polling_rate_box->setEnabled(enabled);
m_strict_settings_sync_box->setEnabled(enabled);
m_host_input_authority_box->setEnabled(enabled);
m_sync_all_wii_saves_box->setEnabled(enabled && m_sync_save_data_box->isChecked());
}

m_record_input_box->setEnabled(enabled);
@@ -1009,6 +1023,7 @@ void NetPlayDialog::SaveSettings()
Config::SetBase(Config::NETPLAY_REDUCE_POLLING_RATE, m_reduce_polling_rate_box->isChecked());
Config::SetBase(Config::NETPLAY_STRICT_SETTINGS_SYNC, m_strict_settings_sync_box->isChecked());
Config::SetBase(Config::NETPLAY_HOST_INPUT_AUTHORITY, m_host_input_authority_box->isChecked());
Config::SetBase(Config::NETPLAY_SYNC_ALL_WII_SAVES, m_sync_all_wii_saves_box->isChecked());
}

void NetPlayDialog::ShowMD5Dialog(const std::string& file_identifier)
@@ -1046,3 +1061,27 @@ void NetPlayDialog::AbortMD5()
m_md5_button->setEnabled(true);
});
}

void NetPlayDialog::ShowChunkedProgressDialog(const std::string& title, const u64 data_size,
const std::vector<int>& players)
{
QueueOnObject(this, [this, title, data_size, players] {
if (m_chunked_progress_dialog->isVisible())
m_chunked_progress_dialog->close();

m_chunked_progress_dialog->show(QString::fromStdString(title), data_size, players);
});
}

void NetPlayDialog::HideChunkedProgressDialog()
{
QueueOnObject(this, [this] { m_chunked_progress_dialog->close(); });
}

void NetPlayDialog::SetChunkedProgress(const int pid, const u64 progress)
{
QueueOnObject(this, [this, pid, progress] {
if (m_chunked_progress_dialog->isVisible())
m_chunked_progress_dialog->SetProgress(pid, progress);
});
}
@@ -10,6 +10,7 @@
#include "Core/NetPlayClient.h"
#include "VideoCommon/OnScreenDisplay.h"

class ChunkedProgressDialog;
class MD5Dialog;
class GameListModel;
class PadMappingDialog;
@@ -67,6 +68,11 @@ class NetPlayDialog : public QDialog, public NetPlay::NetPlayUI
void SetMD5Progress(int pid, int progress) override;
void SetMD5Result(int pid, const std::string& result) override;
void AbortMD5() override;

void ShowChunkedProgressDialog(const std::string& title, u64 data_size,
const std::vector<int>& players) override;
void HideChunkedProgressDialog() override;
void SetChunkedProgress(int pid, u64 progress) override;
signals:
void Boot(const QString& filename);
void Stop();
@@ -117,11 +123,13 @@ class NetPlayDialog : public QDialog, public NetPlay::NetPlayUI
QCheckBox* m_reduce_polling_rate_box;
QCheckBox* m_strict_settings_sync_box;
QCheckBox* m_host_input_authority_box;
QCheckBox* m_sync_all_wii_saves_box;
QPushButton* m_quit_button;
QSplitter* m_splitter;

QGridLayout* m_main_layout;
MD5Dialog* m_md5_dialog;
ChunkedProgressDialog* m_chunked_progress_dialog;
PadMappingDialog* m_pad_mapping;
std::string m_current_game;
Common::Lazy<std::string> m_external_ip_address;
@@ -35,6 +35,8 @@ NetPlaySetupDialog::NetPlaySetupDialog(QWidget* parent)
int connect_port = Config::Get(Config::NETPLAY_CONNECT_PORT);
int host_port = Config::Get(Config::NETPLAY_HOST_PORT);
int host_listen_port = Config::Get(Config::NETPLAY_LISTEN_PORT);
bool enable_chunked_upload_limit = Config::Get(Config::NETPLAY_ENABLE_CHUNKED_UPLOAD_LIMIT);
u32 chunked_upload_limit = Config::Get(Config::NETPLAY_CHUNKED_UPLOAD_LIMIT);
#ifdef USE_UPNP
bool use_upnp = Config::Get(Config::NETPLAY_USE_UPNP);

@@ -50,6 +52,10 @@ NetPlaySetupDialog::NetPlaySetupDialog(QWidget* parent)
m_host_force_port_box->setValue(host_listen_port);
m_host_force_port_box->setEnabled(false);

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);

OnConnectionTypeChanged(m_connection_type->currentIndex());

ConnectWidgets();
@@ -101,6 +107,8 @@ void NetPlaySetupDialog::CreateMainLayout()
m_host_port_box = new QSpinBox;
m_host_force_port_check = new QCheckBox(tr("Force Listen Port:"));
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;

#ifdef USE_UPNP
m_host_upnp = new QCheckBox(tr("Forward port (UPnP)"));
@@ -110,6 +118,12 @@ void NetPlaySetupDialog::CreateMainLayout()

m_host_port_box->setMaximum(65535);
m_host_force_port_box->setMaximum(65535);
m_host_chunked_upload_limit_box->setRange(1, 1000000);
m_host_chunked_upload_limit_box->setSingleStep(100);
m_host_chunked_upload_limit_box->setSuffix(QStringLiteral(" kbps"));

m_host_chunked_upload_limit_check->setToolTip(tr(
"This will limit the speed of chunked uploading per client, which is used for save sync."));

host_layout->addWidget(m_host_port_label, 0, 0);
host_layout->addWidget(m_host_port_box, 0, 1);
@@ -119,7 +133,9 @@ void NetPlaySetupDialog::CreateMainLayout()
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_button, 2, 2, Qt::AlignRight);
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_widget->setLayout(host_layout);

@@ -163,7 +179,14 @@ void NetPlaySetupDialog::ConnectWidgets()
connect(m_host_games, &QListWidget::itemDoubleClicked, this, &NetPlaySetupDialog::accept);

connect(m_host_force_port_check, &QCheckBox::toggled,
[this](int value) { m_host_force_port_box->setEnabled(value); });
[this](bool value) { m_host_force_port_box->setEnabled(value); });
connect(m_host_chunked_upload_limit_check, &QCheckBox::toggled, this, [this](bool value) {
m_host_chunked_upload_limit_box->setEnabled(value);
SaveSettings();
});
connect(m_host_chunked_upload_limit_box,
static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
&NetPlaySetupDialog::SaveSettings);
#ifdef USE_UPNP
connect(m_host_upnp, &QCheckBox::stateChanged, this, &NetPlaySetupDialog::SaveSettings);
#endif
@@ -191,6 +214,11 @@ void NetPlaySetupDialog::SaveSettings()
if (m_host_force_port_check->isChecked())
Config::SetBaseOrCurrent(Config::NETPLAY_LISTEN_PORT,
static_cast<u16>(m_host_force_port_box->value()));

Config::SetBaseOrCurrent(Config::NETPLAY_ENABLE_CHUNKED_UPLOAD_LIMIT,
m_host_chunked_upload_limit_check->isChecked());
Config::SetBaseOrCurrent(Config::NETPLAY_CHUNKED_UPLOAD_LIMIT,
m_host_chunked_upload_limit_box->value());
}

void NetPlaySetupDialog::OnConnectionTypeChanged(int index)
@@ -63,6 +63,8 @@ class NetPlaySetupDialog : public QDialog
QPushButton* m_host_button;
QCheckBox* m_host_force_port_check;
QSpinBox* m_host_force_port_box;
QCheckBox* m_host_chunked_upload_limit_check;
QSpinBox* m_host_chunked_upload_limit_box;

#ifdef USE_UPNP
QCheckBox* m_host_upnp;