Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A gameserver Query Library

0.00/5 (No votes)
26 Dec 2004 3  
A library that allows to query nearly all gameserver types

Sample Image - GameServerInfo.jpg

Introduction

Most gaming servers support status queries to get information like server configuration or the number of players. There are some programs that query master servers to receive all IPs from the gaming servers and list them. The most popular ones are Gamespy, The All Seeing Eye and kQuery.

I did not intend to build another program with just the same functionality, but to create a library that allows easy handling to query the servers and offering their information on your special purpose.

Background

The status queries use specific protocols: the most common are Doom3, Gamespy, Gamespy v2, Half-Life, Quake3, Source (Half-Life 2) and The All Seeing Eye (ASE). These protocols provide basically the same information, but their structure is different. In general, there are three sections:

Infos

The basic information like server name, map name, maximum players and password protection.

Rules

The detailed server information with game specific data like teams or response time.

Players

A list of all players on the server. These information differs a lot: the different protocols have only the player name in common, depending on the protocol there are also the players' score, ping or team.

Protocol Overview

Here is a short overview of the different protocols and how they work:

Doom3

This protocol is currently used only in so called game, but will probably be used in upcoming games built on the same engine. The request starts with a 0xFF 0xFF, followed by a simple getInfo. The server response comes in one big block and is separated by 0x00. That makes the response look like a Key0x00Value – the server information and the player information are separated by a 0x00 0x00 (a double NULL).

Gamespy

Gamespy is one of the oldest and most broadly used protocols. Its structure is very simple: to get the server's information, you just need to send a simple \info\; for the rules, send \rules\; and for the players, \players\. You can also combine these requests by sending something like \infos\\players\\rules\.

The response looks like \Key\Value\Key\Value\, the player info is extended with an integer to group them. They look like \player_0\Name\frags_0\10\ping_0\100\. This is important because some servers send all \player_x\ flags, followed by all \frags_x\ and so on.

Gamespy v2

The name indicates, Gamespy v2 is the revision of the Gamespy protocol. It differs from the first version by managing information more with byte values than with strings. All requests begin with a 0xFE 0xFD 0x00, followed by a string that is used as a ping value. Now we append three more bytes which stand for the information we want to know: the first byte for server information, the second for rules and the third for players. You can set them with 0x00 for NO or 0xFF for YES. The structure of the response is the same as in Doom3.

Half-Life

The request starts with a 0xFF 0xFF 0xFF 0xFF, followed by details, rules or players, so it is pretty much the same as in Gamespy v2, but with strings. The structure of the response is the same as in Doom3.

Quake3

The request is similar to Half-Life: we start with a 0xFF 0xFF 0xFF 0xFF, followed by the string getstatus. The response includes all information in multiple text lines and can be handled with simple string parsing. The first line can be ignored, the second provides the server information in the same format as in Gamespy, (\Key\Value\Key\Value\). The following lines represent the player information, one player per line, in the form Scores Ping „Playername“.

Source (Half-Life 2)

The request structure is nearly the same as in the former Half-Life protocol: only the strings are replaced with single bytes. The query now starts with 0xFF 0xFF 0xFF 0xFF, followed by 0x54 for the server information, 0x55 for the rules and 0x56 for the player information. The structure of the response is the same as in Doom3.

The All Seeing Eye (ASE)

The protocol to the homonymous program is the most complex and also the rarely used one. The request is a simple s, but different from all other protocols. The query must be sent to the server port + 123, so don't send to the game server port. The response is a little bit more difficult, because it has no separators: The structure is „Byte String Byte String“, in which the byte represents the length of the next string, (the byte is self-including). The separator between the server and player information is an empty string, so it's the length of the byte itself - a simple 0x01. The player information looks similar, but starts with an extra byte by using the flags telling which information is included. I do not describe this protocol any further, the only games currently using this protocol are GTA and Farcry.

An excellent documentation and samples for all of these protocols can be found in the newsgroups dev.int64 and dev.kquery, which I used while creating this library.

Using the Code

Using the library is very simple. First, include it in your project, and then simply call it as shown:

GameServer server = new GameServer( "192.168.0.15", 27960, GameType.Quake3 );
Server.QueryServer(); // this may take a while

QueryServer() gets all the information from the server. Depending on the timeout, this may take a while. You can change this with the Timeout property.

Server.Timeout = 1500; // in milliseconds

