Deep-dive reverse engineering of the binary sproto protocol used by Whiteout Survival v1.30.28 by Century Game. Covering packet structure, player data encoding, authentication flows, and security observations.
This investigation reverse-engineers the network protocol used by Whiteout Survival (WOS)
version 1.30.28, developed by Century Game. The game communicates using a
binary sproto protocol over raw TCP (port 30101) for game state and a separate
FPNN/msgpack protocol over TCP (port 13321) for chat functionality.
Binary sproto over raw TCP. Unencrypted. Port 30101. Contains all player data including power.
FPNN with msgpack encoding over TCP. Port 13321. Rich metadata: VIP, alliance, kingdom.
HTTPS only for login, GM API, and platform config. No game-state query endpoints exposed.
Whiteout Survival uses a multi-server architecture with distinct protocols for different functions. The primary game protocol is sproto (a lightweight binary serialization format from the Cloud Skynet framework), while chat uses FPNN (Fast Private Network Namespace).
gof-login-formal-ga.centurygame.com:30101
Sproto/TCP Unencrypted
Primary game protocol. Player state, power, alliance, world map data.
rtm-intl-frontgate.ilivedata.com:13321
FPNN/Msgpack/TCP
Real-time messaging with rich player metadata (VIP, alliance, kingdom).
gof-gm-api-formal-ga.centurygame.com:443
HTTPS
Game Master API. Administrative functions over TLS.
passport-apa.centurygame.com:443
HTTPS
Account authentication and session creation.
platform-config-prod.centurygame.com:443
HTTPS
Configuration distribution, feature flags, server lists.
35.71.149.13 (AWS ap-northeast-1)
and the chat server to 35.156.52.69 (AWS eu-central-1). Century Game uses AWS global infrastructure
with region-specific deployments (ga = Global All?).
┌──────────┬──────────┬──────────┬──────────────────────┐
│ Total Len│ Type │ Session │ Body │
│ 2 bytes │ 2 bytes │ 2 bytes │ (total_len - 6) B │
│ BE uint16│ BE uint16│ BE uint16│ sproto-encoded │
└──────────┴──────────┴──────────┴──────────────────────┘
Offset Size Field Description
─────── ──── ────────── ─────────────────────────────
0x00 2 total_len Total packet length (big-endian)
0x02 2 type Message type identifier (big-endian)
0x04 2 session Session/sequence number (big-endian)
0x06 N body Sproto-encoded payload
The server may send data in packed (zero-byte compressed) or raw format. Packed mode replaces consecutive zero bytes with a length count, reducing wire size for sparse structures.
| Type ID | Hex | Direction | Purpose |
|---|---|---|---|
0x5502 |
5502 | Server → Client | Main Response — Contains PLAYER PROFILES WITH POWER data. This is the primary type for leaderboard and player info. |
0x1D01 |
1D01 | Server → Client | Server Push — State updates, event notifications, resource changes. |
0x5D02 |
5D02 | Client → Server | Client Request — Player actions, queries, and commands. |
0x5D01 |
5D01 | Server → Client | Timer/Data Push — Periodic data updates, timers, scheduled events. |
0x0D01 |
0D01 | Bidirectional | Acknowledge — Message receipt confirmation. |
0x7104 |
7104 | Server → Client | Alliance/Map Data — Alliance information, world map updates, territory data. |
0x1502 |
1502 | Server → Client | Heartbeat Response — Keep-alive pong from server. |
0x1501 |
1501 | Client → Server | Heartbeat Request — Keep-alive ping from client. |
0x4209 |
4209 | Server → Client | Large Data Push — Bulk data transfers (map tiles, rankings, etc.). |
0x5502 is the goldmine — it carries complete player profiles
including power values. When a player views the leaderboard or another player's profile, this message type
contains the full data payload.
The login packet is the first message sent after establishing a TCP connection to the game server. It has been verified through PCAP analysis and contains session authentication, player identification, device fingerprinting, and version information.
[2B total_len] [2B type=0x5502] [2B session=0x0404] [body]
Body Layout:
─────────────────────────────────────────────────────────────
Offset Marker Content
──────── ───────────────── ─────────────────────────────────
+0 15 bytes Sproto header tags (field descriptors)
+15 0x32 0xff 0x05 Token marker + "2" prefix
+ string Session token (from login API)
+N 0xc7 0x72 0x34 Separator bytes
0x0a 0x31 0x31
+M 0xff 0x00 Player ID marker
+ string Player ID as decimal string
+P 0xf1 0x07 Version major marker
+ string Version major number
0x8f Version separator
+ string Version minor number
+Q 0x11 0x58 Device marker
0xff 0x01 Device model marker
+ string Device model string
+R 0xf1 0x07 Platform marker
+ "andr" Platform: "andr"
0x8f Platform separator
+ "oid" Platform: "oid" → "android"
+S 0x0e 0x31 IP marker
0xff 0x01 IP value marker
+ string IP address string
+T 0x04 0xf8 Language marker
+ string Language code (e.g., "en")
+U various Multiple MD5 hash fingerprint fields
─────────────────────────────────────────────────────────────
All field encodings below have been verified against known player data cross-referenced with in-game screenshots and leaderboard observations.
| Field | Encoding | Marker | Range / Format |
|---|---|---|---|
| Power | LE4 (little-endian uint32) | — | 10,000,000 to 2,000,000,000+ |
| Player ID | LE4 (little-endian uint32) | — | 10,000,000 to 500,000,000 |
| Alliance | 2-5 char ASCII string | 0xf1 0x03 |
Alliance abbreviation (e.g., "POL", "IRS") |
| Kingdom | LE4 composite uint32 | 0xf1 0x04 |
Divide by 1,000,000 for kingdom number |
| Name | Variable-length string | 0x04 0x1f |
UTF-8 player display name |
| Language | 2-char ASCII string | — | ISO 639-1 code (e.g., "en", "zh") |
| Avatar | Path string | — | Starts with /202X/ (e.g., "/2024/avatars/...") |
| Power | 762,043,293 |
| Power (hex) | 0x2D6BDB9D (LE) |
| Alliance | POL |
| Player ID | 288,979,040 |
| Kingdom | 2007 |
| Power | 973,248,215 |
| Alliance | IRS |
| Kingdom | — |
| Player ID | — |
| Power | 858,363,844 |
| Alliance | IRS |
| Kingdom | — |
| Player ID | — |
Decimal: 762,043,293
Hex (BE): 0x2D6BDB9D
Hex (LE): 9D DB 6B 2D ← as transmitted on wire
Byte breakdown:
Offset+0: 0x9D (least significant byte)
Offset+1: 0xDB
Offset+2: 0x6B
Offset+3: 0x2D (most significant byte)
Python decode:
power = int.from_bytes(data[offset:offset+4], byteorder='little')
Kingdom number: 2007
Encoded value: 2,007,000,000 (kingdom_num * 1,000,000)
Hex (LE): 00 84 B6 77
Decode:
raw_value = int.from_bytes(data[offset:offset+4], byteorder='little')
kingdom = raw_value // 1_000_000
The chat system uses FPNN (Fast Private Network Namespace), a protocol developed by iLiveData. Messages are encoded using msgpack serialization over TCP. This is the easiest way to obtain VIP, alliance, and kingdom data — though it does not include player power.
┌─────────┬─────────┬────────┬──────────┬──────────┬──────────────┬─────────────┐
│ Magic │ Version │ Flags │ Msg Type │ Seq ID │ Payload Len │ Payload │
│ 4 bytes │ 1 byte │ 1 byte │ 2 bytes │ 4 bytes │ 4 bytes │ N msgpack │
│ "FPNN" │ │ │ │ │ │ │
└─────────┴─────────┴────────┴──────────┴──────────┴──────────────┴─────────────┘
Offset Size Field Description
─────── ──── ────────── ──────────────────────────────
0x00 4 magic ASCII "FPNN" (0x46504E4E)
0x04 1 version Protocol version
0x05 1 flags Connection flags
0x06 2 msg_type Message type (big-endian)
0x08 4 seq Sequence number (big-endian)
0x0C 4 payload_len Payload length in bytes (big-endian)
0x10 N payload Msgpack-encoded data
method: "auth"
params: {
pid: <player_id>,
uid: <user_id>,
token: <chat_token>,
version: <client_version>
}
The chat token follows a specific format that is returned during the login process:
Format: "YCA" + 64 hex characters
Example: "YCA3a2f1b4c5d6e7..."
Length: 67 characters total
Prefix: "YCA" (3 bytes)
Hex: 64 characters (32 bytes when decoded)
Chat messages contain JSON payloads with rich player metadata:
{
"nickName": "PlayerName", // Display name
"vip": 15, // VIP level (integer)
"show_vip": true, // Whether VIP badge is shown
"kid": 2007, // Kingdom ID
"abbr": "POL", // Alliance abbreviation
"uid": 288979040, // Player ID
"rank": 42, // Kingdom rank
"point": 762043293 // POWER VALUE (same as sproto power)
}
point field in chat messages contains the same power value
as the sproto protocol! While the chat server requires FPNN authentication, the JSON payload is far easier
to parse than binary sproto.
The complete authentication sequence involves four distinct steps, each connecting to a different server with a different protocol. The flow must be completed in order.
passport-apa.centurygame.comgof-login-formal-ga.centurygame.com35.71.149.13:3010135.156.52.69:13321
──────────────────────────────────────────────────────────────────
STEP 1: Account Authentication (HTTPS)
──────────────────────────────────────────────────────────────────
POST https://passport-apa.centurygame.com/...
→ Submits account credentials (email/OAuth)
← Returns account token and player list
──────────────────────────────────────────────────────────────────
STEP 2: Session Creation (HTTPS)
──────────────────────────────────────────────────────────────────
POST https://gof-login-formal-ga.centurygame.com/...
→ Submits account token + player selection
← Returns: session_token, chat_token, server IP
──────────────────────────────────────────────────────────────────
STEP 3: Game Server Login (TCP/Sproto)
──────────────────────────────────────────────────────────────────
TCP connect to 35.71.149.13:30101
→ Send sproto login packet (type 0x5502)
- session_token from Step 2
- player_id (decimal string)
- device info, version, fingerprints
← Receive login response + initial state push
← Server begins sending heartbeat cycle (type 0x1502)
──────────────────────────────────────────────────────────────────
STEP 4: Chat Server Auth (TCP/FPNN)
──────────────────────────────────────────────────────────────────
TCP connect to 35.156.52.69:13321
→ Send FPNN "auth" method
- pid: player_id
- uid: user_id
- token: "YCA" + 64 hex chars (from Step 2)
- version: client version
← Receive auth confirmation
← Server begins sending chat messages as msgpack
Three methods exist for obtaining player data, each with different trade-offs:
Offline Full Data
Live Full Data
Live Partial Data
# Method 1: Analyze a PCAP capture file
python3 wos_sproto_client_v6.py --pcap capture.pcap --pid 288979040
# Method 2: Live connection with session token
python3 wos_sproto_client_v6.py --token "SESSION_TOKEN" --pid 288979040
# Method 2+3: Live sproto + chat server combined
python3 wos_sproto_client_v6.py \
--token "SESSION_TOKEN" \
--pid 288979040 \
--chat \
--chat-pid 313621 \
--chat-uid 1086539 \
--chat-token "YCA..."
| Argument | Description | Required |
|---|---|---|
--pcap | Path to PCAP capture file for offline analysis | For Method 1 |
--token | Session token from login API | For Method 2 |
--pid | Player ID to look up | Yes |
--chat | Enable FPNN chat connection | Optional |
--chat-pid | Chat server player ID | With --chat |
--chat-uid | Chat server user ID | With --chat |
--chat-token | Chat authentication token (YCA...) | With --chat |
The game server on port 30101 uses plain TCP with no TLS.
All game data — including player power, alliance info, kingdom data, and session tokens —
is transmitted in cleartext and can be intercepted on the network.
Chat messages contain sensitive player metadata in plain JSON within the msgpack payload. VIP levels, alliance tags, kingdom IDs, and rank information are all visible to anyone with network access.
Session tokens follow a predictable format and structure, making them potentially susceptible to forgery or replay attacks if the token generation algorithm is understood.
The protocol uses MD5-based signing with truncated hashes. MD5 is cryptographically broken and should not be used for security-critical signing operations. Truncation further reduces the hash's collision resistance.
The following credentials and keys were observed during the investigation:
| Type | Value | Source |
|---|---|---|
| GitLab Token | glpat-ni86rqoefyhoTkMsNYiR |
APK configuration |
| App Key | c37aba497f0fc9e6357a4b0d32b45e2f |
CGSettings in APK |
Player power data and other game state information is fully interceptable by any party with access to the network path between client and server. This includes:
| Domain | IP Address | Port | Protocol | Purpose |
|---|---|---|---|---|
gof-login-formal-ga.centurygame.com |
35.71.149.13 | 30101 | Sproto/TCP | Game server (login, state, player data) |
rtm-intl-frontgate.ilivedata.com |
35.156.52.69 | 13321 | FPNN/Msgpack/TCP | Chat server (real-time messaging) |
gof-gm-api-formal-ga.centurygame.com |
— | 443 | HTTPS/TLS | Game Master API (admin functions) |
passport-apa.centurygame.com |
— | 443 | HTTPS/TLS | Account authentication service |
platform-config-prod.centurygame.com |
— | 443 | HTTPS/TLS | Platform configuration & feature flags |
-ga suffix likely denotes "Global All" (international server).
The -apa suffix in the passport domain may indicate "Asia-Pacific" region.
Game servers are in ap-northeast-1 (Tokyo), chat servers in eu-central-1 (Frankfurt).
Complete catalog of observed sproto message types, their identifiers, directions, and purposes as captured during normal gameplay sessions.
| Type ID | Hex Code | Direction | Observed Count | Purpose |
|---|---|---|---|---|
0x5502 |
55 02 | Server → Client | High | Main response with player profiles & power |
0x1D01 |
1D 01 | Server → Client | Very High | Server push: state updates, events, resources |
0x5D02 |
5D 02 | Client → Server | High | Client requests and commands |
0x5D01 |
5D 01 | Server → Client | High | Timer/data push: periodic updates |
0x0D01 |
0D 01 | Bidirectional | Very High | Acknowledge: message receipt confirmation |
0x7104 |
71 04 | Server → Client | Medium | Alliance/map data: territory, alliance info |
0x1502 |
15 02 | Server → Client | Very High | Heartbeat response (keep-alive pong) |
0x1501 |
15 01 | Client → Server | Very High | Heartbeat request (keep-alive ping) |
0x4209 |
42 09 | Server → Client | Low | Large data push: bulk transfers, map tiles, rankings |
0x1501/0x1502) dominate
the traffic volume, exchanged every ~30 seconds. Acknowledgements (0x0D01) are sent for nearly
every non-heartbeat message. The actual game data is a relatively small fraction of total traffic.
| Component | Details |
|---|---|
| APK Type | SAI (Split APK Installer) wrapper — the real game is packaged inside assets/app.zip |
| Unity Version | 2022.3.62f1 with IL2CPP backend |
| IL2CPP Metadata | global-metadata.dat is encrypted with HTPX (NetEase HTProtect) |
| Sproto Library | Found in libtolua.so — contains the sproto C library implementation |
| Game Code | Code identifier: "gom" |
| Publisher | "centurygame" |
The global-metadata.dat file, which normally contains readable IL2CPP type/method
information, is encrypted using HTPX (NetEase HTProtect). This prevents
standard IL2CPP reverse engineering tools from extracting the game's class/method structure.
Related native libraries found in the APK:
libNetHTProtect.so — Network protection layerlibhtpcrash.so — Crash protection/anti-tamperlibhtpcrash_dumper.so — Crash dump handlerlibnesec.so / libnesec-x86.so — NetEase security modulesKey values extracted from APK configuration (CGSettings):
| App Key | c37aba497f0fc9e6357a4b0d32b45e2f |
| Game Code | gom |
| Publisher | centurygame |
| Signer | libsigner.so |
| Lua Engine | libtolua.so (LuaJIT + sproto) |
app.ziplibtolua.so exports) is the most viable approach for understanding protocol encoding
[2B len] [2B type] [2B sess] [body]
type 0x5502 → Main Response (power!)
type 0x1D01 → Server Push
type 0x5D02 → Client Request
type 0x5D01 → Timer/Data Push
type 0x0D01 → Acknowledge
type 0x7104 → Alliance/Map
type 0x1502 → Heartbeat Pong
type 0x1501 → Heartbeat Ping
type 0x4209 → Large Data Push
Power: LE4 uint32 (10M–2B+)
Player ID: LE4 uint32 (10M–500M)
Alliance: ASCII after 0xf1 0x03
Kingdom: LE4/1M after 0xf1 0x04
Name: String after 0x04 0x1f
Language: 2-char ASCII
Avatar: Path starting /202X/
# Python decode power:
power = int.from_bytes(
data[o:o+4], 'little'
)
# Python decode kingdom:
kingdom = int.from_bytes(
data[o:o+4], 'little'
) // 1_000_000
[4B "FPNN"] [1B ver] [1B flags]
[2B msg_type] [4B seq] [4B len]
[N bytes msgpack payload]
Auth: method="auth"
params: pid, uid, token, version
Chat token: "YCA" + 64 hex chars
Game: gof-login-formal-ga
.centurygame.com:30101
Chat: rtm-intl-frontgate
.ilivedata.com:13321
GM: gof-gm-api-formal-ga
.centurygame.com:443
Login: passport-apa
.centurygame.com:443
Config: platform-config-prod
.centurygame.com:443