diff --git a/src/kernel/xam/xam_net.cpp b/src/kernel/xam/xam_net.cpp index 01cbfc2..f19fd2a 100644 --- a/src/kernel/xam/xam_net.cpp +++ b/src/kernel/xam/xam_net.cpp @@ -125,6 +125,57 @@ struct XWSAOVERLAPPED { rex::be 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); + } + id.virtual_ip = (10u << 24) | (80u << 16) | (0u << 8) | id.machine_id; + 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) { out_addr->sa_family = memory::load_and_swap(ptr + 0); switch (out_addr->sa_family) { @@ -433,26 +484,15 @@ struct XnAddrStatus { }; u32 NetDll_XNetGetTitleXnAddr_entry(u32 caller, ppc_ptr_t addr_ptr) { - // Just return a loopback address atm. - addr_ptr->ina.s_addr = htonl(INADDR_LOOPBACK); + // netcode-revival: return this machine's stable virtual identity (10.80.0.x + // + a unique abEnet) so RakNet gets a non-zero, per-machine GUID seed and + // the game's address resolution has a consistent LAN-class address. + const XnetIdentity& id = XnetLocalIdentity(); + addr_ptr->ina.s_addr = htonl(id.virtual_ip); addr_ptr->inaOnline.s_addr = 0; addr_ptr->wPortOnline = 0; - - // 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::memcpy(addr_ptr->abEnet, id.ab_enet, 6); std::memset(addr_ptr->abOnline, 0, 20); - return XnAddrStatus::XNET_GET_XNADDR_STATIC; } @@ -464,33 +504,57 @@ u32 NetDll_XNetGetDebugXnAddr_entry(u32 caller, ppc_ptr_t addr_ptr) { } u32 NetDll_XNetXnAddrToMachineId_entry(u32 caller, ppc_ptr_t addr_ptr, mapped_u32 id_ptr) { - // Tell the caller we're not signed in to live (non-zero ret) - return 1; + // netcode-revival: report this machine's stable machine id instead of "not + // 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, 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 -// 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 xn_addr, mapped_void xid, 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. -// 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 xn_addr, 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 -// 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) { - return 1; + (void)port; + return 0; } // https://github.com/ILOVEPIE/Cxbx-Reloaded/blob/master/src/CxbxKrnl/EmuXOnline.h#L39 @@ -556,7 +620,130 @@ u32 NetDll_XNetQosRelease_entry(u32 caller, ppc_ptr_t qos) { u32 NetDll_XNetQosListen_entry(u32 caller, mapped_void id, mapped_void data, u32 data_size, u32 r7, 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; + return 0; +} + +// netcode-revival: session search. Phase 0 reports zero discovered sessions +// (the relay registry lands in Phase 1) but returns success and signals the +// completion event so the game's search finishes instead of hanging. +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)dwTimeoutMs; + (void)dwFlags; + if (apxnqosResults) { + auto qos_guest = REX_KERNEL_MEMORY()->SystemHeapAlloc(sizeof(XNQOS)); + auto qos = REX_KERNEL_MEMORY()->TranslateVirtual(qos_guest); + qos->count = qos->count_pending = 0; + *apxnqosResults = qos_guest; + } + if (hEvent) { + auto ev = REX_KERNEL_OBJECTS()->LookupObject(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; + } + uint8_t* out = static_cast(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) { + (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 xn_addr) { + (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 xn_addr, mapped_void status_ptr) { + (void)xn_addr; + if (status_ptr) { + const uint32_t connected = 2; // XNET_CONNECT_STATUS_CONNECTED + std::memcpy(status_ptr, &connected, sizeof(connected)); + } + return 0; +} + +// INT XNetServerToInAddr(const XNADDR* pxn, IN_ADDR* pina, DWORD dwFlags) +// netcode-revival: our server addresses also carry the virtual IP in ina. +u32 NetDll_XNetServerToInAddr_entry(u32 caller, ppc_ptr_t pxn, mapped_void pina, + u32 dwFlags) { + (void)dwFlags; + if (!pxn || !pina) { + return X_ERROR_INVALID_PARAMETER; + } + uint32_t ip = pxn->ina.s_addr; + 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(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(static_cast(optval_ptr)); + int ret = getsockopt(int(socket->native_handle()), int(level), int(optname), optval_host, + reinterpret_cast(&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) { @@ -1027,25 +1214,25 @@ REX_EXPORT_STUB(__imp__NetDll_XHttpSetStatusCallback); REX_EXPORT_STUB(__imp__NetDll_XHttpShutdown); REX_EXPORT_STUB(__imp__NetDll_XHttpStartup); REX_EXPORT_STUB(__imp__NetDll_XHttpWriteData); -REX_EXPORT_STUB(__imp__NetDll_XNetConnect); -REX_EXPORT_STUB(__imp__NetDll_XNetCreateKey); +REX_EXPORT(__imp__NetDll_XNetConnect, rex::kernel::xam::NetDll_XNetConnect_entry) +REX_EXPORT(__imp__NetDll_XNetCreateKey, rex::kernel::xam::NetDll_XNetCreateKey_entry) REX_EXPORT_STUB(__imp__NetDll_XNetDnsReverseLookup); REX_EXPORT_STUB(__imp__NetDll_XNetDnsReverseRelease); 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_XNetGetXnAddrPlatform); REX_EXPORT_STUB(__imp__NetDll_XNetInAddrToServer); REX_EXPORT_STUB(__imp__NetDll_XNetQosGetListenStats); -REX_EXPORT_STUB(__imp__NetDll_XNetQosLookup); -REX_EXPORT_STUB(__imp__NetDll_XNetRegisterKey); +REX_EXPORT(__imp__NetDll_XNetQosLookup, rex::kernel::xam::NetDll_XNetQosLookup_entry) +REX_EXPORT(__imp__NetDll_XNetRegisterKey, rex::kernel::xam::NetDll_XNetRegisterKey_entry) 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_XNetStartupEx); REX_EXPORT_STUB(__imp__NetDll_XNetTsAddrToInAddr); 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_XmlDownloadGetParseTime); REX_EXPORT_STUB(__imp__NetDll_XmlDownloadGetReceivedDataSize); @@ -1096,4 +1283,4 @@ REX_EXPORT_STUB(__imp__NetDll_XnpUnregisterKeyForCallerType); REX_EXPORT_STUB(__imp__NetDll_XnpUpdateConfigParams); REX_EXPORT_STUB(__imp__NetDll_getpeername); REX_EXPORT_STUB(__imp__NetDll_getsockname); -REX_EXPORT_STUB(__imp__NetDll_getsockopt); +REX_EXPORT(__imp__NetDll_getsockopt, rex::kernel::xam::NetDll_getsockopt_entry)