This page describes TERA's network protocol, which is built on top of TCP. It is a structured protocol that can be serialized and deserialized automatically based on uniform packet definitions; no special serialization logic is required for any fields.
C/C++-like primitive types and struct
s will be used.
bool
is equivalent to uint8_t
but only allows the values true
(1
) and false
(0
).
Integers (uint8_t
, int8_t
, uint16_t
, int16_t
, etc) are little endian.
offset_t
values are uint16_t
indexes into a packet, including the header.
float
and double
are IEEE 754 binary32
and binary64
, respectively.
Characters (i.e. char16_t
) are UTF-16 and little endian.
Strings (i.e. u16string
) are a series of valid char16_t
characters followed by a NUL character.
Fields are laid out in the declared order with no implied padding anywhere.
TODO: Describe the (insecure) key exchange and the PIKE algorithm.
A packet starts with a simple header:
length
specifies the full length of the packet, including the header. Thus, the maximum length of a packet is 65535
bytes (or 65531
bytes for the payload). For certain large packets (e.g. achievements and inventory), the game works around this by sending 'continuation' packets after the first packet.
code
is the operation code. This tells the client or server what the structure of the payload is.
The packet body consists of a series of fields. Some examples:
Fields are written in the order that they are declared. However, complex fields (strings, object arrays, and byte arrays) are written as offset_t
values that point to the actual data elsewhere in the payload. Primitive types such as integers, floating point numbers, and Boolean values are written in the obvious way as they appear.
Complex types are written after all primitive types in the current 'object' (be that the root packet body, or an object nested arbitrarily within arrays). At the place where the complex field appears, an offset_t
value is written pointing to where the actual data for the field is written in the payload. For object arrays and byte arrays, this value is accompanied by a uint16_t
value representing the number of elements in the array.
When there are multiple complex fields in an object, they are written at the end of the object in the order that they appear in the structure. For example, in CEditPrivateChannelPacket
, the contents of the members
array are written after password
, and the channel_name
string contents after that.
String pointers are represented as follows:
When writing the string contents, the characters are written contiguously, followed by a NUL character.
Object array pointers are represented as follows:
Each element within the array has an object pointer that links to the next element:
start
points to a PacketObjectPointer
, which is immediately followed by the contents of the first element. The next
pointer points to another PacketObjectPointer
, which is immediately followed by the contents of the second element, and so on. This continues count
times. The next
value for the last element is 0
. In each element, here
is just a pointer to itself; it is not clear what purpose it serves.
Due to the way that arrays are serialized, in theory, it would be possible to spread the elements all over the place in a payload, in whatever way would make the most sense for compactness and locality. In practice, the client and official servers almost always do the straightforward thing, with only a few curious (and seemingly nonsensical) exceptions. Regardless, neither party actually cares how arrays are laid out for the purposes of serialization.
Byte array pointers are represented as follows:
Note that count
comes after start
, unlike object arrays.
Byte arrays are serialized in a more compact fashion than object arrays: start
points to an area in the payload containing count
bytes, making up the contents of the byte array. There are no pointer values to follow for each individual element.