1,585
edits
DrinkyBird (talk | contribs) (...missing parameter) Tag: Source edit |
DrinkyBird (talk | contribs) (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 === | |||
{| 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: | ||
|} | |} | ||
=== Segmented challenge === | |||
{{Devfeature|3.2|alpha}} | |||
For | 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: | |||
{| 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. | |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 | |||
|} | |} | ||
==== 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 | ||
|- | |- | ||
| | |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" | |||
! 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 || | |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" | ||
! 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: | ||
|} | |} | ||
== 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 | | '''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. | ||
|} | |} | ||
== 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]] |