Launcher protocol: Difference between revisions

Correct the flag value for SQF2_VOICECHAT
(Adjust the wording of the country codes section)
(Correct the flag value for SQF2_VOICECHAT)
Tag: Source edit
(9 intermediate revisions by 3 users not shown)
Line 7: 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 [https://wiki.zandronum.com/files/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 100: 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 175: Line 174:
|}
|}


=== Extended flags ===
==== Extended flags ====
{| class="wikitable"
{| class="wikitable"
! Name || Value || Description
! Name || Value || Description
Line 182: Line 181:
|-
|-
| <tt>SQF2_COUNTRY</tt> || <tt>0x00000002</tt> || The server's ISO 3166-1 alpha-3 country code.
| <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.
|}
|}


<hr>
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
Line 199: Line 238:
|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 || The extended information you want. Ensure you specify <tt>SQF_EXTENDED_INFO</tt> in the normal Flags field as well. This field is optional - if this field is not present, its value is inferred to be 0. Older servers will ignore this field.
|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 the following:
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
|}
==== Segmented protocol response ====
If you asked for a segmented response, and the server supports it, you'll get:
{| class="wikitable"
! Type||Value||Description
|-
|Byte  || Segment  || Segment number. If the most significant bit is set, then this is the last segment.
|-
|Short  || Size    || The uncompressed size of this packet.
|}
After this, segment 0 also includes:
{| class="wikitable"
{| class="wikitable"
! Type||Value||Description
! Type||Value||Description
|-
|-
| Long
|Long   || Time    || The time you sent to the server.
| Response
|-
|
|String || Version  || Server version string with revision number.
* 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
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 227: 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 351: 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 426: 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 435: 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
| Long
| <tt>SQF_EXTENDED_INFO</tt>
| <tt>SQF_EXTENDED_INFO</tt>
| The flags specifying extended server information you will receive. Check all <tt>SQF2</tt> values against this field.
| '''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.
|}
 
=== Flag set 1 (<tt>SQF2_</tt>) ===
 
{| class="wikitable"
! Type||Flag||Description
|-
|-
| Byte
| Byte
Line 451: Line 551:
| <tt>SQF2_COUNTRY</tt>
| <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.
| 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]].
|}
|}
'''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.
And that's it!


== Notes ==
== Notes ==
Line 488: Line 596:


Also see {{Issue|3894|Allow servers to present their country to launchers}}.
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.
<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]]