Computer networks and the internet for “normal people”

There are a huge amount of buzz words and confusing terms surrounding the internet and computer networking that makes it very difficult to understand for the everyday person. This article attempts to shed some light on the confusion and give you a broad overview of what happens when your computer connects to the internet.

Lets think about a network that most of us navigate and use almost every day without thinking it complex – the road network. The roads provide the way to get from source to destination and the cars provide the mode of transport. Keep that image in your head as you read through the following paragraphs.

Source and Destination

In the road network a source or destination can be thought of as a house or building address. In computing terms this is called an IP address and is of the form 123.123.123.123 – that is 4 sets of numbers from 0 to 255 separated by a dot. Other examples are 192.168.1.23, 10.0.0.1, 62.34.12.67 Just as each house address is unique, each IP address is unique.

How do you get an IP address then? This will differ from computer to computer depending on how yours is setup and what you use it for. One thing for sure is if you want to communicate on a computer network (like the internet) you will definately have an IP address.

Unlike a house address which never changes, your computer IP address may change from time to time. There is a limited set of unique IP addresses so they can’t just give them out to everybody. What normally happens is you lease an IP address for a set period of time while you need to use your computer and then free it up for someone else to use while you don’t need it. This process is hidden away from users so you don’t have to concern yourself with it. If you really want to know – something called a DHCP server is responsible for allocating you an IP address and telling you how long you can lease it for.

Domain Names

