diff --git a/reference/bfris/gamedignote.txt b/reference/bfris/gamedignote.txt new file mode 100644 index 0000000..6791d04 --- /dev/null +++ b/reference/bfris/gamedignote.txt @@ -0,0 +1,2 @@ +gamedig note: +connect over TCP, and the data just starts coming, no packet needs sent? diff --git a/reference/bfris/qstat.txt b/reference/bfris/qstat.txt new file mode 100644 index 0000000..bce1b72 --- /dev/null +++ b/reference/bfris/qstat.txt @@ -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; +} + + diff --git a/reference/cryengine/gamedignote.txt b/reference/cryengine/gamedignote.txt new file mode 100644 index 0000000..b3648f9 --- /dev/null +++ b/reference/cryengine/gamedignote.txt @@ -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. diff --git a/reference/cryengine/gameq.txt b/reference/cryengine/gameq.txt index cd4f003..5c5dcab 100644 --- a/reference/cryengine/gameq.txt +++ b/reference/cryengine/gameq.txt @@ -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: - - +#ifndef _WIN32 +#include +#include +#include +#else +#include +#endif +#include +#include +#include + +#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, """, "\"" ); + return str_replace( val, "&", "&" ); +} + +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%s", 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, "", len ); + if ( NULL == s ) + { + // not valid response + return NULL; + } + s += 46; + len += rawpkt - s; + e = strnstr(s, "", 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; +} + + + diff --git a/reference/cube/qstat_sauerbraten.txt b/reference/cube/qstat_sauerbraten.txt new file mode 100644 index 0000000..637137c --- /dev/null +++ b/reference/cube/qstat_sauerbraten.txt @@ -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 +#include +#include + +#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; +} + + + diff --git a/reference/descent3/gamedignote.txt b/reference/descent3/gamedignote.txt new file mode 100644 index 0000000..5ed3d07 --- /dev/null +++ b/reference/descent3/gamedignote.txt @@ -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. diff --git a/reference/descent3/qstat.txt b/reference/descent3/qstat.txt new file mode 100644 index 0000000..1b060e4 --- /dev/null +++ b/reference/descent3/qstat.txt @@ -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; +} + diff --git a/reference/farcry/gamedignote.txt b/reference/farcry/gamedignote.txt new file mode 100644 index 0000000..dfc5198 --- /dev/null +++ b/reference/farcry/gamedignote.txt @@ -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. diff --git a/reference/farcry/qstat.txt b/reference/farcry/qstat.txt new file mode 100644 index 0000000..58f244c --- /dev/null +++ b/reference/farcry/qstat.txt @@ -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; +} + diff --git a/reference/ghostrecon/qstat.txt b/reference/ghostrecon/qstat.txt new file mode 100644 index 0000000..d0fbfd8 --- /dev/null +++ b/reference/ghostrecon/qstat.txt @@ -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; +} diff --git a/reference/haze/qstat.txt b/reference/haze/qstat.txt new file mode 100644 index 0000000..e037b5f --- /dev/null +++ b/reference/haze/qstat.txt @@ -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 +#ifndef _WIN32 +#include +#include +#endif +#include +#include + +#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 ); +} + diff --git a/reference/hexen2/qstat.txt b/reference/hexen2/qstat.txt new file mode 100644 index 0000000..e69de29 diff --git a/reference/openttd/qstat.txt b/reference/openttd/qstat.txt new file mode 100644 index 0000000..9a56ee0 --- /dev/null +++ b/reference/openttd/qstat.txt @@ -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 +#ifndef _WIN32 +#include +#endif +#include +#include + +#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; +} diff --git a/reference/ravenshield/gamedignote.txt b/reference/ravenshield/gamedignote.txt new file mode 100644 index 0000000..07e0eda --- /dev/null +++ b/reference/ravenshield/gamedignote.txt @@ -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. diff --git a/reference/ravenshield/qstat.txt b/reference/ravenshield/qstat.txt new file mode 100644 index 0000000..5e5addd --- /dev/null +++ b/reference/ravenshield/qstat.txt @@ -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; + } +} diff --git a/reference/savage/qstat.txt b/reference/savage/qstat.txt new file mode 100644 index 0000000..b3bce33 --- /dev/null +++ b/reference/savage/qstat.txt @@ -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"); + } +} + diff --git a/reference/teeworlds/gameq2.txt b/reference/teeworlds/gameq2.txt new file mode 100644 index 0000000..4368455 --- /dev/null +++ b/reference/teeworlds/gameq2.txt @@ -0,0 +1,109 @@ +. + */ + +/** + * Teeworlds Protocol + * + * @author Marcel Bößendörfer + */ +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(); + } +} \ No newline at end of file diff --git a/reference/teeworlds/qstat.txt b/reference/teeworlds/qstat.txt new file mode 100644 index 0000000..d2a5b2d --- /dev/null +++ b/reference/teeworlds/qstat.txt @@ -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 +#include +#include + +#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; +} + diff --git a/reference/tribes/qstat.txt b/reference/tribes/qstat.txt new file mode 100644 index 0000000..8bd0be3 --- /dev/null +++ b/reference/tribes/qstat.txt @@ -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; +} diff --git a/reference/tribes2/qstat.txt b/reference/tribes2/qstat.txt new file mode 100644 index 0000000..22dd553 --- /dev/null +++ b/reference/tribes2/qstat.txt @@ -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; +} diff --git a/reference/worldinconflict/qstat.txt b/reference/worldinconflict/qstat.txt new file mode 100644 index 0000000..739874c --- /dev/null +++ b/reference/worldinconflict/qstat.txt @@ -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 +#ifndef _WIN32 +#include +#endif +#include +#include +#include + +#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; +} +