/* * qstat 2.8 * by Steve Jankowski * * Gamespy query protocol * Copyright 2005 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" int gps_max_players( struct qserver *server ) { struct player *player; int no_players = 0; if ( 0 == server->num_players ) { return 0; } for ( player= server->players; player; player= player->next) { no_players++; } return ( no_players < server->num_players ) ? 1 : 0; } int gps_player_info_key( char *s, char *end) { static char *keys[] = { "frags_", "team_", "ping_", "species_", "race_", "deaths_", "score_", "enemy_", "player_", "keyhash_", "teamname_", "playername_", "keyhash_", "kills_", "queryid" }; int i; for ( i= 0; i < sizeof(keys)/sizeof(char*); i++) { int len= strlen(keys[i]); if ( s+len < end && strncmp( s, keys[i], len) == 0) { return len; } } return 0; } void send_gps_request_packet( struct qserver *server ) { send_packet( server, server->type->status_packet, server->type->status_len ); } void deal_with_gps_packet( struct qserver *server, char *rawpkt, int pktlen ) { char *s, *key, *value, *end; struct player *player = NULL; int id_major=0, id_minor=0, final=0, player_num; char tmp[256]; debug( 2, "processing..." ); 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 ); } /* // We're using the saved_data a bit differently here to track received // packets. // pkt_id is the id_major from the \queryid\ // pkt_max is the total number of packets expected // pkt_index is a bit mask of the packets received. The id_minor of // \queryid\ provides packet numbers (1 through pkt_max). */ if ( server->saved_data.pkt_index == -1) { server->saved_data.pkt_index= 0; } rawpkt[pktlen]= '\0'; end= &rawpkt[pktlen]; s= rawpkt; while ( *s) { // find the '\' while ( *s && *s == '\\') { s++; } if ( !*s ) { // out of packet break; } // Start of key key = s; // while we still have data and its not a '\' while ( *s && *s != '\\') { s++; } if ( !*s ) { // out of packet break; } // Terminate the key *s++= '\0'; // Now for the value value = s; // while we still have data and its not a '\' while ( *s && *s != '\\') { s++; } if ( s[0] && s[1] ) { //fprintf( stderr, "%s = %s\n", key, value ); if ( ! isalpha((unsigned char)s[1]) ) { // escape char? s++; // while we still have data and its not a '\' while ( *s && *s != '\\') { s++; } } else if ( isalpha((unsigned char)s[1]) && 0 == strncmp( key, "player_", 7 ) && 0 != strcmp( key, "player_flags" ) ) { // possible '\' in player name if ( ! gps_player_info_key( s+1, end ) ) { // yep there was an escape in the player name s++; // while we still have data and its not a '\' while ( *s && *s != '\\') { s++; } } } } if ( *s) { *s++= '\0'; } //fprintf( stderr, "%s = %s\n", key, value ); if ( *value == '\0') { if ( strcmp( key, "final" ) == 0 ) { final= 1; if ( id_minor > server->saved_data.pkt_max ) { server->saved_data.pkt_max = id_minor; } continue; } } /* This must be done before looking for player info because "queryid" is a valid according to gps_player_info_key(). */ if ( strcmp( key, "queryid") == 0) { sscanf( value, "%d.%d", &id_major, &id_minor); if ( server->saved_data.pkt_id == 0) { server->saved_data.pkt_id = id_major; } if ( id_major == server->saved_data.pkt_id) { if ( id_minor > 0) { // pkt_index is bitmask of packets recieved server->saved_data.pkt_index |= 1 << (id_minor-1); } if ( final && id_minor > server->saved_data.pkt_max) { server->saved_data.pkt_max = id_minor; } } continue; } if ( player == NULL ) { int len = gps_player_info_key( key, end); if ( len ) { // We have player info int player_number = atoi( key + len ); player = get_player_by_number( server, player_number); // && gps_max_players( server ) due to bf1942 issue // where the actual no players is correct but more player // details are returned if ( player == NULL && gps_max_players( server ) ) { player = add_player( server, player_number ); if ( player ) { // init to -1 so we can tell if // we have team info player->team = -1; player->deaths = -999; } } } } if ( strcmp( key, "mapname") == 0 && !server->map_name) { server->map_name= strdup( value ); } else if ( strcmp( key, "hostname") == 0 && !server->server_name) { server->server_name= strdup( value ); } else if ( strcmp( key, "hostport") == 0) { change_server_port( server, atoi( value), 0 ); } else if ( strcmp( key, "maxplayers") == 0) { server->max_players= atoi( value ); } else if ( strcmp( key, "numplayers") == 0) { server->num_players= atoi( value); } else if ( strcmp( key, server->type->game_rule) == 0 && !server->game) { server->game= strdup( value ); add_rule( server, key, value, NO_FLAGS); } else if ( strcmp( key, "final") == 0) { final= 1; if ( id_minor > server->saved_data.pkt_max) { server->saved_data.pkt_max = id_minor; } continue; } else if ( strncmp( key, "player_", 7) == 0 || strncmp( key, "playername_", 11) == 0 ) { int no; if ( strncmp( key, "player_", 7) == 0 ) { no = atoi(key+7); } else { no = atoi(key+11); } if ( player && player->number == no ) { player->name = strdup( value); player = NULL; } else if ( NULL != ( player = get_player_by_number( server, no ) ) ) { player->name = strdup( value); player = NULL; } else if ( gps_max_players( server ) ) { // gps_max_players( server ) due to bf1942 issue // where the actual no players is correct but more player // details are returned player = add_player( server, no ); if ( player ) { player->name= strdup( value); // init to -1 so we can tell if // we have team info player->team = -1; player->deaths = -999; } } } else if ( strncmp( key, "teamname_", 9) == 0 ) { // Yes plus 1 BF1942 is a silly players_set_teamname( server, atoi( key+9 ) + 1, value ); } else if ( strncmp( key, "team_t", 6) == 0 ) { players_set_teamname( server, atoi( key+6 ), value ); } else if ( strncmp( key, "frags_", 6) == 0 ) { player = get_player_by_number( server, atoi( key+6 ) ); if ( NULL != player ) { player->frags= atoi( value ); } } else if ( strncmp( key, "kills_", 6) == 0 ) { player = get_player_by_number( server, atoi( key+6 ) ); if ( NULL != player ) { player->frags= atoi( value ); } } else if ( strncmp( key, "team_", 5) == 0 ) { player = get_player_by_number( server, atoi( key+5 ) ); if ( NULL != player ) { if ( ! isdigit( (unsigned char)*value ) ) { player->team_name= strdup(value); } else { player->team= atoi( value); } server->flags|= FLAG_PLAYER_TEAMS; } } else if ( strncmp( key, "skin_", 5) == 0 ) { player = get_player_by_number( server, atoi( key+5 ) ); if ( NULL != player ) { player->skin= strdup( value); } } else if ( strncmp( key, "mesh_", 5) == 0 ) { player = get_player_by_number( server, atoi( key+5 ) ); if ( NULL != player ) { player->mesh = strdup( value); } } else if ( strncmp( key, "ping_", 5) == 0 ) { player = get_player_by_number( server, atoi( key+5 ) ); if ( NULL != player ) { player->ping= atoi( value); } } else if ( strncmp( key, "face_", 5) == 0 ) { player = get_player_by_number( server, atoi( key+5 ) ); if ( NULL != player ) { player->face= strdup( value); } } else if ( strncmp( key, "deaths_", 7) == 0 ) { player = get_player_by_number( server, atoi( key+7 ) ); if ( NULL != player ) { player->deaths= atoi( value ); } } // isnum( key[6] ) as halo uses score_tX for team scores else if ( strncmp( key, "score_", 6) == 0 && isdigit( (unsigned char)key[6] ) ) { player = get_player_by_number( server, atoi( key+6 ) ); if ( NULL != player ) { player->score = atoi( value ); } } else if ( player && strncmp( key, "playertype", 10) == 0) { player->team_name= strdup( value ); } else if ( player && strncmp( key, "charactername", 13) == 0) { player->face = strdup( value ); } else if ( player && strncmp( key, "characterlevel", 14) == 0) { player->ship= atoi( value ); } else if ( strncmp( key, "keyhash_", 8) == 0) { // Ensure these dont make it into the rules } else if ( 2 == sscanf( key, "%255[^_]_%d", tmp, &player_num ) ) { // arbitary player info player = get_player_by_number( server, player_num ); if ( NULL != player ) { player_add_info( player, tmp, value, NO_FLAGS ); } else if ( gps_max_players( server ) ) { // gps_max_players( server ) due to bf1942 issue // where the actual no players is correct but more player // details are returned player = add_player( server, player_num ); if ( player ) { player->name = NULL; // init to -1 so we can tell if // we have team info player->team = -1; player->deaths = -999; } player_add_info( player, tmp, value, NO_FLAGS ); } } else { player = NULL; add_rule( server, key, value, NO_FLAGS); } } debug( 2, "final %d\n", final ); debug( 2, "pkt_id %d\n", server->saved_data.pkt_id ); debug( 2, "pkt_max %d\n", server->saved_data.pkt_max ); debug( 2, "pkt_index %x\n", server->saved_data.pkt_index ); if ( ( final && server->saved_data.pkt_id == 0 ) || ( server->saved_data.pkt_max && server->saved_data.pkt_index >= ((1<<(server->saved_data.pkt_max))-1) ) || ( server->num_players < 0 && id_minor >= 3) ) { cleanup_qserver( server, 1); } }