You’re probably thinking that you’ve never come across an IP address before yet you’ve been happily surfing the internet for months. Humans aren’t so good at remembering strings of numbers so we use domain names instead. Examples of domain names are www.google.com, www.bbc.co.uk and tilion.org.uk. Notice that they don’t necessarily have to start with www and they don’t all end the same (the endings are actually predefined and vary from country to country apart from the main three which are .com, .net and .org.

A domain name can be thought of as an easy way to remember an IP address. Servers known as Domain Name Servers (DNS for short) are responsible for converting the domain name you enter into a web browser, into the IP address to locate the machine hosting it. Again this happens behind the scenes and most users are totally unaware.

Transport

Unlike the road network, where you can see the cars travelling around from source to destination, a computer network sends electrical signals along wires (or wirelessly). The road network of the computer world is known as TCPIP and describes a way for your communication to travel around. All you really need to know is that if you try to contact another computer at a specified IP address (or domain name) the communication will succeed providing there are no broken roads between you and the destination.

Ports

Remember how an IP address identified a particular computer somewhere in the world. Most computers do more than one thing at once so there needs to be a way to differentiate between the services offered so the computer knows how to reply – this is where ports come in. An IP address can be likened to a house address, so a port can be likened to a way to enter the house (front door, rear door, window, sky light). A computer port is a number from 1 – 65535 (yep, it has a lot of ways to get in!).

Web Servers

One of the most popular services offered by other computers is serving web pages (known as HTTP or Hypertext Transfer Protocol). The HTTP bit is the way you have to speak to the server at the other end so it can understand you – think of it as speaking english or spanish and you must both be speaking the same one. Don’t worry too much about speaking HTTP as your web browser understands how to do that for you. Web servers typically listen on port 80 so when you type http://www.google.com into your web browser, 80 is the default port it uses to communicate with the other computer. http://www.google.com is exactly the same as typing in http://www.google.com:80 (the :80 mean use port 80). If you try to communicate on a different port the chances are noone will be listening or maybe the service that is listening doesn’t speak the same language as you, e.g. http://www.tilion.org.uk:22

Remember that the specified (or default) port is the one used on the server. Your computer is also using a port to talk to the server, but which one you shouldn’t worry about aside from the fact it will NOT be the same port as the server is using.

Other Services

Some other services you’ve probably used already and not realised are;

  • port 110 – POP3 which is used to get your emails
  • port 443 – HTTP in SSL mode which is a secure way to view a web page and is used for sensitive information like banking

Technical readers can probably spot mistakes in the analogies, but I’ve tried to keep technical detail to a minimum for the sake of understandability!

Bits and Bytes

A gathering of data related to bits, byte, endianess, etc …

Number Systems

Decimal

The numbering system we use in everyday life is called decimal (base 10). Each column can use a number between 0 and 9 (10 possibilities).

103 102 101 100
1 8 2 5

The top row is the value of the column and the bottom row is a decimal number. For anyone who’s a little rusty on their maths – xy means x times x, repeated y times. So, 104 = 10 * 10 * 10 * 10.

The decimal number 1825 is one thousand, eight hundred and twenty five or

(1 * 103) + (8 * 102) + (2 * 101) + (5 * 100)
(1 * 10 * 10 * 10) + (8 * 10 * 10) + (2 * 10) + (5 * 1)
(1 * 1000) + (8 * 100) + (2 * 10) + (5 * 1)
1000 + 800 + 20 + 5 = 1825

Remember that any number to the power of 0 is 1, so 100 = 1

Binary

Computers use the binary system (base 2), where each column can have a 0 or a 1 (2 possibilities). This works well for machinery as they can use positive and negative electrical signals to represents 0 and 1.

23 22 21 20
1 0 1 1

The binary number 1011 is equivalent to 11 in decimal.

(1 * 23) + (0 * 22) + (1 * 21) + (1 * 20)
(1 * 2 * 2 * 2) + (0 * 2 * 2) + (1 * 2) + (1 * 1)
(1 * 8) + (0 * 4) + (1 * 2) + (1 * 1)
8 + 0 + 2 + 1 = 11

As you can see you need a lot more columns to represent a number in binary that you do in decimal. If you had to convert 1825 into decimal it would be

011100100001
210 + 29 + 28 + 25 + 20
1024 + 512 + 256 + 32 + 1 = 1825

When talking about binary each column is refered to as a bit (so each 0 or 1 is a bit) and each collection of 8 bits is refered to as a byte. A kilobyte or kb is 1024 bytes. A megabyte or Mb is 1024 kilobytes or 1048576 bytes. Now that’s a lot of 1’s and 0’s !

Hexadecimal

Binary is a very verbose format so it’s common for binary data to be represented using hexadecimal (hex for short) which is base 16. Thats means every column can contain one of 16 unique values. As we only have 10 numerical digits, hex uses some letters from the alphabet to give a full set of 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F

163 162 161 160
1 b 3 a

The hex number 1b3a is equivalent to 6970 in decimal.

For the following calculations the alphabetical characters maps to decimal equivalents; a = 10, b = 11, c = 12, d = 13, e = 14 and f = 15

(1 * 163) + (11 * 162) + (3 * 161) + (10 * 160)
(1 * 16 * 16 * 16) + (11 * 16 * 16) + (3 * 16) + (10 * 1)
(1 * 4096) + (11 * 256) + (3 * 16) + (10 * 1)
4096 + 2816 + 48 + 10 = 6970

So, how can hex make binary look more attractive? Every 4 bits from a binary number can be represented as 1 column from a hex number! Lets take 011100100001 from earlier and break it up;

011100100001
0111 0010 0001 (in binary)
7 2 1 (in hex)

So 011100100001 in binary becomes 721 in hex. Another example

1110011011010010
1110 0110 1101 0010 (in binary)
e 6 d 2 (in hex)

So 1110011011010010 in binary becomes e6d2 in hex.

Endianess

Endianess referes to which end (left or right) the most significant bit lies. Sparc processors use big endian storage format which stores the most significant byte first. Intel processors (common PC) store data in little endian.

Java uses big endian format whereas a lot of C code I’ve come across uses little endian. If you need to read something in little endian using java it’s best to use the NIO suit.

ByteBuffer.wrap( buffer ).order( ByteOrder.LITTLE_ENDIAN ).getLong();

Decimal (and above discussions on binary and hexadecimal) uses big endian as it stores the largest bit first (on the left hand side). A binary example of this

Big Endian     00110110 = 32 + 16 + 4 + 2 = 54
Little Endian  00110110 = 4 + 8 + 32 + 64 = 108

There is a more thorough description and good C example highlighting the problems with endianess differences located at Wikipedia

Hex Editing

A good Guide to Hex Editing

Volkswagen Passat

Car details;

Year 2001/2002
Make/Model Volkswagen Passat
Engine 1.9TDI 130 Sport AVF
Gearbox 6 speed

Forum post on VW Passat Basic Service

Front coil spring 8D0 411 105 CT (1 grey/2 blue paint marks).

The front damper is 3B0 413 031 R and is available from a number of sources, personally I would fit a Sachs 557 837 or Boge 33-117-A. The rear dampers are Boge 27-C11-F and Sachs 556 277.

Some useful forums;

Reading/writing mixed endian binary files in java

Shameless reproduction of content from http://www.heatonresearch.com/articles/22/page2.html for my own reference.

The BinaryFile class can be seen in BinaryFile.java. To use the BinaryFile class, create a RandomAccessFile class to the file that you would like to work with. This file can be opened for read or write access. Then construct a BinaryFile object, passing in your RandomAccessFile object to the constructor. The following two lines prepare to read/write to a file called “test.dat”.

file=new RandomAccessFile("test.dat","rw");
bin=new BinaryFile(file);

Once this is complete, you can call the various methods provided, to access different data types. The methods to access the various data types are prefixed with either read or write and then the type. For example, the method to read a fixed length string is readFixedLengthString. The complete class is shown in Listing 1.

Listing 1: Reading Java Binary Files

import java.io.*;

/**
 * @author Jeff Heaton(<a href="http://www.jeffheaton.com" title="http://www.jeffheaton.com">http://www.jeffheaton.com</a>)
 * @version 1.0
 */
class BinaryFile
{

  /**
   * Use this constant to specify big-endian integers.
   */
  public static final short BIG_ENDIAN = 1;

  /**
   * Use this constant to specify litte-endian constants.
   */
  public static final short LITTLE_ENDIAN = 2;

  /**
   * The underlying file.
   */
  protected RandomAccessFile _file;

  /**
   * Are we in LITTLE_ENDIAN or BIG_ENDIAN mode.
   */
  protected short _endian;

  /**
   * Are we reading signed or unsigned numbers.
   */
  protected boolean _signed;

  /**
   * The constructor.  Use to specify the underlying file.
   *
   * @param f The file to read/write from/to.
   */
  public BinaryFile(RandomAccessFile f)
  {
    _file = f;
    _endian = LITTLE_ENDIAN;
    _signed = false;
  }

  /**
   * Set the endian mode for reading integers.
   *
   * @param i Specify either LITTLE_ENDIAN or BIG_ENDIAN.
   * @exception java.lang.Exception Will be thrown if this method is 
   * not passed either BinaryFile.LITTLE_ENDIAN or BinaryFile.BIG_ENDIAN.
   */
  public void setEndian(short i) throws Exception
  {
    if ((i == BIG_ENDIAN) || (i == LITTLE_ENDIAN))
      _endian = i;
    else
      throw (new Exception(
          "Must be BinaryFile.LITTLE_ENDIAN or BinaryFile.BIG_ENDIAN"));
  }

  /**
   * Returns the endian mode.  Will be either BIG_ENDIAN or LITTLE_ENDIAN.
   *
   * @return BIG_ENDIAN or LITTLE_ENDIAN to specify the current endian mode.
   */
  public int getEndian()
  {
    return _endian;
  }

  /**
   * Sets the signed or unsigned mode for integers.  true for signed, false for unsigned.
   *
   * @param b True if numbers are to be read/written as signed, false if unsigned.
   */
  public void setSigned(boolean b)
  {
    _signed = b;
  }

  /**
   * Returns the signed mode.
   *
   * @return Returns true for signed, false for unsigned.
   */
  public boolean getSigned()
  {
    return _signed;
  }

  /**
   * Reads a fixed length ASCII string.
   *
   * @param length How long of a string to read.
   * @return The number of bytes read.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public String readFixedString(int length) throws java.io.IOException
  {
    String rtn = "";

    for (int i = 0; i &lt; length; i++)
      rtn += (char) _file.readByte();
    return rtn;
  }

  /**
   * Writes a fixed length ASCII string.  Will truncate the string if it does not fit in the specified buffer.
   *
   * @param str The string to be written.
   * @param length The length of the area to write to.  Should be larger than the length of the string being written.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public void writeFixedString(String str, int length)
      throws java.io.IOException
  {
    int i;

    // trim the string back some if needed

    if (str.length() &gt; length)
      str = str.substring(0, length);

    // write the string

    for (i = 0; i &lt; str.length(); i++)
      _file.write(str.charAt(i));

    // buffer extra space if needed

    i = length - str.length();
    while ((i--) &gt; 0)
      _file.write(0);
  }

  /**
   * Reads a string that stores one length byte before the string.  
   * This string can be up to 255 characters long.  Pascal stores strings this way.
   *
   * @return The string that was read.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public String readLengthPrefixString() throws java.io.IOException
  {
    short len = readUnsignedByte();
    return readFixedString(len);
  }

  /**
   * Writes a string that is prefixed by a single byte that specifies the length of the string.  This is how Pascal usually stores strings.
   *
   * @param str The string to be written.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public void writeLengthPrefixString(String str) throws java.io.IOException
  {
    writeByte((byte) str.length());
    for (int i = 0; i &lt; str.length(); i++)
      _file.write(str.charAt(i));
  }

  /**
   * Reads a fixed length string that is zero(NULL) terminated.  This is a type of string used by C/C++.  For example char str[80].
   *
   * @param length The length of the string.

   * @return The string that was read.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public String readFixedZeroString(int length) throws java.io.IOException
  {
    String rtn = readFixedString(length);
    int i = rtn.indexOf(0);
    if (i != -1)
      rtn = rtn.substring(0, i);
    return rtn;
  }

  /**
   * Writes a fixed length string that is zero terminated.  This is the format generally used by C/C++ for string storage.
   *
   * @param str The string to be written.
   * @param length The length of the buffer to receive the string.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public void writeFixedZeroString(String str, int length)
      throws java.io.IOException
  {
    writeFixedString(str, length);
  }

  /**
   * Reads an unlimited length zero(null) terminated string.
   *
   * @return The string that was read.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public String readZeroString() throws java.io.IOException
  {
    String rtn = "";
    char ch;

    do
    {
      ch = (char) _file.read();
      if (ch != 0)
        rtn += ch;
    } while (ch != 0);
    return rtn;
  }

  /**
   * Writes an unlimited zero(NULL) terminated string to the file.
   *
   * @param str The string to be written.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public void writeZeroString(String str) throws java.io.IOException
  {
    for (int i = 0; i &lt; str.length(); i++)
      _file.write(str.charAt(i));
    writeByte((byte) 0);
  }

  /**
   * Internal function used to read an unsigned byte.  External classes should use the readByte function.
   *
   * @return The byte, unsigned, as a short.
   * @exception java.io.IOException If an IO exception occurs.
   */
  protected short readUnsignedByte() throws java.io.IOException
  {
    return (short) (_file.readByte() & 0xff);
  }

  /**
   * Reads an 8-bit byte.  Can be signed or unsigned depending on the signed property.
   *
   * @return A byte stored in a short.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public short readByte() throws java.io.IOException
  {
    if (_signed)
      return (short) _file.readByte();
    else
      return (short) _file.readUnsignedByte();
  }

  /**
   * Writes a single byte to the file.
   *
   * @param b The byte to be written.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public void writeByte(short b) throws java.io.IOException
  {
    _file.write(b & 0xff);
  }

  /**
   * Reads a 16-bit word.  Can be signed or unsigned depending on the signed property.  
   * Can be little or big endian depending on the endian property.
   *
   * @return A word stored in an int.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public int readWord() throws java.io.IOException
  {
    short a, b;
    int result;

    a = readUnsignedByte();
    b = readUnsignedByte();

    if (_endian == BIG_ENDIAN)
      result = ((a &lt;&lt; 8) | b);
    else
      result = (a | (b &lt;&lt; 8));

    if (_signed)
      if ((result & 0x8000) == 0x8000)
        result = -(0x10000 - result);

    return result;
  }

  /**
   * Write a word to the file.
   *
   * @param w The word to be written to the file.
   * @exception java.io.IOException If an IO exception occurs.
   */

  public void writeWord(int w) throws java.io.IOException
  {
    if (_endian == BIG_ENDIAN)
    {
      _file.write((w & 0xff00) &gt;&gt; 8);
      _file.write(w & 0xff);
    } else
    {
      _file.write(w & 0xff);
      _file.write((w & 0xff00) &gt;&gt; 8);
    }
  }

  /**
   * Reads a 32-bit double word.  Can be signed or unsigned 
   * depending on the signed property.  Can be little or big endian depending on the endian property.
   *
   * @return A double world stored in a long.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public long readDWord() throws java.io.IOException
  {
    short a, b, c, d;
    long result;

    a = readUnsignedByte();
    b = readUnsignedByte();
    c = readUnsignedByte();
    d = readUnsignedByte();

    if (_endian == BIG_ENDIAN)
      result = ((a &lt;&lt; 24) | (b &lt;&lt; 16) | (c &lt;&lt; 8) | d);
    else
      result = (a | (b &lt;&lt; 8) | (c &lt;&lt; 16) | (d &lt;&lt; 24));

    if (_signed)
      if ((result & 0x80000000L) == 0x80000000L)
        result = -(0x100000000L - result);

    return result;
  }

  /**
   * Writes a double word to the file.
   *
   * @param d The double word to be written to the file.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public void writeDWord(long d) throws java.io.IOException
  {
    if (_endian == BIG_ENDIAN)
    {
      _file.write((int) (d & 0xff000000) &gt;&gt; 24);
      _file.write((int) (d & 0xff0000) &gt;&gt; 16);
      _file.write((int) (d & 0xff00) &gt;&gt; 8);
      _file.write((int) (d & 0xff));
    } else
    {
      _file.write((int) (d & 0xff));
      _file.write((int) (d & 0xff00) &gt;&gt; 8);
      _file.write((int) (d & 0xff0000) &gt;&gt; 16);
      _file.write((int) (d & 0xff000000) &gt;&gt; 24);
    }
  }

  /**
   * Allows the file to be aligned to a specified byte boundary.  
   * For example, if a 4(double word) is specified, the file pointer will be 
   * moved to the next double word boundary.
   *
   * @param a The byte-boundary to align to.
   * @exception java.io.IOException If an IO exception occurs.
   */
  public void align(int a) throws java.io.IOException
  {
    if ((_file.getFilePointer() % a) &gt; 0)
    {
      long pos = _file.getFilePointer() / a;
      _file.seek((pos + 1) * a);
    }
  }
}

Quake 3 demo format

I WILL SORT THE FORMATTING OUT IN THIS POST EVENTUALLY, BEAR WITH ME FOR NOW OR USE THE ORIGINAL LINK BELOW.

This document describes the demo format that later releases of quake 3 use. It is not my work, but that of Andrey Nazarov. I am mirroring it here as it is hard to find and makes it easier for me to reference and append to if required. Here is the original Quake 3 Demo Specification and a mirror of the Quake 3 Demo Specification.

Quake III Arena Demo File Specifications

File formats: *.dm_66, *.dm_67, *.dm_68

  1. About
  2. Sizebuf_t structure
  3. Demofile format
  4. MSG_ReadBits and MSG_WriteBits
  5. Common MSG_* functions
  6. Delta encoding of game structures
  7. Huffman encoding
  8. Server to Client commands
  9. Operations with gameState_t
  10. Parsing whole demo file
  11. Thanks to

1. About

This document contains complete Quake III Arena Demo File specifications, useful for programmers working in C or C++ demo players/editors. It introduces demo message encoding/decoding routines that allow parsing and writing Quake III Arena Demo Files.

Note this code doesn’t provide full Quake III Arena network protocol support, as it uses much more encryption. All network-only stuff was removed from this document.

This code is redistributed under the terms of GNU General Public License

You can also download this example application (source code + binary) from http://skuller-vidnoe.narod.ru/downloads/dm_68.zip

It contains all source code present in this document and all necessary headers. The only thing this app does is dumping obituaries and some other info from a *.dm_68 file.

2. Sizebuf_t structure

All MSG_* functions operate with the sizebuf_t structure, which holds all necessary information about current buffer state and is used both when reading and writing bits to the buffer. Normally nothing outside MSG_* functions should modify this structure.

typedef struct sizebuf_s {
  qboolean allowoverflow; // if false, do a Com_Error
  qboolean overflowed; // set to true if the buffer size failed
  qboolean uncompressed; // don't do Huffman encoding, write raw bytes
  byte *data; // pointer to message buffer, set by MSG_Init
  int maxsize; // size in bytes of message buffer, set by MSG_Init
  int cursize; // number of bytes written to the buffer, set by MSG_WriteBits
  int readcount; // number of bytes read from the buffer, set by MSG_ReadBits
  int bit; // number of bits written to or read from the buffer
} sizebuf_t;

3. Demofile format

Actually Quake III Demo File is a version of server-to-client communication protocol, written
to a disk in the following format:

Demo block structure:

Size, bytes

Description

4

Message
sequence, <seq>

4

Message
length, <msglen>

<msglen>

Message
data

 

Demo
is parsed until <seq> and <msglen> are not equal to -1 (end block), which indicates demofile EOF. <msglen>
should be always less than
MAX_MSGLEN.

 

#define MAX_MSGLEN 0x4000 // max length of demo
message

 

Sample routine for demo file
parsing:

 

FILE *demofile;

int demoMessageSequence; // used for delta decoding

 

/*

=====================

Parse_NextDemoMessage

 


Read next message from demo file and parse it


Return qfalse if demo EOF reached or error occured

=====================

*/

qboolean Parse_NextDemoMessage( void ) {

sizebuf_t msg;

byte buffer[MAX_MSGLEN];

int len;

int seq;

 

if( fread( &seq, 1, 4, demofile ) != 4 ) {

Com_Printf(
"Demo file was truncated\n" );

return qfalse;

}

if( fread( &len, 1, 4, demofile ) != 4 ) {

Com_Printf(
"Demo file was truncated\n" );

return qfalse;

}

 

if( seq == -1 || len == -1 ) {

return qfalse; // demo
EOF reached

}

 

MSG_Init(
&msg, buffer, sizeof( buffer ) );

 

demoMessageSequence
= LittleLong( seq );

msg.cursize
= LittleLong( len );

 

if( msg.cursize <= 0 || msg.cursize >=
msg.maxsize ) {

Com_Error(
ERR_DROP, "Illegal demo message length" );

}

 

if( fread( msg.data, 1, msg.cursize, demofile ) !=
msg.cursize ) {

Com_Printf(
"Demo file was truncated\n" );

return qfalse;

}

 

Parse_DemoMessage(
&msg ); // parse the message

 

return qtrue;

}

 

 

4. MSG_ReadBits and MSG_WriteBits

 

The
basic functions for performing i/o operations on message data are MSG_WriteBits and MSG_ReadBits,
which do Huffman encoding/decoding of the data bitstream using Huff_* code. If uncompressed
flag is set on the sizebuf, then raw bytes are used, i.e. bits value is rounded to a full byte (q2-style).
Currently this is not used when working with demo files.

 

/*

============

MSG_WriteBits

============

*/

void MSG_WriteBits( sizebuf_t *msg, int value, int bits
) {

int remaining;

int i;

byte *buf;

 

if( msg->maxsize – msg->cursize < 4 ) {

msg->overflowed
= qtrue;

return;

}

 

if( !bits || bits < -31 || bits > 32 ) {

Com_Error(
ERR_DROP, "MSG_WriteBits: bad bits %i", bits );

}

 

if( bits < 0 ) {

bits
= -bits;

}

 

if( msg->uncompressed ) {

if( bits <= 8 ) {

buf
= MSG_GetSpace( msg, 1 );

buf[0]
= value;

}
else if( bits <= 16 ) {

buf
= MSG_GetSpace( msg, 2 );

buf[0]
= value & 0xFF;

buf[1]
= value >> 8;

}
else if( bits <= 32 ) {

buf
= MSG_GetSpace( msg, 4 );

buf[0]
= value & 0xFF;

buf[1]
= (value >> 8) & 0xFF;

buf[2]
= (value >> 16) & 0xFF;

buf[3]
= value >> 24;

}

return;

}

 

value
&= 0xFFFFFFFFU >> (32 – bits);

remaining
= bits & 7;

 

for( i=0 ; i<remaining ; i++ ) {

if( !(msg->bit & 7) ) {

msg->data[msg->bit
>> 3] = 0;

}

msg->data[msg->bit
>> 3] |= (value & 1) << (msg->bit & 7);

msg->bit++;

value
>>= 1;

}

bits
-= remaining;

 

if( bits > 0 ) {

for( i=0 ; i<(bits+7)>>3 ; i++ ) {

Huff_EmitByte(
value & 255, msg->data, &msg->bit );

value
>>= 8;

}

}

 

msg->cursize
= (msg->bit >> 3) + 1;

}

 

 

/*

============

MSG_ReadBits

============

*/

int MSG_ReadBits( sizebuf_t *msg, int bits ) {

int i;

int val;

int bitmask = 0;

int remaining;

qboolean
extend = qfalse;

 

if( !bits || bits < -31 || bits > 32 ) {

Com_Error(
ERR_DROP, "MSG_ReadBits: bad bits %i", bits );

}

 

if( bits < 0 ) {

bits
= -bits;

extend
= qtrue;

}

 

if( msg->uncompressed ) {

if( bits <= 8 ) {

bitmask
= (unsigned char)msg->data[msg->readcount];

msg->readcount++;

msg->bit
+= 8;

}
else if( bits <= 16 ) {

bitmask
= (unsigned short)(msg->data[msg->readcount]

+
(msg->data[msg->readcount+1] << 8));

msg->readcount
+= 2;

msg->bit
+= 16;

}
else if( bits <= 32 ) {

bitmask
= msg->data[msg->readcount]

+
(msg->data[msg->readcount+1] << 8)

+
(msg->data[msg->readcount+2] << 16)

+
(msg->data[msg->readcount+3] << 24);

msg->readcount
+= 4;

msg->bit
+= 32;

}

}
else {

remaining
= bits & 7;

 

for( i=0 ; i<remaining ; i++ ) {

val
= msg->data[msg->bit >> 3] >> (msg->bit & 7);

msg->bit++;

bitmask
|= (val & 1) << i;

}

for( i=0 ; i<bits-remaining ; i+=8 ) {

val
= Huff_GetByteEx( msg->data, &msg->bit );

bitmask
|= val << (i + remaining);

}

msg->readcount
= (msg->bit >> 3) + 1;

}

 

if( extend ) {

if( bitmask & (1 << (bits – 1)) ) {

bitmask
|= ~((1 << bits) – 1);

}

}

 

return bitmask;

}

 

There is a wrapper interface for
writing/reading bytes, shorts and ints. It is implemented as macros and inline
functions.

 

#define MSG_WriteByte(msg,c) MSG_WriteBits(msg,c,8)

#define MSG_WriteShort(msg,c) MSG_WriteBits(msg,c,16)

#define MSG_WriteSignedShort(msg,c) MSG_WriteBits(msg,c,-16)

#define MSG_WriteLong(msg,c) MSG_WriteBits(msg,c,32)

 

static ID_INLINE int
MSG_ReadByte( sizebuf_t *msg ) {

int c = MSG_ReadBits( msg, 8 ) & 0xFF;

 

if( msg->readcount > msg->cursize ) {

return -1;

}

 

return c;

}

 

static ID_INLINE int
MSG_ReadShort( sizebuf_t *msg ) {

int c = MSG_ReadBits( msg, 16 );

 

if( msg->readcount > msg->cursize ) {

return -1;

}

 

return c;

}

 

static ID_INLINE int
MSG_ReadSignedShort( sizebuf_t *msg ) {

int c = MSG_ReadBits( msg, -16 );

 

if( msg->readcount > msg->cursize ) {

return -1;

}

 

return c;

}

 

static ID_INLINE int
MSG_ReadLong( sizebuf_t *msg ) {

int c = MSG_ReadBits( msg, 32 );

 

if( msg->readcount > msg->cursize ) {

return -1;

}

 

return c;

}

 

5. Common MSG_* functions

 

// initialize sizebuf_t and
associate it with specified buffer

void MSG_Init( sizebuf_t *msg, byte *buffer, int size );

 

// initialize sizebuf_t,
associate it with specified buffer and set uncompressed flag

void MSG_InitRaw( sizebuf_t *msg, byte *buffer, int size );

 

// clear sizebuf_t, prepare it
for writing and unset uncompressed flag

void MSG_Clear( sizebuf_t *msg );

 

// unset uncompressed flag for
sizebuf_t

void MSG_SetBitstream( sizebuf_t *msg );

 

// strcat raw data to the
sizebuf_t

void MSG_WriteRawData( sizebuf_t *msg, const void *data, int
length );

 

// prepare sizebuf_t for writing
and set uncompressed flag

void MSG_BeginWriting( sizebuf_t *msg );

 

// write data as bytes

void MSG_WriteData( sizebuf_t *msg, const void *data, int
length );

 

// write string as bytes up to
MAX_STRING_CHARS

void MSG_WriteString( sizebuf_t *msg, const char *string );

 

// write string as bytes up to BIG_INFO_STRING

void MSG_WriteBigString( sizebuf_t *msg, const char *string );

 

// prepare sizebuf_t for reading and
set uncompressed flag

void MSG_BeginReading( sizebuf_t *msg );

 

// read data as bytes

void MSG_ReadData( sizebuf_t *msg, void *data, int len
);

 

// read string as bytes up to
MAX_STRING_CHARS

char *MSG_ReadString( sizebuf_t *msg );

 

// read string as bytes up to BIG_INFO_STRING

char *MSG_ReadBigString( sizebuf_t *msg );

 

/*

============

MSG_GetSpace

============

*/

static void *MSG_GetSpace( sizebuf_t *msg, int length ) {

void *data;

if( msg->cursize + length > msg->maxsize )
{

if( !msg->allowoverflow ) {

Com_Error(
ERR_FATAL, "MSG_GetSpace: overflow without allowoverflow set" );

}

if( length > msg->maxsize ) {

Com_Error(
ERR_FATAL, "MSG_GetSpace: %i is > full buffer size", length );

}

MSG_Clear(
msg );

msg->overflowed
= qtrue;

}

 

data
= msg->data + msg->cursize;

msg->cursize
+= length;

msg->bit
+= length << 3;

 

return data;

}

 

/*

============

MSG_Init

============

*/

void MSG_Init( sizebuf_t *msg, byte *buffer, int size ) {

memset(
msg, 0, sizeof( *msg ) );

msg->data
= buffer;

msg->maxsize
= size;

}

 

/*

============

MSG_InitRaw

============

*/

void MSG_InitRaw( sizebuf_t *msg, byte *buffer, int size ) {

memset(
msg, 0, sizeof( *msg ) );

msg->data
= buffer;

msg->maxsize
= size;

msg->uncompressed
= qtrue;

}

 

/*

============

MSG_Clear

============

*/

void MSG_Clear( sizebuf_t *msg ) {

msg->cursize
= 0;

msg->overflowed
= qfalse;

msg->uncompressed
= qfalse;

msg->bit
= 0;

}

 

/*

============

MSG_SetBitstream

============

*/

void MSG_SetBitstream( sizebuf_t *msg ) {

msg->uncompressed
= qfalse;

}

 

/*

============

MSG_WriteRawData

============

*/

void MSG_WriteRawData( sizebuf_t *msg, const void *data, int
length ) {

if( length > 0 ) {

memcpy(
MSG_GetSpace( msg, length ), data, length );

}

}

 

/*

============

MSG_BeginWriting

============

*/

void MSG_BeginWriting( sizebuf_t *msg ) {

msg->uncompressed
= qtrue;

msg->overflowed
= 0;

msg->cursize
= 0;

msg->bit
= 0;

}

 

/*

============

MSG_WriteData

============

*/

void MSG_WriteData( sizebuf_t *msg, const void *data, int
length ) {

int i;

 

for( i=0 ; i<length ; i++ ) {

MSG_WriteByte(
msg, ((byte *)data)[i] );

}

}

 

/*

============

MSG_WriteString

============

*/

void MSG_WriteString( sizebuf_t *msg, const char *string ) {

char buffer[MAX_STRING_CHARS];

int i;

int len;

 

if( !string ) {

MSG_WriteByte(
msg, 0 );

return;

}

 

len
= strlen( string );

 

if( len >= sizeof(
buffer ) ) {

Com_Printf(
"MSG_WriteString: MAX_STRING_CHARS\n" );

MSG_WriteByte(
msg, 0 );

return;

}

 

Q_strncpyz(
buffer, string, sizeof( buffer ) );

 

for( i=0 ; i<len ; i++ ) {

if( buffer[i] > 127 ) {

buffer[i]
= ‘.’;

}

}

 

for( i=0 ; i<=len ; i++ ) {

MSG_WriteByte(
msg, buffer[i] );

}

}

 

/*

============

MSG_WriteString

============

*/

void MSG_WriteBigString( sizebuf_t *msg, const char *string ) {

char buffer[BIG_INFO_STRING];

int i;

int len;

 

if( !string ) {

MSG_WriteByte(
msg, 0 );

return;

}

 

len
= strlen( string );

 

if( len >= sizeof(
buffer ) ) {

Com_Printf(
"MSG_WriteString: BIG_INFO_STRING\n" );

MSG_WriteByte(
msg, 0 );

return;

}

 

Q_strncpyz(
buffer, string, sizeof( buffer ) );

 

for( i=0 ; i<len ; i++ ) {

if( buffer[i] > 127 ) {

buffer[i]
= ‘.’;

}

}

 

for( i=0 ; i<=len ; i++ ) {

MSG_WriteByte(
msg, buffer[i] );

}

}

 

/*

============

MSG_BeginReading

============

*/

void MSG_BeginReading( sizebuf_t *msg ) {

msg->readcount
= 0;

msg->bit
= 0;

msg->uncompressed
= qtrue;

}

 

/*

============

MSG_ReadData

============

*/

void MSG_ReadData( sizebuf_t *msg, void *data, int len
) {

int i;

int c;

 

for( i=0 ; i<len ; i++ ) {

c
= MSG_ReadByte( msg );

if( c == -1 ) {

break;

}

if( data ) {

((byte
*)data)[i] = c;

}

}

}

 

/*

============

MSG_ReadString

============

*/

char *MSG_ReadString( sizebuf_t *msg ) {

static char string[MAX_STRING_CHARS];

int i;

int c;

 

for( i=0 ; i<sizeof(
string )-1; i++ ) {

c
= MSG_ReadByte( msg );

if( c == -1 || c == 0 ) {

break;

}

if( c == ‘%’ || c > 127 ) {

c
= ‘.’;

}

string[i]
= c;

}

 

string[i]
= 0;

 

return string;

}

 

/*

============

MSG_ReadBigString

============

*/

char *MSG_ReadBigString( sizebuf_t *msg ) {

static char string[BIG_INFO_STRING];

int i;

int c;

 

for( i=0 ; i<sizeof(
string )-1; i++ ) {

c
= MSG_ReadByte( msg );

if( c == -1 || c == 0 ) {

break;

}

if( c == ‘%’ || c > 127 ) {

c
= ‘.’;

}

string[i]
= c;

}

 

string[i]
= 0;

 

return string;

}

 

6. Delta encoding of game structures

 

For network bandwidth saving the following
entityState_t and playerState_t structures are delta compressed when
transmitted over the network:

 

// playerState_t is the
information needed by both the client and server

// to predict player motion and
actions

// nothing outside of pmove
should modify these, or some degree of prediction error

// will occur

 

// you can’t add anything to this
without modifying the code in msg.c

 

// playerState_t is a full
superset of entityState_t as it is used by players,

// so if a playerState_t is
transmitted, the entityState_t can be fully derived

// from it.

typedef struct playerState_s {

int commandTime; //
cmd->serverTime of last executed command

int pm_type;

int bobCycle; // for
view bobbing and footstep
generation

int pm_flags; //
ducked, jump_held, etc

int pm_time;

 

vec3_t origin;

vec3_t velocity;

int weaponTime;

int gravity;

int speed;

int delta_angles[3]; // add to
command angles to get view direction

//
changed by spawns, rotating objects, and teleporters

 

int groundEntityNum;
// ENTITYNUM_NONE = in air

 

int legsTimer; // don’t
change low priority animations until this runs out

int legsAnim; // mask
off ANIM_TOGGLEBIT

 

int torsoTimer; // don’t
change low priority animations until this runs out

int torsoAnim; // mask
off ANIM_TOGGLEBIT

 

int movementDir; // a number 0 to 7
that represents the reletive angle

//
of movement to the view angle (axial and diagonals)

//
when at rest, the value will remain unchanged

//
used to twist the legs during strafing

 

vec3_t grapplePoint; // location of grapple to pull towards
if PMF_GRAPPLE_PULL

 

int eFlags; //
copied to entityState_t->eFlags

 

int eventSequence; // pmove
generated events

int events[MAX_PS_EVENTS];

int eventParms[MAX_PS_EVENTS];

 

int externalEvent; // events set
on player from another source

int externalEventParm;

int externalEventTime;

 

int clientNum; // ranges
from 0 to MAX_CLIENTS-1

int weapon; //
copied to entityState_t->weapon

int weaponstate;

 

vec3_t viewangles; // for fixed views

int viewheight;

 

// damage feedback

int damageEvent; // when it changes,
latch the other parms

int damageYaw;

int damagePitch;

int damageCount;

 

int stats[MAX_STATS];

int persistant[MAX_PERSISTANT]; // stats that
aren’t cleared on death

int powerups[MAX_POWERUPS]; // level.time
that the powerup runs out

int ammo[MAX_WEAPONS];

 

int generic1;

int loopSound;

int jumppad_ent; // jumppad entity
hit this frame

 

// not communicated over the net at all

int ping; //
server to game info for scoreboard

int pmove_framecount; // FIXME: don’t
transmit over the network

int jumppad_frame;

int entityEventSequence;

} playerState_t;

 

// if entityState->solid ==
SOLID_BMODEL, modelindex is an inline model number

#define SOLID_BMODEL 0xffffff

 

typedef enum {

TR_STATIONARY,

TR_INTERPOLATE, // non-parametric, but interpolate between snapshots

TR_LINEAR,

TR_LINEAR_STOP,

TR_SINE, // value = base + sin( time / duration ) * delta

TR_GRAVITY

} trType_t;

 

typedef struct {

trType_t trType;

int trTime;

int trDuration; //
if non 0, trTime + trDuration = stop time

vec3_t trBase;

vec3_t trDelta; // velocity, etc

} trajectory_t;

 

// entityState_t is the
information conveyed from the server

// in an update message about
entities that the client will

// need to render in some way

// Different eTypes may use the
information in different ways

// The messages are delta
compressed, so it doesn’t really matter if

// the structure size is fairly
large

 

typedef struct
entityState_s {

int number; //
entity index

int eType; //
entityType_t

int eFlags;

 

trajectory_t pos; // for calculating position

trajectory_t apos; // for calculating angles

 

int time;

int time2;

 

vec3_t origin;

vec3_t origin2;

 

vec3_t angles;

vec3_t angles2;

 

int otherEntityNum; // shotgun
sources, etc

int otherEntityNum2;

 

int groundEntityNum; // -1 = in air

 

int constantLight; // r +
(g<<8) + (b<<16) + (intensity<<24)

int loopSound; //
constantly loop this sound

 

int modelindex;

int modelindex2;

int clientNum; // 0 to
(MAX_CLIENTS – 1), for players and corpses

int frame;

 

int solid; // for
client side prediction, trap_linkentity sets this properly

 

int event; //
impulse events — muzzle flashes, footsteps, etc

int eventParm;

 

// for players

int powerups; // bit
flags

int weapon; //
determines weapon and flash model, etc

int legsAnim; // mask
off ANIM_TOGGLEBIT

int torsoAnim; // mask
off ANIM_TOGGLEBIT

 

int generic1;

} entityState_t;

 

The following tables define offsets
of each field in these structures and size of it (in bits):

 

/*

======================================================================================

 


OFFSET TABLES FOR MAIN GAME STRUCTURES

 


If you want something from playerState_t or entityState structures to
be


transmitted on the network, just insert a field into one of the
following tables.

 


For network bandwidth saving, all fields are sorted in order from
highest


modification frequency (during active gameplay) to lowest.

 

======================================================================================

*/

 

typedef struct {

int offset;

int bits; // bits >
0 –> unsigned integer

// bits
= 0 –> float value

// bits < 0
–> signed integer

} field_t;

 

// field declarations

#define PS_FIELD(n,b) { ((int)&(((playerState_t
*)0)->n)), b }

