Add a ton of reference

This commit is contained in:
Michael Morrison 2014-02-03 16:04:51 -06:00
parent 473d9544b1
commit 09d9b87ad6
22 changed files with 5115 additions and 4 deletions

View File

@ -0,0 +1,2 @@
gamedig note:
connect over TCP, and the data just starts coming, no packet needs sent?

276
reference/bfris/qstat.txt Normal file
View File

@ -0,0 +1,276 @@
LICENSE: The Artistic License 2.0
/*
* qstat.h
* by Steve Jankowski
* steve@qstat.org
* http://www.qstat.org
*
* Copyright 1996,1997,1998,1999,2000,2001,2002 by Steve Jankowski
*/
{
/* BFRIS */
BFRIS_SERVER, /* id */
"BFS", /* type_prefix */
"bfs", /* type_string */
"-bfs", /* type_option */
"BFRIS", /* game_name */
0, /* master */
BFRIS_DEFAULT_PORT, /* default_port */
0, /* port_offset */
TF_TCP_CONNECT, /* flags */
"Rules", /* game_rule */
"BFRIS", /* template_var */
NULL, /* status_packet */
0, /* status_len */
NULL, /* player_packet */
0, /* player_len */
NULL, /* rule_packet */
0, /* rule_len */
NULL, /* master_packet */
0, /* master_len */
NULL, /* master_protocol */
NULL, /* master_query */
display_bfris_player_info, /* display_player_func */
display_server_rules, /* display_rule_func */
raw_display_bfris_player_info,/* display_raw_player_func */
raw_display_server_rules, /* display_raw_rule_func */
xml_display_bfris_player_info, /* display_xml_player_func */
xml_display_server_rules, /* display_xml_rule_func */
send_bfris_request_packet, /* status_query_func */
NULL, /* rule_query_func */
NULL, /* player_query_func */
deal_with_bfris_packet, /* packet_func */
},
/* postions of map name, player name (in player substring), zero-based */
#define BFRIS_MAP_POS 18
#define BFRIS_PNAME_POS 11
query_status_t deal_with_bfris_packet(struct qserver *server, char *rawpkt, int pktlen)
{
int i, player_data_pos, nplayers;
SavedData *sdata;
unsigned char *saved_data;
int saved_data_size;
debug( 2, "deal_with_bfris_packet %p, %d", server, pktlen );
server->ping_total += time_delta(&packet_recv_time, &server->packet_time1);
/* add to the data previously saved */
sdata = &server->saved_data;
if (!sdata->data)
{
sdata->data = (char*)malloc(pktlen);
}
else
{
sdata->data = (char*)realloc(sdata->data, sdata->datalen + pktlen);
}
memcpy(sdata->data + sdata->datalen, rawpkt, pktlen);
sdata->datalen += pktlen;
saved_data = (unsigned char*)sdata->data;
saved_data_size = sdata->datalen;
/* after we get the server portion of the data, server->game != NULL */
if (!server->game)
{
/* server data goes up to map name */
if (sdata->datalen <= BFRIS_MAP_POS)
{
return INPROGRESS;
}
/* see if map name is complete */
player_data_pos = 0;
for (i = BFRIS_MAP_POS; i < saved_data_size; i++)
{
if (saved_data[i] == '\0')
{
player_data_pos = i + 1;
/* data must extend beyond map name */
if (saved_data_size <= player_data_pos)
{
return INPROGRESS;
}
break;
}
}
/* did we find beginning of player data? */
if (!player_data_pos)
{
return INPROGRESS;
}
/* now we can go ahead and fill in server data */
server->map_name = strdup((char*)saved_data + BFRIS_MAP_POS);
server->max_players = saved_data[12];
server->protocol_version = saved_data[11];
/* save game type */
switch (saved_data[13] &15)
{
case 0:
server->game = "FFA";
break;
case 5:
server->game = "Rover";
break;
case 6:
server->game = "Occupation";
break;
case 7:
server->game = "SPAAL";
break;
case 8:
server->game = "CTF";
break;
default:
server->game = "unknown";
break;
}
server->flags |= FLAG_DO_NOT_FREE_GAME;
add_rule(server, server->type->game_rule, server->game, NO_FLAGS);
if (get_server_rules)
{
char buf[24];
/* server revision */
sprintf(buf, "%d", (unsigned int)saved_data[11]);
add_rule(server, "Revision", buf, NO_FLAGS);
/* latency */
sprintf(buf, "%d", (unsigned int)saved_data[10]);
add_rule(server, "Latency", buf, NO_FLAGS);
/* player allocation */
add_rule(server, "Allocation", saved_data[13] &16 ? "Automatic" : "Manual", NO_FLAGS);
}
}
/* If we got this far, we know the data saved goes at least to the start of
the player information, and that the server data is taken care of.
*/
/* start of player data */
player_data_pos = BFRIS_MAP_POS + strlen((char*)saved_data + BFRIS_MAP_POS) + 1;
/* ensure all player data have arrived */
nplayers = 0;
while (saved_data[player_data_pos] != '\0')
{
player_data_pos += BFRIS_PNAME_POS;
/* does player data extend to player name? */
if (saved_data_size <= player_data_pos + 1)
{
return INPROGRESS;
}
/* does player data extend to end of player name? */
for (i = 0; player_data_pos + i < saved_data_size; i++)
{
if (saved_data_size == player_data_pos + i + 1)
{
return INPROGRESS;
}
if (saved_data[player_data_pos + i] == '\0')
{
player_data_pos += i + 1;
nplayers++;
break;
}
}
}
/* all player data are complete */
server->num_players = nplayers;
if (get_player_info)
{
/* start of player data */
player_data_pos = BFRIS_MAP_POS + strlen((char*)saved_data + BFRIS_MAP_POS) + 1;
for (i = 0; i < nplayers; i++)
{
struct player *player;
player = add_player(server, saved_data[player_data_pos]);
player->ship = saved_data[player_data_pos + 1];
player->ping = saved_data[player_data_pos + 2];
player->frags = saved_data[player_data_pos + 3];
player->team = saved_data[player_data_pos + 4];
switch (player->team)
{
case 0:
player->team_name = "silver";
break;
case 1:
player->team_name = "red";
break;
case 2:
player->team_name = "blue";
break;
case 3:
player->team_name = "green";
break;
case 4:
player->team_name = "purple";
break;
case 5:
player->team_name = "yellow";
break;
case 6:
player->team_name = "cyan";
break;
default:
player->team_name = "unknown";
break;
}
player->flags |= PLAYER_FLAG_DO_NOT_FREE_TEAM;
player->room = saved_data[player_data_pos + 5];
/* score is little-endian integer */
player->score = saved_data[player_data_pos + 7] +
(saved_data[player_data_pos + 8] << 8) +
(saved_data[player_data_pos + 9] << 16) +
(saved_data[player_data_pos + 10] << 24);
/* for archs with > 4-byte int */
if (player->score &0x80000000)
{
player->score = - (~(player->score)) - 1;
}
player_data_pos += BFRIS_PNAME_POS;
player->name = strdup((char*)saved_data + player_data_pos);
player_data_pos += strlen(player->name) + 1;
}
}
server->server_name = BFRIS_SERVER_NAME;
return DONE_FORCE;
}

View File

@ -0,0 +1,2 @@
I was under the impression all the crysis games used gamespy?
If anyone notices a problem, this is reference for some old cryengine protocol.

View File

@ -1,7 +1,3 @@
I was under the impression all the farcry games used ASE?
If anyone notices a problem, this is reference for some old cryengine protocol:
<?php
/**
* This file is part of GameQ.

View File

@ -0,0 +1,326 @@
LICENSE: The Artistic License 2.0
/*
* qstat.h
* by Steve Jankowski
* steve@qstat.org
* http://www.qstat.org
*
* Copyright 1996,1997,1998,1999,2000,2001,2002 by Steve Jankowski
*/
{
/* CRYSIS PROTOCOL */
CRYSIS_PROTOCOL_SERVER, /* id */
"CRYSIS", /* type_prefix */
"crysis", /* type_string */
"-crysis", /* type_option */
"Crysis", /* game_name */
0, /* master */
0, /* default_port */
0, /* port_offset */
TF_TCP_CONNECT|TF_QUERY_ARG_REQUIRED|TF_QUERY_ARG, /* flags */
"gamerules", /* game_rule */
"CRYSISPROTOCOL", /* template_var */
NULL, /* status_packet */
0, /* status_len */
NULL, /* player_packet */
0, /* player_len */
NULL, /* rule_packet */
0, /* rule_len */
NULL, /* master_packet */
0, /* master_len */
NULL, /* master_protocol */
NULL, /* master_query */
NULL, /* display_player_func */
display_server_rules, /* display_rule_func */
NULL, /* display_raw_player_func */
raw_display_server_rules, /* display_raw_rule_func */
xml_display_player_info, /* display_xml_player_func */
xml_display_server_rules, /* display_xml_rule_func */
send_crysis_request_packet, /* status_query_func */
NULL, /* rule_query_func */
NULL, /* player_query_func */
deal_with_crysis_packet, /* packet_func */
},
/*
* qstat 2.8
* by Steve Jankowski
*
* Crysis query protocol
* Copyright 2012 Steven Hartland
*
* Licensed under the Artistic License, see LICENSE.txt for license terms
*
*/
#include <sys/types.h>
#ifndef _WIN32
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#else
#include <winsock.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include "debug.h"
#include "utils.h"
#include "qstat.h"
#include "md5.h"
#include "packet_manip.h"
char *decode_crysis_val( char *val )
{
// Very basic html conversion
val = str_replace( val, "&quot;", "\"" );
return str_replace( val, "&amp;", "&" );
}
query_status_t send_crysis_request_packet( struct qserver *server )
{
char cmd[256], buf[1024], *password, *md5;
debug( 2, "challenge: %ld", server->challenge );
switch ( server->challenge )
{
case 0:
// Not seen a challenge yet, request it
server->challenge++;
sprintf( cmd, "challenge" );
break;
case 1:
server->challenge++;
password = get_param_value( server, "password", "" );
sprintf( cmd, "%s:%s", server->challenge_string, password );
md5 = md5_hex( cmd, strlen( cmd ) );
sprintf( cmd, "authenticate %s", md5 );
free( md5 );
break;
case 2:
// NOTE: we currently don't support player info
server->challenge++;
server->flags |= TF_STATUS_QUERY;
server->n_servers = 3;
sprintf( cmd, "status" );
break;
case 3:
return DONE_FORCE;
}
server->saved_data.pkt_max = -1;
sprintf(buf, "POST /RPC2 HTTP/1.1\015\012Keep-Alive: 300\015\012User-Agent: qstat %s\015\012Content-Length: %d\015\012Content-Type: text/xml\015\012\015\012<?xml version=\"1.0\" encoding=\"UTF-8\"?><methodCall><methodName>%s</methodName><params /></methodCall>", VERSION, (int)(98 + strlen(cmd)), cmd);
return send_packet( server, buf, strlen( buf ) );
}
query_status_t valid_crysis_response( struct qserver *server, char *rawpkt, int pktlen )
{
char *s;
int len;
int cnt = packet_count( server );
if ( 0 == cnt && 0 != strncmp( "HTTP/1.1 200 OK", rawpkt, 15 ) )
{
// not valid response
return REQ_ERROR;
}
s = strnstr(rawpkt, "Content-Length: ", pktlen );
if ( NULL == s )
{
// not valid response
return INPROGRESS;
}
s += 16;
if ( 1 != sscanf( s, "%d", &len ) )
{
return INPROGRESS;
}
s = strnstr(rawpkt, "\015\012\015\012", pktlen );
if ( NULL == s )
{
return INPROGRESS;
}
s += 4;
if ( pktlen != ( s - rawpkt + len ) )
{
return INPROGRESS;
}
return DONE_FORCE;
}
char* crysis_response( struct qserver *server, char *rawpkt, int pktlen )
{
char *s, *e;
int len = pktlen;
s = strnstr(rawpkt, "<methodResponse><params><param><value><string>", len );
if ( NULL == s )
{
// not valid response
return NULL;
}
s += 46;
len += rawpkt - s;
e = strnstr(s, "</string></value>", len );
if ( NULL == e )
{
// not valid response
return NULL;
}
*e = '\0';
return strdup( s );
}
query_status_t deal_with_crysis_packet( struct qserver *server, char *rawpkt, int pktlen )
{
char *s, *val, *line;
query_status_t state = INPROGRESS;
debug( 2, "processing..." );
if ( ! server->combined )
{
state = valid_crysis_response( server, rawpkt, pktlen );
server->retry1 = n_retries;
if ( 0 == server->n_requests )
{
server->ping_total = time_delta( &packet_recv_time, &server->packet_time1 );
server->n_requests++;
}
switch ( state )
{
case INPROGRESS:
{
// response fragment recieved
int pkt_id;
int pkt_max;
// We're expecting more to come
debug( 5, "fragment recieved..." );
pkt_id = packet_count( server );
pkt_max = pkt_id++;
if ( ! add_packet( server, 0, pkt_id, pkt_max, pktlen, rawpkt, 1 ) )
{
// fatal error e.g. out of memory
return MEM_ERROR;
}
// combine_packets will call us recursively
return combine_packets( server );
}
case DONE_FORCE:
break; // single packet response fall through
default:
return state;
}
}
if ( DONE_FORCE != state )
{
state = valid_crysis_response( server, rawpkt, pktlen );
switch ( state )
{
case DONE_FORCE:
break; // actually process
default:
return state;
}
}
debug( 3, "packet: challenge = %ld", server->challenge );
switch ( server->challenge )
{
case 1:
s = crysis_response( server, rawpkt, pktlen );
if ( NULL != s )
{
server->challenge_string = s;
return send_crysis_request_packet( server );
}
return REQ_ERROR;
case 2:
s = crysis_response( server, rawpkt, pktlen );
if ( NULL == s )
{
return REQ_ERROR;
}
if ( 0 != strncmp( s, "authorized", 10 ) )
{
free( s );
return REQ_ERROR;
}
free( s );
return send_crysis_request_packet( server );
case 3:
s = crysis_response( server, rawpkt, pktlen );
if ( NULL == s )
{
return REQ_ERROR;
}
}
// Correct ping
// Not quite right but gives a good estimate
server->ping_total = ( server->ping_total * server->n_requests ) / 2;
debug( 3, "processing response..." );
s = decode_crysis_val( s );
line = strtok( s, "\012" );
// NOTE: id=XXX and msg=XXX will be processed by the mod following the one they where the response of
while ( NULL != line )
{
debug( 4, "LINE: %s\n", line );
val = strstr( line, ":" );
if ( NULL != val )
{
*val = '\0';
val+=2;
debug( 4, "var: %s, val: %s", line, val );
if ( 0 == strcmp( "name", line ) )
{
server->server_name = strdup( val );
}
else if ( 0 == strcmp( "level", line ) )
{
server->map_name = strdup( val );
}
else if ( 0 == strcmp( "players", line ) )
{
if ( 2 == sscanf( val, "%d/%d", &server->num_players, &server->max_players) )
{
}
}
else if (
0 == strcmp( "version", line ) ||
0 == strcmp( "gamerules", line ) ||
0 == strcmp( "time remaining", line )
)
{
add_rule( server, line, val, NO_FLAGS );
}
}
line = strtok( NULL, "\012" );
}
gettimeofday( &server->packet_time1, NULL );
return DONE_FORCE;
}

View File

