Compare commits
20 commits
skate3-sdk
...
netcode-re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30ee3e2b86 | ||
|
|
edda6c9a06 | ||
|
|
0da17439a7 | ||
|
|
05425a39f0 | ||
|
|
6db6674892 | ||
|
|
3016f14fe8 | ||
|
|
aa8cda81a8 | ||
|
|
7b9d706f79 | ||
|
|
e09f224a6b | ||
|
|
73d8f36af2 | ||
|
|
51b8ce5ed2 | ||
|
|
7b6322b317 | ||
|
|
9e300e14bc | ||
|
|
6fcc96de18 | ||
|
|
3229aa3632 | ||
|
|
3ec27d115b | ||
|
|
01fe706c13 | ||
|
|
bf3635d7ad | ||
|
|
767bd6e4bc | ||
|
|
395e1ce67d |
10 changed files with 1209 additions and 111 deletions
190
include/rex/system/xnet_transport.h
Normal file
190
include/rex/system/xnet_transport.h
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
|
@ -34,19 +34,24 @@ std::mutex g_mutex;
|
||||||
// Recursive: FlagRegistrar chain methods re-enter; change callbacks invoked
|
// Recursive: FlagRegistrar chain methods re-enter; change callbacks invoked
|
||||||
// from SetFlagByName must not mutate the registry.
|
// from SetFlagByName must not mutate the registry.
|
||||||
std::recursive_mutex& GetRegistryMutex() {
|
std::recursive_mutex& GetRegistryMutex() {
|
||||||
static std::recursive_mutex m;
|
// Leaky (heap, never destroyed) so teardown order can't bite: static
|
||||||
return m;
|
// FlagRegistrar destructors run during __cxa_finalize and unregister their
|
||||||
|
// flags; if these function-local statics are destroyed first, that
|
||||||
|
// teardown crashes (static destruction-order fiasco, seen at exit of the
|
||||||
|
// codegen tool). Intentionally leaked - the process is ending.
|
||||||
|
static auto* m = new std::recursive_mutex();
|
||||||
|
return *m;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flag registry - use functions to avoid static init order issues
|
// Flag registry - use functions to avoid static init order issues
|
||||||
std::vector<FlagEntry>& GetRegistryStorage() {
|
std::vector<FlagEntry>& GetRegistryStorage() {
|
||||||
static std::vector<FlagEntry> registry;
|
static auto* registry = new std::vector<FlagEntry>();
|
||||||
return registry;
|
return *registry;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unordered_map<std::string, size_t>& GetRegistryIndex() {
|
std::unordered_map<std::string, size_t>& GetRegistryIndex() {
|
||||||
static std::unordered_map<std::string, size_t> index;
|
static auto* index = new std::unordered_map<std::string, size_t>();
|
||||||
return index;
|
return *index;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::pair<std::string, std::string>>& GetCommandLineOverrides() {
|
std::vector<std::pair<std::string, std::string>>& GetCommandLineOverrides() {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include <rex/chrono/clock.h>
|
#include <rex/chrono/clock.h>
|
||||||
|
#include <rex/cvar.h>
|
||||||
#include <rex/kernel/xam/module.h>
|
#include <rex/kernel/xam/module.h>
|
||||||
#include <rex/kernel/xam/private.h>
|
#include <rex/kernel/xam/private.h>
|
||||||
#include <rex/kernel/xboxkrnl/error.h>
|
#include <rex/kernel/xboxkrnl/error.h>
|
||||||
|
|
@ -26,6 +27,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>
|
||||||
|
|
||||||
|
|
@ -35,6 +37,7 @@
|
||||||
#include <winsock2.h> // NOLINT(build/include_order)
|
#include <winsock2.h> // NOLINT(build/include_order)
|
||||||
#elif REX_PLATFORM_LINUX || REX_PLATFORM_MAC
|
#elif REX_PLATFORM_LINUX || REX_PLATFORM_MAC
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
|
#include <netdb.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <netinet/ip.h>
|
#include <netinet/ip.h>
|
||||||
#include <sys/select.h>
|
#include <sys/select.h>
|
||||||
|
|
@ -47,6 +50,15 @@ namespace xam {
|
||||||
using namespace rex::system;
|
using namespace rex::system;
|
||||||
using namespace rex::system::xam;
|
using namespace rex::system::xam;
|
||||||
|
|
||||||
|
// netcode-revival: trace the online-path calls so a run shows exactly which
|
||||||
|
// XNet/Xam gates the game hits (party/Ea Nation debugging). Default on during
|
||||||
|
// the revival bring-up; disable with xnet_trace=false before any release.
|
||||||
|
REXCVAR_DEFINE_BOOL(xnet_trace, true, "Kernel",
|
||||||
|
"Log online-path XNet/Xam calls (netcode debugging)");
|
||||||
|
bool XnetTrace() {
|
||||||
|
return REXCVAR_GET(xnet_trace);
|
||||||
|
}
|
||||||
|
|
||||||
// https://github.com/G91/TitanOffLine/blob/1e692d9bb9dfac386d08045ccdadf4ae3227bb5e/xkelib/xam/xamNet.h
|
// https://github.com/G91/TitanOffLine/blob/1e692d9bb9dfac386d08045ccdadf4ae3227bb5e/xkelib/xam/xamNet.h
|
||||||
enum {
|
enum {
|
||||||
XNCALLER_INVALID = 0x0,
|
XNCALLER_INVALID = 0x0,
|
||||||
|
|
@ -125,6 +137,58 @@ struct XWSAOVERLAPPED {
|
||||||
rex::be<uint32_t> event_handle;
|
rex::be<uint32_t> event_handle;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ---- netcode-revival: virtual XNet identity ---------------------------------
|
||||||
|
// The Xbox title address (XNADDR) is what RakNet folds into its peer GUID and
|
||||||
|
// what the game resolves into a socket address via XNetXnAddrToInAddr. We give
|
||||||
|
// this machine a stable, unique identity: a reserved virtual IP (10.80.0.x,
|
||||||
|
// "LAN" class) plus a unique 6-byte abEnet. RakPeer::Startup requires
|
||||||
|
// non-zero abEnet and different machines need distinct values (a zero MAC
|
||||||
|
// breaks startup; identical MACs collide GUIDs). Derived from the signed-in
|
||||||
|
// profile XUID so it is stable across runs on one box.
|
||||||
|
struct XnetIdentity {
|
||||||
|
uint8_t machine_id = 1; // 1..254
|
||||||
|
uint8_t ab_enet[6] = {};
|
||||||
|
uint32_t virtual_ip = 0; // host order
|
||||||
|
};
|
||||||
|
|
||||||
|
uint64_t XnetSeed() {
|
||||||
|
const auto& profile = REX_KERNEL_STATE()->user_profile();
|
||||||
|
uint64_t seed = profile->xuid();
|
||||||
|
if (seed == 0) {
|
||||||
|
// Fallback when no profile is signed in: a stable-enough per-process
|
||||||
|
// seed (later refinements can persist one).
|
||||||
|
seed = 0x52454B4E45544CULL;
|
||||||
|
}
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
const XnetIdentity& XnetLocalIdentity() {
|
||||||
|
static const XnetIdentity s_identity = [] {
|
||||||
|
XnetIdentity id;
|
||||||
|
uint64_t seed = XnetSeed();
|
||||||
|
id.machine_id = uint8_t((seed ^ (seed >> 32)) & 0xFEu) + 1; // 1..254
|
||||||
|
id.ab_enet[0] = id.machine_id;
|
||||||
|
for (int i = 1; i < 6; ++i) {
|
||||||
|
seed = seed * 6364136223846793005ULL + 1442695040888963407ULL;
|
||||||
|
id.ab_enet[i] = uint8_t(seed >> 24);
|
||||||
|
}
|
||||||
|
// Single source of truth with the relay transport (same 10.80.0.x).
|
||||||
|
id.virtual_ip = rex::system::XNetTransport::LocalVirtualIp();
|
||||||
|
return id;
|
||||||
|
}();
|
||||||
|
return s_identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple xorshift64* PRNG for session keys (XNetCreateKey is required to
|
||||||
|
// produce a 16-byte XNKEY; content only needs to be unique per session).
|
||||||
|
uint64_t XnetNextRand() {
|
||||||
|
static uint64_t s = XnetSeed() | 1ULL;
|
||||||
|
s ^= s >> 12;
|
||||||
|
s ^= s << 25;
|
||||||
|
s ^= s >> 27;
|
||||||
|
return s * 2685821657736338717ULL;
|
||||||
|
}
|
||||||
|
|
||||||
void LoadSockaddr(const uint8_t* ptr, sockaddr* out_addr) {
|
void LoadSockaddr(const uint8_t* ptr, sockaddr* out_addr) {
|
||||||
out_addr->sa_family = memory::load_and_swap<uint16_t>(ptr + 0);
|
out_addr->sa_family = memory::load_and_swap<uint16_t>(ptr + 0);
|
||||||
switch (out_addr->sa_family) {
|
switch (out_addr->sa_family) {
|
||||||
|
|
@ -180,6 +244,9 @@ struct XNetStartupParams {
|
||||||
XNetStartupParams xnet_startup_params = {0};
|
XNetStartupParams xnet_startup_params = {0};
|
||||||
|
|
||||||
u32 NetDll_XNetStartup_entry(u32 caller, ppc_ptr_t<XNetStartupParams> params) {
|
u32 NetDll_XNetStartup_entry(u32 caller, ppc_ptr_t<XNetStartupParams> params) {
|
||||||
|
if (XnetTrace()) {
|
||||||
|
REXLOG_INFO("xnet trace: XNetStartup(caller={})", caller);
|
||||||
|
}
|
||||||
if (params) {
|
if (params) {
|
||||||
assert_true(params->cfgSizeOfStruct == sizeof(XNetStartupParams));
|
assert_true(params->cfgSizeOfStruct == sizeof(XNetStartupParams));
|
||||||
std::memcpy(&xnet_startup_params, params, sizeof(XNetStartupParams));
|
std::memcpy(&xnet_startup_params, params, sizeof(XNetStartupParams));
|
||||||
|
|
@ -433,27 +500,23 @@ struct XnAddrStatus {
|
||||||
};
|
};
|
||||||
|
|
||||||
u32 NetDll_XNetGetTitleXnAddr_entry(u32 caller, ppc_ptr_t<XNADDR> addr_ptr) {
|
u32 NetDll_XNetGetTitleXnAddr_entry(u32 caller, ppc_ptr_t<XNADDR> addr_ptr) {
|
||||||
// Just return a loopback address atm.
|
// netcode-revival: report this machine as ONLINE so the game's party
|
||||||
addr_ptr->ina.s_addr = htonl(INADDR_LOOPBACK);
|
// state machine advances (Skate 3 maps status bit 0x60 = online: the
|
||||||
addr_ptr->inaOnline.s_addr = 0;
|
// EA-Nation resolver and session-join both gate on it). Also expose the
|
||||||
|
// online address so online-address paths have a valid inaOnline.
|
||||||
|
const XnetIdentity& id = XnetLocalIdentity();
|
||||||
|
addr_ptr->ina.s_addr = htonl(id.virtual_ip);
|
||||||
|
addr_ptr->inaOnline.s_addr = htonl(id.virtual_ip);
|
||||||
addr_ptr->wPortOnline = 0;
|
addr_ptr->wPortOnline = 0;
|
||||||
|
std::memcpy(addr_ptr->abEnet, id.ab_enet, 6);
|
||||||
// TODO(gibbed): A proper mac address.
|
|
||||||
// RakNet's 360 version appears to depend on abEnet to create "random" 64-bit
|
|
||||||
// numbers. A zero value will cause RakPeer::Startup to fail. This causes
|
|
||||||
// 58411436 to crash on startup.
|
|
||||||
// The 360-specific code is scrubbed from the RakNet repo, but there's still
|
|
||||||
// traces of what it's doing which match the game code.
|
|
||||||
// https://github.com/facebookarchive/RakNet/blob/master/Source/RakPeer.cpp#L382
|
|
||||||
// https://github.com/facebookarchive/RakNet/blob/master/Source/RakPeer.cpp#L4527
|
|
||||||
// https://github.com/facebookarchive/RakNet/blob/master/Source/RakPeer.cpp#L4467
|
|
||||||
// "Mac address is a poor solution because you can't have multiple connections
|
|
||||||
// from the same system"
|
|
||||||
std::memset(addr_ptr->abEnet, 0xCC, 6);
|
|
||||||
|
|
||||||
std::memset(addr_ptr->abOnline, 0, 20);
|
std::memset(addr_ptr->abOnline, 0, 20);
|
||||||
|
// 0x60 = the game's "online" status bits (not the standard 0-3 enum; the
|
||||||
return XnAddrStatus::XNET_GET_XNADDR_STATIC;
|
// game checks (status & 0x60) everywhere it means "connected to LIVE").
|
||||||
|
if (XnetTrace()) {
|
||||||
|
REXLOG_INFO("xnet trace: XNetGetTitleXnAddr(caller={}) -> status 0x60 (online)",
|
||||||
|
caller);
|
||||||
|
}
|
||||||
|
return 0x60;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 NetDll_XNetGetDebugXnAddr_entry(u32 caller, ppc_ptr_t<XNADDR> addr_ptr) {
|
u32 NetDll_XNetGetDebugXnAddr_entry(u32 caller, ppc_ptr_t<XNADDR> addr_ptr) {
|
||||||
|
|
@ -464,33 +527,57 @@ u32 NetDll_XNetGetDebugXnAddr_entry(u32 caller, ppc_ptr_t<XNADDR> addr_ptr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 NetDll_XNetXnAddrToMachineId_entry(u32 caller, ppc_ptr_t<XNADDR> addr_ptr, mapped_u32 id_ptr) {
|
u32 NetDll_XNetXnAddrToMachineId_entry(u32 caller, ppc_ptr_t<XNADDR> addr_ptr, mapped_u32 id_ptr) {
|
||||||
// Tell the caller we're not signed in to live (non-zero ret)
|
// netcode-revival: report this machine's stable machine id instead of "not
|
||||||
return 1;
|
// signed in to live", so identity-dependent flows proceed.
|
||||||
|
if (id_ptr) {
|
||||||
|
*id_ptr = XnetLocalIdentity().machine_id;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetDll_XNetInAddrToString_entry(u32 caller, u32 in_addr, mapped_string string_out,
|
void NetDll_XNetInAddrToString_entry(u32 caller, u32 in_addr, mapped_string string_out,
|
||||||
u32 string_size) {
|
u32 string_size) {
|
||||||
rex::string::rex_strcpy(string_out, string_size, "666.666.666.666");
|
char buf[INET_ADDRSTRLEN] = {};
|
||||||
|
in_addr_t a = ntohl(in_addr);
|
||||||
|
std::snprintf(buf, sizeof(buf), "%u.%u.%u.%u", (a >> 24) & 0xFF, (a >> 16) & 0xFF,
|
||||||
|
(a >> 8) & 0xFF, a & 0xFF);
|
||||||
|
rex::string::rex_strcpy(string_out, string_size, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This converts a XNet address to an IN_ADDR. The IN_ADDR is used for
|
// This converts a XNet address to an IN_ADDR. The IN_ADDR is used for
|
||||||
// subsequent socket calls (like a handle to a XNet address)
|
// subsequent socket calls (like a handle to a XNet address).
|
||||||
|
// netcode-revival: our XNADDRs carry the virtual IP in ina (network order);
|
||||||
|
// resolve it into the requested in_addr.
|
||||||
u32 NetDll_XNetXnAddrToInAddr_entry(u32 caller, ppc_ptr_t<XNADDR> xn_addr, mapped_void xid,
|
u32 NetDll_XNetXnAddrToInAddr_entry(u32 caller, ppc_ptr_t<XNADDR> xn_addr, mapped_void xid,
|
||||||
mapped_void in_addr) {
|
mapped_void in_addr) {
|
||||||
return 1;
|
if (!xn_addr || !in_addr) {
|
||||||
|
return X_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
uint32_t ip = xn_addr->ina.s_addr;
|
||||||
|
std::memcpy(in_addr, &ip, sizeof(ip));
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Does the reverse of the above.
|
// Does the reverse of the above.
|
||||||
// FIXME: Arguments may not be correct.
|
// netcode-revival: rebuild an XNADDR from an in_addr (virtual IP).
|
||||||
u32 NetDll_XNetInAddrToXnAddr_entry(u32 caller, mapped_void in_addr, ppc_ptr_t<XNADDR> xn_addr,
|
u32 NetDll_XNetInAddrToXnAddr_entry(u32 caller, mapped_void in_addr, ppc_ptr_t<XNADDR> xn_addr,
|
||||||
mapped_void xid) {
|
mapped_void xid) {
|
||||||
return 1;
|
if (!in_addr || !xn_addr) {
|
||||||
|
return X_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
std::memcpy(&xn_addr->ina.s_addr, in_addr, sizeof(uint32_t));
|
||||||
|
xn_addr->inaOnline.s_addr = 0;
|
||||||
|
xn_addr->wPortOnline = 0;
|
||||||
|
std::memset(xn_addr->abEnet, 0, 6);
|
||||||
|
std::memset(xn_addr->abOnline, 0, 20);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://www.google.com/patents/WO2008112448A1?cl=en
|
// https://www.google.com/patents/WO2008112448A1?cl=en
|
||||||
// Reserves a port for use by system link
|
// Reserves a port for use by system link. netcode-revival: accept any port.
|
||||||
u32 NetDll_XNetSetSystemLinkPort_entry(u32 caller, u32 port) {
|
u32 NetDll_XNetSetSystemLinkPort_entry(u32 caller, u32 port) {
|
||||||
return 1;
|
(void)port;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/ILOVEPIE/Cxbx-Reloaded/blob/master/src/CxbxKrnl/EmuXOnline.h#L39
|
// https://github.com/ILOVEPIE/Cxbx-Reloaded/blob/master/src/CxbxKrnl/EmuXOnline.h#L39
|
||||||
|
|
@ -503,17 +590,55 @@ struct XEthernetStatus {
|
||||||
};
|
};
|
||||||
|
|
||||||
u32 NetDll_XNetGetEthernetLinkStatus_entry(u32 caller) {
|
u32 NetDll_XNetGetEthernetLinkStatus_entry(u32 caller) {
|
||||||
return 0;
|
// netcode-revival: report an active 100Mbps link. The game's party state
|
||||||
|
// machine only reaches the EA-Nation/online path when the link is up
|
||||||
|
// (sub_82A1AAE8 returns the online server IP only if this is non-zero),
|
||||||
|
// so returning 0 left it frozen on "connecting to EA Nation" forever.
|
||||||
|
if (XnetTrace()) {
|
||||||
|
REXLOG_INFO("xnet trace: XNetGetEthernetLinkStatus(caller={}) -> active 100Mbps",
|
||||||
|
caller);
|
||||||
|
}
|
||||||
|
return XEthernetStatus::XNET_ETHERNET_LINK_ACTIVE |
|
||||||
|
XEthernetStatus::XNET_ETHERNET_LINK_100MBPS;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 NetDll_XNetDnsLookup_entry(u32 caller, mapped_string host, u32 event_handle, mapped_u32 pdns) {
|
u32 NetDll_XNetDnsLookup_entry(u32 caller, mapped_string host, u32 event_handle, mapped_u32 pdns) {
|
||||||
// TODO(gibbed): actually implement this
|
// netcode-revival: actually resolve the hostname so the EA Nation resolver
|
||||||
if (pdns) {
|
// (sub_8298A460) and any DNS consumer get a valid address instead of the
|
||||||
auto dns_guest = REX_KERNEL_MEMORY()->SystemHeapAlloc(sizeof(XNDNS));
|
// old always-error (status=1) result. The game polls status until it is
|
||||||
auto dns = REX_KERNEL_MEMORY()->TranslateVirtual<XNDNS*>(dns_guest);
|
// not XNET_STATUS_PENDING (10036), then requires status==0 && cina>0 and
|
||||||
dns->status = 1; // non-zero = error
|
// uses aina[0].
|
||||||
*pdns = dns_guest;
|
auto dns_guest = REX_KERNEL_MEMORY()->SystemHeapAlloc(sizeof(XNDNS));
|
||||||
|
auto dns = REX_KERNEL_MEMORY()->TranslateVirtual<XNDNS*>(dns_guest);
|
||||||
|
dns->status = 1;
|
||||||
|
dns->cina = 0;
|
||||||
|
if (host) {
|
||||||
|
addrinfo hints = {};
|
||||||
|
hints.ai_family = AF_INET;
|
||||||
|
hints.ai_socktype = SOCK_DGRAM;
|
||||||
|
hints.ai_protocol = IPPROTO_UDP;
|
||||||
|
addrinfo* res = nullptr;
|
||||||
|
if (getaddrinfo(host, nullptr, &hints, &res) == 0 && res != nullptr) {
|
||||||
|
uint32_t n = 0;
|
||||||
|
for (addrinfo* p = res; p != nullptr && n < 8; p = p->ai_next) {
|
||||||
|
auto* sin = reinterpret_cast<sockaddr_in*>(p->ai_addr);
|
||||||
|
if (sin->sin_family == AF_INET) {
|
||||||
|
dns->aina[n++] = sin->sin_addr; // network order
|
||||||
|
}
|
||||||
|
}
|
||||||
|
freeaddrinfo(res);
|
||||||
|
dns->status = 0;
|
||||||
|
dns->cina = n;
|
||||||
|
if (XnetTrace()) {
|
||||||
|
REXLOG_INFO("xnet trace: XNetDnsLookup('{}') -> {} address(es)",
|
||||||
|
static_cast<const char*>(host), n);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
REXLOG_WARN("xnet trace: XNetDnsLookup('{}') failed to resolve",
|
||||||
|
static_cast<const char*>(host));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
*pdns = dns_guest;
|
||||||
if (event_handle) {
|
if (event_handle) {
|
||||||
auto ev = REX_KERNEL_OBJECTS()->LookupObject<XEvent>(event_handle);
|
auto ev = REX_KERNEL_OBJECTS()->LookupObject<XEvent>(event_handle);
|
||||||
assert_not_null(ev);
|
assert_not_null(ev);
|
||||||
|
|
@ -556,7 +681,201 @@ u32 NetDll_XNetQosRelease_entry(u32 caller, ppc_ptr_t<XNQOS> qos) {
|
||||||
|
|
||||||
u32 NetDll_XNetQosListen_entry(u32 caller, mapped_void id, mapped_void data, u32 data_size, u32 r7,
|
u32 NetDll_XNetQosListen_entry(u32 caller, mapped_void id, mapped_void data, u32 data_size, u32 r7,
|
||||||
u32 flags) {
|
u32 flags) {
|
||||||
return X_ERROR_FUNCTION_FAILED;
|
// netcode-revival Phase 0: accept session advertisement so the host flow
|
||||||
|
// proceeds. No registry yet (the relay-backed session registry + QoS
|
||||||
|
// discovery land in Phase 1); QosLookup returns an empty result set for
|
||||||
|
// now so the game's search completes cleanly.
|
||||||
|
(void)id;
|
||||||
|
(void)data;
|
||||||
|
(void)data_size;
|
||||||
|
(void)r7;
|
||||||
|
(void)flags;
|
||||||
|
if (XnetTrace()) {
|
||||||
|
REXLOG_INFO("xnet trace: XNetQosListen(caller={}, flags={}) -> ok (advertise)",
|
||||||
|
caller, flags);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// netcode-revival: session search. Phase 1: ask the relay who else is in our
|
||||||
|
// room and hand the discovered sessions to the game, then signal the
|
||||||
|
// 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 cxnk, mapped_void apxnaResults, mapped_u32 apxnqosResults,
|
||||||
|
mapped_void axnqosInfoResults, u32 dwTimeoutMs, u32 hEvent,
|
||||||
|
u32 dwFlags) {
|
||||||
|
(void)cxna;
|
||||||
|
(void)apxna;
|
||||||
|
(void)apxnk;
|
||||||
|
(void)cxnk;
|
||||||
|
(void)apxnaResults;
|
||||||
|
(void)axnqosInfoResults;
|
||||||
|
(void)dwFlags;
|
||||||
|
|
||||||
|
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);
|
||||||
|
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 (XnetTrace()) {
|
||||||
|
REXLOG_INFO("xnet trace: XNetQosLookup(caller={}) -> {} session(s) found",
|
||||||
|
caller, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apxnqosResults) {
|
||||||
|
*apxnqosResults = qos_guest;
|
||||||
|
}
|
||||||
|
if (hEvent) {
|
||||||
|
auto ev = REX_KERNEL_OBJECTS()->LookupObject<XEvent>(hEvent);
|
||||||
|
if (ev) {
|
||||||
|
ev->Set(0, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// netcode-revival: generate a 16-byte session key (XNKEY).
|
||||||
|
u32 NetDll_XNetCreateKey_entry(u32 caller, mapped_void key) {
|
||||||
|
if (!key) {
|
||||||
|
return X_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
if (XnetTrace()) {
|
||||||
|
REXLOG_INFO("xnet trace: XNetCreateKey(caller={})", caller);
|
||||||
|
}
|
||||||
|
uint8_t* out = static_cast<uint8_t*>(key);
|
||||||
|
uint64_t r = XnetNextRand();
|
||||||
|
for (int i = 0; i < 16; ++i) {
|
||||||
|
if ((i & 7) == 0) {
|
||||||
|
r = XnetNextRand();
|
||||||
|
}
|
||||||
|
out[i] = uint8_t(r >> ((i & 7) * 8));
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// netcode-revival: accept + (Phase 1) register the session key.
|
||||||
|
u32 NetDll_XNetRegisterKey_entry(u32 caller, mapped_void key) {
|
||||||
|
if (XnetTrace()) {
|
||||||
|
REXLOG_INFO("xnet trace: XNetRegisterKey(caller={})", caller);
|
||||||
|
}
|
||||||
|
(void)key;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 NetDll_XNetUnregisterKey_entry(u32 caller, mapped_void key) {
|
||||||
|
(void)key;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// netcode-revival: connecting to a peer is reported as established.
|
||||||
|
u32 NetDll_XNetConnect_entry(u32 caller, ppc_ptr_t<XNADDR> xn_addr) {
|
||||||
|
if (XnetTrace()) {
|
||||||
|
REXLOG_INFO("xnet trace: XNetConnect(caller={}, peer=10.80.0.{})",
|
||||||
|
caller, xn_addr ? (ntohl(xn_addr->ina.s_addr) & 0xFFu) : 0);
|
||||||
|
}
|
||||||
|
(void)xn_addr;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://learn.microsoft.com/en-us/previous-versions/windows/desktop/xna/xnetgetconnectstatus
|
||||||
|
// INT XNetGetConnectStatus(const XNADDR* pxna, INT* pxncs)
|
||||||
|
u32 NetDll_XNetGetConnectStatus_entry(u32 caller, ppc_ptr_t<XNADDR> xn_addr, mapped_void status_ptr) {
|
||||||
|
// netcode-revival: the party state machine (sub_82A1B310 state 5) reads the
|
||||||
|
// RETURN VALUE and only advances to 'EA connected' when it equals 2
|
||||||
|
// (XNET_CONNECT_STATUS_CONNECTED). Returning 0 stalled it on "Cannot
|
||||||
|
// Connect to EA Servers" forever.
|
||||||
|
if (XnetTrace()) {
|
||||||
|
REXLOG_INFO("xnet trace: XNetGetConnectStatus(caller={}) -> CONNECTED", caller);
|
||||||
|
}
|
||||||
|
(void)xn_addr;
|
||||||
|
if (status_ptr) {
|
||||||
|
const uint32_t connected = 2; // XNET_CONNECT_STATUS_CONNECTED
|
||||||
|
std::memcpy(status_ptr, &connected, sizeof(connected));
|
||||||
|
}
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The game calls XNetServerToInAddr(caller=1, server_id, service_id, out)
|
||||||
|
// (sub_82F38580 maps r3/r4/r5 accordingly). netcode-revival: return our
|
||||||
|
// virtual IP (the machine's online identity); the party flow fakes EA
|
||||||
|
// connectivity via XNetConnect/XNetGetConnectStatus, so any stable address
|
||||||
|
// works here.
|
||||||
|
u32 NetDll_XNetServerToInAddr_entry(u32 caller, u32 server_id, u32 service_id,
|
||||||
|
mapped_void pina) {
|
||||||
|
(void)caller;
|
||||||
|
(void)server_id;
|
||||||
|
(void)service_id;
|
||||||
|
if (!pina) {
|
||||||
|
return X_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
if (XnetTrace()) {
|
||||||
|
REXLOG_INFO("xnet trace: XNetServerToInAddr(server={}, service=0x{:08X}) -> virtual ip",
|
||||||
|
server_id, service_id);
|
||||||
|
}
|
||||||
|
const uint32_t ip = htonl(rex::system::XNetTransport::LocalVirtualIp());
|
||||||
|
std::memcpy(pina, &ip, sizeof(ip));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic getsockopt reads the game relies on (SO_ERROR, SO_REUSEADDR,
|
||||||
|
// SO_BROADCAST, SO_SNDBUF/RCVBUF, TCP_NODELAY, ...) served from the real
|
||||||
|
// native socket.
|
||||||
|
u32 NetDll_getsockopt_entry(u32 caller, u32 socket_handle, u32 level, u32 optname,
|
||||||
|
mapped_void optval_ptr, mapped_u32 optlen_ptr) {
|
||||||
|
auto socket = REX_KERNEL_OBJECTS()->LookupObject<XSocket>(socket_handle);
|
||||||
|
if (!socket) {
|
||||||
|
XThread::SetLastError(0x2736); // WSAENOTSOCK
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!optval_ptr || !optlen_ptr) {
|
||||||
|
XThread::SetLastError(0x2736);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int len = int(*optlen_ptr);
|
||||||
|
// Windows takes char*, POSIX void*; a char* host pointer satisfies both.
|
||||||
|
char* optval_host = reinterpret_cast<char*>(static_cast<void*>(optval_ptr));
|
||||||
|
int ret = getsockopt(int(socket->native_handle()), int(level), int(optname), optval_host,
|
||||||
|
reinterpret_cast<socklen_t*>(&len));
|
||||||
|
if (ret != 0) {
|
||||||
|
XThread::SetLastError(0x2743); // WSAENOPROTOOPT
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*optlen_ptr = uint32_t(len);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 NetDll_inet_addr_entry(mapped_string addr_ptr) {
|
u32 NetDll_inet_addr_entry(mapped_string addr_ptr) {
|
||||||
|
|
@ -1027,25 +1346,25 @@ REX_EXPORT_STUB(__imp__NetDll_XHttpSetStatusCallback);
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XHttpShutdown);
|
REX_EXPORT_STUB(__imp__NetDll_XHttpShutdown);
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XHttpStartup);
|
REX_EXPORT_STUB(__imp__NetDll_XHttpStartup);
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XHttpWriteData);
|
REX_EXPORT_STUB(__imp__NetDll_XHttpWriteData);
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XNetConnect);
|
REX_EXPORT(__imp__NetDll_XNetConnect, rex::kernel::xam::NetDll_XNetConnect_entry)
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XNetCreateKey);
|
REX_EXPORT(__imp__NetDll_XNetCreateKey, rex::kernel::xam::NetDll_XNetCreateKey_entry)
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XNetDnsReverseLookup);
|
REX_EXPORT_STUB(__imp__NetDll_XNetDnsReverseLookup);
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XNetDnsReverseRelease);
|
REX_EXPORT_STUB(__imp__NetDll_XNetDnsReverseRelease);
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XNetGetBroadcastVersionStatus);
|
REX_EXPORT_STUB(__imp__NetDll_XNetGetBroadcastVersionStatus);
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XNetGetConnectStatus);
|
REX_EXPORT(__imp__NetDll_XNetGetConnectStatus, rex::kernel::xam::NetDll_XNetGetConnectStatus_entry)
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XNetGetSystemLinkPort);
|
REX_EXPORT_STUB(__imp__NetDll_XNetGetSystemLinkPort);
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XNetGetXnAddrPlatform);
|
REX_EXPORT_STUB(__imp__NetDll_XNetGetXnAddrPlatform);
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XNetInAddrToServer);
|
REX_EXPORT_STUB(__imp__NetDll_XNetInAddrToServer);
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XNetQosGetListenStats);
|
REX_EXPORT_STUB(__imp__NetDll_XNetQosGetListenStats);
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XNetQosLookup);
|
REX_EXPORT(__imp__NetDll_XNetQosLookup, rex::kernel::xam::NetDll_XNetQosLookup_entry)
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XNetRegisterKey);
|
REX_EXPORT(__imp__NetDll_XNetRegisterKey, rex::kernel::xam::NetDll_XNetRegisterKey_entry)
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XNetReplaceKey);
|
REX_EXPORT_STUB(__imp__NetDll_XNetReplaceKey);
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XNetServerToInAddr);
|
REX_EXPORT(__imp__NetDll_XNetServerToInAddr, rex::kernel::xam::NetDll_XNetServerToInAddr_entry)
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XNetSetOpt);
|
REX_EXPORT_STUB(__imp__NetDll_XNetSetOpt);
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XNetStartupEx);
|
REX_EXPORT_STUB(__imp__NetDll_XNetStartupEx);
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XNetTsAddrToInAddr);
|
REX_EXPORT_STUB(__imp__NetDll_XNetTsAddrToInAddr);
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XNetUnregisterInAddr);
|
REX_EXPORT_STUB(__imp__NetDll_XNetUnregisterInAddr);
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XNetUnregisterKey);
|
REX_EXPORT(__imp__NetDll_XNetUnregisterKey, rex::kernel::xam::NetDll_XNetUnregisterKey_entry)
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XmlDownloadContinue);
|
REX_EXPORT_STUB(__imp__NetDll_XmlDownloadContinue);
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XmlDownloadGetParseTime);
|
REX_EXPORT_STUB(__imp__NetDll_XmlDownloadGetParseTime);
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XmlDownloadGetReceivedDataSize);
|
REX_EXPORT_STUB(__imp__NetDll_XmlDownloadGetReceivedDataSize);
|
||||||
|
|
@ -1096,4 +1415,4 @@ REX_EXPORT_STUB(__imp__NetDll_XnpUnregisterKeyForCallerType);
|
||||||
REX_EXPORT_STUB(__imp__NetDll_XnpUpdateConfigParams);
|
REX_EXPORT_STUB(__imp__NetDll_XnpUpdateConfigParams);
|
||||||
REX_EXPORT_STUB(__imp__NetDll_getpeername);
|
REX_EXPORT_STUB(__imp__NetDll_getpeername);
|
||||||
REX_EXPORT_STUB(__imp__NetDll_getsockname);
|
REX_EXPORT_STUB(__imp__NetDll_getsockname);
|
||||||
REX_EXPORT_STUB(__imp__NetDll_getsockopt);
|
REX_EXPORT(__imp__NetDll_getsockopt, rex::kernel::xam::NetDll_getsockopt_entry)
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,8 @@ using namespace rex::system::xam;
|
||||||
|
|
||||||
extern std::atomic<int> xam_dialogs_shown_;
|
extern std::atomic<int> xam_dialogs_shown_;
|
||||||
|
|
||||||
|
REXCVAR_DECLARE(bool, xnet_trace);
|
||||||
|
|
||||||
i32 XamUserGetXUID_entry(u32 user_index, u32 type_mask, mapped_u64 xuid_ptr) {
|
i32 XamUserGetXUID_entry(u32 user_index, u32 type_mask, mapped_u64 xuid_ptr) {
|
||||||
assert_true(type_mask == 1 || type_mask == 2 || type_mask == 3 || type_mask == 4 ||
|
assert_true(type_mask == 1 || type_mask == 2 || type_mask == 3 || type_mask == 4 ||
|
||||||
type_mask == 7);
|
type_mask == 7);
|
||||||
|
|
@ -52,19 +54,17 @@ i32 XamUserGetXUID_entry(u32 user_index, u32 type_mask, mapped_u64 xuid_ptr) {
|
||||||
uint32_t result = X_E_NO_SUCH_USER;
|
uint32_t result = X_E_NO_SUCH_USER;
|
||||||
uint64_t xuid = 0;
|
uint64_t xuid = 0;
|
||||||
if (user_index < 4) {
|
if (user_index < 4) {
|
||||||
if (user_index == 0) {
|
const auto& user_profile = REX_KERNEL_STATE()->user_profile();
|
||||||
const auto& user_profile = REX_KERNEL_STATE()->user_profile();
|
if (user_profile->is_signed_in()) {
|
||||||
if (user_profile->is_signed_in()) {
|
auto type = user_profile->type() & type_mask;
|
||||||
auto type = user_profile->type() & type_mask;
|
if (type & (2 | 4)) {
|
||||||
if (type & (2 | 4)) {
|
// maybe online profile?
|
||||||
// maybe online profile?
|
xuid = user_profile->xuid();
|
||||||
xuid = user_profile->xuid();
|
result = X_E_SUCCESS;
|
||||||
result = X_E_SUCCESS;
|
} else if (type & 1) {
|
||||||
} else if (type & 1) {
|
// maybe offline profile?
|
||||||
// maybe offline profile?
|
xuid = user_profile->xuid();
|
||||||
xuid = user_profile->xuid();
|
result = X_E_SUCCESS;
|
||||||
result = X_E_SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -76,10 +76,22 @@ i32 XamUserGetXUID_entry(u32 user_index, u32 type_mask, mapped_u64 xuid_ptr) {
|
||||||
|
|
||||||
u32 XamUserGetSigninState_entry(u32 user_index) {
|
u32 XamUserGetSigninState_entry(u32 user_index) {
|
||||||
uint32_t signin_state = 0;
|
uint32_t signin_state = 0;
|
||||||
|
// netcode-revival: the party flow reads signin state for the "live user
|
||||||
|
// index" (the game's live flag is a user index, not a boolean). We model
|
||||||
|
// one profile, so answer every slot with the same signed-in state.
|
||||||
if (user_index < 4) {
|
if (user_index < 4) {
|
||||||
if (user_index == 0) {
|
const auto& user_profile = REX_KERNEL_STATE()->user_profile();
|
||||||
const auto& user_profile = REX_KERNEL_STATE()->user_profile();
|
signin_state = user_profile->signin_state();
|
||||||
signin_state = user_profile->signin_state();
|
}
|
||||||
|
if (REXCVAR_GET(xnet_trace)) {
|
||||||
|
// Rate-limited: the frontend polls this frequently.
|
||||||
|
static double last_log = 0.0;
|
||||||
|
const double now =
|
||||||
|
double(std::chrono::steady_clock::now().time_since_epoch().count()) / 1e9;
|
||||||
|
if (now - last_log >= 0.5) {
|
||||||
|
last_log = now;
|
||||||
|
REXLOG_INFO("xam trace: XamUserGetSigninState(user={}) -> {}", user_index,
|
||||||
|
signin_state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return signin_state;
|
return signin_state;
|
||||||
|
|
@ -101,7 +113,9 @@ i32 XamUserGetSigninInfo_entry(u32 user_index, u32 flags, ppc_ptr_t<X_USER_SIGNI
|
||||||
}
|
}
|
||||||
|
|
||||||
std::memset(info, 0, sizeof(X_USER_SIGNIN_INFO));
|
std::memset(info, 0, sizeof(X_USER_SIGNIN_INFO));
|
||||||
if (user_index) {
|
// netcode-revival: answer every slot with the one profile (the game's
|
||||||
|
// "live user index" is not necessarily 0).
|
||||||
|
if (user_index >= 4) {
|
||||||
return X_E_NO_SUCH_USER;
|
return X_E_NO_SUCH_USER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,7 +126,15 @@ i32 XamUserGetSigninInfo_entry(u32 user_index, u32 flags, ppc_ptr_t<X_USER_SIGNI
|
||||||
|
|
||||||
info->xuid = user_profile->xuid();
|
info->xuid = user_profile->xuid();
|
||||||
info->signin_state = user_profile->signin_state();
|
info->signin_state = user_profile->signin_state();
|
||||||
|
// netcode-revival: the game's "enter online flow" gate (sub_82703960)
|
||||||
|
// requires unk08 == 1 (a Live indicator) or it shows "please login with
|
||||||
|
// Xbox Live". Set it when the profile is Live signed-in.
|
||||||
|
info->unk08 = user_profile->is_live_signed_in() ? 1 : 0;
|
||||||
rex::string::util_copy_truncating(info->name, user_profile->name(), rex::countof(info->name));
|
rex::string::util_copy_truncating(info->name, user_profile->name(), rex::countof(info->name));
|
||||||
|
if (REXCVAR_GET(xnet_trace)) {
|
||||||
|
REXLOG_INFO("xam trace: XamUserGetSigninInfo(user={}, flags={}) -> signin_state={}",
|
||||||
|
user_index, flags, user_profile->signin_state());
|
||||||
|
}
|
||||||
return X_E_SUCCESS;
|
return X_E_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,10 +143,6 @@ u32 XamUserGetName_entry(u32 user_index, mapped_string buffer, u32 buffer_len) {
|
||||||
return X_E_INVALIDARG;
|
return X_E_INVALIDARG;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user_index) {
|
|
||||||
return X_E_NO_SUCH_USER;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& user_profile = REX_KERNEL_STATE()->user_profile();
|
const auto& user_profile = REX_KERNEL_STATE()->user_profile();
|
||||||
if (!user_profile->is_signed_in()) {
|
if (!user_profile->is_signed_in()) {
|
||||||
if (buffer && buffer_len) {
|
if (buffer && buffer_len) {
|
||||||
|
|
@ -143,10 +161,6 @@ u32 XamUserGetGamerTag_entry(u32 user_index, mapped_wstring buffer, u32 buffer_l
|
||||||
return X_E_INVALIDARG;
|
return X_E_INVALIDARG;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user_index) {
|
|
||||||
return X_E_NO_SUCH_USER;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& user_profile = REX_KERNEL_STATE()->user_profile();
|
const auto& user_profile = REX_KERNEL_STATE()->user_profile();
|
||||||
if (!user_profile->is_signed_in()) {
|
if (!user_profile->is_signed_in()) {
|
||||||
return X_ERROR_NO_SUCH_USER;
|
return X_ERROR_NO_SUCH_USER;
|
||||||
|
|
@ -430,8 +444,13 @@ u32 XamUserCheckPrivilege_entry(u32 user_index, u32 mask, mapped_u32 out_value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we deny everything, games should hopefully not try to do stuff.
|
// netcode-revival: the game gates its party/session flow on
|
||||||
*out_value = 0;
|
// XamUserCheckPrivilege (e.g. MULTIPLAYER_SESSIONS); denying everything
|
||||||
|
// (the old default) made "create a party" refuse. Grant requested online
|
||||||
|
// privileges so the P2P session flow opens.
|
||||||
|
*out_value = 1;
|
||||||
|
REXLOG_INFO("xam: XamUserCheckPrivilege(user={}, mask=0x{:08X}) -> granted",
|
||||||
|
user_index, mask);
|
||||||
return X_ERROR_SUCCESS;
|
return X_ERROR_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -484,9 +503,6 @@ u32 XamUserGetMembershipTier_entry(u32 user_index) {
|
||||||
if (user_index >= 4) {
|
if (user_index >= 4) {
|
||||||
return X_ERROR_INVALID_PARAMETER;
|
return X_ERROR_INVALID_PARAMETER;
|
||||||
}
|
}
|
||||||
if (user_index) {
|
|
||||||
return X_ERROR_NO_SUCH_USER;
|
|
||||||
}
|
|
||||||
if (!REX_KERNEL_STATE()->user_profile()->is_live_signed_in()) {
|
if (!REX_KERNEL_STATE()->user_profile()->is_live_signed_in()) {
|
||||||
return X_ERROR_NOT_LOGGED_ON;
|
return X_ERROR_NOT_LOGGED_ON;
|
||||||
}
|
}
|
||||||
|
|
@ -533,6 +549,8 @@ u32 XamUserAreUsersFriends_entry(u32 user_index, u32 unk1, u32 unk2, mapped_u32
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 XamShowSigninUI_entry(u32 unk, u32 unk_mask) {
|
u32 XamShowSigninUI_entry(u32 unk, u32 unk_mask) {
|
||||||
|
REXLOG_INFO("xam trace: XamShowSigninUI(unk={}, mask={}) - game asked for sign-in",
|
||||||
|
unk, unk_mask);
|
||||||
// Mask values vary. Probably matching user types? Local/remote?
|
// Mask values vary. Probably matching user types? Local/remote?
|
||||||
KernelState* kernel_state = REX_KERNEL_STATE();
|
KernelState* kernel_state = REX_KERNEL_STATE();
|
||||||
|
|
||||||
|
|
@ -540,6 +558,10 @@ u32 XamShowSigninUI_entry(u32 unk, u32 unk_mask) {
|
||||||
// profile has signed in; titles observe the UI notification and then continue
|
// profile has signed in; titles observe the UI notification and then continue
|
||||||
// along their signed-out path.
|
// along their signed-out path.
|
||||||
kernel_state->BroadcastNotification(0x00000009, 1);
|
kernel_state->BroadcastNotification(0x00000009, 1);
|
||||||
|
// netcode-revival: also report the sign-in as COMPLETED (Live) so the
|
||||||
|
// game's sign-in manager designates the live user; otherwise it waits for
|
||||||
|
// a real Live session that never comes.
|
||||||
|
kernel_state->BroadcastNotification(0x80000001, 0x200);
|
||||||
++xam_dialogs_shown_;
|
++xam_dialogs_shown_;
|
||||||
|
|
||||||
const uint32_t close_delay_ms = REXCVAR_GET(xam_signin_ui_auto_close_ms);
|
const uint32_t close_delay_ms = REXCVAR_GET(xam_signin_ui_auto_close_ms);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,10 @@ namespace xam {
|
||||||
|
|
||||||
REXCVAR_DEFINE_BOOL(user_profile_signed_in, true, "Kernel",
|
REXCVAR_DEFINE_BOOL(user_profile_signed_in, true, "Kernel",
|
||||||
"Whether user slot 0 starts with a local profile");
|
"Whether user slot 0 starts with a local profile");
|
||||||
REXCVAR_DEFINE_BOOL(user_live_signed_in, false, "Kernel",
|
REXCVAR_DEFINE_BOOL(user_live_signed_in, true, "Kernel",
|
||||||
"Whether user slot 0 starts signed in to Xbox Live");
|
"Whether user slot 0 starts signed in to Xbox Live (the "
|
||||||
|
"netcode revival needs the game to see LIVE sign-in so its "
|
||||||
|
"P2P session/invite flow opens)");
|
||||||
REXCVAR_DEFINE_STRING(selected_user_profile, "player", "Kernel/Profile",
|
REXCVAR_DEFINE_STRING(selected_user_profile, "player", "Kernel/Profile",
|
||||||
"Selected local Xbox profile id");
|
"Selected local Xbox profile id");
|
||||||
REXCVAR_DEFINE_STRING(user_profile_name, "Player", "Kernel/Profile",
|
REXCVAR_DEFINE_STRING(user_profile_name, "Player", "Kernel/Profile",
|
||||||
|
|
|
||||||
555
src/system/xnet_transport.cpp
Normal file
555
src/system/xnet_transport.cpp
Normal file
|
|
@ -0,0 +1,555 @@
|
||||||
|
/**
|
||||||
|
* 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/logging.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 {
|
||||||
|
|
||||||
|
// 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).
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// The receive loop must wake regularly so the heartbeat fires even with no
|
||||||
|
// inbound traffic (otherwise the relay sweeps a silent transport client
|
||||||
|
// after ~8s) and so StopLocked()'s join completes promptly instead of
|
||||||
|
// deadlocking SetRelay on a room change.
|
||||||
|
void SocketSetRecvTimeout(int64_t s, int timeout_ms) {
|
||||||
|
#if REX_PLATFORM_WIN32
|
||||||
|
DWORD tv = DWORD(timeout_ms);
|
||||||
|
::setsockopt(SOCKET(s), SOL_SOCKET, SO_RCVTIMEO,
|
||||||
|
reinterpret_cast<const char*>(&tv), sizeof(tv));
|
||||||
|
#else
|
||||||
|
timeval tv = {};
|
||||||
|
tv.tv_sec = timeout_ms / 1000;
|
||||||
|
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
||||||
|
::setsockopt(int(s), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||||
|
#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() {
|
||||||
|
StopThread(); // join outside the lock (see StopThread)
|
||||||
|
std::lock_guard<std::mutex> lock(mu_);
|
||||||
|
if (sock_ != -1) {
|
||||||
|
SocketClose(sock_);
|
||||||
|
sock_ = -1;
|
||||||
|
}
|
||||||
|
active_ = false;
|
||||||
|
joined_ = false;
|
||||||
|
sessions_.clear();
|
||||||
|
sockets_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
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::ForceLiveFlag() {
|
||||||
|
// The Skate 3 frontend gates "Xbox Live features" on an internal static
|
||||||
|
// byte (the game's own "live user" flag) and refuses WITHOUT calling any
|
||||||
|
// rexglue API when it is 0. The game sets it from its sign-in manager, but
|
||||||
|
// with no real Live the flag never becomes non-zero. Force it here.
|
||||||
|
// Address from the game: sub_8263E788 does lis r11,-32003 -> r11 =
|
||||||
|
// 0x83030000 (recompiler's -2097348608) then lbz at -30639 =>
|
||||||
|
// 0x82FC8851. (Fragile: tied to this title's build; revival only.)
|
||||||
|
uint8_t* flag = REX_KERNEL_MEMORY()->TranslateVirtual<uint8_t*>(0x82FC8851);
|
||||||
|
// Rate-limited status so we can SEE whether the address maps and its value.
|
||||||
|
static double last_log = 0.0;
|
||||||
|
const double now =
|
||||||
|
double(std::chrono::steady_clock::now().time_since_epoch().count()) / 1e9;
|
||||||
|
const bool rate = now - last_log >= 2.0;
|
||||||
|
if (rate) {
|
||||||
|
last_log = now;
|
||||||
|
REXLOG_INFO("xnet trace: live flag 0x82FC8851 {} value={}",
|
||||||
|
flag != nullptr ? "mapped" : "NOT MAPPED",
|
||||||
|
flag != nullptr ? int(*flag) : -1);
|
||||||
|
// Dump the party state machine's internal state (static party struct at
|
||||||
|
// 0x82FD9750): state@336, eaip@344, byte417 (EA flag), byte421 (online),
|
||||||
|
// byte423 (EA connected). Tells us exactly where the machine is stuck.
|
||||||
|
const auto rd8 = [](uint32_t a) -> int {
|
||||||
|
uint8_t* p = REX_KERNEL_MEMORY()->TranslateVirtual<uint8_t*>(a);
|
||||||
|
return p != nullptr ? int(*p) : -1;
|
||||||
|
};
|
||||||
|
const auto rd32 = [](uint32_t a) -> uint32_t {
|
||||||
|
uint32_t* p = REX_KERNEL_MEMORY()->TranslateVirtual<uint32_t*>(a);
|
||||||
|
return p != nullptr ? *p : 0xFFFFFFFFu;
|
||||||
|
};
|
||||||
|
REXLOG_INFO("xnet trace: party state={} eaip=0x{:08X} b417={} b421={} b423={}",
|
||||||
|
rd32(0x82FD98A0), rd32(0x82FD98A8), rd8(0x82FD98F1), rd8(0x82FD98F5),
|
||||||
|
rd8(0x82FD98F7));
|
||||||
|
// The frontend 'enter online flow' gate (sub_82703960) that leads to
|
||||||
|
// create-party: latch byte 0x830670C8 must be 0, and the frontend-mode
|
||||||
|
// byte at obj@*(0x8306707C)+194 must be non-zero.
|
||||||
|
uint32_t* mode_obj = REX_KERNEL_MEMORY()->TranslateVirtual<uint32_t*>(0x8306707C);
|
||||||
|
REXLOG_INFO("xnet trace: online latch={} mode_obj={} mode_byte={}", rd8(0x830670C8),
|
||||||
|
mode_obj != nullptr ? *mode_obj : 0,
|
||||||
|
(mode_obj != nullptr && *mode_obj != 0) ? rd8(*mode_obj + 194) : -1);
|
||||||
|
}
|
||||||
|
// The flag is the LIVE USER INDEX (not a boolean). The profile lives in
|
||||||
|
// slot 0, so force 0 (any slot answers after the shim fix, but 0 is the
|
||||||
|
// safest for index-into-live-array readers).
|
||||||
|
if (flag != nullptr && *flag != 0) {
|
||||||
|
if (!rate) {
|
||||||
|
REXLOG_INFO("xnet trace: forcing game live flag 0x82FC8851 = 0");
|
||||||
|
}
|
||||||
|
*flag = 0;
|
||||||
|
}
|
||||||
|
// The game also caches "live user exists" flags from its sign-in manager
|
||||||
|
// (set only when it processes a real signin notification, which never
|
||||||
|
// happens with no Live): 0x8308396C (=14700, 'live user exists') and
|
||||||
|
// 0x83084690 (=18064, checked by the frontend online-access controllers
|
||||||
|
// sub_826147C8 / sub_82614F38). Force both so Party Play ungreys.
|
||||||
|
const auto force_byte = [rate](uint32_t addr, const char* what) {
|
||||||
|
uint8_t* p = REX_KERNEL_MEMORY()->TranslateVirtual<uint8_t*>(addr);
|
||||||
|
if (p != nullptr && *p == 0) {
|
||||||
|
if (!rate) {
|
||||||
|
REXLOG_INFO("xnet trace: forcing game live cache {} = 1", what);
|
||||||
|
}
|
||||||
|
*p = 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
force_byte(0x8308396C, "0x8308396C (live user exists)");
|
||||||
|
force_byte(0x83084690, "0x83084690 (online access)");
|
||||||
|
// The party state machine (sub_82A1B310) can't leave state 1 unless the
|
||||||
|
// game's EA-Nation flag (party+417) is 1 - it is set at party-create from
|
||||||
|
// a config-string comparison that never matches in the recomp. The party
|
||||||
|
// struct is a static object at 0x82FD9750, so force its gate bytes:
|
||||||
|
// +417 = EA-Nation enabled (state 1 gate)
|
||||||
|
// +421 = online flag (set by the EA resolver alongside the online IP)
|
||||||
|
// +423 = 'EA connected' marker (state 6 target, unlocks advertise/join)
|
||||||
|
force_byte(0x82FD98F1, "party+417 (EA-Nation flag)");
|
||||||
|
force_byte(0x82FD98F5, "party+421 (online flag)");
|
||||||
|
force_byte(0x82FD98F7, "party+423 (EA connected)");
|
||||||
|
// The frontend 'enter online flow' gate (sub_82703960) blocks the path to
|
||||||
|
// create-party unless the online latch (0x830670C8) is 0 and the
|
||||||
|
// frontend-mode byte (obj@*(0x8306707C)+194) is non-zero. Force both.
|
||||||
|
{
|
||||||
|
uint8_t* latch = REX_KERNEL_MEMORY()->TranslateVirtual<uint8_t*>(0x830670C8);
|
||||||
|
if (latch != nullptr && *latch != 0) {
|
||||||
|
if (!rate) {
|
||||||
|
REXLOG_INFO("xnet trace: forcing online latch 0x830670C8 = 0");
|
||||||
|
}
|
||||||
|
*latch = 0;
|
||||||
|
}
|
||||||
|
uint32_t* mode_obj = REX_KERNEL_MEMORY()->TranslateVirtual<uint32_t*>(0x8306707C);
|
||||||
|
if (mode_obj != nullptr && *mode_obj != 0) {
|
||||||
|
uint8_t* mode = REX_KERNEL_MEMORY()->TranslateVirtual<uint8_t*>(*mode_obj + 194);
|
||||||
|
if (mode != nullptr && *mode == 0) {
|
||||||
|
if (!rate) {
|
||||||
|
REXLOG_INFO("xnet trace: forcing frontend mode byte = 1");
|
||||||
|
}
|
||||||
|
*mode = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Broadcast the Live signin-changed event a few times at startup so the
|
||||||
|
// game's sign-in manager runs and designates the profile as the live user
|
||||||
|
// (it only does this when it processes a real signin notification, which
|
||||||
|
// never happens with no Live). Param = (signin_state<<8)|user_index.
|
||||||
|
static uint32_t signin_broadcasts = 0;
|
||||||
|
if (signin_broadcasts < 6) {
|
||||||
|
REX_KERNEL_STATE()->BroadcastNotification(0x80000001, 0x200);
|
||||||
|
++signin_broadcasts;
|
||||||
|
REXLOG_INFO("xnet trace: broadcast Live signin-changed (0x80000001) #{}/6",
|
||||||
|
signin_broadcasts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XNetTransport::GateStatus XNetTransport::GetGateStatus() const {
|
||||||
|
GateStatus gs;
|
||||||
|
uint8_t* flag = REX_KERNEL_MEMORY()->TranslateVirtual<uint8_t*>(0x82FC8851);
|
||||||
|
gs.live_flag_mapped = flag != nullptr;
|
||||||
|
gs.live_flag_value = flag != nullptr ? *flag : 0;
|
||||||
|
const uint32_t state = REX_KERNEL_STATE()->user_profile()->signin_state();
|
||||||
|
const bool signed_in = REX_KERNEL_STATE()->user_profile()->is_signed_in();
|
||||||
|
for (int slot = 0; slot < 4; ++slot) {
|
||||||
|
gs.signin_state[slot] = state;
|
||||||
|
gs.signin_info_ok[slot] = signed_in;
|
||||||
|
}
|
||||||
|
gs.link_active = true; // XNetGetEthernetLinkStatus now reports active
|
||||||
|
gs.title_online = true; // XNetGetTitleXnAddr now reports 0x60
|
||||||
|
return gs;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
StopThread(); // join the old thread OUTSIDE the lock
|
||||||
|
Reconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
void XNetTransport::StopThread() {
|
||||||
|
std::thread old;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mu_);
|
||||||
|
running_ = false;
|
||||||
|
old = std::move(thread_);
|
||||||
|
thread_ = {};
|
||||||
|
}
|
||||||
|
if (old.joinable()) {
|
||||||
|
old.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void XNetTransport::Reconnect() {
|
||||||
|
std::lock_guard<std::mutex> lock(mu_);
|
||||||
|
if (host_.empty() || port_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sock_ != -1) {
|
||||||
|
SocketClose(sock_);
|
||||||
|
sock_ = -1;
|
||||||
|
}
|
||||||
|
active_ = false;
|
||||||
|
joined_ = false;
|
||||||
|
sessions_.clear();
|
||||||
|
relay_resolved_ = false;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
// Wake the receive loop every 200ms (heartbeat + prompt shutdown).
|
||||||
|
SocketSetRecvTimeout(sock_, 200);
|
||||||
|
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;
|
||||||
|
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));
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
// 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<const sockaddr_in*>(relay_addr_), join_pkt_,
|
||||||
|
join_pkt_size_);
|
||||||
|
last_join_ = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
REXLOG_INFO("xnet transport: relay {} (vip {}.{}.{}.{})", active_ ? "joined" : "refused",
|
||||||
|
(LocalVirtualIp() >> 24) & 0xFF, (LocalVirtualIp() >> 16) & 0xFF,
|
||||||
|
(LocalVirtualIp() >> 8) & 0xFF, LocalVirtualIp() & 0xFF);
|
||||||
|
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_;
|
||||||
|
REXLOG_INFO("xnet transport: session reply with {} session(s) in room '{}'", count,
|
||||||
|
room_.c_str());
|
||||||
|
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;
|
|
||||||
std::memcpy(buf, pkt->data, std::min((uint32_t)pkt->data_len, buf_len));
|
|
||||||
|
|
||||||
from->sin_family = 2;
|
|
||||||
from->sin_addr = pkt->src_ip;
|
|
||||||
from->sin_port = pkt->src_port;
|
|
||||||
|
|
||||||
incoming_packets_.pop();
|
incoming_packets_.pop();
|
||||||
uint8_t* pkt_ui8 = (uint8_t*)pkt;
|
const uint32_t data_len = pkt->data_len;
|
||||||
delete[] pkt_ui8;
|
const uint32_t n = std::min<uint32_t>(data_len, buf_len);
|
||||||
|
std::memcpy(buf, pkt->data, n);
|
||||||
return data_len;
|
if (from) {
|
||||||
|
from->sin_family = 2;
|
||||||
|
from->sin_addr = pkt->src_ip;
|
||||||
|
from->sin_port = pkt->src_port;
|
||||||
|
std::memset(from->x_sin_zero, 0, sizeof(from->x_sin_zero));
|
||||||
|
}
|
||||||
|
if (from_len) {
|
||||||
|
*from_len = sizeof(sockaddr_in);
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
|
|
||||||
|
|
@ -387,7 +387,8 @@ void ImGuiDrawer::SetupFonts() {
|
||||||
// DirectWrite white-on-black edge
|
// DirectWrite white-on-black edge
|
||||||
// profiles (identity was measurably thinner/dimmer). The *_on_light_
|
// profiles (identity was measurably thinner/dimmer). The *_on_light_
|
||||||
// variants below keep gamma 1.0 - dark-on-light needs no correction.
|
// variants below keep gamma 1.0 - dark-on-light needs no correction.
|
||||||
config.RasterizerGamma = 0.62f;
|
// (RasterizerGamma was removed from imgui's ImFontConfig; the modern
|
||||||
|
// rasterizer handles coverage gamma internally, so the knob is gone.)
|
||||||
config.FontLoaderFlags = ImGuiFreeTypeBuilderFlags_NoHinting;
|
config.FontLoaderFlags = ImGuiFreeTypeBuilderFlags_NoHinting;
|
||||||
ui_font_ = io.Fonts->AddFontFromMemoryCompressedBase85TTF(
|
ui_font_ = io.Fonts->AddFontFromMemoryCompressedBase85TTF(
|
||||||
GetInterRegularCompressedBase85(), 18.0f, &config);
|
GetInterRegularCompressedBase85(), 18.0f, &config);
|
||||||
|
|
@ -408,7 +409,7 @@ void ImGuiDrawer::SetupFonts() {
|
||||||
config_on_light.RasterizerMultiply = 1.0f;
|
config_on_light.RasterizerMultiply = 1.0f;
|
||||||
// Dark-on-light matches the browser with NO coverage curve (harness fit:
|
// Dark-on-light matches the browser with NO coverage curve (harness fit:
|
||||||
// identity beat every contrast/gamma variant once bake==draw size).
|
// identity beat every contrast/gamma variant once bake==draw size).
|
||||||
config_on_light.RasterizerGamma = 1.0f;
|
// config_on_light.RasterizerGamma = 1.0f; (removed from imgui)
|
||||||
ui_font_on_light_ = io.Fonts->AddFontFromMemoryCompressedBase85TTF(
|
ui_font_on_light_ = io.Fonts->AddFontFromMemoryCompressedBase85TTF(
|
||||||
GetInterRegularCompressedBase85(), 18.0f, &config_on_light);
|
GetInterRegularCompressedBase85(), 18.0f, &config_on_light);
|
||||||
ui_font_semibold_on_light_ = io.Fonts->AddFontFromMemoryCompressedBase85TTF(
|
ui_font_semibold_on_light_ = io.Fonts->AddFontFromMemoryCompressedBase85TTF(
|
||||||
|
|
|
||||||
2
thirdparty/imgui
vendored
2
thirdparty/imgui
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit cdda62349d6068e906c2e0ee340d6ec31eedbb9c
|
Subproject commit 0af8f475d1cf8b98d603702059c0c73deeb81ac2
|
||||||
Loading…
Add table
Reference in a new issue