#define ES_FIELD(n,b) { ((int)&(((entityState_t
*)0)->n)), b }

 

 

// field data accessing

#define FIELD_INTEGER(s) (*(int *)((byte *)(s)+field->offset))

#define FIELD_FLOAT(s) (*(float *)((byte
*)(s)+field->offset))

 

//

// playerState_t

//

static field_t psTable[] = {

PS_FIELD(
commandTime, 32 ),

PS_FIELD(
origin[0], 0 ),

PS_FIELD(
origin[1], 0 ),

PS_FIELD(
bobCycle, 8 ),

PS_FIELD(
velocity[0], 0 ),

PS_FIELD(
velocity[1], 0 ),

PS_FIELD(
viewangles[1], 0 ),

PS_FIELD(
viewangles[0], 0 ),

PS_FIELD(
weaponTime, -16 ),

PS_FIELD(
origin[2], 0 ),

PS_FIELD(
velocity[2], 0 ),

PS_FIELD(
legsTimer, 8 ),

PS_FIELD(
pm_time, -16 ),

PS_FIELD(
eventSequence, 16 ),

PS_FIELD(
torsoAnim, 8 ),

PS_FIELD(
movementDir, 4 ),

PS_FIELD(
events[0], 8 ),

PS_FIELD(
legsAnim, 8 ),

PS_FIELD(
events[1], 8 ),

PS_FIELD(
pm_flags, 16 ),

PS_FIELD(
groundEntityNum, 10 ),

PS_FIELD(
weaponstate, 4 ),

PS_FIELD(
eFlags, 16 ),

PS_FIELD(
externalEvent, 10 ),

PS_FIELD(
gravity, 16 ),

PS_FIELD(
speed, 16 ),

PS_FIELD(
delta_angles[1], 16 ),

PS_FIELD(
externalEventParm, 8 ),

PS_FIELD(
viewheight, -8 ),

PS_FIELD(
damageEvent, 8 ),

PS_FIELD(
damageYaw, 8 ),

PS_FIELD(
damagePitch, 8 ),

PS_FIELD(
damageCount, 8 ),

PS_FIELD(
generic1, 8 ),

PS_FIELD(
pm_type, 8 ),

PS_FIELD(
delta_angles[0], 16 ),

PS_FIELD(
delta_angles[2], 16 ),

PS_FIELD(
torsoTimer, 12 ),

PS_FIELD(
eventParms[0], 8 ),

PS_FIELD(
eventParms[1], 8 ),

PS_FIELD(
clientNum, 8 ),

PS_FIELD(
weapon, 5 ),

PS_FIELD(
viewangles[2], 0 ),

PS_FIELD(
grapplePoint[0], 0 ),

PS_FIELD(
grapplePoint[1], 0 ),

PS_FIELD(
grapplePoint[2], 0 ),

PS_FIELD(
jumppad_ent, 10 ),

PS_FIELD(
loopSound, 16 )

};

 

