netcode-revival: fix SetRelay deadlock + grant multiplayer privileges
Some checks are pending
Lint & Format Check / Format Check (push) Waiting to run

- Deadlock: SetRelay held the transport mutex while join()ing the receive
  thread, which needs the mutex to observe running_ and exit. With the 200ms
  recv timeout the thread woke and hit the lock -> a room/server change froze
  the game (every room keystroke reconnects the transport). StopThread now
  detaches the thread under the lock and joins WITHOUT it.
- XamUserCheckPrivilege denied everything, which is the gate the game's
  'create a party' flow checks (multiplayer sessions); grant requested
  privileges so the P2P session flow opens, and log the mask.
This commit is contained in:
MiTHRAL 2026-08-28 13:52:20 -04:00
parent 3229aa3632
commit 6fcc96de18
3 changed files with 37 additions and 15 deletions

View file

@ -138,7 +138,10 @@ class XNetTransport {
XNetTransport& operator=(const XNetTransport&) = delete; XNetTransport& operator=(const XNetTransport&) = delete;
void Reconnect(); void Reconnect();
void StopLocked(); // Stop the receive thread and join it WITHOUT holding mu_ (the Run thread
// needs mu_ to observe running_ and exit; joining under the lock deadlocks
// once recvfrom times out and the thread tries to lock mu_).
void StopThread();
void Run(); void Run();
void HandlePacket(const uint8_t* data, int size); void HandlePacket(const uint8_t* data, int size);

View file

@ -430,8 +430,13 @@ u32 XamUserCheckPrivilege_entry(u32 user_index, u32 mask, mapped_u32 out_value)
} }
} }
// If we deny everything, games should hopefully not try to do stuff. // netcode-revival: the game gates its party/session flow on
*out_value = 0; // XamUserCheckPrivilege (e.g. MULTIPLAYER_SESSIONS); denying everything
// (the old default) made "create a party" refuse. Grant requested online
// privileges so the P2P session flow opens.
*out_value = 1;
REXLOG_INFO("xam: XamUserCheckPrivilege(user={}, mask=0x{:08X}) -> granted",
user_index, mask);
return X_ERROR_SUCCESS; return X_ERROR_SUCCESS;
} }

View file

@ -118,8 +118,16 @@ XNetTransport& XNetTransport::Instance() {
} }
XNetTransport::~XNetTransport() { XNetTransport::~XNetTransport() {
StopThread(); // join outside the lock (see StopThread)
std::lock_guard<std::mutex> lock(mu_); std::lock_guard<std::mutex> lock(mu_);
StopLocked(); if (sock_ != -1) {
SocketClose(sock_);
sock_ = -1;
}
active_ = false;
joined_ = false;
sessions_.clear();
sockets_.clear();
} }
uint32_t XNetTransport::LocalVirtualIp() { uint32_t XNetTransport::LocalVirtualIp() {
@ -156,17 +164,29 @@ void XNetTransport::SetRelay(const char* host, uint16_t port, const char* room)
host_ = h; host_ = h;
port_ = port; port_ = port;
room_ = r; room_ = r;
StopLocked();
} }
StopThread(); // join the old thread OUTSIDE the lock
Reconnect(); Reconnect();
} }
void XNetTransport::StopLocked() { void XNetTransport::StopThread() {
if (thread_.joinable()) { std::thread old;
{
std::lock_guard<std::mutex> lock(mu_);
running_ = false; running_ = false;
thread_.join(); old = std::move(thread_);
thread_ = {}; thread_ = {};
} }
if (old.joinable()) {
old.join();
}
}
void XNetTransport::Reconnect() {
std::lock_guard<std::mutex> lock(mu_);
if (host_.empty() || port_ == 0) {
return;
}
if (sock_ != -1) { if (sock_ != -1) {
SocketClose(sock_); SocketClose(sock_);
sock_ = -1; sock_ = -1;
@ -174,13 +194,7 @@ void XNetTransport::StopLocked() {
active_ = false; active_ = false;
joined_ = false; joined_ = false;
sessions_.clear(); sessions_.clear();
} relay_resolved_ = false;
void XNetTransport::Reconnect() {
std::lock_guard<std::mutex> lock(mu_);
if (host_.empty() || port_ == 0) {
return;
}
sockaddr_in relay_addr = {}; sockaddr_in relay_addr = {};
if (!Resolve(host_, port_, &relay_addr)) { if (!Resolve(host_, port_, &relay_addr)) {
REXLOG_WARN("xnet transport: cannot resolve relay '{}:{}'", host_, port_); REXLOG_WARN("xnet transport: cannot resolve relay '{}:{}'", host_, port_);