diff --git a/include/rex/system/xnet_transport.h b/include/rex/system/xnet_transport.h index f927181..cf089bd 100644 --- a/include/rex/system/xnet_transport.h +++ b/include/rex/system/xnet_transport.h @@ -32,6 +32,10 @@ namespace system { class XSocket; +// The wire structs below must match the relay's byte-for-byte (the main repo +// packs them with #pragma pack(1); leaving padding here would inflate the +// sizes and silently corrupt every datagram). +#pragma pack(push, 1) // Header of every relay datagram ("GHST" magic, little-endian type + size). // Must match kGhostMagic / PacketHeader in skate3_native_ghost_proto.h. struct XNetRelayHeader { @@ -69,6 +73,7 @@ struct XNetRelaySessionReply { uint8_t count; XNetRelaySessionEntry list[32]; }; +#pragma pack(pop) // One discovered session (a member of our room). struct XNetSession { @@ -143,6 +148,9 @@ class XNetTransport { // header stays platform-independent. uint8_t relay_addr_[16] = {}; bool relay_resolved_ = false; + uint8_t join_pkt_[7 + sizeof(XNetRelayJoin)] = {}; + uint16_t join_pkt_size_ = 0; + double last_join_ = 0.0; // monotonic seconds (heartbeat to beat the timeout) std::thread thread_; bool running_ = false; diff --git a/src/system/xnet_transport.cpp b/src/system/xnet_transport.cpp index 000d596..04727da 100644 --- a/src/system/xnet_transport.cpp +++ b/src/system/xnet_transport.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -29,6 +30,15 @@ namespace rex { namespace system { +// Pin the packed wire sizes to the values the relay expects (the game-side +// proto packs them identically). Catches accidental un-packing or member +// edits before they corrupt the wire. +static_assert(sizeof(XNetRelayHeader) == 7, "relay header must be 7 bytes"); +static_assert(sizeof(XNetRelayJoin) == 31, "relay join must be 31 bytes"); +static_assert(sizeof(XNetRelayGame) == 1410, "relay game payload must be 1410 bytes"); +static_assert(sizeof(XNetRelaySessionEntry) == 25, "session entry must be 25 bytes"); +static_assert(sizeof(XNetRelaySessionReply) == 801, "session reply must be 801 bytes"); + namespace { // Virtual address range: 10.80.0.0/16 (host order prefix 0x0A500000). @@ -156,14 +166,19 @@ void XNetTransport::Reconnect() { } sockaddr_in relay_addr = {}; if (!Resolve(host_, port_, &relay_addr)) { + REXLOG_WARN("xnet transport: cannot resolve relay '{}:{}'", host_, port_); return; } std::memcpy(relay_addr_, &relay_addr, sizeof(relay_addr)); relay_resolved_ = true; sock_ = SocketCreate(); if (sock_ == -1) { + REXLOG_WARN("xnet transport: cannot open relay socket"); return; } + REXLOG_INFO("xnet transport: connecting to relay '{}:{}' room '{}' (vip {}.{}.{}.{})", host_, + port_, room_.c_str(), (LocalVirtualIp() >> 24) & 0xFF, (LocalVirtualIp() >> 16) & 0xFF, + (LocalVirtualIp() >> 8) & 0xFF, LocalVirtualIp() & 0xFF); // Join the room so the relay routes our game traffic among its members. XNetRelayJoin jp = {}; jp.version = kVersion; @@ -184,6 +199,12 @@ void XNetTransport::Reconnect() { std::memcpy(pkt + 7, &jp, sizeof(jp)); SocketSendTo(sock_, &relay_addr, pkt, sizeof(pkt)); + // Keep the JOIN around for the heartbeat (the relay drops silent clients + // after ~8s). + std::memcpy(join_pkt_, pkt, sizeof(pkt)); + join_pkt_size_ = uint16_t(sizeof(pkt)); + last_join_ = 0.0; + running_ = true; thread_ = std::thread(&XNetTransport::Run, this); } @@ -271,6 +292,14 @@ void XNetTransport::Run() { if (!running_) { return; } + // Heartbeat: re-JOIN every ~4s so the relay never times us out. + const double now = + double(std::chrono::steady_clock::now().time_since_epoch().count()) / 1e9; + if (join_pkt_size_ != 0 && now - last_join_ >= 4.0) { + SocketSendTo(sock_, reinterpret_cast(relay_addr_), join_pkt_, + join_pkt_size_); + last_join_ = now; + } } uint8_t buf[7 + sizeof(XNetRelayGame)]; sockaddr_in from = {}; @@ -307,6 +336,9 @@ void XNetTransport::HandlePacket(const uint8_t* data, int size) { // session_id != 0 means accepted (0 = refused). active_ = payload[0] != 0; joined_ = true; + REXLOG_INFO("xnet transport: relay {} (vip {}.{}.{}.{})", active_ ? "joined" : "refused", + (LocalVirtualIp() >> 24) & 0xFF, (LocalVirtualIp() >> 16) & 0xFF, + (LocalVirtualIp() >> 8) & 0xFF, LocalVirtualIp() & 0xFF); break; } case kGame: { @@ -350,6 +382,8 @@ void XNetTransport::HandlePacket(const uint8_t* data, int size) { std::lock_guard lock(mu_); sessions_ = std::move(fresh); ++reply_seq_; + REXLOG_INFO("xnet transport: session reply with {} session(s) in room '{}'", count, + room_.c_str()); break; } default: diff --git a/thirdparty/imgui b/thirdparty/imgui index cdda623..0af8f47 160000 --- a/thirdparty/imgui +++ b/thirdparty/imgui @@ -1 +1 @@ -Subproject commit cdda62349d6068e906c2e0ee340d6ec31eedbb9c +Subproject commit 0af8f475d1cf8b98d603702059c0c73deeb81ac2