190 lines
No EOL
6.6 KiB
C++
190 lines
No EOL
6.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();
|
|
|
|
// netcode-revival: diagnostics for the frontend "Xbox Live features" gate
|
|
// (the game checks these before enabling the Party Play / online menu).
|
|
struct GateStatus {
|
|
bool live_flag_mapped = false;
|
|
uint8_t live_flag_value = 0; // the game's "live user index"
|
|
uint32_t signin_state[4] = {}; // XamUserGetSigninState per slot
|
|
bool signin_info_ok[4] = {}; // XamUserGetSigninInfo per slot succeeds
|
|
bool link_active = false; // XNetGetEthernetLinkStatus != 0
|
|
bool title_online = false; // XNetGetTitleXnAddr status & 0x60
|
|
};
|
|
GateStatus GetGateStatus() const;
|
|
|
|
// --- 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
|