#!/usr/bin/env python3 """Send the routes a real FIFA session used to BOTH backends and diff the replies. ./route-ab.py [python-host:port] [rust-host:port] WHY The gate 5-6 live session exercised 14 RPCs the recorded fixtures never covered -- Stats, Clubs, OSDKSettings, SponsoredEvents, Messaging::fetchMessages, Util::getTelemetryServer, the transport PING -- and every one took the empty-reply fallback. That Python does the same was an INFERENCE from reading its dispatch table. This turns it into evidence, using the routes FIFA actually sent. None of these RPCs read their request body (they are unhandled on both sides), so sending them with an empty payload exercises the same path the client did. Read-only: opens a client connection to each backend and sends requests. All routes here are unimplemented on both sides, so nothing mutates. """ import socket, struct, sys # (component, command, msg_type) observed in the live FIFA session. MESSAGE, PING = 0, 4 ROUTES = [ (0x0009, 0x0005, MESSAGE, "Util::getTelemetryServer"), (0x0009, 0x000C, MESSAGE, "Util::userSettingsLoadAll"), (0x0009, 0x001C, MESSAGE, "Util::setClientState"), (0x7802, 0x0008, MESSAGE, "UserSessions::cmd:0x0008"), (0x000F, 0x0002, MESSAGE, "Messaging::fetchMessages"), (0x000B, 0x0640, MESSAGE, "Clubs::cmd:0x0640"), (0x000B, 0x0A28, MESSAGE, "Clubs::cmd:0x0a28"), (0x08C9, 0x0001, MESSAGE, "OSDKSettings::cmd:0x0001"), (0x08C9, 0x0002, MESSAGE, "OSDKSettings::cmd:0x0002"), (0x0007, 0x0003, MESSAGE, "Stats::cmd:0x0003"), (0x0007, 0x000F, MESSAGE, "Stats::cmd:0x000f"), (0x0007, 0x0014, MESSAGE, "Stats::cmd:0x0014"), (0x081C, 0x0003, MESSAGE, "SponsoredEvents::cmd:0x0003"), (0x0000, 0x0000, PING, "transport PING"), ] def frame(comp, cmd, num, mtype, payload=b""): h = bytearray(16) struct.pack_into(">I", h, 0, len(payload)) struct.pack_into(">H", h, 4, 0) struct.pack_into(">H", h, 6, comp) struct.pack_into(">H", h, 8, cmd) h[10], h[11], h[12] = (num >> 16) & 0xFF, (num >> 8) & 0xFF, num & 0xFF h[13] = (mtype & 7) << 5 return bytes(h) + payload def ask(host, port, routes): s = socket.create_connection((host, port), timeout=10) s.settimeout(10) out, buf = [], b"" for i, (comp, cmd, mtype, name) in enumerate(routes): s.sendall(frame(comp, cmd, i + 1, mtype)) while len(buf) < 16: d = s.recv(65536) if not d: out.append((name, "NO REPLY")); s.close(); return out buf += d total = 16 + struct.unpack_from(">H", buf, 4)[0] + struct.unpack_from(">I", buf, 0)[0] while len(buf) < total: buf += s.recv(65536) out.append((name, buf[:total].hex())) buf = buf[total:] s.close() return out def endpoint(arg, default_port): if ":" in arg: h, p = arg.rsplit(":", 1) return h, int(p) return arg, default_port args = sys.argv[1:] PY_HOST, PY_PORT = endpoint(args[0], 42130) if len(args) > 0 else ("127.0.0.1", 42130) RS_HOST, RS_PORT = endpoint(args[1], 42230) if len(args) > 1 else ("127.0.0.1", 42230) print("python %s:%d rust %s:%d" % (PY_HOST, PY_PORT, RS_HOST, RS_PORT)) py = ask(PY_HOST, PY_PORT, ROUTES) rs = ask(RS_HOST, RS_PORT, ROUTES) bad = 0 for (n1, a), (n2, b) in zip(py, rs): same = (a == b) if not same: bad += 1 print(" %-32s %s" % (n1, "identical" if same else "DIFFERS\n py=%s\n rs=%s" % (a, b))) print("\n%d/%d identical" % (len(ROUTES) - bad, len(ROUTES))) sys.exit(1 if bad else 0)