protocol.md 18 KB

TirNanoG MMORPG Protocol

This is the binary language of Moisture Vaporators, only intended to be read by protocol droids. :-D If you're interested in the administration REST API, see the TirNanoG Admin Protocol.

All integers are little-endian, and all packets start with a 2 bytes long header:

Offset Length Description
0 2 Packet type (bit 0 - 4) and length (bit 5 - 15)

Type can be 0 - 31, length 4 - 2043 (but maximum length is usually smaller, limited by interface MTU to 1500 minus the IP protocol headers, so around 1400 in best case. In fact, the IPv6 RFC guarantees only 1280). Actually the underlying NetQ library adds another 6 more bytes to the datagrams before this header (but that's hidden from the app).

The communication starts on a strongly encrypted, SSL/TLS TCP channel.

Hello

Client to server

Offset Length Description
0 2 magic: type 0, size 8
2 2 Supported protocol version (0 as of writing)
4 4 TirNanoG Player magic 'TNGP'

Server to client

Offset Length Description
0 2 magic: type 0, size 64
2 2 Supported protocol version (0 as of writing)
4 4 TirNanoG Server magic 'TNGS'
8 8 supported protocol feature flags, must be zero
16 16 game id, padded with zeros
32 1 asset format revision (0 for now)
33 1 game type (0-top-down, 1-isometric, 2-hex...etc.)
34 1 tile width (bits 8-9 in game type byte bits 4-5)
35 1 tile height (bits 8-9 in game type byte bits 6-7)
36 1 map size in power of two
37 1 atlas size in power of two
38 1 audio samples frequency, (0 = 44100 Hz)
39 1 number of frames per second (by default 30)
40 1 number of default action handlers (by default 9)
41 1 number of map layers (by default 11)
42 1 number of transportation methods (by default 3)
43 1 highest bytecode command used in scripts
44 1 character sprites upwards delta y
45 1 number of char sprites per layer (by default 20)
46 2 must be zero
48 8 assets boundle unique id
56 8 size of the assets boundle

NOTE: bytes 16 - 56 are the same as the TirNanoG File Format header in the game world file. The client must compare this with the cached asset boundle's header, and reply with a type 1 message if they differ (see below). Assets boundle size must be either the file's size or the offset of the first Map asset in the file, whichever is the smallest, minus 64.

The client can examine the header fields to decide if it can interpret the given game or not. If not, then it closes the connection without a reply, and shows an error message to the user. (Currently not relevant because only one revision of the format exists, but might be important in the future.)

Request for New Assets

Client to server

Offset Length Description
0 2 magic: type 1, size 2

Server to client

Assets boundle data, as many bytes as advertised in the "Hello" message, then 4 bytes CRC checksum. After sending these bytes the server closes the connection. The client can compare its calculated CRC with the last 4 bytes to determine if the assets were received successfully or if it needs to repeat the process.

Player Registration

Client to server

Offset Length Description
0 2 magic: type 2, size 36 + u + n
2 2 client's preferred language
4 32 password hash
36 u user name, zero terminated UTF-8
36 + u n character specification

The data in the character specification field depends on the available character options and attributes sent in the assets boundle. It is a list of int32_t integers, in two blocks. In the first block each value is (option value << 8 | option palette index), and the second block is just simply attribute values, same as in the boundle definition except values for global attributes are omitted. The client's language is just informational, only used with the "Text Chat" and "Dialog" messages (see below) if the server has a translator configured.

Player Login

Client to server

Offset Length Description
0 2 magic: type 3, size 36 + u
2 2 client's preferred language
4 32 password hash
36 u user name, zero terminated UTF-8

Player Rejected

Server to client

Offset Length Description
0 2 magic: type 4, size 4
2 2 Reason code

If sent as a response to registration, then it means registration is temporarily disabled or another user by that name already exists. When sent as a response to login, then it means bad user name or password. The field reson code might give some insight on the situation.

Player Accepted

Server to client

Offset Length Description
0 2 magic: type 5, size 270
2 256 session token
258 8 session OTP
266 4 compressed world state size (n)
270 n compressed world state

The registration/login was successful. Note how the compressed state is sent right after this message, but it's not counted in the message's size. The compressed world state is in the same format as the TirNanoG Saved Game Format, but without the header or preview, and must not contain DENY chunk and it must contain exactly one USER chunk.

After this message the server closes the connection, and client sends an UDP hello.

Further communication between the client and the server is done over UDP, where the packet data is XOR'd with the session token. Reliability is provided at application level, by the NetQ library.

Packet length is limited in 1400 bytes (including the 6 + 2 bytes header, 6 from NetQ, and 2 from this documentation).

UDP Hello

Client to server

Offset Length Description
0 2 magic: 'Hi'
2 4 must be zero
6 2 magic: type 6, size 10
8 8 session OTP

This is the one and only UDP packet which isn't encrypted. Its purpose is to allow the server to register the peer's remote UDP port, and to punch a hole on firewalls (just like UPnP, this registers a related status between the client and the server in the firewall, so further UDP packets originating from the server will be allowed by the default related-allow rule). When received by the server for the first time, it echoes this packet back (the protocol allows multiple datagrams here because UDP isn't reliable).

If a client sends this with zero OTP, then the server replies with a (shortened) hello message. This is used to detect servers on LAN.

Offset Length Description
0 2 magic: type 0, size 32
2 2 Supported protocol version (0 as of writing)
4 4 TirNanoG Server magic 'TNGS'
8 8 supported protocol feature flags, must be zero
16 16 game id, padded with zeros

NetQ adds 6 bytes to the header, but that's not returned to the application, so hereafter I'm going to exclude the decrypted message's first 6 bytes from the documentation.

Status Update

Server to client

Offset Length Description
0 2 magic: type 7, size 2 + n
2 n data

The data is serialized game state. The server keeps track of player's location, and only sends updates for their surroundings. The server might send this packet with empty game state to check if the client is still connected.

The data is a list of various length records, each prefixed by an identifier byte. 0 - player movement, 1 - player logout, 2 - player sprites, 3 - player inventory, 4 - player equipment.

Player Action

Client to server

Offset Length Description
0 2 magic: type 8, size 12 + n
2 4 internal map id
6 2 map x
8 2 map y
10 1 direction
11 1 altitude
12 n data

The data is serialized action list. The client might send this packet with empty actionlist if it wants to tell the server it's still connected, but there's no player activity to report.

In response the server sends an update to the clients on the neighboring maps.

Offset Length Description
0 2 magic: type 7, size 17
2 1 0, player movement
3 4 internal user id
7 4 internal map id
11 2 map x
13 2 map y
15 1 direction
16 1 altitude

Player Logout

Client to server

Offset Length Description
0 2 magic: type 9, size 2

When the server does not get acknowledged replies from a client for 5 seconds, then it acts as if it has recevied this package. Otherwise this package is sent to the server when the player quits the game.

In response the server sends an update to the clients on the neighboring maps.

Offset Length Description
0 2 magic: type 7, size 7
2 1 1, player logout
3 4 internal user id

Request Map

Client to server

Offset Length Description
0 2 magic: type 10, size 6
2 4 internal map id

Internal map id is the TirNanoG File Format section type 35 map asset number.

Sending Map

Server to client

Offset Length Description
0 2 magic: type 11, size 12 + n
2 4 internal map id
6 3 total size of the map asset data
9 3 fragment's offset
12 n map asset data fragment

The data is deflated, and in the same format as in TirNanoG File Format map asset data. There can be more of this packet if asset data does not fit into one UDP packet, and in that case it is the client's duty to re-arrange the packets into sending order (should be done automatically by the underlying NetQ library).

Text Chat

Sent both by the server to the client and client to the server

Offset Length Description
0 2 magic: type 12, size 6 + n
2 4 internal user id (zero on send)
6 + n n zero terminated UTF-8 message

Also sent by the server in response to Text Chat packet to the players that can "hear" the message. The clients should already have a copy of the nearby players' records, but if by chance not, they might reply with a "Whois" message.

Audio Chat

Sent both by the server to the client and client to the server

Offset Length Description
0 2 magic: type 13, size 8 + n
2 4 speaker internal user id (zero on send)
6 1 sample rate (0 = 22050 Hz, 1 = 44100 Hz)
7 1 bit 0..3: precision, bit 4..7: compression
8 n PCM data

When the server receives this packet, then it fills in speaker id otherwise forwards unmodified to the players that can hear the message (in the game world). If the voice recording happens at 44100 Hz, with 8-bit precision (enough for chat), that means 30 packets per second, each with a 1470 bytes of PCM data uncompressed. Same if 22050 Hz used with 16-bit precision. Using 44100 Hz with 16-bit PCM (which is the default for the sound and music assets in the game) would require 60 packets per second. Compression is left out with 22500 Hz and 8-bit or if the MTU size is configured larger than 1526 (1470 data + 8 TNG header + 48 IPv6/fragment header). Byte 7's lower tetrad encodes sample precision (0: 8 bit, 1: 16 bit, 2: 16 bit big endian), upper tetrad the compression method (0: none, 1: gzip deflate). Voice recording is always mono channel.

Quest

Sent both by the server to the client and client to the server

Offset Length Description
0 2 magic: type 14, size 7
2 4 internal quest id
6 1 0: accepted, 1: completed, 2: cancelled

Whois

Client to server

Offset Length Description
0 2 magic: type 15, size 4
2 4 internal user id

In response the server sends an update packet to the same client.

Offset Length Description
0 2 magic: type 7, size 7 + o + n
2 1 2, player sprites
3 4 internal user id
7 o character options (world.numchar)
7+o 64 equipted objects (32 * uint16_t)
71+o n name (zero terminated UTF-8)

Used to generate the character sprites on client side. For the map, as well as portrait for the dialog window.

Inventory

Client to server

Offset Length Description
0 2 magic: type 16, size 4
2 4 internal user id

To query the client's own inventory, -1 is used as user id. In response the server sends an update packet to the same client.

Offset Length Description
0 2 magic: type 7, size 7 + n
2 1 3, player inventory
3 4 internal user id
7 n deflate compressed inventory list

Used when players exchange items (P2P market).