8. The Network protocol

8.1 Generalities

This is an incomplete description of the Quake Network Protocol used in Quake version 1.01. The protocol used in Quake Worlds will probably be rather different, and hopefully much more optimised.

The network protocol described below is not derived from documents by id software, so it's likely to be incomplete (or even wrong).

The messages exchanged by the Quake client and servers are not really complex to understand, but they are quite numerous, and of heterogeneous structure. Please read the example of network session, before trying to emulate a Quake client or server.

Since the messages in the .DEM files have exactly the same format as some of the network protocol messages, it is highly recommended that you also take a look at the DEM specs by Uwe Girlich.

When describing the content of messages, we will use the same conventions as in the DEM specs:

ReadString() = read a sequence of characters, terminated by NULL ('\0')
ReadAngle()  = read a byte, and multiply it by 360.0 / 256.0 to make it into degrees.
ReadChar()   = read a signed integer, on one byte.
ReadShort()  = read a signed integer, on two bytes, Big Endian order (Intel order).
ReadLong()   = read a signed integer, on four bytes, Big Endian order (Intel order).
ReadFloat()  = read a floating point number, on four bytes, Big Endian order (Intel order).

8.2 Control messages

The structure of control messages is the following:

typedef struct
{ short Type=0x8000;           // (Little Endian) Type of message.
  short Length;                // (Little Endian) Length of message, header included. 
  char  message[Length-4];     // The contents of the message.
} MSGHEADER;
A control message, as opposed to a more ordinary game message, if characterised by the fact that Type & 0x8000 is not zero (which means that bit 15 is always set on a control message).

Warning: the fields Type and Length are in Little Endian order (most significant byte first). To read them on an Intel processor, you must swap the higher and lower bytes.

The structure of the message fields is: a single-byte code, followed by code-dependant informations. The following table details this information.

Thanks to A.Oliver for the description of the control requests 0x3, 0x4, 0x82, 0x84, 0x85 in the Unofficial Quake Network Protocol Specs 1.01.

MessagesCodeData
Connection Request 0x01
GameName = ReadString();      // = "QUAKE"
ProtocolVersion = ReadChar(); // = 3
Request Server Information 0x02
GameName = ReadString();      // = "QUAKE"
ProtocolVersion = ReadChar(); // = 3
Request Player Information 0x03
Player = ReadChar();      // player number, from 0 to 15
Request Rule Information 0x04
Rule = ReadString();     // name of the previous rule
// To pool all rule names, start with Rule = "", then continue
// with the rule name obtained in the reply,
// until an empty reply is received.
Accept Connection 0x81
Port    = ReadLong();    // Client's personal UDP or IPX port number
Reject Connection 0x82
Reason  = ReadString();  // Reason for rejecting the request
Give Server Informations 0x83
Address = ReadString();  // Server address, in ascii
Hostname= ReadString();  // name of the host machine
Mapname = ReadString();  // name of the current map
Players = ReadChar();    // current number of players
Maximum = ReadChar();    // maximum number of players
ProtocolVersion = ReadChar(); // = 3
Give Player Informations 0x84
Player  = ReadChar();    // player number, from 0 to 15
Name    = ReadString();  // name of the player
Colors  = ReadChar();    // shirt/pants colors
ReadChar();              // zero ???
ReadChar();              // zero ???
ReadChar();              // zero ???
frags   = ReadLong();    // Number of frags
time    = ReadLong();    // Connection time in seconds 
Address = ReadString();  // ip address and port, or "LOCAL".
Give Rule Informations 0x85
// If this message is void, there are no more rules.
Rule    = ReadString();  // Name of the rule
Value   = ReadString();  // Ascii value of the requested rule
// Rule is one of "sv_maxspeed", "sv_friction", "sv_gravity",
// "noexit", "teamplay", "timelimit" or "fraglimit".

Note: the control messages contain only one message part (one single code, followed by code-dependant information). But there could be many parts, though it has never been observed.


8.3 Games messages

Every network packet send via UDP (and probably IPX) has the same header:

typedef struct
{ short Type;                  // (Little Endian) Type of message.
  short Length;                // (Little Endian) Length of message, header included. 
  long  Order;                 // (Little Endian) Order of emission.
  char  message[Length-8];     // The contents of the message.
} MSGHEADER;
A game message, as opposed to a Control message, is characterised by the expression (Type & 0x8000) == 0.

Warning: The fields Type, Length and Order are in Little Endian order (most significant byte first). To read them on an Intel processor, you must swap the higher and lower bytes.

8.3.1 Types of messages

