예제 #1
0
파일: ts2.c 프로젝트: Bubelbub/qstat
query_status_t send_ts2_request_packet( struct qserver *server )
{
	char buf[256];

	int serverport = get_param_i_value( server, "port", 0 );
	change_server_port( server, serverport, 1 );

	if ( get_player_info )
	{
		server->flags |= TF_PLAYER_QUERY|TF_RULES_QUERY;
		sprintf( buf, "si %d\npl %d\nquit\n", serverport, serverport );
		server->saved_data.pkt_index = 2;
	}
	else
	{
		server->flags |= TF_STATUS_QUERY;
		sprintf( buf, "si %d\nquit\n", serverport );
		server->saved_data.pkt_index = 1;
	}

	return send_packet( server, buf, strlen( buf ) );
}
예제 #2
0
파일: ts2.c 프로젝트: Bubelbub/qstat
query_status_t deal_with_ts2_packet( struct qserver *server, char *rawpkt, int pktlen )
{
	char *s, *end;
	int ping, connect_time, mode = 0;
	char name[256];
	debug( 2, "processing..." );

	server->n_servers++;
	server->n_requests++;
	server->ping_total += time_delta( &packet_recv_time, &server->packet_time1 );

	if ( 0 == pktlen )
	{
		// Invalid password
		return REQ_ERROR;
	}

	rawpkt[pktlen]= '\0';
	end = &rawpkt[pktlen];

	s = rawpkt;
	s = strtok( rawpkt, "\015\012" );

	while ( NULL != s )
	{
		if ( 0 == mode )
		{
			// Rules
			char *key = s;
			char *value = strchr( key, '=' );
			if ( NULL != value )
			{
				// Server Rule
				*value = '\0';
				value++;
				if ( 0 == strcmp( "server_name", key ) )
				{
					server->server_name = strdup( value );
				}
				else if ( 0 == strcmp( "server_udpport", key ) )
				{
					change_server_port( server, atoi( value ), 0 );
					add_rule( server, key, value, NO_FLAGS );
				}
				else if ( 0 == strcmp( "server_maxusers", key ) )
				{
					server->max_players = atoi( value );
				}
				else if ( 0 == strcmp( "server_currentusers", key ) )
				{
					server->num_players = atoi( value);
				}
				else
				{
					add_rule( server, key, value, NO_FLAGS);
				}
			}
			else if ( 0 == strcmp( "OK", s ) )
			{
				// end of rules request
				server->saved_data.pkt_index--;
				mode++;
			}
			else if ( 0 == strcmp( "[TS]", s ) )
			{
				// nothing to do
			}
			else if ( 0 == strcmp( "ERROR, invalid id", s ) )
			{
				// bad server
				server->server_name = DOWN;
				server->saved_data.pkt_index = 0;
			}
		}
		else if ( 1 == mode )
		{
			// Player info
			if ( 3 == sscanf( s, "%*d %*d %*d %*d %*d %*d %*d %d %d %*d %*d %*d %*d \"0.0.0.0\" \"%255[^\"]", &ping, &connect_time, name ) )
			{
				// Player info
				struct player *player = add_player( server, server->n_player_info );
				if ( NULL != player )
				{
					player->name = strdup( name );
					player->ping = ping;
					player->connect_time = connect_time;
				}
			}
			else if ( 0 == strcmp( "OK", s ) )
			{
				// end of rules request
				server->saved_data.pkt_index--;
				mode++;
			}
			else if ( 0 == strcmp( "[TS]", s ) )
			{
				// nothing to do
			}
			else if ( 0 == strcmp( "ERROR, invalid id", s ) )
			{
				// bad server
				server->server_name = DOWN;
				server->saved_data.pkt_index = 0;
			}
		}
		s = strtok( NULL, "\015\012" );
	}

	gettimeofday( &server->packet_time1, NULL );

	if ( 0 == server->saved_data.pkt_index )
	{
		server->map_name = strdup( "N/A" );
		return DONE_FORCE;
	}

	return INPROGRESS;
}
예제 #3
0
파일: ts3.c 프로젝트: multiplay/qstat
query_status_t
send_ts3_single_server_packet(struct qserver *server)
{
	char buf[256], *password, *username;
	int serverport;

	switch (server->challenge) {
	case 0:
		// Not seen a challenge yet, wait for it
		server->n_servers = 999;
		return (INPROGRESS);

	case 1:
		// Login if needed
		password = get_param_value(server, "password", "");
		if (0 != strlen(password)) {
			username = get_param_value(server, "username", "serveradmin");
			sprintf(buf, "login %s %s\015\012", username, password);
			break;
		}
		// NOTE: no break so we fall through
		server->challenge++;

	case 2:
		// Select port
		serverport = get_param_i_value(server, "port", 0);
		change_server_port(server, serverport, 1);
		// NOTE: we use n_servers as an indication of how many responses we are expecting to get
		if (get_player_info) {
			server->flags |= TF_PLAYER_QUERY | TF_RULES_QUERY;
			server->n_servers = 5;
		} else {
			server->flags |= TF_STATUS_QUERY;
			server->n_servers = 4;
		}
		sprintf(buf, "use port=%d\015\012", serverport);
		break;

	case 3:
		// Server Info
		sprintf(buf, "serverinfo\015\012");
		break;

	case 4:
		// Player Info, Quit or Done
		sprintf(buf, (get_player_info) ? "clientlist\015\012" : "quit\015\012");
		break;

	case 5:
		// Quit or Done
		if (get_player_info) {
			sprintf(buf, "quit\015\012");
		} else {
			return (DONE_FORCE);
		}
		break;
	}
	server->saved_data.pkt_max = -1;

	return (send_packet(server, buf, strlen(buf)));
}
예제 #4
0
파일: ts3.c 프로젝트: multiplay/qstat
query_status_t
deal_with_ts3_packet(struct qserver *server, char *rawpkt, int pktlen)
{
	char *s, *player_name = "unknown";
	int valid_response = 0, mode = 0, all_servers = 0;
	char last_char;
	unsigned short port = 0, down = 0, auth_seen = 0;

	debug(2, "processing...");

	if (0 == pktlen) {
		// Invalid password
		return (REQ_ERROR);
	}

	last_char = rawpkt[pktlen - 1];
	rawpkt[pktlen - 1] = '\0';
	s = rawpkt;
	all_servers = all_ts3_servers(server);

	debug(3, "packet: combined = %d, challenge = %ld, n_servers = %d", server->combined, server->challenge, server->n_servers);
	if (!server->combined) {
		server->retry1 = n_retries;
		if (0 == server->n_requests) {
			server->ping_total = time_delta(&packet_recv_time, &server->packet_time1);
			server->n_requests++;
		}

		if (server->n_servers >= server->challenge) {
			// 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 + 1;
			rawpkt[pktlen - 1] = last_char; // restore the last character
			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));
		}
	} else {
		valid_response = valid_ts3_response(server, rawpkt + server->master_pkt_len, pktlen - server->master_pkt_len);

		debug(2, "combined packet: valid_response: %d, challenge: %ld, n_servers: %d, offset: %d", valid_response, server->challenge, server->n_servers, server->master_pkt_len);

		if (0 > valid_response) {
			// Error occured
			return (valid_response);
		}

		server->challenge += valid_response;

		if (valid_response) {
			// Got a valid response, send the next request
			int ret = send_ts3_request_packet(server);
			if (0 != ret) {
				// error sending packet
				debug(4, "Request failed: %d", ret);
				return (ret);
			}
		}

		if (server->n_servers > server->challenge) {
			// recursive call which is still incomplete
			return (INPROGRESS);
		}
	}

	// 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 = strtok(rawpkt, "\012\015 |");

	// NOTE: id=XXX and msg=XXX will be processed by the mod following the one they where the response of
	while (NULL != s) {
		debug(4, "LINE: %d, %s\n", mode, s);
		switch (mode) {
		case 0:
			// prompt, use or serverlist response
			if (0 == strcmp("TS3", s)) {
				// nothing to do unless in all servers mode
				if (1 == all_servers) {
					mode++;
				}
			} else if (0 == strncmp("error", s, 5)) {
				// end of use response
				mode++;
			}
			break;

		case 1:
			// serverinfo or serverlist response including condition authentication
			if ((0 == auth_seen) && (0 != strlen(get_param_value(server, "password", ""))) && (0 == strncmp("error", s, 5))) {
				// end of auth response
				auth_seen = 1;
			} else if (0 == strncmp("error", s, 5)) {
				// end of serverinfo response
				mode++;
			} else {
				// Server Rule
				char *key = s;
				char *value = strchr(key, '=');
				if (NULL != value) {
					*value = '\0';
					value++;
					debug(6, "Rule: %s = %s\n", key, value);
					if (0 == strcmp("virtualserver_name", key)) {
						if (1 == all_servers) {
							struct qserver *new_server = add_qserver_byaddr(ntohl(server->ipaddr), port, server->type, NULL);
							if (NULL != new_server) {
								if (down) {
									// Status indicates this server is actually offline
									new_server->server_name = DOWN;
								} else {
									new_server->max_players = server->max_players;
									new_server->num_players = server->num_players;
									new_server->server_name = strdup(decode_ts3_val(value));
									new_server->map_name = strdup("N/A");
									new_server->ping_total = server->ping_total;
									new_server->n_requests = server->n_requests;
								}
								cleanup_qserver(new_server, FORCE);
							}
							down = 0;
						} else {
							server->server_name = strdup(decode_ts3_val(value));
						}
					} else if (0 == strcmp("virtualserver_port", key)) {
						port = atoi(value);
						change_server_port(server, port, 0);
						add_rule(server, key, value, NO_FLAGS);
					} else if (0 == strcmp("virtualserver_maxclients", key)) {
						server->max_players = atoi(value);
					} else if (0 == strcmp("virtualserver_clientsonline", key)) {
						server->num_players = atoi(value);
					} else if (0 == strcmp("virtualserver_queryclientsonline", key)) {
						// clientsonline includes queryclientsonline so remove these from our count
						server->num_players -= atoi(value);
					} else if ((0 == strcmp("virtualserver_status", key)) && (0 != strcmp("online", value))) {
						// Server is actually offline to client so display as down
						down = 1;
						if (1 != all_servers) {
							server->server_name = DOWN;
							//server->saved_data.pkt_index = 0;
							return (DONE_FORCE);
						}
					} else if ((0 == strcmp("id", key)) || (0 == strcmp("msg", key))) {
						// Ignore details from the response code
					} else if (1 != all_servers) {
						add_rule(server, key, value, NO_FLAGS);
					}
				}
			}
			break;

		case 2:
			// clientlist response
			if (0 == strncmp("error", s, 5)) {
				// end of serverinfo response
				mode++;
			} else {
				// Client
				char *key = s;
				char *value = strchr(key, '=');
				if (NULL != value) {
					*value = '\0';
					value++;
					debug(6, "Player: %s = %s\n", key, value);
					if (0 == strcmp("client_nickname", key)) {
						player_name = value;
					} else if (0 == strcmp("clid", key)) {
					} else if ((0 == strcmp("client_type", key)) && (0 == strcmp("0", value))) {
						struct player *player = add_player(server, server->n_player_info);
						if (NULL != player) {
							player->name = strdup(decode_ts3_val(player_name));
						}
					} else if ((0 == strcmp("id", key)) || (0 == strcmp("msg", key))) {
						// Ignore details from the response code
					}
				}
			}
			break;
		}
		s = strtok(NULL, "\012\015 |");
	}

	gettimeofday(&server->packet_time1, NULL);

	server->map_name = strdup("N/A");
	return (DONE_FORCE);
}
예제 #5
0
파일: gs2.c 프로젝트: Bubelbub/qstat
// See the following for protocol details:
// http://dev.kquery.com/index.php?article=42
query_status_t deal_with_gs2_packet( struct qserver *server, char *rawpkt, int pktlen )
{
	char *ptr = rawpkt;
	char *end = rawpkt + pktlen;
	unsigned char type = 0;
	unsigned char no_players = 0;
	unsigned char total_players = 0;
	unsigned char no_teams = 0;
	unsigned char total_teams = 0;
	unsigned char no_headers = 0;
	char **headers = NULL;
	debug( 2, "processing packet..." );

	if ( pktlen < 15 )
	{
		// invalid packet?
		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);
	}

	// Could check the header here should
	// match the 4 byte id sent
	ptr += 5;
	while ( 0 == type && ptr < end )
	{
		// server info:
		// name value pairs null seperated
		// empty name && value signifies the end of section
		char *var, *val;
		int var_len, val_len;

		var = ptr;
		var_len = strlen( var );

		if ( ptr + var_len + 2 > end )
		{
			if ( 0 != var_len )
			{
				malformed_packet( server, "no rule value" );
			}
			else if ( get_player_info )
			{
				malformed_packet( server, "no player headers" );
			}
			return PKT_ERROR;
		}
		ptr += var_len + 1;

		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, "hostname" ) )
		{
			server->server_name = strdup( val );
		}
		else if( 0 == strcmp( var, "game_id" ) )
		{
			server->game = strdup( val );
		}
		else if( 0 == strcmp( var, "gamever" ) )
		{
			// format:
			// v1.0
			server->protocol_version = atoi( val+1 );
			add_rule( server, var, val, NO_FLAGS );
		}
		else if( 0 == strcmp( var, "mapname" ) )
		{
			server->map_name = strdup( val );
		}
		else if( 0 == strcmp( var, "map" ) )
		{
			// For BF2MC compatibility
			server->map_name = strdup( val );
		}
		else if( 0 == strcmp( var, "maxplayers" ) )
		{
			server->max_players = atoi( val );

		}
		else if( 0 == strcmp( var, "numplayers" ) )
		{
			server->num_players = no_players = atoi( val );
		}
		else if( 0 == strcmp( var, "hostport" ) )
		{
			change_server_port( server, atoi( val ), 0 );
		}
		else if ( 0 == var_len )
		{
			// check for end of section
			type = 1;
		}
		else
		{
			add_rule( server, var, val, NO_FLAGS );
		}
	}

	if ( 1 != type )
	{
		// no more info should be player headers here as we
		// requested it
		malformed_packet( server, "no player headers" );
		return PKT_ERROR;
	}

	// player info header
	// format:
	// first byte = player count
	// followed by null seperated header
	no_players = (unsigned char)*ptr;
	debug( 2, "No Players:%d\n", no_players );
	ptr++;

	if ( ptr >= end )
	{
		malformed_packet( server, "no player headers" );
		return PKT_ERROR;
	}

	while ( 1 == type && ptr < end )
	{
		// first we have the headers null seperated
		char **tmpp;
		char *head = ptr;
		int head_len = strlen( head );
		no_headers++;
		tmpp = (char**)realloc( headers, no_headers * sizeof( char* ) );
		if ( NULL == tmpp )
		{
			debug( 0, "Failed to realloc memory for headers\n" );
			if ( NULL != headers )
			{
				free( headers );
			}
			return MEM_ERROR;
		}

		headers = tmpp;
		headers[no_headers-1] = head;

		ptr += head_len + 1;

		// end of headers check
		if ( 0x00 == *ptr )
		{
			type = 2;
			ptr++;
		}
		debug( 2, "player header[%d] = '%s'", no_headers-1, head );
	}

	if ( 2 != type )
	{
		// no more info should be player info here as we
		// requested it
		malformed_packet( server, "no players" );
		return PKT_ERROR;
	}

	while( 2 == type && ptr < end )
	{
		// now each player details
		// add the player
		if ( 0x00 == *ptr )
		{
			// no players
			if ( 0 != no_players )
			{
				malformed_packet( server, "no players" );
				return PKT_ERROR;
			}
		}
		else
		{
			struct player *player = add_player( server, total_players );
			int i;
			for ( i = 0; i < no_headers; i++ )
			{
				char *header = headers[i];
				char *val;
				int val_len;

				if ( ptr >= end )
				{
					malformed_packet( server, "short player detail" );
					return PKT_ERROR;
				}
				val = ptr;
				val_len = strlen( val );
				ptr += val_len + 1;

				// lets see what we got
				if ( 0 == strcmp( header, "player_" ) )
				{
					player->name = strdup( val );
				}
				else if ( 0 == strcmp( header, "score_" ) )
				{
					player->score = atoi( val );
				}
				else if ( 0 == strcmp( header, "deaths_" ) )
				{
					player->deaths = atoi( val );
				}
				else if ( 0 == strcmp( header, "ping_" ) )
				{
					player->ping = atoi( val );
				}
				else if ( 0 == strcmp( header, "kills_" ) )
				{
					player->frags = atoi( val );
				}
				else if ( 0 == strcmp( header, "team_" ) )
				{
					player->team = atoi( val );
				}
				else
				{
					int len = strlen( header );
					if ( '_' == header[len-1] )
					{
						header[len-1] = '\0';
					}
					player_add_info( player, header, val, NO_FLAGS );
				}

				debug( 2, "Player[%d][%s]=%s\n", total_players, headers[i], val );
			}
			total_players++;
		}

		if ( total_players > no_players )
		{
			malformed_packet( server, "to many players %d > %d", total_players, no_players );
			return PKT_ERROR;
		}

		// check for end of player info
		if ( 0x00 == *ptr )
		{
			if ( total_players != no_players )
			{
				malformed_packet( server, "bad number of players %d != %d", total_players, no_players );
				return PKT_ERROR;
			}
			type = 3;
			ptr++;
		}
	}

	if ( 3 != type )
	{
		// no more info should be team info here as we
		// requested it
		malformed_packet( server, "no teams" );
		return PKT_ERROR;
	}

	no_teams = (unsigned char)*ptr;
	ptr++;

	debug( 2, "No teams:%d\n", no_teams );
	no_headers = 0;

	while ( 3 == type && ptr < end )
	{
		// first we have the headers null seperated
		char **tmpp;
		char *head = ptr;
		int head_len = strlen( head );
		no_headers++;
		tmpp = (char**)realloc( headers, no_headers * sizeof( char* ) );
		if ( NULL == tmpp )
		{
			debug( 0, "Failed to realloc memory for headers\n" );
			if ( NULL != headers )
			{
				free( headers );
			}
			return MEM_ERROR;
		}

		headers = tmpp;
		headers[no_headers-1] = head;

		ptr += head_len + 1;

		// end of headers check
		if ( 0x00 == *ptr )
		{
			type = 4;
			ptr++;
		}
	}

	if ( 4 != type )
	{
		// no more info should be team info here as we
		// requested it
		malformed_packet( server, "no teams" );
		return PKT_ERROR;
	}

	while( 4 == type && ptr < end )
	{
		// now each teams details
		int i;
		for ( i = 0; i < no_headers; i++ )
		{
			char *val;
			int val_len;

			if ( ptr >= end )
			{
				malformed_packet( server, "short team detail" );
				return PKT_ERROR;
			}
			val = ptr;
			val_len = strlen( val );
			ptr += val_len + 1;

			// lets see what we got
			if ( 0 == strcmp( headers[i], "team_t" ) )
			{
				// BF being stupid again teams 1 based instead of 0
				players_set_teamname( server, total_teams + 1, val );
			}
			debug( 2, "Team[%d][%s]=%s\n", total_teams, headers[i], val );
		}
		total_teams++;

		if ( total_teams > no_teams )
		{
			malformed_packet( server, "to many teams" );
			return PKT_ERROR;
		}
	}

	return DONE_FORCE;
}
예제 #6
0
파일: fl.c 프로젝트: JasonRivers/qstat
query_status_t deal_with_fl_packet(struct qserver *server, char *rawpkt, int pktlen)
{
	struct fl_status* status = (struct fl_status*)server->master_query_tag;
	char* pkt = rawpkt;
	char buf[16];
	char* str;
	unsigned cnt;
	unsigned short tmp_short;

	if(server->server_name == NULL)
	{
		server->ping_total += time_delta( &packet_recv_time, &server->packet_time1);
		server->n_requests++;
	}

	if(pktlen < 5) goto out_too_short;

	if( 0 == memcmp(pkt, "\xFF\xFF\xFF\xFE", 4) )
	{
		// fragmented packet
		unsigned char pkt_index, pkt_max;
		unsigned int pkt_id;
		SavedData *sdata;

		if(pktlen < 9) goto out_too_short;

		// format:
		// int Header
		// int RequestId
		// byte PacketNumber
		// byte NumPackets
		// Short SizeOfPacketSplits

		// Header
		pkt += 4;

		// RequestId
		pkt_id = ntohl( *(long *)pkt );
		debug( 3, "RequestID: %d", pkt_id );
		pkt += 4;

		// The next two bytes are:
		// 1. the max packets sent ( byte )
		// 2. the index of this packet starting from 0 ( byte )
		// 3. Size of the split ( short )
		if(pktlen < 10) goto out_too_short;

		// PacketNumber
		pkt_index = ((unsigned char)*pkt);

		// NumPackates
		pkt_max = ((unsigned char)*(pkt+1));

		// SizeOfPacketSplits
		debug( 3, "packetid[2]: 0x%hhx => idx: %hhu, max: %hhu", *pkt, pkt_index, pkt_max );
		pkt+=4;
		pktlen -= 12;

		// pkt_max is the total number of packets expected
		// pkt_index is a bit mask of the packets received.
		if ( server->saved_data.data == NULL )
		{
			sdata = &server->saved_data;
		}
		else
		{
			sdata = (SavedData*) calloc( 1, sizeof(SavedData));
			sdata->next = server->saved_data.next;
			server->saved_data.next = sdata;
		}

		sdata->pkt_index = pkt_index;
		sdata->pkt_max = pkt_max;
		sdata->pkt_id = pkt_id;
		sdata->datalen = pktlen;
		sdata->data= (char*)malloc( pktlen );
		if ( NULL == sdata->data )
		{
			malformed_packet(server, "Out of memory");
			return MEM_ERROR;
		}

		memcpy( sdata->data, pkt, sdata->datalen );

		// combine_packets will call us recursively
		return combine_packets( server );
	}
	else if ( 0 != memcmp(pkt, "\xFF\xFF\xFF\xFF", 4) )
	{
		malformed_packet(server, "invalid packet header");
		return PKT_ERROR;
	}

	pkt += 4;
	pktlen -= 4;

	pktlen -= 1;
	debug( 2, "FL type = 0x%x", *pkt );
	switch(*pkt++)
	{
	case FL_CHALLENGERESPONSE:
		if(pktlen < 4) goto out_too_short;
		memcpy(&status->challenge, pkt, 4);
		// do not count challenge as retry
		if(!status->have_challenge && server->retry1 != n_retries)
		{
			++server->retry1;
			if(server->n_retries)
			{
				--server->n_retries;
			}
		}
		status->have_challenge = 1;
		debug(3, "challenge %x", status->challenge);
		break;

	case FL_INFORESPONSE:
		if(pktlen < 1) goto out_too_short;
		status->type = *pkt;
		if ( *pkt > 1 && ( get_server_rules || get_player_info ) )
		{
			 server->next_rule = ""; // trigger calling send_fl_rule_request_packet
		}
		snprintf(buf, sizeof(buf), "%hhX", *pkt);
		add_rule(server, "protocol", buf, 0);
		pktlen--;
		pkt++;

		// ServerName
		str = memchr(pkt, '\0', pktlen);
		if(!str) goto out_too_short;
		server->server_name = strdup(pkt);
		pktlen -= str-pkt+1;
		pkt += str-pkt+1;

		// MapName
		str = memchr(pkt, '\0', pktlen);
		if(!str) goto out_too_short;
		server->map_name = strdup(pkt);
		pktlen -= str-pkt+1;
		pkt += str-pkt+1;

		// ModName
		str = memchr(pkt, '\0', pktlen);
		if(!str) goto out_too_short;
		server->game = strdup(pkt);
		add_rule(server, "modname", pkt, 0);
		pktlen -= str-pkt+1;
		pkt += str-pkt+1;

		// GameMode
		str = memchr(pkt, '\0', pktlen);
		if(!str) goto out_too_short;
		add_rule(server, "gamemode", pkt, 0);
		pktlen -= str-pkt+1;
		pkt += str-pkt+1;

		// GameDescription
		str = memchr(pkt, '\0', pktlen);
		if(!str) goto out_too_short;
		add_rule(server, "gamedescription", pkt, 0);
		pktlen -= str-pkt+1;
		pkt += str-pkt+1;

		// GameVersion
		str = memchr(pkt, '\0', pktlen);
		if(!str) goto out_too_short;
		add_rule(server, "gameversion", pkt, 0);
		pktlen -= str-pkt+1;
		pkt += str-pkt+1;

		if( pktlen < 13 )
		{
			goto out_too_short;
		}

		// GamePort
		tmp_short = ((unsigned short)pkt[0] <<8 ) | ((unsigned short)pkt[1]);
		change_server_port( server, tmp_short, 0 );
		pkt += 2;

		// Num Players
		server->num_players = (unsigned char)*pkt++;

		// Max Players
		server->max_players = (unsigned char)*pkt++;

		// Dedicated
		add_rule(server, "dedicated", ( 'd' == *pkt++) ? "1" : "0", 0);

		// OS
		switch( *pkt )
		{
		case 'l':
			add_rule(server, "sv_os", "linux", 0);
			break;

		case 'w':
			add_rule(server, "sv_os", "windows", 0);
			break;

		default:
			buf[0] = *pkt;
			buf[1] = '\0';
			add_rule(server, "sv_os", buf, 0);
			break;
		}
		pkt++;

		// Passworded
		add_rule(server, "passworded", ( *pkt++ ) ? "1" : "0" , 0);

		// Anticheat
		add_rule(server, "passworded", ( *pkt++ ) ? "1" : "0" , 0);

		// FrameTime
		sprintf( buf, "%hhu", *pkt++ );
		add_rule(server, "frametime", buf , 0);

		// Round
		sprintf( buf, "%hhu", *pkt++ );
		add_rule(server, "round", buf , 0);

		// RoundMax
		sprintf( buf, "%hhu", *pkt++ );
		add_rule(server, "roundmax", buf , 0);

		// RoundSeconds
		tmp_short = ((unsigned short)pkt[0] <<8 ) | ((unsigned short)pkt[1]);
		sprintf( buf, "%hu", tmp_short );
		add_rule(server, "roundseconds", buf , 0);
		pkt += 2;

		status->have_info = 1;

		server->retry1 = n_retries;

		server->next_player_info = server->num_players;

		break;

	case FL_RULESRESPONSE:
		if(pktlen < 2) goto out_too_short;

		cnt = ((unsigned char)pkt[0] << 8 ) + ((unsigned char)pkt[1]);
		pktlen -= 2;
		pkt += 2;

		debug(3, "num_rules: %d", cnt);

		for(;cnt && pktlen > 0; --cnt)
		{
			char* key, *value;
			str = memchr(pkt, '\0', pktlen);
			if(!str) break;
			key = pkt;
			pktlen -= str-pkt+1;
			pkt += str-pkt+1;

			str = memchr(pkt, '\0', pktlen);
			if(!str) break;
			value = pkt;
			pktlen -= str-pkt+1;
			pkt += str-pkt+1;

			add_rule(server, key, value, NO_FLAGS);
		}

		if(cnt)
		{
			malformed_packet(server, "packet contains too few rules, missing %d", cnt);
			server->missing_rules = 1;
		}
		if(pktlen)
		malformed_packet(server, "garbage at end of rules, %d bytes left", pktlen);

		status->have_rules = 1;

		server->retry1 = n_retries;

		break;

	case FL_PLAYERRESPONSE:

		if(pktlen < 1) goto out_too_short;

		cnt = (unsigned char)pkt[0];
		pktlen -= 1;
		pkt += 1;

		debug(3, "num_players: %d", cnt);

		for(;cnt && pktlen > 0; --cnt)
		{
			unsigned idx;
			const char* name;
			struct player* p;

			// Index
			idx = *pkt++;
			--pktlen;

			// PlayerName
			str = memchr(pkt, '\0', pktlen);
			if(!str) break;
			name = pkt;
			pktlen -= str-pkt+1;
			pkt += str-pkt+1;

			if(pktlen < 8) goto out_too_short;

			debug(3, "player index %d", idx);
			p = add_player(server, server->n_player_info);
			if(p)
			{
				union
				{
					int i;
					float fl;
				} temp;

				p->name = strdup(name);

				// Score
				p->frags = ntohl( *(unsigned int *)pkt );

				// TimeConnected
				temp.i = ntohl( *(unsigned int *)(pkt+4) );
				p->connect_time = temp.fl;

				// Ping
				p->ping = 0;
				p->ping = ntohs( *(unsigned int *)(pkt+8) );
				//((unsigned char*)&p->ping)[0] = pkt[9];
				//((unsigned char*)&p->ping)[1] = pkt[8];

				// ProfileId
				//p->profileid = ntohl( *(unsigned int *)pkt+10 );

				// Team
				p->team = *(pkt+14);
//fprintf( stderr, "Player: '%s', Frags: %u, Time: %u, Ping: %hu, Team: %d\n", p->name, p->frags,  p->connect_time, p->ping, p->team );
			}
			pktlen -= 15;
			pkt += 15;
		}

#if 0 // seems to be a rather normal condition
		if(cnt)
		{
			malformed_packet(server, "packet contains too few players, missing %d", cnt);
		}
#endif
		if(pktlen)
		malformed_packet(server, "garbage at end of player info, %d bytes left", pktlen);

		status->have_player = 1;

		server->retry1 = n_retries;

		break;

	default:
		malformed_packet(server, "invalid packet id %hhx", *--pkt);
		return PKT_ERROR;
	}

	if(
		(!get_player_info || (get_player_info && status->have_player)) &&
		(!get_server_rules || (get_server_rules && status->have_rules))
	)
	{
		server->next_rule = NULL;
	}

	return DONE_AUTO;

out_too_short:
	malformed_packet(server, "packet too short");

	return PKT_ERROR;
}