@ -0,0 +1,284 @@
LICENSE: The Artistic License 2.0
/*
* qstat.h
* by Steve Jankowski
* steve@qstat.org
* http://www.qstat.org
*
* Copyright 1996,1997,1998,1999,2000,2001,2002 by Steve Jankowski
*/
char cube2_serverstatus[3] = {'\x80', '\x10', '\x27'};
{
/* Cube 2/Sauerbraten/Blood Frontier */
CUBE2_SERVER, /* id */
"CUBE2", /* type_prefix */
"cube2", /* type_string */
"-cubes", /* type_option */
"Sauerbraten", /* game_name */
0, /* master */
CUBE2_DEFAULT_PORT, /* default_port */
1, /* port_offset */
0, /* flags */
"", /* game_rule */
"CUBE2", /* template_var */
cube2_serverstatus, /* status_packet */
sizeof(cube2_serverstatus), /* status_len */
NULL, /* player_packet */
0, /* player_len */
NULL, /* rule_packet */
0, /* rule_len */
NULL, /* master_packet */
0, /* master_len */
NULL, /* master_protocol */
NULL, /* master_query */
NULL, /* display_player_func */
display_server_rules, /* display_rule_func */
NULL, /* display_raw_player_func */
raw_display_server_rules, /* display_raw_rule_func */
NULL, /* display_xml_player_func */
xml_display_server_rules, /* display_xml_rule_func */
send_cube2_request_packet, /* status_query_func */
NULL, /* rule_query_func */
NULL, /* player_query_func */
deal_with_cube2_packet, /* packet_func */
},
/*
* qstat 2.12
* by Steve Jankowski
*
* Cube 2 / Sauerbraten protocol
* Copyright 2011 NoisyB
*
* Licensed under the Artistic License, see LICENSE.txt for license terms
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "debug.h"
#include "qstat.h"
#include "packet_manip.h"
struct offset
{
unsigned char *d;
int pos;
int len;
};
//#define SB_MASTER_SERVER "http://sauerbraten.org/masterserver/retrieve.do?item=list"
#define SB_PROTOCOL 258
#define MIN(a,b) ((a)<(b)?(a):(b))
#define MAX_ATTR 255
#define MAX_STRING 1024
static int
getint (struct offset * d)
{
int val = 0;
if ( d->pos >= d->len )
{
return 0;
}
val = d->d[d->pos++] & 0xff; // 8 bit value
// except...
if ( val == 0x80 && d->pos < d->len - 2 ) // 16 bit value
{
val = (d->d[d->pos++] & 0xff);
val |= (d->d[d->pos++] & 0xff) << 8;
}
else if ( val == 0x81 && d->pos < d->len - 4 ) // 32 bit value
{
val = (d->d[d->pos++] & 0xff);
val |= (d->d[d->pos++] & 0xff) << 8;
val |= (d->d[d->pos++] & 0xff) << 16;
val |= (d->d[d->pos++] & 0xff) << 24;
}
return val;
}
static char * getstr( char *dest, int dest_len, struct offset *d )
{
int len = 0;
if (d->pos >= d->len)
{
return NULL;
}
len = MIN( dest_len, d->len - d->pos );
strncpy( dest, (const char *) d->d + d->pos, len )[len - 1] = 0;
d->pos += strlen (dest) + 1;
return dest;
}
static char* sb_getversion_s (int n)
{
static char *version_s[] =
{
"Justice",
"CTF",
"Assassin",
"Summer",
"Spring",
"Gui",
"Water",
"Normalmap",
"Sp",
"Occlusion",
"Shader",
"Physics",
"Mp",
"",
"Agc",
"Quakecon",
"Independence"
};
n = SB_PROTOCOL - n;
if (n >= 0 && (size_t) n < sizeof(version_s) / sizeof(version_s[0]))
{
return version_s[n];
}
return "unknown";
}
static char* sb_getmode_s(int n)
{
static char *mode_s[] =
{
"slowmo SP",
"slowmo DMSP",
"demo",
"SP",
"DMSP",
"ffa/default",
"coopedit",
"ffa/duel",
"teamplay",
"instagib",
"instagib team",
"efficiency",
"efficiency team",
"insta arena",
"insta clan arena",
"tactics arena",
"tactics clan arena",
"capture",
"insta capture",
"regen capture",
"assassin",
"insta assassin",
"ctf",
"insta ctf"
};
n += 6;
if (n >= 0 && (size_t) n < sizeof(mode_s) / sizeof(mode_s[0]))
{
return mode_s[n];
}
return "unknown";
}
query_status_t send_cube2_request_packet( struct qserver *server )
{
return send_packet( server, server->type->status_packet, server->type->status_len );
}
query_status_t deal_with_cube2_packet( struct qserver *server, char *rawpkt, int pktlen )
{
// skip unimplemented ack, crc, etc
int i;
int numattr;
int attr[MAX_ATTR];
char buf[MAX_STRING];
enum {
MM_OPEN = 0,
MM_VETO,
MM_LOCKED,
MM_PRIVATE
};
struct offset d;
d.d = (unsigned char *) rawpkt;
d.pos = 0;
d.len = pktlen;
server->ping_total += time_delta( &packet_recv_time, &server->packet_time1 );
getint( &d ); // we have the ping already
server->num_players = getint( &d );
numattr = getint( &d );
for ( i = 0; i < numattr && i < MAX_ATTR; i++ )
{
attr[i] = getint (&d);
}
server->protocol_version = attr[0];
sprintf( buf, "%d %s", attr[0], sb_getversion_s (attr[0]) );
add_rule( server, "version", buf, NO_FLAGS );
sprintf( buf, "%d %s", attr[1], sb_getmode_s (attr[1]) );
add_rule( server, "mode", buf, NO_FLAGS );
sprintf( buf, "%d", attr[2] );
add_rule( server, "seconds_left", buf, NO_FLAGS );
server->max_players = attr[3];
switch ( attr[5] )
{
case MM_OPEN:
sprintf( buf, "%d open", attr[5] );
break;
case MM_VETO:
sprintf( buf, "%d veto", attr[5] );
break;
case MM_LOCKED:
sprintf( buf, "%d locked", attr[5] );
break;
case MM_PRIVATE:
sprintf( buf, "%d private", attr[5] );
break;
default:
sprintf( buf, "%d unknown", attr[5] );
}
add_rule( server, "mm", buf, NO_FLAGS);
for ( i = 0; i < numattr && i < MAX_ATTR; i++ )
{
char buf2[MAX_STRING];
sprintf( buf, "attr%d", i );
sprintf( buf2, "%d", attr[i] );
add_rule( server, buf, buf2, NO_FLAGS );
}
getstr( buf, MAX_STRING, &d );
server->map_name = strdup(buf);
getstr( buf, MAX_STRING, &d );
server->server_name = strdup(buf);
return DONE_FORCE;
}

View File

@ -0,0 +1,2 @@
This is the doc for some descent 3 protocol that ISNT the gamespy
protocol. Not really sure what it's for.

View File

@ -0,0 +1,223 @@
LICENSE: The Artistic License 2.0
/*
* qstat.h
* by Steve Jankowski
* steve@qstat.org
* http://www.qstat.org
*
* Copyright 1996,1997,1998,1999,2000,2001,2002 by Steve Jankowski
*/
/* for some reason Descent3 uses a different request for pxo/non-pxo games. blah. */
unsigned char descent3_pxoinfoquery[] = {
0x01, /* "internal descent3 routing" */
0x29, /* request server info? (pxo listed servers) */
0x0b, 0x00, /* packet length (- routing byte) */
0x1b, 0x2f, 0xf4, 0x41, 0x09, 0x00, 0x00, 0x00 /* unknown */
};
unsigned char descent3_tcpipinfoquery[] = {
0x01, /* "internal descent3 routing" */
0x1e, /* request server info? (tcpip only servers) */
0x0b, 0x00, /* packet length (- routing byte) */
0x1b, 0x2f, 0xf4, 0x41, 0x09, 0x00, 0x00, 0x00 /* unknown */
};
/* http://ml.warpcore.org/d3dl/200101/msg00001.html
* http://ml.warpcore.org/d3dl/200101/msg00004.html */
unsigned char descent3_playerquery[] = {
0x01, /* "internal descent3 routing" */
0x72, /* MP_REQUEST_PLAYERLIST */
0x03, 0x00 /* packet length (- routing byte) */
};
{
/* DESCENT3 PROTOCOL */
DESCENT3_SERVER, /* id */
"D3S", /* type_prefix */
"d3s", /* type_string */
"-d3s", /* type_option */
"Descent3", /* game_name */
0, /* master */
DESCENT3_DEFAULT_PORT, /* default_port */
0, /* port_offset */
0, /* flags */
"gametype", /* game_rule */
"DESCENT3", /* template_var */
(char*) &descent3_tcpipinfoquery, /* status_packet */
sizeof( descent3_tcpipinfoquery), /* status_len */
(char*) &descent3_playerquery, /* player_packet */
sizeof( descent3_playerquery), /* player_len */
NULL, /* rule_packet */
0, /* rule_len */
NULL, /* master_packet */
0, /* master_len */
NULL, /* master_protocol */
NULL, /* master_query */
display_descent3_player_info, /* display_player_func */
display_server_rules, /* display_rule_func */
raw_display_descent3_player_info, /* display_raw_player_func */
raw_display_server_rules, /* display_raw_rule_func */
xml_display_descent3_player_info, /* display_xml_player_func */
xml_display_server_rules, /* display_xml_rule_func */
send_gps_request_packet, /* status_query_func */
NULL, /* rule_query_func */
NULL, /* player_query_func */
deal_with_descent3_packet, /* packet_func */
},
{
/* DESCENT3 PROTOCOL */
DESCENT3_PXO_SERVER, /* id */
"D3P", /* type_prefix */
"d3p", /* type_string */
"-d3p", /* type_option */
"Descent3 PXO protocol", /* game_name */
0, /* master */
DESCENT3_DEFAULT_PORT, /* default_port */
0, /* port_offset */
0, /* flags */
"gametype", /* game_rule */
"DESCENT3", /* template_var */
(char*) &descent3_pxoinfoquery, /* status_packet */
sizeof( descent3_pxoinfoquery), /* status_len */
(char*) &descent3_playerquery, /* player_packet */
sizeof( descent3_playerquery), /* player_len */
NULL, /* rule_packet */
0, /* rule_len */
NULL, /* master_packet */
0, /* master_len */
NULL, /* master_protocol */
NULL, /* master_query */
display_descent3_player_info, /* display_player_func */
display_server_rules, /* display_rule_func */
raw_display_descent3_player_info, /* display_raw_player_func */
raw_display_server_rules, /* display_raw_rule_func */
xml_display_descent3_player_info, /* display_xml_player_func */
xml_display_server_rules, /* display_xml_rule_func */
send_gps_request_packet, /* status_query_func */
NULL, /* rule_query_func */
NULL, /* player_query_func */
deal_with_descent3_packet, /* packet_func */
},
query_status_t deal_with_descent3_packet(struct qserver *server, char *rawpkt, int pktlen)
{
char *pkt;
char buf[24];
debug( 2, "deal_with_descent3_packet %p, %d", server, pktlen );
if (server->server_name == NULL)
{
server->ping_total += time_delta(&packet_recv_time, &server->packet_time1);
}
if (pktlen < 4)
{
fprintf(stderr, "short descent3 packet\n");
print_packet(server, rawpkt, pktlen);
return PKT_ERROR;
}
/* 'info' response */
if (rawpkt[1] == 0x1f)
{
if (server->server_name != NULL)
{
return PKT_ERROR;
}
pkt = &rawpkt[0x15];
server->server_name = strdup(pkt);
pkt += strlen(pkt) + 2;
server->map_name = strdup(pkt); /* mission name (blah.mn3) */
pkt += strlen(pkt) + 2;
add_rule(server, "level_name", pkt, NO_FLAGS);
pkt += strlen(pkt) + 2;
add_rule(server, "gametype", pkt, NO_FLAGS);
pkt += strlen(pkt) + 1;
sprintf(buf, "%hu", swap_short_from_little(pkt));
add_rule(server, "level_num", buf, NO_FLAGS);
pkt += 2;
server->num_players = swap_short_from_little(pkt);
pkt += 2;
server->max_players = swap_short_from_little(pkt);
pkt += 2;
/* unknown/undecoded fields.. stuff like permissible, banned items/ships, etc */
add_uchar_rule(server, "u0", pkt[0]);
add_uchar_rule(server, "u1", pkt[1]);
add_uchar_rule(server, "u2", pkt[2]);
add_uchar_rule(server, "u3", pkt[3]);
add_uchar_rule(server, "u4", pkt[4]);
add_uchar_rule(server, "u5", pkt[5]);
add_uchar_rule(server, "u6", pkt[6]);
add_uchar_rule(server, "u7", pkt[7]);
add_uchar_rule(server, "u8", pkt[8]);
add_uchar_rule(server, "randpowerup", (unsigned char)!(pkt[4] &1)); /*
randomize powerup spawn */
add_uchar_rule(server, "acccollisions", (unsigned char)((pkt[5] &4) > 0));
/* accurate collision detection */
add_uchar_rule(server, "brightships", (unsigned char)((pkt[5] &16) > 0));
/* bright player ships */
add_uchar_rule(server, "mouselook", (unsigned char)((pkt[6] &1) > 0)); /*
mouselook enabled */
sprintf(buf, "%s%s", (pkt[4] &16) ? "PP" : "CS", (pkt[6] &1) ? "-ML" : "");
add_rule(server, "servertype", buf, NO_FLAGS);
sprintf(buf, "%hhu", pkt[9]);
add_rule(server, "difficulty", buf, NO_FLAGS);
/* unknown/undecoded fields after known flags removed */
add_uchar_rule(server, "x4", (unsigned char)(pkt[4] &~(1+16)));
add_uchar_rule(server, "x5", (unsigned char)(pkt[5] &~(4+16)));
add_uchar_rule(server, "x6", (unsigned char)(pkt[6] &~1));
if (get_player_info && server->num_players)
{
server->next_player_info = 0;
send_player_request_packet(server);
return INPROGRESS;
}
}
/* MP_PLAYERLIST_DATA */
else if (rawpkt[1] == 0x73)
{
struct player *player;
struct player **last_player = &server->players;
if (server->players != NULL)
{
return PKT_ERROR;
}
pkt = &rawpkt[0x4];
while (*pkt)
{
player = (struct player*)calloc(1, sizeof(struct player));
player->name = strdup(pkt);
pkt += strlen(pkt) + 1;
*last_player = player;
last_player = &player->next;
}
server->next_player_info = NO_PLAYER_INFO;
}
else
{
fprintf(stderr, "unknown d3 packet\n");
print_packet(server, rawpkt, pktlen);
}
return DONE_FORCE;
}

View File

@ -0,0 +1,2 @@
I was under the impression farcry uses ASE.
If anyone ever has issues, this is the doc for some old farcry query protocol.

261
reference/farcry/qstat.txt Normal file
View File

