I'm using UdpClient to query game servers about server name, map, number of players, etc.
I've followed the guidelines on this page (A2S_INFO)
http://developer.valvesoftware.com/wiki/Server_queries#Source_servers
and I'm getting a correct reply:
alt text http://data.fuskbugg.se/skalman01/reply.JPG
I have no idea how I would go about to get each chunk of information (server name, map and the like).
Any help? I'm assuming one would have to look at the reply format specified in the wiki I linked, but I don't know what to make of it.
The reply format gives you the order and type of fields in the reply packet, basically like a struct. You can use a class like BinaryReader
to read groups of bytes in the packet and interpret them as the appropriate data types.
How are you getting the response?
- If it's in a stream already, you're set.
- If it's in a byte array, wrap it in a
MemoryStream
first. I think UdpClient
does it this way.
Then, construct a BinaryReader
using the stream. Remember, both the stream and the reader need to be Disposed
of.
BinaryReader reader = new BinaryReader(stream);
You can now call the reader's methods like ReadByte
, ReadInt32
etc. to read each field in turn from the response, using methods corresponding to the fields' types. The stream updates its internal offset as it's read, so you automatically read successive fields from the right place in the response buffer. BinaryReader
already has methods appropriate to the five non-string types used in the Steam packets:
byte: ReadByte
short: ReadInt16
long: ReadInt32
float: ReadSingle
long long: ReadUInt64
(yes, there's a U in there; the Valve page says these are unsigned)
string
is a bit trickier, because BinaryReader
doesn't already have methods to read strings in the format specified by Valve (null-terminated UTF-8), so you'll have to do it yourself, byte by byte. To make it look as much like any other BinaryReader
method as possible, you could write an extension method (untested code; this is the gist of it):
public static string ReadSteamString(this BinaryReader reader)
{
// To hold the list of bytes making up the string
List<byte> str = new List<byte>();
byte nextByte = reader.ReadByte();
// Read up to and including the null terminator...
while (nextByte != 0)
{
// ...but don't include it in the string
str.Add(nextByte);
nextByte = reader.ReadByte();
}
// Interpret the result as a UTF-8 sequence
return Encoding.UTF8.GetString(str.ToArray());
}
Some example usage, with the response packet you gave:
// Returns -1, corresponding to FF FF FF FF
int header = reader.ReadInt32();
// Returns 0x49, for packet type
byte packetType = reader.ReadByte();
// Returns 15, for version
byte ver = reader.ReadByte();
// Returns "Lokalen TF2 #03 All maps | Vanilla"
string serverName = reader.ReadSteamString();
// Returns "cp_well"
string mapName = reader.ReadSteamString();
// etc.
You can use similar code for creating your request packets, using a BinaryWriter
instead of manually assembling individual byte values.