//

// entityState_t

//

static field_t esTable[] = {

ES_FIELD(
pos.trTime, 32 ),

ES_FIELD(
pos.trBase[0], 0 ),

ES_FIELD(
pos.trBase[1], 0 ),

ES_FIELD(
pos.trDelta[0], 0 ),

ES_FIELD(
pos.trDelta[1], 0 ),

ES_FIELD(
pos.trBase[2], 0 ),

ES_FIELD(
apos.trBase[1], 0 ),

ES_FIELD(
pos.trDelta[2], 0 ),

ES_FIELD(
apos.trBase[0], 0 ),

ES_FIELD(
event, 10 ),

ES_FIELD(
angles2[1], 0 ),

ES_FIELD(
eType, 8 ),

ES_FIELD(
torsoAnim, 8 ),

ES_FIELD(
eventParm, 8 ),

ES_FIELD(
legsAnim, 8 ),

ES_FIELD(
groundEntityNum, 10 ),

ES_FIELD(
pos.trType, 8 ),

ES_FIELD(
eFlags, 19 ),

ES_FIELD(
otherEntityNum, 10 ),

ES_FIELD(
weapon, 8 ),

ES_FIELD(
clientNum, 8 ),

ES_FIELD(
angles[1], 0 ),

ES_FIELD(
pos.trDuration, 32 ),

ES_FIELD(
apos.trType, 8 ),

ES_FIELD(
origin[0], 0 ),

ES_FIELD(
origin[1], 0 ),

ES_FIELD(
origin[2], 0 ),

ES_FIELD(
solid, 24 ),

ES_FIELD(
powerups, 16 ),

ES_FIELD(
modelindex, 8 ),

ES_FIELD(
otherEntityNum2, 10 ),

ES_FIELD(
loopSound, 8 ),

ES_FIELD(
generic1, 8 ),

ES_FIELD(
origin2[2], 0 ),

ES_FIELD(
origin2[0], 0 ),

ES_FIELD(
origin2[1], 0 ),

ES_FIELD(
modelindex2, 8 ),

ES_FIELD(
angles[0], 0 ),

ES_FIELD(
time, 32
),

ES_FIELD(
apos.trTime, 32 ),

ES_FIELD(
apos.trDuration, 32 ),

ES_FIELD(
apos.trBase[2], 0 ),

ES_FIELD(
apos.trDelta[0], 0 ),

ES_FIELD(
apos.trDelta[1], 0 ),

ES_FIELD(
apos.trDelta[2], 0 ),

ES_FIELD(
time2, 32 ),

ES_FIELD(
angles[2], 0 ),

ES_FIELD(
angles2[0], 0 ),

ES_FIELD(
angles2[2], 0 ),

ES_FIELD(
constantLight, 32 ),

ES_FIELD(
frame, 16 )

};

 

static const int psTableSize = sizeof( psTable ) / sizeof(
psTable[0] );

static const int esTableSize = sizeof( esTable ) / sizeof(
esTable[0] );

 

static const entityState_t nullEntityState;

static const playerState_t nullPlayerState;

 

Float values (or ‘vectors’) can be written as 32 bits or 13 bits. ‘Snapped’ vectors, i.e. floats without fractional
part, are written as unsigned 13-bit integers for network bandwidth saving, if
their values donÂ’t exceed MAX_SNAPPED.

 

// snapped vectors are packed in 13 bits instead of 32

#define
SNAPPED_BITS 13

#define
MAX_SNAPPED (1<<SNAPPED_BITS)

 

Delta compressed structures are
read/write using the following functions:

 

/*

============

MSG_WriteDeltaEntity

 


If ‘force’ parm is false, this won’t result any bits


emitted if entity didn’t changed at all

 


‘from’ == NULL –> nodelta update


‘to’ == NULL –>
entity removed

============

*/

void MSG_WriteDeltaEntity( sizebuf_t *msg, const entityState_t *from, const
entityState_t *to, qboolean force ) {

field_t *field;

int to_value;

int to_integer;

float to_float;

int maxFieldNum;

int i;

 

if( !to ) {

if( from ) {

MSG_WriteBits(
msg, from->number, GENTITYNUM_BITS );

MSG_WriteBits(
msg, 1, 1 );

}

return; // removed

}

 

if( to->number < 0 || to->number >
MAX_GENTITIES ) {

Com_Error(
ERR_DROP, "MSG_WriteDeltaEntity: Bad entity number: %i",
to->number );

}

 

if( !from ) {

from
= &nullEntityState; // nodelta update

}

 

//

//
find last modified field in table

//

maxFieldNum
= 0;

for( i=0, field=esTable ; i<esTableSize ; i++,
field++ ) {

if( FIELD_INTEGER( from ) != FIELD_INTEGER( to ) )
{

maxFieldNum
= i + 1;

}

}

 

if( !maxFieldNum ) {

if( !force ) {

return; // don’t emit any
bits at all

}

 

MSG_WriteBits(
msg, to->number, GENTITYNUM_BITS );

MSG_WriteBits(
msg, 0, 1 );

MSG_WriteBits(
msg, 0, 1 );

return; // unchanged

}

 

MSG_WriteBits(
msg, to->number, GENTITYNUM_BITS );

MSG_WriteBits(
msg, 0, 1 );

MSG_WriteBits(
msg, 1, 1 );

MSG_WriteByte(
msg, maxFieldNum );

 

//

//
write all modified fields

//

for( i=0, field=esTable ; i<maxFieldNum ; i++,
field++ ) {

to_value
= FIELD_INTEGER( to );

if( FIELD_INTEGER( from ) == to_value ) {

MSG_WriteBits(
msg, 0, 1 );

continue; // field
unchanged

}

MSG_WriteBits(
msg, 1, 1 );

 

if( !to_value ) {

MSG_WriteBits(
msg, 0, 1 );

continue; // field set to
zero

}

MSG_WriteBits(
msg, 1, 1 );

 

if( field->bits ) {

MSG_WriteBits(
msg, to_value, field->bits );

continue; // integer
value

}

 

//

//
figure out how to pack float value

//

to_float
= FIELD_FLOAT( to );

to_integer
= (int)to_float;

 

if( (float)to_integer
== to_float

&&
to_integer + MAX_SNAPPED/2 >= 0

&&
to_integer + MAX_SNAPPED/2 < MAX_SNAPPED )

{

MSG_WriteBits(
msg, 0, 1 ); // pack in 13 bits

MSG_WriteBits(
msg, to_integer + MAX_SNAPPED/2, SNAPPED_BITS );

}
else {

MSG_WriteBits(
msg, 1, 1 ); // pack in 32 bits

MSG_WriteLong(
msg, to_value );

}

}

 

}

 

/*

============

MSG_ReadDeltaEntity

 


‘from’ == NULL –> nodelta update


‘to’ == NULL –>
do nothing

============

*/