@ -0,0 +1,261 @@
LICENSE: The Artistic License 2.0
/*
* qstat.h
* by Steve Jankowski
* steve@qstat.org
* http://www.qstat.org
*
* Copyright 1996,1997,1998,1999,2000,2001,2002 by Steve Jankowski
*/
unsigned char farcry_serverquery[] = {
0x08,0x80
};
{
/* FARCRY PROTOCOL */
FARCRY_SERVER, /* id */
"FCS", /* type_prefix */
"fcs", /* type_string */
"-fcs", /* type_option */
"FarCry", /* game_name */
0, /* master */
FARCRY_DEFAULT_PORT, /* default_port */
0, /* port_offset */
TF_QUERY_ARG, /* flags */
"gametype", /* game_rule */
"FARCRY", /* template_var */
(char*)farcry_serverquery, /* status_packet */
sizeof( savage_serverquery ) - 1, /* status_len */
NULL, /* player_packet */
0, /* player_len */
NULL, /* rule_packet */
0, /* rule_len */
NULL, /* master_packet */
0, /* master_len */
NULL, /* master_protocol */
NULL, /* master_query */
display_farcry_player_info, /* display_player_func */
display_server_rules, /* display_rule_func */
raw_display_farcry_player_info, /* display_raw_player_func */
raw_display_server_rules, /* display_raw_rule_func */
xml_display_farcry_player_info, /* display_xml_player_func */
xml_display_server_rules, /* display_xml_rule_func */
send_farcry_request_packet, /* status_query_func */
NULL, /* rule_query_func */
NULL, /* player_query_func */
deal_with_farcry_packet, /* packet_func */
},
query_status_t send_farcry_request_packet(struct qserver *server)
{
int len;
char *pkt;
if (get_player_info)
{
pkt = server->type->player_packet;
len = server->type->player_len;
}
else
{
pkt = server->type->status_packet;
len = server->type->status_len;
}
return send_packet(server, pkt, len);
}
query_status_t deal_with_farcry_packet(struct qserver *server, char *rawpkt, int pktlen)
{
char *s, *key, *value, *end;
debug( 2, "deal_with_farcry_packet %p, %d", server, pktlen );
server->n_servers++;
if (NULL == server->server_name)
{
server->ping_total += time_delta(&packet_recv_time, &server->packet_time1);
}
else
{
gettimeofday(&server->packet_time1, NULL);
}
rawpkt[pktlen] = '\0';
end = s = rawpkt;
end += pktlen;
while (*s)
{
// Find the seperator
while (s <= end && *s != '\xFF')
{
s++;
}
if (s >= end)
{
// Hit the end no more
break;
}
// key start
key = ++s;
while (s < end && *s != '\xFE')
{
s++;
}
if (*s != '\xFE')
{
// malformed
break;
}
*s++ = '\0';
// key end
// value start
value = s;
while (s < end && *s != '\xFF')
{
s++;
}
if (*s == '\xFF')
{
*s = '\0';
}
//fprintf( stderr, "'%s' = '%s'\n", key, value );
// Decode current key par
if (0 == strcmp("cmax", key))
{
// Max players
server->max_players = atoi(value);
}
else if (0 == strcmp("cnum", key))
{
// Current players
server->num_players = atoi(value);
}
else if (0 == strcmp("bal", key))
{
// Balance
add_rule(server, "Balance", value, NO_FLAGS);
}
else if (0 == strcmp("world", key))
{
// Current map
server->map_name = strdup(value);
}
else if (0 == strcmp("gametype", key))
{
// Game type
server->game = find_savage_game(value);
add_rule(server, server->type->game_rule, server->game, NO_FLAGS);
}
else if (0 == strcmp("pure", key))
{
// Pure
add_rule(server, "Pure", value, NO_FLAGS);
}
else if (0 == strcmp("time", key))
{
// Current game time
add_rule(server, "Time", value, NO_FLAGS);
}
else if (0 == strcmp("notes", key))
{
// Notes
add_rule(server, "Notes", value, NO_FLAGS);
}
else if (0 == strcmp("needcmdr", key))
{
// Need Commander
add_rule(server, "Need Commander", value, NO_FLAGS);
}
else if (0 == strcmp("name", key))
{
// Server name
server->server_name = strdup(value);
}
else if (0 == strcmp("fw", key))
{
// Firewalled
add_rule(server, "Firewalled", value, NO_FLAGS);
}
else if (0 == strcmp("players", key))
{
// Players names
int player_number = 0;
int team_number = 1;
char *team_name, *player_name, *n;
n = team_name = value;
// team name
n++;
while (*n && *n != '\x0a')
{
n++;
}
if (*n != '\x0a')
{
// Broken data
break;
}
*n = '\0';
player_name = ++n;
while (*n)
{
while (*n && *n != '\x0a')
{
n++;
}
if (*n != '\x0a')
{
// Broken data
break;
}
*n = '\0';
n++;
if (0 == strncmp("Team ", player_name, 5))
{
team_name = player_name;
team_number++;
}
else
{
if (0 != strlen(player_name))
{
struct player *player = add_player(server, player_number);
if (NULL != player)
{
player->name = strdup(player_name);
player->team = team_number;
player->team_name = strdup(team_name);
} player_number++;
}
}
player_name = n;
}
}
*s = '\xFF';
}
return DONE_FORCE;
}

View File

@ -0,0 +1,694 @@
LICENSE: The Artistic License 2.0
/*
* qstat.h
* by Steve Jankowski
* steve@qstat.org
* http://www.qstat.org
*
* Copyright 1996,1997,1998,1999,2000,2001,2002 by Steve Jankowski
*/
unsigned char ghostrecon_serverquery[] = {
0xc0,0xde,0xf1,0x11, /* const ? header */
0x42, /* start flag */
0x03,0x00, /* data len */
0xe9,0x03,0x00 /* server request ?? */
};
unsigned char ghostrecon_playerquery[] = {
0xc0,0xde,0xf1,0x11, /* const ? header */
0x42, /* start flag */
0x06,0x00, /* data len */
0xf5,0x03,0x00,0x78,0x30,0x63 /* player request ?? may be flag 0xf5; len 0x03,0x00; data 0x78, 0x30, 0x63 */
};
{
/* GHOSTRECON PROTOCOL */
GHOSTRECON_SERVER, /* id */
"GRS", /* type_prefix */
"grs", /* type_string */
"-grs", /* type_option */
"Ghost Recon", /* game_name */
0, /* master */
GHOSTRECON_PLAYER_DEFAULT_PORT, /* default_port */
2, /* port_offset */
TF_QUERY_ARG, /* flags */
"gametype", /* game_rule */
"GHOSTRECON", /* template_var */
(char*) &ghostrecon_playerquery, /* status_packet */
sizeof( ghostrecon_playerquery), /* status_len */
NULL, /* player_packet */
0, /* player_len */
NULL, /* rule_packet */
0, /* rule_len */
NULL, /* master_packet */
0, /* master_len */
NULL, /* master_protocol */
NULL, /* master_query */
display_ghostrecon_player_info, /* display_player_func */
display_server_rules, /* display_rule_func */
raw_display_ghostrecon_player_info, /* display_raw_player_func */
raw_display_server_rules, /* display_raw_rule_func */
xml_display_ghostrecon_player_info, /* display_xml_player_func */
xml_display_server_rules, /* display_xml_rule_func */
send_qserver_request_packet, /* status_query_func */
NULL, /* rule_query_func */
NULL, /* player_query_func */
deal_with_ghostrecon_packet, /* packet_func */
},
static const char GrPacketHead[] =
{
'\xc0', '\xde', '\xf1', '\x11'
};
static const char PacketStart = '\x42';
static char Dat2Reply1_2_10[] =
{
'\xf4', '\x03', '\x14', '\x02', '\x0a', '\x41', '\x02', '\x0a', '\x41', '\x00', '\x00', '\x78', '\x30', '\x63'
};
static char Dat2Reply1_3[] =
{
'\xf4', '\x03', '\x14', '\x03', '\x05', '\x41', '\x03', '\x05', '\x41', '\x00', '\x00', '\x78', '\x30', '\x63'
};
static char Dat2Reply1_4[] =
{
'\xf4', '\x03', '\x14', '\x04', '\x00', '\x41', '\x04', '\x00', '\x41', '\x00', '\x00', '\x78', '\x30', '\x63'
};
//static char HDat2[]={'\xea','\x03','\x02','\x00','\x14'};
#define SHORT_GR_LEN 75
#define LONG_GR_LEN 500
#define UNKNOWN_VERSION 0
#define VERSION_1_2_10 1
#define VERSION_1_3 2
#define VERSION_1_4 3
query_status_t deal_with_ghostrecon_packet(struct qserver *server, char *pkt, int pktlen)
{
char str[256], *start, *end, StartFlag, *lpszIgnoreServerPlayer;
char *lpszMission;
unsigned int iIgnoreServerPlayer, iDedicatedServer, iUseStartTimer;
unsigned short GrPayloadLen;
int i;
struct player *player;
int iLen, iTemp;
short sLen;
int iSecsPlayed;
long iSpawnType;
int ServerVersion = UNKNOWN_VERSION;
float flStartTimerSetPoint;
debug( 2, "deal_with_ghostrecon_packet %p, %d", server, pktlen );
start = pkt;
end = &pkt[pktlen];
pkt[pktlen] = '\0';
/*
This function walks a packet that is recieved from a ghost recon server - default from port 2348. It does quite a few
sanity checks along the way as the structure is not documented. The packet is mostly binary in nature with many string
fields being variable in length, ie the length is listed foloowed by that many bytes. There are two structure arrays
that have an array size followed by structure size * number of elements (player name and player data). This routine
walks this packet and increments a pointer "pkt" to extract the info.
*/
if (server->server_name == NULL)
{
server->ping_total += time_delta(&packet_recv_time, &server->packet_time1);
}
/* sanity check against packet */
if (memcmp(pkt, GrPacketHead, sizeof(GrPacketHead)) != 0)
{
server->server_name = strdup("Unknown Packet Header");
return PKT_ERROR;
}
pkt += sizeof(GrPacketHead);
StartFlag = pkt[0];
pkt += 1;
if (StartFlag != 0x42)
{
server->server_name = strdup("Unknown Start Flag");
return PKT_ERROR;
}
/* compare packet length recieved to included size - header info */
sLen = swap_short_from_little(pkt);
pkt += 2;
GrPayloadLen = pktlen - sizeof(GrPacketHead) - 3;
// 3 = size slen + size start flag
if (sLen != GrPayloadLen)
{
server->server_name = strdup("Packet Size Mismatch");
return PKT_ERROR;
}
/*
Will likely need to verify and add to this "if" construct with every patch / add-on.
*/
if (memcmp(pkt, Dat2Reply1_2_10, sizeof(Dat2Reply1_2_10)) == 0)
{
ServerVersion = VERSION_1_2_10;
}
else if (memcmp(pkt, Dat2Reply1_3, sizeof(Dat2Reply1_3)) == 0)
{
ServerVersion = VERSION_1_3;
}
else if (memcmp(pkt, Dat2Reply1_4, sizeof(Dat2Reply1_4)) == 0)
{
ServerVersion = VERSION_1_4;
}
if (ServerVersion == UNKNOWN_VERSION)
{
server->server_name = strdup("Unknown GR Version");
return PKT_ERROR;
}
switch (ServerVersion)
{
case VERSION_1_2_10:
strcpy(str, "1.2.10");
pkt += sizeof(Dat2Reply1_2_10);
break;
case VERSION_1_3:
strcpy(str, "1.3");
pkt += sizeof(Dat2Reply1_3);
break;
case VERSION_1_4:
strcpy(str, "1.4");
pkt += sizeof(Dat2Reply1_4);
break;
}
add_rule(server, "patch", str, NO_FLAGS);
/* have player packet */
// Ghost recon has one of the player slots filled up with the server program itself. By default we will
// drop the first player listed. This causes a bit of a mess here and below but makes for the best display
// a user can specify -grs,ignoreserverplayer=no to override this behaviour.
lpszIgnoreServerPlayer = get_param_value(server, "ignoreserverplayer", "yes");
for (i = 0; i < 4; i++)
{
str[i] = tolower(lpszIgnoreServerPlayer[i]);
}
if (strcmp(str, "yes") == 0)
{
iIgnoreServerPlayer = 1;
}
else
{
iIgnoreServerPlayer = 0;
}
pkt += 4; /* unknown */
// this is the first of many variable strings. get the length,
// increment pointer over length, check for sanity,
// get the string, increment the pointer over string (using length)
iLen = swap_long_from_little(pkt);
pkt += 4;
if ((iLen < 1) || (iLen > SHORT_GR_LEN))
{
server->server_name = strdup("Server Name too Long");
return PKT_ERROR;
}
server->server_name = strndup(pkt, iLen);
pkt += iLen;
iLen = swap_long_from_little(pkt);
pkt += 4;
if ((iLen < 1) || (iLen > SHORT_GR_LEN))
{
add_rule(server, "error", "Map Name too Long", NO_FLAGS);
return PKT_ERROR;
}
server->map_name = strndup(pkt, iLen);
pkt += iLen;
iLen = swap_long_from_little(pkt);
pkt += 4;
if ((iLen < 1) || (iLen > SHORT_GR_LEN))
{
add_rule(server, "error", "Mission Name too Long", NO_FLAGS);
return PKT_ERROR;
}
/* mission does not make sense unless a coop game type. Since
we dont know that now, we will save the mission and set
the rule and free memory below when we know game type */
lpszMission = strndup(pkt, iLen);
pkt += iLen;
iLen = swap_long_from_little(pkt);
pkt += 4;
if ((iLen < 1) || (iLen > SHORT_GR_LEN))
{
add_rule(server, "error", "Mission Type too Long", NO_FLAGS);
return PKT_ERROR;
}
add_nrule(server, "missiontype", pkt, iLen);
pkt += iLen;
if (pkt[1])
{
add_rule(server, "password", "Yes", NO_FLAGS);
}
else
{
add_rule(server, "password", "No", NO_FLAGS);
}
pkt += 2;
server->max_players = swap_long_from_little(pkt);
pkt += 4;
if (server->max_players > 36)
{
add_rule(server, "error", "Max players more then 36", NO_FLAGS);
return PKT_ERROR;
}
server->num_players = swap_long_from_little(pkt);
pkt += 4;
if (server->num_players > server->max_players)
{
add_rule(server, "error", "More then MAX Players", NO_FLAGS);
return PKT_ERROR;
}
if (iIgnoreServerPlayer)
// skip past first player
{
server->num_players--;
server->max_players--;
iLen = swap_long_from_little(pkt);
pkt += 4;
pkt += iLen;
}
for (i = 0; i < server->num_players; i++)
// read each player name
{
iLen = swap_long_from_little(pkt);
pkt += 4;
player = (struct player*)calloc(1, sizeof(struct player));
if ((iLen < 1) || (iLen > SHORT_GR_LEN))
{
add_rule(server, "error", "Player Name too Long", NO_FLAGS);
return PKT_ERROR;
}
player->name = strndup(pkt, iLen);
pkt += iLen; /* player name */
player->team = i; // tag so we can find this record when we have player dat.
player->team_name = "Unassigned";
player->flags |= PLAYER_FLAG_DO_NOT_FREE_TEAM;
player->frags = 0;
player->next = server->players;
server->players = player;
}
pkt += 17;
iLen = swap_long_from_little(pkt);
pkt += 4;
if ((iLen < 1) || (iLen > SHORT_GR_LEN))
{
add_rule(server, "error", "Version too Long", NO_FLAGS);
return PKT_ERROR;
}
strncpy(str, pkt, iLen);
add_rule(server, "version", str, NO_FLAGS);
pkt += iLen; /* version */
iLen = swap_long_from_little(pkt);
pkt += 4;
if ((iLen < 1) || (iLen > LONG_GR_LEN))
{
add_rule(server, "error", "Mods too Long", NO_FLAGS);
return PKT_ERROR;
}
server->game = strndup(pkt, iLen);
for (i = 0; i < (int)strlen(server->game) - 5; i++)
// clean the "/mods/" part from every entry
{
if (memcmp(&server->game[i], "\\mods\\", 6) == 0)
{
server->game[i] = ' ';
strcpy(&server->game[i + 1], &server->game[i + 6]);
}
}
add_rule(server, "game", server->game, NO_FLAGS);
pkt += iLen; /* mods */
iDedicatedServer = pkt[0];
if (iDedicatedServer)
{
add_rule(server, "dedicated", "Yes", NO_FLAGS);
}
else
{
add_rule(server, "dedicated", "No", NO_FLAGS);
}
pkt += 1; /* unknown */
iSecsPlayed = swap_float_from_little(pkt);
add_rule(server, "timeplayed", play_time(iSecsPlayed, 2), NO_FLAGS);
pkt += 4; /* time played */
switch (pkt[0])
{
case 3:
strcpy(str, "Joining");
break;
case 4:
strcpy(str, "Playing");
break;
case 5:
strcpy(str, "Debrief");
break;
default:
strcpy(str, "Undefined");
}
add_rule(server, "status", str, NO_FLAGS);
pkt += 1;
pkt += 3; /* unknown */
switch (pkt[0])
{
case 2:
strcpy(str, "COOP");
break;
case 3:
strcpy(str, "SOLO");
break;
case 4:
strcpy(str, "TEAM");
break;
default:
sprintf(str, "UNKOWN %u", pkt[0]);
break;
}
add_rule(server, "gamemode", str, NO_FLAGS);
if (pkt[0] == 2)
{
add_rule(server, "mission", lpszMission, NO_FLAGS);
}
else
{
add_rule(server, "mission", "No Mission", NO_FLAGS);
}
free(lpszMission);
pkt += 1; /* Game Mode */
pkt += 3; /* unknown */
iLen = swap_long_from_little(pkt);
pkt += 4;
if ((iLen < 1) || (iLen > LONG_GR_LEN))
{
add_rule(server, "error", "MOTD too Long", NO_FLAGS);
return PKT_ERROR;
}
strncpy(str, pkt, sizeof(str));
str[sizeof(str) - 1] = 0;
add_rule(server, "motd", str, NO_FLAGS);
pkt += iLen; /* MOTD */
iSpawnType = swap_long_from_little(pkt);
switch (iSpawnType)
{
case 0:
strcpy(str, "None");
break;
case 1:
strcpy(str, "Individual");
break;
case 2:
strcpy(str, "Team");
break;
case 3:
strcpy(str, "Infinite");
break;
default:
strcpy(str, "Unknown");
}
add_rule(server, "spawntype", str, NO_FLAGS);
pkt += 4; /* spawn type */
iTemp = swap_float_from_little(pkt);
add_rule(server, "gametime", play_time(iTemp, 2), NO_FLAGS);
iTemp = iTemp - iSecsPlayed;
if (iTemp <= 0)
{
iTemp = 0;
}
add_rule(server, "remainingtime", play_time(iTemp, 2), NO_FLAGS);
pkt += 4; /* Game time */
iTemp = swap_long_from_little(pkt);
if (iIgnoreServerPlayer)
{
iTemp--;
}
if (iTemp != server->num_players)
{
add_rule(server, "error", "Number of Players Mismatch", NO_FLAGS);
}
pkt += 4; /* player count 2 */
if (iIgnoreServerPlayer)
{
pkt += 5; // skip first player data
}
for (i = 0; i < server->num_players; i++)
// for each player get binary data
{
player = server->players;
// first we must find the player - lets look for the tag
while (player && (player->team != i))
{
player = player->next;
}
/* get to player - linked list is in reverse order */
if (player)
{
player->team = pkt[2];
switch (player->team)
{
case 1:
player->team_name = "Red";
break;
case 2:
player->team_name = "Blue";
break;
case 3:
player->team_name = "Yellow";
break;
case 4:
player->team_name = "Green";
break;
case 5:
player->team_name = "Unassigned";
break;
default:
player->team_name = "Not Known";
break;
}
player->flags |= PLAYER_FLAG_DO_NOT_FREE_TEAM;
player->deaths = pkt[1];
}
pkt += 5; /* player data*/
}
for (i = 0; i < 5; i++)
{
pkt += 8; /* team data who knows what they have in here */
}
pkt += 1;
iUseStartTimer = pkt[0]; // UseStartTimer
pkt += 1;
iTemp = flStartTimerSetPoint = swap_float_from_little(pkt);
// Start Timer Set Point
pkt += 4;
if (iUseStartTimer)
{
add_rule(server, "usestarttime", "Yes", NO_FLAGS);
add_rule(server, "starttimeset", play_time(iTemp, 2), NO_FLAGS);
}
else
{
add_rule(server, "usestarttime", "No", NO_FLAGS);
add_rule(server, "starttimeset", play_time(0, 2), NO_FLAGS);
}
if ((ServerVersion == VERSION_1_3) || // stuff added in patch 1.3
(ServerVersion == VERSION_1_4))
{
iTemp = swap_float_from_little(pkt); // Debrief Time
add_rule(server, "debrieftime", play_time(iTemp, 2), NO_FLAGS);
pkt += 4;
iTemp = swap_float_from_little(pkt); // Respawn Min
add_rule(server, "respawnmin", play_time(iTemp, 2), NO_FLAGS);
pkt += 4;
iTemp = swap_float_from_little(pkt); // Respawn Max
add_rule(server, "respawnmax", play_time(iTemp, 2), NO_FLAGS);
pkt += 4;
iTemp = swap_float_from_little(pkt); // Respawn Invulnerable
add_rule(server, "respawnsafe", play_time(iTemp, 2), NO_FLAGS);
pkt += 4;
}
else
{
add_rule(server, "debrieftime", "Undefined", NO_FLAGS);
add_rule(server, "respawnmin", "Undefined", NO_FLAGS);
add_rule(server, "respawnmax", "Undefined", NO_FLAGS);
add_rule(server, "respawnsafe", "Undefined", NO_FLAGS);
}
pkt += 4; // 4
iTemp = pkt[0]; // Spawn Count
if ((iSpawnType == 1) || (iSpawnType == 2))
/* Individual or team */
{
sprintf(str, "%u", iTemp);
}
else
/* else not used */
{
sprintf(str, "%u", 0);
}
add_rule(server, "spawncount", str, NO_FLAGS);
pkt += 1; // 5
pkt += 4; // 9
iTemp = pkt[0]; // Allow Observers
if (iTemp)
{
strcpy(str, "Yes");
}
else
/* else not used */
{
strcpy(str, "No");
}
add_rule(server, "allowobservers", str, NO_FLAGS);
pkt += 1; // 10
pkt += 3; // 13
// pkt += 13;
if (iUseStartTimer)
{
iTemp = swap_float_from_little(pkt); // Start Timer Count
add_rule(server, "startwait", play_time(iTemp, 2), NO_FLAGS);
}
else
{
add_rule(server, "startwait", play_time(0, 2), NO_FLAGS);
}
pkt += 4; //17
iTemp = pkt[0]; // IFF
switch (iTemp)
{
case 0:
strcpy(str, "None");
break;
case 1:
strcpy(str, "Reticule");
break;
case 2:
strcpy(str, "Names");
break;
default:
strcpy(str, "Unknown");
break;
}
add_rule(server, "iff", str, NO_FLAGS);
pkt += 1; // 18
iTemp = pkt[0]; // Threat Indicator
if (iTemp)
{
add_rule(server, "ti", "ON ", NO_FLAGS);
}
else
{
add_rule(server, "ti", "OFF", NO_FLAGS);
}
pkt += 1; // 19
pkt += 5; // 24
iLen = swap_long_from_little(pkt);
pkt += 4;
if ((iLen < 1) || (iLen > SHORT_GR_LEN))
{
add_rule(server, "error", "Restrictions too Long", NO_FLAGS);
return PKT_ERROR;
}
add_rule(server, "restrict", pkt, NO_FLAGS);
pkt += iLen; /* restrictions */
pkt += 23;
/*
if ( ghostrecon_debug) print_packet( pkt, GrPayloadLen);
*/
return DONE_FORCE;
}

