Have you ever wondered what’s going on under the hood when you play a game of efficiency ctf, with all that action of players shooting, jumping, dying, picking up flags, and spectators chatting? Here’s the explanation! Read on and learn how Sauerbraten’s networking works.

Sauerbraten’s networking code is based on the ENet library. Without going into too much detail, ENet is a layer on top of UDP offering (optional) sequential and/or reliable transmission of packets (similar to TCP), the concept of using multiple channels on one connection, and more.

For this article, we assume a simple connect to a vanilla server with default config, nothing fancy like auth-on-connect or server passwords will be covered in-depth.

Channels

Sauerbraten uses 3 channels for communication between server and client:

  • channel 0 is for transmitting position data (that is, where in the map is a client?)
  • channel 1 is for game events, like a player shooting, a player using a teleport, a player joining the game, chat messages, and much more
  • channel 2 is used in “coop edit” mode to send maps (/sendmap and /getmap)

Requesting a CN and server information

When you try to connect to a server (for example by clicking on it in the server browser), your client will establish a connection to the server over ENet. The server will notice that you want to connect and will add you as a client and assign you a client number (CN). The server will then reply with a “server info” packet, which looks something like this (when you display the decimal values of the bytes):

1 0 128 3 1 129 184 252 162 0 0 108 111 99 97 108 32 116 101 115 116 32
115 101 114 118 101 114 32 50 0 0

Here’s a short listing of the parts that make up a “server info” packet, followed by a more detailed explanation:

1                   → N_SERVINFO
0                   → cn given to you by the server
128 3 1             → protocol number (3 + 256 = 259)
129 184 252 162 0   → session ID (184 + 64512 + 10616832 + 0 = 10681528)
0                   → not password-protected
108 111 ...  50 0   → server description ("local test server 2")
0                   → server auth domain ("")

N_SERVINFO

The N_SERVINFO byte is part of a list of network message codes that is known to the client and the server (you can find that list in src/fpsgame/game.h). N_SERVINFO just happens to have 1 as its byte representation; it doesn’t really matter what value it is, as long as the client and server both know the meaning of it.

cn

The next number is the CN the server assigned to your connection. If you are the first client to connect to a server, it is usually 0. Connecting clients get the lowest free number (not a requirement, but it is this way in the vanilla server and in all server mods).

protocol number

Next up is the protocol number (259 in collect edition). Don’t get confused by the weird three byte representation; it will be explained later.

session ID

The session ID is always generated, but only actually used when the server requires you to provide a password in order to connect. The password you type in is hashed together with this session ID and your CN, and only the hash is then sent to the server and compared to the server’s hash.

passwort-protected indicator

The following byte can be either 0 or 1, and indicates if the server requires you to provide a password or not.

server description

The server description (C/C++ strings always end with a 0x00 byte). This is the same description that you can also read in your server browser.

server auth domain

Last comes the server’s auth domain for which a connecting client has to provide an auth key in order to be let in (an empty string signals the server does not use auth-on-connect; however, it does not mean the server doesn’t use local auth domains at all).

Note regarding integer compression

Notice how the protocol number (259 in collect edition) consists of three bytes (also, the CN was only one byte even though it is a uint32 internally and should be four bytes): this is because Sauerbraten uses an encoding for integer numbers that saves bandwitdh:

  • values less than 128 and greater than -127 (i.e. fit into one byte) will simply be sent as one byte
  • values that would fit into two bytes are sent as two bytes, preceeded by a 0x80 byte (128 in decimal)
  • all other values will be sent as their normal four bytes, preceeded by a 0x81 byte (129 in decimal)

This works well for Sauerbraten even though big numbers need five bytes instead of just four, since most numbers sent are small and only take up one byte or two bytes (three after encoding).

Joining the game

After receiving a CN, Session ID and the server information, the client replies with a N_CONNECT packet, which looks like this:

0 124 121 101 115 86 73 58 112 105 120 0 1 0 0 0

0                   → N_CONNECT
124 121 ... 120 0   → player name ("|yesVI:pix")
1                   → player model ID
0                   → connect password hash ("")
0                   → auth domain ("")
0                   → auth name ("")

N_CONNECT

N_CONNECT (like N_SERVINFO) tells the server what kind of packet this is so it knows what parts will come next.

player name

The next bytes up to a 0x00 byte are the player’s name.

player model ID

Up next is the ID of the player model the client uses, so that other clients know what player model to show for that player.

connect password hash

Next comes a string (all bytes up to 0x00) containing the hashed connect password the user entered, in case the N_SERVINFO packet told the client that this server is password protected.

auth domain and auth name

Last come the auth domain (which is the same as the server auth domain the server sent in the N_SERVINFO packet) as a string and the name corresponding to the auth key the client found for the specified auth domain. For more information on the auth system in Sauerbraten, read the article on Sauerbraten’s Auth Mechanism.

