Launcher protocol: Difference between revisions

Correct the flag value for SQF2_VOICECHAT
(Update for https://bitbucket.org/Torr_Samaho/zandronum/commits/4c133967a65a)
(Correct the flag value for SQF2_VOICECHAT)
Tag: Source edit
(18 intermediate revisions by 3 users not shown)
Line 2: Line 2:


== Protocol information ==
== Protocol information ==
Version: '''0.58''' (October 28, 2012), compatible with the developmental version, 1.1.
See "article history" to look up the protocol for prior versions.
See "article history" to look up the protocol for prior versions.


Line 10: Line 7:


All Zandronum servers use '''UDP''' as their network protocol.
All Zandronum servers use '''UDP''' as their network protocol.
Additionally, all traffic is compressed using the Huffman algorithm to save bandwidth. Therefore, you'll need [http://www.skulltag.com/download/huffman.zip a copy of huffman.cpp or huffman.java] to encode and decode your traffic appropriately.
Additionally, all traffic is compressed using the Huffman algorithm to save bandwidth. Refer to code in [https://osdn.net/projects/zandronum/scm/hg/zandronum-stable/tree/tip/src/huffman/ src/huffman] for a standalone implementation of the Huffman encoding needed to encode/decode traffic.
 
There is also an implementation of the Huffman codec written in Python [https://bitbucket.org/alexmax2742/pyskull/src/fd5bf1ec8987ccb28a0861db965a087d561c0997/pyskull/huffman.py?at=default here]. Also in Python 3 [https://bitbucket.org/crimsondusk/pyskull/src/28e9f5cdd3d855d799b62fe9dc0b8f0bea7eee48/pyskull/huffman.py?at=default&fileviewer=file-view-default here].


Definition of data types used in this article:
Definition of data types used in this article:
Line 103: Line 98:
== Querying individual servers ==
== Querying individual servers ==


You need to choose what data you'd like. To do so, bitshift the combination of flags with a boolean OR.
You need to choose what data you'd like. To do so, bitshift the combination of flags with a boolean OR. For example, to get the server's name and player count, you'd use <tt><nowiki>(SQF_NAME|SQF_NUMPLAYERS)</nowiki></tt>. Use these flags appropriately; doing so saves your users' and server hosters' bandwidth.
 
=== Query flags ===


The flags are:
{| class="wikitable"
{| class="wikitable"
! Name || Value || Description
! Name || Value || Description
Line 173: Line 169:
| <tt>SQF_OPTIONAL_WADS</tt> || <tt>0x20000000</tt> || Which PWADs are optional
| <tt>SQF_OPTIONAL_WADS</tt> || <tt>0x20000000</tt> || Which PWADs are optional
|-
|-
| <tt>SQF_DEH</tt> || <tt>0x40000000</tt> || {{devfeature|3.0|151228-1140}} List of DEHACKED patches loaded by the server.
| <tt>SQF_DEH</tt> || <tt>0x40000000</tt> || List of DEHACKED patches loaded by the server.
|-
| <tt>SQF_EXTENDED_INFO</tt> || <tt>0x80000000</tt> || Additional server information, see the table below for more information.
|}
 
==== Extended flags ====
{| class="wikitable"
! Name || Value || Description
|-
| <tt>SQF2_PWAD_HASHES</tt> || <tt>0x00000001</tt> || The MD5 hashes of the server's loaded PWADs.
|-
| <tt>SQF2_COUNTRY</tt> || <tt>0x00000002</tt> || The server's ISO 3166-1 alpha-3 country code.
|-
| <tt>SQF2_GAMEMODE_NAME</tt> || <tt>0x00000004</tt> || {{Devfeature|3.2|alpha}} The name of the server's current game mode.
|-
| <tt>SQF2_GAMEMODE_SHORTNAME</tt> || <tt>0x00000008</tt> || {{Devfeature|3.2|alpha}} The short name of the server's current game mode.
|-
| <tt>SQF2_VOICECHAT</tt> || <tt>0x00000010</tt> || {{Devfeature|3.2|alpha}} The server's voice chat setting.
|}
 
=== Segmented challenge ===
{{Devfeature|3.2|alpha}}
 
This challenge is only available on servers running Zandronum 3.2 or newer. For backwards compatibility, the old [[#Old protocol]] is still supported and can also request a segmented response, but is now deprecated. It is recommended that launchers prefer this new challenge when 3.2 reaches widespread adoption and fall back to the old challenge.
 
{| class="wikitable"
! Type||Value||Description
|-
|Long || 200 || Launcher challenge
|-
|Long || Flags || Desired information
|-
|Long || Time || Current time, this will be sent back to you so you can determine ping.
|-
|Long || Flags2 || The extended information you want. Ensure you specify <tt>SQF_EXTENDED_INFO</tt> in the normal Flags field as well.
|}
 
The server will respond with a Long, which will be one of the following:
 
{| class="wikitable"
! Symbol || Value || Description
|-
! <tt>SERVER_LAUNCHER_IGNORING</tt>
| 5660024
| Denied; your IP has made a request in the past [[Server_Variables#sv_queryignoretime|sv_queryignoretime]] seconds
|-
! <tt>SERVER_LAUNCHER_BANNED</tt>
| 5660025
| Denied; your IP is banned
|-
! <tt>SERVER_LAUNCHER_SEGMENTED_CHALLENGE</tt>
| 5660031
| Accepted; segmented information follows
|}
|}


For example, to get the server's name and player count, you'd use <tt>(SQF_NAME|SQF_NUMPLAYERS)</tt>. Use these flags appropriately; doing so saves your users' and server hosters' bandwidth.
If you were denied, this response is followed by another Long, which is the time you sent to the server.


Now send the server the following packet:
=== Old protocol challenge ===
Send the server the following packet:


{| class="wikitable"
{| class="wikitable"
! Type||Value||Description
! Type||Value||Description
|-
|-
|Long || 199 || Launcher challenge
|Long || 199|| Launcher challenge
|-
|-
|Long || Flags || Desired information
|Long || Flags || Desired information
|-
|-
|Long || Time || Current time, this will be sent back to you so you can determine ping.
|Long || Time || Current time, this will be sent back to you so you can determine ping.
|-
|Long || Flags2 || Optional. The extended information you want. Ensure you specify <tt>SQF_EXTENDED_INFO</tt> in the normal Flags field as well.
|-
|Byte || Segmented || Optional. Set this to 1 to indicate that you want a segmented response. This exists for backwards compatibility with older servers.
|}
The server will respond with a Long, which will be one of the following:
{| class="wikitable"
! Symbol || Value || Description
|-
! <tt>SERVER_LAUNCHER_CHALLENGE</tt>
| 5660023
| Accepted; server information follows
|-
! <tt>SERVER_LAUNCHER_IGNORING</tt>
| 5660024
| Denied; your IP has made a request in the past [[Server_Variables#sv_queryignoretime|sv_queryignoretime]] seconds
|-
! <tt>SERVER_LAUNCHER_BANNED</tt>
| 5660025
| Denied; your IP is banned
|-
! <tt>SERVER_LAUNCHER_SEGMENTED_CHALLENGE</tt>
| 5660031
| Accepted; segmented information follows
|}
|}


The server will respond with the following:
==== Segmented protocol response ====
If you asked for a segmented response, and the server supports it, you'll get:


{| class="wikitable"
{| class="wikitable"
! Type||Value||Description
! Type||Value||Description
|-
|-
| Long
|Byte  || Segment  || Segment number. If the most significant bit is set, then this is the last segment.
| Response
|-
|
|Short  || Size    || The uncompressed size of this packet.
* 5660023: Accepted; server information follows
|}
* 5660024: Denied; your IP has made a request in the past [[Server_Variables#sv_queryignoretime|sv_queryignoretime]] seconds
 
* 5660025: Denied; your IP is banned
After this, segment 0 also includes:
{| class="wikitable"
! Type||Value||Description
|-
|Long  || Time    || The time you sent to the server.
|-
|String || Version  || Server version string with revision number.
|}
 
Each field set returned then contains the following information before the actual data.
 
{| class="wikitable"
! Type||Value||Description
|-
|Byte  || Fieldset || The field set this packet contains data for (0 -> <tt>SQF_</tt>, 1 -> <tt>SQF2_</tt>)
|-
|-
|Long ||Time || The time you sent to the server.
|Long   || Flags    || The fields contained in this packet.
|}
|}


Next, assuming you were accepted, you'll get:
==== Single packet response ====
If you didn't ask for a segmented response, or the server doesn't support segmented packets, you'll get:


{| class="wikitable"
{| class="wikitable"
! Type||Value||Description
! Type||Value||Description
|-
|Long  || Time    || The time you sent to the server.
|-
|-
|String || Version || Server version string with revision number.
|String || Version || Server version string with revision number.
Line 215: Line 308:
|}
|}


Followed by:
== Field data ==
 
Field data is sent in the order the fields appear in the table below. The server will automatically correct your request if you made a mistake (like request team scores in cooperative games), so always use the flags it sends back to you when reading your data.
 
=== Flag set 0 (<tt>SQF_</tt>) ===


{| class="wikitable"
{| class="wikitable"
Line 339: Line 436:
| <tt>SQF_NUMPLAYERS</tt>
| <tt>SQF_NUMPLAYERS</tt>
| The number of players in the server
| The number of players in the server
|-
| Byte
| <tt>SQF_PLAYERDATA</tt>
| '''Segmented only:''' Whether the team field for each player is sent. Unlike all other fields for <tt>SQF_PLAYERDATA</tt>, this is only sent once, and '''not''' for each player.
|-
|-
| String
| String
Line 414: Line 515:
| Byte
| Byte
| <tt>SQF_OPTIONAL_WADS</tt>
| <tt>SQF_OPTIONAL_WADS</tt>
| Index number int the list sent with SQF_PWADS - this wad is optional (sent for each optional Wad)
| Index number into the list sent with SQF_PWADS - this wad is optional (sent for each optional Wad)
|-
|-
| Byte
| Byte
Line 423: Line 524:
| <tt>SQF_DEH</tt>
| <tt>SQF_DEH</tt>
| Deh patch name (one string for each deh patch)
| Deh patch name (one string for each deh patch)
|-
| Byte
| <tt>SQF_EXTENDED_INFO</tt>
| '''Segmented protocol:''' The next flag set you will receive.
|-
| Long
| <tt>SQF_EXTENDED_INFO</tt>
| '''Segmented protocol:''' The flags specifying the information from the next flag set that immediately follows.
'''Old protocol:''' The flags specifying the <tt>SQF2_</tt>data that immediately follows.
|}
|}


'''Note:''' The server will automatically correct your request if you made a mistake (like request team scores in cooperative games), so always use the flags it sends back to you when reading your data.
=== Flag set 1 (<tt>SQF2_</tt>) ===


And that's it!
{| class="wikitable"
! Type||Flag||Description
|-
| Byte
| <tt>SQF2_PWAD_HASHES</tt>
| The number of hashes sent.
|-
| String
| <tt>SQF2_PWAD_HASHES</tt>
| The hash of the PWAD, sent for each PWAD. The indices are the same as sent in <tt>SQF_PWADS</tt>
|-
| Byte[3]
| <tt>SQF2_COUNTRY</tt>
| The server's ISO 3166-1 alpha-3 country code. This is sent as a raw char array of 3 length, there is no null terminator. Zandronum also has two special values for this field, <tt>XIP</tt> and <tt>XUN</tt>. See [[#Country codes]] below for how to handle this field.
|-
| String
| <tt>SQF2_GAMEMODE_NAME</tt>
| {{Devfeature|3.2|alpha}} The name of the server's current game mode, as defined in the [[GAMEMODE]] lump.
|-
| String
| <tt>SQF2_GAMEMODE_SHORTNAME</tt>
| {{Devfeature|3.2|alpha}} The short name of the server's current game mode, as defined in the [[GAMEMODE]] lump.
|-
| Byte
| <tt>SQF2_VOICECHAT</tt>
| {{Devfeature|3.2|alpha}} The server's voice chat setting which is based on [[Server_Variables#sv_allowvoicechat|sv_allowvoicechat]].
|}


== Notes ==
== Notes ==


'''Game modes:''' Game modes are defined as:
=== Game modes ===
 
Game modes are defined as:
     0  GAMEMODE_COOPERATIVE
     0  GAMEMODE_COOPERATIVE
     1  GAMEMODE_SURVIVAL
     1  GAMEMODE_SURVIVAL
Line 449: Line 587:
     15  GAMEMODE_DOMINATION
     15  GAMEMODE_DOMINATION


'''To discuss the protocol, ''' use [http://zandronum.com/forum/index.php this forum and create a new topic].
=== Country codes ===
 
Zandronum 3.1 introduces the ability to server hosts to specify the country their server is located in via a new CVAR, <tt>sv_country</tt>. This was introduced to allow hosts to combat the inaccuracies of IP geolocation that launchers rely on. This value is specified to launchers via the new <tt>SQF2_COUNTRY</tt> field, which can have the following values:
 
* '''an ISO 3166-1 alpha-3 country code.''' Note that Zandronum doesn't validate whether the country code supplied in <tt>sv_country</tt> is a registered country code. If the launcher does not recognise the given code, then it should display the server's country as unknown.
* '''the value <tt>XIP</tt>''', which suggests to the launcher that it should attempt IP geolocation to determine the server's country. The default value of <tt>sv_country</tt> will cause it to return this by default, to preserve existing behaviour and make it easier for casual hosts. If geolocation is unsupported, disabled, or fails, then the server's country should be displayed as unknown.
* '''the value <tt>XUN</tt>''', in which case the launcher should display the server's country as unknown.
 
Also see {{Issue|3894|Allow servers to present their country to launchers}}.
 
== Examples ==
 
=== Basic segmented example ===
 
In this example we query a simple co-operative server for some basic information.
 
'''Request'''
 
{| class="wikitable"
! Type || Value || Description
|-
| Long || 200 || The launcher challenge.
|-
| Long || 1234 || Ping value.
|-
| Long || 36700161 || The fields from set 0 we want: <tt><nowiki>SQF_NAME|SQF_PLAYERDATA|SQF_TEAMINFO_NUMBER|SQF_TESTING_SERVER</nowiki></tt>
|-
| Long || 6 || The fields from set 1 we want: <tt><nowiki>SQF2_COUNTRY|SQF2_GAMEMODE_NAME</nowiki></tt>
|}
 
'''Response'''
 
{| class="wikitable"
! Type || Value || Description
|-
| Long || 5660031|| The server's response, indicating we're getting a segmented response packet.
|-
| Byte || 177 || The segment number. The most significant bit is set, as this is the final and only packet. Clear the MSB to reveal this is packet 0: <tt>128 & ~0x80</tt> = 0.
|-
| Short || 143 || Size of this packet in bytes.
|-
| Long || 1234 || The ping value we sent before, sent only in segment 0.
|-
| String || <tt>3.2-alpha-r230430-1741 on Linux 5.15.0-69-generic</tt> || The server version, sent only in segment 0.
|-
| Byte || 0 || The initial flag set being sent.
|-
| Long || 35127297 || The initial flags from that set being sent: <tt><nowiki>SQF_NAME|SQF_NUMPLAYERS|SQF_PLAYERDATA|SQF_TESTING_SERVER|SQF_EXTENDED_INFO</nowiki></tt>. Observe how:
* <tt>SQF_NUMPLAYERS</tt> was added by the server as we requested <tt>SQF_PLAYERDATA</tt>.
* <tt>SQF_EXTENDED_INFO</tt> was added indicating we'll get data from another flag set later in this segment.
* <tt>SQF_TEAMINFO_NUMBER</tt> was removed as this is a co-operative server and team information is irrelevant.
|-
| String || <tt>Skipper Pavilion</tt> || From <tt>SQF_NAME</tt>. The server's name.
|-
| Byte || 1 || From <tt>SQF_NUMPLAYERS</tt>. The number of players online.
|-
| Byte|| 0 || From <tt>SQF_PLAYERDATA</tt>. Whether the team field will be sent for players.
|-
| String || <tt>Alphus</tt> || From <tt>SQF_PLAYERDATA</tt>. The name of the first player in the server.
|-
| Short || 0 || From <tt>SQF_PLAYERDATA</tt>. Alphus' score.
|-
| Short || 0 || From <tt>SQF_PLAYERDATA</tt>. Alphus' ping.
|-
| Byte || 0 || From <tt>SQF_PLAYERDATA</tt>. Whether Alphus is a spectator or not.
|-
| Byte || 1 || From <tt>SQF_PLAYERDATA</tt>. Whether Alphus is a bot or not.
|-
| Byte || 5 || From <tt>SQF_PLAYERDATA</tt>. How many minutes Alphus has been in the server.
|-
| Byte || 1 || From <tt>SQF_TESTING_SERVER</tt>. Whether the server is running a testing binary.
|-
| String || <tt>downloads/testing/3.2/ZandroDev3.2-230430-1741windows.zip</tt> || From <tt>SQF_TESTING_SERVER</tt>. Path to the testing binary on zandronum.com.
|-
| Byte || 1 || From <tt>SQF_EXTENDED_INFO</tt>. The flag set that follows.
|-
| Long || 6 || From <tt>SQF_EXTENDED_INFO</tt>. The fields that follow from that set: <tt><nowiki>SQF2_COUNTRY|SQF2_GAMEMODE_NAME</nowiki></tt>
|-
| char[3] || <tt>GBR</tt> || From <tt>SQF2_COUNTRY</tt>. The country code the server presents, as set by sv_country.
|-
| String|| <tt>Cooperative</tt> || From <tt>SQF2_GAMEMODE_NAME</tt>. The name of the game mode currently being played.
|}
 
=== Example code ===
This uses a loop to handle switching flag sets in the middle of a segment.


Happy coding!
<syntaxhighlight lang="javascript" line="1">
function handleFields() {
    while (readOffset < packetLength) {
        const fieldsetNum = readByte();
        const flags = readLong();
       
        if (fieldsetNum == 0) {
            if (flags & SQF_NAME) {
                const name = readString();
            }
           
            if (flags & SQF_URL) {
                const url = readString();
            }
           
            // ...
        }
       
        else if (fieldsetNum == 1) {
            if (flags & SQF2_PWAD_HASHES) {
                const numHashes = readByte();
                for (let i = 0; i < numHashes; i++) {
                    const hash = readString();
                }
            }
           
            // ...
        }
       
        if (!(flags & SQF_EXTENDED_INFO)) {
            break;
        }
    }
}
</syntaxhighlight>


A variation that can be used with the old single-packet protocol:


<syntaxhighlight lang="javascript" line="1">
function handleFields() {
    let fieldsetNum = 0;
    let flags = 0;
   
    while (readOffset < packetLength) {
        fieldsetNum = segmented ? readByte() : fieldsetNum;
        flags = readLong();
       
        if (fieldsetNum == 0) {
            // ...
        }
       
        else if (fieldsetNum == 1) {
            // ...
        }
       
        if (flags & SQF_EXTENDED_INFO) {
            fieldsetNum++;
        } else {
            break;
        }
    }
}
</syntaxhighlight>


[[Category:Developers_Articles]]
[[Category:Developers_Articles]]