716
reference/haze/qstat.txt Normal file
View File

@ -0,0 +1,716 @@
LICENSE: The Artistic License 2.0
/*
* qstat.h
* by Steve Jankowski
* steve@qstat.org
* http://www.qstat.org
*
* Copyright 1996,1997,1998,1999,2000,2001,2002 by Steve Jankowski
*/
#define HAZE_BASIC_INFO 0x01
#define HAZE_GAME_RULES 0x02
#define HAZE_PLAYER_INFO 0x04
#define HAZE_TEAM_INFO 0x08
// Format:
// 1 - 8: Query Request
// 9 - 12: Query Header
// 13: Query ID
// Query ID is made up of the following
// 0x01: Basic Info
// 0x02: Game Rules
// 0x03: Player Information
// 0x04: Team Information
unsigned char haze_status_query[] = {
'f', 'r', 'd', 'q', 'u', 'e', 'r', 'y',
0x10,0x20,0x30,0x40,
0x0A
};
// Format:
// 1 - 8: Query Request
// 9 - 12: Query Header
// 13: Query ID
// Query ID is made up of the following
// 0x01: Basic Info
// 0x02: Game Rules
// 0x03: Player Information
// 0x04: Team Information
unsigned char haze_player_query[] = {
'f', 'r', 'd', 'q', 'u', 'e', 'r', 'y',
0x10,0x20,0x30,0x40,
0x03
};
{
/* HAZE PROTOCOL */
HAZE_SERVER, /* id */
"HAZES", /* type_prefix */
"hazes", /* type_string */
"-hazes", /* type_option */
"Haze Protocol", /* game_name */
0, /* master */
0, /* default_port */
0, /* port_offset */
TF_SINGLE_QUERY, /* flags */
"gametype", /* game_rule */
"HAZE", /* template_var */
(char*) &haze_status_query, /* status_packet */
sizeof( haze_status_query), /* status_len */
(char*) &haze_player_query, /* player_packet */
sizeof( haze_player_query), /* player_len */
NULL, /* rule_packet */
0, /* rule_len */
NULL, /* master_packet */
0, /* master_len */
NULL, /* master_protocol */
NULL, /* master_query */
display_gs2_player_info, /* display_player_func */
display_server_rules, /* display_rule_func */
raw_display_gs2_player_info, /* display_raw_player_func */
raw_display_server_rules, /* display_raw_rule_func */
xml_display_player_info, /* display_xml_player_func */
xml_display_server_rules, /* display_xml_rule_func */
send_haze_request_packet, /* status_query_func */
NULL, /* rule_query_func */
NULL, /* player_query_func */
deal_with_haze_packet, /* packet_func */
},
/*
* qstat 2.8
* by Steve Jankowski
*
* New Haze query protocol
* Copyright 2005 Steven Hartland
*
* Licensed under the Artistic License, see LICENSE.txt for license terms
*
*/
#include <sys/types.h>
#ifndef _WIN32
#include <netinet/in.h>
#include <sys/socket.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include "debug.h"
#include "qstat.h"
#include "packet_manip.h"
// Format:
// 1 - 8: Challenge Request / Response
char haze_challenge[] = {
'f', 'r', 'd', 'c', '_', '_', '_', '_'
};
int process_haze_packet( struct qserver *server );
// Player headers
#define PLAYER_NAME_HEADER 1
#define PLAYER_SCORE_HEADER 2
#define PLAYER_DEATHS_HEADER 3
#define PLAYER_PING_HEADER 4
#define PLAYER_KILLS_HEADER 5
#define PLAYER_TEAM_HEADER 6
#define PLAYER_OTHER_HEADER 7
// Team headers
#define TEAM_NAME_HEADER 1
#define TEAM_OTHER_HEADER 2
// Challenge response algorithum
// Before sending a qr2 query (type 0x00) the client must first send a
// challenge request (type 0x09). The host will respond with the same
// packet type containing a string signed integer.
//
// Once the challenge is received the client should convert the string to a
// network byte order integer and embed it in the keys query.
//
// Example:
//
// challenge request: [0xFE][0xFD][0x09][0x.. 4-byte-instance]
// challenge response: [0x09][0x.. 4-byte-instance]["-1287574694"]
// query: [0xFE][0xFD][0x00][0x.. 4-byte-instance][0xb3412b5a "-1287574694"]
//
query_status_t deal_with_haze_packet( struct qserver *server, char *rawpkt, int pktlen )
{
char *ptr = rawpkt;
unsigned int pkt_id;
unsigned short len;
unsigned char pkt_max, pkt_index;
debug( 2, "packet..." );
if ( pktlen < 8 )
{
// invalid packet
malformed_packet( server, "too short" );
return PKT_ERROR;
}
if ( 0 == strncmp( ptr, "frdcr", 5 ) )
{
// challenge response
ptr += 8;
server->challenge = 1;
// Correct the stats due to two phase protocol
server->retry1++;
server->n_packets--;
if ( server->retry1 == n_retries || server->flags & FLAG_BROADCAST )
{
//server->n_requests--;
}
else
{
server->n_retries--;
}
return send_haze_request_packet( server );
}
if ( pktlen < 12 )
{
// invalid packet
malformed_packet( server, "too short" );
return PKT_ERROR;
}
server->n_servers++;
if ( server->server_name == NULL )
{
server->ping_total += time_delta( &packet_recv_time, &server->packet_time1 );
}
else
{
gettimeofday( &server->packet_time1, NULL);
}
// Query version ID
ptr += 4;
// Could check the header here should
// match the 4 byte id sent
memcpy( &pkt_id, ptr, 4 );
ptr += 4;
// Max plackets
pkt_max = ((unsigned char)*ptr);
ptr++;
// Packet ID
pkt_index = ((unsigned char)*ptr);
ptr++;
// Query Length
//len = (unsigned short)ptr[0] | ((unsigned short)ptr[1] << 8);
//len = swap_short_from_little( ptr );
debug( 1, "%04hx, %04hx", (unsigned short)ptr[0], ((unsigned short)ptr[1] << 8) );
//len = (unsigned short)(unsigned short)ptr[0] | ((unsigned short)ptr[1] << 8);
// TODO: fix this crap
memcpy( &len, ptr+1, 1 );
//memcpy( &len+1, ptr, 1 );
//memcpy( &len, ptr, 2 );
ptr += 2;
debug( 1, "pkt_index = %d, pkt_max = %d, len = %d", pkt_index, pkt_max, len );
if ( 0 != pkt_max )
{
// not a single packet response or callback
debug( 2, "pkt_max %d", pkt_max );
if ( 0 == pkt_index )
{
// to prevent reprocessing when we get the call back
// override the packet flag so it looks like a single
// packet response
rawpkt[8] = '\0';
}
// add the packet recalcing maxes
if ( ! add_packet( server, pkt_id, pkt_index, pkt_max, pktlen, rawpkt, 1 ) )
{
// fatal error e.g. out of memory
return MEM_ERROR;
}
// combine_packets will call us recursively
return combine_packets( server );
}
// if we get here we have what should be a full packet
return process_haze_packet( server );
}
query_status_t deal_with_haze_status( struct qserver *server, char *rawpkt, int pktlen )
{
char *pkt = rawpkt;
int len;
debug( 1, "status packet" );
// Server name
server->server_name = strdup( pkt );
pkt += strlen( pkt ) + 1;
// gametype
add_rule( server, "gametype", pkt, NO_FLAGS );
pkt += strlen( pkt ) + 1;
// map
len = strlen( pkt );
// remove .res from map names
if ( 0 == strncmp( pkt + len - 4, ".res", 4 ) )
{
*(pkt + len - 4) = '\0';
}
server->map_name = strdup( pkt );
pkt += len + 1;
// num players
server->num_players = atoi( pkt );
pkt += strlen( pkt ) + 1;
// max_players
server->max_players = atoi( pkt );
pkt += strlen( pkt ) + 1;
// hostport
change_server_port( server, atoi( pkt ), 0 );
pkt += strlen( pkt ) + 1;
return DONE_FORCE;
}
int process_haze_packet( struct qserver *server )
{
unsigned char state = 0;
unsigned char no_players = 0;
unsigned char total_players = 0;
unsigned char no_teams = 0;
unsigned char total_teams = 0;
int pkt_index = 0;
SavedData *fragment;
debug( 2, "processing packet..." );
while ( NULL != ( fragment = get_packet_fragment( pkt_index++ ) ) )
{
int pktlen = fragment->datalen;
char *ptr = fragment->data;
char *end = ptr + pktlen;
debug( 2, "processing fragment[%d]...", fragment->pkt_index );
// check we have a full header
if ( pktlen < 12 )
{
// invalid packet
malformed_packet( server, "too short" );
return PKT_ERROR;
}
// skip over the header
//server->protocol_version = atoi( val+1 );
ptr += 12;
// 4 * null's signifies the end of a section
// Basic Info
while ( 0 == state && ptr < end )
{
// name value pairs null seperated
char *var, *val;
int var_len, val_len;
if ( ptr+4 <= end && 0x00 == ptr[0] && 0x00 == ptr[1] && 0x00 == ptr[2] && 0x00 == ptr[3] )
{
// end of rules
state++;
ptr += 4;
break;
}
var = ptr;
var_len = strlen( var );
ptr += var_len + 1;
if ( ptr + 1 > end )
{
malformed_packet( server, "no basic value" );
return PKT_ERROR;
}
val = ptr;
val_len = strlen( val );
ptr += val_len + 1;
debug( 2, "var:%s (%d)=%s (%d)\n", var, var_len, val, val_len );
// Lets see what we've got
if ( 0 == strcmp( var, "serverName" ) )
{
server->server_name = strdup( val );
}
else if( 0 == strcmp( var, "map" ) )
{
// remove .res from map names
if ( 0 == strncmp( val + val_len - 4, ".res", 4 ) )
{
*(val + val_len - 4) = '\0';
}
server->map_name = strdup( val );
}
else if( 0 == strcmp( var, "maxPlayers" ) )
{
server->max_players = atoi( val );
}
else if( 0 == strcmp( var, "currentPlayers" ) )
{
server->num_players = no_players = atoi( val );
}
else
{
add_rule( server, var, val, NO_FLAGS );
}
}
// rules
while ( 1 == state && ptr < end )
{
// name value pairs null seperated
char *var, *val;
int var_len, val_len;
if ( ptr+4 <= end && 0x00 == ptr[0] && 0x00 == ptr[1] && 0x00 == ptr[2] && 0x00 == ptr[3] )
{
// end of basic
state++;
ptr += 4;
break;
}
var = ptr;
var_len = strlen( var );
ptr += var_len + 1;
if ( ptr + 1 > end )
{
malformed_packet( server, "no basic value" );
return PKT_ERROR;
}
val = ptr;
val_len = strlen( val );
ptr += val_len + 1;
debug( 2, "var:%s (%d)=%s (%d)\n", var, var_len, val, val_len );
// add the rule
add_rule( server, var, val, NO_FLAGS );
}
// players
while ( 2 == state && ptr < end )
{
// first we have the header
char *header = ptr;
int head_len = strlen( header );
ptr += head_len + 1;
if ( ptr+2 <= end && 0x00 == ptr[0] && 0x00 == ptr[1] )
{
// end of player headers
state++;
ptr += 2;
break;
}
if ( 0 == head_len )
{
// no more info
debug( 3, "All done" );
return DONE_FORCE;
}
debug( 2, "player header '%s'", header );
if ( ptr > end )
{
malformed_packet( server, "no details for header '%s'", header );
return PKT_ERROR;
}
}
while ( 3 == state && ptr < end )
{
char *header = ptr;
int head_len = strlen( header );
int header_type;
// the next byte is the starting number
total_players = *ptr++;
if ( 0 == strcmp( header, "player_" ) || 0 == strcmp( header, "name_" ) )
{
header_type = PLAYER_NAME_HEADER;
}
else if ( 0 == strcmp( header, "score_" ) )
{
header_type = PLAYER_SCORE_HEADER;
}
else if ( 0 == strcmp( header, "deaths_" ) )
{
header_type = PLAYER_DEATHS_HEADER;
}
else if ( 0 == strcmp( header, "ping_" ) )
{
header_type = PLAYER_PING_HEADER;
}
else if ( 0 == strcmp( header, "kills_" ) )
{
header_type = PLAYER_KILLS_HEADER;
}
else if ( 0 == strcmp( header, "team_" ) )
{
header_type = PLAYER_TEAM_HEADER;
}
else
{
header_type = PLAYER_OTHER_HEADER;
}
while( ptr < end )
{
// now each player details
// add the player
struct player *player;
char *val;
int val_len;
// check for end of this headers player info
if ( 0x00 == *ptr )
{
debug( 3, "end of '%s' detail", header );
ptr++;
// Note: can't check ( total_players != no_players ) here as we may have more packets
if ( ptr < end && 0x00 == *ptr )
{
debug( 3, "end of players" );
// end of all player headers / detail
state = 2;
ptr++;
}
break;
}
player = get_player_by_number( server, total_players );
if ( NULL == player )
{
player = add_player( server, total_players );
}
if ( ptr >= end )
{
malformed_packet( server, "short player detail" );
return PKT_ERROR;
}
val = ptr;
val_len = strlen( val );
ptr += val_len + 1;
debug( 2, "Player[%d][%s]=%s\n", total_players, header, val );
// lets see what we got
switch( header_type )
{
case PLAYER_NAME_HEADER:
player->name = strdup( val );
break;
case PLAYER_SCORE_HEADER:
player->score = atoi( val );
break;
case PLAYER_DEATHS_HEADER:
player->deaths = atoi( val );
break;
case PLAYER_PING_HEADER:
player->ping = atoi( val );
break;
case PLAYER_KILLS_HEADER:
player->frags = atoi( val );
break;
case PLAYER_TEAM_HEADER:
player->team = atoi( val );
break;
case PLAYER_OTHER_HEADER:
default:
if ( '_' == header[head_len-1] )
{
header[head_len-1] = '\0';
player_add_info( player, header, val, NO_FLAGS );
header[head_len-1] = '_';
}
else
{
player_add_info( player, header, val, NO_FLAGS );
}
break;
}
total_players++;
if ( total_players > no_players )
{
malformed_packet( server, "to many players %d > %d", total_players, no_players );
return PKT_ERROR;
}
}
}
if ( 3 == state )
{
no_teams = (unsigned char)*ptr;
ptr++;
debug( 2, "No teams:%d\n", no_teams );
state = 3;
}
while ( 4 == state && ptr < end )
{
// first we have the header
char *header = ptr;
int head_len = strlen( header );
int header_type;
ptr += head_len + 1;
if ( 0 == head_len )
{
// no more info
debug( 3, "All done" );
return DONE_FORCE;
}
debug( 2, "team header '%s'", header );
if ( 0 == strcmp( header, "team_t" ) )
{
header_type = TEAM_NAME_HEADER;
}
else
{
header_type = TEAM_OTHER_HEADER;
}
// the next byte is the starting number
total_teams = *ptr++;
while( ptr < end )
{
// now each teams details
char *val;
int val_len;
char rule[512];
if ( ptr >= end )
{
malformed_packet( server, "short team detail" );
return PKT_ERROR;
}
val = ptr;
val_len = strlen( val );
ptr += val_len + 1;
debug( 2, "Team[%d][%s]=%s\n", total_teams, header, val );
// lets see what we got
switch ( header_type )
{
case TEAM_NAME_HEADER:
// BF being stupid again teams 1 based instead of 0
players_set_teamname( server, total_teams + 1, val );
// N.B. yes no break
case TEAM_OTHER_HEADER:
default:
// add as a server rule
sprintf( rule, "%s%d", header, total_teams );
add_rule( server, rule, val, NO_FLAGS );
break;
}
total_teams++;
if ( 0x00 == *ptr )
{
// end of this headers teams
ptr++;
break;
}
}
}
}
return DONE_FORCE;
}
query_status_t send_haze_request_packet( struct qserver *server )
{
char *packet;
char query_buf[128];
size_t len;
unsigned char required = HAZE_BASIC_INFO;
if ( get_server_rules )
{
required |= HAZE_GAME_RULES;
server->flags |= TF_PLAYER_QUERY;
}
if ( get_player_info )
{
required |= HAZE_PLAYER_INFO;
required |= HAZE_TEAM_INFO;
server->flags |= TF_RULES_QUERY;
}
server->flags |= TF_STATUS_QUERY;
if ( server->challenge )
{
// we've recieved a challenge response, send the query + challenge id
len = sprintf(
query_buf,
"frdquery%c%c%c%c%c",
(unsigned char)(server->challenge >> 24),
(unsigned char)(server->challenge >> 16),
(unsigned char)(server->challenge >> 8),
(unsigned char)(server->challenge >> 0),
required
);
packet = query_buf;
}
else
{
// Either basic v3 protocol or challenge request
packet = haze_challenge;
len = sizeof( haze_challenge );
}
return send_packet( server, packet, len );
}

