Checked against client release: a4a25ae6
Protocol¶
Goodgame Empire speaks a SmartFoxServer dialect over a WebSocket
(wss://<host>:<port>). The handshake is XML system messages; everything after
it is %xt% extension messages. Frames from the server end with a null byte;
the client sends its own with none (sendCommand and sendXMLMessage pass the
bare string to socket.send, dll lines 7198-7203), and so does the library
(Connection.send strips a trailing null byte before sending).
Line numbers below are ggs.dll.split.js lines (see the roadmap issue for how
to fetch and split the client); search by the class name when they drift.
1. Handshake¶
What the HTML5 client does (BasicSmartfoxClient, dll lines 7132-7240):
verChk, sent as soon as the socket opens (there is no policy request):<msg t='sys'><body action='verChk' r='0'><ver v='166' /></body></msg>apiOKcomes back. The client then logs in to the zone with an empty nick and<build date>%<language>%<distributor id>as the password (handleSystemMessage,login):<msg t='sys'><body action='login' r='0'><login z='EmpireEx_21'><nick><![CDATA[]]></nick><pword><![CDATA[...]]></pword></login></body></msg>rlu(room list, an%xt%message) comes back; the client sendsautoJoinonce:<msg t='sys'><body action='autoJoin' r='-1'></body></msg>.joinOKcomes back. Itsrattribute is the room id the client sends in every later%xt%message (activeRoomId).- In the lobby room the client measures a
roundTrip(answered byroundTripRes), starts apinevery 60 seconds (onJoinRoom) and sendsvckwith the build number,web-html5,<RoundHouseKick>and the session id (BasicJoinedRoomCommand, dll line 33011).sendMessagesends any argument that is falsy but not0as<RoundHouseKick>, so the empty stringvckpasses third goes out as that, and eachpinis%xt%<zone>%pin%<room id>%<RoundHouseKick>%. lli: the account login, whose JSON payload carries the name (NOM) and password (PW).
The library (Session._login_sequence, in client/session.py) runs the same
steps in the same order: verChk, the zone login with the build number,
autoJoin, then roundTrip and vck once it has joined the lobby, and lli
with the measured connection and round-trip times. It then waits for gbd. Its pin goes out
every 60 seconds from its own keepalive thread.
2. Extension messages¶
%xt%<zone>%<command>%<room id>%<argument>%...%
- zone:
EmpireEx_21unless configured otherwise. - command: the command id, for example
gaaorcra. The ids the client knows are intests/data/client_commands.json(seeCONTRIBUTING.md). - room id: the id from
joinOK;EmpireClient.framepasses it toBaseRequest.to_packet. - arguments: nearly every command has one,
JSON.stringifyof itsC2S...VO(sendCommandVO). A few, such asvckandpin, send plain%-separated arguments. Before sending, the client turns every%in a string argument into%and drops every'(sendMessage,TextValide.getValideSmartFoxText, dll line 5816), and so doesBaseRequest.to_packet(protocol.base.smartfox_text).
A reply is %xt%<command>%<room id>%<error code>%<JSON>%; an error code other
than 0 is a failure.
Example, a gaa map request as the client builds it (C2SGetAreasVO, bundle line 65991):
3. In the library¶
protocol/packet.py:Packet.iter_from_bytessplits a frame on null bytes andPacket.from_bytesparses one message, XML or%xt%, splitting at most five times so a%in the JSON survives.protocol/base.py: each command's request model subclassesBaseRequest(to_payload,to_packet); each reply model subclassesBaseResponseand registers itself for its command, soparse_response(command, payload)finds it.network/connection.py: the receive loop and the waiters thatEmpireClient.send/requestblock on; see architecture.md.