diff --git a/src/core/netplay.cpp b/src/core/netplay.cpp
index 0977c34b0..a9d45516f 100644
--- a/src/core/netplay.cpp
+++ b/src/core/netplay.cpp
@@ -129,7 +129,7 @@ static void SendTraversalPingRequest();
// GGPO session.
static void CreateGGPOSession();
static void DestroyGGPOSession();
-static bool Start(bool is_hosting, std::string nickname, const std::string& remote_addr, s32 port, s32 ldelay, bool traversal = true);
+static bool Start(bool is_hosting, std::string nickname, const std::string& remote_addr, s32 port, s32 ldelay, bool traversal);
static void CloseSession();
// Host functions.
@@ -372,8 +372,6 @@ bool Netplay::Start(bool is_hosting, std::string nickname, const std::string& re
Log_ErrorPrintf("Failed to create enet host.");
return false;
}
- // temp testing
- s_traversal_host_code = nickname;
s_host_player_id = 0;
s_local_nickname = std::move(nickname);
@@ -2359,6 +2357,11 @@ u32 Netplay::GetMaxPrediction()
return MAX_ROLLBACK_FRAMES;
}
+std::string_view Netplay::GetHostCode()
+{
+ return s_traversal_host_code;
+}
+
void Netplay::SetInputs(Netplay::Input inputs[2])
{
for (u32 i = 0; i < 2; i++)
@@ -2370,7 +2373,7 @@ void Netplay::SetInputs(Netplay::Input inputs[2])
}
}
-bool Netplay::CreateSession(std::string nickname, s32 port, s32 max_players, std::string password, int inputdelay)
+bool Netplay::CreateSession(std::string nickname, s32 port, s32 max_players, std::string password, int inputdelay, bool traversal)
{
s_local_session_password = std::move(password);
@@ -2378,7 +2381,7 @@ bool Netplay::CreateSession(std::string nickname, s32 port, s32 max_players, std
// to have the same data, and we don't want to trash their local memcards. We should therefore load
// the memory cards for this game (based on game/global settings), and copy that to the temp card.
- if (!Netplay::Start(true, std::move(nickname), std::string(), port, inputdelay))
+ if (!Netplay::Start(true, std::move(nickname), std::string(), port, inputdelay, traversal))
{
CloseSession();
return false;
@@ -2393,12 +2396,14 @@ bool Netplay::CreateSession(std::string nickname, s32 port, s32 max_players, std
}
bool Netplay::JoinSession(std::string nickname, const std::string& hostname, s32 port, std::string password,
- bool spectating, int inputdelay)
+ bool spectating, int inputdelay, bool traversal, const std::string& hostcode)
{
s_local_session_password = std::move(password);
s_local_spectating = spectating;
- if (!Netplay::Start(false, std::move(nickname), hostname, port, inputdelay))
+ s_traversal_host_code = hostcode;
+
+ if (!Netplay::Start(false, std::move(nickname), hostname, port, inputdelay, traversal))
{
CloseSession();
return false;
diff --git a/src/core/netplay.h b/src/core/netplay.h
index 674c6372d..af4e1cd89 100644
--- a/src/core/netplay.h
+++ b/src/core/netplay.h
@@ -31,8 +31,9 @@ enum : u8
NUM_ENET_CHANNELS,
};
-bool CreateSession(std::string nickname, s32 port, s32 max_players, std::string password, int inputdelay);
-bool JoinSession(std::string nickname, const std::string& hostname, s32 port, std::string password, bool spectating, int inputdelay);
+bool CreateSession(std::string nickname, s32 port, s32 max_players, std::string password, int inputdelay, bool traversal);
+bool JoinSession(std::string nickname, const std::string& hostname, s32 port, std::string password, bool spectating,
+ int inputdelay, bool traversal, const std::string& hostcode);
bool IsActive();
@@ -49,6 +50,7 @@ void SendChatMessage(const std::string_view& msg);
s32 GetPing();
u32 GetMaxPrediction();
+std::string_view GetHostCode();
/// Updates the throttle period, call when target emulation speed changes.
void UpdateThrottlePeriod();
diff --git a/src/duckstation-qt/createnetplaysessiondialog.ui b/src/duckstation-qt/createnetplaysessiondialog.ui
index 14a3ca781..9aa4b6d98 100644
--- a/src/duckstation-qt/createnetplaysessiondialog.ui
+++ b/src/duckstation-qt/createnetplaysessiondialog.ui
@@ -10,7 +10,7 @@
0
0
496
- 267
+ 302
@@ -53,7 +53,7 @@
-
- Select a nickname and port to open your current game session to other players via netplay. A password may optionally be supplied to restrict who can join.
+ <html><head/><body><p>Select a nickname and port to open your current game session to other players via netplay. A password may optionally be supplied to restrict who can join. The traversal mode option can be enabled to allow other players to join via a host code without needing to portforward.</p></body></html>
true
@@ -159,11 +159,22 @@
-
-
-
- QDialogButtonBox::Cancel|QDialogButtonBox::Ok
-
-
+
+
-
+
+
+ Enable Traversal Mode
+
+
+
+ -
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
diff --git a/src/duckstation-qt/joinnetplaysessiondialog.ui b/src/duckstation-qt/joinnetplaysessiondialog.ui
index 8f50d0657..42a38e579 100644
--- a/src/duckstation-qt/joinnetplaysessiondialog.ui
+++ b/src/duckstation-qt/joinnetplaysessiondialog.ui
@@ -10,7 +10,7 @@
0
0
480
- 254
+ 308
@@ -65,95 +65,183 @@
Qt::Vertical
+
+ QSizePolicy::Expanding
+
20
- 40
+ 10
-
-
-
-
-
-
- Nickname:
-
-
-
- -
-
-
- Netplay Peer
-
-
- 128
-
-
-
- -
-
-
- Port:
-
-
-
- -
-
-
- 1
-
-
- 65535
-
-
- 31200
-
-
-
- -
-
-
- Hostname:
-
-
-
- -
-
-
- localhost
-
-
-
- -
-
-
- Password:
-
-
-
- -
-
-
- 128
-
-
- QLineEdit::Normal
-
-
-
- -
-
-
- Input Delay:
-
-
-
- -
-
-
-
+
+
+
+ 0
+ 190
+
+
+
+ 0
+
+
+
+ Direct Mode
+
+
+ -
+
+
-
+
+
+ Nickname:
+
+
+
+ -
+
+
+ Netplay Peer
+
+
+ 128
+
+
+
+ -
+
+
+ Input Delay:
+
+
+
+ -
+
+
+ -
+
+
+ Hostname:
+
+
+
+ -
+
+
+ localhost
+
+
+
+ -
+
+
+ Port:
+
+
+
+ -
+
+
+ 1
+
+
+ 65535
+
+
+ 31200
+
+
+
+ -
+
+
+ Password:
+
+
+
+ -
+
+
+ 128
+
+
+ QLineEdit::Normal
+
+
+
+
+
+
+
+
+
+ Traversal Mode
+
+
+ -
+
+
-
+
+
+ Nickname:
+
+
+
+ -
+
+
+ Netplay Peer
+
+
+ 128
+
+
+
+ -
+
+
+ -
+
+
+ Password:
+
+
+
+ -
+
+
+ 128
+
+
+ QLineEdit::Normal
+
+
+
+ -
+
+
+ Host Code:
+
+
+
+ -
+
+
+ -
+
+
+ Input Delay:
+
+
+
+
+
+
+
+
-
diff --git a/src/duckstation-qt/netplaydialogs.cpp b/src/duckstation-qt/netplaydialogs.cpp
index 15942cd46..c37c6f978 100644
--- a/src/duckstation-qt/netplaydialogs.cpp
+++ b/src/duckstation-qt/netplaydialogs.cpp
@@ -38,9 +38,10 @@ void CreateNetplaySessionDialog::accept()
const int inputdelay = m_ui.inputDelay->value();
const QString& nickname = m_ui.nickname->text();
const QString& password = m_ui.password->text();
+ const bool traversal = m_ui.traversal->isChecked();
QDialog::accept();
- g_emu_thread->createNetplaySession(nickname.trimmed(), port, players, password, inputdelay);
+ g_emu_thread->createNetplaySession(nickname.trimmed(), port, players, password, inputdelay, traversal);
}
bool CreateNetplaySessionDialog::validate()
@@ -64,9 +65,14 @@ JoinNetplaySessionDialog::JoinNetplaySessionDialog(QWidget* parent)
connect(m_ui.port, &QSpinBox::valueChanged, this, &JoinNetplaySessionDialog::updateState);
connect(m_ui.inputDelay, &QSpinBox::valueChanged, this, &JoinNetplaySessionDialog::updateState);
+ connect(m_ui.inputDelayTraversal, &QSpinBox::valueChanged, this, &JoinNetplaySessionDialog::updateState);
connect(m_ui.nickname, &QLineEdit::textChanged, this, &JoinNetplaySessionDialog::updateState);
+ connect(m_ui.nicknameTraversal, &QLineEdit::textChanged, this, &JoinNetplaySessionDialog::updateState);
connect(m_ui.password, &QLineEdit::textChanged, this, &JoinNetplaySessionDialog::updateState);
+ connect(m_ui.passwordTraversal, &QLineEdit::textChanged, this, &JoinNetplaySessionDialog::updateState);
connect(m_ui.hostname, &QLineEdit::textChanged, this, &JoinNetplaySessionDialog::updateState);
+ connect(m_ui.hostCode, &QLineEdit::textChanged, this, &JoinNetplaySessionDialog::updateState);
+ connect(m_ui.tabConnectMode, &QTabWidget::currentChanged, this, &JoinNetplaySessionDialog::updateState);
connect(m_ui.buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, this,
&JoinNetplaySessionDialog::accept);
@@ -80,18 +86,22 @@ JoinNetplaySessionDialog::~JoinNetplaySessionDialog() = default;
void JoinNetplaySessionDialog::accept()
{
- if (!validate())
+ const bool direct_mode = !m_ui.tabDirect->isHidden();
+ const bool valid = direct_mode ? validate() : validateTraversal();
+ if (!valid)
return;
- const int port = m_ui.port->value();
- const int inputdelay = m_ui.inputDelay->value();
- const QString& nickname = m_ui.nickname->text();
+ int port = m_ui.port->value();
+ int inputdelay = direct_mode ? m_ui.inputDelay->value() : m_ui.inputDelayTraversal->value();
+ const QString& nickname = direct_mode ? m_ui.nickname->text() : m_ui.nicknameTraversal->text();
+ const QString& password = direct_mode ? m_ui.password->text() : m_ui.passwordTraversal->text();
const QString& hostname = m_ui.hostname->text();
- const QString& password = m_ui.password->text();
+ const QString& hostcode = m_ui.hostCode->text();
const bool spectating = m_ui.spectating->isChecked();
QDialog::accept();
- g_emu_thread->joinNetplaySession(nickname.trimmed(), hostname.trimmed(), port, password, spectating, inputdelay);
+ g_emu_thread->joinNetplaySession(nickname.trimmed(), hostname.trimmed(), port, password, spectating, inputdelay,
+ !direct_mode, hostcode.trimmed());
}
bool JoinNetplaySessionDialog::validate()
@@ -100,10 +110,20 @@ bool JoinNetplaySessionDialog::validate()
const int inputdelay = m_ui.inputDelay->value();
const QString& nickname = m_ui.nickname->text();
const QString& hostname = m_ui.hostname->text();
- return (!nickname.isEmpty() && !hostname.isEmpty() && port > 0 && port <= 65535 && inputdelay >= 0 && inputdelay <= 10);
+ return (!nickname.isEmpty() && !hostname.isEmpty() && port > 0 && port <= 65535 && inputdelay >= 0 &&
+ inputdelay <= 10);
+}
+
+bool JoinNetplaySessionDialog::validateTraversal()
+{
+ const int inputdelay = m_ui.inputDelayTraversal->value();
+ const QString& nickname = m_ui.nicknameTraversal->text();
+ const QString& hostcode = m_ui.hostCode->text();
+ return (!nickname.isEmpty() && !hostcode.isEmpty() && inputdelay >= 0 && inputdelay <= 10);
}
void JoinNetplaySessionDialog::updateState()
{
- m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(validate());
+ m_ui.buttonBox->button(QDialogButtonBox::Ok)
+ ->setEnabled(!m_ui.tabDirect->isHidden() ? validate() : validateTraversal());
}
diff --git a/src/duckstation-qt/netplaydialogs.h b/src/duckstation-qt/netplaydialogs.h
index 2e5e3f5a7..986a319b7 100644
--- a/src/duckstation-qt/netplaydialogs.h
+++ b/src/duckstation-qt/netplaydialogs.h
@@ -44,6 +44,7 @@ private Q_SLOTS:
private:
bool validate();
+ bool validateTraversal();
private:
Ui::JoinNetplaySessionDialog m_ui;
diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp
index abb5c491d..fbba8fad7 100644
--- a/src/duckstation-qt/qthost.cpp
+++ b/src/duckstation-qt/qthost.cpp
@@ -1071,13 +1071,13 @@ void EmuThread::reloadPostProcessingShaders()
System::ReloadPostProcessingShaders();
}
-void EmuThread::createNetplaySession(const QString& nickname, qint32 port, qint32 max_players, const QString& password, int inputdelay)
+void EmuThread::createNetplaySession(const QString& nickname, qint32 port, qint32 max_players, const QString& password, int inputdelay, bool traversal)
{
if (!isOnThread())
{
QMetaObject::invokeMethod(this, "createNetplaySession", Qt::QueuedConnection, Q_ARG(const QString&, nickname),
Q_ARG(qint32, port), Q_ARG(qint32, max_players), Q_ARG(const QString&, password),
- Q_ARG(int, inputdelay));
+ Q_ARG(int, inputdelay), Q_ARG(bool, traversal));
return;
}
@@ -1085,7 +1085,7 @@ void EmuThread::createNetplaySession(const QString& nickname, qint32 port, qint3
if (!System::IsValid())
return;
- if (!Netplay::CreateSession(nickname.toStdString(), port, max_players, password.toStdString(), inputdelay))
+ if (!Netplay::CreateSession(nickname.toStdString(), port, max_players, password.toStdString(), inputdelay, traversal))
{
errorReported(tr("Netplay Error"), tr("Failed to create netplay session. The log may contain more information."));
return;
@@ -1093,18 +1093,19 @@ void EmuThread::createNetplaySession(const QString& nickname, qint32 port, qint3
}
void EmuThread::joinNetplaySession(const QString& nickname, const QString& hostname, qint32 port,
- const QString& password, bool spectating, int inputdelay)
+ const QString& password, bool spectating, int inputdelay, bool traversal,
+ const QString& hostcode)
{
if (!isOnThread())
{
QMetaObject::invokeMethod(this, "joinNetplaySession", Qt::QueuedConnection, Q_ARG(const QString&, nickname),
Q_ARG(const QString&, hostname), Q_ARG(qint32, port), Q_ARG(const QString&, password),
- Q_ARG(bool, spectating), Q_ARG(int, inputdelay));
+ Q_ARG(bool, spectating), Q_ARG(int, inputdelay), Q_ARG(bool, traversal),
+ Q_ARG(const QString&, hostcode));
return;
}
- if (!Netplay::JoinSession(nickname.toStdString(), hostname.toStdString(), port, password.toStdString(), spectating,
- inputdelay))
+ if (!Netplay::JoinSession(nickname.toStdString(), hostname.toStdString(), port, password.toStdString(), spectating, inputdelay, traversal, hostcode.toStdString()))
{
errorReported(tr("Netplay Error"), tr("Failed to join netplay session. The log may contain more information."));
return;
@@ -2256,11 +2257,11 @@ int main(int argc, char* argv[])
params->override_fast_boot = true;
params->fast_forward_to_first_frame = true;
g_emu_thread->bootSystem(std::move(params));
- g_emu_thread->createNetplaySession(nickname, port, 2, QString(), 0);
+ g_emu_thread->createNetplaySession(nickname, port, 2, QString(), 0, false);
}
else
{
- g_emu_thread->joinNetplaySession(nickname, remote, port, QString(), false, 0);
+ g_emu_thread->joinNetplaySession(nickname, remote, port, QString(), false, 0, false, "");
}
});
}
diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h
index 4f01f83c3..793c261fd 100644
--- a/src/duckstation-qt/qthost.h
+++ b/src/duckstation-qt/qthost.h
@@ -188,9 +188,9 @@ public Q_SLOTS:
void applyCheat(quint32 index);
void reloadPostProcessingShaders();
void createNetplaySession(const QString& nickname, qint32 port, qint32 max_players, const QString& password,
- int inputdelay);
+ int inputdelay, bool traversal);
void joinNetplaySession(const QString& nickname, const QString& hostname, qint32 port, const QString& password,
- bool spectating, int inputdelay);
+ bool spectating, int inputdelay, bool traversal, const QString& hostcode);
void clearInputBindStateFromSource(InputBindingKey key);
private Q_SLOTS:
diff --git a/src/frontend-common/imgui_netplay.cpp b/src/frontend-common/imgui_netplay.cpp
index f5881db04..3502e8845 100644
--- a/src/frontend-common/imgui_netplay.cpp
+++ b/src/frontend-common/imgui_netplay.cpp
@@ -141,7 +141,12 @@ void ImGuiManager::DrawNetplayStats()
// We'll probably want to draw a graph too..
LargeString text;
- text.AppendFmtString("Ping: {}", Netplay::GetPing());
+ text.AppendFmtString("Ping: {}\n", Netplay::GetPing());
+
+ // temporary show the hostcode here for now
+ auto hostcode = Netplay::GetHostCode();
+ if (!hostcode.empty())
+ text.AppendFmtString("Host Code: {}", hostcode);
const float scale = ImGuiManager::GetGlobalScale();
const float shadow_offset = 1.0f * scale;