void MSG_ReadDeltaEntity( sizebuf_t *msg, const entityState_t *from, entityState_t *to, int number ) {

field_t *field;

int maxFieldNum;

int i;

 

if( number < 0 || number >= MAX_GENTITIES ) {

Com_Error(
ERR_DROP, "MSG_ReadDeltaEntity: Bad delta entity number: %i\n",
number );

}

 

if( !to ) {

return;

}

 

if( MSG_ReadBits( msg, 1 ) ) {

memset(
to, 0, sizeof( *to ) );

to->number
= ENTITYNUM_NONE;

return; // removed

}

 

if( !from ) {

memset(
to, 0, sizeof( *to ) ); // nodelta update

}
else {

memcpy(
to, from, sizeof( *to ) );

}

to->number
= number;

 

if( !MSG_ReadBits( msg, 1 ) ) {

return; // unchanged

}

 

maxFieldNum
= MSG_ReadByte( msg );

if( maxFieldNum > esTableSize ) {

Com_Error(
ERR_DROP, "MSG_ReadDeltaEntity: maxFieldNum > esTableSize" );

}

 

//

//
read all modified fields

//

for( i=0, field=esTable ; i<maxFieldNum ; i++,
field++ ) {

if( !MSG_ReadBits( msg, 1 ) ) {

continue; // field
unchanged

}

if( !MSG_ReadBits( msg, 1 ) ) {

FIELD_INTEGER(
to ) = 0;

continue; // field set to
zero

}

 

if( field->bits ) {

FIELD_INTEGER(
to ) = MSG_ReadBits( msg, field->bits );

continue; // integer value

}

 

//

//
read packed float value

//

if( !MSG_ReadBits( msg, 1 ) ) {

FIELD_FLOAT(
to ) = (float)(MSG_ReadBits( msg, SNAPPED_BITS
) – MAX_SNAPPED/2);

}
else {

FIELD_INTEGER(
to ) = MSG_ReadLong( msg );

}

}

}

 

/*

============

MSG_WriteDeltaPlayerstate

 


‘from’ == NULL –> nodelta update


‘to’ == NULL –>
do nothing

============

*/

void MSG_WriteDeltaPlayerstate( sizebuf_t *msg, const playerState_t *from, const
playerState_t *to ) {

field_t *field;

int to_value;

float to_float;

int to_integer;

int maxFieldNum;

int statsMask;

int persistantMask;

int ammoMask;

int powerupsMask;

int i;

 

if( !to ) {

return;

}

 

if( !from ) {

from
= &nullPlayerState; // nodelta update

}

 

//

//
find last modified field in table

//

maxFieldNum
= 0;

for( i=0, field=psTable ; i<psTableSize ; i++,
field++ ) {

if( FIELD_INTEGER( from ) != FIELD_INTEGER( to ) )
{

maxFieldNum
= i + 1;

}

}

 

MSG_WriteByte(
msg, maxFieldNum );

 

//

//
write all modified fields

//

for( i=0, field=psTable ; i<maxFieldNum ; i++,
field++ ) {

to_value
= FIELD_INTEGER( to );

if( FIELD_INTEGER( from ) == to_value ) {

MSG_WriteBits(
msg, 0, 1 );

continue; // field
unchanged

}

MSG_WriteBits(
msg, 1, 1 );

 

if( field->bits ) {

MSG_WriteBits(
msg, to_value, field->bits );

continue; // integer
value

}

 

//

//
figure out how to pack float value

//

to_float
= FIELD_FLOAT( to );

to_integer
= (int)to_float;

 

if( (float)to_integer
== to_float

&&
to_integer + MAX_SNAPPED/2 >= 0

&&
to_integer + MAX_SNAPPED/2 < MAX_SNAPPED )

{

MSG_WriteBits(
msg, 0, 1 ); // pack in 13 bits

MSG_WriteBits(
msg, to_integer + MAX_SNAPPED/2, SNAPPED_BITS );

}
else {

MSG_WriteBits(
msg, 1, 1 ); // pack in 32 bits

MSG_WriteLong(
msg, to_value );

}

}

 

//

//
find modified arrays

//

statsMask
= 0;

for( i=0 ; i<MAX_STATS ; i++ ) {

if( from->stats[i] != to->stats[i] ) {

statsMask
|= (1 << i);

}

}

 

persistantMask
= 0;

for( i=0 ; i<MAX_PERSISTANT ; i++ ) {

if( from->persistant[i] != to->persistant[i]
) {

persistantMask
|= (1 << i);

}

}

 

ammoMask
= 0;

for( i=0 ; i<MAX_WEAPONS ; i++ ) {

if( from->ammo[i] != to->ammo[i] ) {

ammoMask
|= (1 << i);

}

}

 

powerupsMask
= 0;

for( i=0 ; i<MAX_POWERUPS ; i++ ) {

if( from->powerups[i] != to->powerups[i] ) {

powerupsMask
|= (1 << i);

}

}

 

if( !statsMask && !persistantMask
&& !ammoMask && !powerupsMask ) {

MSG_WriteBits(
msg, 0, 1 );

return; // no arrays
modified

}

 

//

//
write all modified arrays

//

MSG_WriteBits(
msg, 1, 1 );

 

// PS_STATS

if( statsMask ) {

MSG_WriteBits(
msg, 1, 1 );

MSG_WriteShort(
msg, statsMask );

for( i=0 ; i<MAX_STATS ; i++ ) {

if( statsMask & (1 << i) ) {

MSG_WriteSignedShort(
msg, to->stats[i] );

}

}

}
else {

MSG_WriteBits(
msg, 0, 1 ); // unchanged

}

 

// PS_PERSISTANT

if( persistantMask ) {

MSG_WriteBits(
msg, 1, 1 );

MSG_WriteShort(
msg, persistantMask );

for( i=0 ; i<MAX_PERSISTANT ; i++ ) {

if( persistantMask & (1 << i) ) {

MSG_WriteSignedShort(
msg, to->persistant[i] );

}

}

}
else {

MSG_WriteBits(
msg, 0, 1 ); // unchanged

}

 

 

// PS_AMMO

if( ammoMask ) {

MSG_WriteBits(
msg, 1, 1 );

MSG_WriteShort(
msg, ammoMask );

for( i=0 ; i<MAX_WEAPONS ; i++ ) {

if( ammoMask & (1 << i) ) {

MSG_WriteShort(
msg, to->ammo[i] );

}

}

}
else {

MSG_WriteBits(
msg, 0, 1 ); // unchanged

}

 

// PS_POWERUPS

if( powerupsMask ) {

MSG_WriteBits(
msg, 1, 1 );

MSG_WriteShort(
msg, powerupsMask );

for( i=0 ; i<MAX_POWERUPS ; i++ ) {

if( powerupsMask & (1 << i) ) {

MSG_WriteLong(
msg, to->powerups[i] ); // WARNING: powerups use
32 bits, not 16

}

}

}
else {

MSG_WriteBits(
msg, 0, 1 ); // unchanged

}

 

}

 

 

/*

============

MSG_ReadDeltaPlayerstate

 


‘from’ == NULL –> nodelta update


‘to’ == NULL –>
do nothing

============

*/

void MSG_ReadDeltaPlayerstate( sizebuf_t *msg, const playerState_t *from, playerState_t *to ) {

field_t *field;

int maxFieldNum;

int bitmask;

int i;

 

if( !to ) {

return;

}

 

if( !from ) {

memset(
to, 0, sizeof( *to ) ); // nodelta update

}
else {

memcpy(
to, from, sizeof( *to ) );

}

 

maxFieldNum
= MSG_ReadByte( msg );

if( maxFieldNum > psTableSize ) {

Com_Error(
ERR_DROP, "MSG_ReadDeltaPlayerstate: maxFieldNum > psTableSize"
);

}

 

//

//
read all modified fields

//

for( i=0, field=psTable ; i<maxFieldNum ; i++,
field++ ) {

if( !MSG_ReadBits( msg, 1 ) ) {

continue; // field
unchanged

}

 

if( field->bits ) {

FIELD_INTEGER(
to ) = MSG_ReadBits( msg, field->bits );

continue; // integer value

}

 

//

//
read packed float value

//

if( !MSG_ReadBits( msg, 1 ) ) {

FIELD_FLOAT(
to ) = (float)(MSG_ReadBits( msg,
SNAPPED_BITS ) – MAX_SNAPPED/2);

}
else {

FIELD_INTEGER(
to ) = MSG_ReadLong( msg );

}

}

 

//

//
read all modified arrays

//

if( !MSG_ReadBits( msg, 1 ) ) {

return; // no arrays modified

}

 

// PS_STATS

if( MSG_ReadBits( msg, 1 ) ) {

bitmask
= MSG_ReadShort( msg );

for( i=0 ; i<MAX_STATS ; i++ ) {

if( bitmask & (1 << i) ) {

to->stats[i]
= MSG_ReadSignedShort( msg ); // PS_STATS can be
negative

}

}

}

 

// PS_PERSISTANT

if( MSG_ReadBits( msg, 1 ) ) {

bitmask
= MSG_ReadShort( msg );

for( i=0 ; i<MAX_PERSISTANT ; i++ ) {

if( bitmask & (1 << i) ) {

to->persistant[i]
= MSG_ReadSignedShort( msg ); // PS_PERSISTANT can
be negative

}

}

}

 

// PS_AMMO

if( MSG_ReadBits( msg, 1 ) ) {

bitmask
= MSG_ReadShort( msg );

for( i=0 ; i<MAX_WEAPONS ; i++ ) {

if( bitmask & (1 << i) ) {

to->ammo[i]
= MSG_ReadShort( msg );

}

}

}

 

// PS_POWERUPS

if( MSG_ReadBits( msg, 1 ) ) {

bitmask
= MSG_ReadShort( msg );

for( i=0 ; i<MAX_POWERUPS ; i++ ) {

if( bitmask & (1 << i) ) {

to->powerups[i]
= MSG_ReadLong( msg ); // WARNING: powerups use 32
bits, not 16

}

}

}

}

 

7. Huffman encoding

The following routines do Huffman encoding/decoding. Note that you have to do a Huff_Init call before using any MSG_* functions.

//
// huff.c - Huffman compression routines for data bitstream
//
 
#define VALUE(a) ((int )(a))
#define NODE(a) ((void *)(a))
 
#define NODE_START NODE( 1)
#define NODE_NONE NODE(256)
#define NODE_NEXT NODE(257)
 
#define HUFF_TREE_SIZE 7175
typedef void *tree_t[HUFF_TREE_SIZE];

//
// pre-defined frequency counts for all bytes [0..255]
//
static int huffCounts[256] = {
0x3D1CB, 0x0A0E9, 0x01894, 0x01BC2, 0x00E92, 0x00EA6, 0x017DE, 0x05AF3,
0x08225, 0x01B26, 0x01E9E, 0x025F2, 0x02429, 0x0436B, 0x00F6D, 0x006F2,
0x02060, 0x00644, 0x00636, 0x0067F, 0x0044C, 0x004BD, 0x004D6, 0x0046E,
0x006D5, 0x00423, 0x004DE, 0x0047D, 0x004F9, 0x01186, 0x00AF5, 0x00D90,
0x0553B, 0x00487, 0x00686, 0x0042A, 0x00413, 0x003F4, 0x0041D, 0x0042E,
0x006BE, 0x00378, 0x0049C, 0x00352, 0x003C0, 0x0030C, 0x006D8, 0x00CE0,
0x02986, 0x011A2, 0x016F9, 0x00A7D, 0x0122A, 0x00EFD, 0x0082D, 0x0074B,
0x00A18, 0x0079D, 0x007B4, 0x003AC, 0x0046E, 0x006FC, 0x00686, 0x004B6,
0x01657, 0x017F0, 0x01C36, 0x019FE, 0x00E7E, 0x00ED3, 0x005D4, 0x005F4,
0x008A7, 0x00474, 0x0054B, 0x003CB, 0x00884, 0x004E0, 0x00530, 0x004AB,
0x006EA, 0x00436, 0x004F0, 0x004F2, 0x00490, 0x003C5, 0x00483, 0x004A2,
0x00543, 0x004CC, 0x005F9, 0x00640, 0x00A39, 0x00800, 0x009F2, 0x00CCB,
0x0096A, 0x00E01, 0x009C8, 0x00AF0, 0x00A73, 0x01802, 0x00E4F, 0x00B18,
0x037AD, 0x00C5C, 0x008AD, 0x00697, 0x00C88, 0x00AB3, 0x00DB8, 0x012BC,
0x00FFB, 0x00DBB, 0x014A8, 0x00FB0, 0x01F01, 0x0178F, 0x014F0, 0x00F54,
0x0131C, 0x00E9F, 0x011D6, 0x012C7, 0x016DC, 0x01900, 0x01851, 0x02063,
0x05ACB, 0x01E9E, 0x01BA1, 0x022E7, 0x0153D, 0x01183, 0x00E39, 0x01488,
0x014C0, 0x014D0, 0x014FA, 0x00DA4, 0x0099A, 0x0069E, 0x0071D, 0x00849,
0x0077C, 0x0047D, 0x005EC, 0x00557, 0x004D4, 0x00405, 0x004EA, 0x00450,
0x004DD, 0x003EE, 0x0047D, 0x00401, 0x004D9, 0x003B8, 0x00507, 0x003E5,
0x006B1, 0x003F1, 0x004A3, 0x0036F, 0x0044B, 0x003A1, 0x00436, 0x003B7,
0x00678, 0x003A2, 0x00481, 0x00406, 0x004EE, 0x00426, 0x004BE, 0x00424,
0x00655, 0x003A2, 0x00452, 0x00390, 0x0040A, 0x0037C, 0x00486, 0x003DE,
0x00497, 0x00352, 0x00461, 0x00387, 0x0043F, 0x00398, 0x00478, 0x00420,
0x00D86, 0x008C0, 0x0112D, 0x02F68, 0x01E4E, 0x00541, 0x0051B, 0x00CCE,
0x0079E, 0x00376, 0x003FF, 0x00458, 0x00435, 0x00412, 0x00425, 0x0042F,
0x005CC, 0x003E9, 0x00448, 0x00393, 0x0041C, 0x003E3, 0x0042E, 0x0036C,
0x00457, 0x00353, 0x00423, 0x00325, 0x00458, 0x0039B, 0x0044F, 0x00331,
0x0076B, 0x00750, 0x003D0, 0x00349, 0x00467, 0x003BC, 0x00487, 0x003B6,
0x01E6F, 0x003BA, 0x00509, 0x003A5, 0x00467, 0x00C87, 0x003FC, 0x0039F,
0x0054B, 0x00300, 0x00410, 0x002E9, 0x003B8, 0x00325, 0x00431, 0x002E4,
0x003F5, 0x00325, 0x003F0, 0x0031C, 0x003E4, 0x00421, 0x02CC1, 0x034C0
};
 
