netcode-revival: relay transport for the game's netcode (Phase 1b)
Some checks are pending
Lint & Format Check / Format Check (push) Waiting to run
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.
This commit is contained in:
parent
767bd6e4bc
commit
bf3635d7ad
5 changed files with 598 additions and 34 deletions
161
include/rex/system/xnet_transport.h
Normal file
161
include/rex/system/xnet_transport.h
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
#include <rex/system/kernel_state.h>
|
#include <rex/system/kernel_state.h>
|
||||||
#include <rex/system/xevent.h>
|
#include <rex/system/xevent.h>
|
||||||
#include <rex/system/xsocket.h>
|
#include <rex/system/xsocket.h>
|
||||||
|
#include <rex/system/xnet_transport.h>
|
||||||
#include <rex/system/xthread.h>
|
#include <rex/system/xthread.h>
|
||||||
#include <rex/system/xtypes.h>
|
#include <rex/system/xtypes.h>
|
||||||
|
|
||||||
|
|
@ -160,7 +161,8 @@ const XnetIdentity& XnetLocalIdentity() {
|
||||||
seed = seed * 6364136223846793005ULL + 1442695040888963407ULL;
|
seed = seed * 6364136223846793005ULL + 1442695040888963407ULL;
|
||||||
id.ab_enet[i] = uint8_t(seed >> 24);
|
id.ab_enet[i] = uint8_t(seed >> 24);
|
||||||
}
|
}
|
||||||
id.virtual_ip = (10u << 24) | (80u << 16) | (0u << 8) | id.machine_id;
|
// Single source of truth with the relay transport (same 10.80.0.x).
|
||||||
|
id.virtual_ip = rex::system::XNetTransport::LocalVirtualIp();
|
||||||
return id;
|
return id;
|
||||||
}();
|
}();
|
||||||
return s_identity;
|
return s_identity;
|
||||||
|
|
@ -632,9 +634,10 @@ u32 NetDll_XNetQosListen_entry(u32 caller, mapped_void id, mapped_void data, u32
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// netcode-revival: session search. Phase 0 reports zero discovered sessions
|
// netcode-revival: session search. Phase 1: ask the relay who else is in our
|
||||||
// (the relay registry lands in Phase 1) but returns success and signals the
|
// room and hand the discovered sessions to the game, then signal the
|
||||||
// completion event so the game's search finishes instead of hanging.
|
// completion event. With no relay configured the search returns empty (the
|
||||||
|
// game's flow completes cleanly).
|
||||||
u32 NetDll_XNetQosLookup_entry(u32 caller, u32 cxna, mapped_void apxna, mapped_void apxnk,
|
u32 NetDll_XNetQosLookup_entry(u32 caller, u32 cxna, mapped_void apxna, mapped_void apxnk,
|
||||||
u32 cxnk, mapped_void apxnaResults, mapped_u32 apxnqosResults,
|
u32 cxnk, mapped_void apxnaResults, mapped_u32 apxnqosResults,
|
||||||
mapped_void axnqosInfoResults, u32 dwTimeoutMs, u32 hEvent,
|
mapped_void axnqosInfoResults, u32 dwTimeoutMs, u32 hEvent,
|
||||||
|
|
@ -645,12 +648,47 @@ u32 NetDll_XNetQosLookup_entry(u32 caller, u32 cxna, mapped_void apxna, mapped_v
|
||||||
(void)cxnk;
|
(void)cxnk;
|
||||||
(void)apxnaResults;
|
(void)apxnaResults;
|
||||||
(void)axnqosInfoResults;
|
(void)axnqosInfoResults;
|
||||||
(void)dwTimeoutMs;
|
|
||||||
(void)dwFlags;
|
(void)dwFlags;
|
||||||
if (apxnqosResults) {
|
|
||||||
auto qos_guest = REX_KERNEL_MEMORY()->SystemHeapAlloc(sizeof(XNQOS));
|
using rex::system::XNetTransport;
|
||||||
|
|
||||||
|
std::vector<rex::system::XNetSession> sessions;
|
||||||
|
if (XNetTransport::Instance().Active()) {
|
||||||
|
// Ask the relay, wait up to ~500ms for the reply, then report members.
|
||||||
|
XNetTransport::Instance().QuerySessions();
|
||||||
|
XNetTransport::Instance().WaitForReply(500);
|
||||||
|
sessions = XNetTransport::Instance().Sessions();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Up to 8 discovered sessions (the game's XNQOS addresses array is 8).
|
||||||
|
auto qos_guest = REX_KERNEL_MEMORY()->SystemHeapAlloc(sizeof(XNQOS) + sizeof(XNQOSINFO) * 7);
|
||||||
auto qos = REX_KERNEL_MEMORY()->TranslateVirtual<XNQOS*>(qos_guest);
|
auto qos = REX_KERNEL_MEMORY()->TranslateVirtual<XNQOS*>(qos_guest);
|
||||||
qos->count = qos->count_pending = 0;
|
qos->count = qos->count_pending = 0;
|
||||||
|
uint32_t n = 0;
|
||||||
|
for (const auto& s : sessions) {
|
||||||
|
if (n >= 8) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!XNetTransport::IsVirtualIp(s.virtual_ip) || s.virtual_ip == XNetTransport::LocalVirtualIp()) {
|
||||||
|
continue; // only real peers, not ourselves
|
||||||
|
}
|
||||||
|
// Each session's QoS info references the host's XNADDR (built from the
|
||||||
|
// discovered virtual IP) as its QoS data.
|
||||||
|
XNQOSINFO& info = qos->info[n];
|
||||||
|
info.flags = 1; // XNET_QOS_FLAG_NONE equivalent - address present
|
||||||
|
info.probes_xmit = 1;
|
||||||
|
info.probes_recv = 1;
|
||||||
|
info.data_len = sizeof(XNADDR);
|
||||||
|
auto xnaddr_guest = REX_KERNEL_MEMORY()->SystemHeapAlloc(sizeof(XNADDR));
|
||||||
|
auto xnaddr = REX_KERNEL_MEMORY()->TranslateVirtual<XNADDR*>(xnaddr_guest);
|
||||||
|
std::memset(xnaddr, 0, sizeof(XNADDR));
|
||||||
|
xnaddr->ina.s_addr = htonl(s.virtual_ip);
|
||||||
|
info.data_ptr = xnaddr_guest;
|
||||||
|
++n;
|
||||||
|
}
|
||||||
|
qos->count = qos->count_pending = n;
|
||||||
|
|
||||||
|
if (apxnqosResults) {
|
||||||
*apxnqosResults = qos_guest;
|
*apxnqosResults = qos_guest;
|
||||||
}
|
}
|
||||||
if (hEvent) {
|
if (hEvent) {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ set(REXSYSTEM_SOURCES
|
||||||
xnotifylistener.cpp
|
xnotifylistener.cpp
|
||||||
xenumerator.cpp
|
xenumerator.cpp
|
||||||
xsocket.cpp
|
xsocket.cpp
|
||||||
|
xnet_transport.cpp
|
||||||
xsymboliclink.cpp
|
xsymboliclink.cpp
|
||||||
xobject.cpp
|
xobject.cpp
|
||||||
xmodule.cpp
|
xmodule.cpp
|
||||||
|
|
|
||||||
361
src/system/xnet_transport.cpp
Normal file
361
src/system/xnet_transport.cpp
Normal file
|
|
@ -0,0 +1,361 @@
|
||||||
|
/**
|
||||||
|
* netcode-revival: relay transport for the game's own netcode.
|
||||||
|
* See xnet_transport.h for the design.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <rex/system/xnet_transport.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstring>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <rex/platform.h>
|
||||||
|
#include <rex/system/kernel_state.h>
|
||||||
|
#include <rex/system/xsocket.h>
|
||||||
|
#include <rex/system/xam/user_profile.h>
|
||||||
|
|
||||||
|
#if REX_PLATFORM_WIN32
|
||||||
|
#include <WinSock2.h>
|
||||||
|
#include <WS2tcpip.h>
|
||||||
|
#else
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace rex {
|
||||||
|
namespace system {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Virtual address range: 10.80.0.0/16 (host order prefix 0x0A500000).
|
||||||
|
constexpr uint32_t kVirtualPrefix = 0x0A500000u;
|
||||||
|
|
||||||
|
int64_t SocketCreate() {
|
||||||
|
#if REX_PLATFORM_WIN32
|
||||||
|
return int64_t(::socket(AF_INET, SOCK_DGRAM, 0));
|
||||||
|
#else
|
||||||
|
return int64_t(::socket(AF_INET, SOCK_DGRAM, 0));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void SocketClose(int64_t s) {
|
||||||
|
#if REX_PLATFORM_WIN32
|
||||||
|
::closesocket(SOCKET(s));
|
||||||
|
#else
|
||||||
|
::close(int(s));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SocketSendTo(int64_t s, const sockaddr_in* to, const void* buf, int size) {
|
||||||
|
#if REX_PLATFORM_WIN32
|
||||||
|
const int n = ::sendto(SOCKET(s), reinterpret_cast<const char*>(buf), size, 0,
|
||||||
|
reinterpret_cast<const sockaddr*>(to), sizeof(*to));
|
||||||
|
#else
|
||||||
|
const int n = ::sendto(int(s), buf, size, 0,
|
||||||
|
reinterpret_cast<const sockaddr*>(to), sizeof(*to));
|
||||||
|
#endif
|
||||||
|
return n == size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve "host" to a sockaddr_in (UDP), defaulting the port. Returns true
|
||||||
|
// on success.
|
||||||
|
bool Resolve(const std::string& host, uint16_t port, sockaddr_in* out) {
|
||||||
|
addrinfo hints = {};
|
||||||
|
hints.ai_family = AF_INET;
|
||||||
|
hints.ai_socktype = SOCK_DGRAM;
|
||||||
|
hints.ai_protocol = IPPROTO_UDP;
|
||||||
|
addrinfo* res = nullptr;
|
||||||
|
const std::string port_str = std::to_string(port);
|
||||||
|
if (getaddrinfo(host.c_str(), port_str.c_str(), &hints, &res) != 0 ||
|
||||||
|
res == nullptr) {
|
||||||
|
if (res != nullptr) {
|
||||||
|
freeaddrinfo(res);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*out = *reinterpret_cast<sockaddr_in*>(res->ai_addr);
|
||||||
|
out->sin_port = htons(port);
|
||||||
|
freeaddrinfo(res);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
XNetTransport& XNetTransport::Instance() {
|
||||||
|
static XNetTransport s_instance;
|
||||||
|
return s_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
XNetTransport::~XNetTransport() {
|
||||||
|
std::lock_guard<std::mutex> lock(mu_);
|
||||||
|
StopLocked();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t XNetTransport::LocalVirtualIp() {
|
||||||
|
// Mirrors the XNet identity: machine id from the signed-in profile XUID,
|
||||||
|
// mapped onto 10.80.0.x. Stable across runs on one box.
|
||||||
|
uint64_t seed = 0;
|
||||||
|
if (xam::UserProfile* profile = REX_KERNEL_STATE()->user_profile()) {
|
||||||
|
seed = profile->xuid();
|
||||||
|
}
|
||||||
|
if (seed == 0) {
|
||||||
|
seed = 0x52454B4E45544CULL;
|
||||||
|
}
|
||||||
|
const uint8_t machine_id = uint8_t((seed ^ (seed >> 32)) & 0xFEu) + 1;
|
||||||
|
return kVirtualPrefix | machine_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool XNetTransport::IsVirtualIp(uint32_t host_order_ip) {
|
||||||
|
return (host_order_ip & 0xFFFF0000u) == kVirtualPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool XNetTransport::Active() const {
|
||||||
|
std::lock_guard<std::mutex> lock(mu_);
|
||||||
|
return active_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XNetTransport::SetRelay(const char* host, uint16_t port, const char* room) {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mu_);
|
||||||
|
const std::string h = host ? host : "";
|
||||||
|
const std::string r = room ? room : "";
|
||||||
|
if (h == host_ && port == port_ && r == room_ && active_) {
|
||||||
|
return; // unchanged and already connected
|
||||||
|
}
|
||||||
|
host_ = h;
|
||||||
|
port_ = port;
|
||||||
|
room_ = r;
|
||||||
|
StopLocked();
|
||||||
|
}
|
||||||
|
Reconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
void XNetTransport::StopLocked() {
|
||||||
|
if (thread_.joinable()) {
|
||||||
|
running_ = false;
|
||||||
|
thread_.join();
|
||||||
|
thread_ = {};
|
||||||
|
}
|
||||||
|
if (sock_ != -1) {
|
||||||
|
SocketClose(sock_);
|
||||||
|
sock_ = -1;
|
||||||
|
}
|
||||||
|
active_ = false;
|
||||||
|
joined_ = false;
|
||||||
|
sessions_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void XNetTransport::Reconnect() {
|
||||||
|
std::lock_guard<std::mutex> lock(mu_);
|
||||||
|
if (host_.empty() || port_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sockaddr_in relay_addr = {};
|
||||||
|
if (!Resolve(host_, port_, &relay_addr)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::memcpy(relay_addr_, &relay_addr, sizeof(relay_addr));
|
||||||
|
relay_resolved_ = true;
|
||||||
|
sock_ = SocketCreate();
|
||||||
|
if (sock_ == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Join the room so the relay routes our game traffic among its members.
|
||||||
|
XNetRelayJoin jp = {};
|
||||||
|
jp.version = kVersion;
|
||||||
|
jp.virtual_ip = LocalVirtualIp();
|
||||||
|
const uint32_t room_len =
|
||||||
|
std::min<uint32_t>(room_.size(), sizeof(jp.room));
|
||||||
|
std::memcpy(jp.room, room_.data(), room_len);
|
||||||
|
jp.room_len = uint8_t(room_len);
|
||||||
|
const char name[] = "skate3";
|
||||||
|
const uint32_t name_len = std::min<uint32_t>(sizeof(name) - 1, sizeof(jp.name));
|
||||||
|
std::memcpy(jp.name, name, name_len);
|
||||||
|
jp.name_len = uint8_t(name_len);
|
||||||
|
uint8_t pkt[7 + sizeof(jp)] = {};
|
||||||
|
XNetRelayHeader* h = reinterpret_cast<XNetRelayHeader*>(pkt);
|
||||||
|
h->magic = kMagic;
|
||||||
|
h->type = kJoin;
|
||||||
|
h->payload_size = uint16_t(sizeof(jp));
|
||||||
|
std::memcpy(pkt + 7, &jp, sizeof(jp));
|
||||||
|
SocketSendTo(sock_, &relay_addr, pkt, sizeof(pkt));
|
||||||
|
|
||||||
|
running_ = true;
|
||||||
|
thread_ = std::thread(&XNetTransport::Run, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
int XNetTransport::SendGame(const void* buf, uint32_t len, uint32_t dst_virtual_ip_host_order) {
|
||||||
|
if (len == 0 || len > sizeof(XNetRelayGame::data)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!IsVirtualIp(dst_virtual_ip_host_order) && dst_virtual_ip_host_order != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
std::lock_guard<std::mutex> lock(mu_);
|
||||||
|
if (!active_ || sock_ == -1 || !relay_resolved_) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
const sockaddr_in* relay = reinterpret_cast<const sockaddr_in*>(relay_addr_);
|
||||||
|
XNetRelayGame gp = {};
|
||||||
|
gp.dst_virtual_ip = dst_virtual_ip_host_order;
|
||||||
|
gp.data_len = uint16_t(len);
|
||||||
|
std::memcpy(gp.data, buf, len);
|
||||||
|
uint8_t pkt[7 + sizeof(gp)] = {};
|
||||||
|
XNetRelayHeader* h = reinterpret_cast<XNetRelayHeader*>(pkt);
|
||||||
|
h->magic = kMagic;
|
||||||
|
h->type = kGame;
|
||||||
|
h->payload_size = uint16_t(sizeof(gp));
|
||||||
|
std::memcpy(pkt + 7, &gp, sizeof(gp));
|
||||||
|
if (!SocketSendTo(sock_, relay, pkt, sizeof(pkt))) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return int(len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void XNetTransport::QuerySessions() {
|
||||||
|
std::lock_guard<std::mutex> lock(mu_);
|
||||||
|
if (!active_ || sock_ == -1 || !relay_resolved_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const sockaddr_in* relay = reinterpret_cast<const sockaddr_in*>(relay_addr_);
|
||||||
|
uint8_t pkt[7] = {};
|
||||||
|
XNetRelayHeader* h = reinterpret_cast<XNetRelayHeader*>(pkt);
|
||||||
|
h->magic = kMagic;
|
||||||
|
h->type = kSessionQuery;
|
||||||
|
h->payload_size = 0;
|
||||||
|
SocketSendTo(sock_, relay, pkt, sizeof(pkt));
|
||||||
|
}
|
||||||
|
|
||||||
|
void XNetTransport::WaitForReply(int timeout_ms) {
|
||||||
|
const uint64_t target = [this] {
|
||||||
|
std::lock_guard<std::mutex> lock(mu_);
|
||||||
|
return reply_seq_;
|
||||||
|
}();
|
||||||
|
const auto deadline = std::chrono::steady_clock::now() +
|
||||||
|
std::chrono::milliseconds(timeout_ms);
|
||||||
|
while (std::chrono::steady_clock::now() < deadline) {
|
||||||
|
std::lock_guard<std::mutex> lock(mu_);
|
||||||
|
if (reply_seq_ > target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<XNetSession> XNetTransport::Sessions() const {
|
||||||
|
std::lock_guard<std::mutex> lock(mu_);
|
||||||
|
return sessions_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XNetTransport::RegisterSocket(XSocket* s) {
|
||||||
|
if (s == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::lock_guard<std::mutex> lock(mu_);
|
||||||
|
sockets_.insert(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void XNetTransport::UnregisterSocket(XSocket* s) {
|
||||||
|
std::lock_guard<std::mutex> lock(mu_);
|
||||||
|
sockets_.erase(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void XNetTransport::Run() {
|
||||||
|
while (true) {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mu_);
|
||||||
|
if (!running_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uint8_t buf[7 + sizeof(XNetRelayGame)];
|
||||||
|
sockaddr_in from = {};
|
||||||
|
socklen_t from_len = sizeof(from);
|
||||||
|
#if REX_PLATFORM_WIN32
|
||||||
|
int n = ::recvfrom(SOCKET(sock_), reinterpret_cast<char*>(buf), sizeof(buf), 0,
|
||||||
|
reinterpret_cast<sockaddr*>(&from), &from_len);
|
||||||
|
#else
|
||||||
|
int n = ::recvfrom(int(sock_), buf, sizeof(buf), 0,
|
||||||
|
reinterpret_cast<sockaddr*>(&from), &from_len);
|
||||||
|
#endif
|
||||||
|
if (n < 0) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (n < 7) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
HandlePacket(buf, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void XNetTransport::HandlePacket(const uint8_t* data, int size) {
|
||||||
|
const XNetRelayHeader* h = reinterpret_cast<const XNetRelayHeader*>(data);
|
||||||
|
if (h->magic != kMagic || h->payload_size > 3200 ||
|
||||||
|
size < int(sizeof(*h) + h->payload_size)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const uint8_t* payload = data + sizeof(*h);
|
||||||
|
|
||||||
|
switch (h->type) {
|
||||||
|
case kHello: {
|
||||||
|
std::lock_guard<std::mutex> lock(mu_);
|
||||||
|
// session_id != 0 means accepted (0 = refused).
|
||||||
|
active_ = payload[0] != 0;
|
||||||
|
joined_ = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kGame: {
|
||||||
|
if (h->payload_size < sizeof(XNetRelayGame)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
XNetRelayGame gp = {};
|
||||||
|
std::memcpy(&gp, payload, sizeof(gp));
|
||||||
|
if (gp.data_len > sizeof(gp.data)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::vector<XSocket*> targets;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mu_);
|
||||||
|
for (XSocket* s : sockets_) {
|
||||||
|
targets.push_back(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (XSocket* s : targets) {
|
||||||
|
// src_virtual_ip is host order; QueuePacket's src is host order too.
|
||||||
|
s->QueuePacket(gp.src_virtual_ip, 0, gp.data, gp.data_len);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kSessionReply: {
|
||||||
|
if (h->payload_size < sizeof(XNetRelaySessionReply)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
XNetRelaySessionReply sr = {};
|
||||||
|
std::memcpy(&sr, payload, sizeof(sr));
|
||||||
|
std::vector<XNetSession> fresh;
|
||||||
|
const uint8_t count = std::min<uint8_t>(sr.count, 32);
|
||||||
|
for (uint8_t i = 0; i < count; ++i) {
|
||||||
|
XNetSession s;
|
||||||
|
s.session_id = sr.list[i].session_id;
|
||||||
|
s.virtual_ip = sr.list[i].virtual_ip;
|
||||||
|
s.name_len = std::min<uint8_t>(sr.list[i].name_len, sizeof(s.name));
|
||||||
|
std::memcpy(s.name, sr.list[i].name, s.name_len);
|
||||||
|
fresh.push_back(s);
|
||||||
|
}
|
||||||
|
std::lock_guard<std::mutex> lock(mu_);
|
||||||
|
sessions_ = std::move(fresh);
|
||||||
|
++reply_seq_;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break; // kTypeRooms / Pose / Ping / Pong: not this transport's business.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace system
|
||||||
|
} // namespace rex
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
#include <rex/system/kernel_state.h>
|
#include <rex/system/kernel_state.h>
|
||||||
#include <rex/system/xsocket.h>
|
#include <rex/system/xsocket.h>
|
||||||
// #include <rex/system/xnet.h>
|
// #include <rex/system/xnet.h>
|
||||||
|
#include <rex/system/xnet_transport.h>
|
||||||
|
|
||||||
#include <rex/net/socket.h>
|
#include <rex/net/socket.h>
|
||||||
|
|
||||||
|
|
@ -65,10 +66,15 @@ X_STATUS XSocket::Initialize(AddressFamily af, Type type, Protocol proto) {
|
||||||
return X_STATUS_UNSUCCESSFUL;
|
return X_STATUS_UNSUCCESSFUL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// netcode-revival: relayed game traffic is injected into registered
|
||||||
|
// sockets; the game's RakNet netcode socket is one of them.
|
||||||
|
XNetTransport::Instance().RegisterSocket(this);
|
||||||
|
|
||||||
return X_STATUS_SUCCESS;
|
return X_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
X_STATUS XSocket::Close() {
|
X_STATUS XSocket::Close() {
|
||||||
|
XNetTransport::Instance().UnregisterSocket(this);
|
||||||
int ret = rex::net::socket_close(native_handle_);
|
int ret = rex::net::socket_close(native_handle_);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
return X_STATUS_UNSUCCESSFUL;
|
return X_STATUS_UNSUCCESSFUL;
|
||||||
|
|
@ -171,28 +177,29 @@ int XSocket::Recv(uint8_t* buf, uint32_t buf_len, uint32_t flags) {
|
||||||
|
|
||||||
int XSocket::RecvFrom(uint8_t* buf, uint32_t buf_len, uint32_t flags, N_XSOCKADDR_IN* from,
|
int XSocket::RecvFrom(uint8_t* buf, uint32_t buf_len, uint32_t flags, N_XSOCKADDR_IN* from,
|
||||||
uint32_t* from_len) {
|
uint32_t* from_len) {
|
||||||
// Pop from secure packets first
|
// netcode-revival: relayed game datagrams (from the XNetTransport receive
|
||||||
// TODO(DrChat): Enable when I commit XNet
|
// thread) are queued here and surface ahead of any native traffic.
|
||||||
/*
|
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(incoming_packet_mutex_);
|
std::lock_guard<std::mutex> lock(incoming_packet_mutex_);
|
||||||
if (incoming_packets_.size()) {
|
if (!incoming_packets_.empty()) {
|
||||||
packet* pkt = (packet*)incoming_packets_.front();
|
packet* pkt = reinterpret_cast<packet*>(incoming_packets_.front());
|
||||||
int data_len = pkt->data_len;
|
incoming_packets_.pop();
|
||||||
std::memcpy(buf, pkt->data, std::min((uint32_t)pkt->data_len, buf_len));
|
const uint32_t data_len = pkt->data_len;
|
||||||
|
const uint32_t n = std::min<uint32_t>(data_len, buf_len);
|
||||||
|
std::memcpy(buf, pkt->data, n);
|
||||||
|
if (from) {
|
||||||
from->sin_family = 2;
|
from->sin_family = 2;
|
||||||
from->sin_addr = pkt->src_ip;
|
from->sin_addr = pkt->src_ip;
|
||||||
from->sin_port = pkt->src_port;
|
from->sin_port = pkt->src_port;
|
||||||
|
std::memset(from->x_sin_zero, 0, sizeof(from->x_sin_zero));
|
||||||
incoming_packets_.pop();
|
}
|
||||||
uint8_t* pkt_ui8 = (uint8_t*)pkt;
|
if (from_len) {
|
||||||
delete[] pkt_ui8;
|
*from_len = sizeof(sockaddr_in);
|
||||||
|
}
|
||||||
return data_len;
|
delete[] reinterpret_cast<uint8_t*>(pkt);
|
||||||
|
return int(n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
sockaddr_in nfrom;
|
sockaddr_in nfrom;
|
||||||
socklen_t nfromlen = sizeof(sockaddr_in);
|
socklen_t nfromlen = sizeof(sockaddr_in);
|
||||||
|
|
@ -218,16 +225,12 @@ int XSocket::Send(const uint8_t* buf, uint32_t buf_len, uint32_t flags) {
|
||||||
|
|
||||||
int XSocket::SendTo(uint8_t* buf, uint32_t buf_len, uint32_t flags, N_XSOCKADDR_IN* to,
|
int XSocket::SendTo(uint8_t* buf, uint32_t buf_len, uint32_t flags, N_XSOCKADDR_IN* to,
|
||||||
uint32_t to_len) {
|
uint32_t to_len) {
|
||||||
// Send 2 copies of the packet: One to XNet (for network security) and an
|
// netcode-revival: a datagram addressed to a virtual 10.80.0.x peer is
|
||||||
// unencrypted copy for other Xenia hosts.
|
// routed through the relay (the game's RakNet netcode), not sent raw to
|
||||||
// TODO(DrChat): Enable when I commit XNet.
|
// an unreachable address.
|
||||||
/*
|
if (to && XNetTransport::IsVirtualIp(uint32_t(to->sin_addr))) {
|
||||||
auto xam = kernel_state()->GetKernelModule<xam::XamModule>("xam.xex");
|
return XNetTransport::Instance().SendGame(buf, buf_len, uint32_t(to->sin_addr));
|
||||||
auto xnet = xam->xnet();
|
|
||||||
if (xnet) {
|
|
||||||
xnet->SendPacket(this, to, buf, buf_len);
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
sockaddr_in nto;
|
sockaddr_in nto;
|
||||||
if (to) {
|
if (to) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue