Click here to Skip to main content
15,867,594 members
Articles / Programming Languages / C#
Article

UT2003 Gameserver Status

Rate me:
Please Sign up or sign in to vote.
5.00/5 (6 votes)
16 Oct 200216 min read 158.2K   920   40   33
Getting the current Status of a UT2003 Gameserver via UDP Queries

Image 1

Image 2

What the hell...

Today first person shooter games like Counter Strike, Quake and Unreal Tournament are played online mostly. Each game ships with a built-in server-browser to show active servers on the net. A lot of people would like to have such a server-browser or similar tools as standalone applications. Maybe they want to look for more than one game at the same time, maybe standalone tools have additional features (like enhanced filtering) or are lightweight applications that do their specific job much faster than in the in-game-browsers, whatever. With Unreal Tournament 2003 being available soon, I thought it would be a good idea to share my code. Provided with this backend-class, you are able to develop your own server-browser, server-watching-tool or whatever you like.

Introduction

There are several sources of information on how to get the latest status of an Unreal Tournament server (see the Links-section below). (I personally play none of the other games, so I don't know about their query-protocols, but i believe, they are similar to the Unreal-ones presented here.) When speaking about Unreal Tournament 2003, there are two protocols available. The GameSpy-protocol that already worked with Unreal Tournament (the old version of the game) and a brand-new protocol, that was first seen with the Unreal Tournament 2003 Demo available in mid-September 2002 (when this article was written).

The 'old' GameSpy-protocol is well documented by Epic (the company that does all the Unreal-series games). On Epic's Unreal Technology Site there is a page on server-communication that gives us a lot of information. In short: send '\status\' via UDP to the server, but add 1 to the official gameport (e.g: server-address = 62.93.201.43:8980 - send query to 62.93.201.43:8981). What you get back is a bunch of packets, each in the following format: "\property\value\property\value\...\queryid\X.Y".

Each query is assigned a unique ID to - all the packets you receive from one query should have the same X-value. The second number Y is the packet-number (1-indexed). This is essential, since UDP is a connectionless protocol, thus packages may take different routes through the internet and may arrive out of order. Additionally from time to time packets get lost. Checking the packet-numbers you can asure, that you received all packets in the correct ordering and that none is missing. The last package ends with the string '\final\'.

To give you an idea, of how a typical answer looks like, here is a very simple one:

\gamename\ut2d\gamever\1080\minnetver\1000\location\0
\hostname\www.inUnreal.de UT2k3 TDM2 @Clanserver4u\hostport\8980
\maptitle\Citadel\mapname\CTF-Citadel\gametype\xCTFGame
\numplayers\0\maxplayers\12\gamemode\openplaying\gamever\1080
\minnetver\1000\AdminName\hEx\AdminEMail\hex@inunreal.de\queryid\66.1\final\

Currently there's no player active on the server and we received all the information in one single packet.

Querying an 'old' UT-server, you would use Ip:gameport+1 with that GameSpy-protocol (as written in that official document I mentioned above). Now in times of UT2003, that protocol was moved over to gameport+10. On gameport+1 there's this new, let's call it Unreal-protocol, which is byte-oriented.

There's no official documentation on this protocol, but since it is far more robust (no backslash-bug, see below) and faster to parse, it will be used a lot in the future. See the Links-section for pages of people that looked at the packets (sent by the original built-in UT2003 server-browser) and have written down, what they found out.

Update: After observing the two protocols for a few weeks now, there are certain important things to mention. See the Protocol Discussion Section below.

You guess what the two 'in-depth' parts of this article (and the main problems writing the code for our purpose) are: connect to the server and parse the answer. But before getting into detail, let's have a look at how to use this class:

Usage

First thing to do is to add the provided C#-file UT2003Server.cs into your project.

Next you construct your server-object and call at least once the Refresh()method. This method connects to the server, parses the answer and assigns the current values to all the server-objects properties. Well, and then you just use them for your displaying pleasure. :)