View File

404
reference/openttd/qstat.txt Normal file
View File

@ -0,0 +1,404 @@
LICENSE: The Artistic License 2.0
/*
* qstat.h
* by Steve Jankowski
* steve@qstat.org
* http://www.qstat.org
*
* Copyright 1996,1997,1998,1999,2000,2001,2002 by Steve Jankowski
*/
char ottd_serverinfo[] = {
0x03, 0x00, // packet length
0x00, // packet type
};
char ottd_serverdetails[] = {
0x03, 0x00, // packet length
0x02, // packet type
};
{
/* openTTD */
OTTD_SERVER, /* id */
"OTTDS", /* type_prefix */
"ottds", /* type_string */
"-ottds", /* type_option */
"OpenTTD", /* game_name */
0, /* master */
3979, /* default_port */
0, /* port_offset */
0, /* flags */
"", /* game_rule */
"OPENTTD", /* template_var */
(char*) &ottd_serverinfo, /* status_packet */
sizeof( ottd_serverinfo), /* status_len */
NULL, /* player_packet */
0, /* player_len */
(char*) &ottd_serverdetails,/* rule_packet */
sizeof( ottd_serverdetails), /* rule_len */
NULL, /* master_packet */
0, /* master_len */
NULL, /* master_protocol */
NULL, /* master_query */
display_q2_player_info, /* display_player_func */
display_server_rules, /* display_rule_func */
raw_display_q2_player_info, /* display_raw_player_func */
raw_display_server_rules, /* display_raw_rule_func */
xml_display_player_info,/* display_xml_player_func */
xml_display_server_rules, /* display_xml_rule_func */
send_ottd_request_packet, /* status_query_func */
NULL, /* rule_query_func */
NULL, /* player_query_func */
deal_with_ottd_packet, /* packet_func */
},
/*
* qstat 2.11
*
* opentTTD protocol
* Copyright 2007 Ludwig Nussel
*
* Licensed under the Artistic License, see LICENSE.txt for license terms
*/
#include <sys/types.h>
#ifndef _WIN32
#include <sys/socket.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include "qstat.h"
#include "qserver.h"
#include "debug.h"
enum { MAX_VEHICLE_TYPES = 5, MAX_STATION_TYPES = 5 };
static const char* vehicle_types[] = {
"num_trains",
"num_trucks",
"num_busses",
"num_aircrafts",
"num_ships",
};
static const char* station_types[] = {
"num_stations",
"num_truckbays",
"num_busstations",
"num_airports",
"num_docks",
};
query_status_t deal_with_ottdmaster_packet(struct qserver *server, char *rawpkt, int pktlen)
{
unsigned num;
server->ping_total += time_delta(&packet_recv_time, &server->packet_time1);
server->server_name = MASTER;
if(swap_short_from_little(rawpkt) != pktlen)
{
malformed_packet( server, "invalid packet length" );
return PKT_ERROR;
}
if(rawpkt[2] != 7)
{
malformed_packet( server, "invalid packet type" );
return PKT_ERROR;
}
if(rawpkt[3] != 1)
{
malformed_packet( server, "invalid packet version" );
return PKT_ERROR;
}
num = swap_short_from_little(&rawpkt[4]);
rawpkt += 6;
pktlen -= 6;
if( num && num*6 <= pktlen )
{
unsigned i;
server->master_pkt = (char*)realloc(server->master_pkt, server->master_pkt_len + pktlen );
memset(server->master_pkt + server->master_pkt_len, 0, pktlen );
server->master_pkt_len += pktlen;
for( i = 0; i < num * 6; i += 6 )
{
memcpy(&server->master_pkt[i], &rawpkt[i], 4);
server->master_pkt[i+4] = rawpkt[i+5];
server->master_pkt[i+5] = rawpkt[i+4];
}
server->n_servers += num;
}
else
{
malformed_packet( server, "invalid packet" );
return PKT_ERROR;
}
bind_sockets();
return DONE_AUTO;
}
#define xstr(s) str(s)
#define str(s) #s
#define GET_STRING do { \
str = (char*)ptr; \
ptr = memchr(ptr, '\0', end-ptr); \
if ( !ptr ) \
{ \
malformed_packet( server, "%s:%s invalid packet", __FILE__, xstr(__LINE__) ); \
return PKT_ERROR; \
} \
++ptr; \
} while(0)
#define FAIL_IF(cond, msg) \
if((cond)) { \
malformed_packet( server, "%s:%s %s", __FILE__, xstr(__LINE__), msg ); \
return PKT_ERROR; \
}
#define INVALID_IF(cond) \
FAIL_IF(cond, "invalid packet")
query_status_t deal_with_ottd_packet(struct qserver *server, char *rawpkt, int pktlen)
{
unsigned char *ptr = (unsigned char*)rawpkt;
unsigned char *end = (unsigned char*)(rawpkt + pktlen);
unsigned char type;
char* str;
char buf[32];
unsigned ver;
server->n_servers++;
if ( server->server_name == NULL)
{
server->ping_total += time_delta( &packet_recv_time, &server->packet_time1);
server->n_requests++;
}
else
{
gettimeofday( &server->packet_time1, NULL);
}
FAIL_IF(pktlen < 4 || swap_short_from_little(rawpkt) > pktlen, "invalid packet");
type = ptr[2];
ver = ptr[3];
ptr += 4;
debug(3, "len %hu type %hhu ver %hhu", swap_short_from_little(rawpkt), type, ver);
FAIL_IF(ver != 4 && ver != 5, "only version 4 and 5 servers are supported");
if(type == 1) // info packet
{
unsigned numgrf = *ptr;
FAIL_IF(ptr + numgrf * 20 + 1 > end, "invalid newgrf number");
ptr += numgrf * 20 + 1;
snprintf(buf, sizeof(buf), "%u", swap_long_from_little(ptr));
add_rule(server, "date_days", buf, NO_FLAGS);
ptr += 4;
snprintf(buf, sizeof(buf), "%u", swap_long_from_little(ptr));
add_rule(server, "startdate_days", buf, NO_FLAGS);
ptr += 4;
FAIL_IF(ptr + 3 > end, "invalid packet");
snprintf(buf, sizeof(buf), "%hhu", ptr[0]);
add_rule(server, "maxcompanies", buf, NO_FLAGS);
snprintf(buf, sizeof(buf), "%hhu", ptr[1]);
add_rule(server, "numcompanies", buf, NO_FLAGS);
server->max_spectators = ptr[2];
ptr += 3;
GET_STRING;
server->server_name = strdup(str);
GET_STRING;
add_rule(server, "version", str, NO_FLAGS);
FAIL_IF(ptr + 7 > end, "invalid packet");
{
static const char* langs[] = {
"any",
"English",
"German",
"French"
};
unsigned i = *ptr++;
if(i > 3) i = 0;
add_rule(server, "language", (char*)langs[i], NO_FLAGS);
}
add_rule(server, "password", *ptr++ ? "1" : "0", NO_FLAGS);
server->max_players = *ptr++;
server->num_players = *ptr++;
server->num_spectators = *ptr++;
GET_STRING;
server->map_name = strdup(str);
snprintf(buf, sizeof(buf), "%hu", swap_short_from_little(ptr));
add_rule(server, "map_width", buf, NO_FLAGS);
snprintf(buf, sizeof(buf), "%hu", swap_short_from_little(ptr));
add_rule(server, "map_height", buf, NO_FLAGS);
{
static const char* sets[] = {
"temperate",
"arctic",
"desert",
"toyland"
};
unsigned i = *ptr++;
if(i > 3) i = 0;
add_rule(server, "map_set", (char*)sets[i], NO_FLAGS);
}
add_rule(server, "dedicated", *ptr++ ? "1" : "0", NO_FLAGS);
}
else if(type == 3) // player packet
{
unsigned i, j;
INVALID_IF(ptr + 2 > end);
server->num_players = *ptr++;
for(i = 0; i < server->num_players; ++i)
{
unsigned long long lli;
struct player* player;
unsigned char nr;
nr = *ptr++;
debug(3, "player number %d", nr);
player = add_player(server, i);
FAIL_IF(!player, "can't allocate player");
GET_STRING;
player->name = strdup(str);
debug(3, "name %s", str);
player->frags = 0;
INVALID_IF(ptr + 4 + 3*8 + 2 + 1 + 2*MAX_VEHICLE_TYPES + 2*MAX_STATION_TYPES > end);
snprintf(buf, sizeof(buf), "%u", swap_long_from_little(ptr));
player_add_info(player, "startdate", buf, 0);
ptr += 4;
lli = swap_long_from_little(ptr+4);
lli <<= 32;
lli += swap_long_from_little(ptr);
snprintf(buf, sizeof(buf), "%lld", lli);
player_add_info(player, "value", buf, 0);
ptr += 8;
lli = swap_long_from_little(ptr+4);
lli <<= 32;
lli = swap_long_from_little(ptr);
snprintf(buf, sizeof(buf), "%lld", lli);
player_add_info(player, "money", buf, 0);
ptr += 8;
lli = swap_long_from_little(ptr+4);
lli <<= 32;
lli += swap_long_from_little(ptr);
snprintf(buf, sizeof(buf), "%lld", lli);
player_add_info(player, "income", buf, 0);
ptr += 8;
snprintf(buf, sizeof(buf), "%hu", swap_short_from_little(ptr));
player_add_info(player, "performance", buf, 0);
ptr += 2;
player_add_info(player, "password", *ptr?"1":"0", 0);
++ptr;
for (j = 0; j < MAX_VEHICLE_TYPES; ++j)
{
snprintf(buf, sizeof(buf), "%hu", swap_short_from_little(ptr));
player_add_info(player, (char*)vehicle_types[j], buf, 0);
ptr += 2;
}
for (j = 0; j < MAX_STATION_TYPES; ++j)
{
snprintf(buf, sizeof(buf), "%hu", swap_short_from_little(ptr));
player_add_info(player, (char*)station_types[j], buf, 0);
ptr += 2;
}
if (ver != 5)
{
// connections
while(ptr + 1 < end && *ptr)
{
++ptr;
GET_STRING; // client name
debug(3, "%s played by %s", str, player->name);
GET_STRING; // id
INVALID_IF(ptr + 4 > end);
ptr += 4;
}
++ptr; // record terminated by zero byte
}
}
// spectators
while(ptr + 1 < end && *ptr)
{
++ptr;
GET_STRING; // client name
debug(3, "spectator %s", str);
GET_STRING; // id
INVALID_IF(ptr + 4 > end);
ptr += 4;
}
++ptr; // record terminated by zero byte
server->next_rule = NO_SERVER_RULES; // we're done
server->next_player_info = server->num_players; // we're done
}
else
{
malformed_packet( server, "invalid type" );
return PKT_ERROR;
}
server->retry1 = n_retries; // we're done with this packet, reset retry counter
return DONE_AUTO;
}
query_status_t send_ottdmaster_request_packet(struct qserver *server)
{
return qserver_send_initial(server, server->type->master_packet, server->type->master_len);
}
query_status_t send_ottd_request_packet(struct qserver *server)
{
qserver_send_initial(server, server->type->status_packet, server->type->status_len);
if(get_server_rules || get_player_info)
{
server->next_rule = ""; // trigger calling send_a2s_rule_request_packet
}
return INPROGRESS;
}

