V2 — FINAL

Whiteout Survival
Sproto Protocol Investigation

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.

Game
Whiteout Survival
Version
1.30.28
Developer
Century Game
Protocol
Sproto / Binary TCP
Report Date
2026-06-03
Classification
Confidential
1

Executive Summary

Key Findings

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.

No public HTTP API exists for player power data. All power, alliance, kingdom, and VIP information flows exclusively through the binary sproto protocol. There is no REST endpoint, GraphQL API, or any other HTTP-based mechanism to query player power.

Game Server

Binary sproto over raw TCP. Unencrypted. Port 30101. Contains all player data including power.

Chat Server

FPNN with msgpack encoding over TCP. Port 13321. Rich metadata: VIP, alliance, kingdom.

HTTP Endpoints

HTTPS only for login, GM API, and platform config. No game-state query endpoints exposed.


2

Protocol Architecture

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).

Game Server

gof-login-formal-ga.centurygame.com:30101

Sproto/TCP Unencrypted

Primary game protocol. Player state, power, alliance, world map data.

Chat Server

rtm-intl-frontgate.ilivedata.com:13321

FPNN/Msgpack/TCP

Real-time messaging with rich player metadata (VIP, alliance, kingdom).

GM API

gof-gm-api-formal-ga.centurygame.com:443

HTTPS

Game Master API. Administrative functions over TLS.

Login API

passport-apa.centurygame.com:443

HTTPS

Account authentication and session creation.

Platform Config

platform-config-prod.centurygame.com:443

HTTPS

Configuration distribution, feature flags, server lists.

Architecture Note: The game server resolves to 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?).

3

Sproto Protocol Details

Wire Format

Sproto packet structure (6-byte header + variable body)
┌──────────┬──────────┬──────────┬──────────────────────┐
│ 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.

Key Message Types

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.).
Key Finding: Type 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.

4

Login Packet Format

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.

Packet Header

[2B total_len] [2B type=0x5502] [2B session=0x0404] [body]
            

Body Structure (PCAP-Verified)

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
─────────────────────────────────────────────────────────────
            
Fingerprinting: The login packet includes multiple MD5 hash fields for device fingerprinting. These are used for anti-fraud and device verification but are transmitted in cleartext over the unencrypted TCP connection.

5

Player Data Encoding

V2 CONFIRMED — Player Data 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/...")

Verified Examples

Pano

Power762,043,293
Power (hex)0x2D6BDB9D (LE)
AlliancePOL
Player ID288,979,040
Kingdom2007

VISHENAEBA

Power973,248,215
AllianceIRS
Kingdom
Player ID

BandiMadman

Power858,363,844
AllianceIRS
Kingdom
Player ID

Power Value Encoding Detail

Example: Pano's power = 762,043,293
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 Encoding Detail

Example: Kingdom 2007
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
            

6

FPNN Chat Protocol

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.

Wire Format

┌─────────┬─────────┬────────┬──────────┬──────────┬──────────────┬─────────────┐
│  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
            

Authentication

FPNN auth method
method: "auth"
params: {
    pid:     <player_id>,
    uid:     <user_id>,
    token:   <chat_token>,
    version: <client_version>
}
                

Chat Token Format

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 Message Structure

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)
}
            
Discovery: The 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.

7

Authentication Flow

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.

Step 1
HTTPS → passport-apa.centurygame.com
Authenticate account credentials
Step 2
HTTPS → gof-login-formal-ga.centurygame.com
Create session, obtain token
Step 3
TCP → 35.71.149.13:30101
Sproto login with session token + player ID
Step 4
TCP → 35.156.52.69:13321
FPNN auth with pid, uid, chat_token

Detailed Flow

──────────────────────────────────────────────────────────────────
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
            

8

How to Get Player Power

Three methods exist for obtaining player data, each with different trade-offs:

Method 1: PCAP Analysis

Offline Full Data

  • Capture traffic while viewing leaderboard in-game
  • Analyze PCAP with Python script
  • Extracts power, alliance, kingdom, name
  • Pros: No auth needed, complete data
  • Cons: Requires packet capture setup

Method 2: Live Sproto

Live Full Data

  • Connect with session token from login API
  • Receive real-time state pushes
  • Can query specific player profiles
  • Pros: Real-time, programmatic
  • Cons: Requires valid session token

Method 3: Chat Server

Live Partial Data

  • Connect to FPNN chat with credentials
  • Receive chat messages with metadata
  • Gets VIP, alliance, kingdom — NO direct power
  • Pros: Easiest protocol to parse
  • Cons: No direct power query

Python Tool: wos_sproto_client_v6.py

Usage examples
# 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..."
            

Command-Line Arguments

ArgumentDescriptionRequired
--pcapPath to PCAP capture file for offline analysisFor Method 1
--tokenSession token from login APIFor Method 2
--pidPlayer ID to look upYes
--chatEnable FPNN chat connectionOptional
--chat-pidChat server player IDWith --chat
--chat-uidChat server user IDWith --chat
--chat-tokenChat authentication token (YCA...)With --chat

9

Security Observations

Disclaimer: This section documents security observations for defensive purposes only. The findings highlight areas where the game's network protocol could be improved to protect player data.

Unencrypted Game Traffic

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.

Plaintext Chat Metadata

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.

Predictable Session Tokens

Session tokens follow a predictable format and structure, making them potentially susceptible to forgery or replay attacks if the token generation algorithm is understood.

Weak Signing Mechanism

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.

Exposed Credentials

The following credentials and keys were observed during the investigation:

TypeValueSource
GitLab Token glpat-ni86rqoefyhoTkMsNYiR APK configuration
App Key c37aba497f0fc9e6357a4b0d32b45e2f CGSettings in APK

Interceptable Player Data

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:

  • ISP-level interception — No TLS means ISPs can read all game data
  • Wi-Fi sniffing — Open/public Wi-Fi networks expose all game traffic
  • MITM attacks — Without certificate pinning on TCP, MITM is trivial

10

Server Infrastructure

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
Infrastructure Notes: Century Game uses AWS with region-based deployments. The -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).

11

Sproto Message Type Catalog

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
Message Frequency: Heartbeat messages (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.

12

APK Analysis Notes

APK Structure

ComponentDetails
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"

HTPX Encryption (NetEase HTProtect)

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 layer
  • libhtpcrash.so — Crash protection/anti-tamper
  • libhtpcrash_dumper.so — Crash dump handler
  • libnesec.so / libnesec-x86.so — NetEase security modules

Extracted Configuration

Key values extracted from APK configuration (CGSettings):

App Key c37aba497f0fc9e6357a4b0d32b45e2f
Game Code gom
Publisher centurygame
Signer libsigner.so
Lua Engine libtolua.so (LuaJIT + sproto)

Reverse Engineering Implications

  • IL2CPP + HTPX makes traditional C# decompilation impossible; the metadata is encrypted
  • libtolua.so is the key target — it contains the Lua VM and sproto serialization, and is not encrypted
  • The game logic is written in Lua (running via tolua), not C# — this means the interesting protocol handling code is in Lua scripts, which are loaded from app.zip
  • Dynamic analysis (hooking libtolua.so exports) is the most viable approach for understanding protocol encoding

A

Appendix: Quick Reference

Sproto Wire Format Quick Ref

[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
                

Player Data Quick Ref

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
                

FPNN Wire Format Quick Ref

[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
                

Key Domains Quick Ref

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