UT3 query protocol
The query port (on the server) is 6500 by default, contrary to the UT2003/4 situation where it was one port above the gameport. When multiple servers are run simultaneously, UT3 will use the next available port when it notices 6500(in steps of 100, i.e. 6600 would be the next runner up) is in use. In version 1.01 the query port can be defined in the config file.
The protocol is Gamespy Query Protocol version 4, which uses UDP to send datagrams. This is quite different from the UT2003/2004 query protocols, which were proprietary.
This is the basic schema:
- Client sends request to Server
- Server replies with a unique number
- Client sends new request to Server, including said number
- Server replies with query information
These are the specifications of the packets:
Packet 1: Initial request
FE FD 09 XX XX XX XX
The first 2 bytes (FE FD) are protocol identifier. In this case, they represent the Gamespy Query Protocol version 4.
Byte 3 (09) indicate that the client wishes to receive the challenge string.
Bytes 4 to 7 (XX XX XX XX) are a binary-packed timestamp, in seconds. According to the available data on Gamespy protocol, this can be any value, and is only used as an identifier to distinguish between multiple packets from the same server. Apparently UT3 uses time to generate these timestamps. Since time_t is a 4-byte signed integer data type, you should use such datatype here.
Packet 2: First response
09 XX XX XX XX YY YY YY YY YY YY YY YY YY YY YY YY
The first byte (09) indicates this is a reply to the initial request.
Bytes 2 to 6 (XX XX XX XX) are the sequence number that was in the initial request.
Bytes 7 to 18 (YY YY YY YY YY YY YY YY YY YY YY YY) is an ASCII represented number. This number can start with a minus sign, and is null-padded at the end. You'll need to convert it to integer data type to use it later.
Packet 3: Second request
FE FD 00 WW WW WW WW ZZ ZZ ZZ ZZ FF FF FF 01
The first two bytes (FE FD) again indicate the query protocol.
Byte 3 (00) indicates that this is a request for server information.
Bytes 4 to 7 (WW WW WW WW) is a timestamp again.
Bytes 8 to 11 (ZZ ZZ ZZ ZZ) is binary packed number received from Packet 2 (signed 4-byte big-endian integer).
Bytes 12 to 15 are always (FF FF FF 01).
Or, in code (source):
// $challenge was the number received from Packet 2, // We assume the timestamp is 10 20 30 40: $query = sprintf( "\xFE\xFD\x00\x10\x20\x30\x40%c%c%c%c\xFF\xFF\xFF\x01", ( $challenge >> 24 ), ( $challenge >> 16 ), ( $challenge >> 8 ), ( $challenge >> 0 ) ); // Now you can send $query and receive the server details.
Packet 4: Server information response
00 WW WW WW WW *DATA*
Byte 1 (00) indicates that this is a reply for a server information request.
Bytes 2 to 5 (WW WW WW WW) are the new timestamp, sent in Packet 3.
Bytes 6 to 14 (SPLITNUM\x00) are the splitnum. This is random, and conventionally set to the string splitnum, null terminated.
Byte 15 is the 'numPackets' byte. The high bit is whether or not this is the last packet. The low 7 bits are the packet index (starting at zero). So if you have one and only one packet, this byte would be \x80.
Byte 16 is another 'packet number' byte, but isn't understood.
What follows is server information, in the following format:
This is taken from a Multiplay server, the first 16 bytes of the response packet are stripped, these included all the bytes mentioned above. This data is then exploded, meaning that the \0 character is used to separate different values. These values can be combined as "value n" => "value n+1", resulting in these values (updated for UT3 final release with 1st patch):
|hostname||This is currently the OwningPlayerName. This is clearly a bug, it should either be the server's IP address or the hostname (though IP address would be better)|
|hostport||8277||The server port|
|numplayers||4||Current number of players|
|maxplayers||24||Maximum number of players|
|gamemode||openplaying||Can be 'openplaying' or 'closedplaying', depends on the "bAllowJoinInProgress" setting (?)|
|mapname||<comma seperated list of ALL these variables and their values>||Seems like this is a bug. It definitely isn't a map name.|
|OwningPlayerId||113350319||"Owning" Player ID|
|NumPublicConnections||24||Maximum number of players|
|bUsesStats||True||Stats enabled (True/False)|
|OwningPlayerName||Multiplay#83||Should be linked to the OwningPlayerId above|
|AverageSkillRating||1000.000000||Is used for the feature where players join a server of similar skill level|
|EngineVersion||3543||Server version (available as of Patch 2)|
|MinNetVersion||3467||Minimum client version required (available as of Patch 2)|
|s32779||3||GameMode (1=DM, 2=WAR, 3=VCTF, 4=TDM, 5=DUEL)|
|s0||2||The bot skill level, 1 for Novice, incrementing for Average, Experienced, Skilled, Adept, Masterful, Inhuman, Godlike|
|s1||0||? (always 0)|
|s6||1||Pure server (1) or 'tainted' with mutators (0)|
|s7||0||Private server (requires password to join) (1) or public server (0)|
|s8||0||"Vs Bots" with the options "Disabled" (0), "" (1), "1:1" (2), "3:2"(3), "2:1" (4)|
|s9||0||? (always 0)|
|s10||0||Forced Respawn (1) or not (0)|
|s11||0||? (always 0)|
|s12||0||? (varies between 0/1)|
|s13||0||? (varies between 0/1)|
|s14||0||? (varies between 0/1)|
|p1073741826||UTGame.UTDeathmatch||Gametype (can also be "UTGameContent.UTVehicleCTFGame_Content" or "UTGame.UTTeamGame" or "UTGameContent.UTOnslaughtGame_Content" or "UTGameContent.UTCTFGame_Content")|
|p1073741827||Deathmatch (24 Player)||Server name in browser|
|p1073741828||BattleRPG Server Advertisements (Progressive)||List of Custom Mutators (available as of Patch 1) [seperated by 0x1c (end of file)]|
|p268435705||15||Time limit (minutes)|
|p268435703||5||Number of bots|
|p268435717||0||Stock Mutator bit string (See below)|
Stock Mutator bit string
p268435717 returns an int32 value:
UTGame.UTMutator_BigHead, = 1
UTGame.UTMutator_FriendlyFire, = 2
UTGame.UTMutator_Handicap, = 4
UTGame.UTMutator_Instagib, = 8
UTGame.UTMutator_LowGrav, = 16
UTGame.UTMutator_NoPowerups, = 64
UTGame.UTMutator_NoTranslocator, = 128
UTGame.UTMutator_Slomo, = 256
UTGame.UTMutator_SpeedFreak, = 1024
UTGame.UTMutator_SuperBerserk, = 2048
UTGame.UTMutator_WeaponReplacement, = 8192
UTGame.UTMutator_WeaponsRespawn, = 16384
Using multiple mutators together results in the sum being returned:
UTGame.UTMutator_BigHead,UTGame.UTMutator_LowGrav, = 17