The basic information is available over properties and collections, depending on which protocol you are querying. Not all values are set. Here are the diagrams for all of them:

Image 2

Using the Players property, you can get a PlayerCollection containing the following data:

Image 3

The Parameters property contains a StringCollection with all information from the server. The values strongly depend on the protocols being used!

Connecting to Servers

The complete connection and parsing process is handled through the abstract protocol class. Here is the main connection part:

protected void Connect( string host, int port )
{
    _serverConnection = new Socket( AddressFamily.InterNetwork, 
        SocketType.Dgram, ProtocolType.Udp );
    _serverConnection.SetSocketOption( SocketOptionLevel.Socket, 
        SocketOptionName.ReceiveTimeout, _timeout );
    IPAddress ip;
    try
    {
        ip = IPAddress.Parse( host );
    }
    catch( System.FormatException )
    {
        ip = Dns.Resolve( host ).AddressList[0];
    }
    _remoteIpEndPoint = new IPEndPoint( ip, port );
}

Server Communication

The communication always uses UDP supplied by the methods Socket.SendTo() and Socket.ReceiveFrom().

_readBuffer = new byte[100 * 1024]; // 100kb should be enough
EndPoint _remoteEndPoint = (EndPoint)_remoteIpEndPoint;
_packages = 0;
int read = 0, bufferOffset = 0;

// Request
_sendBuffer = System.Text.Encoding.Default.GetBytes( request );
_serverConnection.SendTo( _sendBuffer, _remoteIpEndPoint );
...
read = _serverConnection.ReceiveFrom( _readBuffer, ref _remoteEndPoint );

Most servers send packages with a maximum length of 1400 to 1500 bytes. For long status information, this is not enough memory to send it in one package. Usually, they fit into two packets. UDP is a stateless connection, so we must implement how to handle these multi-packages and how to merge them.

Half-Life

In Half-Life, these UDP-packages start with a 9 byte-long header, different from single-package responses. The ninth position is telling us how many packages the response includes, and which package we are currently working on.

byte[] _tempBuffer = new byte[100 * 1024];
read = _serverConnection.ReceiveFrom( 
  _tempBuffer, ref _remoteEndPoint );

int packets = ( _tempBuffer[8] & 15 );
int packetNr = ( _tempBuffer[8] >> 4 ) + 1;

if ( packetNr < packets )
{
    Array.Copy( _readBuffer, 9, _tempBuffer, read, bufferOffset );
    _readBuffer = _tempBuffer;
}
else
{
    Array.Copy( _tempBuffer, 9, _readBuffer, bufferOffset, read );
}

Gamespy v1 and v2

All Gamespy responses are built in a Key/Value scheme, so it doesn’t really matter if we merge them in the right order. The protocol itself is smart enough to always split at a separator and not inside a value, so we can simply attach them at the end of the readBuffer.

read = _serverConnection.ReceiveFrom( _readBuffer, bufferOffset, 
    ( _readBuffer.Length - bufferOffset ), 
      SocketFlags.None, ref _remoteEndPoint );

Parsing the Response

The main part of parsing the strings that are separated by a NULL byte (0x00):

protected string ReadNextParam()
{
    string temp = "";
    for ( ; _offset < _readBuffer.Length; _offset++ )
    {
        if ( _readBuffer[_offset] == 0 )
        {
            _offset++;
            break;
        }
        temp += (char)_readBuffer[_offset];
    }
    return temp;
}

String-separated responses are even more simple:

AddParams( ResponseString.Split( '\\' ) );
//...
protected void AddParams( string[] parts )
{
    if ( !IsOnline )
    {
        return;
    }
    string key, val;
    for ( int i = 0; i < parts.Length; i++ )
    {
        if ( parts[i] == "" )
        {
            continue;
        }
        key = parts[i++];
        val = parts[i];

        // Gamespy uses this
        if ( key == "final" )
        {
            break;
        }
        if ( key == "querid" )
        {
            continue;
        }

        _params[key] = val;
    }
}

I avoided explaining how to parse the ASE protocol, because it doesn’t work 100%. I’m missing some good documentation, and it seems that many people besides me are also thinking that it’s not worth the work.

Links

I can't thank enough these guys. For more information, check these pages:

Summary

I learned something about UDP protocol and used classes and methods I never used before, like the BitConverter(), so I think it was worth the work. I hope this library can be useful in your future projects.

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here