C#
server = new UT2003Server("62.93.201.43",8980);
// ...
try
{
    server.Refresh();
    textBoxServerName.Text = server.HostName;
    textBoxMapTitle.Text = server.MapTitle;
    textBoxPlayerCount.Text = server.NumPlayers.ToString();
    for(int i=0; i<server.Players.Length;++i)
    {
        listBox.Add(server.Players[i].Name);
    }
    // other properties are Version,  MinClientVersion, MapFilename, 
    // Gametype (DM, CTF, ...)
    // MaxPlayers, AdminName, AdminEmail, Player-array containing 
    // name,ping,frags for each player
    // and more...
}
catch (GameserverQueryException ex)
{
    MessageBox.Show(ex.Message,"Error");
}

Pretty simple. I would strongly recommend to put the Refresh() into a try-block and check for GameserverQueryExceptions and handle these cases accordingly in your application. UDP really isn't that reliable and from time to time the server doesn't respond or responds too slow or the user just isn't online. These exceptions do really occur. ;)

Refinement I: Using server.Timeout you can adjust the maximum number of milliseconds, the query-class waits for a response before raising a ServerNotReachableException (which is a GameserverQueryException) when executing a refresh.

Refinement II: Using server.Protocol you can decide what query-protocol is used, the next time you call Refresh(). What protocol you use is up to your needs. The GameSpy-protocol delivers less information on the game settings but current player-statistics. The Unreal-protocol gives outdated player-information but more general settings. I've introduced a third protocol, that is a mixture of the two. First it uses the GameSpy-protocol to get the current player-statistics and after that a part of the Unreal-protocol enlarges the information on the game-settings.

Attention: Be aware that some properties of the server-object may equal to null after refreshing. Some servers just don't deliver all possible information. Additionally future patches of the game may change or add certain properties. Hopefully I have the time to update this article each time something changes dramatically.

Update: In version 2.1 there was a new property introduced to the class, named ServerVars. This is a collection of all the server variables available, making the usage of the class far nicer and easier. The 108 lines of code for displaying all these values in the example-application dropped down to 7 lines. :)

for(int i=0; i<server.ServerVars.Count; ++i)
{
    ListViewItem prop = new ListViewItem(new String[] {
      UT2003Server.Prop2En(server.ServerVars.GetKey(i)),
      server.ServerVars[i]});
    listViewServer.Items.Add(prop);
}

The new static function Prop2En translates a property-name given by the server into a human readable English phrase (e.g. "gamestats"→"Stats logging" or "maptitle"→"Map Title").

There are a few things left to mention about ServerVars. As, for ease of use (i.e. without casting), all the items in the collection need to be of the same type (which is string), the properties, that are not strings by nature are converted internally. So you don't get Port, NumPlayers, MaxPlayers etc back as ints, but as strings, when using server.ServerVars["numplayers"] instead of server.NumPlayers. But as both are still available, you can choose the way you like it. The same with mutators: server.Mutators returns a string array containing all mutators, server.ServerVars["mutator"] one string containing all mutators as a comma-separated list. Another advantage is the fact, that the ServerVars-collection is cleared, when you assign a new IP to the server-class.

The rule is: When you want to display all server-properties in a list or similar, loop through ServerVars. When you need access to a certain server-variable use the corresponding property.

Querying the Masterserver

There is no real querying of the masterserver, at least if you don't know the authentication-algorithm, that UT2003 uses to connect to the masterserver. EPIC doesn't want everyone to query the server via TCP (as in good old UT times), but rather updates a textfile every 20 seconds (or something in that dimension) which is free for download. There's one file for the UT2003 demo-version and another for the retail:

The Format is pretty simple: one line for each server and each line containing three tab-separated values, which are

IP → Gameport → GameSpy-Queryport
I've added two little static functions to the UT2003Server-class for each list. In your code, querying the masterserver will look like this:
...
UT2003Server [] serverList;
serverList = UT2003Server.QueryMaster();
...

That's it.

Protocol Discussion

How to ask?

The UT2003Server.QueryProtocol enumeration contains three entries. And it's up to you to choose, which protocol is used when querying a server. Some thoughts may help you to decide, what fits your needs best. But there's nothing, you could damage, so just try, if you're unsure.