TypeMeaningExplanations
0x01 Part of a Reliable Message This message must be acknowledged.
When message that doesn't fit into a single network packet must be sent, it is split in as many parts as necessary. Each of these parts will be sent in order, as message type 0x01, except the last part that will be sent as a message type Ox09.
0x02 Acknowledge of a Reliable Message This message is sent by the recipient of a message type 0x01 or 0x09, to confirm that the message was received correctly.
0x09 End of a Reliable Message This message must be acknowledged.
This is just like message type 0x01, except that once the message is received, the whole message buffer can be interpreted.
Note that if the message to be sent is small enough to fit into a single network packet, then it will be sent as a single message type 0x09.
0x10 Update Message This message must not be acknowledged.
The updates message are made of a single packet, and are usually very short (about 20 to 200 bytes).

8.3.2 Usage of messages

8.3.3 Interpretation of the message contents

The message content is made of a single byte code, followed by some code-dependent data, and then followed by another byte code, and so on until the end of the message.

That means you need to know how to interpret all the possible byte codes to read a single message. That also means that if you make a mistake in the encoding of a single code, then all the rest will be screwed up, and can eventually crash the game. So trust it on luck!

To make matter worse, the byte codes must be interpreted differently, wether the message comes from a game Server or from a game Client.

8.4 Format of client messages

This is the format of the message field, for messages sent by a game client. This field is an unstructured set of bytes, that must be read one by one in order to decode all the parts of the message.

First, you should read one byte (the message code). Then, depending, on this code, you must read zero or more bytes, depending on the code. That gives you one prt of the message. To read the next part, read one byte, that gives you a code, and so on... until the end of the message field.

Stop at the first unrecognised code, it's no use to continue if you have lost your marks.

8.4.1 Codes for client messages

CodeMessageInformationsExplanations
0x00 No Operations
Void
This message indicates an error.
0x01 Keep Alive
Void
This message is sent when the client is iddle, but the connection should not be dropped.
0x02 Disconnect
Void
This message is sent when the player has left the game, and the connection should be dropped. This is the last message sent by a client.
0x03 Client Movement
ActionTime= ReadFloat();  
TiltAngle = ReadAngle();  
YawAngle  = ReadAngle();  
FlipAngle = ReadAngle();  
SpeedFront = ReadInt16();  
SpeedRight = ReadInt16();  
SpeedUp    = ReadInt16();  
Flag     = ReadChar();   
// Flag & 1: fire.
// Flag & 2: jump.
Impulse   = ReadChar();   
// Impulse command
This message is sent when the player tries to move.
All fields (except the angles) are zero by default, if the player doesn't move.
The action time is usually some time in the near past, when the command was issued. Action messages are often repeated with the same action time, and then they all count for one message.
The angles and speed are those desired by the player, but maybe not those that the physic of the worl will allow. The player's angle, position and velocity are controlled by the game server, not by the client, so the consequences of a given movement attempt will only be known when the server sends an update.
0x04 Console order
Command = ReadString()
This message is sent by the client, when it must send an order on the console.
Those messages are used both for hidden game startup commands, and for the orders that a player will type on the console (like say xxx or say_team xxx).
others Unknown
Void
Those codes seem to be useless.

8.4.2 About the player movement order

The angles transmitted by a player movements are directly the angles set by the player, whether via the mouse or via the look up/down keys. Note that there is a flip angle indicated, but since no player command can possibly act on it, it's always zero. Too bad... but this is not DESCENT.

The speed are ordinary signed integers, that seem to indicate a desired speed in units per second. Experiment shows that when the player places an order for +200 forward speed (typical run order), then the player accelerate, until the speed stabilises at +200 coordinates per second (which makes it 10 coordinates per update, that happen every 20th of second).

This behavior proves that this parameter is a speed, not an acceleration. Well, this is a walking or swimming dude after all, not a ship in the void. He's moving in a world where friction is preponderant. Acceleration is only simulated, in the sense that you cannot change to arbitrary speed values in no time. On ordinary ground, accelerations seem limited to about 1000 to 15000 units/secondē.

8.5 Format of server messages

The game messages sent by the Quake server are exactly the same as the ones used in the demo files (.DEM), so they will not be repeated here. Look at the DEM file specifications.

A single Quake server message contains exactly the same informations as those contained in a .DEM file block of messages. That is rather logical, since they certainly didn't create a new format just for the .DEM files, but used the network protocol instead.

Let's just recall those useful orders:
CodeMessageInformationsExplanations
0x19 Set signon state
state = ReadChar()
This sets the signon state, a state variable that is used to guide the intialisation of a client.
 1 = PRESPAWN (prepare for entity spawning).
 2 = INIT LIGHT EFFECTS
 3 = START 3D RENDERING (run the game).
0x80-0xFF Entity update
Based on mask.
Very complicated.
This is the standard message that updates the position, angles, frame and models of a given entity.

8.6 Network session example

Here are some examples of how a Quake server and a Quake client interact. They sumarise an actual game session, observed by intercepting the game packets.

8.6.1 Request informations about the game

Those messages are sent to and from the PUBLIC UDP PORT of the server (which is 26000 by default).

Client sends a Request Informations message,
	with game name QUAKE.
Server sends a Give Informations message,
        with indications on the current map, number of players...