//
// static Huffman tree
//
static tree_t huffTree;
 
//
// received from MSG_* code
//
static int huffBitPos;

 
/*
======================================================================================
HUFFMAN TREE CONSTRUCTION
======================================================================================
*/
 
/*
============
Huff_PrepareTree
============
*/
static ID_INLINE void Huff_PrepareTree( tree_t tree ) {
void **node;
memset( tree, 0, sizeof( tree_t ) );

// create first node
node = &tree[263];
VALUE( tree[0] )++;
 
node[7] = NODE_NONE;
tree[2] = node;
tree[3] = node;
tree[4] = node;
tree[261] = node;
}
 
/*
============
Huff_GetNode
============
*/
static void **Huff_GetNode( void **tree ) {
void **node;
int value;
 
node = tree[262];
if( !node ) {
value = VALUE( tree[1] )++;
node = &tree[value + 6407];
return node;
}
 
tree[262] = node[0];
return node;
}
 
/*
============
Huff_Swap
============
*/
static void Huff_Swap( void **tree1, void **tree2, void **tree3 ) {
void **a, **b;
 
a = tree2[2];
if( a ) {
if( a[0] == tree2 ) {
a[0] = tree3;
} else {
a[1] = tree3;
}
} else {
tree1[2] = tree3;
}
 
b = tree3[2];
 
if( b ) {
if( b[0] == tree3 ) {
b[0] = tree2;
tree2[2] = b;
tree3[2] = a;
return;
}
 
b[1] = tree2;
tree2[2] = b;
tree3[2] = a;
return;
}
 
tree1[2] = tree2;
tree2[2] = NULL;
tree3[2] = a;
}
 
/*
============
Huff_SwapTrees
============
*/
static void Huff_SwapTrees( void **tree1, void **tree2 ) {
void **temp;
 
temp = tree1[3];
tree1[3] = tree2[3];
tree2[3] = temp;
 
temp = tree1[4];
tree1[4] = tree2[4];
tree2[4] = temp;
 
if( tree1[3] == tree1 ) {
tree1[3] = tree2;
}
 
if( tree2[3] == tree2 ) {
tree2[3] = tree1;
}
 
temp = tree1[3];
if( temp ) {
temp[4] = tree1;
}
 
temp = tree2[3];
if( temp ) {
temp[4] = tree2;
}
 
temp = tree1[4];
if( temp ) {
temp[3] = tree1;
}
 
temp = tree2[4];
if( temp ) {
temp[3] = tree2;
}
 
}
 
/*
============
Huff_DeleteNode
============
*/
static void Huff_DeleteNode( void **tree1, void **tree2 ) {
tree2[0] = tree1[262];
tree1[262] = tree2;
}
 
/*
============
Huff_IncrementFreq_r
============
*/
static void Huff_IncrementFreq_r( void **tree1, void **tree2 ) {
void **a, **b;
 
if( !tree2 ) {
return;
}
 
a = tree2[3];
if( a && a[6] == tree2[6] ) {
b = tree2[5];
if( b[0] != tree2[2] ) {
Huff_Swap( tree1, b[0], tree2 );
}
Huff_SwapTrees( b[0], tree2 );
}
 
a = tree2[4];
if( a && a[6] == tree2[6] ) {
b = tree2[5];
b[0] = a;
} else {
a = tree2[5];
a[0] = 0;
Huff_DeleteNode( tree1, tree2[5] );
}
 

VALUE( tree2[6] )++;
a = tree2[3];
if( a && a[6] == tree2[6] ) {
tree2[5] = a[5];
} else {
a = Huff_GetNode( tree1 );
tree2[5] = a;
a[0] = tree2;
}
 
if( tree2[2] ) {
Huff_IncrementFreq_r( tree1, tree2[2] );

if( tree2[4] == tree2[2] ) {
Huff_SwapTrees( tree2, tree2[2] );
a = tree2[5];
 
if( a[0] == tree2 ) {
a[0] = tree2[2];
}
}
}
}
 
/*
============
Huff_AddReference
 
Insert 'ch' into the tree or increment it's frequency
============
*/
static void Huff_AddReference( void **tree, int ch ) {
void **a, **b, **c, **d;
int value;
 
ch &= 255;
if( tree[ch + 5] ) {
Huff_IncrementFreq_r( tree, tree[ch + 5] );
return; // already added
}
 
value = VALUE( tree[0] )++;
b = &tree[value * 8 + 263];
 
value = VALUE( tree[0] )++;
a = &tree[value * 8 + 263];
 
a[7] = NODE_NEXT;
a[6] = NODE_START;
d = tree[3];
a[3] = d[3];
if( a[3] ) {
d = a[3];
d[4] = a;
d = a[3];
if( d[6] == NODE_START ) {
a[5] = d[5];
} else {
d = Huff_GetNode( tree );
a[5] = d;
d[0] = a;
}
} else {
d = Huff_GetNode( tree );
a[5] = d;
d[0] = a; 
}

d = tree[3];
d[3] = a;
a[4] = tree[3];
b[7] = NODE( ch );
b[6] = NODE_START;
d = tree[3];
b[3] = d[3];
if( b[3] ) {
d = b[3];
d[4] = b;
if( d[6] == NODE_START ) {
b[5] = d[5];
} else {
d = Huff_GetNode( tree );
b[5] = d;
d[0] = a;
}
} else {
d = Huff_GetNode( tree );
b[5] = d;
d[0] = b;
}
 
d = tree[3];
d[3] = b;
b[4] = tree[3];
b[1] = NULL;
b[0] = NULL;
d = tree[3];
c = d[2];
if( c ) {
if( c[0] == tree[3] ) {
c[0] = a;
} else {
c[1] = a;
}
} else {
tree[2] = a;
}
 
a[1] = b;
d = tree[3];
a[0] = d;
a[2] = d[2];
b[2] = a;
d = tree[3];
d[2] = a;
tree[ch + 5] = b;
 
Huff_IncrementFreq_r( tree, a[2] );
}

/*
======================================================================================
BITSTREAM I/O
======================================================================================
*/
 
/*
============
Huff_EmitBit
 
Put one bit into buffer
============
*/
static ID_INLINE void Huff_EmitBit( int bit, byte *buffer ) {
if( !(huffBitPos & 7) ) {
buffer[huffBitPos >> 3] = 0;
}
 
buffer[huffBitPos >> 3] |= bit << (huffBitPos & 7);
huffBitPos++;
}
 
/*
============
Huff_GetBit
 
Read one bit from buffer
============
*/
static ID_INLINE int Huff_GetBit( byte *buffer ) {
int bit;
 
bit = buffer[huffBitPos >> 3] >> (huffBitPos & 7);
huffBitPos++;
 
return (bit & 1);
}
 
/*
============
Huff_EmitPathToByte
============
*/
static ID_INLINE void Huff_EmitPathToByte( void **tree, void **subtree, byte *buffer ) {
if( tree[2] ) {
Huff_EmitPathToByte( tree[2], tree, buffer );
}
 
if( !subtree ) {
return;
}
 
//
// emit tree walking control bits
//
if( tree[1] == subtree ) {
Huff_EmitBit( 1, buffer );
} else {
Huff_EmitBit( 0, buffer );
}
}
 
/*
============
Huff_GetByteFromTree
 
Get one byte using dynamic or static tree
============
*/
static ID_INLINE int Huff_GetByteFromTree( void **tree, byte *buffer ) {
if( !tree ) {
return 0;
}
 
//
// walk through the tree until we get a value
//
while( tree[7] == NODE_NEXT ) {
if( !Huff_GetBit( buffer ) ) {
tree = tree[0];
} else {
tree = tree[1];
}
 
if( !tree ) {
return 0;
}
}
 
return VALUE( tree[7] );
}
 
/*
======================================================================================
PUBLIC INTERFACE
======================================================================================
*/
 
/*
============
Huff_EmitByte
============
*/
void Huff_EmitByte( int ch, byte *buffer, int *count ) {
huffBitPos = *count;
Huff_EmitPathToByte( huffTree[ch + 5], NULL, buffer );
*count = huffBitPos;
}
 
/*
============
Huff_GetByte
============
*/
int Huff_GetByte( byte *buffer, int *count ) {
int ch;
 
huffBitPos = *count;
ch = Huff_GetByteFromTree( huffTree[2], buffer );
*count = huffBitPos;
 
return ch;
}

/*
============
Huff_Init
============
*/
void Huff_Init( void ) {
int i, j;

// build empty tree
Huff_PrepTreeComp( huffTree );

// add all pre-defined byte references
for( i=0 ; i<256 ; i++ ) {
for( j=0 ; j<huffCounts[i] ; j++ ) {
Huff_AddReference( huffTree, i );
}
}

}

8. Server to Client commands

SerVer to Client commands (SVC_*) are hints to the client how to parse data stream received from server. There are 8 SVC_* types, but only 6 of them are used in demo files.

//
// possible server to client commands
//
typedef enum svc_ops_e {
  SVC_BAD, // not used in demos
  SVC_NOP, // not used in demos
  SVC_GAMESTATE,
  SVC_CONFIGSTRING, // only inside gameState
  SVC_BASELINE, // only inside gameState
  SVC_SERVERCOMMAND,
  SVC_DOWNLOAD, // not used in demos
  SVC_SNAPSHOT,
  SVC_EOM
} svc_ops_t;

SVC_BAD – zero command, should never appear in network messages and demos

SVC_NOP – No OPeration command, should never appear in demos

SVC_SERVERCOMMAND – reliable text command to be processed by the client

SVC_DOWNLOAD – should never appear in demos

SVC_SNAPSHOT – contains delta compressed updates of playerState and entityStates

SVC_EOM – indicates End Of Message, also used inside gameState

Each demo message should contain either SVC_GAMESTATE or SVC_SNAPSHOT, but never them both and never no one of them.

9. Operations with gameState_t

#define MAX_CONFIGSTRINGS 1024

// these are the only configstrings that the system reserves, all the
// other ones are strictly for servergame to clientgame communication
#define CS_SERVERINFO 0 // an info string with all the serverinfo cvars
#define CS_SYSTEMINFO 1 // an info string for server system to client system configuration (timescale, etc)

#define RESERVED_CONFIGSTRINGS 2 // game can't modify below this, only the system can

#define MAX_GAMESTATE_CHARS 16000
typedef struct {
int stringOffsets[MAX_CONFIGSTRINGS];
char stringData[MAX_GAMESTATE_CHARS];
int dataCount;
} gameState_t;

gameState_t structure is used to hold all configStrings in a single text buffer. Because configStrings can be up to BIG_INFO_STRING chars, it would be better to use 20100 bytes of gameState_t instead of 8388608 bytes of static array of configStrings.

The following functions allow inserting and fetching configStrings from gameState_t:

/*
============
Com_AppendToGameState
============
*/
static void Com_AppendToGameState( gameState_t *gameState, int index, const char *configString ) {
int len;

if( !configString || !(len=strlen( configString )) ) {
return;
}

if( gameState->dataCount + len + 2 >= MAX_GAMESTATE_CHARS ) {
Com_Error( ERR_DROP, "Com_AppendToGameState: MAX_GAMESTATE_CHARS" );
}

gameState->stringOffsets[index] = gameState->dataCount + 1;
strcpy( &gameState->stringData[gameState->dataCount + 1], configString );
gameState->dataCount += len + 1;
}

