Jump to content

Launcher protocol: Difference between revisions

Document the rework segmented protocol
(Correct the flag value for SQF2_VOICECHAT)
Tag: Source edit
(Document the rework segmented protocol)
Tag: Source edit
Line 189: Line 189:
|}
|}


=== Segmented challenge ===
=== Challenge packet ===
{{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
|}
 
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:
Send the server the following packet:


Line 232: Line 195:
! Type||Value||Description
! Type||Value||Description
|-
|-
|Long || 199|| Launcher challenge
|Long || 199 || Launcher challenge
|-
|-
|Long || Flags || Desired information
|Long || Flags || Desired information
Line 240: Line 203:
|Long || Flags2 || Optional. The extended information you want. Ensure you specify <tt>SQF_EXTENDED_INFO</tt> in the normal Flags field as well.
|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.
|Byte || Segmented || Optional. Set this to '''2''' to indicate that you want a segmented response.
|}
|}


Line 261: Line 224:
|-
|-
! <tt>SERVER_LAUNCHER_SEGMENTED_CHALLENGE</tt>
! <tt>SERVER_LAUNCHER_SEGMENTED_CHALLENGE</tt>
| 5660031
| 5660032
| Accepted; segmented information follows
| Accepted; segmented information follows
|}
|}
Line 273: Line 236:
|Byte  || Segment  || Segment number. If the most significant bit is set, then this is the last segment.
|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.
|Byte || NumSegments     || Total number of segments that will be sent.
|}
|-
 
|Short  || Size    || The size of this.
After this, segment 0 also includes:
{| class="wikitable"
! Type||Value||Description
|-
|-
|Long  || Time     || The time you sent to the server.
|Short  || Offset     || Offset into the full packet this packet contains.
|-
|-
|String || Version  || Server version string with revision number.
|Short  || TotalSize    || Total size of the data to be sent.
|}
|}


Each field set returned then contains the following information before the actual data.
The segmented responses contain a full launcher packet split up into multiple segments. To use them, create a buffer of TotalSize bytes, then copy the data from each segment at the given offset.


{| class="wikitable"
=== Packet contents ===
! Type||Value||Description
The full packet contains the following header:
|-
|Byte  || Fieldset || The field set this packet contains data for (0 -> <tt>SQF_</tt>, 1 -> <tt>SQF2_</tt>)
|-
|Long  || Flags    || The fields contained in this packet.
|}
 
==== 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"
Line 309: Line 261:


== Field data ==
== 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 535: Line 483:
|}
|}


=== Flag set 1 (<tt>SQF2_</tt>) ===
=== Extended flags ===


{| class="wikitable"
{| class="wikitable"
Line 608: Line 556:
! Type || Value || Description
! Type || Value || Description
|-
|-
| Long || 200 || The launcher challenge.
| Long || 199 || The launcher challenge.
|-
|-
| Long || 1234 || Ping value.
| 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 || 36700161 || The flags 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>
| Long || 6 || The extended flags we want: <tt><nowiki>SQF2_COUNTRY|SQF2_GAMEMODE_NAME</nowiki></tt>
|-
| Byte || 2 || Indicates we want a segmented response, if the server supports it.
|}
|}


'''Response'''
'''Response'''
{| class="wikitable"
{| class="wikitable"
! Type || Value || Description
! Type || Value || Description
|-
|-
| Long || 5660031|| The server's response, indicating we're getting a segmented response packet.
| Long || 5660032 || The server's response, indicating we're getting a segmented response packet.
|-
| Byte || 0 || The segment number.
|-
| Byte || 1 || Total number of segments.
|-
|-
| 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 || 0 || Offset of the packet.
|-
|-
| Short || 143 || Size of this packet in bytes.
| Short || 747 || Size of this packet
|-
|-
| Long || 1234 || The ping value we sent before, sent only in segment 0.
| Short || 747 || Total amount of data being sent.
|-
|-
| String || <tt>3.2-alpha-r230430-1741 on Linux 5.15.0-69-generic</tt> || The server version, sent only in segment 0.
| colspan = 3 | Packet data follows after this point.
|-  
| Long || 1234 || The ping value we sent to the server.
|-
|-
| Byte || 0 || The initial flag set being sent.
| String || 3.2-alpha-r240718-2351 on Linux 6.9.9-1-default || The server's version.
|-
|-
| 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:
| Long || 35127297 || The fields contained in the response. <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_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_EXTENDED_INFO</tt> was added indicating we'll get data from another flag set later in this segment.
Line 671: Line 626:


=== Example code ===
=== Example code ===
This uses a loop to handle switching flag sets in the middle of a segment.


<syntaxhighlight lang="javascript" line="1">
<syntaxhighlight lang="javascript" line="1">
function handleFields() {
function handlePacket(data) {
     while (readOffset < packetLength) {
     const response = data.readLong();
         const fieldsetNum = readByte();
    switch (response) {
        const flags = readLong();
         case SERVER_LAUNCHER_CHALLENGE: {
       
            parseResponse(data);
        if (fieldsetNum == 0) {
 
             if (flags & SQF_NAME) {
            break;
                 const name = readString();
        }
 
        case SERVER_LAUNCHER_SEGMENTED_CHALLENGE: {
            const segmentNumber = data.readByte();
            const totalSegments = data.readByte();
            const offset = data.readShort();
            const size = data.readShort();
            const totalSize = data.readShort();
 
             if (buffer == null) {
                 buffer = newBuffer(totalSize);
             }
             }
              
 
             if (flags & SQF_URL) {
             copyBuffer(from: data, to: buffer, toOffset: offset);
                 const url = readString();
 
             if (segmentNumber == totalSegments) {
                 parseResponse(buffer);
             }
             }
           
 
             // ...
             break;
         }
         }
       
 
         else if (fieldsetNum == 1) {
         case SERVER_LAUNCHER_BANNED: {
             if (flags & SQF2_PWAD_HASHES) {
             // handle error
                const numHashes = readByte();
 
                for (let i = 0; i < numHashes; i++) {
            break;
                    const hash = readString();
                }
            }
           
            // ...
         }
         }
       
 
         if (!(flags & SQF_EXTENDED_INFO)) {
         case SERVER_LAUNCHER_IGNORING: {
            // handle error
 
             break;
             break;
         }
         }
     }
     }
}
}
</syntaxhighlight>


A variation that can be used with the old single-packet protocol:
function parseResponse(data) {
    const flags = data.readLong();


<syntaxhighlight lang="javascript" line="1">
     if (flags & SQF_NAME) {
function handleFields() {
         const name = data.readString();
     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>
</syntaxhighlight>


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