View File

@ -0,0 +1,3 @@
Most sources say raven shield uses gamespy1, but these docs are for
some other raven shield protocol. If anyone has an issue, this may
be the actual protocol.

View File

@ -0,0 +1,446 @@
LICENSE: The Artistic License 2.0
/*
* qstat.h
* by Steve Jankowski
* steve@qstat.org
* http://www.qstat.org
*
* Copyright 1996,1997,1998,1999,2000,2001,2002 by Steve Jankowski
*/
char ravenshield_serverquery[] = "REPORT";
{
/* RAVENSHIELD PROTOCOL */
RAVENSHIELD_SERVER, /* id */
"RSS", /* type_prefix */
"rss", /* type_string */
"-rss", /* type_option */
"Ravenshield", /* game_name */
0, /* master */
RAVENSHIELD_DEFAULT_PORT, /* default_port */
1000, /* port_offset */
TF_QUERY_ARG, /* flags */
"gametype", /* game_rule */
"RAVENSHIELD", /* template_var */
(char*)ravenshield_serverquery, /* status_packet */
sizeof( ravenshield_serverquery ) - 1, /* status_len */
NULL, /* player_packet */
0, /* player_len */
NULL, /* rule_packet */
0, /* rule_len */
NULL, /* master_packet */
0, /* master_len */
NULL, /* master_protocol */
NULL, /* master_query */
display_ravenshield_player_info, /* display_player_func */
display_server_rules, /* display_rule_func */
raw_display_ravenshield_player_info, /* display_raw_player_func */
raw_display_server_rules, /* display_raw_rule_func */
xml_display_ravenshield_player_info, /* display_xml_player_func */
xml_display_server_rules, /* display_xml_rule_func */
send_qserver_request_packet, /* status_query_func */
NULL, /* rule_query_func */
NULL, /* player_query_func */
deal_with_ravenshield_packet, /* packet_func */
},
query_status_t deal_with_ravenshield_packet(struct qserver *server, char *rawpkt, int pktlen)
{
char *s, *key, *value;
debug( 2, "deal_with_ravenshield_packet %p, %d", server, pktlen );
server->n_servers++;
if (NULL == server->server_name)
{
server->ping_total += time_delta(&packet_recv_time, &server->packet_time1);
}
else
{
gettimeofday(&server->packet_time1, NULL);
}
rawpkt[pktlen] = '\0';
s = rawpkt;
while (*s)
{
// Find the seperator
while (*s && *s != '\xB6')
{
s++;
}
if (! *s)
{
// Hit the end no more
break;
}
// key start
key = ++s;
while (*s && *s != ' ')
{
s++;
}
if (*s != ' ')
{
// malformed
break;
}
*s++ = '\0';
// key end
// value start
value = s;
while (*s && *s != '\xB6')
{
s++;
}
if (*s == '\xB6')
{
*(s - 1) = '\0';
}
// Decode current key par
if (0 == strcmp("A1", key))
{
// Max players
server->max_players = atoi(value);
}
else if (0 == strcmp("A2", key))
{
// TeamKillerPenalty
add_rule(server, "TeamKillerPenalty", value, NO_FLAGS);
}
else if (0 == strcmp("B1", key))
{
// Current players
server->num_players = atoi(value);
}
else if (0 == strcmp("B2", key))
{
// AllowRadar
add_rule(server, "AllowRadar", value, NO_FLAGS);
}
else if (0 == strcmp("D2", key))
{
// Version info
add_rule(server, "Version", value, NO_FLAGS);
}
else if (0 == strcmp("E1", key))
{
// Current map
server->map_name = strdup(value);
}
else if (0 == strcmp("E2", key))
{
// Unknown
}
else if (0 == strcmp("F1", key))
{
// Game type
server->game = find_ravenshield_game(value);
add_rule(server, server->type->game_rule, server->game, NO_FLAGS);
}
else if (0 == strcmp("F2", key))
{
// Unknown
}
else if (0 == strcmp("G1", key))
{
// Password
add_rule(server, "Password", value, NO_FLAGS);
}
else if (0 == strcmp("G2", key))
{
// Query port
}
else if (0 == strcmp("H1", key))
{
// Unknown
}
else if (0 == strcmp("H2", key))
{
// Number of Terrorists
add_rule(server, "nbTerro", value, NO_FLAGS);
}
else if (0 == strcmp("I1", key))
{
// Server name
server->server_name = strdup(value);
}
else if (0 == strcmp("I2", key))
{
// Unknown
}
else if (0 == strcmp("J1", key))
{
// Game Type Order
// Not pretty ignore for now
//add_rule( server, "Game Type Order", value, NO_FLAGS );
}
else if (0 == strcmp("J2", key))
{
// RotateMap
add_rule(server, "RotateMap", value, NO_FLAGS);
}
else if (0 == strcmp("K1", key))
{
// Map Cycle
// Not pretty ignore for now
//add_rule( server, "Map Cycle", value, NO_FLAGS );
}
else if (0 == strcmp("K2", key))
{
// Force First Person Weapon
add_rule(server, "ForceFPersonWeapon", value, NO_FLAGS);
}
else if (0 == strcmp("L1", key))
{
// Players names
int player_number = 0;
char *n = value;
if (*n == '/')
{
// atleast 1 player
n++;
while (*n && *n != '\xB6')
{
char *player_name = n;
while (*n && *n != '/' && *n != '\xB6')
{
n++;
}
if (*n == '/')
{
*n++ = '\0';
}
else if (*n == '\xB6')
{
*(n - 1) = '\0';
}
if (0 != strlen(player_name))
{
struct player *player = add_player(server, player_number);
if (NULL != player)
{
player->name = strdup(player_name);
}
player_number++;
}
}
}
}
else if (0 == strcmp("L3", key))
{
// PunkBuster state
add_rule(server, "PunkBuster", value, NO_FLAGS);
}
else if (0 == strcmp("M1", key))
{
// Players times
int player_number = 0;
char *n = value;
if (*n == '/')
{
// atleast 1 player
n++;
while (*n && *n != '\xB6')
{
char *time = n;
while (*n && *n != '/' && *n != '\xB6')
{
n++;
}
if (*n == '/')
{
*n++ = '\0';
}
else if (*n == '\xB6')
{
*(n - 1) = '\0';
}
if (0 != strlen(time))
{
int mins, seconds;
if (2 == sscanf(time, "%d:%d", &mins, &seconds))
{
struct player *player = get_player_by_number(server, player_number);
if (NULL != player)
{
player->connect_time = mins * 60+seconds;
}
}
player_number++;
}
}
}
}
else if (0 == strcmp("N1", key))
{
// Players ping
int player_number = 0;
char *n = value;
if (*n == '/')
{
// atleast 1 player
n++;
while (*n && *n != '\xB6')
{
char *ping = n;
while (*n && *n != '/' && *n != '\xB6')
{
n++;
}
if (*n == '/')
{
*n++ = '\0';
}
else if (*n == '\xB6')
{
*(n - 1) = '\0';
}
if (0 != strlen(ping))
{
struct player *player = get_player_by_number(server, player_number);
if (NULL != player)
{
player->ping = atoi(ping);
} player_number++;
}
}
}
}
else if (0 == strcmp("O1", key))
{
// Players fags
int player_number = 0;
char *n = value;
if (*n == '/')
{
// atleast 1 player
n++;
while (*n && *n != '\xB6')
{
char *frags = n;
while (*n && *n != '/' && *n != '\xB6')
{
n++;
}
if (*n == '/')
{
*n++ = '\0';
}
else if (*n == '\xB6')
{
*(n - 1) = '\0';
}
if (0 != strlen(frags))
{
struct player *player = get_player_by_number(server, player_number);
if (NULL != player)
{
player->frags = atoi(frags);
} player_number++;
}
}
}
}
else if (0 == strcmp("P1", key))
{
// Game port
// Not pretty ignore for now
/*
change_server_port( server, atoi( value ), 0 );
*/
}
else if (0 == strcmp("Q1", key))
{
// RoundsPerMatch
add_rule(server, "RoundsPerMatch", value, NO_FLAGS);
}
else if (0 == strcmp("R1", key))
{
// RoundTime
add_rule(server, "RoundTime", value, NO_FLAGS);
}
else if (0 == strcmp("S1", key))
{
// BetweenRoundTime
add_rule(server, "BetweenRoundTime", value, NO_FLAGS);
}
else if (0 == strcmp("T1", key))
{
// BombTime
add_rule(server, "BombTime", value, NO_FLAGS);
}
else if (0 == strcmp("W1", key))
{
// ShowNames
add_rule(server, "ShowNames", value, NO_FLAGS);
}
else if (0 == strcmp("X1", key))
{
// InternetServer
add_rule(server, "InternetServer", value, NO_FLAGS);
}
else if (0 == strcmp("Y1", key))
{
// FriendlyFire
add_rule(server, "FriendlyFire", value, NO_FLAGS);
}
else if (0 == strcmp("Z1", key))
{
// Autobalance
add_rule(server, "Autobalance", value, NO_FLAGS);
}
}
return DONE_FORCE;
}
char *find_ravenshield_game(char *gameno)
{
switch (atoi(gameno))
{
case 8:
return strdup("Team Deathmatch");
break;
case 13:
return strdup("Deathmatch");
break;
case 14:
return strdup("Team Deathmatch");
break;
case 15:
return strdup("Bomb");
break;
case 16:
return strdup("Escort Pilot");
break;
default:
// 1.50 and above actually uses a string so
// return that
return strdup(gameno);
break;
}
}

278
reference/savage/qstat.txt Normal file
View File

@ -0,0 +1,278 @@
LICENSE: The Artistic License 2.0
/*
* qstat.h
* by Steve Jankowski
* steve@qstat.org
* http://www.qstat.org
*
* Copyright 1996,1997,1998,1999,2000,2001,2002 by Steve Jankowski
*/
unsigned char savage_serverquery[] = {
0x9e,0x4c,0x23,0x00,0x00,0xc8,0x01,0x21,0x00,0x00
};
unsigned char savage_playerquery[] = {
0x9e,0x4c,0x23,0x00,0x00,0xce,0x76,0x46,0x00,0x00
};
{
/* SAVAGE PROTOCOL */
SAVAGE_SERVER, /* id */
"SAS", /* type_prefix */
"sas", /* type_string */
"-sas", /* type_option */
"Savage", /* game_name */
0, /* master */
SAVAGE_DEFAULT_PORT, /* default_port */
0, /* port_offset */
TF_QUERY_ARG, /* flags */
"gametype", /* game_rule */
"SAVAGE", /* template_var */
(char*)savage_serverquery, /* status_packet */
sizeof( savage_serverquery ) - 1, /* status_len */
(char*)savage_playerquery, /* player_packet */
sizeof( savage_playerquery ) - 1, /* player_len */
NULL, /* rule_packet */
0, /* rule_len */
NULL, /* master_packet */
0, /* master_len */
NULL, /* master_protocol */
NULL, /* master_query */
display_savage_player_info, /* display_player_func */
display_server_rules, /* display_rule_func */
raw_display_savage_player_info, /* display_raw_player_func */
raw_display_server_rules, /* display_raw_rule_func */
xml_display_savage_player_info, /* display_xml_player_func */
xml_display_server_rules, /* display_xml_rule_func */
send_savage_request_packet, /* status_query_func */
NULL, /* rule_query_func */
NULL, /* player_query_func */
deal_with_savage_packet, /* packet_func */
},
query_status_t send_savage_request_packet(struct qserver *server)
{
int len;
char *pkt;
if (get_player_info)
{
pkt = server->type->player_packet;
len = server->type->player_len;
}
else
{
pkt = server->type->status_packet;
len = server->type->status_len;
}
return send_packet(server, pkt, len);
}
query_status_t deal_with_savage_packet(struct qserver *server, char *rawpkt, int pktlen)
{
char *s, *key, *value, *end;
debug( 2, "deal_with_savage_packet %p, %d", server, pktlen );
server->n_servers++;
if (NULL == server->server_name)
{
server->ping_total += time_delta(&packet_recv_time, &server->packet_time1);
}
else
{
gettimeofday(&server->packet_time1, NULL);
}
rawpkt[pktlen] = '\0';
end = s = rawpkt;
end += pktlen;
while (*s)
{
// Find the seperator
while (s <= end && *s != '\xFF')
{
s++;
}
if (s >= end)
{
// Hit the end no more
break;
}
// key start
key = ++s;
while (s < end && *s != '\xFE')
{
s++;
}
if (*s != '\xFE')
{
// malformed
break;
}
*s++ = '\0';
// key end
// value start
value = s;
while (s < end && *s != '\xFF')
{
s++;
}
if (*s == '\xFF')
{
*s = '\0';
}
//fprintf( stderr, "'%s' = '%s'\n", key, value );
// Decode current key par
if (0 == strcmp("cmax", key))
{
// Max players
server->max_players = atoi(value);
}
else if (0 == strcmp("cnum", key))
{
// Current players
server->num_players = atoi(value);
}
else if (0 == strcmp("bal", key))
{
// Balance
add_rule(server, "Balance", value, NO_FLAGS);
}
else if (0 == strcmp("world", key))
{
// Current map
server->map_name = strdup(value);
}
else if (0 == strcmp("gametype", key))
{
// Game type
server->game = find_savage_game(value);
add_rule(server, server->type->game_rule, server->game, NO_FLAGS);
}
else if (0 == strcmp("pure", key))
{
// Pure
add_rule(server, "Pure", value, NO_FLAGS);
}
else if (0 == strcmp("time", key))
{
// Current game time
add_rule(server, "Time", value, NO_FLAGS);
}
else if (0 == strcmp("notes", key))
{
// Notes
add_rule(server, "Notes", value, NO_FLAGS);
}
else if (0 == strcmp("needcmdr", key))
{
// Need Commander
add_rule(server, "Need Commander", value, NO_FLAGS);
}
else if (0 == strcmp("name", key))
{
// Server name
server->server_name = strdup(value);
}
else if (0 == strcmp("fw", key))
{
// Firewalled
add_rule(server, "Firewalled", value, NO_FLAGS);
}
else if (0 == strcmp("players", key))
{
// Players names
int player_number = 0;
int team_number = 1;
char *team_name, *player_name, *n;
n = team_name = value;
// team name
n++;
while (*n && *n != '\x0a')
{
n++;
}
if (*n != '\x0a')
{
// Broken data
break;
}
*n = '\0';
player_name = ++n;
while (*n)
{
while (*n && *n != '\x0a')
{
n++;
}
if (*n != '\x0a')
{
// Broken data
break;
}
*n = '\0';
n++;
if (0 == strncmp("Team ", player_name, 5))
{
team_name = player_name;
team_number++;
}
else
{
if (0 != strlen(player_name))
{
struct player *player = add_player(server, player_number);
if (NULL != player)
{
player->name = strdup(player_name);
player->team = team_number;
player->team_name = strdup(team_name);
} player_number++;
}
}
player_name = n;
}
}
*s = '\xFF';
}
return DONE_FORCE;
}
char *find_savage_game(char *gametype)
{
if (0 == strcmp("RTSS", gametype))
{
return strdup("RTSS");
}
else
{
return strdup("Unknown");
}
}

View File