/*
============
Com_InsertIntoGameState
============
*/
void Com_InsertIntoGameState( gameState_t *gameState, int index, const char *configString ) {
char *strings[MAX_CONFIGSTRINGS];
int ofs;
int i;

if( index < 0 || index >= MAX_CONFIGSTRINGS ) {
Com_Error( ERR_DROP, "Com_InsertIntoGameState: bad index %i", index );
}

if( !gameState->stringOffsets[index] ) {
// just append to the end of gameState
Com_AppendToGameState( gameState, index, configString );
return;
}

//
// resort gameState
//
for( i=0 ; i<MAX_CONFIGSTRINGS ; i++ ) {
ofs = gameState->stringOffsets[i];
if( !ofs ) {
strings[i] = NULL;
} else if( i == index ) {
strings[i] = CopyString( configString );
} else {
strings[i] = CopyString( &gameState->stringData[ofs] );
}
}

memset( gameState, 0, sizeof( *gameState ) );

for( i=0 ; i<MAX_CONFIGSTRINGS ; i++ ) {
if( strings[i] ) {
Com_AppendToGameState( gameState, i, strings[i] );
Z_Free( strings[i] );
}
}

}

/*
============
Com_GetStringFromGameState
============
*/
char *Com_GetStringFromGameState( gameState_t *gameState, int index ) {
int ofs;

if( index < 0 || index >= MAX_CONFIGSTRINGS ) {
Com_Error( ERR_DROP, "Com_GetStringFromGameState: bad index %i", index );
}

ofs = gameState->stringOffsets[index ];

if( !ofs ) {
return "";
}

return &gameState->stringData[ofs];
}

10. Parsing whole demo file

//
// sizes of misc circular buffers in client and server system
//

// for delta compression of snapshots
#define MAX_SNAPSHOTS 32
#define SNAPSHOT_MASK (MAX_SNAPSHOTS-1)

// for keeping reliable text commands not acknowledged by receiver yet
#define MAX_SERVERCMDS 64
#define SERVERCMD_MASK (MAX_SERVERCMDS-1)

// for keeping all entityStates for delta encoding
#define MAX_PARSE_ENTITIES (MAX_GENTITIES*2)
#define PARSE_ENTITIES_MASK (MAX_PARSE_ENTITIES-1)

//
// max number of entityState_t present in a single update
//
#define MAX_ENTITIES_IN_SNAPSHOT 256

typedef struct {
qboolean valid;

int seq; // die seqeunc number des snapshots
int deltaSeq;
int snapFlags; // SNAPFLAG_RATE_DELAYED, etc

int serverTime; // server time the message is valid for (in msec)

byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits

playerState_t ps; // complete information about the current player at this time

int numEntities; // all of the entities that need to be presented
int firstEntity; // ab dieser Position sind im Ringbuffer die numEntities viele Entities des Snapshots
// ersetzt entities[Max_entities_in snapshot]
} snapshot_t;

typedef struct {
int lastServerCommandNum;
int currentServerCommandNum;
char serverCommands[MAX_SERVERCMDS][MAX_STRING_CHARS];

gameState_t gameState;

entityState_t baselines[MAX_GENTITIES];

entityState_t parseEntities[MAX_PARSE_ENTITIES];
int firstParseEntity;

snapshot_t snapshots[MAX_SNAPSHOTS];
snapshot_t *snap;
} demoState_t;

demoState_t ds;
/*
==================
Parse_GameState
==================
*/
static void Parse_GameState( sizebuf_t *msg ) {
int c;
int index;
char *configString;

memset( &ds, 0, sizeof( ds ) ); 
ds.lastServerCommandNum = MSG_ReadLong( msg );
ds.currentServerCommandNum = ds.lastServerCommandNum;

while( 1 ) {
c = MSG_ReadByte( msg );

if( c == -1 ) {
Com_Error( ERR_DROP, "Parse_GameState: read past end of demo message" );
}

if( c == SVC_EOM ) {
break;
}

switch( c ) {
default:
Com_Error( ERR_DROP, "Parse_GameState: bad command byte" );
break;

case SVC_CONFIGSTRING:
index = MSG_ReadShort( msg );
if( index < 0 || index >= MAX_CONFIGSTRINGS ) {
Com_Error( ERR_DROP, "Parse_GameState: configString index %i out of range", index );
}
configString = MSG_ReadBigString( msg );
Com_InsertIntoGameState( &ds.gameState, index, configString );
break;

case SVC_BASELINE:
index = MSG_ReadBits( msg, GENTITYNUM_BITS );
if( index < 0 || index >= MAX_GENTITIES ) {
Com_Error( ERR_DROP, "Parse_GameState: baseline index %i out of range", index );
}
MSG_ReadDeltaEntity( msg, NULL, &ds.baselines[index], index );
break;
}
}

// FIXME: unknown stuff, not used in demos?
MSG_ReadLong( msg );
MSG_ReadLong( msg );

demo.gameStatesParsed++;
}

/*
=====================================================================
ACTION MESSAGES
=====================================================================
*/

/*
=====================
Parse_ServerCommand
=====================
*/
static void Parse_ServerCommand( sizebuf_t *msg ) {
int number;
char *string;

number = MSG_ReadLong( msg );
string = MSG_ReadString( msg );

if( number < ds.lastServerCommandNum ) {
return; // we have already received this command
}
ds.lastServerCommandNum = number;

// archive the command to be processed later
Q_strncpyz( ds.serverCommands[number & SERVERCMD_MASK], string, sizeof( ds.serverCommands[0] ) );
}

/*
==================
Parse_DeltaEntity

Parses deltas from the given base and adds the resulting entity
to the current frame
==================
*/
static void Parse_DeltaEntity( sizebuf_t *msg, snapshot_t *frame, int newnum, entityState_t *old, qboolean unchanged ) {
entityState_t *state;

state = &ds.parseEntities[ds.firstParseEntity & PARSE_ENTITIES_MASK];

if( unchanged ) {
memcpy( state, old, sizeof( *state ) ); // don't read any bits
} else {
MSG_ReadDeltaEntity( msg, old, state, newnum );
if( state->number == ENTITYNUM_NONE ) {
// the entity present in oldframe is not in the current frame
return;
}
}

ds.firstParseEntity++;
frame->numEntities++;
}

/*
==================
Parse_PacketEntities

An svc_packetentities has just been parsed, deal with the
rest of the data stream.
==================
*/
static void Parse_PacketEntities( sizebuf_t *msg, snapshot_t *oldframe, snapshot_t *newframe ) {
int newnum;
entityState_t *oldstate;
int oldindex, oldnum;

newframe->firstEntity = ds.firstParseEntity;
newframe->numEntities = 0;

// delta from the entities present in oldframe
oldindex = 0;
if( !oldframe ) {
oldnum = 99999;
} else {
if( oldindex >= oldframe->numEntities ) {
oldnum = 99999;
} else {
oldstate = &ds.parseEntities[(oldframe->firstEntity + oldindex) & PARSE_ENTITIES_MASK];
oldnum = oldstate->number;
}
}

while( 1 ) {
newnum = MSG_ReadBits( msg, GENTITYNUM_BITS );
if( newnum < 0 || newnum >= MAX_GENTITIES ) {
Com_Error( ERR_DROP, "Parse_PacketEntities: bad number %i", newnum );
}

if( msg->readcount > msg->cursize ) {
Com_Error( ERR_DROP, "Parse_PacketEntities: end of message" );
}

if( newnum == ENTITYNUM_NONE ) {
break; // end of packetentities
}

while( oldnum < newnum ) {
// one or more entities from the old packet are unchanged
Parse_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue );

oldindex++;

if( oldindex >= oldframe->numEntities ) {
oldnum = 99999;
} else {
oldstate = &ds.parseEntities[(oldframe->firstEntity + oldindex) & PARSE_ENTITIES_MASK];
oldnum = oldstate->number;
}
}

if( oldnum == newnum ) {
// delta from previous state
Parse_DeltaEntity( msg, newframe, newnum, oldstate, qfalse );

oldindex++;

if( oldindex >= oldframe->numEntities ) {
oldnum = 99999;
} else {
oldstate = &ds.parseEntities[(oldframe->firstEntity + oldindex) & PARSE_ENTITIES_MASK];
oldnum = oldstate->number;
}
continue;
}

if( oldnum > newnum ) {
// delta from baseline
Parse_DeltaEntity( msg, newframe, newnum, &ds.baselines[newnum], qfalse );
}
}

// any remaining entities in the old frame are copied over
while( oldnum != 99999 ) {
// one or more entities from the old packet are unchanged
Parse_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue );

oldindex++;

if( oldindex >= oldframe->numEntities ) {
oldnum = 99999;
} else {
oldstate = &ds.parseEntities[(oldframe->firstEntity + oldindex) & PARSE_ENTITIES_MASK];
oldnum = oldstate->number;
}
}
}

/*
=====================
ParseSnapshot
=====================
*/
static void Parse_Snapshot( sizebuf_t *msg ) {
snapshot_t *oldsnap;
int delta;
int len;

// save the frame off in the backup array for later delta comparisons
ds.snap = &ds.snapshots[demo.demoMessageSequence & SNAPSHOT_MASK];
memset( ds.snap, 0, sizeof( *ds.snap ) );

ds.snap->seq = demo.demoMessageSequence;
ds.snap->serverTime = MSG_ReadLong( msg );

// If the frame is delta compressed from data that we
// no longer have available, we must suck up the rest of
// the frame, but not use it, then ask for a non-compressed
// message
delta = MSG_ReadByte( msg );

if( delta ) {
  ds.snap->deltaSeq = demo.demoMessageSequence - delta;
  oldsnap = &ds.snapshots[ds.snap->deltaSeq & SNAPSHOT_MASK];

if( !oldsnap->valid ) {
  // should never happen
  Com_Printf( "Delta from invalid frame (not supposed to happen!).n" );
} else if( oldsnap->seq != ds.snap->deltaSeq ) {
  // The frame that the server did the delta from
  // is too old, so we can't reconstruct it properly.
  Com_Printf( "Delta frame too old.n" );
} else if( ds.firstParseEntity - oldsnap->firstEntity > MAX_PARSE_ENTITIES - MAX_ENTITIES_IN_SNAPSHOT) {
  Com_Printf( "Delta parse_entities too old.n" );
} else {
  ds.snap->valid = qtrue; // valid delta parse
}

} else {
  oldsnap = NULL;
  ds.snap->deltaSeq = -1;
  ds.snap->valid = qtrue; // uncompressed frame
}

// read snapFlags
ds.snap->snapFlags = MSG_ReadByte( msg );

// read areabits
len = MSG_ReadByte( msg );
MSG_ReadData( msg, ds.snap->areamask, len );

// read playerinfo
MSG_ReadDeltaPlayerstate( msg, oldsnap ? &oldsnap->ps : NULL, &ds.snap->ps );

// read packet entities
Parse_PacketEntities( msg, oldsnap, ds.snap );
}

/*
=====================
Parse_DemoMessage
=====================
*/
static void Parse_DemoMessage( sizebuf_t *msg ) {
int cmd;

// remaining data is Huffman compressed
MSG_SetBitstream( msg );
MSG_ReadLong( msg ); // FIXME: not used in demos

//
// parse the message
//
while( 1 ) {
  if( msg->readcount > msg->cursize ) {
    Com_Error( ERR_DROP, "Parse_DemoMessage: read past end of demo message" );
    break;
  }

  cmd = MSG_ReadByte( msg );

  if( cmd == SVC_EOM ) {
    break;
  }

  // other commands
  switch( cmd ) {
    default:
    case SVC_BASELINE:
    case SVC_CONFIGSTRING:
    case SVC_DOWNLOAD:
      Com_Error( ERR_DROP, "Parse_DemoMessage: illegible demo message" );
      break;
    case SVC_NOP: /* do nothing */ break;
    case SVC_GAMESTATE: Parse_GameState( msg ); break;
    case SVC_SERVERCOMMAND: Parse_ServerCommand( msg ); break;
    case SVC_SNAPSHOT: Parse_Snapshot( msg ); break;
 }
}

}

11. Thanks to

– Martin Otten for Argus

– ID Software for releasing Quake II source code and Q3 SDK

Copyright (C) 2003 Andrey Nazarov (skuller-vidnoe@narod.ru)

Quake 3 config file structure

A description of how quake 3 uses your file structure and how best to deal with configuration changes – taken from my posts on various forums.

Regardless of the directory you installed quake3 in, it follows a set subdirectory structure.

  • Quake3 Install Directory
    • baseq3
    • Mod Directory 1
    • Mod Directory 2
    • Mod Directory 3
    • quake3.exe

baseq3 directory is where most stuff exists. Within it pak0.pk3 and pak1.pk3 (which are actually just zip files with a pk3 extension instead of zip) contain all the standard quake data from maps to gun shaders.

Every mod you play will create its own directory at the same level as baseq3, so most of you will have other folders specific to the mods you play.

All configuration parameters are stored in a file called q3config.cfg and you will find this file exists in baseq3 and any mod directory. If you remove this file from a mod directory then next time you play that mod quake3 will copy the q3config.cfg file from baseq3 into the mod directory. If you remove the q3config.cfg file from the baseq3 directory then you’ll lose all your settings and have to start again! Quake3 won’t break, it’ll just create a new q3config.cfg file, but it will be a default one without your tweaks in it.