Note: Those messages can be sent to interrogate a given Quake server, without really disturbing it. Of course, if you flood a Quake server with such messages, it might slow down.

8.6.2 Establish a game session

Those messages are sent to and from the PUBLIC UDP PORT of the server (which is 26000 by default).

Client sends a Connect message,
	with game name QUAKE.
Server sends an Accept message,
	containing the identifier of a PERSONAL UDP PORT for the client.
        Client now expects server message coming from the PERSONAL UDP PORT

Warning: after it replied with an Accept message, the game server will only use a PERSONAL UDP PORT to communicate with a given client. The server PUBLIC UDP PORT is not used anymore to talk to that client. It can only be used by clients that are not connected yet.

8.6.3 Prepare the client for the game (precache)

Server sends a big message that contains:
  - the server banner "VERSION 1.01 SERVER (21456 CRC)"
  - a server info message (code 0x0B) with map name, and precached models and sounds
  - an indication that client should move to PRESPAWN state.
Client sends a No Operation (code 0x01)
Client sends a console command (code 0x04) "prespawn", indicating prespawn is finished.
Server sends a big message that contains order to spawn static sounds and static entities
  - For all the static entities described in the .BSP file, 
     One "Spawn Static entity" order, and possibly a Static Sound order.
Client sends a player information message:
  - console order "name PLAYER\n" (where PLAYER is the player name)
  - console order "color 0 0\n"   (shirt and pants colors)
  - console order "spawn " to indicate the client is ready to play.
Server sends a big message:
  - For each possible player in the game (including those not connected):
     a set of orders UPDATE NAME, UPDATE FRAGS, UPDATE COLORS
  - For each of the 64 light style:
     a SET LIGHT STYLE message.
  - an UPDATE STATE message for total and found monsters and secrets
  - a SET ANGLE order, to orient the player's view
  - a CLIENT DATA order, fix the status bar display
  - an order to move to Start 3d Rendering state.
  - a last update NAMe, FRAGS, and COLORS, for the client.
Client sends a console message "begin".

8.6.4 Run the game with the client

Every 50 milliseconds,
Server sends an update message, that contains:
  - an indication of the game time
  - a CLIENT DATA order, to fix the status bar display (mostly useless)
  - an UPDATE ENTITY order for each entity possibly in sight
     if the entity has not changed, only minimal informations will be sent.

Each time the player moves,
Client sends an update message containing:
  - a movement order.

8.7 Making a fake client (aka Bot)

These are only partial informations, but that should be enough for a start.

Connecting the fake client and the Quake server

Here is a simplified connection procedure, to register into a Quake server.

  1. Client sends Connect message to the server, on his public port (usually 26000).
  2. Server replies with an Accept message, that contains the name of an UDP port that is dedicated to the client (usually in the range 1025-1200).
  3. Now, to talk the the server, the client sends messages only to the dedicated UDP port. The public UDP port becomes useless for the client.
  4. From now on, the client must acknowledge all the message parts and message last parts that are sent by the server, otherwise the server will repeat the same message forever and no further progress will be made.
  5. The client waits for a long message that contains and order to set the signon state to 1 (prespawn).
  6. Client sends a message containing a single Console order:
        "prespawn".       (there is no return character at the end)
    
  7. The client waits for a long message that contains and order to set the signon state to 2 (init light effects).
  8. Client sends a message containing three Console orders:
        "name player\n"   ('\n' is the return character)
        "color 0 0\n"     ('\n' is the return character)   
        "spawn "          (there is white space at the end).
    
  9. The client waits for a long message that contains and order to set the signon state to 3 (start 3D rendering).
  10. Client sends a message containing a single Console order:
        "begin"           (there is no return character at the end).
    
  11. From now on, the game is running, and the client starts receiving a flood of update messages.

The fake client at play

How to send movement orders:

  1. Server sends an Update message, containing entity update orders for all the entities in sight.
  2. Client calculates whatever clever moves he wishes.
  3. Client sends an Update message, containing a single Movement order.

How to send orders on the server console:

  1. The client sends a reliable message containing a Console order.

A client receives no update for the entities not in sight, so you cannot cheat and make some radar that sees through walls. BUT you can of course make a simple bot that will turn immediatly toward any enemy coming in his back. And will of course avoid any rockets, or even straffe out of the line of sight of any hostile player.

Beware that the client must rely on himself alone to guess the geometry of the room he is in. The best is of course to load the .BSP for the level, and to keep track of the position of every entities, so as to know which paths are possible, and which are not. But you will soon discover that the .BSP per se is not enough to code a good behavior for a Bot, because there is no trace of the links between the rooms.

Leaving the server

Here is the cleanest way to leave the server:

  1. Client sends a Disconnect order.
Of course, if a client hangs and stops emitting messages, the server will kick him out after a few minutes. But please be kind to the server and log off when you cannot emit anymore. That means that you should catch any exception in your code, and send a disconnect order if it crashes.