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;
}
