mirror of
https://github.com/gamedig/node-gamedig.git
synced 2024-11-16 16:58:32 +01:00
455 lines
10 KiB
Text
455 lines
10 KiB
Text
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;
|
|
}
|