None of the two protocols is better in each and every case. It seems, that the output of the new Unreal-protocol is cached on the server and far from being up-to-date. If you query a server every 2 seconds, you see that the output of the GameSpy-protocol changes everytime something happened (especially the ping and frags of the players) while the Unreal-protocol still delivers things from the past. On the other hand, the GameSpy-protocol gives you less information on the game and the server in general. Thus getting information about mutators, friendly fire, gamespeed, etc., you have to use the new Unreal-protocol. Summary: only using both protocols gives you complete and up-to-date information. That's why I introduced the Mixed-Protocol, that does the job for you.

The following table outlines the properties, the protocols deliver exclusively (i.e. the other one doesn't):

GameSpy Unreal
  • current player statistics
  • current number of players
  • name of the level (not the file name), i.e.: Temple of Anubis instead of BR-Anubis
  • game name
  • game version
  • min client version
  • server mode
  • game speed
  • game stats
  • mutators
  • goal score
  • time limit
  • translocator
  • friendly fire
  • min players

Where to ask?

Some Servers don't have their GameSpy-Queryport on Gameport+10, since this is freely configurable by the server-admin. When I wrote this, there were 1196 servers online, 951 (79.5%) of them using Gameport+10 (default), 118 (9.8%) using Gameport+12, 67 (5.6%) using Gameport+1 and 60 (5.0%) using other Offsets (even negative ones, i.e. Gameport > Queryport). That means: there's no 100% reliable way to get the Queryport out of the Gameport, but with +10 or +12 you are right with a probability of 90%. Using Gameport+1 will work with half of the remaining 10%, but I have no idea, where these servers put the Unreal-protocol Queryport (since Gameport+1 is the default value here). But it turns out, that the entire story isn't that frustrating, because EPICs masterserver just tells you the GameSpy-Queryport - so if you get your servers from the masterserver, everything is alright. If you want to observe a certain server given by IP and Gameport, you have to ask the administrator for the GameSpy-Queryport or try yourself. The message is: it is not always Gameport+10!

If you just want to use the class and don't care much about details and the problems I had to face, you can stop reading here and do a little C# instead. ;)

Connect to the Server

Well, now lets dive into the Details. First some preparatory work: The UdpClient-class in the System.Net.Sockets-namespace offers a blocking Receive() method, that doesn't return until a UDP-packet arrives at your computer. What I wanted is something that does return null or raises an exception, if a timer expires, such that you don't have to wait infinitely if something goes wrong at the network communication level. Daniel Turini sent me with this post here on the codeproject forums in the direction to try

C#
UdpClient::Client.SetSocketOption(SocketOptionLevel.Socket, 
    SocketOptionName.ReceiveTimeout, 10000);

but that just didn't work - would have been too nice. Notice the Client-property is protected, you have to build your own child-class to be able to access it. And that's what I did in the end to solve the problem: I created a new TimedUdpClient class which I present shortly here:

C#
public class TimedUdpClient : UdpClient
{
    private int timeout = 1000; // 1 sec default
    private Thread timeoutWatchdog;
    public int Timeout {
        get { return timeout; }
        set { timeout = value; }
    }

    public new byte[] Receive(ref IPEndPoint remote)
    {
        byte [] ret;
        timeoutWatchdog = new Thread(new ThreadStart(StartWatchdog));
        timeoutWatchdog.Start();
        try
        {
            ret = base.Receive(ref remote);
        }
        catch (SocketException) { ret=null; }
        finally { timeoutWatchdog.Abort(); }
        return ret;
    }

    private void StartWatchdog()
    {
        Thread.Sleep(timeout);
        this.Send(new byte[] {0x00},1,"",8000); // port is arbitrary
    }
}

It makes a new Receive()-function available, that starts a "watchdog"-thread before calling the original blocking Receive()-function from its base class UdpClient. The watchdog just waits the specified amount of time and then just sends a single byte on the same socket the client is listening to. Unfortunately this byte is not received normally (so that one could distinguish between packets sent from outside and packets sent by myself), instead an exception is raised somewhere inside UdpClient::Receive() and the function returns. Hey, actually that's the only thing we wanted. :) Maybe there are nicer ways, maybe not. But it works.

Having that and the base functionality of UdpClient due to inheritance, we can start querying the server.

The first version of this article had a lot of code here, which was kind of straightforward and only lengthened the page needlessly. I will give you only the core parts now, if you're interested you will look at the code anyway ;)

First lets have a look at the GameSpy-Query part:

C#
// set the server
remote = new IPEndPoint(ip,port+gameSpyQueryOffset);
// form the query
byte[] query = Encoding.ASCII.GetBytes("\\status\\");
// send the query
udp.Send(query,query.Length,remote);
// receive result
answer = "";
byte[] receive = null;
DateTime start = DateTime.Now;
TimeSpan diff;
do
{
    receive=udp.Receive(ref remote);
    // turn received byte array into a string
    if (receive!=null)
        answer += Encoding.ASCII.GetString(receive);
    DateTime now = DateTime.Now;
    diff = now-start;
} while (!answer.EndsWith("\\final\\") && receive!=null && 
    (diff.Seconds*1000+diff.Milliseconds < timeout));
if (receive==null || (diff.Seconds*1000+diff.Milliseconds >= timeout))
    throw new ServerNotReachableException("timed out");
// evaluate
AnalyseGameSpyAnswer();

Sending the query via UDP needs the query itself to be in a byte-array, which is easily constructed with the help of the Encoding-namespace. As you know from the previous discussions gameSpyQueryOffset will be equal to 10 in most cases. The AnalyseGameSpyAnswer-function is described later on in the parsing-part of this article.

Update: I found one server on the masterlist, that returned nonsense, when asked with '\status\' on the correct port. Since I want that class to work with every server on the masterlist, I updated the piece of code above with an additional timeout-condition, when there is something received, but no '\final\' could be found within the given timeout. The ominous server sent an increasing stream of zero-bytes...

The Unreal-Query is devided into three parts (see the documentation for details), where all three queries are sent and evaluated, when the Unreal-Protocol is used, and only the second query is used, when working with the mixed protocol (since basic and player information is already there from the GameSpy-Answer in that case).

C#
// server props query
remote = new IPEndPoint(ip,port+unrealQueryOffset);
byte [] queryServer = {0x78,0x00,0x00,0x00,0x01};
udp.Send(queryServer,queryServer.Length,remote);
receiveServer=udp.Receive(ref remote);

This is the code for the second query, which receives the server properties packet. The two other ones are analog. Afterwards the AnalyseUnrealAnswer-function is called, which extracts the correct information from the byte-arrays.

Parse the Answer

GameSpy Protocol

First, I'll give you a real world example:

\gamename\ut2d\gamever\1077\minnetver\1000\location\0
\hostname\The Drunk Snipers UT2003 CTF Instagib (DEMO) #2\hostport\7777
\maptitle\Citadel\mapname\CTF-Citadel\gametype\xCTFGame\
    numplayers\11\maxplayers\16
\gamemode\openplaying\gamever\1077\minnetver\1000\AdminName\
    Sirius\AdminEMail\sirius@drunksnipers.com
\player_0\Player\frags_0\1\ping_0\ 63\team_0\0
\player_1\Perdition\frags_1\0\ping_1\ 221\team_1\1
\player_2\Porter\frags_2\10\ping_2\ 143\team_2\1
\player_3\JoeDozer\frags_3\20\ping_3\ 289\team_3\0
\player_4\Kee\/n\frags_4\38\ping_4\ 117\team_4\0
\player_5\Rekklih\frags_5\42\ping_5\ 166\team_5\1
\player_6\Hanover_Fist\frags_6\4\ping_6\ 196\team_6\0
\player_7\HELLAS-4774CK\frags_7\101\ping_7\ 107\team_7\1
\player_8\Psycho\frags_8\49\ping_8\ 105\team_8\1\queryid\29.1
\player_9\Levodopa\frags_9\45\ping_9\ 36\team_9\0
\player_10\Psy[Duck]\frags_10\49\ping_10\ 73\team_10\0\queryid\29.2\final\

This answer comes in two packets (29.1 and 29.2) in correct ordering and 29.2 is definitely the last packet, because it ends with '\final\'. There are 11 players currently on the server - the demo-version had a bug, that caused the server to send a weird team-value. The class takes care of that and simply sets the team-value to an empty string, when querying a UT2003-Demo server.

There's another problem in that answer, that is well known to GameSpy-Style-Query-Programmers. The 'very cool' Player named 'Kee\/n' confuses our nice little answer.Split(new char[] {'\\'});-parser, because he uses the splitter-character (the backslash) in his name. If you don't take care of that, you will get his name has 'Kee' and after that a property named '/n' with the value 'frags_4' - which is nonsense. *grmpf*

But hey, you're a programmer and programmers are there to solve problems. Well, at least I solved the problem with the following hack: if the next property immediately after 'player_x' is not 'frags_x' or 'queryid' a playername with a backslash is detected and handled appropriately. In fact a backslash ('\') and the nonsense-property-name ('/n') (which is the second part of the players name) are appended to the name and the nonsense-value ('frags_4') becomes the new property-name. Put that into a while loop (to handle even cooler players using more than one backslash in their name) and everything goes fine.

Since there's nothing technically new and it should be an easy read, just go ahead and look at the code, if you're interested in the details.

UT2003-protocol

When you stick to the specification, parsing the byte-arrays is pretty straightforward. I needed a function that extracts a string from the byte-array, another reading an int and the rest is ++pos. ;)

