Some checks are pending
Lint & Format Check / Format Check (push) Waiting to run
Route the game's own RakNet UDP through the ghost relay so real internet
peers can play, with the server as a pure relay/NAT stabilizer:
- XNetTransport: a rexglue relay client (joins the same relay + room as the
ghost client), a receive thread, and:
- SendGame(): wraps a datagram addressed to a virtual 10.80.0.x peer in a
relay Game payload and sends it OUTBOUND to the relay (no inbound
reachability / port forwarding needed).
- QuerySessions()/Sessions(): discovery - the room members that
XNetQosLookup reports to the game's session search.
- Receive injection: relayed Game payloads are pushed into the game's
registered sockets via XSocket::QueuePacket.
- XSocket: SendTo() to a virtual address routes through the transport;
RecvFrom() drains queued relayed packets first; sockets register with the
transport on Initialize.
- XNetQosLookup: queries the relay and returns the room's members as
discovered sessions (with their virtual addresses), signaling completion.
- XNetGetTitleXnAddr identity now shares one source of truth with the
transport (same 10.80.0.x).
Wire structs are minimal copies of the game's ghost protocol (kept
dependency-free); the relay's version check refuses mismatches.
161 lines
No EOL
5.1 KiB
C++
161 lines
No EOL
5.1 KiB
C++
/**
|
|
* netcode-revival: relay transport for the game's own netcode.
|
|
*
|
|
* The game's RakNet sockets talk to peers at "virtual" addresses
|
|
* (10.80.0.x, assigned per machine by the XNet layer). Real internet peers
|
|
* are not reachable at those addresses, so this transport routes their UDP
|
|
* through the ghost relay: a datagram to a virtual address is wrapped in a
|
|
* relay Game payload and sent OUTBOUND to the relay, which forwards it to
|
|
* the destination member's real socket (the server is a pure relay / NAT
|
|
* stabilizer). Inbound Game payloads are injected into the game's sockets
|
|
* via XSocket::QueuePacket, and XNetQosLookup discovers sessions by asking
|
|
* the relay who else is in the room.
|
|
*
|
|
* The wire structs below are minimal copies of the main repo's
|
|
* skate3_native_ghost_proto.h (deliberately kept dependency-free and
|
|
* one-way: the SDK must not include game-side headers). If they ever
|
|
* diverge, the relay's version check refuses the connection rather than
|
|
* corrupting traffic.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
namespace rex {
|
|
namespace system {
|
|
|
|
class XSocket;
|
|
|
|
// Header of every relay datagram ("GHST" magic, little-endian type + size).
|
|
// Must match kGhostMagic / PacketHeader in skate3_native_ghost_proto.h.
|
|
struct XNetRelayHeader {
|
|
uint32_t magic; // "GHST" little-endian
|
|
uint8_t type;
|
|
uint16_t payload_size;
|
|
};
|
|
|
|
// kTypeJoin payload. Must match JoinPayload.
|
|
struct XNetRelayJoin {
|
|
uint8_t version;
|
|
uint8_t room_len;
|
|
char room[8];
|
|
uint8_t name_len;
|
|
char name[16];
|
|
uint32_t virtual_ip; // host-order 10.80.0.x
|
|
};
|
|
|
|
// kTypeGame payload. Must match GamePayload.
|
|
struct XNetRelayGame {
|
|
uint32_t dst_virtual_ip; // recipient (host order) or 0 = all room members
|
|
uint32_t src_virtual_ip; // filled by the relay on forward (host order)
|
|
uint16_t data_len;
|
|
uint8_t data[1400];
|
|
};
|
|
|
|
// kTypeSessionReply payload. Must match SessionReplyPayload.
|
|
struct XNetRelaySessionEntry {
|
|
uint32_t session_id;
|
|
uint32_t virtual_ip; // host order
|
|
uint8_t name_len;
|
|
char name[16];
|
|
};
|
|
struct XNetRelaySessionReply {
|
|
uint8_t count;
|
|
XNetRelaySessionEntry list[32];
|
|
};
|
|
|
|
// One discovered session (a member of our room).
|
|
struct XNetSession {
|
|
uint32_t session_id;
|
|
uint32_t virtual_ip; // host order
|
|
char name[16];
|
|
uint8_t name_len;
|
|
};
|
|
|
|
class XNetTransport {
|
|
public:
|
|
static constexpr uint8_t kVersion = 5;
|
|
static constexpr uint32_t kMagic = 0x54484753u; // "GHST" LE
|
|
enum Type : uint8_t {
|
|
kJoin = 1,
|
|
kHello = 2,
|
|
kGame = 10,
|
|
kSessionQuery = 11,
|
|
kSessionReply = 12,
|
|
};
|
|
|
|
static XNetTransport& Instance();
|
|
|
|
// Configure the relay (host[:port], port) and the room to join. Idempotent;
|
|
// a changed relay/room reconnects. Called by the game-side ghost client
|
|
// when it (re)joins a room, so the game's netcode follows the same room.
|
|
void SetRelay(const char* host, uint16_t port, const char* room);
|
|
|
|
// True once the transport has successfully joined the relay.
|
|
bool Active() const;
|
|
|
|
// True when ip (host order) is in the virtual 10.80.0.x range.
|
|
static bool IsVirtualIp(uint32_t host_order_ip);
|
|
|
|
// Route one datagram to a virtual destination via the relay. Returns the
|
|
// datagram length on accept, -1 when the transport is not active or the
|
|
// destination is not a virtual address.
|
|
int SendGame(const void* buf, uint32_t len, uint32_t dst_virtual_ip_host_order);
|
|
|
|
// The local machine's virtual IP (host order) - mirrors XNetGetTitleXnAddr.
|
|
static uint32_t LocalVirtualIp();
|
|
|
|
// --- discovery (used by XNetQosLookup) ------------------------------------
|
|
// Send a session query for our room; the reply lands asynchronously and is
|
|
// surfaced by Sessions() once WaitForReply returns.
|
|
void QuerySessions();
|
|
// Block up to timeout_ms for a fresh session reply to arrive.
|
|
void WaitForReply(int timeout_ms);
|
|
std::vector<XNetSession> Sessions() const;
|
|
|
|
// --- receive injection -----------------------------------------------------
|
|
// A UDP socket the game expects inbound netcode datagrams on. Delivered
|
|
// Game payloads are pushed into every registered socket via QueuePacket.
|
|
void RegisterSocket(XSocket* s);
|
|
void UnregisterSocket(XSocket* s);
|
|
|
|
~XNetTransport();
|
|
|
|
private:
|
|
XNetTransport() = default;
|
|
XNetTransport(const XNetTransport&) = delete;
|
|
XNetTransport& operator=(const XNetTransport&) = delete;
|
|
|
|
void Reconnect();
|
|
void StopLocked();
|
|
void Run();
|
|
void HandlePacket(const uint8_t* data, int size);
|
|
|
|
int64_t sock_ = -1; // native UDP socket to the relay (platform handle)
|
|
// Resolved relay endpoint as raw bytes (sockaddr_in is 16 bytes on POSIX
|
|
// and Windows); cast back in the .cpp. Kept byte-form so this public
|
|
// header stays platform-independent.
|
|
uint8_t relay_addr_[16] = {};
|
|
bool relay_resolved_ = false;
|
|
std::thread thread_;
|
|
bool running_ = false;
|
|
|
|
mutable std::mutex mu_;
|
|
std::string host_;
|
|
uint16_t port_ = 0;
|
|
std::string room_;
|
|
bool active_ = false;
|
|
bool joined_ = false;
|
|
uint64_t reply_seq_ = 0; // bumped by every SessionReply
|
|
std::vector<XNetSession> sessions_;
|
|
std::unordered_set<XSocket*> sockets_;
|
|
};
|
|
|
|
} // namespace system
|
|
} // namespace rex
|