4.5 KiB
Netcode revival (branch netcode-revival)
Goal: revive Skate 3's original Xbox 360 multiplayer — the game's own
RakNet-based peer-to-peer netcode — instead of only the ghost co-presence
layer. main keeps the working ghost multiplayer + releases; this branch
carries the netcode work and stays clearly separate.
The transport is RakNet over XNet/XSocket (confirmed: rexglue's XNet
comments reference RakNet, and the game's socket → setsockopt → bind → sendto sequence matches RakPeer::Startup). The game speaks its own
protocol; we only need the platform netcode layer to work (implement
rexglue's stubbed XNet session APIs + route the game's UDP through the
relay). We do not decode the game protocol.
Architecture
- Virtual addressing: each machine gets a stable
XNADDR(virtual IP10.80.0.x+ unique 6-byteabEnet→ RakNet GUID).XNetXnAddrToInAddr/XNetServerToInAddrresolve those into the socket layer's address space. - Relay = P2P stabilizer: session UDP is forwarded outbound through the
relay (tagged
gamevsghost) to the destination member's real socket; on a LAN the transport can go direct. The server stabilizes NAT/remote play but is not required on a LAN. - Session registry in the relay:
XNetQosListenadvertises a session;XNetQosLookupreturns discovered sessions; the lobby/room-code UI stays as the manual join path. - Ghost layer is kept as the guaranteed fallback (co-presence that works for everyone even if the netcode path fails).
Phase 0 — XNet session/address layer (done, rexglue 767bd6e)
Implemented in rexglue-sdk/src/kernel/xam/xam_net.cpp:
XNetGetTitleXnAddr: stable virtual identity (was loopback +0xCCMAC).XNetXnAddrToInAddr/XNetInAddrToXnAddr/XNetServerToInAddr/XNetXnAddrToMachineId/XNetInAddrToString: resolve virtual addresses (were failure /666.666.666.666).XNetQosListen: accept advertisement (wasX_ERROR_FUNCTION_FAILED).XNetQosLookup: success + emptyXNQOS+ completion signal (Phase 1 fills it from the relay registry).XNetCreateKey/XNetRegisterKey/XNetUnregisterKey: session keys.XNetConnect/XNetGetConnectStatus: report established.getsockopt: real basic reads off the native socket.XNetSetSystemLinkPort: accept.
Phase 1 — relay game-transport + session registry (done, built)
- v5 relay:
kTypeGame(opaque game UDP, unicast or room-broadcast) +kTypeSessionQuery/Reply(discovery). JoinPayload carries the virtual IP. - rexglue
XNetTransport: joins the same relay + room as the ghost client, routessendtoto10.80.0.xpeers through the relay, injects inbound game datagrams into the game's sockets, and serves session discovery toXNetQosLookup. - Wire structs byte-verified against the game's proto (sizes 7/31/1410/25/801)
with
static_asserts on both sides.
Test recipe (two instances on one machine, local relay)
- Build:
cmake --build out/build/linux-relwithdebinfo --target ghost_relay skate3 -j$(nproc) - Start the local relay:
./out/build/linux-relwithdebinfo/ghost_relay --port 40001 - Launch instance A:
./out/build/linux-relwithdebinfo/skate3Launch instance B (same machine; the relay loops them via 127.0.0.1). - In each instance's M menu: set
skate3_ghost_server = 127.0.0.1, enable multiplayer, join the SAME room code. The relay then routes both the ghost poses and the game's own netcode traffic. - Verify in the log (stdout):
xnet transport: connecting to relay ...,xnet transport: relay joined, and on session searchxnet transport: session reply with N session(s). Enable the game's online flow (invite/join) and check whether RakNet establishes a session. - Two instances bind the same UDP data port → the second may fail to bind. If so, the game's socket bind must become relay-aware (bind to an ephemeral port in relay mode) — a known Phase 3 item.
Phase 2 — invite UI
XamShowGameInviteUI/XamShowFriendsUIas ImGuiXamDialogs backed by the relay lobby (real player list). Receive side (XamNotifyCreateListener/XamSessionRefObjByHandle) already works.
Phase 3 — iterate to playable
Whatever the game needs past discovery (RakNet handshake, ports, privilege fakes). Ghost stays as fallback throughout.
Notes
- Testing is done with two instances on one machine (no second device);
the relay loops them via
127.0.0.1. A headless harness may be added. - Build:
clang-22override, presetlinux-relwithdebinfo, perdocs/build.md— same asmain.