@ -0,0 +1,109 @@
<?php
/**
* This file is part of GameQ.
*
* GameQ is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GameQ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Teeworlds Protocol
*
* @author Marcel Bößendörfer <m.boessendoerfer@marbis.net>
*/
class GameQ_Protocols_Teeworlds extends GameQ_Protocols {
/**
* Array of packets we want to look up.
* Each key should correspond to a defined method in this or a parent class
*
* @var array
*/
protected $packets = array(
self::PACKET_ALL => "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x67\x69\x65\x33\x05",
// 0.5 Packet (not compatible, maybe some wants to implement "Teeworldsold")
//self::PACKET_STATUS => "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFFgief",
);
/**
* Methods to be run when processing the response(s)
*
* @var array
*/
protected $process_methods = array(
"process_all"
);
/**
* Default port for this server type
*
* @var int
*/
protected $port = 8303; // Default port, used if not set when instanced
/**
* The protocol being used
*
* @var string
*/
protected $protocol = 'teeworlds';
/**
* String name of this protocol class
*
* @var string
*/
protected $name = 'teeworlds';
/**
* Longer string name of this protocol class
*
* @var string
*/
protected $name_long = "Teeworlds";
/*
* Internal methods
*/
protected function process_all() {
if(!$this->hasValidResponse(self::PACKET_ALL))
{
return array();
}
$data = $this->packets_response[self::PACKET_ALL][0];
$buf = new GameQ_Buffer($data);
$result = new GameQ_Result();
$buf->readString();
$result->add('version', $buf->readString());
$result->add('hostname', $buf->readString());
$result->add('map', $buf->readString());
$result->add('game_descr', $buf->readString());
$result->add('flags', $buf->readString()); // not use about that
$result->add('num_players', $buf->readString());
$result->add('maxplayers', $buf->readString());
$result->add('num_players_total', $buf->readString());
$result->add('maxplayers_total', $buf->readString());
// Players
while ($buf->getLength()) {
$result->addPlayer('name', $buf->readString());
$result->addPlayer('clan', $buf->readString());
$result->addPlayer('flag', $buf->readString());
$result->addPlayer('score', $buf->readString());
$result->addPlayer('team', $buf->readString());
}
return $result->fetch();
}
}

View File

@ -0,0 +1,144 @@
LICENSE: The Artistic License 2.0
/*
* qstat.h
* by Steve Jankowski
* steve@qstat.org
* http://www.qstat.org
*
* Copyright 1996,1997,1998,1999,2000,2001,2002 by Steve Jankowski
*/
char tee_serverstatus[14] = { '\x20', '\0', '\0', '\0', '\0', '\0', '\xFF', '\xFF', '\xFF', '\xFF', 'g', 'i', 'e', 'f' };
{
/* Teeworlds */
TEE_SERVER, /* id */
"TEE", /* type_prefix */
"tee", /* type_string */
"-tee", /* type_option */
"Teeworlds", /* game_name */
0, /* master */
35515, /* default_port */
0, /* port_offset */
0, /* flags */
"gametype", /* game_rule */
"TEE", /* template_var */
tee_serverstatus, /* status_packet */
sizeof(tee_serverstatus), /* status_len */
NULL, /* player_packet */
0, /* player_len */
NULL, /* rule_packet */
0, /* rule_len */
NULL, /* master_packet */
0, /* master_len */
NULL, /* master_protocol */
NULL, /* master_query */
display_tee_player_info, /* display_player_func */
display_server_rules, /* display_rule_func */
raw_display_tee_player_info, /* display_raw_player_func */
raw_display_server_rules, /* display_raw_rule_func */
xml_display_tee_player_info, /* display_xml_player_func */
xml_display_server_rules, /* display_xml_rule_func */
send_tee_request_packet, /* status_query_func */
NULL, /* rule_query_func */
NULL, /* player_query_func */
deal_with_tee_packet, /* packet_func */
},
/*
* qstat 2.11
* by Steve Jankowski
*
* Teeworlds protocol
* Copyright 2008 ? Emiliano Leporati
*
* Licensed under the Artistic License, see LICENSE.txt for license terms
*
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "debug.h"
#include "qstat.h"
#include "packet_manip.h"
char tee_serverinfo[8] = { '\xFF', '\xFF', '\xFF', '\xFF', 'i', 'n', 'f', 'o' };
query_status_t send_tee_request_packet( struct qserver *server )
{
return send_packet( server, server->type->status_packet, server->type->status_len );
}
query_status_t deal_with_tee_packet( struct qserver *server, char *rawpkt, int pktlen )
{
// skip unimplemented ack, crc, etc
char *pkt = rawpkt + 6;
char *tok = NULL, *version = NULL;
int i;
struct player* player;
server->ping_total += time_delta(&packet_recv_time, &server->packet_time1);
if (0 == memcmp( pkt, tee_serverinfo, 8))
{
pkt += 8;
// version
version = strdup(pkt); pkt += strlen(pkt) + 1;
// server name
server->server_name = strdup(pkt); pkt += strlen(pkt) + 1;
// map name
server->map_name = strdup(pkt); pkt += strlen(pkt) + 1;
// game type
switch(atoi(pkt)) {
case 0:
add_rule( server, server->type->game_rule, "dm", NO_FLAGS);
break;
case 1:
add_rule( server, server->type->game_rule, "tdm", NO_FLAGS);
break;
case 2:
add_rule( server, server->type->game_rule, "ctf", NO_FLAGS);
break;
default:
add_rule( server, server->type->game_rule, "unknown", NO_FLAGS);
break;
}
pkt += strlen(pkt) + 1;
pkt += strlen(pkt) + 1;
pkt += strlen(pkt) + 1;
// num players
server->num_players = atoi(pkt); pkt += strlen(pkt) + 1;
// max players
server->max_players = atoi(pkt); pkt += strlen(pkt) + 1;
// players
for(i = 0; i < server->num_players; i++)
{
player = add_player( server, i );
player->name = strdup(pkt); pkt += strlen(pkt) + 1;
player->score = atoi(pkt); pkt += strlen(pkt) + 1;
}
// version reprise
server->protocol_version = 0;
if (NULL == (tok = strtok(version, "."))) return -1;
server->protocol_version |= (atoi(tok) & 0x000F) << 12;
if (NULL == (tok = strtok(NULL, "."))) return -1;
server->protocol_version |= (atoi(tok) & 0x000F) << 8;
if (NULL == (tok = strtok(NULL, "."))) return -1;
server->protocol_version |= (atoi(tok) & 0x00FF);
free(version);
return DONE_FORCE;
}
// unknown packet type
return PKT_ERROR;
}

247
reference/tribes/qstat.txt Normal file
View File

@ -0,0 +1,247 @@
LICENSE: The Artistic License 2.0
/*
* qstat.h
* by Steve Jankowski
* steve@qstat.org
* http://www.qstat.org
*
* Copyright 1996,1997,1998,1999,2000,2001,2002 by Steve Jankowski
*/
/* TRIBES */
char tribes_info[] = { '`', '*', '*' };
char tribes_players[] = { 'b', '*', '*' };
/* This is what the game sends to get minimal status
{ '\020', '\03', '\377', 0, (unsigned char)0xc5, 6 };
*/
char tribes_info_reponse[] = { 'a', '*', '*', 'b' };
char tribes_players_reponse[] = { 'c', '*', '*', 'b' };
char tribes_masterquery[] = { 0x10, 0x3, '\377', 0, 0x2 };
char tribes_master_response[] = { 0x10, 0x6 };
{
/* TRIBES */
TRIBES_SERVER, /* id */
"TBS", /* type_prefix */
"tbs", /* type_string */
"-tbs", /* type_option */
"Tribes", /* game_name */
0, /* master */
TRIBES_DEFAULT_PORT, /* default_port */
0, /* port_offset */
TF_SINGLE_QUERY, /* flags */
"game", /* game_rule */
"TRIBES", /* template_var */
(char*) &tribes_info, /* status_packet */
sizeof( tribes_info), /* status_len */
(char*) &tribes_players, /* player_packet */
sizeof( tribes_players), /* player_len */
(char*) &tribes_players, /* rule_packet */
sizeof( tribes_players), /* rule_len */
NULL, /* master_packet */
0, /* master_len */
NULL, /* master_protocol */
NULL, /* master_query */
display_tribes_player_info, /* display_player_func */
display_server_rules, /* display_rule_func */
raw_display_tribes_player_info, /* display_raw_player_func */
raw_display_server_rules, /* display_raw_rule_func */
xml_display_tribes_player_info, /* display_xml_player_func */
xml_display_server_rules, /* display_xml_rule_func */
send_tribes_request_packet, /* status_query_func */
NULL, /* rule_query_func */
NULL, /* player_query_func */
deal_with_tribes_packet, /* packet_func */
},
query_status_t deal_with_tribes_packet(struct qserver *server, char *rawpkt, int pktlen)
{
unsigned char *pkt, *end;
int len, pnum, ping, packet_loss, n_teams, t;
struct player *player;
struct player **teams = NULL;
struct player **last_player = &server->players;
char buf[24];
debug( 2, "deal_with_tribes_packet %p, %d", server, pktlen );
if (server->server_name == NULL)
{
server->ping_total += time_delta(&packet_recv_time, &server->packet_time1);
}
else
{
gettimeofday(&server->packet_time1, NULL);
}
if (pktlen < sizeof(tribes_info_reponse))
{
return PKT_ERROR;
}
if (strncmp(rawpkt, tribes_players_reponse, sizeof(tribes_players_reponse)) != 0)
{
return PKT_ERROR;
}
pkt = (unsigned char*) &rawpkt[sizeof(tribes_info_reponse)];
len = *pkt; /* game name: "Tribes" */
add_nrule(server, "gamename", (char*)pkt + 1, len);
pkt += len + 1;
len = *pkt; /* version */
add_nrule(server, "version", (char*)pkt + 1, len);
pkt += len + 1;
len = *pkt; /* server name */
server->server_name = strndup((char*)pkt + 1, len);
pkt += len + 1;
add_rule(server, "dedicated", *pkt ? "1" : "0", NO_FLAGS);
pkt++; /* flag: dedicated server */
add_rule(server, "needpass", *pkt ? "1" : "0", NO_FLAGS);
pkt++; /* flag: password on server */
server->num_players = *pkt++;
server->max_players = *pkt++;
sprintf(buf, "%u", (unsigned int)pkt[0] + (unsigned int)pkt[1] *256);
add_rule(server, "cpu", buf, NO_FLAGS);
pkt++; /* cpu speed, lsb */
pkt++; /* cpu speed, msb */
len = *pkt; /* Mod (game) */
add_nrule(server, "mods", (char*)pkt + 1, len);
pkt += len + 1;
len = *pkt; /* game (mission): "C&H" */
add_nrule(server, "game", (char*)pkt + 1, len);
pkt += len + 1;
len = *pkt; /* Mission (map) */
server->map_name = strndup((char*)pkt + 1, len);
pkt += len + 1;
len = *pkt; /* description (contains Admin: and Email: ) */
debug( 2, "%.*s\n", len, pkt + 1);
pkt += len + 1;
n_teams = *pkt++; /* number of teams */
if (n_teams == 255)
{
return PKT_ERROR;
}
sprintf(buf, "%d", n_teams);
add_rule(server, "numteams", buf, NO_FLAGS);
len = *pkt; /* first title */
debug( 2, "%.*s\n", len, pkt + 1);
pkt += len + 1;
len = *pkt; /* second title */
debug( 2, "%.*s\n", len, pkt + 1);
pkt += len + 1;
if (n_teams > 1)
{
teams = (struct player **)calloc(1, sizeof(struct player*) * n_teams);
for (t = 0; t < n_teams; t++)
{
teams[t] = (struct player*)calloc(1, sizeof(struct player));
teams[t]->number = TRIBES_TEAM;
teams[t]->team = t;
len = *pkt; /* team name */
teams[t]->name = strndup((char*)pkt + 1, len);
debug( 2, "team#0 <%.*s>\n", len, pkt + 1);
pkt += len + 1;
len = *pkt; /* team score */
if (len > 2)
{
strncpy(buf, (char*)pkt + 1+3, len - 3);
buf[len - 3] = '\0';
}
else
{
debug( 2, "%s score len %d\n", server->arg, len);
buf[0] = '\0';
}
teams[t]->frags = atoi(buf);
debug( 2, "team#0 <%.*s>\n", len - 3, pkt + 1+3);
pkt += len + 1;
}
}
else
{
len = *pkt; /* DM team? */
debug( 2, "%.*s\n", len, pkt + 1);
pkt += len + 1;
pkt++;
n_teams = 0;
}
pnum = 0;
while ((char*)pkt < (rawpkt + pktlen))
{
ping = (unsigned int) *pkt << 2;
pkt++;
packet_loss = *pkt;
pkt++;
debug( 2, "player#%d, team #%d\n", pnum, (int) *pkt);
pkt++;
len = *pkt;
if ((char*)pkt + len > (rawpkt + pktlen))
{
break;
}
player = (struct player*)calloc(1, sizeof(struct player));
player->team = pkt[ - 1];
if (n_teams && player->team < n_teams)
{
player->team_name = teams[player->team]->name;
}
else if (player->team == 255 && n_teams)
{
player->team_name = "Unknown";
}
player->flags |= PLAYER_FLAG_DO_NOT_FREE_TEAM;
player->ping = ping;
player->packet_loss = packet_loss;
player->name = strndup((char*)pkt + 1, len);
debug( 2, "player#%d, name %.*s\n", pnum, len, pkt + 1);
pkt += len + 1;
len = *pkt;
debug( 2, "player#%d, info <%.*s>\n", pnum, len, pkt + 1);
end = (unsigned char*)strchr((char*)pkt + 9, 0x9);
if (end)
{
strncpy(buf, (char*)pkt + 9, end - (pkt + 9));
buf[end - (pkt + 9)] = '\0';
player->frags = atoi(buf);
debug( 2, "player#%d, score <%.*s>\n", pnum, (unsigned)(end - (pkt + 9)), pkt + 9);
}
*last_player = player;
last_player = &player->next;
pkt += len + 1;
pnum++;
}
for (t = n_teams; t;)
{
t--;
teams[t]->next = server->players;
server->players = teams[t];
}
free(teams);
return DONE_AUTO;
}

455
reference/tribes2/qstat.txt Normal file
View File

