rexglue-skate3/include/rex/system/xnet_transport.h
MiTHRAL 73d8f36af2
Some checks are pending
Lint & Format Check / Format Check (push) Waiting to run
netcode-revival: XNetTransport::ForceLiveFlag - force the game's live-user static
Writes the title's internal 'live user' flag byte (0x83028851, derived from
sub_8263E788's lis -32003 / lbz -30639 = recompiler value -2097348608 ->
0x83030000 - 0x77AF) to 1 so the frontend stops refusing Xbox Live features
before it ever calls a rexglue API. Logs the first force.
2026-08-28 14:32:13 -04:00

178 lines
No EOL
6 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;
// 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 {
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];
};
#pragma pack(pop)
// 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();
// netcode-revival: force the title's internal "live user" static flag
// (Skate 3 build) so its frontend stops refusing "Xbox Live features"
// before it ever calls a rexglue API. Called periodically by the ghost
// client. Fragile: the address is tied to this title's build.
void ForceLiveFlag();
// --- 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();
// 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 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;
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;
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