netcode-revival: fix the EA-connect gate - XNetGetConnectStatus returns 2
Some checks are pending
Lint & Format Check / Format Check (push) Waiting to run

The party state machine (sub_82A1B310 state 5) reads the RETURN VALUE of
XNetGetConnectStatus and only advances to 'EA connected' (party[423]=1, which
unlocks session advertise/join) when it equals 2. rexglue returned 0, so the
game stalled on 'Cannot Connect to EA Servers' forever despite the pointer
write. Also:

- XNetDnsLookup now actually resolves hostnames (getaddrinfo) into XNDNS
  (status=0, cina, aina[0..]) instead of always erroring - the EA resolver
  (sub_8298A460) busy-waits on it.
- XNetServerToInAddr ABI fixed: the game calls (caller, server_id,
  service_id, out), not (caller, XNADDR*, out, flags); returns the virtual
  IP (the party flow fakes EA connectivity via Connect/GetConnectStatus).
This commit is contained in:
MiTHRAL 2026-08-28 22:23:34 -04:00
parent 3016f14fe8
commit 6db6674892

View file

@ -37,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>
@ -602,13 +603,42 @@ u32 NetDll_XNetGetEthernetLinkStatus_entry(u32 caller) {
} }
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);
@ -784,6 +814,10 @@ u32 NetDll_XNetConnect_entry(u32 caller, ppc_ptr_t<XNADDR> xn_addr) {
// https://learn.microsoft.com/en-us/previous-versions/windows/desktop/xna/xnetgetconnectstatus // https://learn.microsoft.com/en-us/previous-versions/windows/desktop/xna/xnetgetconnectstatus
// INT XNetGetConnectStatus(const XNADDR* pxna, INT* pxncs) // INT XNetGetConnectStatus(const XNADDR* pxna, INT* pxncs)
u32 NetDll_XNetGetConnectStatus_entry(u32 caller, ppc_ptr_t<XNADDR> xn_addr, mapped_void status_ptr) { 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()) { if (XnetTrace()) {
REXLOG_INFO("xnet trace: XNetGetConnectStatus(caller={}) -> CONNECTED", caller); REXLOG_INFO("xnet trace: XNetGetConnectStatus(caller={}) -> CONNECTED", caller);
} }
@ -792,18 +826,27 @@ u32 NetDll_XNetGetConnectStatus_entry(u32 caller, ppc_ptr_t<XNADDR> xn_addr, map
const uint32_t connected = 2; // XNET_CONNECT_STATUS_CONNECTED const uint32_t connected = 2; // XNET_CONNECT_STATUS_CONNECTED
std::memcpy(status_ptr, &connected, sizeof(connected)); std::memcpy(status_ptr, &connected, sizeof(connected));
} }
return 0; return 2;
} }
// INT XNetServerToInAddr(const XNADDR* pxn, IN_ADDR* pina, DWORD dwFlags) // The game calls XNetServerToInAddr(caller=1, server_id, service_id, out)
// netcode-revival: our server addresses also carry the virtual IP in ina. // (sub_82F38580 maps r3/r4/r5 accordingly). netcode-revival: return our
u32 NetDll_XNetServerToInAddr_entry(u32 caller, ppc_ptr_t<XNADDR> pxn, mapped_void pina, // virtual IP (the machine's online identity); the party flow fakes EA
u32 dwFlags) { // connectivity via XNetConnect/XNetGetConnectStatus, so any stable address
(void)dwFlags; // works here.
if (!pxn || !pina) { 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; return X_ERROR_INVALID_PARAMETER;
} }
uint32_t ip = pxn->ina.s_addr; 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)); std::memcpy(pina, &ip, sizeof(ip));
return 0; return 0;
} }