@ -0,0 +1,455 @@
LICENSE: The Artistic License 2.0
/*
* qstat.h
* by Steve Jankowski
* steve@qstat.org
* http://www.qstat.org
*
* Copyright 1996,1997,1998,1999,2000,2001,2002 by Steve Jankowski
*/
/* TRIBES 2 */
#define TRIBES2_QUERY_GAME_TYPES 2
#define TRIBES2_QUERY_MASTER 6
#define TRIBES2_QUERY_PING 14
#define TRIBES2_QUERY_INFO 18
#define TRIBES2_RESPONSE_GAME_TYPES 4
#define TRIBES2_RESPONSE_MASTER 8
#define TRIBES2_RESPONSE_PING 16
#define TRIBES2_RESPONSE_INFO 20
#define TRIBES2_NO_COMPRESS 2
#define TRIBES2_DEFAULT_PACKET_INDEX 255
#define TRIBES2_STATUS_DEDICATED (1<<0)
#define TRIBES2_STATUS_PASSWORD (1<<1)
#define TRIBES2_STATUS_LINUX (1<<2)
#define TRIBES2_STATUS_TOURNAMENT (1<<3)
#define TRIBES2_STATUS_NOALIAS (1<<4)
#define TRIBES2_STATUS_TEAMDAMAGE (1<<5)
#define TRIBES2_STATUS_TOURNAMENT_VER3 (1<<6)
#define TRIBES2_STATUS_NOALIAS_VER3 (1<<7)
char tribes2_game_types_request[] = { TRIBES2_QUERY_GAME_TYPES, 0, 1,2,3,4 };
char tribes2_ping[] = { TRIBES2_QUERY_PING, TRIBES2_NO_COMPRESS, 1,2,3,4 };
char tribes2_info[] = { TRIBES2_QUERY_INFO, TRIBES2_NO_COMPRESS, 1,2,3,4 };
unsigned char tribes2_masterquery[] = {
TRIBES2_QUERY_MASTER, 128, /* <= build 22228, this was 0 */
11,12,13,14,
255,
3, 'a', 'n', 'y',
3, 'a', 'n', 'y',
0, 255, /* min/max players */
0xff, 0xff, 0xff, 0xff, /* region mask */
0, 0, 0, 0, /* build version */
0, /* status */
255, /* max bots */
0, 0, /* min cpu */
0 /* # buddies */
};
#define TRIBES2_ID_OFFSET 2
{
/* TRIBES 2 */
TRIBES2_SERVER, /* id */
"T2S", /* type_prefix */
"t2s", /* type_string */
"-t2s", /* type_option */
"Tribes 2", /* game_name */
0, /* master */
TRIBES2_DEFAULT_PORT, /* default_port */
0, /* port_offset */
0, /* flags */
"game", /* game_rule */
"TRIBES2", /* template_var */
(char*) &tribes2_ping, /* status_packet */
sizeof( tribes2_ping), /* status_len */
(char*) &tribes2_info, /* player_packet */
sizeof( tribes2_info), /* player_len */
(char*) NULL, /* rule_packet */
0, /* rule_len */
NULL, /* master_packet */
0, /* master_len */
NULL, /* master_protocol */
NULL, /* master_query */
display_tribes2_player_info, /* display_player_func */
display_server_rules, /* display_rule_func */
raw_display_tribes2_player_info, /* display_raw_player_func */
raw_display_server_rules, /* display_raw_rule_func */
xml_display_tribes2_player_info, /* display_xml_player_func */
xml_display_server_rules, /* display_xml_rule_func */
send_tribes2_request_packet, /* status_query_func */
NULL, /* rule_query_func */
NULL, /* player_query_func */
deal_with_tribes2_packet, /* packet_func */
},
query_status_t send_tribes2_request_packet(struct qserver *server)
{
int rc;
if (server->flags &FLAG_BROADCAST && server->server_name == NULL)
{
rc = send_broadcast(server, server->type->status_packet, server->type->status_len);
}
else if (server->server_name == NULL)
{
rc = send(server->fd, server->type->status_packet, server->type->status_len, 0);
}
else
{
rc = send(server->fd, server->type->player_packet, server->type->player_len, 0);
}
if (rc == SOCKET_ERROR)
{
return send_error( server, rc );
}
register_send(server);
return rc;
}
void get_tribes2_player_type(struct player *player)
{
char *name = player->name;
for (; *name; name++)
{
switch (*name)
{
case 0x8:
player->type_flag = PLAYER_TYPE_NORMAL;
continue;
case 0xc:
player->type_flag = PLAYER_TYPE_ALIAS;
continue;
case 0xe:
player->type_flag = PLAYER_TYPE_BOT;
continue;
case 0xb:
break;
default:
continue;
}
name++;
if (isprint(*name))
{
char *n = name;
for (; isprint(*n); n++)
;
player->tribe_tag = strndup(name, n - name);
name = n;
}
if (! *name)
{
break;
}
}
}
query_status_t deal_with_tribes2_packet(struct qserver *server, char *pkt, int pktlen)
{
char str[256], *pktstart = pkt, *term, *start;
unsigned int minimum_net_protocol, build_version, i, t, len, s, status;
unsigned int net_protocol;
unsigned short cpu_speed;
int n_teams = 0, n_players;
struct player **teams = NULL, *player;
struct player **last_player = &server->players;
int query_version;
debug( 2, "deal_with_tribes2_packet %p, %d", server, pktlen );
pkt[pktlen] = '\0';
if (server->server_name == NULL)
{
server->ping_total += time_delta(&packet_recv_time, &server->packet_time1);
}
/*
else
gettimeofday( &server->packet_time1, NULL);
*/
if (pkt[0] == TRIBES2_RESPONSE_PING)
{
if (pkt[6] < 4 || pkt[6] > 12 || strncmp(pkt + 7, "VER", 3) != 0)
{
return PKT_ERROR;
}
strncpy(str, pkt + 10, pkt[6] - 3);
str[pkt[6] - 3] = '\0';
query_version = atoi(str);
add_nrule(server, "queryversion", pkt + 7, pkt[6]);
pkt += 7+pkt[6];
server->protocol_version = query_version;
if (query_version != 3 && query_version != 5)
{
server->server_name = strdup("Unknown query version");
return PKT_ERROR;
}
if (query_version == 5)
{
net_protocol = swap_long_from_little(pkt);
sprintf(str, "%u", net_protocol);
add_rule(server, "net_protocol", str, NO_FLAGS);
pkt += 4;
}
minimum_net_protocol = swap_long_from_little(pkt);
sprintf(str, "%u", minimum_net_protocol);
add_rule(server, "minimum_net_protocol", str, NO_FLAGS);
pkt += 4;
build_version = swap_long_from_little(pkt);
sprintf(str, "%u", build_version);
add_rule(server, "build_version", str, NO_FLAGS);
pkt += 4;
server->server_name = strndup(pkt + 1, *(unsigned char*)(pkt));
/* Always send the player request because the ping packet
* contains very little information */
send_player_request_packet(server);
return 0;
}
else if (pkt[0] != TRIBES2_RESPONSE_INFO)
{
return PKT_ERROR;
}
pkt += 6;
for (i = 0; i < *(unsigned char*)pkt; i++)
if (!isprint(pkt[i + 1]))
{
return PKT_ERROR;
}
add_nrule(server, server->type->game_rule, pkt + 1, *(unsigned char*)pkt);
server->game = strndup(pkt + 1, *(unsigned char*)pkt);
pkt += *pkt + 1;
add_nrule(server, "mission", pkt + 1, *(unsigned char*)pkt);
pkt += *pkt + 1;
server->map_name = strndup(pkt + 1, *(unsigned char*)pkt);
pkt += *pkt + 1;
status = *(unsigned char*)pkt;
sprintf(str, "%u", status);
add_rule(server, "status", str, NO_FLAGS);
if (status &TRIBES2_STATUS_DEDICATED)
{
add_rule(server, "dedicated", "1", NO_FLAGS);
}
if (status &TRIBES2_STATUS_PASSWORD)
{
add_rule(server, "password", "1", NO_FLAGS);
}
if (status &TRIBES2_STATUS_LINUX)
{
add_rule(server, "linux", "1", NO_FLAGS);
}
if (status &TRIBES2_STATUS_TEAMDAMAGE)
{
add_rule(server, "teamdamage", "1", NO_FLAGS);
}
if (server->protocol_version == 3)
{
if (status &TRIBES2_STATUS_TOURNAMENT_VER3)
{
add_rule(server, "tournament", "1", NO_FLAGS);
}
if (status &TRIBES2_STATUS_NOALIAS_VER3)
{
add_rule(server, "no_aliases", "1", NO_FLAGS);
}
}
else
{
if (status &TRIBES2_STATUS_TOURNAMENT)
{
add_rule(server, "tournament", "1", NO_FLAGS);
}
if (status &TRIBES2_STATUS_NOALIAS)
{
add_rule(server, "no_aliases", "1", NO_FLAGS);
}
}
pkt++;
server->num_players = *(unsigned char*)pkt;
pkt++;
server->max_players = *(unsigned char*)pkt;
pkt++;
sprintf(str, "%u", *(unsigned char*)pkt);
add_rule(server, "bot_count", str, NO_FLAGS);
pkt++;
cpu_speed = swap_short_from_little(pkt);
sprintf(str, "%hu", cpu_speed);
add_rule(server, "cpu_speed", str, NO_FLAGS);
pkt += 2;
if (strcmp(server->server_name, "VER3") == 0)
{
free(server->server_name);
server->server_name = strndup(pkt + 1, *(unsigned char*)pkt);
}
else
{
add_nrule(server, "info", pkt + 1, *(unsigned char*)pkt);
}
pkt += *(unsigned char*)pkt + 1;
len = swap_short_from_little(pkt);
pkt += 2;
start = pkt;
if (len + (pkt - pktstart) > pktlen)
{
len -= (len + (pkt - pktstart)) - pktlen;
}
if (len == 0 || pkt - pktstart >= pktlen)
{
goto info_done;
}
term = strchr(pkt, 0xa);
if (!term)
{
goto info_done;
}
*term = '\0';
n_teams = atoi(pkt);
sprintf(str, "%d", n_teams);
add_rule(server, "numteams", str, NO_FLAGS);
pkt = term + 1;
if (pkt - pktstart >= pktlen)
{
goto info_done;
}
teams = (struct player **)calloc(1, sizeof(struct player*) * n_teams);
for (t = 0; t < n_teams; t++)
{
teams[t] = (struct player*)calloc(1, sizeof(struct player));
teams[t]->number = TRIBES_TEAM;
teams[t]->team = t;
/* team name */
term = strchr(pkt, 0x9);
if (!term)
{
n_teams = t;
goto info_done;
} teams[t]->name = strndup(pkt, term - pkt);
pkt = term + 1;
term = strchr(pkt, 0xa);
if (!term)
{
n_teams = t;
goto info_done;
}
*term = '\0';
teams[t]->frags = atoi(pkt);
pkt = term + 1;
if (pkt - pktstart >= pktlen)
{
goto info_done;
}
}
term = strchr(pkt, 0xa);
if (!term || term - start >= len)
{
goto info_done;
}
*term = '\0';
n_players = atoi(pkt);
pkt = term + 1;
for (i = 0; i < n_players && pkt - start < len; i++)
{
pkt++; /* skip first byte (0x10) */
if (pkt - start >= len)
{
break;
}
player = (struct player*)calloc(1, sizeof(struct player));
term = strchr(pkt, 0x11);
if (!term || term - start >= len)
{
free(player);
break;
} player->name = strndup(pkt, term - pkt);
get_tribes2_player_type(player);
pkt = term + 1;
pkt++; /* skip 0x9 */
if (pkt - start >= len)
{
break;
}
term = strchr(pkt, 0x9);
if (!term || term - start >= len)
{
free(player->name);
free(player);
break;
}
for (t = 0; t < n_teams; t++)
{
if (term - pkt == strlen(teams[t]->name) && strncmp(pkt, teams[t]->name, term - pkt) == 0)
{
break;
}
}
if (t == n_teams)
{
player->team = - 1;
player->team_name = "Unassigned";
}
else
{
player->team = t;
player->team_name = teams[t]->name;
}
player->flags |= PLAYER_FLAG_DO_NOT_FREE_TEAM;
pkt = term + 1;
for (s = 0; *pkt != 0xa && pkt - start < len; pkt++)
{
str[s++] = *pkt;
}
str[s] = '\0';
player->frags = atoi(str);
if (*pkt == 0xa)
{
pkt++;
}
*last_player = player;
last_player = &player->next;
}
info_done:
for (t = n_teams; t;)
{
t--;
teams[t]->next = server->players;
server->players = teams[t];
}
if (teams)
{
free(teams);
}
return DONE_FORCE;
}

View File

@ -0,0 +1,241 @@
LICENSE: The Artistic License 2.0
/*
* qstat.h
* by Steve Jankowski
* steve@qstat.org
* http://www.qstat.org
*
* Copyright 1996,1997,1998,1999,2000,2001,2002 by Steve Jankowski
*/
{
/* World in Confict PROTOCOL */
WIC_PROTOCOL_SERVER, /* id */
"WICS", /* type_prefix */
"wics", /* type_string */
"-wics", /* type_option */
"World in Conflict", /* game_name */
0, /* master */
0, /* default_port */
0, /* port_offset */
TF_TCP_CONNECT|TF_QUERY_ARG_REQUIRED|TF_QUERY_ARG, /* flags */
"N/A", /* game_rule */
"WICPROTOCOL", /* template_var */
NULL, /* status_packet */
0, /* status_len */
NULL, /* player_packet */
0, /* player_len */
NULL, /* rule_packet */
0, /* rule_len */
NULL, /* master_packet */
0, /* master_len */
NULL, /* master_protocol */
NULL, /* master_query */
display_wic_player_info, /* display_player_func */
display_server_rules, /* display_rule_func */
raw_display_wic_player_info, /* display_raw_player_func */
raw_display_server_rules, /* display_raw_rule_func */
xml_display_wic_player_info, /* display_xml_player_func */
xml_display_server_rules, /* display_xml_rule_func */
send_wic_request_packet, /* status_query_func */
NULL, /* rule_query_func */
NULL, /* player_query_func */
deal_with_wic_packet, /* packet_func */
},
/*
* qstat 2.8
* by Steve Jankowski
*
* World in Conflict Protocol
* Copyright 2007 Steven Hartland
*
* Licensed under the Artistic License, see LICENSE.txt for license terms
*
*/
#include <sys/types.h>
#ifndef _WIN32
#include <sys/socket.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include "debug.h"
#include "qstat.h"
#include "packet_manip.h"
query_status_t send_wic_request_packet( struct qserver *server )
{
char buf[256];
int serverport = get_param_i_value( server, "port", 0 );
char *password = get_param_value( server, "password", "N/A" );
change_server_port( server, serverport, 1 );
if ( get_player_info )
{
server->flags |= TF_PLAYER_QUERY|TF_RULES_QUERY;
sprintf( buf, "%s\x0d\x0a/listsettings\x0d\x0a/listplayers\x0d\x0a/exit\x0d\x0a", password );
server->saved_data.pkt_index = 2;
}
else
{
server->flags |= TF_STATUS_QUERY;
sprintf( buf, "%s\x0d\x0a/listsettings\x0d\x0a/exit\x0d\x0a", password );
server->saved_data.pkt_index = 1;
}
return send_packet( server, buf, strlen( buf ) );
}
query_status_t deal_with_wic_packet( struct qserver *server, char *rawpkt, int pktlen )
{
char *s, *end, *team = NULL;
int mode = server->n_servers, slot, score;
char name[256], role[256];
debug( 2, "processing n_requests %d, retry1 %d, n_retries %d, delta %d", server->n_requests, server->retry1, n_retries, time_delta( &packet_recv_time, &server->packet_time1 ) );
server->ping_total += time_delta( &packet_recv_time, &server->packet_time1 );
server->n_requests++;
if ( 0 == pktlen )
{
// Invalid password
return REQ_ERROR;
}
gettimeofday( &server->packet_time1, NULL);
rawpkt[pktlen]= '\0';
end = &rawpkt[pktlen];
s = rawpkt;
while ( NULL != s )
{
int len = strlen( s );
*(s + len - 2) = '\0'; // strip off \x0D\x0A
debug( 2, "Line[%d]: %s", mode, s );
if ( 0 == mode )
{
// Settings
// TODO: make parse safe
if ( 0 == strncmp( s, "Settings: ", 9 ) )
{
// Server Rule
char *key = s + 10;
char *value = strchr( key, ' ' );
*value = '\0';
value++;
debug( 2, "key: '%s' = '%s'", key, value );
if ( 0 == strcmp( "myGameName", key ) )
{
server->server_name = strdup( value );
}
else if ( 0 == strcmp( "myMapFilename", key ) )
{
server->map_name = strdup( value );
}
else if ( 0 == strcmp( "myMaxPlayers", key ) )
{
server->max_players = atoi( value );
}
else if ( 0 == strcmp( "myCurrentNumberOfPlayers", key ) )
{
server->num_players = atoi( value);
}
else
{
add_rule( server, key, value, NO_FLAGS );
}
}
else if ( 0 == strcmp( "Listing players", s ) )
{
// end of rules request
server->saved_data.pkt_index--;
mode++;
}
else if ( 0 == strcmp( "Exit confirmed.", s ) )
{
server->n_servers = mode;
return DONE_FORCE;
}
}
else if ( 1 == mode )
{
// Player info
if ( 0 == strncmp( s, "Team: ", 6 ) )
{
team = s + 6;
debug( 2, "Team: %s", team );
}
else if ( 4 == sscanf( s, "Slot: %d Role: %s Score: %d Name: %255[^\x0d\x0a]", &slot, role, &score, name ) )
{
// Player info
struct player *player = add_player( server, server->n_player_info );
if ( NULL != player )
{
player->flags |= PLAYER_FLAG_DO_NOT_FREE_TEAM;
player->name = strdup( name );
player->score = score;
player->team_name = team;
player->tribe_tag = strdup( role );
// Indicate if its a bot
player->type_flag = ( 0 == strcmp( name, "Computer: Balanced" ) ) ? 1 : 0;
}
debug( 2, "player %d, role %s, score %d, name %s", slot, role, score, name );
}
else if ( 3 == sscanf( s, "Slot: %d Role: Score: %d Name: %255[^\x0d\x0a]", &slot, &score, name ) )
{
// Player info
struct player *player = add_player( server, server->n_player_info );
if ( NULL != player )
{
player->flags |= PLAYER_FLAG_DO_NOT_FREE_TEAM;
player->name = strdup( name );
player->score = score;
player->team_name = team;
// Indicate if its a bot
player->type_flag = ( 0 == strcmp( name, "Computer: Balanced" ) ) ? 1 : 0;
}
debug( 2, "player %d, score %d, name %s", slot, score, name );
}
else if ( 0 == strcmp( "Exit confirmed.", s ) )
{
server->n_servers = mode;
return DONE_FORCE;
}
}
s += len;
if ( s + 1 < end )
{
s++; // next line
}
else
{
s = NULL;
}
}
server->n_servers = mode;
if ( 0 == server->saved_data.pkt_index )
{
server->map_name = strdup( "N/A" );
return DONE_FORCE;
}
return INPROGRESS;
}