aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcirakg <cirakg@cirakg.xyz>2026-11-02 16:23:40 +0100
committercirakg <cirakg@cirakg.xyz>2026-11-02 16:34:08 +0100
commitff234849d6a41d2c2d29cf09408ac66e202d68e7 (patch)
treea090ae9e2532741bb67ada2d518c8b4b8bf6bda4
parentcaed5a34484942d3ecf9b9a84c83f31d1a6e3020 (diff)
ldn: wait for the host's reply when joining a network
Connect slept for a second while holding packet_mutex, so the host's SyncNetwork reply could not be handled until Connect had already returned success in StationOpened. Players could see each other's group and enter it, but never actually met. Wait on a condition variable instead, accept the reply only from the selected host and session, and fail after five seconds. Check packet sizes and senders, stop duplicate joins from taking another station, and seed session IDs so separate networks don't share one. The SDL frontend never called Network::Init, so its multiplayer option could not join a room. Initialize and shut down networking around the core. Assisted-by: Sol (implementation) Assisted-by: Claude Opus 5.5 (code review)
-rw-r--r--src/core/hle/service/ldn/lan_discovery.cpp85
-rw-r--r--src/core/hle/service/ldn/lan_discovery.h8
-rw-r--r--src/yuzu_cmd/yuzu.cpp11
3 files changed, 89 insertions, 15 deletions
diff --git a/src/core/hle/service/ldn/lan_discovery.cpp b/src/core/hle/service/ldn/lan_discovery.cpp
index 90adcdcee4..d7debdd67b 100644
--- a/src/core/hle/service/ldn/lan_discovery.cpp
+++ b/src/core/hle/service/ldn/lan_discovery.cpp
@@ -5,6 +5,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/service/ldn/lan_discovery.h"
+#include "common/random.h"
#include "core/internal_network/network.h"
#include "core/internal_network/network_interface.h"
@@ -70,6 +71,7 @@ void LANDiscovery::InitNodeStateChange() {
}
State LANDiscovery::GetState() const {
+ std::scoped_lock lock{packet_mutex};
return state;
}
@@ -78,6 +80,7 @@ void LANDiscovery::SetState(State new_state) {
}
Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network) const {
+ std::scoped_lock lock{packet_mutex};
if (state == State::AccessPointCreated || state == State::StationConnected) {
std::memcpy(&out_network, &network_info, sizeof(network_info));
return ResultSuccess;
@@ -88,6 +91,7 @@ Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network) const {
Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network,
std::span<NodeLatestUpdate> out_updates) {
+ std::scoped_lock lock{packet_mutex};
if (out_updates.size() > NodeCountMax) {
return ResultInvalidBufferCount;
}
@@ -105,6 +109,7 @@ Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network,
}
DisconnectReason LANDiscovery::GetDisconnectReason() const {
+ std::scoped_lock lock{packet_mutex};
return disconnect_reason;
}
@@ -194,7 +199,7 @@ Result LANDiscovery::CloseAccessPoint() {
}
if (state == State::AccessPointCreated) {
- DestroyNetwork();
+ DestroyNetworkImpl();
}
ResetStations();
@@ -222,8 +227,8 @@ Result LANDiscovery::CloseStation() {
return ResultBadState;
}
- if (state == State::StationConnected) {
- Disconnect();
+ if (state == State::StationConnected || host_ip) {
+ DisconnectImpl();
}
ResetStations();
@@ -251,7 +256,8 @@ Result LANDiscovery::CreateNetwork(const SecurityConfig& security_config,
network_info.common.channel = network_config.channel;
}
- std::independent_bits_engine<std::mt19937, 64, u64> bits_engine;
+ std::independent_bits_engine<std::mt19937, 64, u64> bits_engine{
+ Common::Random::GetMT19937()};
network_info.network_id.session_id.high = bits_engine();
network_info.network_id.session_id.low = bits_engine();
network_info.network_id.intent_id = network_config.intent_id;
@@ -272,6 +278,11 @@ Result LANDiscovery::CreateNetwork(const SecurityConfig& security_config,
}
Result LANDiscovery::DestroyNetwork() {
+ std::scoped_lock lock{packet_mutex};
+ return DestroyNetworkImpl();
+}
+
+Result LANDiscovery::DestroyNetworkImpl() {
for (auto local_ip : connected_clients) {
SendPacket(Network::LDNPacketType::DestroyNetwork, local_ip);
}
@@ -286,7 +297,10 @@ Result LANDiscovery::DestroyNetwork() {
Result LANDiscovery::Connect(const NetworkInfo& network_info_, const UserConfig& user_config,
u16 local_communication_version) {
- std::scoped_lock lock{packet_mutex};
+ std::unique_lock lock{packet_mutex};
+ if (state != State::StationOpened || host_ip) {
+ return ResultBadState;
+ }
if (network_info_.ldn.node_count == 0) {
return ResultInvalidNodeCount;
}
@@ -299,21 +313,39 @@ Result LANDiscovery::Connect(const NetworkInfo& network_info_, const UserConfig&
Ipv4Address node_host = network_info_.ldn.nodes[0].ipv4_address;
std::reverse(std::begin(node_host), std::end(node_host)); // htonl
host_ip = node_host;
- SendPacket(Network::LDNPacketType::Connect, node_info, *host_ip);
-
+ network_info = network_info_;
InitNodeStateChange();
+ SendPacket(Network::LDNPacketType::Connect, node_info, *host_ip);
- std::this_thread::sleep_for(std::chrono::seconds(1));
+ // The room callback needs packet_mutex to install the host's connection reply.
+ const bool replied = connection_event.wait_for(lock, connection_timeout, [this] {
+ return state != State::StationOpened || !host_ip;
+ });
+ if (!replied) {
+ SendPacket(Network::LDNPacketType::Disconnect, node_info, *host_ip);
+ host_ip.reset();
+ return ResultAuthenticationTimeout;
+ }
+ if (state != State::StationConnected) {
+ return ResultConnectionFailed;
+ }
return ResultSuccess;
}
Result LANDiscovery::Disconnect() {
+ std::scoped_lock lock{packet_mutex};
+ return DisconnectImpl();
+}
+
+Result LANDiscovery::DisconnectImpl() {
if (host_ip) {
SendPacket(Network::LDNPacketType::Disconnect, node_info, *host_ip);
}
+ host_ip.reset();
SetState(State::StationOpened);
+ connection_event.notify_all();
lan_event();
return ResultSuccess;
@@ -346,10 +378,10 @@ Result LANDiscovery::Finalize() {
if (inited) {
if (state == State::AccessPointCreated) {
- DestroyNetwork();
+ DestroyNetworkImpl();
}
- if (state == State::StationConnected) {
- Disconnect();
+ if (state == State::StationConnected || host_ip) {
+ DisconnectImpl();
}
ResetStations();
@@ -392,6 +424,7 @@ void LANDiscovery::OnSyncNetwork(const NetworkInfo& info) {
SetState(State::StationConnected);
}
OnNetworkInfoChanged();
+ connection_event.notify_all();
}
void LANDiscovery::OnDisconnectFromHost() {
@@ -401,6 +434,7 @@ void LANDiscovery::OnDisconnectFromHost() {
SetState(State::StationOpened);
lan_event();
}
+ connection_event.notify_all();
}
void LANDiscovery::OnNetworkInfoChanged() {
@@ -489,6 +523,9 @@ void LANDiscovery::ReceivePacket(const Network::LDNPacket& packet) {
break;
}
case Network::LDNPacketType::ScanResp: {
+ if (packet.data.size() != sizeof(NetworkInfo)) {
+ break;
+ }
LOG_INFO(Frontend, "ScanResp packet received!");
NetworkInfo info{};
@@ -498,6 +535,17 @@ void LANDiscovery::ReceivePacket(const Network::LDNPacket& packet) {
break;
}
case Network::LDNPacketType::Connect: {
+ if (state != State::AccessPointCreated || packet.data.size() != sizeof(NodeInfo)) {
+ break;
+ }
+ if (std::find(connected_clients.begin(), connected_clients.end(), packet.local_ip) !=
+ connected_clients.end()) {
+ SendPacket(Network::LDNPacketType::SyncNetwork, network_info, packet.local_ip);
+ break;
+ }
+ if (network_info.ldn.node_count >= network_info.ldn.node_count_max) {
+ break;
+ }
LOG_INFO(Frontend, "Connect packet received!");
NodeInfo info{};
@@ -518,6 +566,9 @@ void LANDiscovery::ReceivePacket(const Network::LDNPacket& packet) {
break;
}
case Network::LDNPacketType::Disconnect: {
+ if (state != State::AccessPointCreated || packet.data.size() != sizeof(NodeInfo)) {
+ break;
+ }
LOG_INFO(Frontend, "Disconnect packet received!");
connected_clients.erase(
@@ -538,17 +589,23 @@ void LANDiscovery::ReceivePacket(const Network::LDNPacket& packet) {
break;
}
case Network::LDNPacketType::DestroyNetwork: {
+ if (!host_ip || packet.local_ip != *host_ip) {
+ break;
+ }
ResetStations();
OnDisconnectFromHost();
break;
}
case Network::LDNPacketType::SyncNetwork: {
- if (state == State::StationOpened || state == State::StationConnected) {
+ if (host_ip && packet.local_ip == *host_ip &&
+ packet.data.size() == sizeof(NetworkInfo) &&
+ (state == State::StationOpened || state == State::StationConnected)) {
LOG_INFO(Frontend, "SyncNetwork packet received!");
NetworkInfo info{};
std::memcpy(&info, packet.data.data(), sizeof(NetworkInfo));
-
- OnSyncNetwork(info);
+ if (info.network_id.session_id == network_info.network_id.session_id) {
+ OnSyncNetwork(info);
+ }
} else {
LOG_INFO(Frontend, "SyncNetwork packet received but in wrong State!");
}
diff --git a/src/core/hle/service/ldn/lan_discovery.h b/src/core/hle/service/ldn/lan_discovery.h
index f1f3c4bece..e641b11409 100644
--- a/src/core/hle/service/ldn/lan_discovery.h
+++ b/src/core/hle/service/ldn/lan_discovery.h
@@ -10,6 +10,8 @@
#pragma once
#include <array>
+#include <chrono>
+#include <condition_variable>
#include <cstring>
#include <functional>
#include <memory>
@@ -92,6 +94,8 @@ protected:
void InitNodeStateChange();
void ResetStations();
+ Result DestroyNetworkImpl();
+ Result DisconnectImpl();
void UpdateNodes();
void OnSyncNetwork(const NetworkInfo& info);
@@ -118,7 +122,9 @@ protected:
static constexpr Ssid fake_ssid{"YuzuFakeSsidForLdn"};
bool inited{};
- std::mutex packet_mutex;
+ mutable std::mutex packet_mutex;
+ std::condition_variable connection_event;
+ std::chrono::nanoseconds connection_timeout{std::chrono::seconds(5)};
std::array<LanStation, StationCountMax> stations;
std::array<NodeLatestUpdate, NodeCountMax> node_changes{};
std::array<u8, NodeCountMax> node_last_states{};
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index bc4002d8af..6342c5d353 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -249,6 +249,7 @@ struct SdlState {
// Must outlive emu_window, which keeps a pointer to it.
InputCommon::InputSubsystem input_subsystem{};
std::unique_ptr<EmuWindow_SDL3> emu_window;
+ bool network_initialized{};
};
static std::atomic_bool pack_nup_cancel{false};
@@ -353,6 +354,12 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
return SDL_APP_FAILURE;
}
+ state->network_initialized = Network::Init();
+ if (!state->network_initialized) {
+ LOG_CRITICAL(Network, "Failed to initialize room networking");
+ return SDL_APP_FAILURE;
+ }
+
state->system.Initialize();
@@ -555,7 +562,11 @@ extern "C" void SDL_AppQuit(void *appstate, SDL_AppResult result) {
void(state->system.Pause());
state->system.ShutdownMainProcess();
}
+ const bool network_initialized = state->network_initialized;
delete state;
+ if (network_initialized) {
+ Network::Shutdown();
+ }
}
#define VMA_IMPLEMENTATION