Launcher protocol: Difference between revisions

Updated with segmented protocol changes
(...missing parameter)
Tag: Source edit
(Updated with segmented protocol changes)
Tag: Source edit
Line 100: Line 100:
== 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 176:
|}
|}


=== Extended flags ===
==== Extended flags ====
{| class="wikitable"
{| class="wikitable"
! Name || Value || Description
! Name || Value || Description
Line 188: Line 189:
|}
|}


<hr>
=== Segmented challenge ===
{{Devfeature|3.2|alpha}}


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.
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.


Now send the server the following packet:
{| 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
|}
 
If you were denied, this response is followed by another Long, which is the time you sent to the server.
 
=== 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 203: 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 a Long, which will be one of the following:
 
{| class="wikitable"
! Symbol || Value || Description
|-
! <tt>SERVER_LAUNCHER_CHALLENGE</tt>
| 5660031
| 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 231: 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 355: 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 439: 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 464: Line 560:
| {{Devfeature|3.2|alpha}} The short name of the server's current game mode, as defined in the [[GAMEMODE]] lump.
| {{Devfeature|3.2|alpha}} The short name of the server's current game mode, as defined in the [[GAMEMODE]] lump.
|}
|}
'''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 500: Line 592:


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]]