Notice, that this protocol may change with each patch version released for UT2003. Currently it is working with the UT2003 Demo and Retail version! Maybe some patched servers deliver additional information, maybe something is altered - please contact me, if you think you've found a server, that sends things that are not taken care of by my class.

Other interesting Problems

What about the player-properties? I included a very basic Player-class and wanted the user from outside to use a simple Player-array. The problem with arrays is, that you have to know how big they are, when you create them. The other possibility, an ArrayList, is more dynamic by nature, but it only handles objects. So I made a tradeoff and went the middle way: When parsing the answer, the players are put together in a growing ArrayList and at the very end it is kind of converted into an array, which is fast accessible and doesn't require the user to cast anything. :) The same principle is used for the returned string-array containing the mutators and the list of servers returned by the masterserver query function.

Btw: I won't rely on the 'numplayers'-property to be there all the time. Using that (and assuming it always is delivered before the first player-property) might be a solution to know the size of the player-array before the first player occurs.

A little request: if you're using that class when debugging your application or for private use, I would be glad if you set RaiseExceptionOnUnhandledProperty to true. This raises an exception, when the class receives a property that is not known so far. When this member is false (default) unknown properties are just ignored. I've tested the class a lot, but you never know. If you've found an exotic server delivering unknown properties, please contact me and I will extend the class with the new property.