Personal note: I find N_CONNECT to be a very confusing name for this packet: on the ENet level, you are already connected when you get the N_SERVINFO packet. “N_JOIN” would be a more apt name I think.)

Welcoming the client

When the server receives the N_CONNECT packet, it checks for a number of things:

  • is the password correct? (if needed)
  • if not, is the server in private mode maybe?
  • is the auth key valid? (if needed)
  • is there a free spot (server not full)?
  • is the IP that connected banned?

If everything is OK, it prepares the client’s on-server representation for playing and then sends a welcome packet back to the client that joined.

The server then welcomes the client by sending a number of packets. Here is a (small) example of that list of packets making up the “welcome packet“:

2 22 114 101 105 115 115 101 110 0 5 0 33 87 58 1 3 1 -1 61 0 103 111
111 100 0 -1 17 0 0 100 100 100 1 2 20 20 10 10 20 0 37 1 0 12 1 0 3
100 100 100 1 2 16 4 3 8 15 0 -1 3 1 67 111 111 107 105 101 0 101 118
105 108 0 1

2                    → N_WELCOME

22                   → N_MAPCHANGE
114 101 ... 110 0    → map name ("reissen")
5                    → game mode (effic)
0                    → whether or not the server has representations
                       for items (quad, health boost, etc); 0 means it
                       already has them

33                   → N_TIMEUP
87                   → seconds left to play

58                   → N_CURRENTMASTER
1                    → veto
3                    → a CN
1                    → the privilege of client with CN 3
-1                   → end of privileges list

61                   → N_SETTEAM
0                    → CN of the client whose team to set (= client
	                   receiving this packet)
103 111 111 100 0    → team name ("good")
-1                   → end of N_SETTEAM packet

17                   → N_SPAWNSTATE
0                    → CN of the client the spawn state date belongs to
                       (= client receiving this packet)
0                    → client's life sequence (changes at every
	                   respawn)
100                  → client's health
100                  → maximum health the player can have (can
	                   change when health boost is picked up)
100                  → armour
1                    → armour type (pre-defined constant: 0 = blue,
	                   1 = green, 2 = yellow)
2                    → weapon the player is currently using (pre-
	                   defined: 2 = minigun)
20                   → shotgun ammo
20                   → minigun ammo
10                   → rocket launcher ammo
10                   → rifle ammo
20                   → grenades ammo
0                    → pistol ammo

37                   → N_RESUME
1                    → CN of another player
0                    → player 1's current state (0 = alive, ...)
12                   → frags
1                    → flags player 1 scored
0                    → time left for player 1's quad damage, if he
                       has it
3                    → player 1's life sequence (changes at every
	                   respawn)
100                  → player 1's health
100                  → maximum health the player can have (can change
                       when health boost is picked up)
100                  → armour
1                    → armour type (pre-defined constant: 0 = blue,
	                   1 = green, 2 = yellow)
2                    → weapon the player is currently using (pre-
	                   defined: 2 = minigun)
16                   → shotgun ammo
4                    → minigun ammo
3                    → rocket launcher ammo
8                    → rifle ammo
15                   → grenades ammo
0                    → pistol ammo
-1                   → end of N_RESUME packet

3                    → N_INITCLIENT
1                    → CN of another client
67 111 ... 101 0     → name of client with above CN ("Cookie")
101 118 105 108 0    → team name of client with above CN ("evil")
1                    → player model ID of client with above CN

Basically, every time you see a network message code (N_*), a new game packet begins:

  • N_WELCOME tells the client to close the server browser in case it is open
  • N_MAPCHANGE tells the client what map to load and what mode is played on that map
  • N_TIMEUP tells the client how many seconds are left to play (so the clock is set correctly when joining mid-game)
  • N_CURRENTMASTER is followed by the master mode currently set on the server and a list of CNs of players with higher than normal privilege (this list can be empty; the end is marked by a -1)
  • N_SETTEAM tells the client what team the player was put into
  • N_SPAWNSTATE tells the client what state to use when the player spawns
  • N_RESUME tells the client to start the countdown and is followed by a list of the current game state of each client already connected to the server (again, ends when -1 is read)
  • now, one or more N_INITCLIENT packets follow, each describing a client’s name, team and player model (if more than one client are connected, there will be multiple N_INITCLIENT packets)

After the server sent the above packet to the new client, it will notify all other clients of the newly connected client by sending a N_INITCLIENT packet to each of them containing the info of the new client. In case the new client was automatically set to spectator (e.g. when the server is locked), all other clients will also receive a N_SPECTATOR packet.

At the end of all this, the client is now doing all the stuff it has to do in order to get “up-to-speed” with the other players, i.e. load the map, put itself into the correct team, set the local player’s spawn state, etc. As far as the server is concerned, the client is now treated exactly the same as all the other clients, because the process of joining the current game is now finished.