When you join a server quake3 reads the q3config.cfg file from the relevent mod directory. This means you can have a different config for each mod if you like, but more often than not it means you have inconsistent settings between each mod because you’ll altered a particular q3config.cfg file. Sometimes quake3 even goes a little haywire and trashes your q3config.cfg file, leaving you with the default one again!

When quake3 starts up it searches for q3config.cfg and autoexec.cfg in baseq3. If it finds autoexec.cfg it will use the settings in that file in preference to those in q3config.cfg. As quake3 will never touch autoexec.cfg (apart from to load it) that is where you should put all customisations. It means you’ll have to fiddle around with notepad to edit it as the quake3 menus interact with q3config.cfg.

If you take a look in q3config.cfg you’ll find huge amounts of stuff that you couldn’t care less about so don’t just copy and paste it all into autoexec.cfg. Once you’ve made your own autoexec.cfg remember to delete the q3config.cfg files out of each of the mod directories so they can be recreated next time you play than mod (and include your new changes!).

A few important commands you may want to put in autoexec.cfg are listed below …

com_maxfps 125  // make sure you can get optimum fps
cg_drawFPS 1  // draw your fps in top right hand corner, default 0

r_picmip 3  // reduces detail level slightly, default 1
r_mapOverBrightBits 3  // most people find q3 too dark, set this to 2 if it is too bright

snaps 40  // how many updates per second you ask the server for (server has final say so 40 is a good default)

name UnnamedPlayer  // your player name!
model doom  // your preferred model

Quake 3 pk3 loading

A description of how pk3 files are loaded by quake 3 and in what order – taken from my posts on various forums

First off a pk3 file is just a zip file with a different extension. A program like winrar is great for examining the contents without actually unzipping it first.

pak0.pk3, pak1.pk3, pakX.pk3 (where X is a number) are standard quake pk3 files that contain all the game data such as models, maps and sounds.

When quake starts up it looks in baseq3 for any pk3 files. The ones it finds will be loaded in alphabetical order, so if two pk3 files contain the same named file the one which is loaded will come from the pk3 file with the latest alphabetical name. This is why id software released extra pakX.pk3 files with newer point releases – they just contain updates/additions to pak0.pk3 so as not to redistribute the whole 450mb!

The most common thing for mod developers to rewrite is the virtual machine files for game, cgame and ui. These 3 files (cgame.qvm, ui.qvm, game.qvm) are stored in the vm within a pk3 file. A mod like Nemesis needs to assure its pk3 file is loaded last which is why it is prefixed with zzz-, just like osp pk3 files.

Extra pk3 files may get loaded (servers running 1.11 don’t force this) when you join a server which is running something other than standard quake 3. Mod folders are stored in a directory alongside baseq3 (i.e damned_arena) and when you join a server running a particular mod, it’s pk3 files are also loaded.

For example, if I had zzz-nemesis-core.pk3 in a freezeplus mod directory quake 3 would start and act as normal. When I joined a server running freezeplus, Nemesis would get loaded from the pk3 file and I would be able to use it’s commands.

Maybe this explanation will shed some light for some people. I hope you realise you don’t often need to delete every pk3 file to get a particular mod to work, just rename them correctly or remove only the ones with similar files (e.g. a map pk3 will hardly ever interfer with a cgame modification like nemesis).

Quake 3 network protocol

This document describes the network protocol that quake 3 uses to converse with clients and the outside world (query servers). Currently more of a work in progress.

I recently (August 2012) added a blog entry with a revised version of my protocol 43 proxy server incase anyone finds it useful to continue experimenting.

Query

To query a server is very simple. Send a connectionless (UDP) packet with 4 OOB header bytes (0xff) and the text string getstatus. There are many sites which contain a thorough description of this so I won’t go into details.

Game Protocol 68 – used by 1.32

All game packets are connectionless (UDP), but there is still a handshaking process which must occur before you are allowed to join the server.

The client sends a challenge request (sometimes you need to send multiple requests before the server will respond).

+------------+----------------+
| Header     | Content        |
+------------+----------------+
| 0xffffffff | getchallenge   |
+------------+----------------+

If the server is able to accept more connections it will reply with.

+------------+------------------------+
| Header     | Content                |
+------------+------------------------+
| 0xffffffff | challengeResponse <ID> |
+------------+------------------------+

Once the client has the <ID> it can send a connect request. However, the CS is huffman compressed in protocol 68 and is NOT clear text as indicated below. Protocol 43 uses the plain text version.

+------------+----------------+
| Header     | Content        |
+------------+----------------+
| 0xffffffff | connect "<CS>" |
+------------+----------------+

<CS> represents a connection string containing the player details, e.g.
\cg_predictItems\1\sex\male\handicap\100\color\3\snaps\40\rate\10000\model\doom/red\name\UnnamaedPlayer\protocol\68\qport\<PORT>\challenge\<ID>
<PORT> represents the local port used to send this packet

If the connect is successful the server will reply with the following

+------------+-----------------+
| Header     | Content         |
+------------+-----------------+
| 0xffffffff | connectResponse |
+------------+-----------------+

The server will now place you in the CNCT (connecting) state and start sending you game updates.

This is where the communication gets substantially more difficult, so I warn you what follows may be incomplete and perhaps incorrect – although I hope not!.

Client to Server

+-----------------------------------------------------------------------------------------------+
| NAME                    | LEN | ENCODING     | COMMENT                                        |
+-------------------------+-----+---------------------------------------------------------------+
| sequenceNumber          | 32  | None         | MSG_ReadLong                                   |
| qport                   | 16  | None         | MSG_ReadShort                                  |
| serverId                | 32  | Huff (1)     | MSG_ReadLong                                   |
| messageAcknowledge      | 32  | Huff (1)     | MSG_ReadLong                                   |
| reliableAcknowledge     | 32  | Huff (1)     | MSG_ReadLong                                   |
+-------------------------+-----+--------------+------------------------------------------------+
| clientCommand           | 8   | (1), XOR (2) | MSG_ReadByte                                   |
| ...                     |     |              |                                                |
+-------------------------+-----+--------------+------------------------------------------------+

Client commands

0 - clc_bad
1 - clc_nop
2 - clc_move
3 - clc_moveNoDelta
4 - clc_clientCommand
5 - clc_EOF

Server to Client

+-----------------------------------------------------------------------------------------------+
| NAME                    | LEN | ENCODING     | COMMENT                                        |
+-------------------------+-----+---------------------------------------------------------------+
| sequenceNumber          | 32  | None         | MSG_ReadLong                                   |
| reliableAcknowledge     | 32  | Huff (1)     | MSG_ReadLong                                   |
+-------------------------+-----+--------------+------------------------------------------------+
| serverCommand           | 8   | (1), XOR (3) | MSG_ReadByte                                   |
| ...                     |     |              |                                                |
+-------------------------+-----+--------------+------------------------------------------------+

Server Commands

0 - svc_bad
1 - svc_nop
2 - svc_gamestate
3 - svc_configstring
4 - svc_baseline
5 - svc_serverCommand
6 - svc_download
7 - svc_snapshot
8 - svc_EOF

Details

(1) – Huffman compression using a predefined set of nodes to further reduce message length (detailed below).

int msg_hData[256] = {
250315,// 0
41193,// 1
6292,// 2
7106,// 3
3730,// 4
3750,// 5
6110,// 6
23283,// 7
33317,// 8
6950,// 9
7838,// 10
9714,// 11
9257,// 12
17259,// 13
3949,// 14
1778,// 15
8288,// 16
1604,// 17
1590,// 18
1663,// 19
1100,// 20
1213,// 21
1238,// 22
1134,// 23
1749,// 24
1059,// 25
1246,// 26
1149,// 27
1273,// 28
4486,// 29
2805,// 30
3472,// 31
21819,// 32
1159,// 33
1670,// 34
1066,// 35
1043,// 36
1012,// 37
1053,// 38
1070,// 39
1726,// 40
888,// 41
1180,// 42
850,// 43
960,// 44
780,// 45
1752,// 46
3296,// 47
10630,// 48
4514,// 49
5881,// 50
2685,// 51
4650,// 52
3837,// 53
2093,// 54
1867,// 55
2584,// 56
1949,// 57
1972,// 58
940,// 59
1134,// 60
1788,// 61
1670,// 62
1206,// 63
5719,// 64
6128,// 65
7222,// 66
6654,// 67
3710,// 68
3795,// 69
1492,// 70
1524,// 71
2215,// 72
1140,// 73
1355,// 74
971,// 75
2180,// 76
1248,// 77
1328,// 78
1195,// 79
1770,// 80
1078,// 81
1264,// 82
1266,// 83
1168,// 84
965,// 85
1155,// 86
1186,// 87
1347,// 88
1228,// 89
1529,// 90
1600,// 91
2617,// 92
2048,// 93
2546,// 94
3275,// 95
2410,// 96
3585,// 97
2504,// 98
2800,// 99
2675,// 100
6146,// 101
3663,// 102
2840,// 103
14253,// 104
3164,// 105
2221,// 106
1687,// 107
3208,// 108
2739,// 109
3512,// 110
4796,// 111
4091,// 112
3515,// 113
5288,// 114
4016,// 115
7937,// 116
6031,// 117
5360,// 118
3924,// 119
4892,// 120
3743,// 121
4566,// 122
4807,// 123
5852,// 124
6400,// 125
6225,// 126
8291,// 127
23243,// 128
7838,// 129
7073,// 130
8935,// 131
5437,// 132
4483,// 133
3641,// 134
5256,// 135
5312,// 136
5328,// 137
5370,// 138
3492,// 139
2458,// 140
1694,// 141
1821,// 142
2121,// 143
1916,// 144
1149,// 145
1516,// 146
1367,// 147
1236,// 148
1029,// 149
1258,// 150
1104,// 151
1245,// 152
1006,// 153
1149,// 154
1025,// 155
1241,// 156
952,// 157
1287,// 158
997,// 159
1713,// 160
1009,// 161
1187,// 162
879,// 163
1099,// 164
929,// 165
1078,// 166
951,// 167
1656,// 168
930,// 169
1153,// 170
1030,// 171
1262,// 172
1062,// 173
1214,// 174
1060,// 175
1621,// 176
930,// 177
1106,// 178
912,// 179
1034,// 180
892,// 181
1158,// 182
990,// 183
1175,// 184
850,// 185
1121,// 186
903,// 187
1087,// 188
920,// 189
1144,// 190
1056,// 191
3462,// 192
2240,// 193
4397,// 194
12136,// 195
7758,// 196
1345,// 197
1307,// 198
3278,// 199
1950,// 200
886,// 201
1023,// 202
1112,// 203
1077,// 204
1042,// 205
1061,// 206
1071,// 207
1484,// 208
1001,// 209
1096,// 210
915,// 211
1052,// 212
995,// 213
1070,// 214
876,// 215
1111,// 216
851,// 217
1059,// 218
805,// 219
1112,// 220
923,// 221
1103,// 222
817,// 223
1899,// 224
1872,// 225
976,// 226
841,// 227
1127,// 228
956,// 229
1159,// 230
950,// 231
7791,// 232
954,// 233
1289,// 234
933,// 235
1127,// 236
3207,// 237
1020,// 238
927,// 239
1355,// 240
768,// 241
1040,// 242
745,// 243
952,// 244
805,// 245
1073,// 246
740,// 247
1013,// 248
805,// 249
1008,// 250
796,// 251
996,// 252
1057,// 253
11457,// 254
13504,// 255
};

(2) – XOR algorithm used by the server to decode the message content.

#define CL_ENCODE_START 12
byte key, *string;
int i, index;

string = (byte *)clc.serverCommands[ reliableAcknowledge &amp; (MAX_RELIABLE_COMMANDS-1) ];
index = 0;
//
key = clc.challenge ^ serverId ^ messageAcknowledge;
for (i = CL_ENCODE_START; i &lt; msg-&gt;cursize; i++) {
    // modify the key with the last received now acknowledged server command
    if (!string[index]) {
        index = 0;
    }

    if (string[index] &gt; 127 || string[index] == '%') {
        key ^= '.' &lt;&lt; (i &amp; 1);
    }  else {
        key ^= string[index] &lt;&lt; (i &amp; 1); } index++; // encode the data with this key *(msg-&gt;data + i) = (*(msg-&gt;data + i)) ^ key;
}

(3) – XOR algorithm used by the client to decode the message content.

Notes

data has mixed endianess – is this right?

Packet fragmentation is worked out using a sequencetNumber & FRAGMENT_BIT (where FRAGMENT_BIT is 1<<31) calculation. If fragmented, flip the bit to 0 to correct the sequence number.

cl_shownet 1 – MSG_SIZE
cl_shownet 2|3 – READ_COUNTCMD
showpackets 1 – WHO recv MSG_SIZE : s=SEQ_NO (optional fragment info)

Acknowledgements