Links

Changelog

Version 2.2 - 10/17/2002

  • nothing new about the queryclass - just extended the demo-application to show pictures, if present in the 'mappics'-subfolder (relative to the folder the executable was started from)

Version 2.1 - 10/16/2002

This version is compatible with 2.0 - so replacing UT2003Server.cs with that new version in an existing project, should work fine.

  • new class-property ServerVars: a collection of all server-properties (thx to Todd Smith for the suggestion, see the notes and the very end of this page)
  • new property handled by the Unreal-Protocol: servermode
  • new static function Prop2En translating property-names into a nicer short description
  • fixed an issue in ExtractString() (a private helping function for AnalyseUnrealAnswer()) - leading to some code-beautification
  • additional timeout in GameSpy-Protocol receive loop: ignore servers sending crap

Version 2.0 - 10/08/2002

  • Article: completely revised
  • new properties handled by the Unreal-Protocol: gamestats, translocator, password, gamespeed
  • mutators are returned as a string-array
  • new properties handled by the GameSpy-Protocol: password, teams
  • query-port offsets configurable
  • mixed protocol query
  • masterserver-query

Version 1.1 - 09/25/2002

  • fixed a little bug in the demo-application, nothing about the query-class

Version 1.0 - 09/25/2002

  • things seem to work
  • Unreal-protocol is in but tested too little

Aftermath

Sorry for my bad English. I'm trying to improve it, but here and there, you see I'm natively German-speaking. :rolleyes:

