Launcher protocol: Difference between revisions
DrinkyBird (talk | contribs) (Remove remnants of old segmented protocol) Tag: Source edit |
DrinkyBird (talk | contribs) (Further corrections to segmented protocol) Tag: Source edit |
||
Line 206: | Line 206: | ||
|} | |} | ||
The server will respond with one or more | The server will respond with one or more datagrams (depending on if a segmented response was requested and supported by the server), each starting with a Long, which will be one of the following: | ||
{| class="wikitable" | {| class="wikitable" | ||
Line 229: | Line 229: | ||
==== Segmented protocol response ==== | ==== Segmented protocol response ==== | ||
If you asked for a segmented response, and the server supports it, you'll get: | If you asked for a segmented response, and the server supports it, you'll get in each datagram: | ||
{| class="wikitable" | {| class="wikitable" | ||
! Type||Value||Description | ! Type||Value||Description | ||
|- | |- | ||
|Byte || Segment || Segment number | |Byte || Segment || Segment number. | ||
|- | |- | ||
|Byte || NumSegments || Total number of segments that will be sent. | |Byte || NumSegments || Total number of segments that will be sent. | ||
Line 240: | Line 240: | ||
|Short || Offset || Offset into the full packet this datagram contains. | |Short || Offset || Offset into the full packet this datagram contains. | ||
|- | |- | ||
|Short || Size || The size of | |Short || Size || The size of the segment, not including this header. (The size of the datagram is this value plus 12 bytes.) | ||
|- | |- | ||
|Short || TotalSize || Total size of the | |Short || TotalSize || Total size of the assembled packet. | ||
|} | |} | ||
Following this header, 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. | |||
=== Packet contents === | === Packet contents === | ||
Line 582: | Line 582: | ||
| 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: | | 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 | * <tt>SQF_EXTENDED_INFO</tt> was added indicating we'll get data from the extended flags. | ||
* <tt>SQF_TEAMINFO_NUMBER</tt> was removed as this is a co-operative server and team information is irrelevant. | * <tt>SQF_TEAMINFO_NUMBER</tt> was removed as this is a co-operative server and team information is irrelevant. | ||
|- | |- | ||
Line 588: | Line 588: | ||
|- | |- | ||
| Byte || 1 || From <tt>SQF_NUMPLAYERS</tt>. The number of players online. | | Byte || 1 || From <tt>SQF_NUMPLAYERS</tt>. The number of players online. | ||
|- | |- | ||
| String || <tt>Alphus</tt> || From <tt>SQF_PLAYERDATA</tt>. The name of the first player in the server. | | String || <tt>Alphus</tt> || From <tt>SQF_PLAYERDATA</tt>. The name of the first player in the server. | ||
Line 607: | Line 605: | ||
| 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. | | 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. | ||
|- | |- | ||
| Long || 6 || From <tt>SQF_EXTENDED_INFO</tt>. The fields that follow from the extended flags: <tt><nowiki>SQF2_COUNTRY|SQF2_GAMEMODE_NAME</nowiki></tt> | |||
| Long || 6 || From <tt>SQF_EXTENDED_INFO</tt>. The fields that follow from | |||
|- | |- | ||
| char[3] || <tt>GBR</tt> || From <tt>SQF2_COUNTRY</tt>. The country code the server presents, as set by sv_country. | | char[3] || <tt>GBR</tt> || From <tt>SQF2_COUNTRY</tt>. The country code the server presents, as set by sv_country. |
Revision as of 13:35, 27 July 2024
This launcher protocol lets you to talk to servers and get information from them, allowing you to make your own custom programs like browsers, stat tools, and so on.
Protocol information
See "article history" to look up the protocol for prior versions.
The basics
All Zandronum servers use UDP as their network protocol. Additionally, all traffic is compressed using the Huffman algorithm to save bandwidth. Refer to code in src/huffman for a standalone implementation of the Huffman encoding needed to encode/decode traffic.
Definition of data types used in this article:
- Byte: 8 bit integer
- Short: 16 bit integer
- Long: 32 bit integer
- Float: Long representation of a float
- String: null-terminated series of Bytes
Traffic is encoded in little-endian.
Getting the list of servers
This is very easy, accomplished by sending a long and a short to the master server:
Type | Value | Description |
---|---|---|
Long | 5660028 | LAUNCHER_MASTER_CHALLENGE |
Short | 2 | MASTER_SERVER_VERSION |
If your request is denied, the server will respond with one of the following:
Type | Value | Description |
---|---|---|
Long | 3 | Denied; your IP is banned (MSC_IPISBANNED) |
Long | 4 | Denied; your IP has made a request in the past 3 seconds (MSC_REQUESTIGNORED) |
Long | 5 | Denied; you're using an older version of the master protocol (MSC_WRONGVERSION) |
If you are accepted, you will receive multiple packets, each starting with:
Type | Value | Description |
---|---|---|
Long | 6 | Beginning of list (MSC_BEGINSERVERLISTPART) |
Byte | 0-255 | Packet number (starting with 0) |
Byte | 8 | Beginning of server block (MSC_SERVERBLOCK) |
Then you will get this next block, which is repeated for every IP with servers on it.
Type | Value | Description |
---|---|---|
Byte | 0-255 | n = Number of servers with this IP address |
Byte | 0-255 | IP address octet |
Byte | 0-255 | IP address octet |
Byte | 0-255 | IP address octet |
Byte | 0-255 | IP address octet |
Short | 0-65535 | IP port (sent 'n' times) |
Once the packet is full or all servers are transferred, you will receive 0 as number of servers on the next IP.
Type | Value | Description |
---|---|---|
Byte | 0 | There are no ports for the next server, i.e. no more servers in this packet. |
Finally, the response should end with either
Type | Value | Description |
---|---|---|
Byte | 2 | End of list, you got the full server list now (MSC_ENDSERVERLIST) |
or
Type | Value | Description |
---|---|---|
Byte | 7 | End of current part of the list, you'll get more packets (MSC_ENDSERVERLISTPART) |
Querying individual servers
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 (SQF_NAME|SQF_NUMPLAYERS). Use these flags appropriately; doing so saves your users' and server hosters' bandwidth.
Query flags
Name | Value | Description |
---|---|---|
SQF_NAME | 0x00000001 | The name of the server |
SQF_URL | 0x00000002 | The associated website |
SQF_EMAIL | 0x00000004 | Contact address |
SQF_MAPNAME | 0x00000008 | Current map being played |
SQF_MAXCLIENTS | 0x00000010 | Maximum amount of clients who can connect to the server |
SQF_MAXPLAYERS | 0x00000020 | Maximum amount of players who can join the game (the rest must spectate) |
SQF_PWADS | 0x00000040 | PWADs loaded by the server |
SQF_GAMETYPE | 0x00000080 | Game type code |
SQF_GAMENAME | 0x00000100 | Game mode name |
SQF_IWAD | 0x00000200 | The IWAD used by the server |
SQF_FORCEPASSWORD | 0x00000400 | Whether or not the server enforces a password |
SQF_FORCEJOINPASSWORD | 0x00000800 | Whether or not the server enforces a join password |
SQF_GAMESKILL | 0x00001000 | The skill level on the server |
SQF_BOTSKILL | 0x00002000 | The skill level of any bots on the server |
SQF_DMFLAGS | 0x00004000 | (Deprecated) The values of dmflags, dmflags2 and compatflags. Use SQF_ALL_DMFLAGS instead. |
SQF_LIMITS | 0x00010000 | Timelimit, fraglimit, etc. |
SQF_TEAMDAMAGE | 0x00020000 | Team damage factor. |
SQF_TEAMSCORES | 0x00040000 | (Deprecated) The scores of the red and blue teams. Use SQF_TEAMINFO_* instead. |
SQF_NUMPLAYERS | 0x00080000 | Amount of players currently on the server. |
SQF_PLAYERDATA | 0x00100000 | Information of each player in the server. |
SQF_TEAMINFO_NUMBER | 0x00200000 | Amount of teams available. |
SQF_TEAMINFO_NAME | 0x00400000 | Names of teams. |
SQF_TEAMINFO_COLOR | 0x00800000 | RGB colors of teams. |
SQF_TEAMINFO_SCORE | 0x01000000 | Scores of teams. |
SQF_TESTING_SERVER | 0x02000000 | Whether or not the server is a testing server, also the name of the testing binary. |
SQF_DATA_MD5SUM | 0x04000000 | (Deprecated) Used to retrieve the MD5 checksum of skulltag_data.pk3, now obsolete and returns an empty string instead. |
SQF_ALL_DMFLAGS | 0x08000000 | Values of various dmflags used by the server. |
SQF_SECURITY_SETTINGS | 0x10000000 | Security setting values (for now only whether the server enforces the master banlist) |
SQF_OPTIONAL_WADS | 0x20000000 | Which PWADs are optional |
SQF_DEH | 0x40000000 | List of DEHACKED patches loaded by the server. |
SQF_EXTENDED_INFO | 0x80000000 | Additional server information, see the table below for more information. |
Extended flags
Name | Value | Description |
---|---|---|
SQF2_PWAD_HASHES | 0x00000001 | The MD5 hashes of the server's loaded PWADs. |
SQF2_COUNTRY | 0x00000002 | The server's ISO 3166-1 alpha-3 country code. |
SQF2_GAMEMODE_NAME | 0x00000004 | (development version 3.2-alpha and above only) The name of the server's current game mode. |
SQF2_GAMEMODE_SHORTNAME | 0x00000008 | (development version 3.2-alpha and above only) The short name of the server's current game mode. |
SQF2_VOICECHAT | 0x00000010 | (development version 3.2-alpha and above only) The server's voice chat setting. |
Challenge packet
Send the server the following packet:
Type | Value | Description |
---|---|---|
Long | 199 | Launcher challenge |
Long | Flags | Desired information |
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 SQF_EXTENDED_INFO in the normal Flags field as well. |
Byte | Segmented | Optional. Set this to 2 to indicate that you want a segmented response. |
The server will respond with one or more datagrams (depending on if a segmented response was requested and supported by the server), each starting with a Long, which will be one of the following:
Symbol | Value | Description |
---|---|---|
SERVER_LAUNCHER_CHALLENGE | 5660023 | Accepted; server information follows |
SERVER_LAUNCHER_IGNORING | 5660024 | Denied; your IP has made a request in the past sv_queryignoretime seconds |
SERVER_LAUNCHER_BANNED | 5660025 | Denied; your IP is banned |
SERVER_LAUNCHER_CHALLENGE_SEGMENTED | 5660032 | Accepted; segmented information follows |
Segmented protocol response
If you asked for a segmented response, and the server supports it, you'll get in each datagram:
Type | Value | Description |
---|---|---|
Byte | Segment | Segment number. |
Byte | NumSegments | Total number of segments that will be sent. |
Short | Offset | Offset into the full packet this datagram contains. |
Short | Size | The size of the segment, not including this header. (The size of the datagram is this value plus 12 bytes.) |
Short | TotalSize | Total size of the assembled packet. |
Following this header, 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.
Packet contents
The full packet contains the following header:
Type | Value | Description |
---|---|---|
Long | Time | The time you sent to the server. |
String | Version | Server version string with revision number. |
Long | Flags | The info you'll be receiving, possibly corrected (see below). |
Field data
Type | Flag | Description |
---|---|---|
String | SQF_NAME | The server's name (sv_hostname) |
String | SQF_URL | The server's WAD URL (sv_website) |
String | SQF_EMAIL | The server host's e-mail (sv_hostemail) |
String | SQF_MAPNAME | The current map's name |
Byte | SQF_MAXCLIENTS | The max number of clients (sv_maxclients) |
Byte | SQF_MAXPLAYERS | The max number of players (sv_maxplayers) |
Byte | SQF_PWADS | The number of PWADs loaded |
String | SQF_PWADS | The PWAD's name (Sent for each PWAD) |
Byte | SQF_GAMETYPE | The current game mode. See below. |
Byte | SQF_GAMETYPE | Instagib - true (1) / false (0) |
Byte | SQF_GAMETYPE | Buckshot - true (1) / false (0) |
String | SQF_GAMENAME | The base game's name ("DOOM", "DOOM II", "HERETIC", "HEXEN", "ERROR!") |
String | SQF_IWAD | The IWAD's name |
Byte | SQF_FORCEPASSWORD | Whether a password is required to join the server (sv_forcepassword) |
Byte | SQF_FORCEJOINPASSWORD | Whether a password is required to join the game (sv_forcejoinpassword) |
Byte | SQF_GAMESKILL | The game's difficulty (skill) |
Byte | SQF_BOTSKILL | The bot difficulty (botskill) |
Long | SQF_DMFLAGS | [Deprecated] Value of dmflags |
Long | SQF_DMFLAGS | [Deprecated] Value of dmflags2 |
Long | SQF_DMFLAGS | [Deprecated] Value of compatflags |
Short | SQF_LIMITS | Value of fraglimit |
Short | SQF_LIMITS | Value of timelimit |
Short | SQF_LIMITS | time left in minutes (only sent if timelimit > 0) |
Short | SQF_LIMITS | duellimit |
Short | SQF_LIMITS | pointlimit |
Short | SQF_LIMITS | winlimit |
Float | SQF_TEAMDAMAGE | The team damage scalar (teamdamage) |
Short | SQF_TEAMSCORES | [Deprecated] Blue team's fragcount/wincount/score |
Short | SQF_TEAMSCORES | [Deprecated] Red team's fragcount/wincount/score |
Byte | SQF_NUMPLAYERS | The number of players in the server |
String | SQF_PLAYERDATA | Player's name |
Short | SQF_PLAYERDATA | Player's pointcount/fragcount/killcount |
Short | SQF_PLAYERDATA | Player's ping |
Byte | SQF_PLAYERDATA | Player is spectating - true (1) / false (0) |
Byte | SQF_PLAYERDATA | Player is a bot - true (1) / false (0) |
Byte | SQF_PLAYERDATA | Player's team (returned on team games, 255 is no team) |
Byte | SQF_PLAYERDATA | Player's time on the server, in minutes. Note: SQF_PLAYERDATA information is sent once for each player on the server. |
Byte | SQF_TEAMINFO_NUMBER | The number of teams used. |
String | SQF_TEAMINFO_NAME | The team's name. (Sent for each team.) |
Long | SQF_TEAMINFO_COLOR | The team's color. (Sent for each team.) |
Short | SQF_TEAMINFO_SCORE | The team's score. (Sent for each team.) |
Byte | SQF_TESTING_SERVER | Whether this server is running a testing binary - true (1) / false (0) |
String | SQF_TESTING_SERVER | An empty string in case the server is running a stable binary, otherwise name of the testing binary archive found in http://www.skulltag.com/testing/files/ |
String | SQF_DATA_MD5SUM | [Deprecated] Returns an empty string. |
Byte | SQF_ALL_DMFLAGS | The number of flags that will be sent. |
Long | SQF_ALL_DMFLAGS | The value of the flags (Sent for each flag in the order dmflags, dmflags2, zadmflags, compatflags, zacompatflags, compatflags2) |
Byte | SQF_SECURITY_SETTINGS | Whether the server is enforcing the master ban list - true (1) / false (0) The other bits of this byte may be used to transfer other security related settings in the future. |
Byte | SQF_OPTIONAL_WADS | Amount of optional wad indices that follow |
Byte | SQF_OPTIONAL_WADS | Index number into the list sent with SQF_PWADS - this wad is optional (sent for each optional Wad) |
Byte | SQF_DEH | Amount of deh patches loaded |
String | SQF_DEH | Deh patch name (one string for each deh patch) |
Long | SQF_EXTENDED_INFO | The flags specifying the SQF2_data that immediately follows. |
Extended flags
Type | Flag | Description |
---|---|---|
Byte | SQF2_PWAD_HASHES | The number of hashes sent. |
String | SQF2_PWAD_HASHES | The hash of the PWAD, sent for each PWAD. The indices are the same as sent in SQF_PWADS |
Byte[3] | SQF2_COUNTRY | 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, XIP and XUN. See #Country codes below for how to handle this field. |
String | SQF2_GAMEMODE_NAME | (development version 3.2-alpha and above only) The name of the server's current game mode, as defined in the GAMEMODE lump. |
String | SQF2_GAMEMODE_SHORTNAME | (development version 3.2-alpha and above only) The short name of the server's current game mode, as defined in the GAMEMODE lump. |
Byte | SQF2_VOICECHAT | (development version 3.2-alpha and above only) The server's voice chat setting which is based on sv_allowvoicechat. |
Notes
Game modes
Game modes are defined as:
0 GAMEMODE_COOPERATIVE 1 GAMEMODE_SURVIVAL 2 GAMEMODE_INVASION 3 GAMEMODE_DEATHMATCH 4 GAMEMODE_TEAMPLAY 5 GAMEMODE_DUEL 6 GAMEMODE_TERMINATOR 7 GAMEMODE_LASTMANSTANDING 8 GAMEMODE_TEAMLMS 9 GAMEMODE_POSSESSION 10 GAMEMODE_TEAMPOSSESSION 11 GAMEMODE_TEAMGAME 12 GAMEMODE_CTF 13 GAMEMODE_ONEFLAGCTF 14 GAMEMODE_SKULLTAG 15 GAMEMODE_DOMINATION
Country codes
Zandronum 3.1 introduces the ability to server hosts to specify the country their server is located in via a new CVAR, sv_country. 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 SQF2_COUNTRY 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 sv_country 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 XIP, which suggests to the launcher that it should attempt IP geolocation to determine the server's country. The default value of sv_country 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 XUN, 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
Type | Value | Description |
---|---|---|
Long | 199 | The launcher challenge. |
Long | 1234 | Ping value. |
Long | 36700161 | The flags we want: SQF_NAME|SQF_PLAYERDATA|SQF_TEAMINFO_NUMBER|SQF_TESTING_SERVER |
Long | 6 | The extended flags we want: SQF2_COUNTRY|SQF2_GAMEMODE_NAME |
Byte | 2 | Indicates we want a segmented response, if the server supports it. |
Response
Type | Value | Description |
---|---|---|
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. |
Short | 0 | Offset of the packet. |
Short | 747 | Size of this packet |
Short | 747 | Total amount of data being sent. |
Packet data follows after this point. | ||
Long | 1234 | The ping value we sent to the server. |
String | 3.2-alpha-r240718-2351 on Linux 6.9.9-1-default | The server's version. |
Long | 35127297 | The fields contained in the response. SQF_NAME|SQF_NUMPLAYERS|SQF_PLAYERDATA|SQF_TESTING_SERVER|SQF_EXTENDED_INFO. Observe how:
|
String | Skipper Pavilion | From SQF_NAME. The server's name. |
Byte | 1 | From SQF_NUMPLAYERS. The number of players online. |
String | Alphus | From SQF_PLAYERDATA. The name of the first player in the server. |
Short | 0 | From SQF_PLAYERDATA. Alphus' score. |
Short | 0 | From SQF_PLAYERDATA. Alphus' ping. |
Byte | 0 | From SQF_PLAYERDATA. Whether Alphus is a spectator or not. |
Byte | 1 | From SQF_PLAYERDATA. Whether Alphus is a bot or not. |
Byte | 5 | From SQF_PLAYERDATA. How many minutes Alphus has been in the server. |
Byte | 1 | From SQF_TESTING_SERVER. Whether the server is running a testing binary. |
String | downloads/testing/3.2/ZandroDev3.2-230430-1741windows.zip | From SQF_TESTING_SERVER. Path to the testing binary on zandronum.com. |
Long | 6 | From SQF_EXTENDED_INFO. The fields that follow from the extended flags: SQF2_COUNTRY|SQF2_GAMEMODE_NAME |
char[3] | GBR | From SQF2_COUNTRY. The country code the server presents, as set by sv_country. |
String | Cooperative | From SQF2_GAMEMODE_NAME. The name of the game mode currently being played. |
Example code
Validation of the given offsets and sizes has been omitted here.
let receivedSegments = 0;
function handlePacket(data) {
const response = data.readLong();
switch (response) {
case SERVER_LAUNCHER_CHALLENGE: {
parseResponse(data);
break;
}
case SERVER_LAUNCHER_CHALLENGE_SEGMENTED: {
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);
}
copyBuffer(from: data, to: buffer, toOffset: offset);
receivedSegments++;
if (receivedSegments == totalSegments) {
parseResponse(buffer);
}
break;
}
case SERVER_LAUNCHER_BANNED: {
// handle error
break;
}
case SERVER_LAUNCHER_IGNORING: {
// handle error
break;
}
}
}
function parseResponse(data) {
const time = data.readLong();
const version = data.readString();
const flags = data.readLong();
if (flags & SQF_NAME) {
const name = data.readString();
}
// ...
}