Skip to main content
Live-tested findings on this page were confirmed against production on 2026-07-03, with the reconnect/collection path re-confirmed still working in 2026-08 (including with the shipped placeholder ta sub-fields), using client identity com.lastwar.ios v1.0.344 (build 786) for the reconnect/collection path and com.fun.lastwar.gp v1.0.351 for the bootstrap/guest-login path. This documents an unofficial third-party game server’s behavior, which the operator can change at any time without notice — treat anything marked Confirmed or Resolved on this page as true as of that date, not a permanent guarantee. If a command, field, or error code described here stops matching reality, re-verify against a fresh packet capture before assuming this documentation is still correct.
The client bundles the real SmartFoxServer 2X SDK but never actually uses its socket layer. Every byte on the wire is written by a hand-rolled reimplementation that only reuses SFS2X’s data model — and the “encryption” bit is a length-derived XOR, not a cipher.

Transport

Plain TCP socket, no TLS. Host/port/zone/connection-type arrive dynamically from the GSL response. Two connection types exist:
  • connectionType 0 — raw TCP, straight into the packet framing below.
  • connectionType 1 — a one-shot literal HTTP/1.1 Upgrade: websocket preamble (with a hardcoded, non-random Sec-WebSocket-Key; only the HTTP status line is checked on response — the Sec-WebSocket-Accept challenge/response is never validated) is sent first, purely to satisfy load balancers that require a valid HTTP handshake — then the exact same raw SFS2X binary framing runs on the same socket. This is not a real WebSocket: there’s no RFC 6455 frame masking anywhere in the read/write path. A Go client can dial TCP directly, or replay the literal handshake bytes if a load balancer demands them.

Packet envelope — three nested layers

For every gameplay cmd, the "p" content is itself another SFSObject:
So a full mail.read request is schematically {c:1, a:13, p:{c:"mail.read", r:-1, p:{uid:..., type:..., toUser:..., _id:42}}}.

Packet header bits

The “encryption” is not encryption
The XOR keystream is derived entirely from the packet’s own (cleartext) length field. No secret material, no negotiation, no per-session state. This is obfuscation, not confidentiality, and needs zero cryptographic material to implement in Go — just XOR the body against the little-endian length, computed after compression.

SFSObject binary format

Big-endian throughout. A standalone serialized object is self-describing — it starts with its own tag byte and count. BIG_NUM, present in some SFS2X SDK builds, doesn’t exist in this game’s enum at all — no gap in the numbering. Not needed.

Validated against the official client — sfs2x-api (npm)

SmartFoxServer publishes its own JS client SDK (npm install sfs2x-api — the official reference implementation, not a third-party reimplementation). Pulling it and diffing its packet/codec internals against everything above turned into a genuinely useful correctness check:
Checked against the official SDKResult
SFSObject type tags (0 NULL … 20 TEXT, full range)Exact match
Envelope shape {c: BYTE, a: SHORT, p: SFS_OBJECT}Exact match
Action ids — Login=1, PingPong=29Exact match
zlib compression, header bits 0x80/0x20/0x08Exact match
bigSized threshold specificallyCorrection, not a match — the JS SDK’s own IoHandler.onPacketWrite uses 65335, which this dossier briefly (and wrongly) adopted as a “fix.” The actual reference for this game is the Unity SFS2X SDK it embeds (Smartfox2xLw.decompiled.cs:13563), which uses the standard 65535 — the JS client’s 65335 is an idiosyncratic quirk of that specific (different) implementation, not a shared protocol constant. Reverted after a later audit pass caught the discrepancy; a good reminder that “official” only means authoritative for the exact client it ships with.
Encryption flag (0x40) / XOR obfuscationRefined — the bit itself is real vanilla SFS2X: the Unity SDK sets it exactly when a session-negotiated CryptoKey (a real AES key+IV pair, via Handshake) is present (bitSwarm.CryptoKey != null, Smartfox2xLw.decompiled.cs:13557). This game never negotiates a CryptoKey at all, yet the server always sets 0x40 anyway and expects the length-derived XOR instead of real AES — a deliberate server-side override of the vanilla semantics, not evidence the bit is undocumented. The JS SDK’s read/write path (which never sets or checks this bit) simply never implements the CryptoKey path at all, consistent with this reading.
0x10 LZ4-repurposed-as-Zstandard flagConfirmed game-specific — same story, absent from the official client entirely
The official SDK’s connection sequence also revealed a real gap: it sends a Handshake request (action 0, fields api/cl, a step this dossier previously called out below as unused) before every Login, receiving back a session token / max-message-size / compression-threshold. Sending it against this game’s real server got a clean, correctly-shaped response — {ct=3072, ms=1000000, tk=<32-hex>} — so the server-side handshake handler is real and working, contradicting the “never instantiated” framing below at least on the server side (whether the shipped Android/iOS client itself exercises this path wasn’t re-confirmed). Practically, though, adding it to the Go client changed nothing: at the time this was tested, the init-push and reconnect-block problems were still unresolved, and this Handshake step made no difference to either — guest login and email-bind behaved identically with or without it. Both problems have since been resolved by unrelated fixes (a Zstd decompression bug and a token-identity mismatch, respectively — see Live validation against production), confirming the Handshake step was never the missing piece.

Heartbeat & reconnect

A PingPongRequest (system controller, action 29, payload {clientTime}) fires every 4000 ms. Client-perceived timeout is 12s since the last pong, at which point the client disconnects and re-runs the entire connect→login sequence — there is no lightweight session-resume. No exponential backoff was found; retry cadence is a fixed timer.
What’s genuinely unused — partially revised, see aboveSFS2X’s SDK ships a full Handshake request (session token negotiation + a real AES CryptoKey, via DefaultPacketEncrypter) and a complete MMORoom/AOI (area-of-interest) streaming subsystem. The MMORoom/AOI claim stands untouched. The Handshake claim needs an asterisk: this game’s own decompiled SDK (Smartfox2xLw.decompiled.cs:4363, SendHandshakeRequest) does contain a working implementation, called from the socket’s own OnSocketConnect — and the live server answers it correctly when sent (see above). Whether LoginState’s actual connect path invokes that code or bypasses it in favor of jumping straight to LoginMessage.Send (as this dossier’s original read of LoginState.OnEnter suggested) remains unsettled; either way, a reimplementation can skip it — it demonstrably makes no difference to anything tested live. The client reimplements its own point-id-addressed query model on top of plain extension requests instead of AOI streaming (see World map).