Prospective: When future patches are released, I will make sure, the class runs with all versions of UT2003. This is my first article. Feel free to make suggestions, both on the code and on the article itself. Email: ruepel@gmx.li

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


Written By
Web Developer
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralBuddy List Pin
bluehawk8029-Oct-06 6:36
bluehawk8029-Oct-06 6:36 
GeneralSetSocketOption works Pin
Ralph Varjabedian17-Feb-05 6:58
Ralph Varjabedian17-Feb-05 6:58 
GeneralRe: SetSocketOption works Pin
Weckmann25-May-05 2:33
Weckmann25-May-05 2:33 
GeneralRe: SetSocketOption works Pin
rhofboer20-Sep-05 23:06
rhofboer20-Sep-05 23:06 
See:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemnetsocketsudpclientclassclienttopic.asp[^]

// This derived class demonstrate the use of three protected methods belonging to the UdpClient class.
public class MyUdpClientDerivedClass : UdpClient{

public MyUdpClientDerivedClass() : base(){
}
public void UsingProtectedMethods(){

//Uses the protected Active property belonging to the UdpClient base class to determine if a connection is established.
if (this.Active){
//Calls the protected Client property belonging to the UdpClient base class.
Socket s = this.Client;
//Uses the Socket returned by Client to set an option that is not available using UdpClient.
s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
}

}



Greets

Rogier
QuestionCan you show us how to do sockets? Pin
Anonymous29-Jan-04 7:40
Anonymous29-Jan-04 7:40 
AnswerRe: Can you show us how to do sockets? Pin
Anonymous29-Jan-04 7:41
Anonymous29-Jan-04 7:41 
GeneralRe: Can you show us how to do sockets? Pin
Rüpel29-Jan-04 8:17
Rüpel29-Jan-04 8:17 
GeneralRe: Can you show us how to do sockets? Pin
FocusedWolf1-Feb-04 8:38
FocusedWolf1-Feb-04 8:38 
GeneralRe: Can you show us how to do sockets? Pin
Anonymous6-Feb-04 9:37
Anonymous6-Feb-04 9:37 
QuestionOther Game-Protocols ? Pin
Gellay20-Jan-03 18:36
Gellay20-Jan-03 18:36 
AnswerRe: Other Game-Protocols ? Pin
Rüpel21-Jan-03 0:13
Rüpel21-Jan-03 0:13 
AnswerRe: Other Game-Protocols ? Pin
KNaLL12-Feb-03 11:12
KNaLL12-Feb-03 11:12 
GeneralTimeout in C++ Pin
User 66583-Jan-03 12:11
User 66583-Jan-03 12:11 
GeneralRe: Timeout in C++ Pin
Rüpel3-Jan-03 21:23
Rüpel3-Jan-03 21:23 
GeneralRe: Timeout in C++ Pin
User 66584-Jan-03 3:07
User 66584-Jan-03 3:07 
GeneralRe: Timeout in C++ Pin
Rüpel8-Jan-03 22:56
Rüpel8-Jan-03 22:56 
QuestionSlow search...Any tips? Pin
Gegzy10-Dec-02 7:56
Gegzy10-Dec-02 7:56 
AnswerRe: Slow search...Any tips? Pin
Rüpel10-Dec-02 9:54
Rüpel10-Dec-02 9:54 
QuestionASP or Webservice? Pin
IanCaz15-Nov-02 9:43
IanCaz15-Nov-02 9:43 
AnswerRe: ASP or Webservice? Pin
Rüpel17-Nov-02 20:08
Rüpel17-Nov-02 20:08 
GeneralRe: ASP or Webservice? Pin
Anonymous10-Jul-03 13:51
Anonymous10-Jul-03 13:51 
GeneralJust a little compliment ;) Pin
ThK31-Oct-02 8:36
ThK31-Oct-02 8:36 
GeneralRe: Just a little compliment ;) Pin
Rüpel31-Oct-02 9:54
Rüpel31-Oct-02 9:54 
GeneralRe: Just a little compliment ;) Pin
ThK31-Oct-02 10:11
ThK31-Oct-02 10:11 
GeneralAwesome improvements! Pin
Matt Philmon17-Oct-02 5:34
Matt Philmon17-Oct-02 5:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.