/* * qstat 2.8 * by Steve Jankowski * steve@qstat.org * http://www.qstat.org * * Thanks to Per Hammer for the OS/2 patches (per@mindbend.demon.co.uk) * Thanks to John Ross Hunt for the OpenVMS Alpha patches (bigboote@ais.net) * Thanks to Scott MacFiggen for the quicksort code (smf@webmethods.com) * Thanks to Simon Garner for the XML patch (sgarner@gameplanet.co.nz) * Thanks to Bob Marriott for the Ghost Recon code (bmarriott@speakeasy.net) * * Inspired by QuakePing by Len Norton * * Copyright 1996,1997,1998,1999,2000,2001,2002,2003,2004 by Steve Jankowski * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifdef HAVE_CONFIG_H # include "gnuconfig.h" #else # ifndef VERSION # define VERSION "2.11" # endif #endif char *qstat_version= VERSION; /* OS/2 defines */ #ifdef __OS2__ #define BSD_SELECT #endif #include #include #include #include #include #include #include #include #define QUERY_PACKETS #include "qstat.h" #include "packet_manip.h" #include "config.h" #ifndef _WIN32 #include #include #include #ifndef VMS #include #endif #include #include #include #include #include #ifndef F_SETFL #include #endif #ifdef __hpux extern int h_errno; #define STATIC static #else #define STATIC #endif #define INVALID_SOCKET -1 #ifndef INADDR_NONE #define INADDR_NONE ~0 #endif #define sockerr() errno #endif /* _WIN32 */ #ifdef __OS2__ #include #include #include #include #include #include #define INVALID_SOCKET -1 #define SOCKET_ERROR -1 #define close(a) soclose(a) #endif /* __OS2__ */ #ifndef FD_SETSIZE #define FD_SETSIZE 64 #endif /* Figure out whether to use poll() or select() */ #ifndef USE_POLL #ifndef USE_SELECT #ifdef sun #define USE_POLL #endif #ifdef linux #define USE_POLL #include #endif #ifdef __linux__ #define USE_POLL #include #endif #ifdef __linux #define USE_POLL #include #endif #ifdef __hpux #define USE_POLL #include #endif #ifdef __FreeBSD__ #define USE_POLL #include #endif #ifdef _AIX #define USE_POLL #include #endif #ifdef _WIN32 #define USE_SELECT #endif #ifdef __EMX__ #define USE_SELECT #endif #endif /* USE_SELECT */ #endif /* USE_POLL */ /* If did not chose, then use select() */ #ifndef USE_POLL #ifndef USE_SELECT #define USE_SELECT #endif #endif #ifdef ENABLE_DUMP #ifndef _WIN32 #include #include #endif #include #include static int do_dump; #endif #include "debug.h" server_type *types; int n_server_types; /* * Values set by command-line arguments */ int hostname_lookup= 0; /* set if -H was specified */ int new_style= 1; /* unset if -old was specified */ int n_retries= DEFAULT_RETRIES; int retry_interval= DEFAULT_RETRY_INTERVAL; int master_retry_interval= DEFAULT_RETRY_INTERVAL*4; int get_player_info= 0; int get_server_rules= 0; int up_servers_only= 0; int no_full_servers= 0; int no_empty_servers= 0; int no_header_display= 0; int raw_display= 0; char *raw_delimiter= "\t"; char *multi_delimiter = "|"; int player_address= 0; int hex_player_names= 0; int hex_server_names= 0; int name_xforms= 1; int strip_carets= 1; int max_simultaneous= MAXFD_DEFAULT; int sendinterval = 5; int html_names= -1; extern int html_mode; int raw_arg= 0; int show_game_in_raw= 0; int progress= 0; int num_servers_total= 0; int num_players_total= 0; int max_players_total= 0; int num_servers_returned= 0; int num_servers_timed_out= 0; int num_servers_down= 0; server_type* default_server_type= NULL; FILE *OF; /* output file */ unsigned int source_ip= INADDR_ANY; unsigned short source_port_low= 0; unsigned short source_port_high= 0; unsigned short source_port= 0; int show_game_port = 0; int no_port_offset = 0; #define ENCODING_LATIN_1 1 #define ENCODING_UTF_8 8 int xml_display= 0; int xml_encoding= ENCODING_LATIN_1; #define SUPPORTED_SERVER_SORT "pgihn" #define SUPPORTED_PLAYER_SORT "PFTNS" #define SUPPORTED_SORT_KEYS "l" SUPPORTED_SERVER_SORT SUPPORTED_PLAYER_SORT char sort_keys[32]; int player_sort= 0; int server_sort= 0; void quicksort( void **array, int i, int j, int (*compare)(void*,void*)); int qpartition( void **array, int i, int j, int (*compare)(void*,void*)); void sort_servers( struct qserver **array, int size); void sort_players( struct qserver *server); int server_compare( struct qserver *one, struct qserver *two); int player_compare( struct player *one, struct player *two); int type_option_compare( server_type *one, server_type *two ); int type_string_compare( server_type *one, server_type *two ); STATIC int u2xmp_html_color( short color, char *dest, int *font_tag ); STATIC int ut2k4_html_color( const unsigned char *color, char *dest, int *font_tag ); int show_errors= 0; static int noserverdups = 1; #define DEFAULT_COLOR_NAMES_RAW 0 #define DEFAULT_COLOR_NAMES_DISPLAY 1 int color_names= -1; #define SECONDS 0 #define CLOCK_TIME 1 #define STOPWATCH_TIME 2 #define DEFAULT_TIME_FMT_RAW SECONDS #define DEFAULT_TIME_FMT_DISPLAY CLOCK_TIME int time_format= -1; struct qserver *servers= NULL; struct qserver **last_server= &servers; struct qserver **connmap= NULL; int max_connmap; struct qserver *last_server_bind= NULL; struct qserver *first_server_bind= NULL; int connected= 0; time_t run_timeout= 0; time_t start_time; int waiting_for_masters; #define ADDRESS_HASH_LENGTH 2999 static unsigned num_servers; /* current number of servers in memory */ static struct qserver **server_hash[ADDRESS_HASH_LENGTH]; static unsigned int server_hash_len[ADDRESS_HASH_LENGTH]; static void free_server_hash(); static void xml_display_player_info_info(struct player* player); char *DOWN= "DOWN"; char *SYSERROR= "SYSERROR"; char *TIMEOUT= "TIMEOUT"; char *MASTER= "MASTER"; char *SERVERERROR= "ERROR"; char *HOSTNOTFOUND= "HOSTNOTFOUND"; char *BFRIS_SERVER_NAME= "BFRIS Server"; char *GAMESPY_MASTER_NAME= "Gamespy Master"; int display_prefix= 0; char *current_filename; int current_fileline; int count_bits( int n); static int qserver_get_timeout(struct qserver* server, struct timeval* now); static int wait_for_timeout( unsigned int ms); static void finish_output(); static void decode_stefmaster_packet( struct qserver *server, char *pkt, int pktlen); static void decode_q3master_packet( struct qserver *server, char *pkt, int pktlen); char * ut2003_strdup( const char *string, const char *end, char **next ); int html_entity( const char c, char *dest ); static const char * unreal_colors[] = { "AliceBlue", "AntiqueWhite", "Aqua", "Aquamarine", "Azure", "Beige", "Bisque", "Black", "BlanchedAlmond", "Blue", "BlueViolet", "Brown", "BurlyWood", "CadetBlue", "Chartreuse", "Chocolate", "Coral", "CornflowerBlue", "Cornsilk", "Crimson", "Cyan", "DarkBlue", "DarkCyan", "DarkGoldenrod", "DarkGray", "DarkGreen", "DarkKhaki", "DarkMagenta", "DarkOliveGreen", "DarkOrange", "DarkOrchid", "DarkRed", "DarkSalmon", "DarkSeaGreen", "DarkSlateBlue", "DarkSlateGray", "DarkTurquoise", "DarkViolet", "DeepPink", "DeepSkyBlue", "DimGray", "DodgerBlue", "FireBrick", "FloralWhite", "ForestGreen", "Fuchsia", "Gainsboro", "GhostWhite", "Gold", "Goldenrod", "Gray", "Green", "GreenYellow", "Honeydew", "HotPink", "IndianRed", "Indigo", "Ivory", "Khaki", "Lavender", "LavenderBlush", "LawnGreen", "LemonChiffon", "LightBlue", "LightCoral", "LightCyan", "LightGoldenrodYellow", "LightGreen", "LightGrey", "LightPink", "LightSalmon", "LightSeaGreen", "LightSkyBlue", "LightSlateGray", "LightSteelBlue", "LightYellow", "Lime", "LimeGreen", "Linen", "Magenta", "Maroon", "MediumAquamarine", "MediumBlue", "MediumOrchid", "MediumPurple", "MediumSeaGreen", "MediumSlateBlue", "MediumSpringGreen", "MediumTurquoise", "MediumVioletRed", "MidnightBlue", "MintCream", "MistyRose", "Moccasin", "NavajoWhite", "Navy", "OldLace", "Olive", "OliveDrab", "Orange", "OrangeRed", "Orchid", "PaleGoldenrod", "PaleGreen", "PaleTurquoise", "PaleVioletRed", "PapayaWhip", "PeachPuff", "Peru", "Pink", "Plum", "PowderBlue", "Purple", "Red", "RosyBrown", "RoyalBlue", "SaddleBrown", "Salmon", "SandyBrown", "SeaGreen", "Seashell", "Sienna", "Silver", "SkyBlue", "SlateBlue", "SlateGray", "Snow", "SpringGreen", "SteelBlue", "Tan", "Teal", "Thistle", "Tomato", "Turquoise", "Violet", "Wheat", "White", "WhiteSmoke", "Yellow", "YellowGreen", }; static const char *unreal_rgb_colors[] = { "#F0F8FF", "#FAEBD7", "#00FFFF", "#7FFFD4", "#F0FFFF", "#F5F5DC", "#FFE4C4", "#000000", "#FFEBCD", "#0000FF", "#8A2BE2", "#A52A2A", "#DEB887", "#5F9EA0", "#7FFF00", "#D2691E", "#FF7F50", "#6495ED", "#FFF8DC", "#DC143C", "#00FFFF", "#00008B", "#008B8B", "#B8860B", "#A9A9A9", "#006400", "#BDB76B", "#8B008B", "#556B2F", "#FF8C00", "#9932CC", "#8B0000", "#E9967A", "#8FBC8F", "#483D8B", "#2F4F4F", "#00CED1", "#9400D3", "#FF1493", "#00BFFF", "#696969", "#1E90FF", "#B22222", "#FFFAF0", "#228B22", "#FF00FF", "#DCDCDC", "#F8F8FF", "#FFD700", "#DAA520", "#808080", "#008000", "#ADFF2F", "#F0FFF0", "#FF69B4", "#CD5C5C", "#4B0082", "#FFFFF0", "#F0E68C", "#E6E6FA", "#FFF0F5", "#7CFC00", "#FFFACD", "#ADD8E6", "#F08080", "#E0FFFF", "#FAFAD2", "#90EE90", "#D3D3D3", "#FFB6C1", "#FFA07A", "#20B2AA", "#87CEFA", "#778899", "#B0C4DE", "#FFFFE0", "#00FF00", "#32CD32", "#FAF0E6", "#FF00FF", "#800000", "#66CDAA", "#0000CD", "#BA55D3", "#9370DB", "#3CB371", "#7B68EE", "#00FA9A", "#48D1CC", "#C71585", "#191970", "#F5FFFA", "#FFE4E1", "#FFE4B5", "#FFDEAD", "#000080", "#FDF5E6", "#808000", "#6B8E23", "#FFA500", "#FF4500", "#DA70D6", "#EEE8AA", "#98FB98", "#AFEEEE", "#DB7093", "#FFEFD5", "#FFDAB9", "#CD853F", "#FFC0CB", "#DDA0DD", "#B0E0E6", "#800080", "#FF0000", "#BC8F8F", "#4169E1", "#8B4513", "#FA8072", "#F4A460", "#2E8B57", "#FFF5EE", "#A0522D", "#C0C0C0", "#87CEEB", "#6A5ACD", "#708090", "#FFFAFA", "#00FF7F", "#4682B4", "#D2B48C", "#008080", "#D8BFD8", "#FF6347", "#40E0D0", "#EE82EE", "#F5DEB3", "#FFFFFF", "#F5F5F5", "#FFFF00", "#9ACD32", }; void free_server( struct qserver *server); void free_player( struct player *player); void free_rule( struct rule *rule); void standard_display_server( struct qserver *server); /* MODIFY HERE * Change these functions to display however you want */ void display_server( struct qserver *server) { if ( player_sort) sort_players( server); if ( raw_display) raw_display_server( server); else if ( xml_display) xml_display_server( server); else if ( have_server_template()) template_display_server( server); else standard_display_server( server); free_server( server); } void standard_display_server( struct qserver *server) { char prefix[64]; if ( display_prefix) sprintf( prefix, "%-4s ", server->type->type_prefix); else prefix[0]='\0'; if ( server->server_name == DOWN) { if ( ! up_servers_only) fprintf( OF, "%s%-16s %10s\n", prefix, (hostname_lookup) ? server->host_name : server->arg, DOWN); return; } if ( server->server_name == TIMEOUT) { if ( server->flags & FLAG_BROADCAST && server->n_servers) fprintf( OF, "%s%-16s %d servers\n", prefix, server->arg, server->n_servers); else if ( ! up_servers_only) fprintf( OF, "%s%-16s no response\n", prefix, (hostname_lookup) ? server->host_name : server->arg); return; } if ( server->type->master) { display_qwmaster(server); return; } if ( no_full_servers && server->num_players >= server->max_players) return; if ( no_empty_servers && server->num_players == 0) return; if ( server->error != NULL) { fprintf( OF, "%s%-21s ERROR <%s>\n", prefix, (hostname_lookup) ? server->host_name : server->arg, server->error); return; } if ( new_style) { char *game= get_qw_game( server); int map_name_width= 8, game_width=0; switch( server->type->id) { case QW_SERVER: case Q2_SERVER: case Q3_SERVER: game_width= 9; break; case TRIBES2_SERVER: map_name_width= 14; game_width= 8; break; case GHOSTRECON_SERVER: map_name_width= 15; game_width= 15; break; case HL_SERVER: map_name_width= 12; break; default: break; } fprintf( OF, "%s%-21s %2d/%2d %*s %6d / %1d %*s %s\n", prefix, (hostname_lookup) ? server->host_name : server->arg, server->num_players, server->max_players, map_name_width, (server->map_name) ? server->map_name : "?", server->n_requests ? server->ping_total/server->n_requests : 999, server->n_retries, game_width, game, xform_name(server->server_name, server)); if ( get_server_rules) server->type->display_rule_func( server); if ( get_player_info) server->type->display_player_func( server); } else { char name[512]; sprintf( name, "\"%s\"", server->server_name); fprintf( OF, "%-16s %10s map %s at %22s %d/%d players %d ms\n", (hostname_lookup) ? server->host_name : server->arg, name, server->map_name, server->address, server->num_players, server->max_players, server->n_requests ? server->ping_total/server->n_requests : 999); } } void display_qwmaster( struct qserver *server) { char *prefix; prefix= server->type->type_prefix; if ( server->error != NULL) fprintf( OF, "%s %-17s ERROR <%s>\n", prefix, (hostname_lookup) ? server->host_name : server->arg, server->error); else fprintf( OF, "%s %-17s %d servers %6d / %1d\n", prefix, (hostname_lookup) ? server->host_name : server->arg, server->n_servers, server->n_requests ? server->ping_total/server->n_requests : 999, server->n_retries); } void display_header() { if ( ! no_header_display) fprintf( OF, "%-16s %8s %8s %15s %s\n", "ADDRESS", "PLAYERS", "MAP", "RESPONSE TIME", "NAME"); } void display_server_rules( struct qserver *server) { struct rule *rule; int printed= 0; rule= server->rules; for ( ; rule != NULL; rule= rule->next) { if ( (server->type->id != Q_SERVER && server->type->id != H2_SERVER) || ! is_default_rule( rule)) { fprintf( OF, "%c%s=%s", (printed)?',':'\t', rule->name, rule->value); printed++; } } if ( printed) fputs( "\n", OF); } void display_q_player_info( struct qserver *server) { char fmt[128]; struct player *player; strcpy( fmt, "\t#%-2d %3d frags %9s "); if ( color_names) strcat( fmt, "%9s:%-9s "); else strcat( fmt, "%2s:%-2s "); if ( player_address) strcat( fmt, "%22s "); else strcat( fmt, "%s"); strcat( fmt, "%s\n"); player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, fmt, player->number, player->frags, play_time(player->connect_time,1), quake_color(player->shirt_color), quake_color(player->pants_color), (player_address)?player->address:"", xform_name( player->name, server)); } } void display_qw_player_info( struct qserver *server) { char fmt[128]; struct player *player; strcpy( fmt, "\t#%-6d %3d frags %6s@%-5s %8s"); if ( color_names) strcat( fmt, "%9s:%-9s "); else strcat( fmt, "%2s:%-2s "); strcat( fmt, "%s\n"); player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, fmt, player->number, player->frags, play_time(player->connect_time,0), ping_time(player->ping), player->skin ? player->skin : "", quake_color(player->shirt_color), quake_color(player->pants_color), xform_name( player->name, server)); } } void display_q2_player_info( struct qserver *server) { struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { if ( server->flags & FLAG_PLAYER_TEAMS) fprintf( OF, "\t%3d frags team#%d %8s %s\n", player->frags, player->team, ping_time(player->ping), xform_name( player->name, server)); else fprintf( OF, "\t%3d frags %8s %s\n", player->frags, ping_time(player->ping), xform_name( player->name, server)); } } void display_unreal_player_info( struct qserver *server) { struct player *player; static const char * fmt_team_number= "\t%3d frags team#%-3d %7s %s\n"; static const char * fmt_team_name= "\t%3d frags %8s %7s %s\n"; static const char * fmt_no_team= "\t%3d frags %8s %s\n"; player= server->players; for ( ; player != NULL; player= player->next) { if ( server->flags & FLAG_PLAYER_TEAMS) { // we use (player->score) ? player->score : player->frags, // so we get details from halo if ( player->team_name) { fprintf( OF, fmt_team_name, (player->score && NA_INT != player->score ) ? player->score : player->frags, player->team_name, ping_time(player->ping), xform_name( player->name, server) ); } else { fprintf( OF, fmt_team_number, (player->score && NA_INT != player->score ) ? player->score : player->frags, player->team, ping_time(player->ping), xform_name( player->name, server) ); } } else { fprintf( OF, fmt_no_team, player->frags, ping_time(player->ping), xform_name( player->name, server) ); } } } void display_shogo_player_info( struct qserver *server) { struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t%3d frags %8s %s\n", player->frags, ping_time(player->ping), xform_name( player->name, server)); } } void display_halflife_player_info( struct qserver *server) { struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t%3d frags %8s %s\n", player->frags, play_time( player->connect_time,1), xform_name( player->name, server)); } } void display_tribes_player_info( struct qserver *server) { struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t%4d score team#%d %8s %s\n", player->frags, player->team, ping_time( player->ping), xform_name( player->name, server)); } } void display_tribes2_player_info( struct qserver *server) { struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\tscore %4d %14s %s\n", player->frags, player->team_name ? player->team_name : (player->number == TRIBES_TEAM ? "TEAM" : "?"), xform_name( player->name, server)); } } void display_bfris_player_info( struct qserver *server) { struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\ttid: %d, ship: %d, team: %s, ping: %d, score: %d, kills: %d, name: %s\n", player->number, player->ship, player->team_name, player->ping, player->score, player->frags, xform_name( player->name, server)); } } void display_descent3_player_info( struct qserver *server) { struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t%3d frags %3d deaths team#%-3d %7s %s\n", player->frags, player->deaths, player->team, ping_time(player->ping), xform_name( player->name, server)); } } void display_ghostrecon_player_info( struct qserver *server) { struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\tdead=%3d team#%-3d %s\n", player->deaths, player->team, xform_name( player->name, server)); } } void display_eye_player_info( struct qserver *server) { struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { if ( player->team_name) fprintf( OF, "\tscore %4d %6s team %12s %s\n", player->score, ping_time(player->ping), player->team_name, xform_name( player->name, server)); else fprintf( OF, "\tscore %4d %6s team#%d %s\n", player->score, ping_time(player->ping), player->team, xform_name( player->name, server)); } } int calculate_armyops_score( struct player *player) { /* Calculates a player's score for ArmyOps from the basic components */ int score = 0; int kill_score = 0; struct info *info; for ( info = player->info; info; info = info->next ) { if ( 0 == strcmp( info->name, "leader") || 0 == strcmp( info->name, "goal") || 0 == strcmp( info->name, "roe") ) score += atoi( info->value ); else if ( 0 == strcmp( info->name, "kia") || 0 == strcmp( info->name, "enemy") ) kill_score += atoi( info->value ); } if ( kill_score > 0 ) score += kill_score; return score; } void display_gs2_player_info( struct qserver *server) { struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { if ( player->team_name) fprintf( OF, "\tscore %4d %6s team %12s %s\n", player->score, ping_time(player->ping), player->team_name, xform_name( player->name, server)); else fprintf( OF, "\tscore %4d %6s team#%d %s\n", player->score, ping_time(player->ping), player->team, xform_name( player->name, server)); } } void display_armyops_player_info( struct qserver *server) { struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { player->score = calculate_armyops_score( player ); } display_gs2_player_info(server); } void display_ts2_player_info( struct qserver *server) { struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t%6s %s\n", ping_time(player->ping), xform_name( player->name, server) ); } } void display_tm_player_info( struct qserver *server) { struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t%6s %s\n", ping_time(player->ping), xform_name( player->name, server) ); } } void display_doom3_player_info( struct qserver *server) { struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { if ( player->tribe_tag) fprintf( OF, "\t#%-4d score %4d %6s team %12s %s\n", player->number, player->score, ping_time(player->ping), player->tribe_tag, xform_name( player->name, server)); else fprintf( OF, "\t#%-4d score %4d %6s team#%d %s\n", player->number, player->score, ping_time(player->ping), player->team, xform_name( player->name, server)); } } void display_hl2_player_info( struct qserver *server ) { // ATM this looks like halflife player info display_halflife_player_info( server ); } void display_ravenshield_player_info( struct qserver *server) { struct player *player = server->players; for ( ; player != NULL; player = player->next ) { fprintf( OF, "\t%3d frags %8s %s\n", player->frags, play_time( player->connect_time,1), xform_name( player->name, server)); } } void display_savage_player_info( struct qserver *server) { struct player *player = server->players; for ( ; player != NULL; player = player->next ) { fprintf( OF, "\t%3d frags %8s %s\n", player->frags, play_time( player->connect_time,1), xform_name( player->name, server)); } } void display_farcry_player_info( struct qserver *server) { struct player *player = server->players; for ( ; player != NULL; player = player->next ) { fprintf( OF, "\t%3d frags %8s %s\n", player->frags, play_time( player->connect_time,1), xform_name( player->name, server)); } } char * get_qw_game( struct qserver *server) { struct rule *rule; if ( server->type->game_rule == NULL || *server->type->game_rule == '\0') { return ""; } rule = server->rules; for ( ; rule != NULL; rule = rule->next) { if ( strcmp( rule->name, server->type->game_rule) == 0) { if ( server->type->id == Q3_SERVER && strcmp( rule->value, "baseq3") == 0) { return ""; } return rule->value; } } rule= server->rules; for ( ; rule != NULL; rule = rule->next) { if ( strcmp( rule->name, "game" ) == 0) { return rule->value; } } return ""; } /* Raw output for web master types */ #define RD raw_delimiter void raw_display_server( struct qserver *server) { char *prefix; int ping_time; prefix= server->type->type_prefix; if ( server->n_requests) ping_time= server->ping_total/server->n_requests; else ping_time= 999; if ( server->server_name == DOWN) { if ( ! up_servers_only) fprintf( OF, "%s" "%.*s%.*s" "%s%s" "%s%s\n\n", prefix, raw_arg, RD, raw_arg, server->arg, RD, (hostname_lookup)?server->host_name:server->arg, RD, DOWN); return; } if ( server->server_name == TIMEOUT) { if ( server->flags & FLAG_BROADCAST && server->n_servers) fprintf( OF, "%s" "%.*s%.*s" "%s%s" "%s%d\n", prefix, raw_arg, RD, raw_arg, server->arg, RD, server->arg, RD, server->n_servers); else if ( ! up_servers_only) fprintf( OF, "%s" "%.*s%.*s" "%s%s" "%s%s\n\n", prefix, raw_arg, RD, raw_arg, server->arg, RD, (hostname_lookup)?server->host_name:server->arg, RD, TIMEOUT); return; } if ( server->error != NULL) { fprintf( OF, "%s" "%.*s%.*s" "%s%s" "%s%s" "%s%s", prefix, raw_arg, RD, raw_arg, server->arg, RD, (hostname_lookup) ? server->host_name : server->arg, RD, "ERROR", RD, server->error); } else if ( server->type->flags & TF_RAW_STYLE_QUAKE) { fprintf( OF, "%s" "%.*s%.*s" "%s%s" "%s%s" "%s%s" "%s%d" "%s%s" "%s%d" "%s%d" "%s%d" "%s%d" "%s%s", prefix, raw_arg, RD, raw_arg, server->arg, RD, (hostname_lookup) ? server->host_name : server->arg, RD, xform_name( server->server_name, server), RD, server->address, RD, server->protocol_version, RD, server->map_name, RD, server->max_players, RD, server->num_players, RD, ping_time, RD, server->n_retries, show_game_in_raw ? RD : "", show_game_in_raw ? get_qw_game(server) : "" ); } else if ( server->type->flags & TF_RAW_STYLE_TRIBES) { fprintf( OF, "%s" "%.*s%.*s" "%s%s" "%s%s" "%s%s" "%s%d" "%s%d", prefix, raw_arg, RD, raw_arg, server->arg, RD, (hostname_lookup) ? server->host_name : server->arg, RD, xform_name( server->server_name, server), RD, (server->map_name) ? server->map_name : "?", RD, server->num_players, RD, server->max_players ); } else if ( server->type->flags & TF_RAW_STYLE_GHOSTRECON) { fprintf( OF, "%s" "%.*s%.*s" "%s%s" "%s%s" "%s%s" "%s%d" "%s%d", prefix, raw_arg, RD, raw_arg, server->arg, RD, (hostname_lookup) ? server->host_name : server->arg, RD, xform_name( server->server_name, server), RD, (server->map_name) ? server->map_name : "?", RD, server->num_players, RD, server->max_players ); } else if ( server->type->master) { fprintf( OF, "%s" "%.*s%.*s" "%s%s" "%s%d", prefix, raw_arg, RD, raw_arg, server->arg, RD, (hostname_lookup) ? server->host_name : server->arg, RD, server->n_servers ); } else { fprintf( OF, "%s" "%.*s%.*s" "%s%s" "%s%s" "%s%s" "%s%d" "%s%d" "%s%d" "%s%d" "%s%s", prefix, raw_arg, RD, raw_arg, server->arg, RD, (hostname_lookup) ? server->host_name : server->arg, RD, xform_name( server->server_name, server), RD, (server->map_name) ? server->map_name : "?", RD, server->max_players, RD, server->num_players, RD, ping_time, RD, server->n_retries, show_game_in_raw ? RD : "", show_game_in_raw ? get_qw_game(server) : "" ); } fputs( "\n", OF); if ( server->type->master || server->error != NULL) { fputs( "\n", OF); return; } if ( get_server_rules) server->type->display_raw_rule_func( server); if ( get_player_info) server->type->display_raw_player_func( server); fputs( "\n", OF); } void raw_display_server_rules( struct qserver *server) { struct rule *rule; int printed= 0; rule= server->rules; for ( ; rule != NULL; rule= rule->next) { if ( server->type->id == TRIBES2_SERVER) { char *v; for ( v= rule->value; *v; v++) if ( *v == '\n') *v= ' '; } fprintf( OF, "%s%s=%s", (printed)?RD:"", rule->name, rule->value); printed++; } if ( server->missing_rules) fprintf( OF, "%s?", (printed)?RD:""); fputs( "\n", OF); } void raw_display_q_player_info( struct qserver *server) { char fmt[] = "%d" "%s%s" "%s%s" "%s%d" "%s%s" "%s%s" "%s%s"; struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, fmt, player->number, RD, xform_name( player->name, server), RD, player->address, RD, player->frags, RD, play_time(player->connect_time,1), RD, quake_color(player->shirt_color), RD, quake_color(player->pants_color) ); fputs( "\n", OF); } } void raw_display_qw_player_info( struct qserver *server) { char fmt[128]; struct player *player; strcpy( fmt, "%d" "%s%s" "%s%d" "%s%s" "%s%s" "%s%s"); strcat( fmt, "%s%d" "%s%s"); player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, fmt, player->number, RD, xform_name( player->name, server), RD, player->frags, RD, play_time(player->connect_time,1), RD, quake_color(player->shirt_color), RD, quake_color(player->pants_color), RD, player->ping, RD, player->skin ? player->skin : "" ); fputs( "\n", OF); } } void raw_display_q2_player_info( struct qserver *server) { static const char *fmt = "%s" "%s%d" "%s%d"; static const char *fmt_team = "%s" "%s%d" "%s%d" "%s%d"; struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { if ( server->flags & FLAG_PLAYER_TEAMS) fprintf( OF, fmt_team, xform_name( player->name, server), RD, player->frags, RD, player->ping, RD, player->team ); else fprintf( OF, fmt, xform_name( player->name, server), RD, player->frags, RD, player->ping ); fputs( "\n", OF); } } void raw_display_unreal_player_info( struct qserver *server) { static const char *fmt= "%s" "%s%d" "%s%d" "%s%d" "%s%s" "%s%s" "%s%s"; static const char *fmt_team_name= "%s" "%s%d" "%s%d" "%s%s" "%s%s" "%s%s" "%s%s"; struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { if ( player->team_name) fprintf( OF, fmt_team_name, xform_name( player->name, server), RD, player->frags, RD, player->ping, RD, player->team_name, RD, player->skin ? player->skin : "", RD, player->mesh ? player->mesh : "", RD, player->face ? player->face : "" ); else fprintf( OF, fmt, xform_name( player->name, server), RD, player->frags, RD, player->ping, RD, player->team, RD, player->skin ? player->skin : "", RD, player->mesh ? player->mesh : "", RD, player->face ? player->face : "" ); fputs( "\n", OF); } } void raw_display_halflife_player_info( struct qserver *server) { static char fmt[24]= "%s" "%s%d" "%s%s"; struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, fmt, xform_name( player->name, server), RD, player->frags, RD, play_time( player->connect_time,1) ); fputs( "\n", OF); } } void raw_display_tribes_player_info( struct qserver *server) { static char fmt[24]= "%s" "%s%d" "%s%d" "%s%d" "%s%d"; struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, fmt, xform_name( player->name, server), RD, player->frags, RD, player->ping, RD, player->team, RD, player->packet_loss ); fputs( "\n", OF); } } void raw_display_tribes2_player_info( struct qserver *server) { static char fmt[]= "%s" "%s%d" "%s%d" "%s%s" "%s%s" "%s%s"; struct player *player; char *type; player= server->players; for ( ; player != NULL; player= player->next) { switch( player->type_flag) { case PLAYER_TYPE_BOT: type= "Bot"; break; case PLAYER_TYPE_ALIAS: type= "Alias"; break; default: type= ""; break; } fprintf( OF, fmt, xform_name( player->name, server), RD, player->frags, RD, player->team, RD, player->team_name ? player->team_name : "TEAM", RD, type, RD, player->tribe_tag ? xform_name(player->tribe_tag,server) : "" ); fputs( "\n", OF); } } void raw_display_bfris_player_info( struct qserver *server) { static char fmt[] = "%d" "%s%d" "%s%s" "%s%d" "%s%d" "%s%d" "%s%s"; struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, fmt, player->number, RD, player->ship, RD, player->team_name, RD, player->ping, RD, player->score, RD, player->frags, RD, xform_name( player->name, server) ); fputs( "\n", OF); } } void raw_display_descent3_player_info( struct qserver *server) { static char fmt[]= "%s" "%s%d" "%s%d" "%s%d" "%s%d"; struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, fmt, xform_name( player->name, server), RD, player->frags, RD, player->deaths, RD, player->ping, RD, player->team ); fputs( "\n", OF); } } void raw_display_ghostrecon_player_info( struct qserver *server) { static char fmt[28]= "%s" "%s%d" "%s%d"; struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, fmt, xform_name( player->name, server), RD, player->deaths, RD, player->team ); fputs( "\n", OF); } } void raw_display_eye_player_info( struct qserver *server) { static const char *fmt= "%s" "%s%d" "%s%d" "%s%d" "%s%s" "%s%s"; static const char *fmt_team_name= "%s" "%s%d" "%s%d" "%s%s" "%s%s" "%s%s"; struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { if ( player->team_name) fprintf( OF, fmt_team_name, xform_name( player->name, server), RD, player->score, RD, player->ping, RD, player->team_name, RD, player->skin ? player->skin : "", RD, play_time( player->connect_time,1) ); else fprintf( OF, fmt, xform_name( player->name, server), RD, player->score, RD, player->ping, RD, player->team, RD, player->skin ? player->skin : "", RD, play_time( player->connect_time,1) ); fputs( "\n", OF); } } void raw_display_doom3_player_info( struct qserver *server) { static const char *fmt= "%s" "%s%d" "%s%d" "%s%d" "%s%u"; static const char *fmt_team_name= "%s" "%s%d" "%s%d" "%s%s" "%s%u"; struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { if ( player->tribe_tag) fprintf( OF, fmt_team_name, xform_name( player->name, server), RD, player->score, RD, player->ping, RD, player->tribe_tag, RD, player->number ); else fprintf( OF, fmt, xform_name( player->name, server), RD, player->score, RD, player->ping, RD, player->team, RD, player->number ); fputs( "\n", OF); } } void raw_display_hl2_player_info( struct qserver *server ) { // ATM this looks like halflife player info raw_display_halflife_player_info( server ); } void raw_display_gs2_player_info( struct qserver *server) { static const char *fmt= "%s" "%s%d" "%s%d" "%s%d" "%s%s" "%s%s"; static const char *fmt_team_name= "%s" "%s%d" "%s%d" "%s%s" "%s%s" "%s%s"; struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { if ( player->team_name) fprintf( OF, fmt_team_name, xform_name( player->name, server), RD, player->score, RD, player->ping, RD, player->team_name, RD, player->skin ? player->skin : "", RD, play_time( player->connect_time,1) ); else fprintf( OF, fmt, xform_name( player->name, server), RD, player->score, RD, player->ping, RD, player->team, RD, player->skin ? player->skin : "", RD, play_time( player->connect_time,1) ); fputs( "\n", OF); } } void raw_display_armyops_player_info( struct qserver *server) { struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { player->score = calculate_armyops_score( player ); } raw_display_gs2_player_info(server); } void raw_display_ts2_player_info( struct qserver *server) { static const char *fmt= "%s" "%s%d" "%s%s" "%s%s"; struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, fmt, xform_name( player->name, server), RD, player->ping, RD, player->skin ? player->skin : "", RD, play_time( player->connect_time, 1 ) ); fputs( "\n", OF); } } void raw_display_tm_player_info( struct qserver *server) { static const char *fmt= "%s" "%s%d" "%s%s" "%s%s"; struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, fmt, xform_name( player->name, server), RD, player->ping, RD, player->skin ? player->skin : "", RD, play_time( player->connect_time, 1 ) ); fputs( "\n", OF); } } void raw_display_ravenshield_player_info( struct qserver *server) { static char fmt[24]= "%s" "%s%d" "%s%s"; struct player *player = server->players; for ( ; player != NULL; player= player->next ) { fprintf( OF, fmt, xform_name( player->name, server), RD, player->frags, RD, play_time( player->connect_time,1) ); fputs( "\n", OF); } } void raw_display_savage_player_info( struct qserver *server) { static char fmt[24]= "%s" "%s%d" "%s%s"; struct player *player = server->players; for ( ; player != NULL; player= player->next ) { fprintf( OF, fmt, xform_name( player->name, server), RD, player->frags, RD, play_time( player->connect_time,1) ); fputs( "\n", OF); } } void raw_display_farcry_player_info( struct qserver *server) { static char fmt[24]= "%s" "%s%d" "%s%s"; struct player *player = server->players; for ( ; player != NULL; player= player->next ) { fprintf( OF, fmt, xform_name( player->name, server), RD, player->frags, RD, play_time( player->connect_time,1) ); fputs( "\n", OF); } } /* XML output * Contributed by :-) */ void xml_display_server( struct qserver *server) { char *prefix; prefix= server->type->type_prefix; if ( server->server_name == DOWN) { if ( ! up_servers_only) { fprintf( OF, "\t\n", xml_escape(prefix), xml_escape(server->arg), xml_escape(DOWN)); fprintf( OF, "\t\t%s\n", xml_escape((hostname_lookup)?server->host_name:server->arg)); fprintf( OF, "\t\n"); } return; } if ( server->server_name == TIMEOUT) { if ( server->flags & FLAG_BROADCAST && server->n_servers) { fprintf( OF, "\t\n", xml_escape(prefix), xml_escape(server->arg), xml_escape(TIMEOUT), server->n_servers); fprintf( OF, "\t\n"); } else if ( ! up_servers_only) { fprintf( OF, "\t\n", xml_escape(prefix), xml_escape(server->arg), xml_escape(TIMEOUT)); fprintf( OF, "\t\t%s\n", xml_escape((hostname_lookup)?server->host_name:server->arg)); fprintf( OF, "\t\n"); } return; } if ( server->error != NULL) { fprintf( OF, "\t\n", xml_escape(prefix), xml_escape(server->arg), "ERROR"); fprintf( OF, "\t\t%s\n", xml_escape((hostname_lookup)?server->host_name:server->arg)); fprintf( OF, "\t\t%s\n", xml_escape(server->error)); } else if ( server->type->master) { fprintf( OF, "\t\n", xml_escape(prefix), xml_escape(server->arg), "UP", server->n_servers); } else { fprintf( OF, "\t\n", xml_escape(prefix), xml_escape(server->arg), "UP"); fprintf( OF, "\t\t%s\n", xml_escape((hostname_lookup)?server->host_name:server->arg)); fprintf( OF, "\t\t%s\n", xml_escape(xform_name( server->server_name, server))); fprintf( OF, "\t\t%s\n", xml_escape(get_qw_game(server))); fprintf( OF, "\t\t%s\n", xml_escape(server->map_name)); fprintf( OF, "\t\t%d\n", server->num_players); fprintf( OF, "\t\t%d\n", server->max_players); if ( !(server->type->flags & TF_RAW_STYLE_TRIBES)) { fprintf( OF, "\t\t%d\n", server->n_requests ? server->ping_total/server->n_requests : 999); fprintf( OF, "\t\t%d\n", server->n_retries); } if ( server->type->flags & TF_RAW_STYLE_QUAKE) { fprintf( OF, "\t\t
%s
\n", xml_escape(server->address)); fprintf( OF, "\t\t%d\n", server->protocol_version); } } if ( ! server->type->master && server->error == NULL) { if ( get_server_rules) server->type->display_xml_rule_func( server); if ( get_player_info) server->type->display_xml_player_func( server); } fprintf( OF, "\t
\n"); } void xml_header() { fprintf( OF, "\n\n", xml_encoding == ENCODING_LATIN_1 ? "iso-8859-1" : "UTF-8"); } void xml_footer() { fprintf( OF, "\n"); } void xml_display_server_rules( struct qserver *server) { struct rule *rule; rule= server->rules; fprintf( OF, "\t\t\n"); for ( ; rule != NULL; rule= rule->next) { fprintf( OF, "\t\t\t%s\n", xml_escape(rule->name), xml_escape(rule->value)); } fprintf( OF, "\t\t\n"); } void xml_display_q_player_info( struct qserver *server) { struct player *player; fprintf( OF, "\t\t\n"); player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t\t\t\n", player->number); fprintf( OF, "\t\t\t\t%s\n", xml_escape(xform_name( player->name, server))); fprintf( OF, "\t\t\t\t
%s
\n", xml_escape(player->address)); fprintf( OF, "\t\t\t\t%d\n", player->frags); fprintf( OF, "\t\t\t\t\n", xml_escape(play_time(player->connect_time,1))); if ( color_names) { fprintf( OF, "\t\t\t\t%s\n", xml_escape(quake_color(player->shirt_color))); fprintf( OF, "\t\t\t\t%s\n", xml_escape(quake_color(player->pants_color))); } else { fprintf( OF, "\t\t\t\t%s\n", quake_color(player->shirt_color)); fprintf( OF, "\t\t\t\t%s\n", quake_color(player->pants_color)); } fprintf( OF, "\t\t\t
\n"); } fprintf( OF, "\t\t
\n"); } void xml_display_qw_player_info( struct qserver *server) { struct player *player; fprintf( OF, "\t\t\n"); player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t\t\t\n", player->number); fprintf( OF, "\t\t\t\t%s\n", xml_escape(xform_name( player->name, server))); fprintf( OF, "\t\t\t\t%d\n", player->frags); fprintf( OF, "\t\t\t\t\n", xml_escape(play_time(player->connect_time,1))); if ( color_names) { fprintf( OF, "\t\t\t\t%s\n", xml_escape(quake_color(player->shirt_color))); fprintf( OF, "\t\t\t\t%s\n", xml_escape(quake_color(player->pants_color))); } else { fprintf( OF, "\t\t\t\t%s\n", quake_color(player->shirt_color)); fprintf( OF, "\t\t\t\t%s\n", quake_color(player->pants_color)); } fprintf( OF, "\t\t\t\t%d\n", player->ping); fprintf( OF, "\t\t\t\t%s\n", player->skin ? xml_escape(player->skin) : ""); fprintf( OF, "\t\t\t\n"); } fprintf( OF, "\t\t\n"); } void xml_display_q2_player_info( struct qserver *server) { struct player *player; fprintf( OF, "\t\t\n"); player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t\t\t\n"); fprintf( OF, "\t\t\t\t%s\n", xml_escape(xform_name( player->name, server))); fprintf( OF, "\t\t\t\t%d\n", player->frags); if ( server->flags & FLAG_PLAYER_TEAMS) fprintf( OF, "\t\t\t\t%d\n", player->team); fprintf( OF, "\t\t\t\t%d\n", player->ping); fprintf( OF, "\t\t\t\n"); } fprintf( OF, "\t\t\n"); } void xml_display_player_info_info(struct player* player) { struct info *info; for (info = player->info; info; info = info->next ) { if ( info->name ) { char *name = xml_escape( info->name ); char *value = xml_escape( info->value ); fprintf( OF, "\t\t\t\t<%s>%s\n", name, value, name ); } } } void xml_display_unreal_player_info( struct qserver *server) { struct player *player; fprintf( OF, "\t\t\n"); player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t\t\t\n"); fprintf( OF, "\t\t\t\t%s\n", xml_escape(xform_name( player->name, server))); fprintf( OF, "\t\t\t\t%d\n", player->frags); if ( -999 != player->deaths ) { fprintf( OF, "\t\t\t\t%d\n", player->deaths); } fprintf( OF, "\t\t\t\t%d\n", player->ping); if ( player->team_name) { fprintf( OF, "\t\t\t\t%s\n", xml_escape(player->team_name)); } else if ( -1 != player->team ) { fprintf( OF, "\t\t\t\t%d\n", player->team); } // Some games dont provide // so only display if they do if ( player->skin ) { fprintf( OF, "\t\t\t\t%s\n", player->skin ? xml_escape(player->skin) : ""); } if ( player->mesh ) { fprintf( OF, "\t\t\t\t%s\n", player->mesh ? xml_escape(player->mesh) : ""); } if ( player->face ) { fprintf( OF, "\t\t\t\t%s\n", player->face ? xml_escape(player->face) : ""); } xml_display_player_info_info(player); fprintf( OF, "\t\t\t\n"); } fprintf( OF, "\t\t\n"); } void xml_display_halflife_player_info( struct qserver *server) { struct player *player; fprintf( OF, "\t\t\n"); player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t\t\t\n"); fprintf( OF, "\t\t\t\t%s\n", xml_escape(xform_name( player->name, server))); fprintf( OF, "\t\t\t\t%d\n", player->frags); fprintf( OF, "\t\t\t\t\n", xml_escape(play_time( player->connect_time,1))); fprintf( OF, "\t\t\t\n"); } fprintf( OF, "\t\t\n"); } void xml_display_tribes_player_info( struct qserver *server) { struct player *player; fprintf( OF, "\t\t\n"); player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t\t\t\n"); fprintf( OF, "\t\t\t\t%s\n", xml_escape(xform_name( player->name, server))); fprintf( OF, "\t\t\t\t%d\n", player->frags); fprintf( OF, "\t\t\t\t%d\n", player->team); fprintf( OF, "\t\t\t\t%d\n", player->ping); fprintf( OF, "\t\t\t\t%d\n", player->packet_loss); fprintf( OF, "\t\t\t\n"); } fprintf( OF, "\t\t\n"); } void xml_display_tribes2_player_info( struct qserver *server) { struct player *player; char *type; fprintf( OF, "\t\t\n"); player= server->players; for ( ; player != NULL; player= player->next) { if ( player->team_name) { switch( player->type_flag) { case PLAYER_TYPE_BOT: type= "Bot"; break; case PLAYER_TYPE_ALIAS: type= "Alias"; break; default: type= ""; break; } fprintf( OF, "\t\t\t\n"); fprintf( OF, "\t\t\t\t%s\n", xml_escape(xform_name( player->name, server))); fprintf( OF, "\t\t\t\t%d\n", player->frags); fprintf( OF, "\t\t\t\t%s\n", player->team, xml_escape(player->team_name)); fprintf( OF, "\t\t\t\t%s\n", xml_escape(type)); fprintf( OF, "\t\t\t\t%s\n", player->tribe_tag ? xml_escape(xform_name(player->tribe_tag,server)) : ""); fprintf( OF, "\t\t\t\n"); } } fprintf( OF, "\t\t\n"); } void xml_display_bfris_player_info( struct qserver *server) { struct player *player; fprintf( OF, "\t\t\n"); player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t\t\t\n", player->number); fprintf( OF, "\t\t\t\t%s\n", xml_escape(xform_name( player->name, server))); fprintf( OF, "\t\t\t\t%d\n", player->score); fprintf( OF, "\t\t\t\t%d\n", player->frags); fprintf( OF, "\t\t\t\t%s\n", xml_escape(player->team_name)); fprintf( OF, "\t\t\t\t%d\n", player->ping); fprintf( OF, "\t\t\t\t%d\n", player->ship); fprintf( OF, "\t\t\t\n"); } fprintf( OF, "\t\t\n"); } void xml_display_descent3_player_info( struct qserver *server) { struct player *player; fprintf( OF, "\t\t\n"); player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t\t\t\n"); fprintf( OF, "\t\t\t\t%s\n", xml_escape(xform_name( player->name, server))); fprintf( OF, "\t\t\t\t%d\n", player->frags); fprintf( OF, "\t\t\t\t%d\n", player->deaths); fprintf( OF, "\t\t\t\t%d\n", player->ping); fprintf( OF, "\t\t\t\t%d\n", player->team); fprintf( OF, "\t\t\t\n"); } fprintf( OF, "\t\t\n"); } void xml_display_ravenshield_player_info( struct qserver *server) { struct player *player; fprintf( OF, "\t\t\n"); player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t\t\t\n"); fprintf( OF, "\t\t\t\t%s\n", xml_escape(xform_name( player->name, server))); fprintf( OF, "\t\t\t\t%d\n", player->frags); fprintf( OF, "\t\t\t\t\n", xml_escape(play_time( player->connect_time,1))); fprintf( OF, "\t\t\t\n"); } fprintf( OF, "\t\t\n"); } void xml_display_ghostrecon_player_info( struct qserver *server) { struct player *player; fprintf( OF, "\t\t\n"); player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t\t\t\n"); fprintf( OF, "\t\t\t\t%s\n", xml_escape(xform_name( player->name, server))); fprintf( OF, "\t\t\t\t%d\n", player->deaths); fprintf( OF, "\t\t\t\t%d\n", player->team); fprintf( OF, "\t\t\t\n"); } fprintf( OF, "\t\t\n"); } void xml_display_eye_player_info( struct qserver *server) { struct player *player; fprintf( OF, "\t\t\n"); player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t\t\t\n"); fprintf( OF, "\t\t\t\t%s\n", xml_escape(xform_name( player->name, server))); fprintf( OF, "\t\t\t\t%d\n", player->score); fprintf( OF, "\t\t\t\t%d\n", player->ping); if ( player->team_name) fprintf( OF, "\t\t\t\t%s\n", xml_escape(player->team_name)); else fprintf( OF, "\t\t\t\t%d\n", player->team); if ( player->skin) fprintf( OF, "\t\t\t\t%s\n", xml_escape(player->skin)); if ( player->connect_time) fprintf( OF, "\t\t\t\t\n", xml_escape(play_time( player->connect_time,1))); fprintf( OF, "\t\t\t\n"); } fprintf( OF, "\t\t\n"); } void xml_display_doom3_player_info( struct qserver *server) { struct player *player; fprintf( OF, "\t\t\n"); player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t\t\t\n"); fprintf( OF, "\t\t\t\t%u\n", player->number); fprintf( OF, "\t\t\t\t%s\n", xml_escape(xform_name( player->name, server))); fprintf( OF, "\t\t\t\t%d\n", player->score); fprintf( OF, "\t\t\t\t%d\n", player->ping); if ( player->tribe_tag) fprintf( OF, "\t\t\t\t%s\n", player->tribe_tag ? xml_escape(xform_name(player->tribe_tag,server)) : ""); else fprintf( OF, "\t\t\t\t%d\n", player->team); if ( player->skin) fprintf( OF, "\t\t\t\t%s\n", xml_escape(player->skin)); if ( player->connect_time) fprintf( OF, "\t\t\t\t\n", xml_escape(play_time( player->connect_time,1))); xml_display_player_info_info(player); fprintf( OF, "\t\t\t\n"); } fprintf( OF, "\t\t\n"); } void xml_display_hl2_player_info( struct qserver *server ) { // ATM this looks like halflife player info xml_display_halflife_player_info( server ); } void xml_display_gs2_player_info( struct qserver *server) { struct player *player; fprintf( OF, "\t\t\n"); player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t\t\t\n"); fprintf( OF, "\t\t\t\t%s\n", xml_escape(xform_name( player->name, server))); fprintf( OF, "\t\t\t\t%d\n", player->ping); if ( NA_INT != player->score ) { fprintf( OF, "\t\t\t\t%d\n", player->score); } if ( NA_INT != player->deaths ) { fprintf( OF, "\t\t\t\t%d\n", player->deaths); } if ( NA_INT != player->frags ) { fprintf( OF, "\t\t\t\t%d\n", player->frags); } if ( player->team_name ) { fprintf( OF, "\t\t\t\t%s\n", xml_escape(player->team_name)); } else { fprintf( OF, "\t\t\t\t%d\n",player->team); } if ( player->skin) { fprintf( OF, "\t\t\t\t%s\n", xml_escape(player->skin)); } if ( player->connect_time) { fprintf( OF, "\t\t\t\t\n", xml_escape(play_time( player->connect_time,1))); } xml_display_player_info_info(player); fprintf( OF, "\t\t\t\n"); } fprintf( OF, "\t\t\n"); } void xml_display_armyops_player_info( struct qserver *server) { struct player *player; player= server->players; for ( ; player != NULL; player= player->next) { player->score = calculate_armyops_score( player ); } xml_display_gs2_player_info(server); } void xml_display_ts2_player_info( struct qserver *server) { struct player *player; fprintf( OF, "\t\t\n"); player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t\t\t\n"); fprintf( OF, "\t\t\t\t%s\n", xml_escape(xform_name( player->name, server))); fprintf( OF, "\t\t\t\t%d\n", player->ping); if ( player->connect_time ) { fprintf( OF, "\t\t\t\t\n", xml_escape(play_time( player->connect_time,1))); } xml_display_player_info_info(player); fprintf( OF, "\t\t\t\n"); } fprintf( OF, "\t\t\n"); } void xml_display_tm_player_info( struct qserver *server) { struct player *player; fprintf( OF, "\t\t\n"); player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t\t\t\n"); fprintf( OF, "\t\t\t\t%s\n", xml_escape(xform_name( player->name, server))); fprintf( OF, "\t\t\t\t%d\n", player->ping); if ( player->connect_time ) { fprintf( OF, "\t\t\t\t\n", xml_escape(play_time( player->connect_time,1))); } xml_display_player_info_info(player); fprintf( OF, "\t\t\t\n"); } fprintf( OF, "\t\t\n"); } void xml_display_savage_player_info( struct qserver *server) { struct player *player; fprintf( OF, "\t\t\n"); player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t\t\t\n"); fprintf( OF, "\t\t\t\t%s\n", xml_escape(xform_name( player->name, server))); fprintf( OF, "\t\t\t\t%d\n", player->frags); fprintf( OF, "\t\t\t\t\n", xml_escape(play_time( player->connect_time,1))); fprintf( OF, "\t\t\t\n"); } fprintf( OF, "\t\t\n"); } void xml_display_farcry_player_info( struct qserver *server) { struct player *player; fprintf( OF, "\t\t\n"); player= server->players; for ( ; player != NULL; player= player->next) { fprintf( OF, "\t\t\t\n"); fprintf( OF, "\t\t\t\t%s\n", xml_escape(xform_name( player->name, server))); fprintf( OF, "\t\t\t\t%d\n", player->frags); fprintf( OF, "\t\t\t\t\n", xml_escape(play_time( player->connect_time,1))); fprintf( OF, "\t\t\t\n"); } fprintf( OF, "\t\t\n"); } void display_progress() { static struct timeval rate_start= {0,0}; char rate[32]; struct timeval now; gettimeofday( &now, NULL); if ( ! rate_start.tv_sec) { rate_start= now; rate[0]='\0'; } else { int delta= time_delta( &now, &rate_start); if ( delta > 1500 ) sprintf( rate, " %d servers/sec ", (num_servers_returned+num_servers_timed_out)*1000 / delta); else rate[0]='\0'; } // only print out every 'progress' number of servers. if ( 0 != num_servers_returned+num_servers_timed_out && ( progress == 1 || (num_servers_returned+num_servers_timed_out) % progress == 0 ) ) { fprintf( stderr, "\r%d/%d (%d timed out, %d down)%s", num_servers_returned+num_servers_timed_out, num_servers_total, num_servers_timed_out, num_servers_down, rate ); } } /* ----- END MODIFICATION ----- Don't need to change anything below here. */ void set_non_blocking( int fd); int set_fds( fd_set *fds); void get_next_timeout( struct timeval *timeout); void set_file_descriptors(); int wait_for_file_descriptors( struct timeval *timeout); struct qserver * get_next_ready_server(); /* Misc flags */ struct timeval packet_recv_time; int one_server_type_id= ~ MASTER_SERVER; static int one= 1; static int little_endian; static int big_endian; unsigned int swap_long( void *); unsigned short swap_short( void *); float swap_float_from_little( void *f); char * strndup( const char *string, size_t len); #define FORCE 1 /* Print an error message and the program usage notes */ void usage( char *msg, char **argv, char *a1) { int i; server_type *type; server_type **sorted_types; if ( msg) fprintf( stderr, msg, a1); printf( "Usage: %s [options ...]\n", argv[0]); printf( "\t[-default server-type] [-cfg file] [-f file] [host[:port]] ...\n"); printf( "Where host is an IP address or host name\n"); sorted_types = (server_type **) malloc( sizeof(server_type *) * n_server_types ); type = &types[0]; for ( i = 0; type->id != Q_UNKNOWN_TYPE; type++, i++ ) { sorted_types[i] = type; } quicksort( (void**)sorted_types, 0, n_server_types-1, (int (*)(void*,void*)) type_option_compare ); for ( i = 0; i < n_server_types; i++ ) { type = sorted_types[i]; printf( "%s\t\tquery %s server\n", type->type_option, type->game_name ); } quicksort( (void**)sorted_types, 0, n_server_types-1, (int (*)(void*,void*)) type_string_compare ); printf( "-default\tset default server type:"); for ( i = 0; i < n_server_types; type++, i++ ) { type = sorted_types[i]; printf( " %s", type->type_string ); } puts(""); printf( "-nocfg\t\tIgnore qstat configuration loaded from any default location. Must be the first option on the command-line.\n" ); printf( "-cfg\t\tread the extended types from given file not the default one\n"); printf( "-f\t\tread hosts from file\n"); printf( "-R\t\tfetch and display server rules\n"); printf( "-P\t\tfetch and display player info\n"); printf( "-sort\t\tsort servers and/or players\n"); printf( "-u\t\tonly display servers that are up\n"); printf( "-nf\t\tdo not display full servers\n"); printf( "-ne\t\tdo not display empty servers\n"); printf( "-nh\t\tdo not display header line.\n" ); printf( "-cn\t\tdisplay color names instead of numbers\n"); printf( "-ncn\t\tdisplay color numbers instead of names\n"); printf( "-hc\t\tdisplay colors in #rrggbb format\n"); printf( "-tc\t\tdisplay time in clock format (DhDDmDDs)\n"); printf( "-tsw\t\tdisplay time in stop-watch format (DD:DD:DD)\n"); printf( "-ts\t\tdisplay time in seconds\n"); printf( "-pa\t\tdisplay player address\n"); printf( "-hpn\t\tdisplay player names in hex\n"); printf( "-hsn\t\tdisplay server names in hex\n"); printf( "-nh\t\tdo not display header\n"); printf( "-old\t\told style display\n"); printf( "-progress\tdisplay progress meter (text only)\n"); printf( "-retry\t\tnumber of retries, default is %d\n", DEFAULT_RETRIES); printf( "-interval\tinterval between retries, default is %.2f seconds\n", DEFAULT_RETRY_INTERVAL / 1000.0); printf( "-mi\t\tinterval between master server retries, default is %.2f seconds\n", (DEFAULT_RETRY_INTERVAL*4) / 1000.0); printf( "-timeout\ttotal time in seconds before giving up\n"); printf( "-maxsim\t\tset maximum simultaneous queries\n"); printf( "-sendinterval\t\tset time in ms between sending packets, default %u\n", sendinterval); printf( "-errors\t\tdisplay errors\n"); printf( "-allowserverdups\t\tallow adding multiple servers with same ip:port (needed for ts2)\n"); printf( "-of\t\toutput file\n"); printf( "-af\t\tLike -of, but append to the file\n" ); printf( "-raw \toutput in raw format using as delimiter\n"); printf( "-mdelim \tFor rules with multi values use as delimiter\n"); printf( "-xml\t\toutput status data as an XML document\n"); printf( "-Th,-Ts,-Tpt\toutput templates: header, server and player\n"); printf( "-Tr,-Tt\t\toutput templates: rule, and trailer\n"); printf( "-srcport \tSend packets from these network ports\n"); printf( "-srcip \tSend packets using this IP address\n"); printf( "-H\t\tresolve host names\n"); printf( "-Hcache\t\thost name cache file\n"); printf( "-carets\t\tDisplay carets in Quake 3 player names\n" ); printf( "-d\t\tEnable debug options. Specify multiple times to increase debug level.\n"); #ifdef ENABLE_DUMP printf( "-dump\t\twrite received raw packets to dumpNNN files which must not exist before\n"); printf( "-pkt \tuse file as server reply instead of quering the server. Works only with TF_SINGLE_QUERY servers\n"); #endif printf( "-htmlmode\tConvert <, >, and & to the equivalent HTML entities\n" ); printf( "-htmlnames\tColorize Quake 3 and Tribes 2 player names using html font tags\n" ); printf( "-nohtmlnames\tDo not colorize Quake 3 and Tribes 2 player names even if $HTML is used in an output template.\n" ); printf( "-showgameport\tAlways display the game port in QStat output.\n" ); printf( "-noportoffset\tDont use builtin status port offsets ( assume query port was specified ).\n" ); printf( "-raw-arg\tWhen used with -raw, always display the server address as it appeared in a file or on the command-line.\n" ); printf( "-utf8\t\tUse the UTF-8 character encoding for XML output.\n" ); #ifdef _WIN32 printf( "-noconsole\t\tFree the console\n" ); #endif printf( "\n"); printf( "Sort keys:\n"); printf( " servers: p=by-ping, g=by-game, i=by-IP-address, h=by-hostname, n=by-#-players, l=by-list-order\n"); printf( " players: P=by-ping, F=by-frags, T=by-team, N=by-name\n"); printf( "\nqstat version %s\n", VERSION); exit(0); } struct server_arg { int type_id; server_type *type; char *arg; char *outfilename; char *query_arg; }; server_type* find_server_type_id( int type_id) { server_type *type= &types[0]; for ( ; type->id != Q_UNKNOWN_TYPE; type++) if ( type->id == type_id) return type; return NULL; } server_type* find_server_type_string( char* type_string) { server_type *type= &types[0]; char *t= type_string; for ( ; *t; t++) *t= tolower( *t); for ( ; type->id != Q_UNKNOWN_TYPE; type++) if ( strcmp( type->type_string, type_string) == 0) return type; return NULL; } server_type* find_server_type_option( char* option) { server_type *type= &types[0]; for ( ; type->id != Q_UNKNOWN_TYPE; type++) if ( strcmp( type->type_option, option) == 0) return type; return NULL; } server_type* parse_server_type_option( char* option, int *outfile, char **query_arg) { server_type *type= &types[0]; char *comma, *arg; int len; *outfile= 0; *query_arg= 0; comma= strchr( option, ','); if ( comma) *comma++= '\0'; for ( ; type->id != Q_UNKNOWN_TYPE; type++) if ( strcmp( type->type_option, option) == 0) break; if ( type->id == Q_UNKNOWN_TYPE) return NULL; if ( ! comma) return type; if ( strcmp( comma, "outfile") == 0) { *outfile= 1; comma= strchr( comma, ','); if ( ! comma) return type; *comma++= '\0'; } *query_arg= strdup(comma); arg= comma; do { comma= strchr( arg, ','); if (comma) len= comma-arg; else len= strlen( arg); if ( strncmp( arg, "outfile", len) == 0) *outfile= 1; arg= comma+1; } while ( comma); return type; } void add_server_arg( char *arg, int type, char *outfilename, char *query_arg, struct server_arg **args, int *n, int *max) { if ( *n == *max) { if ( *max == 0) { *max= 4; *args= (struct server_arg*)malloc(sizeof(struct server_arg) * (*max)); } else { (*max)*= 2; *args= (struct server_arg*) realloc( *args, sizeof(struct server_arg) * (*max)); } } (*args)[*n].type_id= type; /* (*args)[*n].type= find_server_type_id( type); */ (*args)[*n].type= NULL; (*args)[*n].arg= arg; (*args)[*n].outfilename= outfilename; (*args)[*n].query_arg= query_arg; (*n)++; } void add_query_param( struct qserver *server, char *arg) { char *equal; struct query_param *param; equal= strchr( arg, '='); *equal++= '\0'; param= (struct query_param *) malloc( sizeof(struct query_param)); param->key= arg; param->value= equal; sscanf( equal, "%i", ¶m->i_value); sscanf( equal, "%i", ¶m->ui_value); param->next= server->params; server->params= param; } char * get_param_value( struct qserver *server, const char *key, char *default_value) { struct query_param *p= server->params; for ( ; p; p= p->next) if ( strcasecmp( key, p->key) == 0) return p->value; return default_value; } int get_param_i_value( struct qserver *server, char *key, int default_value) { struct query_param *p= server->params; for ( ; p; p= p->next) if ( strcasecmp( key, p->key) == 0) return p->i_value; return default_value; } unsigned int get_param_ui_value( struct qserver *server, char *key, unsigned int default_value) { struct query_param *p= server->params; for ( ; p; p= p->next) if ( strcasecmp( key, p->key) == 0) return p->ui_value; return default_value; } int parse_source_address( char *addr, unsigned int *ip, unsigned short *port) { char *colon; colon= strchr( addr, ':'); if ( colon) { *colon= '\0'; *port= atoi( colon+1); if ( colon == addr) return 0; } else *port= 0; *ip= inet_addr( addr); if ( *ip == INADDR_NONE && !isdigit( (unsigned char)*ip)) *ip= hcache_lookup_hostname( addr); if ( *ip == INADDR_NONE) { fprintf( stderr, "%s: Not an IP address or unknown host name\n", addr); return -1; } *ip= ntohl( *ip); return 0; } int parse_source_port( char *port, unsigned short *low, unsigned short *high) { char *dash; *low= atoi( port); dash= strchr( port, '-'); *high= 0; if ( dash) *high= atoi( dash+1); if ( *high == 0) *high= *low; if ( *high < *low) { fprintf( stderr, "%s: Invalid port range\n", port); return -1; } return 0; } void add_config_server_types() { int n_config_types, n_builtin_types, i; server_type **config_types; server_type *new_types, *type; config_types= qsc_get_config_server_types( &n_config_types); if ( n_config_types == 0) return; n_builtin_types= (sizeof( builtin_types) / sizeof(server_type)) - 1; new_types= (server_type*) malloc( sizeof(server_type) * (n_builtin_types + n_config_types + 1)); memcpy( new_types, &builtin_types[0], n_builtin_types*sizeof(server_type)); type= &new_types[n_builtin_types]; for ( i= n_config_types; i; i--, config_types++, type++) *type= **config_types; n_server_types= n_builtin_types + n_config_types; new_types[n_server_types].id= Q_UNKNOWN_TYPE; if ( types != &builtin_types[0]) free( types); types= new_types; } void revert_server_types() { if ( types != &builtin_types[0]) free( types); n_server_types= (sizeof( builtin_types) / sizeof(server_type)) - 1; types= &builtin_types[0]; } #ifdef ENABLE_DUMP unsigned pkt_dump_pos = 0; const char* pkt_dumps[64] = {0}; static void add_pkt_from_file(const char* file) { if(pkt_dump_pos >= sizeof(pkt_dumps)/sizeof(pkt_dumps[0])) return; pkt_dumps[pkt_dump_pos++] = file; } static void replay_pkt_dumps() { struct qserver* server = servers; char* pkt = NULL; int fd; int bytes_read = 0; // should be ssize_t but for ease with win32 int i; struct stat statbuf; gettimeofday( &packet_recv_time, NULL); for ( i = 0; i < pkt_dump_pos; i++ ) { if((fd = open(pkt_dumps[i], O_RDONLY)) == -1) { goto err; } if( fstat( fd, &statbuf) == -1 ) { goto err; } pkt = malloc( statbuf.st_size ); if ( NULL == pkt ) { goto err; } bytes_read = read( fd, pkt, statbuf.st_size ); if ( bytes_read != statbuf.st_size ) { fprintf( stderr, "Failed to read entire packet from disk got %d of %ld bytes\n", bytes_read, (long)statbuf.st_size ); goto err; } server->type->packet_func( server, pkt, statbuf.st_size); close(fd); fd = 0; } goto out; err: perror(__FUNCTION__); close(fd); out: fd = 0; // NOP } #endif // ENABLE_DUMP struct rcv_pkt { struct qserver* server; struct sockaddr_in addr; struct timeval recv_time; char data[PACKET_LEN]; int len; int _errno; }; void do_work(void) { int pktlen, rc, fd; char *pkt= NULL; int bind_retry= 0; struct timeval timeout; struct rcv_pkt* buffer; unsigned buffill = 0, i = 0; unsigned bufsize = max_simultaneous*2; struct timeval t,ts; gettimeofday(&t, NULL); ts = t; buffer = malloc(sizeof(struct rcv_pkt) * bufsize); if(!buffer) return; #ifdef ENABLE_DUMP if(pkt_dump_pos) { replay_pkt_dumps(); } else #endif { bind_retry = bind_sockets(); } send_packets(); debug(2, "connected: %d", connected); while ( connected || (!connected && bind_retry==-2)) { if ( ! connected && bind_retry==-2) { rc= wait_for_timeout( 60); bind_retry= bind_sockets(); continue; } bind_retry= 0; set_file_descriptors(); if ( progress) display_progress(); get_next_timeout( &timeout); rc= wait_for_file_descriptors( &timeout); debug(2, "rc %d", rc); if ( rc == SOCKET_ERROR) { #ifndef _WIN32 if ( errno == EINTR) continue; #endif perror("select"); break; } fd= 0; for ( ; rc && buffill < bufsize; rc--) { int addrlen= sizeof(buffer[buffill].addr); struct qserver* server= get_next_ready_server(); if ( server == NULL) break; gettimeofday( &buffer[buffill].recv_time, NULL); pktlen = recvfrom( server->fd, buffer[buffill].data, sizeof(buffer[buffill].data), 0, (struct sockaddr*)&buffer[buffill].addr, (void*)&addrlen); if ( pktlen == SOCKET_ERROR) { if(errno == EAGAIN) { malformed_packet(server, "EAGAIN on UDP socket, probably incorrect checksum"); } else if ( connection_refused()) { server->server_name= DOWN; num_servers_down++; cleanup_qserver( server, 1); } continue; } debug(1, "recv %3d %3d %d.%d.%d.%d:%hu\n", time_delta(&buffer[buffill].recv_time, &ts), time_delta(&buffer[buffill].recv_time, &t), server->ipaddr&0xff, (server->ipaddr>>8)&0xff, (server->ipaddr>>16)&0xff, (server->ipaddr>>24)&0xff, server->port); t = buffer[buffill].recv_time; buffer[buffill].server = server; buffer[buffill].len = pktlen; ++buffill; } for(i = 0; i < buffill; ++i) { struct qserver* server = buffer[i].server; pkt = buffer[i].data; pktlen = buffer[i].len; memcpy(&packet_recv_time, &buffer[i].recv_time, sizeof(packet_recv_time)); if ( get_debug_level() > 0 ) print_packet( server, pkt, pktlen); #ifdef ENABLE_DUMP if (do_dump) dump_packet(pkt, pktlen); #endif if ( server->flags & FLAG_BROADCAST) { struct qserver *broadcast= server; unsigned short port= ntohs(buffer[i].addr.sin_port); /* create new server and init */ if ( ! (no_port_offset || server->flags & TF_NO_PORT_OFFSET)) { port-= server->type->port_offset; } server= add_qserver_byaddr( ntohl(buffer[i].addr.sin_addr.s_addr), port, server->type, NULL); if ( server == NULL) { server= find_server_by_address( buffer[i].addr.sin_addr.s_addr, ntohs(buffer[i].addr.sin_port)); if ( server == NULL) { continue; } /* if ( show_errors) { fprintf(stderr, "duplicate or invalid packet received from 0x%08x:%hu\n", ntohl(buffer[i].addr.sin_addr.s_addr), ntohs(buffer[i].addr.sin_port)); print_packet( NULL, pkt, pktlen); } continue; */ } else { server->packet_time1= broadcast->packet_time1; server->packet_time2= broadcast->packet_time2; server->ping_total= broadcast->ping_total; server->n_requests= broadcast->n_requests; server->n_packets= broadcast->n_packets; broadcast->n_servers++; } } debug(2, "connected: %d", connected); server->type->packet_func( server, pkt, pktlen); debug(2, "connected: %d", connected); } buffill = 0; if ( run_timeout && time(0)-start_time >= run_timeout) { debug(2, "run timeout reached"); break; } send_packets(); if ( connected < max_simultaneous) bind_retry= bind_sockets(); debug(2, "connected: %d", connected); } free(buffer); } #ifndef _WIN32 void sigpipe( int sig ) { fprintf( stderr, "SIG: %d\n", sig ); } #endif int main( int argc, char *argv[]) { int arg, n_files, i; char **files, *outfilename, *query_arg; struct server_arg *server_args= NULL; int n_server_args= 0, max_server_args= 0; int default_server_type_id; #ifdef _WIN32 WORD version= MAKEWORD(1,1); WSADATA wsa_data; if ( WSAStartup(version,&wsa_data) != 0) { fprintf( stderr, "Could not open winsock\n"); exit(1); } #else signal( SIGPIPE, &sigpipe ); #endif types= &builtin_types[0]; n_server_types= (sizeof( builtin_types) / sizeof(server_type)) - 1; i= qsc_load_default_config_files(); if ( i == -1) return 1; else if ( i == 0) add_config_server_types(); if ( argc == 1) usage(NULL,argv,NULL); OF= stdout; files= (char **) malloc( sizeof(char*) * (argc/2)); n_files= 0; default_server_type_id= Q_SERVER; little_endian= ((char*)&one)[0]; big_endian= !little_endian; for ( arg= 1; arg < argc; arg++) { if ( argv[arg][0] != '-') break; outfilename= NULL; if ( strcmp( argv[arg], "-nocfg") == 0 && arg == 1) { revert_server_types(); } else if ( strcmp( argv[arg], "--help") == 0) { usage(NULL,argv,NULL); } else if ( strcmp( argv[arg], "-f") == 0) { arg++; if ( arg >= argc) usage( "missing argument for -f\n", argv,NULL); files[n_files++]= argv[arg]; } else if ( strcmp( argv[arg], "-retry") == 0) { arg++; if ( arg >= argc) usage( "missing argument for -retry\n", argv,NULL); n_retries= atoi( argv[arg]); if ( n_retries <= 0) { fprintf( stderr, "retries must be greater than zero\n"); exit(1); } } else if ( strcmp( argv[arg], "-interval") == 0) { double value= 0.0; arg++; if ( arg >= argc) usage( "missing argument for -interval\n", argv,NULL); sscanf( argv[arg], "%lf", &value); if ( value < 0.1) { fprintf( stderr, "retry interval must be greater than 0.1\n"); exit(1); } retry_interval= (int)(value * 1000); } else if ( strcmp( argv[arg], "-mi") == 0) { double value= 0.0; arg++; if ( arg >= argc) usage( "missing argument for -mi\n", argv,NULL); sscanf( argv[arg], "%lf", &value); if ( value < 0.1) { fprintf( stderr, "interval must be greater than 0.1\n"); exit(1); } master_retry_interval= (int)(value * 1000); } else if ( strcmp( argv[arg], "-H") == 0) hostname_lookup= 1; else if ( strcmp( argv[arg], "-u") == 0) up_servers_only= 1; else if ( strcmp( argv[arg], "-nf") == 0) no_full_servers= 1; else if ( strcmp( argv[arg], "-ne") == 0) no_empty_servers= 1; else if ( strcmp( argv[arg], "-nh") == 0) no_header_display= 1; else if ( strcmp( argv[arg], "-old") == 0) new_style= 0; else if ( strcmp( argv[arg], "-P") == 0) get_player_info= 1; else if ( strcmp( argv[arg], "-R") == 0) get_server_rules= 1; else if ( strncmp( argv[arg], "-raw", 4) == 0) { if ( argv[arg][4] == ',') { if ( strcmp( &argv[arg][5], "game") == 0) { show_game_in_raw= 1; } else { usage( "Unknown -raw option\n", argv, NULL); } } arg++; if ( arg >= argc) { usage( "missing argument for -raw\n", argv,NULL); } raw_delimiter= argv[arg]; // Check the multi rule delimiter isnt the same // If it is fix to maintain backwards compatibility if ( 0 == strcmp( raw_delimiter, multi_delimiter ) && 0 == strcmp( raw_delimiter, "|" ) ) { multi_delimiter = ":"; } raw_display= 1; } else if ( strcmp( argv[arg], "-mdelim" ) == 0 ) { arg++; if ( arg >= argc) { usage( "missing argument for -mdelim\n", argv,NULL); } multi_delimiter = argv[arg]; } else if ( strcmp( argv[arg], "-xml") == 0) { xml_display= 1; if (raw_display == 1) usage( "cannot specify both -raw and -xml\n", argv,NULL); } else if ( strcmp( argv[arg], "-utf8") == 0) { xml_encoding= ENCODING_UTF_8; name_xforms= 0; } else if ( strcmp( argv[arg], "-ncn") == 0) { color_names= 0; } else if ( strcmp( argv[arg], "-cn") == 0) { color_names= 1; } else if ( strcmp( argv[arg], "-hc") == 0) { color_names= 2; } else if ( strcmp( argv[arg], "-nx") == 0) { name_xforms= 1; } else if ( strcmp( argv[arg], "-nnx") == 0) { name_xforms= 0; } else if ( strcmp( argv[arg], "-tc") == 0) { time_format= CLOCK_TIME; } else if ( strcmp( argv[arg], "-tsw") == 0) { time_format= STOPWATCH_TIME; } else if ( strcmp( argv[arg], "-ts") == 0) { time_format= SECONDS; } else if ( strcmp( argv[arg], "-pa") == 0) { player_address= 1; } else if ( strcmp( argv[arg], "-hpn") == 0) { hex_player_names= 1; } else if ( strcmp( argv[arg], "-hsn") == 0) { hex_server_names= 1; } else if ( strncmp( argv[arg], "-maxsimultaneous", 7) == 0) { arg++; if ( arg >= argc) usage( "missing argument for -maxsimultaneous\n", argv,NULL); max_simultaneous= atoi(argv[arg]); if ( max_simultaneous <= 0) usage( "value for -maxsimultaneous must be > 0\n", argv,NULL); if ( max_simultaneous > FD_SETSIZE) max_simultaneous= FD_SETSIZE; } else if ( strcmp( argv[arg], "-sendinterval") == 0) { arg++; if ( arg >= argc) usage( "missing argument for -sendinterval\n", argv,NULL); sendinterval= atoi(argv[arg]); if ( sendinterval < 0) usage( "value for -sendinterval must be >= 0\n", argv,NULL); } else if ( strcmp( argv[arg], "-raw-arg") == 0) { raw_arg= 1000; } else if ( strcmp( argv[arg], "-timeout") == 0) { arg++; if ( arg >= argc) usage( "missing argument for -timeout\n", argv,NULL); run_timeout= atoi( argv[arg]); if ( run_timeout <= 0) usage( "value for -timeout must be > 0\n", argv,NULL); } else if ( strncmp(argv[arg], "-progress", sizeof("-progress")-1) == 0) { char *p= argv[arg] + sizeof("-progress")-1; progress= 1; if ( *p == ',') progress= atoi(p+1); } else if ( strcmp( argv[arg], "-Hcache") == 0) { arg++; if ( arg >= argc) usage( "missing argument for -Hcache\n", argv,NULL); if ( hcache_open( argv[arg], 0) == -1) return 1; } else if ( strcmp( argv[arg], "-default") == 0) { arg++; if ( arg >= argc) usage( "missing argument for -default\n", argv,NULL); default_server_type= find_server_type_string( argv[arg]); if ( default_server_type == NULL) { char opt[256], *o= &opt[0]; sprintf( opt, "-%s", argv[arg]); for ( ; *o; o++) *o= tolower(*o); default_server_type= find_server_type_option( opt); } if ( default_server_type == NULL) { fprintf( stderr, "unknown server type \"%s\"\n", argv[arg]); usage( NULL, argv,NULL); } default_server_type_id= default_server_type->id; default_server_type= NULL; } else if ( strncmp( argv[arg], "-Tserver", 3) == 0) { arg++; if ( arg >= argc) usage( "missing argument for %s\n", argv, argv[arg-1]); if ( read_qserver_template( argv[arg]) == -1) return 1; } else if ( strncmp( argv[arg], "-Trule", 3) == 0) { arg++; if ( arg >= argc) usage( "missing argument for %s\n", argv, argv[arg-1]); if ( read_rule_template( argv[arg]) == -1) return 1; } else if ( strncmp( argv[arg], "-Theader", 3) == 0) { arg++; if ( arg >= argc) usage( "missing argument for %s\n", argv, argv[arg-1]); if ( read_header_template( argv[arg]) == -1) return 1; } else if ( strncmp( argv[arg], "-Ttrailer", 3) == 0) { arg++; if ( arg >= argc) usage( "missing argument for %s\n", argv, argv[arg-1]); if ( read_trailer_template( argv[arg]) == -1) return 1; } else if ( strncmp( argv[arg], "-Tplayer", 3) == 0) { arg++; if ( arg >= argc) usage( "missing argument for %s\n", argv, argv[arg-1]); if ( read_player_template( argv[arg]) == -1) return 1; } else if ( strcmp( argv[arg], "-sort") == 0) { size_t pos; arg++; if ( arg >= argc) usage( "missing argument for -sort\n", argv, NULL); strncpy( sort_keys, argv[arg], sizeof(sort_keys)-1); pos= strspn( sort_keys, SUPPORTED_SORT_KEYS); if ( pos != strlen( sort_keys)) { fprintf( stderr, "Unknown sort key \"%c\", valid keys are \"%s\"\n", sort_keys[pos], SUPPORTED_SORT_KEYS); return 1; } server_sort= strpbrk( sort_keys, SUPPORTED_SERVER_SORT) != NULL; if ( strchr( sort_keys, 'l')) server_sort= 1; player_sort= strpbrk( sort_keys, SUPPORTED_PLAYER_SORT) != NULL; } else if ( strcmp( argv[arg], "-errors") == 0) { show_errors++; } else if ( strcmp( argv[arg], "-of") == 0) { arg++; if ( arg >= argc) usage( "missing argument for %s\n", argv, argv[arg-1]); if ( argv[arg][0] == '-' && argv[arg][1] == '\0') OF= stdout; else OF= fopen( argv[arg], "w"); if ( OF == NULL) { perror( argv[arg]); return 1; } } else if ( strcmp( argv[arg], "-af") == 0) { arg++; if ( arg >= argc) usage( "missing argument for %s\n", argv, argv[arg-1]); if ( argv[arg][0] == '-' && argv[arg][1] == '\0') OF= stdout; else OF= fopen( argv[arg], "a"); if ( OF == NULL) { perror( argv[arg]); return 1; } } else if ( strcmp( argv[arg], "-htmlnames") == 0) { html_names= 1; } else if ( strcmp( argv[arg], "-nohtmlnames") == 0) { html_names= 0; } else if ( strcmp( argv[arg], "-htmlmode") == 0) { html_mode= 1; } else if ( strcmp( argv[arg], "-carets") == 0) { strip_carets= 0; } else if ( strcmp( argv[arg], "-d") == 0) { set_debug_level(get_debug_level() + 1); } else if ( strcmp( argv[arg], "-showgameport") == 0) { show_game_port= 1; } else if ( strcmp( argv[arg], "-noportoffset") == 0) { no_port_offset= 1; } else if ( strcmp( argv[arg], "-srcip") == 0) { arg++; if ( arg >= argc) usage( "missing argument for %s\n", argv, argv[arg-1]); if ( parse_source_address( argv[arg], &source_ip, &source_port) == -1) return 1; if ( source_port) { source_port_low= source_port; source_port_high= source_port; } } else if ( strcmp( argv[arg], "-srcport") == 0) { arg++; if ( arg >= argc) usage( "missing argument for %s\n", argv, argv[arg-1]); if ( parse_source_port( argv[arg], &source_port_low, &source_port_high) == -1) return 1; source_port= source_port_low; } else if ( strcmp( argv[arg], "-cfg") == 0) { arg++; if ( arg >= argc) usage( "missing argument for %s\n", argv, argv[arg-1]); if ( qsc_load_config_file( argv[arg]) == -1) return 1; add_config_server_types(); } else if ( strcmp( argv[arg], "-allowserverdups") == 0) { noserverdups = 0; } #ifdef ENABLE_DUMP else if ( strcmp( argv[arg], "-dump") == 0) { do_dump = 1; } else if ( strcmp( argv[arg], "-pkt") == 0) { arg++; if ( arg >= argc) usage( "missing argument for %s\n", argv, argv[arg-1]); add_pkt_from_file(argv[arg]); } #endif #ifdef _WIN32 else if ( strcmp( argv[arg], "-noconsole") == 0) { FreeConsole(); } #endif else { int outfile; server_type *type; arg++; if ( arg >= argc) { fprintf( stderr, "missing argument for \"%s\"\n", argv[arg-1]); return 1; } type= parse_server_type_option( argv[arg-1], &outfile, &query_arg); if ( type == NULL) { fprintf( stderr, "unknown option \"%s\"\n", argv[arg-1]); return 1; } outfilename= NULL; if ( outfile) { outfilename= strchr( argv[arg], ','); if ( outfilename == NULL) { fprintf( stderr, "missing file name for \"%s,outfile\"\n", argv[arg-1]); return 1; } *outfilename++= '\0'; } /* if ( query_arg && !(type->flags & TF_QUERY_ARG)) { fprintf( stderr, "option flag \"%s\" not allowed for this server type\n", query_arg); return 1; } */ if ( type->flags & TF_QUERY_ARG_REQUIRED && !query_arg) { fprintf( stderr, "option flag missing for server type \"%s\"\n", argv[arg-1]); return 1; } add_server_arg( argv[arg], type->id, outfilename, query_arg, &server_args, &n_server_args, &max_server_args); } } start_time= time(0); default_server_type= find_server_type_id( default_server_type_id); for ( i= 0; i < n_files; i++) add_file( files[i]); for ( ; arg < argc; arg++) add_qserver( argv[arg], default_server_type, NULL, NULL); for ( i= 0; i < n_server_args; i++) { server_type *server_type= find_server_type_id( server_args[i].type_id); add_qserver( server_args[i].arg, server_type, server_args[i].outfilename, server_args[i].query_arg); } free( server_args); if ( servers == NULL) exit(1); max_connmap= max_simultaneous + 10; connmap= (struct qserver**) calloc( 1, sizeof(struct qserver*) * max_connmap); if ( color_names == -1) color_names= ( raw_display) ? DEFAULT_COLOR_NAMES_RAW : DEFAULT_COLOR_NAMES_DISPLAY; if ( time_format == -1) time_format= ( raw_display) ? DEFAULT_TIME_FMT_RAW : DEFAULT_TIME_FMT_DISPLAY; if ( (one_server_type_id & MASTER_SERVER) || one_server_type_id == 0) display_prefix= 1; if ( xml_display) xml_header(); else if ( new_style && ! raw_display && ! have_server_template()) display_header(); else if ( have_header_template()) template_display_header(); q_serverinfo.length= htons( q_serverinfo.length); h2_serverinfo.length= htons( h2_serverinfo.length); q_player.length= htons( q_player.length); do_work(); finish_output(); free_server_hash(); free(files); free(connmap); return 0; } void finish_output() { int i; hcache_update_file(); if ( progress) { display_progress(); fputs( "\n", stderr); } if ( server_sort) { struct qserver **array, *server, *next_server; if ( strchr( sort_keys, 'l') && strpbrk( sort_keys, SUPPORTED_SERVER_SORT) == NULL) { server= servers; for ( ; server; server= next_server) { next_server= server->next; display_server( server); } } else { array= (struct qserver **) malloc( sizeof(struct qserver *) * num_servers_total); server= servers; for ( i= 0; server != NULL; i++) { array[i]= server; server= server->next; } sort_servers( array, num_servers_total); if ( progress) fprintf( stderr, "\n"); for ( i= 0; i < num_servers_total; i++) display_server( array[i]); free( array); } } else { struct qserver *server, *next_server; server= servers; for ( ; server; server= next_server) { next_server= server->next; if ( server->server_name == HOSTNOTFOUND) display_server( server); } } if ( xml_display) xml_footer(); else if ( have_trailer_template()) template_display_trailer(); if ( OF != stdout) fclose( OF); } void add_file( char *filename) { FILE *file; char name[200], *comma, *query_arg = NULL; server_type* type; if ( strcmp( filename, "-") == 0) { file= stdin; current_filename= NULL; } else { file= fopen( filename, "r"); current_filename= filename; } current_fileline= 1; if ( file == NULL) { perror( filename); return; } for ( ; fscanf( file, "%s", name) == 1; current_fileline++) { comma= strchr( name, ','); if ( comma) { *comma++= '\0'; query_arg= strdup( comma); } type = find_server_type_string( name); if ( type == NULL) { add_qserver( name, default_server_type, NULL, NULL); } else if ( fscanf( file, "%s", name) == 1) { if ( type->flags & TF_QUERY_ARG && comma && *query_arg) { add_qserver( name, type, NULL, query_arg ); } else { add_qserver( name, type, NULL, NULL ); } } } if ( file != stdin) fclose(file); current_fileline= 0; } void print_file_location() { if ( current_fileline != 0) fprintf( stderr, "%s:%d: ", current_filename?current_filename:"", current_fileline); } void parse_query_params( struct qserver *server, char *params) { char *comma, *arg= params; do { comma= strchr(arg,','); if ( comma) *comma= '\0'; if ( strchr( arg, '=')) add_query_param( server, arg); else if ( strcmp( arg, "noportoffset") == 0 || strcmp( arg, "qp") == 0) server->flags |= TF_NO_PORT_OFFSET; else if ( strcmp( arg, "showgameport") == 0 || strcmp( arg, "gp") == 0) server->flags |= TF_SHOW_GAME_PORT; arg= comma+1; } while ( comma); } int add_qserver( char *arg, server_type* type, char *outfilename, char *query_arg) { struct qserver *server, *prev_server; int flags= 0; char *colon= NULL, *arg_copy, *hostname= NULL; unsigned int ipaddr; unsigned short port, port_max; int portrange = 0; unsigned colonpos = 0; if ( run_timeout && time(0)-start_time >= run_timeout) { finish_output(); exit(0); } port = port_max = type->default_port; if ( outfilename && strcmp( outfilename, "-") != 0) { FILE *outfile= fopen( outfilename, "r+"); if ( outfile == NULL && (errno == EACCES || errno == EISDIR || errno == ENOSPC || errno == ENOTDIR)) { perror( outfilename); return -1; } if ( outfile) { fclose(outfile); } } arg_copy= strdup(arg); colon= strchr( arg, ':'); if ( colon != NULL) { if(sscanf( colon+1, "%hu-%hu", &port, &port_max) == 2) { portrange = 1; } else { port_max = port; } *colon= '\0'; colonpos = colon-arg; } if ( *arg == '+') { flags|= FLAG_BROADCAST; arg++; } ipaddr= inet_addr(arg); if ( ipaddr == INADDR_NONE) { if ( strcmp( arg, "255.255.255.255") != 0) { ipaddr= htonl( hcache_lookup_hostname(arg)); } } else if ( hostname_lookup && !(flags&FLAG_BROADCAST)) { hostname= hcache_lookup_ipaddr( ntohl(ipaddr)); } if ( (ipaddr == INADDR_NONE || ipaddr == 0) && strcmp( arg, "255.255.255.255") != 0) { print_file_location(); fprintf( stderr, "%s: %s\n", arg, strherror(h_errno)); server= (struct qserver *) calloc( 1, sizeof( struct qserver)); for(;port <= port_max; ++port) { init_qserver( server, type); if(portrange) { server->arg = ( port==port_max ) ? arg_copy : strdup(arg_copy); } if(portrange) { sprintf(server->arg+colonpos+1, "%hu", port); } server->server_name= HOSTNOTFOUND; server->error= strdup( strherror(h_errno)); server->query_port = server->port= port; if ( last_server != &servers) { prev_server= (struct qserver*) ((char*)last_server - ((char*)&server->next - (char*)server)); server->prev= prev_server; } *last_server= server; last_server= & server->next; if ( one_server_type_id == ~MASTER_SERVER) { one_server_type_id= type->id; } else if ( one_server_type_id != type->id) { one_server_type_id= 0; } } return -1; } for(; port > 0 && port <= port_max; ++port) { if ( noserverdups && find_server_by_address( ipaddr, port) != NULL) { continue; } server= (struct qserver *) calloc( 1, sizeof( struct qserver)); server->arg= port==port_max?arg_copy:strdup(arg_copy); if(portrange) { sprintf(server->arg+colonpos+1, "%hu", port); } if ( hostname && colon) { server->host_name= (char*)malloc( strlen(hostname) + 5 +2); sprintf( server->host_name, "%s:%hu", hostname, port); } else { server->host_name= strdup((hostname)?hostname:arg); } server->ipaddr= ipaddr; server->query_port = server->port = port; server->type= type; server->outfilename= outfilename; server->flags= flags; if (query_arg) { server->query_arg = ( port == port_max ) ? query_arg : strdup(query_arg); parse_query_params( server, server->query_arg); } else { server->query_arg = NULL; } init_qserver( server, type); if ( server->type->master) { waiting_for_masters++; } if ( num_servers_total % 10 == 0) { hcache_update_file(); } if ( last_server != &servers ) { prev_server= (struct qserver*) ((char*)last_server - ((char*)&server->next - (char*)server)); server->prev= prev_server; } *last_server= server; last_server= &server->next; add_server_to_hash( server ); if ( one_server_type_id == ~MASTER_SERVER) { one_server_type_id= type->id; } else if ( one_server_type_id != type->id) { one_server_type_id= 0; } ++num_servers; } return 0; } struct qserver * add_qserver_byaddr( unsigned int ipaddr, unsigned short port, server_type* type, int *new_server) { char arg[36]; struct qserver *server, *prev_server; char *hostname= NULL; if ( run_timeout && time(0)-start_time >= run_timeout) { finish_output(); exit(0); } if ( new_server) { *new_server= 0; } ipaddr= htonl(ipaddr); if ( ipaddr == 0) { return 0; } // TODO: this prevents servers with the same ip:port being queried // and hence breaks virtual servers support e.g. Teamspeak 2 if ( find_server_by_address( ipaddr, port) != NULL) { return 0; } if ( new_server) { *new_server= 1; } server= (struct qserver *) calloc( 1, sizeof( struct qserver)); server->ipaddr= ipaddr; ipaddr= ntohl(ipaddr); sprintf( arg, "%d.%d.%d.%d:%hu", ipaddr>>24, (ipaddr>>16)&0xff, (ipaddr>>8)&0xff, ipaddr&0xff, port); server->arg= strdup(arg); if ( hostname_lookup) { hostname= hcache_lookup_ipaddr( ipaddr); } if ( hostname) { server->host_name= (char*)malloc( strlen(hostname) + 6 + 2); sprintf( server->host_name, "%s:%hu", hostname, port); } else { server->host_name= strdup( arg); } server->query_port = server->port = port; init_qserver( server, type); if ( num_servers_total % 10 == 0) { hcache_update_file(); } if ( last_server != &servers) { prev_server= (struct qserver*) ((char*)last_server - ((char*)&server->next - (char*)server)); server->prev= prev_server; } *last_server= server; last_server= & server->next; add_server_to_hash( server); ++num_servers; return server; } void add_servers_from_masters() { struct qserver *server; unsigned int ipaddr, i; unsigned short port; int n_servers, new_server, port_adjust= 0; char *pkt; server_type* server_type; FILE *outfile; for ( server= servers; server != NULL; server= server->next) { if ( !server->type->master || server->master_pkt == NULL) continue; pkt= server->master_pkt; if ( server->query_arg && server->type->id == GAMESPY_MASTER) { server_type= find_server_type_string( server->query_arg); if ( server_type == NULL) server_type= find_server_type_id( server->type->master); } else server_type= find_server_type_id( server->type->master); if ( server->type->id == GAMESPY_MASTER && server_type) { if ( server_type->id == UN_SERVER) port_adjust= -1; else if ( server_type->id == KINGPIN_SERVER) port_adjust= 10; } outfile= NULL; if ( server->outfilename) { if ( strcmp( server->outfilename, "-") == 0) outfile= stdout; else outfile= fopen( server->outfilename, "w"); if ( outfile == NULL) { perror( server->outfilename); continue; } } n_servers= 0; for ( i= 0; i < server->master_pkt_len; i+= 6) { memcpy( &ipaddr, &pkt[i], 4); memcpy( &port, &pkt[i+4], 2); ipaddr= ntohl( ipaddr); port= ntohs( port) + port_adjust; new_server= 1; if ( outfile) fprintf( outfile, "%s %d.%d.%d.%d:%hu\n", server_type ? server_type->type_string : "", (ipaddr>>24)&0xff, (ipaddr>>16)&0xff, (ipaddr>>8)&0xff, ipaddr&0xff, port); else if ( server_type == NULL) fprintf( OF, "%d.%d.%d.%d:%hu\n", (ipaddr>>24)&0xff, (ipaddr>>16)&0xff, (ipaddr>>8)&0xff, ipaddr&0xff, port); else add_qserver_byaddr( ipaddr, port, server_type, &new_server); n_servers+= new_server; } free( server->master_pkt); server->master_pkt= NULL; server->master_pkt_len= 0; server->n_servers= n_servers; if ( outfile) fclose( outfile); } if ( hostname_lookup) hcache_update_file(); bind_sockets(); } void init_qserver( struct qserver *server, server_type* type) { server->server_name= NULL; server->map_name= NULL; server->game= NULL; server->num_players= 0; server->fd= -1; if ( server->flags & FLAG_BROADCAST) { server->retry1= 1; server->retry2= 1; } else { server->retry1= n_retries; server->retry2= n_retries; } server->n_retries= 0; server->ping_total= 0; server->n_packets= 0; server->n_requests= 0; server->n_servers= 0; server->master_pkt_len= 0; server->master_pkt= NULL; server->error= NULL; server->saved_data.data= NULL; server->saved_data.datalen= 0; server->saved_data.pkt_index= -1; server->saved_data.pkt_max= 0; server->saved_data.next= NULL; server->type = type; server->next_rule= (get_server_rules) ? "" : NO_SERVER_RULES; server->next_player_info= (get_player_info && type->player_packet) ? 0 : NO_PLAYER_INFO; server->n_player_info= 0; server->players= NULL; server->n_rules= 0; server->rules= NULL; server->last_rule= &server->rules; server->missing_rules= 0; num_servers_total++; } // ipaddr should be network byte-order // port should be host byte-order struct qserver * find_server_by_address( unsigned int ipaddr, unsigned short port) { struct qserver **hashed; unsigned int hash, i; hash= (ipaddr + port) % ADDRESS_HASH_LENGTH; if ( ipaddr == 0) { for ( hash= 0; hash < ADDRESS_HASH_LENGTH; hash++) printf( "%3d %d\n", hash, server_hash_len[hash]); return NULL; } hashed = server_hash[hash]; for ( i= server_hash_len[hash]; i; i--, hashed++) if ( *hashed && (*hashed)->ipaddr == ipaddr && (*hashed)->port == port) return *hashed; return NULL; } void add_server_to_hash( struct qserver *server) { unsigned int hash; hash = (server->ipaddr + server->port) % ADDRESS_HASH_LENGTH; if ( server_hash_len[hash] % 16 == 0) { server_hash[hash]= (struct qserver**) realloc( server_hash[hash], sizeof( struct qserver **) * (server_hash_len[hash]+16)); memset( &server_hash[hash][server_hash_len[hash]], 0, sizeof( struct qserver **) * 16); } server_hash[hash][server_hash_len[hash]]= server; server_hash_len[hash]++; } void remove_server_from_hash( struct qserver *server) { struct qserver **hashed; unsigned int hash, i, ipaddr; unsigned short port; hash= (server->ipaddr + server->port) % ADDRESS_HASH_LENGTH; ipaddr= server->ipaddr; port= server->port; hashed= server_hash[hash]; for ( i= server_hash_len[hash]; i; i--, hashed++) if ( *hashed && (*hashed)->ipaddr == ipaddr && (*hashed)->port == port) { *hashed= NULL; break; } } void free_server_hash() { int i; for ( i= 0; i < ADDRESS_HASH_LENGTH; i++) if ( server_hash[i]) free( server_hash[i]); } /* Functions for binding sockets to Quake servers */ int bind_qserver( struct qserver *server) { struct sockaddr_in addr; static int one= 1; if ( server->type->flags & TF_TCP_CONNECT) server->fd= socket( AF_INET, SOCK_STREAM, 0); else server->fd= socket( AF_INET, SOCK_DGRAM, 0); if ( server->fd == INVALID_SOCKET) { if ( sockerr() == EMFILE) { server->fd= -1; return -2; } perror( "socket" ); server->server_name= SYSERROR; return -1; } addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl( source_ip); if ( server->type->id == Q2_MASTER) addr.sin_port= htons(26500); else if ( source_port == 0) addr.sin_port= 0; else { addr.sin_port= htons( source_port); source_port++; if ( source_port > source_port_high) source_port= source_port_low; } memset( &(addr.sin_zero), 0, sizeof(addr.sin_zero) ); if ( bind( server->fd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) == SOCKET_ERROR) { if ( sockerr() != EADDRINUSE) { perror( "bind" ); server->server_name= SYSERROR; } close(server->fd); server->fd= -1; return -1; } if ( server->flags & FLAG_BROADCAST) setsockopt( server->fd, SOL_SOCKET, SO_BROADCAST, (char*)&one, sizeof(one)); if ( server->type->id != Q2_MASTER && !(server->flags & FLAG_BROADCAST)) { addr.sin_family= AF_INET; if ( no_port_offset || server->flags & TF_NO_PORT_OFFSET) addr.sin_port= htons(server->port); else addr.sin_port= htons( (unsigned short)( server->port + server->type->port_offset ) ); addr.sin_addr.s_addr= server->ipaddr; memset( &(addr.sin_zero), 0, sizeof(addr.sin_zero) ); if ( connect( server->fd, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR) { char error[50]; if ( server->type->id == UN_MASTER) { if ( connection_refused()) { /* server->fd= -2; */ /* set up for connect retry */ } } sprintf( error, "connect:%s:%u", inet_ntoa( addr.sin_addr ), ntohs( addr.sin_port ) ); perror( error ); server->server_name= SYSERROR; close(server->fd); server->fd= -1; return -1; } } // we need nonblocking always. poll on an udp socket would wake // up and recv blocks if a packet with incorrect checksum is // received set_non_blocking( server->fd); if ( server->type->flags & TF_TCP_CONNECT) { int one= 1; setsockopt( server->fd, IPPROTO_TCP, TCP_NODELAY, (char*) &one, sizeof(one)); } #ifndef _WIN32 if ( server->fd >= max_connmap) { int old_max= max_connmap; max_connmap= server->fd + 32; connmap= (struct qserver **) realloc( connmap, max_connmap * sizeof( struct qserver *)); memset( &connmap[old_max], 0, (max_connmap - old_max) * sizeof( struct qserver *)); } connmap[server->fd]= server; #endif #ifdef _WIN32 { int i; for ( i= 0; i < max_connmap; i++) { if ( connmap[i] == NULL) { connmap[i]= server; break; } } if ( i >= max_connmap) printf( "could not put server in connmap\n"); } #endif return 0; } static struct timeval t_lastsend = {0,0}; int bind_sockets() { struct qserver *server; struct timeval now; int rc, retry_count= 0;; gettimeofday(&now, NULL); if(connected && sendinterval && time_delta(&now, &t_lastsend) < sendinterval) { server = NULL; } else if ( !waiting_for_masters) { if ( last_server_bind == NULL) { last_server_bind= servers; } server= last_server_bind; } else { server= servers; } for ( ; server != NULL && connected < max_simultaneous; server= server->next) { if ( server->server_name == NULL && server->fd == -1) { if ( waiting_for_masters && !server->type->master) { continue; } if ( (rc= bind_qserver( server)) == 0) { debug(1, "send %d.%d.%d.%d:%hu\n", server->ipaddr&0xff, (server->ipaddr>>8)&0xff, (server->ipaddr>>16)&0xff, (server->ipaddr>>24)&0xff, server->port); gettimeofday(&t_lastsend, NULL); server->type->status_query_func( server); connected++; if ( !waiting_for_masters) { last_server_bind= server; } break; } else if ( rc == -2 && ++retry_count > 2) { return -2; } } } if ( ! connected && retry_count) { return -2; } return 0; } /* * Functions for sending packets */ // this is so broken, someone please rewrite the timeout handling void send_packets() { struct qserver *server; struct timeval now; int interval, n_sent=0, prev_n_sent; unsigned i; debug( 3, "processing..." ); gettimeofday( &now, NULL); if(!t_lastsend.tv_sec) { // nothing } else if(connected && sendinterval && time_delta(&now, &t_lastsend) < sendinterval) { return; } for ( i = 0; i < max_connmap; ++i ) { server = connmap[i]; if( ! server ) { continue; } if(server->fd == -1) { debug(0, "invalid entry in connmap\n"); } if ( server->type->id & MASTER_SERVER) { interval = master_retry_interval; } else { interval = retry_interval; } debug(2, "server %p, name %s, retry1 %d, next_rule %p, next_player_info %d, num_players %d, n_retries %d", server, server->server_name, server->retry1, server->next_rule, server->next_player_info, server->num_players, n_retries); prev_n_sent = n_sent; if ( server->server_name == NULL ) { // We havent seen the server yet? if ( server->retry1 != n_retries && time_delta( &now, &server->packet_time1) < (interval*(n_retries-server->retry1+1)) ) { continue; } if ( ! server->retry1 ) { // No more retries cleanup_qserver( server, 1); continue; } if( qserver_get_timeout(server, &now) <= 0 ) { // Query status debug(2, "calling status_query_func for %p", server); server->type->status_query_func( server); gettimeofday(&t_lastsend, NULL); n_sent++; continue; } } if ( server->next_rule != NO_SERVER_RULES ) { // We want server rules if ( server->retry1 != n_retries && time_delta( &now, &server->packet_time1) < (interval*(n_retries-server->retry1+1)) ) { continue; } if ( ! server->retry1 ) { // no more retries server->next_rule= NULL; server->missing_rules = 1; cleanup_qserver( server, 0); continue; } send_rule_request_packet( server); gettimeofday(&t_lastsend, NULL); n_sent++; } if ( server->next_player_info < server->num_players) { // Expecting player details if ( server->retry2 != n_retries && time_delta( &now, &server->packet_time2) < (interval*(n_retries-server->retry2+1)) ) { continue; } if ( ! server->retry2 ) { server->next_player_info++; if ( server->next_player_info >= server->num_players ) { // no more retries cleanup_qserver( server, 0); continue; } server->retry2 = n_retries; } send_player_request_packet( server); gettimeofday(&t_lastsend, NULL); n_sent++; } if ( prev_n_sent == n_sent ) { // we didnt send any additional queries debug(2, "%d %d", time_delta( &now, &server->packet_time1 ), (interval*(n_retries+1))); if ( ! server->retry1 ) { // no retries left if ( time_delta( &now, &server->packet_time1 ) > (interval*(n_retries+1) ) ) { cleanup_qserver( server, 1 ); } } else { // decrement as we didnt send any packets server->retry1--; } } } } /* server starts sending data immediately, so we need not do anything */ void send_bfris_request_packet( struct qserver *server) { register_send( server ); } /* First packet for a normal Quake server */ void send_qserver_request_packet( struct qserver *server) { int rc = send_packet( server, server->type->status_packet, server->type->status_len ); if ( rc == SOCKET_ERROR ) { cleanup_qserver( server, 1 ); } } /* First packet for a QuakeWorld server */ void send_qwserver_request_packet( struct qserver *server) { int rc; if ( server->flags & FLAG_BROADCAST) 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 if ( server->server_name != NULL && server->type->rule_packet) rc= send( server->fd, server->type->rule_packet, server->type->rule_len, 0); else rc= SOCKET_ERROR; if ( rc == SOCKET_ERROR) { unsigned int ipaddr= ntohl(server->ipaddr); fprintf( stderr, "Error on %d.%d.%d.%d, skipping ...\n", (ipaddr>>24)&0xff, (ipaddr>>16)&0xff, (ipaddr>>8)&0xff, ipaddr&0xff); perror( "send"); cleanup_qserver( server, 1); return; } if ( server->retry1 == n_retries || server->flags & FLAG_BROADCAST) { gettimeofday( &server->packet_time1, NULL); server->n_requests++; } else if ( server->server_name == NULL) server->n_retries++; server->retry1--; if ( server->server_name == NULL) server->n_packets++; server->next_player_info = NO_PLAYER_INFO; // we don't have a player packet } // First packet for an Unreal Tournament 2003 server void send_ut2003_request_packet( struct qserver *server) { send_packet( server, server->type->status_packet, server->type->status_len ); server->next_player_info = NO_PLAYER_INFO; } // First packet for an Half-Life 2 server void send_hl2_request_packet( struct qserver *server) { send_packet( server, server->type->status_packet, server->type->status_len ); } /* First packet for an Unreal master */ void send_unrealmaster_request_packet( struct qserver *server) { send_packet( server, server->type->status_packet, server->type->status_len ); } static const char* steam_region[] = { "US East Coast", "US West Coast", "South America", "Europe", "Asia", "Australia", "Middle East", "Africa", NULL }; char * build_hlmaster_packet( struct qserver *server, int *len) { static char packet[1600]; char *pkt, *r, *sep= ""; char *gamedir, *map, *flags; int flen; pkt= &packet[0]; memcpy( pkt, server->type->master_packet, *len ); if ( server->type->flags & TF_MASTER_STEAM ) { // default the region to 0xff const char* regionstring = get_param_value( server, "region", NULL ); int region = 0xFF; if(regionstring) { char* tmp = NULL; region = strtol( regionstring, &tmp, 10); if(tmp == regionstring) { int i = 0; region = 0xFF; for(;steam_region[i]; ++i) { if(!strcmp(regionstring, steam_region[i])) { region = i; break; } } } } *(pkt+1) = region; } pkt+= *len; gamedir = get_param_value( server, "game", NULL); if ( gamedir ) { pkt+= sprintf( pkt, "\\gamedir\\%s", gamedir); } // not valid for steam? map = get_param_value( server, "map", NULL); if ( map ) { pkt+= sprintf( pkt, "\\map\\%s", map); } // not valid for steam? flags= get_param_value( server, "status", NULL); r= flags; while ( flags && sep ) { sep= strchr( r, ':'); if ( sep ) flen= sep-r; else flen= strlen( r); if ( strncmp( r, "notempty", flen) == 0) pkt+= sprintf( pkt, "\\empty\\1"); else if ( strncmp( r, "notfull", flen) == 0) pkt+= sprintf( pkt, "\\full\\1"); else if ( strncmp( r, "dedicated", flen) == 0) { if ( server->type->flags & TF_MASTER_STEAM ) pkt+= sprintf( pkt, "\\type\\d"); else pkt+= sprintf( pkt, "\\dedicated\\1"); } else if ( strncmp( r, "linux", flen) == 0) pkt+= sprintf( pkt, "\\linux\\1"); else if ( strncmp( r, "proxy", flen) == 0) // steam pkt+= sprintf( pkt, "\\proxy\\1"); else if ( strncmp( r, "secure", flen) == 0) // steam pkt+= sprintf( pkt, "\\secure\\1"); r= sep+1; } // always need null terminator *pkt = 0x00; pkt++; *len = pkt - packet; return packet; } /* First packet for a QuakeWorld master server */ void send_qwmaster_request_packet( struct qserver *server) { int rc= 0; server->next_player_info = NO_PLAYER_INFO; if ( server->type->id == Q2_MASTER) { struct sockaddr_in addr; addr.sin_family= AF_INET; if ( no_port_offset || server->flags & TF_NO_PORT_OFFSET) { addr.sin_port= htons(server->port); } else { addr.sin_port= htons((unsigned short)( server->port + server->type->port_offset )); } addr.sin_addr.s_addr= server->ipaddr; memset( &(addr.sin_zero), 0, sizeof(addr.sin_zero)); rc= sendto( server->fd, server->type->master_packet, server->type->master_len, 0, (struct sockaddr *) &addr, sizeof(addr)); } else { char *packet; int packet_len; char query_buf[4096] = {0}; packet= server->type->master_packet; packet_len= server->type->master_len; if ( server->type->id == HL_MASTER) { memcpy( server->type->master_packet+1, server->master_query_tag, 3); if ( server->query_arg) { packet_len = server->type->master_len; packet= build_hlmaster_packet( server, &packet_len); } } else if ( server->type->flags & TF_MASTER_STEAM) { // region int tag_len = strlen( server->master_query_tag ); if ( tag_len < 9 ) { // initial case tag_len = 9; strcpy( server->master_query_tag, "0.0.0.0:0" ); } // 1 byte packet id // 1 byte region // ip:port // 1 byte null packet_len = 2 + tag_len + 1; if ( server->query_arg ) { // build_hlmaster_packet uses server->type->master_packet // as the basis so copy from server->master_query_tag strcpy( server->type->master_packet+2, server->master_query_tag ); packet = build_hlmaster_packet( server, &packet_len ); } else { // default region *(packet + 1) = 0xff; memcpy( packet+2, server->master_query_tag, tag_len ); // filter null *(packet + packet_len ) = 0x00; packet_len++; } } else if ( server->type->flags & TF_QUERY_ARG) { // fill in the master protocol details char *master_protocol= server->query_arg; if ( master_protocol == NULL) { master_protocol= server->type->master_protocol; } packet_len = sprintf( query_buf, server->type->master_packet, master_protocol?master_protocol:"", server->type->master_query?server->type->master_query:""); packet = query_buf; } rc= send( server->fd, packet, packet_len, 0); } if ( rc == SOCKET_ERROR) { perror( "send"); } if ( server->retry1 == n_retries) { gettimeofday( &server->packet_time1, NULL); server->n_requests++; } else { server->n_retries++; } server->retry1--; server->n_packets++; } void send_tribes_request_packet( struct qserver *server ) { send_packet( server, server->type->player_packet, server->type->player_len ); } void 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) perror( "send"); register_send( server ); } void send_ghostrecon_request_packet( struct qserver *server) { send_packet( server, server->type->status_packet, server->type->status_len ); } void send_eye_request_packet( struct qserver *server) { send_packet( server, server->type->status_packet, server->type->status_len ); } void send_ravenshield_request_packet( struct qserver *server) { send_packet( server, server->type->status_packet, server->type->status_len ); } void 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; } send_packet( server, pkt, len ); } void 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; } send_packet( server, pkt, len ); } void send_tribes2master_request_packet( struct qserver *server) { int rc; unsigned char packet[1600], *pkt; unsigned int len, min_players, max_players, region_mask=0; unsigned int build_version, max_bots, min_cpu, status; char *game, *mission, *buddies; static char *region_list[]= { "naeast", "nawest", "sa", "aus", "asia", "eur", NULL }; static char *status_list[]= { "dedicated", "nopassword", "linux" }; if ( strcmp( get_param_value( server, "query", ""), "types") == 0) { rc= send( server->fd, tribes2_game_types_request, sizeof(tribes2_game_types_request), 0); goto send_done; } memcpy( packet, server->type->master_packet, server->type->master_len); pkt= packet + 7; game= get_param_value( server, "game", "any"); len= strlen(game); if ( len > 255) len= 255; *pkt++= len; memcpy( pkt, game, len); pkt+= len; mission= get_param_value( server, "mission", "any"); len= strlen(mission); if ( len > 255) len= 255; *pkt++= len; memcpy( pkt, mission, len); pkt+= len; min_players= get_param_ui_value( server, "minplayers", 0); max_players= get_param_ui_value( server, "maxplayers", 255); *pkt++= min_players; *pkt++= max_players; region_mask= get_param_ui_value( server, "regions", 0xffffffff); if ( region_mask == 0) { char *regions= get_param_value( server, "regions", ""); char *r= regions; char **list, *sep; do { sep= strchr( r, ':'); if ( sep) len= sep-r; else len= strlen( r); for ( list= region_list; *list; list++) if ( strncasecmp( r, *list, len) == 0) break; if ( *list) region_mask|= 1<<(list-region_list); r= sep+1; } while ( sep); } if ( little_endian) memcpy( pkt, ®ion_mask, 4); else { pkt[0]= region_mask&0xff; pkt[1]= (region_mask>>8)&0xff; pkt[2]= (region_mask>>16)&0xff; pkt[3]= (region_mask>>24)&0xff; } pkt+= 4; build_version= get_param_ui_value( server, "build", 0); /* if ( build_version && build_version < 22337) { packet[1]= 0; build_version= 0; } */ if ( little_endian) memcpy( pkt, &build_version, 4); else { pkt[0]= build_version&0xff; pkt[1]= (build_version>>8)&0xff; pkt[2]= (build_version>>16)&0xff; pkt[3]= (build_version>>24)&0xff; } pkt+= 4; status= get_param_ui_value( server, "status", -1); if ( status == 0) { char *flags= get_param_value( server, "status", ""); char *r= flags; char **list, *sep; do { sep= strchr( r, ':'); if ( sep) len= sep-r; else len= strlen( r); for ( list= status_list; *list; list++) if ( strncasecmp( r, *list, len) == 0) break; if ( *list) status|= 1<<(list-status_list); r= sep+1; } while ( sep); } else if ( status == -1) status= 0; *pkt++= status; max_bots= get_param_ui_value( server, "maxbots", 255); *pkt++= max_bots; min_cpu= get_param_ui_value( server, "mincpu", 0); if ( little_endian) memcpy( pkt, &min_cpu, 2); else { pkt[0]= min_cpu&0xff; pkt[1]= (min_cpu>>8)&0xff; } pkt+= 2; buddies= get_param_value( server, "buddies", NULL); if ( buddies) { char *b= buddies, *sep; unsigned int buddy, n_buddies= 0; unsigned char *n_loc= pkt++; do { sep= strchr( b, ':'); if ( sscanf( b, "%u", &buddy)) { n_buddies++; if ( little_endian) memcpy( pkt, &buddy, 4); else { pkt[0]= buddy&0xff; pkt[1]= (buddy>>8)&0xff; pkt[2]= (buddy>>16)&0xff; pkt[3]= (buddy>>24)&0xff; } pkt+= 4; } b= sep+1; } while ( sep && n_buddies < 255); *n_loc= n_buddies; } else *pkt++= 0; rc= send( server->fd, (char*)packet, pkt-packet, 0); send_done: if ( rc == SOCKET_ERROR) perror( "send"); if ( server->retry1 == n_retries) { gettimeofday( &server->packet_time1, NULL); server->n_requests++; } else server->n_retries++; server->retry1--; server->n_packets++; } static struct _gamespy_query_map { char *qstat_type; char *gamespy_type; } gamespy_query_map[] = { { "qws", "quakeworld" }, { "q2s", "quake2" }, { "q3s", "quake3" }, { "tbs", "tribes" }, { "uns", "ut" }, { "sgs", "shogo" }, { "hls", "halflife" }, { "kps", "kingpin" }, { "hrs", "heretic2" }, { "sfs", "sofretail" }, { NULL, NULL } }; void send_gamespy_master_request( struct qserver *server) { int rc, i; char request[1024]; if ( server->n_packets) return; rc= send( server->fd, server->type->master_packet, server->type->master_len, 0); if ( rc != server->type->master_len) perror( "send"); strcpy( request, server->type->status_packet); for ( i= 0; gamespy_query_map[i].qstat_type; i++) if ( strcasecmp( server->query_arg, gamespy_query_map[i].qstat_type) == 0) break; if ( gamespy_query_map[i].gamespy_type == NULL) strcat( request, server->query_arg); else strcat( request, gamespy_query_map[i].gamespy_type); strcat(request, "\\final\\"); assert(strlen(request) < sizeof(request)); rc= send( server->fd, request, strlen( request), 0); if ( rc != strlen( request)) perror( "send"); if ( server->retry1 == n_retries) { gettimeofday( &server->packet_time1, NULL); server->n_requests++; } server->n_packets++; } void send_rule_request_packet( struct qserver *server) { int rc, len; debug(3, "%p", server); /* Server created via broadcast, so bind it */ if ( server->fd == -1) { if ( bind_qserver( server) < 0) goto setup_retry; } if(server->type->rule_query_func && server->type->rule_query_func != send_rule_request_packet) { server->type->rule_query_func(server); return; } if ( server->type->id == Q_SERVER) { strcpy( (char*)q_rule.data, server->next_rule); len= Q_HEADER_LEN + strlen((char*)q_rule.data) + 1; q_rule.length= htons( (short)len); } else len= server->type->rule_len; rc= send( server->fd, (const char *) server->type->rule_packet, len, 0); if ( rc == SOCKET_ERROR) perror( "send"); setup_retry: if ( server->retry1 == n_retries) { gettimeofday( &server->packet_time1, NULL); server->n_requests++; } else if ( server->server_name == NULL) server->n_retries++; server->retry1--; if ( server->server_name == NULL) server->n_packets++; } void send_player_request_packet( struct qserver *server) { int rc; if(!server->type->player_packet) return; /* Server created via broadcast, so bind it */ if ( server->fd == -1) { if ( bind_qserver( server) < 0) goto setup_retry; } if(server->type->player_query_func && server->type->player_query_func != send_player_request_packet) { server->type->player_query_func(server); return; } if(!server->type->player_packet) { debug(0, "error: server %p has no player_packet", server); return; } if ( server->type->id == Q_SERVER) q_player.data[0]= server->next_player_info; rc= send( server->fd, (const char *) server->type->player_packet, server->type->player_len, 0); if ( rc == SOCKET_ERROR) perror( "send"); setup_retry: if ( server->retry2 == n_retries) { gettimeofday( &server->packet_time2, NULL); server->n_requests++; } else server->n_retries++; server->retry2--; server->n_packets++; } void qserver_disconnect(struct qserver* server) { #ifdef _WIN32 int i; #endif if ( server->fd != -1) { close( server->fd); #ifndef _WIN32 connmap[server->fd]= NULL; #else for ( i= 0; i < max_connmap; i++) { if ( connmap[i] == server) { connmap[i]= NULL; break; } } #endif server->fd= -1; connected--; } } /* Functions for figuring timeouts and when to give up * Returns 1 if the query is done (server may be freed) and 0 if not. */ int cleanup_qserver( struct qserver *server, int force ) { int close_it = force; if ( server->server_name == NULL) { close_it = 1; if ( server->type->id & MASTER_SERVER && server->master_pkt != NULL) { server->server_name = MASTER; } else { server->server_name = TIMEOUT; num_servers_timed_out++; } } else if ( server->type->flags & TF_SINGLE_QUERY ) { close_it = 1; } else if ( server->next_rule == NO_SERVER_RULES && server->next_player_info >= server->num_players ) { close_it = 1; } debug( 3, "close_it %d", close_it ); if ( close_it ) { if ( server->saved_data.data) { SavedData *sdata= server->saved_data.next; free(server->saved_data.data); server->saved_data.data= NULL; while ( sdata != NULL) { SavedData *next; free(sdata->data); next= sdata->next; free(sdata); sdata= next; } server->saved_data.next= NULL; } qserver_disconnect(server); if ( server->server_name != TIMEOUT) { num_servers_returned++; if ( server->server_name != DOWN) { num_players_total+= server->num_players; max_players_total+= server->max_players; } } if ( server->server_name == TIMEOUT || server->server_name == DOWN) { server->ping_total= 999999; } if ( server->type->master) { waiting_for_masters--; if ( waiting_for_masters == 0) { add_servers_from_masters(); } } if ( ! server_sort) { display_server( server); } return 1; } return 0; } /** must be called only on connected servers * @returns time in ms until server needs timeout handling. timeout handling is needed if <= zero */ static int qserver_get_timeout(struct qserver* server, struct timeval* now) { int diff, diff1, diff2, interval; if ( server->type->id & MASTER_SERVER) interval= master_retry_interval; else interval= retry_interval; diff2= 0xffff; diff1= interval*(n_retries-server->retry1+1) - time_delta( now, &server->packet_time1); if (server->next_player_info < server->num_players) { diff2= interval*(n_retries-server->retry2+1) - time_delta( now, &server->packet_time2); } debug(2, "timeout for %p is diff1 %d diff2 %d", server, diff1, diff2); diff= (diff1fd == -1; server= server->next) ; /* if there are unconnected servers and slots left we retry in 10ms */ if ( server == NULL || (num_servers > connected && connected < max_simultaneous)) { timeout->tv_sec= 0; timeout->tv_usec= 10 * 1000; return; } first_server_bind= server; gettimeofday( &now, NULL); for ( ; server != NULL && bind_count < connected; server= server->next) { if ( server->fd == -1) continue; diff = qserver_get_timeout(server, &now); if ( diff < smallest) smallest= diff; bind_count++; } if ( smallest < 10) smallest= 10; timeout->tv_sec= smallest / 1000; timeout->tv_usec= (smallest % 1000) * 1000; } #ifdef USE_SELECT static fd_set select_read_fds; static int select_maxfd; static int select_cursor; int set_fds( fd_set *fds) { int maxfd= -1, i; for ( i= 0; i < max_connmap; i++) { if ( connmap[i] != NULL) { FD_SET( connmap[i]->fd, fds); if ( connmap[i]->fd > maxfd) { maxfd= connmap[i]->fd; } } } return maxfd; } void set_file_descriptors() { FD_ZERO( &select_read_fds); select_maxfd= set_fds( &select_read_fds); } int wait_for_file_descriptors( struct timeval *timeout) { select_cursor= 0; return select( select_maxfd+1, &select_read_fds, NULL, NULL, timeout); } struct qserver * get_next_ready_server() { while ( select_cursor < max_connmap && ( connmap[select_cursor] == NULL || ! FD_ISSET( connmap[select_cursor]->fd, &select_read_fds)) ) { select_cursor++; } if ( select_cursor >= max_connmap) return NULL; return connmap[select_cursor++]; } int wait_for_timeout( unsigned int ms) { struct timeval timeout; timeout.tv_sec= ms/1000; timeout.tv_usec= (ms%1000) * 1000; return select( 0, 0, NULL, NULL, &timeout); } #endif /* USE_SELECT */ #ifdef USE_POLL static struct pollfd *pollfds; static int n_pollfds; static int max_pollfds= 0; static int poll_cursor; void set_file_descriptors() { struct pollfd *p; int i; if ( max_connmap > max_pollfds) { max_pollfds= max_connmap; pollfds= (struct pollfd *) realloc( pollfds, max_pollfds * sizeof(struct pollfd)); } p= pollfds; for ( i= 0; i < max_connmap; i++) if ( connmap[i] != NULL) { p->fd= connmap[i]->fd; p->events= POLLIN; p->revents= 0; p++; } n_pollfds= p - pollfds; } int wait_for_file_descriptors( struct timeval *timeout) { poll_cursor= 0; return poll( pollfds, n_pollfds, timeout->tv_sec*1000 + timeout->tv_usec/1000); } struct qserver * get_next_ready_server() { for ( ; poll_cursor < n_pollfds; poll_cursor++) { if ( pollfds[ poll_cursor].revents) break; } if ( poll_cursor >= n_pollfds) return NULL; return connmap[pollfds[poll_cursor++].fd]; } int wait_for_timeout( unsigned int ms) { return poll( 0, 0, ms); } #endif /* USE_POLL */ void free_server( struct qserver *server) { struct player *player, *next_player; struct rule *rule, *next_rule; /* remove from servers list */ if ( server == servers) { servers= server->next; if ( servers) servers->prev= NULL; } if ( (void*) &server->next == (void*) last_server) { if ( server->prev) last_server= & server->prev->next; else last_server= & servers; } if ( server == first_server_bind) first_server_bind= server->next; if ( server == last_server_bind) last_server_bind= server->next; if ( server->prev) server->prev->next= server->next; if ( server->next) server->next->prev= server->prev; /* remove from server hash table */ remove_server_from_hash( server); /* free all the data */ for ( player= server->players; player; player= next_player) { next_player= player->next; free_player( player); } for ( rule= server->rules; rule; rule= next_rule) { next_rule= rule->next; free_rule( rule); } if ( server->arg) free( server->arg); if ( server->host_name) free( server->host_name); if ( server->error) free( server->error); if ( server->address) free( server->address); if ( server->map_name) free( server->map_name); if ( !(server->flags&FLAG_DO_NOT_FREE_GAME) && server->game) free(server->game); if ( server->master_pkt) free( server->master_pkt); free(server->query_arg); /* These fields are never malloc'd: outfilename */ if ( server->server_name != NULL && server->server_name != DOWN && server->server_name != HOSTNOTFOUND && server->server_name != SYSERROR && server->server_name != MASTER && server->server_name != SERVERERROR && server->server_name != TIMEOUT && server->server_name != GAMESPY_MASTER_NAME && server->server_name != BFRIS_SERVER_NAME) { free( server->server_name); } /* params ... saved_data ... */ free( server); --num_servers; } void free_player( struct player *player) { if ( player->name) free( player->name); if ( ! (player->flags&PLAYER_FLAG_DO_NOT_FREE_TEAM) && player->team_name) free( player->team_name); if ( player->address) free( player->address); if ( player->tribe_tag) free( player->tribe_tag); if ( player->skin) free( player->skin); if ( player->mesh) free( player->mesh); if ( player->face) free( player->face); free( player); } void free_rule( struct rule *rule) { if ( rule->name) free( rule->name); if ( rule->value) free( rule->value); free( rule); } /* Functions for handling response packets */ /* Packet from normal Quake server */ void deal_with_q_packet( struct qserver *server, char *rawpkt, int pktlen) { struct q_packet *pkt= (struct q_packet *)rawpkt; int rc; if ( ntohs( pkt->length) != pktlen) { fprintf( stderr, "%s Ignoring bogus packet; length %d != %d\n", server->arg, ntohs( pkt->length), pktlen); cleanup_qserver(server,FORCE); return; } rawpkt[pktlen]= '\0'; switch ( pkt->op_code) { case Q_CCREP_ACCEPT: case Q_CCREP_REJECT: return; case Q_CCREP_SERVER_INFO: server->ping_total+= time_delta( &packet_recv_time, &server->packet_time1); rc= server_info_packet( server, pkt, pktlen-Q_HEADER_LEN); break; case Q_CCREP_PLAYER_INFO: server->ping_total+= time_delta( &packet_recv_time, &server->packet_time2); rc= player_info_packet( server, pkt, pktlen-Q_HEADER_LEN); break; case Q_CCREP_RULE_INFO: server->ping_total+= time_delta( &packet_recv_time, &server->packet_time1); rc= rule_info_packet( server, pkt, pktlen-Q_HEADER_LEN); break; case Q_CCREQ_CONNECT: case Q_CCREQ_SERVER_INFO: case Q_CCREQ_PLAYER_INFO: case Q_CCREQ_RULE_INFO: default: return; } if ( rc == -1) fprintf( stderr, "%s error on packet opcode %x\n", server->arg, (int)pkt->op_code); cleanup_qserver( server, (rc == -1) ? FORCE : 0); } /* Packet from QuakeWorld server */ void deal_with_qw_packet( struct qserver *server, char *rawpkt, int pktlen) { if ( server->server_name == NULL) server->ping_total+= time_delta( &packet_recv_time, &server->packet_time1); if ( ((rawpkt[0] != '\377' && rawpkt[0] != '\376') || rawpkt[1] != '\377' || rawpkt[2] != '\377' || rawpkt[3] != '\377') && show_errors) { unsigned int ipaddr= ntohl(server->ipaddr); fprintf( stderr, "Odd packet from server %d.%d.%d.%d:%hu, processing ...\n", (ipaddr>>24)&0xff, (ipaddr>>16)&0xff, (ipaddr>>8)&0xff, ipaddr&0xff, ntohs(server->port)); print_packet( server, rawpkt, pktlen); } rawpkt[pktlen]= '\0'; if ( rawpkt[4] == 'n') { if ( server->type->id != QW_SERVER) server->type= find_server_type_id( QW_SERVER); deal_with_q1qw_packet( server, rawpkt, pktlen); return; } else if ( rawpkt[4] == '\377' && rawpkt[5] == 'n') { if ( server->type->id != HW_SERVER) server->type= find_server_type_id( HW_SERVER); deal_with_q1qw_packet( server, rawpkt, pktlen); return; } else if ( strncmp( &rawpkt[4], "print\n\\", 7) == 0) { deal_with_q2_packet( server, rawpkt+10, pktlen-10, 0); return; } else if ( strncmp( &rawpkt[4], "print\n", 6) == 0) { /* work-around for occasional bug in Quake II status packets */ char *c, *p; p= c= &rawpkt[10]; while ( *p != '\\' && (c= strchr( p, '\n'))) p= c+1; if ( *p == '\\' && c != NULL) { deal_with_q2_packet( server, p, pktlen-(p-rawpkt), 0); return; } } else if ( strncmp( &rawpkt[4], "infoResponse", 12) == 0 || (rawpkt[4] == '\001' && strncmp( &rawpkt[5], "infoResponse", 12) == 0) ) { /* quake3 info response */ if ( rawpkt[4] == '\001') { rawpkt++; pktlen--; } rawpkt+= 12; pktlen-= 12; for ( ; pktlen && *rawpkt != '\\'; pktlen--, rawpkt++) ; if ( !pktlen) return; if ( rawpkt[pktlen-1] == '"') { rawpkt[pktlen-1]= '\0'; pktlen--; } if ( get_player_info || get_server_rules) server->next_rule= ""; deal_with_q2_packet( server, rawpkt, pktlen, 0); if ( (get_player_info || get_server_rules) && ( server->flags & FLAG_BROADCAST || server->fd != -1)) { send_rule_request_packet( server); server->retry1= n_retries-1; } return; } else if ( strncmp( &rawpkt[4], "statusResponse\n", 15) == 0 || (rawpkt[4] == '\001' && strncmp( &rawpkt[5], "statusResponse\n", 15) == 0) ) { /* quake3 status response */ server->next_rule= NO_SERVER_RULES; server->retry1 = 0; if ( rawpkt[4] == '\001') { rawpkt++; pktlen--; } deal_with_q2_packet( server, rawpkt + 19, pktlen - 19, CHECK_DUPLICATE_RULES); return; } else if ( strncmp( &rawpkt[4], "infostringresponse", 19) == 0) { deal_with_q2_packet( server, rawpkt+23, pktlen-23, 0); return; } if ( show_errors) { unsigned int ipaddr= ntohl(server->ipaddr); fprintf( stderr, "Odd packet from server %d.%d.%d.%d:%hu, ignoring ...\n", (ipaddr>>24)&0xff, (ipaddr>>16)&0xff, (ipaddr>>8)&0xff, ipaddr&0xff, ntohs(server->port)); print_packet( server, rawpkt, pktlen); cleanup_qserver( server, 1); } else cleanup_qserver( server, 0); } void deal_with_q1qw_packet( struct qserver *server, char *rawpkt, int pktlen) { char *key, *value, *end; struct player *player= NULL, **last_player= &server->players; int len, rc, complete= 0; int number, frags, connect_time, ping; char *pkt= &rawpkt[5]; if ( server->type->id == HW_SERVER) pkt= &rawpkt[6]; while ( *pkt && pkt-rawpkt < pktlen) { if ( *pkt == '\\') { pkt++; end= strchr( pkt, '\\'); if ( end == NULL) break; *end= '\0'; key= pkt; pkt+= strlen(pkt)+1; end= strchr( pkt, '\\'); if ( end == NULL) end= strchr( pkt, '\n'); value= (char*) malloc(end-pkt+1); memcpy( value, pkt, end-pkt); value[end-pkt]= '\0'; pkt= end; if ( strcmp( key, "hostname") == 0) server->server_name= value; else if ( strcmp( key, "map") == 0) server->map_name= value; else if ( strcmp( key, "maxclients") == 0) { server->max_players= atoi(value); free( value); } else if ( get_server_rules || strncmp( key, "*game", 5) == 0) { add_rule( server, key, value, NO_VALUE_COPY); if ( strcmp( key, "*gamedir") == 0) { server->game= value; server->flags |= FLAG_DO_NOT_FREE_GAME; } } } else if ( *pkt == '\n') { pkt++; if ( pkt-rawpkt>=pktlen || *pkt == '\0') break; rc= sscanf( pkt, "%d %d %d %d %n", &number, &frags, &connect_time, &ping, &len); if ( rc != 4) { char *nl; /* assume it's an error packet */ server->error= (char*)malloc( pktlen+1); nl= strchr( pkt, '\n'); if ( nl != NULL) { strncpy( server->error, pkt, nl-pkt); server->error[nl-pkt]= '\0'; } else strcpy( server->error, pkt); server->server_name= SERVERERROR; complete= 1; break; } if ( get_player_info) { player= (struct player *) calloc( 1, sizeof( struct player)); player->number= number; player->frags= frags; player->connect_time= connect_time * 60; player->ping= ping; } else player= NULL; pkt+= len; if ( *pkt != '"') break; pkt++; end= strchr( pkt, '"'); if ( end == NULL) break; if ( player != NULL) { player->name= (char*) malloc(end-pkt+1); memcpy( player->name, pkt, end-pkt); player->name[end-pkt]= '\0'; } pkt= end+2; if ( *pkt != '"') break; pkt++; end= strchr( pkt, '"'); if ( end == NULL) break; if ( player != NULL) { player->skin= (char*) malloc(end-pkt+1); memcpy( player->skin, pkt, end-pkt); player->skin[end-pkt]= '\0'; } pkt= end+2; if ( player != NULL) { sscanf( pkt, "%d %d%n", &player->shirt_color, &player->pants_color, &len); *last_player= player; last_player= & player->next; } else sscanf( pkt, "%*d %*d%n", &len); pkt+= len; server->num_players++; } else pkt++; complete= 1; } if ( !complete) { if ( rawpkt[4] != 'n' || rawpkt[5] != '\0') { fprintf( stderr, "Odd packet from QW server %d.%d.%d.%d:%hu ...\n", (server->ipaddr>>24)&0xff, (server->ipaddr>>16)&0xff, (server->ipaddr>>8)&0xff, server->ipaddr&0xff, ntohs(server->port)); print_packet( server, rawpkt, pktlen); } } else if ( server->server_name == NULL) server->server_name= strdup(""); cleanup_qserver( server, 0); } void deal_with_q2_packet( struct qserver *server, char *rawpkt, int pktlen, int check_duplicate_rules) { char *key, *value, *end; struct player *player= NULL; struct player **last_player= & server->players; int len, rc, complete= 0; int frags=0, ping=0, num_players= 0; char *pkt= rawpkt; while ( *pkt && pkt-rawpkt < pktlen) { if ( *pkt == '\\') { pkt++; if ( *pkt == '\n' && server->type->id == SOF_SERVER) goto player_info; end= strchr( pkt, '\\'); if ( end == NULL) break; *end= '\0'; key= pkt; pkt+= strlen(pkt)+1; end= strpbrk( pkt, "\\\n"); if(!end) { end= rawpkt+pktlen; } value= (char*) malloc(end-pkt+1); memcpy( value, pkt, end-pkt); value[end-pkt]= '\0'; pkt= end; debug(3, "%s = %s", key, value); if ( server->server_name == NULL && (strcmp( key, "hostname") == 0 || strcmp( key, "sv_hostname") == 0)) server->server_name= value; else if ( strcmp( key, "mapname") == 0 || (strcmp( key, "map") == 0 && server->map_name == NULL)) { if ( server->map_name != NULL) free( server->map_name); server->map_name= value; } else if ( strcmp( key, "maxclients") == 0 || strcmp( key, "sv_maxclients") == 0 || strcmp( key, "max") == 0) { server->max_players= atoi(value); /* MOHAA Q3 protocol max players is always 0 */ if ( server->max_players == 0) server->max_players= -1; free(value); } else if ( strcmp( key, "clients") == 0 || strcmp( key, "players") == 0) { server->num_players= atoi(value); free(value); } else if ( server->server_name == NULL && strcmp( key, "pure") == 0) { add_rule( server, key, value, NO_VALUE_COPY); } else if ( get_server_rules || strncmp( key, "game", 4) == 0) { if ( add_rule( server, key, value, NO_VALUE_COPY|check_duplicate_rules) == NULL) { free(value); /* duplicate, so free value */ } if ( server->game == NULL && strcmp( key, server->type->game_rule) == 0) { server->game= value; server->flags |= FLAG_DO_NOT_FREE_GAME; } } else free(value); } else if ( *pkt == '\n') { player_info: pkt++; if ( *pkt == '\0') break; if(!strncmp(pkt, "\\challenge\\", 11)) { // qfusion // This doesnt support getstatus looking at warsow source: // server/sv_main.c: SV_ConnectionlessPacket server->next_rule = NO_SERVER_RULES; break; } rc= sscanf( pkt, "%d %n", &frags, &len); if ( rc == 1 && pkt[len] != '"') { pkt+= len; rc= sscanf( pkt, "%d %n", &ping, &len); } else if ( rc == 1) { /* MOHAA Q3 protocol only provides player ping */ ping= frags; frags= 0; } if ( rc != 1) { char *nl; /* assume it's an error packet */ server->error= (char*)malloc( pktlen+1); nl= strchr( pkt, '\n'); if ( nl != NULL) strncpy( server->error, pkt, nl-pkt); else strcpy( server->error, pkt); server->server_name= SERVERERROR; complete= 1; break; } if ( get_player_info) { player= (struct player *) calloc( 1, sizeof( struct player)); player->number= 0; player->connect_time= -1; player->frags= frags; player->ping= ping; } else player= NULL; pkt+= len; if ( isdigit((unsigned char)*pkt)) { /* probably an SOF2 1.01 server, includes team # */ int team; rc= sscanf( pkt, "%d %n", &team, &len); if ( rc == 1) { pkt+= len; if ( player) { player->team= team; server->flags|= FLAG_PLAYER_TEAMS; } } } if ( *pkt != '"') break; pkt++; end= strchr( pkt, '"'); if ( end == NULL) break; if ( player != NULL) { player->name= (char*) malloc(end-pkt+1); memcpy( player->name, pkt, end-pkt); player->name[end-pkt]= '\0'; } pkt= end+1; //WarSoW team number if ( *pkt != '\n') { int team; rc= sscanf( pkt, "%d%n", &team, &len); if ( rc == 1) { pkt+= len; if ( player) { player->team= team; server->flags|= FLAG_PLAYER_TEAMS; } } } if ( player != NULL) { player->skin= NULL; player->shirt_color= -1; player->pants_color= -1; *last_player= player; last_player= & player->next; } num_players++; } else pkt++; complete= 1; } if ( server->num_players == 0 || num_players > server->num_players) server->num_players= num_players; if ( !complete) { cleanup_qserver( server, 1); return; } else if ( server->server_name == NULL) server->server_name= strdup(""); cleanup_qserver( server, 0); } void ack_descent3master_packet( struct qserver *server, char *curtok ) { int rc; char packet[0x1e]; memcpy( packet, descent3_masterquery,0x1a); packet[1]= 0x1d; packet[0x16]= 1; memcpy( packet + 0x1a, curtok, 4); rc= send( server->fd, packet, sizeof(packet), 0); if ( rc == SOCKET_ERROR) perror( "send"); } /* Packet from Descent3 master server (PXO) */ void deal_with_descent3master_packet( struct qserver *server, char *rawpkt, int pktlen) { int i= 0, lastpacket= 0; char *names= rawpkt + 0x1f; char *ips= rawpkt + 0x29f; char *ports= rawpkt + 0x2ef; /* printf ("s=%p p=%p l=%i\n",server,rawpkt,pktlen); */ while ( i<20) { if ( *names) { char *c; server->master_pkt_len += 6; server->master_pkt= (char*)realloc( server->master_pkt, server->master_pkt_len); c= server->master_pkt + server->master_pkt_len - 6; memcpy( c, ips, 4); memcpy( c + 4, ports, 2); }else if (i>0) lastpacket= 1; names+= 0x20; ips+= 4; ports+= 2; i++; } ack_descent3master_packet( server, rawpkt+0x1a); server->n_servers= server->master_pkt_len / 6; server->next_player_info= -1; server->retry1= 0; if (lastpacket) { cleanup_qserver( server, 0); } } /* Packet from QuakeWorld master server */ void deal_with_qwmaster_packet( struct qserver *server, char *rawpkt, int pktlen) { server->ping_total+= time_delta( &packet_recv_time, &server->packet_time1); if ( rawpkt[0] == QW_NACK) { server->error= strdup( &rawpkt[2]); server->server_name= SERVERERROR; cleanup_qserver( server, 1); return; } if ( *((unsigned int*)rawpkt) == 0xffffffff) { rawpkt+= 4; /* QW 1.5 */ pktlen-= 4; } if ( rawpkt[0] == QW_SERVERS && rawpkt[1] == QW_NEWLINE) { rawpkt+= 2; pktlen-= 2; } else if ( rawpkt[0] == HL_SERVERS && rawpkt[1] == 0x0d ) { // 2 byte id + 4 byte sequence memcpy( server->master_query_tag, rawpkt+2, 3); rawpkt+= 6; pktlen-= 6; } else if ( rawpkt[0] == HL_SERVERS && rawpkt[1] == 0x0a ) { // no sequence id for steam // instead we use the ip:port of the last recieved server struct in_addr *sin_addr = (struct in_addr*)(rawpkt+pktlen-6); char *ip = inet_ntoa( *sin_addr ); unsigned short port = htons( *((unsigned short*)(rawpkt+pktlen-2)) ); //fprintf( stderr, "NEXT IP=%s:%u\n", ip, port ); sprintf( server->master_query_tag, "%s:%u", ip, port ); // skip over the 2 byte id rawpkt+= 2; pktlen-= 2; } else if ( strncmp( rawpkt, "servers", 7) == 0) { rawpkt+= 8; pktlen-= 8; } else if ( strncmp( rawpkt, "getserversResponse", 18) == 0) { static int q3m_debug= 0; rawpkt+= 18; pktlen-= 18; for ( ; *rawpkt != '\\' && pktlen; pktlen--, rawpkt++) { } if ( !pktlen) { return; } rawpkt++; pktlen--; if ( q3m_debug) printf( "q3m pktlen %d lastchar %x\n", pktlen, (unsigned int)rawpkt[pktlen-1]); server->master_pkt= (char*)realloc( server->master_pkt, server->master_pkt_len + pktlen+1); if ( server->type->id == STEF_MASTER) { decode_stefmaster_packet( server, rawpkt, pktlen); } else { decode_q3master_packet( server, rawpkt, pktlen); } if ( q3m_debug) printf( "q3m %d servers\n", server->n_servers); return; } else if ( show_errors) { unsigned int ipaddr= ntohl(server->ipaddr); fprintf( stderr, "Odd packet from QW master %d.%d.%d.%d, processing ...\n", (ipaddr>>24)&0xff, (ipaddr>>16)&0xff, (ipaddr>>8)&0xff, ipaddr&0xff ); print_packet( server, rawpkt, pktlen); } server->master_pkt= (char*)realloc( server->master_pkt, server->master_pkt_len+pktlen+1); rawpkt[pktlen]= '\0'; memcpy( server->master_pkt+server->master_pkt_len, rawpkt, pktlen+1); server->master_pkt_len+= pktlen; server->n_servers= server->master_pkt_len / 6; if ( server->type->flags & TF_MASTER_MULTI_RESPONSE) { server->next_player_info= -1; server->retry1= 0; } else if ( server->type->id == HL_MASTER ) { if ( server->master_query_tag[0] == 0 && server->master_query_tag[1] == 0 && server->master_query_tag[2] == 0 ) { // all done server->server_name = MASTER; cleanup_qserver( server, 1); bind_sockets(); } else { // more to come server->retry1++; send_qwmaster_request_packet( server); } } else if ( server->type->flags & TF_MASTER_STEAM ) { // should the HL_MASTER be the same as this? int i; for ( i = pktlen - 6; i < pktlen && 0x00 == rawpkt[i] ; i++ ) { } if ( i == pktlen ) { // last 6 bytes where 0x00 so we have reached the last packet server->n_servers--; server->master_pkt_len -= 6; server->server_name = MASTER; cleanup_qserver( server, 1); bind_sockets(); } else { // more to come server->retry1++; send_qwmaster_request_packet( server ); } } else { server->server_name = MASTER; cleanup_qserver( server, 0); bind_sockets(); } } void decode_q3master_packet( struct qserver *server, char *pkt, int pktlen) { char *p; pkt[pktlen]= 0; p= pkt; while ( *p && p < &pkt[pktlen-6]) { memcpy( server->master_pkt + server->master_pkt_len, &p[0], 4); memcpy( server->master_pkt + server->master_pkt_len + 4, &p[4], 2); server->master_pkt_len += 6; p+= 6; while ( *p && *p == '\\') p++; } server->n_servers= server->master_pkt_len / 6; // server->next_player_info= -1; evil, causes busy loop! server->retry1 = 0; // received at least one packet so no need to retry } void decode_stefmaster_packet( struct qserver *server, char *pkt, int pktlen) { unsigned char *p, *m, *end; unsigned int i, b; pkt[pktlen]= 0; p= (unsigned char*) pkt; m= (unsigned char*) server->master_pkt + server->master_pkt_len; end= (unsigned char*) &pkt[pktlen-12]; while ( *p && p < end) { for ( i= 6; i; i--) { sscanf( (char*)p, "%2x", &b); p+= 2; *m++= b; } server->master_pkt_len += 6; while ( *p && *p == '\\') p++; } server->n_servers= server->master_pkt_len / 6; server->next_player_info= -1; server->retry1= 0; } /* Packet from Tribes master server */ void deal_with_tribesmaster_packet( struct qserver *server, char *rawpkt, int pktlen) { unsigned char *upkt= (unsigned char*) rawpkt; int packet_number= upkt[2]; int n_packets= upkt[3]; unsigned char *p; char *mpkt; int len; unsigned int ipaddr; if ( memcmp( rawpkt, tribes_master_response, sizeof(tribes_master_response)) != 0) { fprintf( stderr, "Odd packet from Tribes master server\n"); print_packet( server, rawpkt, pktlen); } /* 0x1006 01 packet number 08 # packets 02 0000 66 0d length of following string "Tribes Master" 3c length of following string "Check out the Starsiege demo now! www.starsiegeplayers.com" 0035 06 d143 4764 812c 06 d1e2 8df3 616d 06 1804 6d50 616d 06 d81c 6dc0 616d */ /* 0x1006 02 08 02 0000 66 00 3f 06 cf88 344c 1227 */ /* printf( "packet_number %d n_packets %d\n", packet_number, n_packets); */ len= upkt[8]; if ( len > 0) { p= (unsigned char*) rawpkt+9; /* printf( "%.*s\n", len, p); */ p+= len; len= upkt[8+len+1]; /* printf( "%.*s\n", len, p+1); */ p+= len+1; p+= 2; } else p= (unsigned char*) rawpkt+10; if ( server->master_pkt == NULL) { server->master_pkt= (char*)malloc( n_packets*64*6); mpkt= server->master_pkt; } else mpkt= server->master_pkt + server->n_servers*6; while ( (char*)p < rawpkt+pktlen) { if ( *p != 0x6) printf( "*p %u\n", (unsigned)*p); memcpy( mpkt, p+1, sizeof(ipaddr)); if ( 0) { mpkt[4]= p[5]; mpkt[5]= p[6]; } else { mpkt[5]= p[5]; mpkt[4]= p[6]; } /* printf( "%08x:%hu %u.%u.%u.%u:%hu\n", ipaddr, port, ipaddr>>24, (ipaddr>>16)&0xff, (ipaddr>>8)&0xff, ipaddr&0xff, port); */ p+= 7; mpkt+= 6; } /* if ( (char*)p != rawpkt+pktlen) printf( "%x %x\n", p, rawpkt+pktlen); */ server->master_pkt_len= mpkt - server->master_pkt; server->n_servers= server->master_pkt_len / 6; server->server_name= MASTER; server->next_player_info= -1; if ( packet_number >= n_packets) cleanup_qserver( server, 1); else cleanup_qserver( server, 0); } char * display_tribes2_string_list( unsigned char *pkt) { char *delim=""; unsigned int count, len; count= *pkt; pkt++; for ( ; count; count--) { len= *pkt; pkt++; if ( len > 0) { if ( raw_display) { fprintf( OF, "%s%.*s", delim, (int)len, pkt); delim= raw_delimiter; } else fprintf( OF, "%.*s\n", (int)len, pkt); } pkt+= len; } if ( raw_display) fputs( "\n", OF); return (char*)pkt; } void deal_with_tribes2master_packet( struct qserver *server, char *pkt, int pktlen) { unsigned int n_servers, index, total, server_limit; char *p, *mpkt; if ( pkt[0] == TRIBES2_RESPONSE_GAME_TYPES) { pkt+= 6; if ( raw_display) { fprintf( OF, "%s%s%s%s", server->type->type_prefix, raw_delimiter, server->arg, raw_delimiter); } else { fprintf( OF, "Game Types\n"); fprintf( OF, "----------\n"); } pkt= display_tribes2_string_list( (unsigned char *)pkt); if ( raw_display) { fprintf( OF, "%s%s%s%s", server->type->type_prefix, raw_delimiter, server->arg, raw_delimiter); } else { fprintf( OF, "\nMission Types\n"); fprintf( OF, "-------------\n"); } display_tribes2_string_list( (unsigned char *)pkt); server->master_pkt_len= 0; server->n_servers= 0; server->server_name= MASTER; server->next_player_info= -1; cleanup_qserver( server, 1); return; } if ( pkt[0] != TRIBES2_RESPONSE_MASTER) { /* error */ cleanup_qserver( server, 1); return; } server_limit= get_param_ui_value( server, "limit", ~0); n_servers= little_endian ? *(unsigned short*)(pkt+8) : swap_short(pkt+8); index= *(unsigned char*)(pkt+6); total= *(unsigned char*)(pkt+7); if ( server->master_pkt == NULL) { server->master_pkt= (char*)malloc( total * n_servers * 6); mpkt= server->master_pkt; } else mpkt= server->master_pkt + server->n_servers*6; p= pkt+10; for ( ; n_servers && ((char*)mpkt - server->master_pkt)/6 < server_limit; n_servers--, p+= 6, mpkt+= 6) { memcpy( mpkt, p, 4); mpkt[4]= p[5]; mpkt[5]= p[4]; } server->master_pkt_len= (char*)mpkt - server->master_pkt; server->n_servers= server->master_pkt_len / 6; server->server_name= MASTER; server->next_player_info= -1; if ( index >= total-1 || server->n_servers >= server_limit) cleanup_qserver( server, 1); else cleanup_qserver( server, 0); } int server_info_packet( struct qserver *server, struct q_packet *pkt, int datalen) { int off= 0; /* ignore duplicate packets */ if ( server->server_name != NULL) return 0; server->address= strdup((char*)&pkt->data[off]); off+= strlen(server->address) + 1; if ( off >= datalen) return -1; server->server_name= strdup((char*)&pkt->data[off]); off+= strlen(server->server_name) + 1; if ( off >= datalen) return -1; server->map_name= strdup((char*)&pkt->data[off]); off+= strlen(server->map_name) + 1; if ( off > datalen) return -1; server->num_players= pkt->data[off++]; server->max_players= pkt->data[off++]; server->protocol_version= pkt->data[off++]; server->retry1= n_retries; if ( get_server_rules) send_rule_request_packet( server); if ( get_player_info) send_player_request_packet( server); return 0; } int player_info_packet( struct qserver *server, struct q_packet *pkt, int datalen) { char *name, *address; int off, colors, frags, connect_time, player_number; struct player *player, *last; off= 0; player_number= pkt->data[off++]; name= (char*) &pkt->data[off]; off+= strlen(name)+1; if ( off >= datalen) return -1; colors= pkt->data[off+3]; colors= (colors<<8) + pkt->data[off+2]; colors= (colors<<8) + pkt->data[off+1]; colors= (colors<<8) + pkt->data[off]; off+= sizeof(colors); frags= pkt->data[off+3]; frags= (frags<<8) + pkt->data[off+2]; frags= (frags<<8) + pkt->data[off+1]; frags= (frags<<8) + pkt->data[off]; off+= sizeof(frags); connect_time= pkt->data[off+3]; connect_time= (connect_time<<8) + pkt->data[off+2]; connect_time= (connect_time<<8) + pkt->data[off+1]; connect_time= (connect_time<<8) + pkt->data[off]; off+= sizeof(connect_time); address= (char*) &pkt->data[off]; off+= strlen(address)+1; if ( off > datalen) return -1; last= server->players; while ( last != NULL && last->next != NULL) { if ( last->number == player_number) return 0; last= last->next; } if ( last != NULL && last->number == player_number) return 0; player= (struct player *) calloc( 1, sizeof(struct player)); player->number= player_number; player->name= strdup( name); player->address= strdup( address); player->connect_time= connect_time; player->frags= frags; player->shirt_color= colors>>4; player->pants_color= colors&0xf; player->next= NULL; if ( last == NULL) server->players= player; else last->next= player; server->next_player_info++; server->retry2= n_retries; if ( server->next_player_info < server->num_players) send_player_request_packet( server); return 0; } int rule_info_packet( struct qserver *server, struct q_packet *pkt, int datalen) { int off= 0; struct rule *rule, *last; char *name, *value; /* Straggler packet after we've already given up fetching rules */ if ( server->next_rule == NULL) return 0; if ( ntohs(pkt->length) == Q_HEADER_LEN) { server->next_rule= NULL; return 0; } name= (char*)&pkt->data[off]; off+= strlen( name)+1; if ( off >= datalen) return -1; value= (char*)&pkt->data[off]; off+= strlen( value)+1; if ( off > datalen) return -1; last= server->rules; while ( last != NULL && last->next != NULL) { if ( strcmp( last->name, name) == 0) return 0; last= last->next; } if ( last != NULL && strcmp( last->name, name) == 0) return 0; rule= (struct rule *) malloc( sizeof( struct rule)); rule->name= strdup( name); rule->value= strdup( value); rule->next= NULL; if ( last == NULL) server->rules= rule; else last->next= rule; server->n_rules++; server->next_rule= rule->name; server->retry1= n_retries; send_rule_request_packet( server); return 0; } struct info * player_add_info( struct player *player, char *key, char *value, int flags) { struct info *info; if ( flags & CHECK_DUPLICATE_RULES ) { for ( info = player->info; info; info = info->next ) { if ( 0 == strcmp( info->name, key ) ) { return NULL; } } } if ( flags & COMBINE_VALUES ) { for ( info = player->info; info; info = info->next ) { if ( 0 == strcmp( info->name, key ) ) { char *full_value = (char*)calloc( sizeof(char), strlen( info->value ) + strlen( value ) + 2 ); if ( NULL == full_value ) { fprintf( stderr, "Failed to malloc combined value\n" ); exit( 1 ); } sprintf( full_value, "%s%s%s", info->value, multi_delimiter, value ); // We should be able to free this free( info->value ); info->value = full_value; return info; } } } info = (struct info *) malloc( sizeof( struct info)); if ( flags & NO_KEY_COPY) { info->name = key; } else { info->name = strdup(key); } if ( flags & NO_VALUE_COPY) { info->value = value; } else { info->value = strdup(value); } info->next = NULL; if ( NULL == player->info ) { player->info = info; } else { *player->last_info = info; } player->last_info = &info->next; player->n_info++; return info; } struct rule * add_rule( struct qserver *server, char *key, char *value, int flags) { struct rule *rule; if ( flags & CHECK_DUPLICATE_RULES ) { for ( rule = server->rules; rule; rule = rule->next ) { if ( 0 == strcmp( rule->name, key ) ) { return NULL; } } } if ( flags & COMBINE_VALUES ) { for ( rule = server->rules; rule; rule = rule->next ) { if ( 0 == strcmp( rule->name, key ) ) { char *full_value = (char*)calloc( sizeof(char), strlen( rule->value ) + strlen( value ) + 2 ); if ( NULL == full_value ) { fprintf( stderr, "Failed to malloc combined value\n" ); exit( 1 ); } sprintf( full_value, "%s%s%s", rule->value, multi_delimiter, value ); // We should be able to free this free( rule->value ); rule->value = full_value; return rule; } } } rule = (struct rule *) malloc( sizeof( struct rule)); if ( flags & NO_KEY_COPY) { rule->name = key; } else { rule->name = strdup(key); } if ( flags & NO_VALUE_COPY) { rule->value = value; } else { rule->value = strdup(value); } rule->next = NULL; *server->last_rule = rule; server->last_rule = & rule->next; server->n_rules++; return rule; } void add_nrule( struct qserver *server, char *key, char *value, int len) { struct rule *rule; for ( rule= server->rules; rule; rule= rule->next) if ( strcmp( rule->name, key) == 0) return; rule= (struct rule *) malloc( sizeof( struct rule)); rule->name= strdup(key); rule->value= strndup(value,len); rule->next= NULL; *server->last_rule= rule; server->last_rule= & rule->next; server->n_rules++; } struct player * add_player( struct qserver *server, int player_number ) { struct player *player; for ( player = server->players; player; player = player->next ) { if ( player->number == player_number) { return NULL; } } player = (struct player *) calloc( 1, sizeof( struct player)); player->number = player_number; player->next = server->players; player->n_info = 0; player->score = NA_INT; player->deaths = NA_INT; player->frags = NA_INT; player->last_info = NULL; server->players = player; server->n_player_info++; return player; } STATIC struct player * get_player_by_number( struct qserver *server, int player_number) { struct player *player; for ( player= server->players; player; player= player->next) if ( player->number == player_number) return player; return NULL; } // Updates a servers port information. // Sets the rules: // _queryport // hostport void change_server_port( struct qserver *server, unsigned short port, int force ) { if ( port > 0 && port != server->port ) { // valid port and changing char arg[64]; if ( show_game_port || force || server->flags & TF_SHOW_GAME_PORT ) { unsigned int ipaddr = ntohl(server->ipaddr); // Update the servers hostname as required sprintf( arg, "%d.%d.%d.%d:%hu", ipaddr>>24, (ipaddr>>16)&0xff, (ipaddr>>8)&0xff, ipaddr&0xff, port ); if ( 0 != strcmp( server->arg, server->host_name )) { // hostname isnt the query arg char *colon = strchr( server->host_name, ':' ); if ( colon ) { // dns hostname or hostname:port char *hostname = malloc( strlen(server->host_name) + 6 ); *colon= '\0'; sprintf( hostname, "%s:%hu", server->host_name, port); free( server->host_name); server->host_name= hostname; } } // Update the server arg free( server->arg ); server->arg = strdup( arg ); // Add a rule noting the previous query port sprintf( arg, "%hu", server->port ); add_rule( server, "_queryport", arg, NO_FLAGS); // Update the servers port server->port = port; } // Add a rule noting the servers hostport sprintf( arg, "%hu", port ); add_rule( server, "hostport", arg, NO_FLAGS); } } STATIC void players_set_teamname( struct qserver *server, int teamid, char *teamname ) { struct player *player; for ( player= server->players; player; player= player->next) { if ( player->team == teamid ) { player->team_name = strdup( teamname ); } } } STATIC char * dup_nstring( const char *pkt, const char *end, char **next) { char *pt = (char*)pkt; int len= ((unsigned char*)pkt)[0]; pt++; if ( *pt == '\1') { len++; } if ( pt + len > end) { return NULL; } *next = pt+len; return strndup( pt, len ); } STATIC char * dup_n1string( char *pkt, char *end, char **next) { unsigned len; if(!pkt || pkt >= end) return NULL; len = (unsigned char)pkt[0]-1; pkt++; if ( pkt + len > end) return NULL; *next= pkt+len; return strndup( pkt, len); } STATIC int pariah_basic_packet( struct qserver *server, char *rawpkt, char *end) { char *next; char *string; change_server_port( server, swap_short_from_little( &rawpkt[14]), 0 ); if ( NULL == ( string = ut2003_strdup( &rawpkt[18], end, &next) ) ) { return -1; } if ( server->server_name == NULL) { server->server_name = string; } else { free(string); } if ( NULL == ( string = ut2003_strdup( next, end, &next) ) ) { return -1; } if ( server->map_name == NULL) { server->map_name = string; } else { free(string); } if ( NULL == ( string= ut2003_strdup( next, end, &next) ) ) { return -1; } if ( server->game == NULL) { server->game = string; add_rule( server, "gametype", server->game, NO_FLAGS | CHECK_DUPLICATE_RULES); } else { free(string); } server->num_players = (unsigned char)next[0]; server->max_players = (unsigned char)next[1]; return 0; } STATIC int ut2003_basic_packet( struct qserver *server, char *rawpkt, char *end) { char *next; char *string; change_server_port( server, swap_short_from_little( &rawpkt[6]), 0 ); if ( NULL == ( string = ut2003_strdup( &rawpkt[14], end, &next) ) ) { return -1; } if ( server->server_name == NULL) { server->server_name = string; } else { free(string); } if ( NULL == ( string = ut2003_strdup( next, end, &next) ) ) { return -1; } if ( server->map_name == NULL) { server->map_name= string; } else { free(string); } if ( NULL == ( string= ut2003_strdup( next, end, &next) ) ) { return -1; } if ( server->game == NULL) { server->game= string; add_rule( server, "gametype", server->game, NO_FLAGS | CHECK_DUPLICATE_RULES); } else { free(string); } server->num_players= swap_long_from_little( next); next+= 4; server->max_players= swap_long_from_little( next); return 0; } STATIC int pariah_rule_packet( struct qserver *server, char *rawpkt, char *end ) { char *key, *value; unsigned char no_rules = (unsigned char)rawpkt[1]; unsigned char seen = 0; // type + no_rules rawpkt+=2; // we get size encoded key = value pairs while ( rawpkt < end && no_rules > seen ) { // first byte is the rule count seen = (unsigned char)rawpkt[0]; rawpkt++; if ( NULL == ( key = ut2003_strdup( rawpkt, end, &rawpkt) ) ) { break; } if ( '\0' == rawpkt[0] ) { value = strdup( "" ); rawpkt++; } else if ( NULL == ( value = ut2003_strdup( rawpkt, end, &rawpkt) ) ) { break; } if ( NULL == add_rule( server, key, value, NO_KEY_COPY | NO_VALUE_COPY | COMBINE_VALUES ) ) { /* duplicate, so free key and value */ free(value); free(key); } seen++; } if ( no_rules == seen ) { // all done server->next_rule = NULL; return 1; } return 0; } STATIC int ut2003_rule_packet( struct qserver *server, char *rawpkt, char *end ) { char *key, *value; int result= 0; // Packet Type rawpkt++; // we get size encoded key = value pairs while ( rawpkt < end ) { if ( NULL == ( key = ut2003_strdup( rawpkt, end, &rawpkt) ) ) { break; } if ( NULL == ( value = ut2003_strdup( rawpkt, end, &rawpkt) ) ) { break; } if ( strcmp( key, "minplayers") == 0) { result = atoi(value); } if ( NULL == add_rule( server, key, value, NO_KEY_COPY | NO_VALUE_COPY | COMBINE_VALUES ) ) { /* duplicate, so free key and value */ free(value); free(key); } } return result; } char * ut2003_strdup( const char *string, const char *end, char **next ) { unsigned char len = string[0]; char *result = NULL; if ( len < 128 ) { // type 1 string //fprintf( stderr, "Type 1:" ); result = dup_nstring( string, end, next ); } else { // type 2 string //fprintf( stderr, "Type 2:\n" ); const char *last; char *resp, *pos; // minus indicator len -= 128; // double byte chars so * 2 len = len * 2; last = string + len; if ( last > end ) { *next = (char*)end; fprintf( stderr, "Type 2 string format error ( too short )\n" ); return NULL; } *next = (char*)last+1; if ( NULL == ( result = (char*)calloc( last - string, sizeof(char) ) ) ) { fprintf( stderr, "Failed to malloc string memory\n" ); return NULL; } resp = result; pos = (char*)string + 1; while ( pos <= last ) { // check for a color code if ( pos + 6 <= last && 0 == memcmp( pos, "^\0#\0", 4 ) ) { // we have a color code //fprintf( stderr, "color:%02hhx%02hhx\n", pos[4], pos[5] ); // indicator transformed to ^\1 *resp = *pos; resp++; pos++; *resp = '\1'; resp++; pos+=3; // color byte *resp = *pos; resp++; pos+=2; //pos += 6; } // standard char //fprintf( stderr, "char: %02hhx\n", *pos ); *resp = *pos; resp++; pos += 2; } } //fprintf( stderr, "'%s'\n", result ); return result; } STATIC int pariah_player_packet( struct qserver *server, char *rawpkt, char *end) { unsigned char no_players = rawpkt[1]; unsigned char seen = 0; /* XXX: cannot work this way, it takes only this packet into consideration. What if player info is spread across multiple packets? */ // type + no_players + some unknown preamble rawpkt += 3; while ( rawpkt < end && seen < no_players ) { struct player *player; // Player Number rawpkt += 4; // Create a player if ( NULL == ( player = add_player( server, server->n_player_info ) ) ) { return 0; } // Name ( min 3 bytes ) player->name = ut2003_strdup( rawpkt, end, &rawpkt ); // Ping player->ping = swap_long_from_little( rawpkt ); rawpkt += 4; // Frags player->frags = (unsigned char)rawpkt[0]; rawpkt++; // unknown rawpkt++; seen++; } if ( no_players == seen ) { // all done server->num_players = server->n_player_info; return 1; } // possibly more to come return 0; } STATIC int ut2003_player_packet( struct qserver *server, char *rawpkt, char *end) { // skip type rawpkt++; switch ( server->protocol_version ) { case 0x7e: // XMP packet //fprintf( stderr, "XMP packet\n" ); while ( rawpkt < end ) { struct player *player; char *var, *val; unsigned char no_props; if ( rawpkt + 24 > end ) { malformed_packet( server, "player info too short" ); rawpkt = end; return 1; } // Player Number never set rawpkt += 4; // Player ID never set rawpkt += 4; if ( NULL == ( player = add_player( server, server->n_player_info ) ) ) { return 0; } // Name ( min 3 bytes ) player->name = ut2003_strdup( rawpkt, end, &rawpkt ); // Ping player->ping = swap_long_from_little( rawpkt ); rawpkt += 4; // Frags player->frags = swap_long_from_little( rawpkt ); rawpkt += 4; // Stat ID never set rawpkt += 4; // Player properties no_props = rawpkt[0]; //fprintf( stderr, "noprops %d\n", no_props ); rawpkt++; while( rawpkt < end && no_props > 0 ) { if ( NULL == ( var = ut2003_strdup( rawpkt, end, &rawpkt ) ) ) { break; } if ( NULL == ( val = ut2003_strdup( rawpkt, end, &rawpkt ) ) ) { break; } //fprintf( stderr, "attrib: %s = %s\n", var, val ); // Things we can use if ( 0 == strcmp( var, "team" ) ) { player->team_name = val; } else if ( 0 == strcmp( var, "class" ) ) { player->skin = val; } else { free( val ); } free( var ); no_props--; } } break; default: while ( rawpkt < end ) { struct player *player; if(rawpkt+4 > end) { malformed_packet(server, "player packet too short"); return 1; } if ( NULL == ( player = add_player( server, swap_long_from_little(rawpkt) ) ) ) { return 0; } player->name = ut2003_strdup( rawpkt+4, end, &rawpkt ); if(rawpkt+8 > end) { malformed_packet(server, "player packet too short"); return 1; } player->ping = swap_long_from_little(rawpkt); rawpkt+= 4; player->frags = swap_long_from_little(rawpkt); rawpkt+= 4; { unsigned team = swap_long_from_little(rawpkt); rawpkt+= 4; player->flags |= PLAYER_FLAG_DO_NOT_FREE_TEAM; if(team & 1<<29) player->team_name = "red"; else if(team & 1<<30) player->team_name = "blue"; } } } return 0; } char * get_rule( struct qserver *server, char *name) { struct rule *rule; rule= server->rules; for ( ; rule != NULL; rule= rule->next) { if ( strcmp( name, rule->name) == 0) return rule->value; } return NULL; } void deal_with_ut2003_packet( struct qserver *server, char *rawpkt, int pktlen) { // For protocol spec see: // http://unreal.student.utwente.nl/UT2003-queryspec.html char *end; int error = 0, before; unsigned int packet_header; rawpkt[pktlen]= '\0'; end = &rawpkt[pktlen]; packet_header = swap_long_from_little( &rawpkt[0] ); rawpkt += 4; server->protocol_version = packet_header; if ( packet_header != 0x77 // Pariah Demo? && packet_header != 0x78 // UT2003 Demo && packet_header != 0x79 // UT2003 Retail && packet_header != 0x7e // Unreal2 XMP && packet_header != 0x7f // UT2004 Demo && packet_header != 0x80 // UT2004 Retail ) { malformed_packet(server, "Unknown type 0x%x", packet_header); } switch( rawpkt[0] ) { case 0x00: // Server info if ( server->server_name == NULL ) { server->ping_total+= time_delta( &packet_recv_time, &server->packet_time1); } error = ut2003_basic_packet( server, rawpkt, end ); if ( ! error ) { if(get_server_rules || get_player_info) { int requests = server->n_requests; server->next_rule = ""; server->retry1 = n_retries; server->retry2 = 0; // don't wait for player packet send_rule_request_packet( server); server->n_requests = requests; // would produce wrong ping } } break; case 0x01: // Game info ut2003_rule_packet( server, rawpkt, end ); server->next_rule = ""; server->retry1 = 0; /* we received at least one rule packet so no need to retry. We'd get double entries otherwise. */ break; case 0x02: // Player info before = server->n_player_info; error = ut2003_player_packet( server, rawpkt, end); if (before == server->n_player_info ) { error = 1; } break; case 0x10: // Pariah Server info if ( server->server_name == NULL ) { server->ping_total+= time_delta( &packet_recv_time, &server->packet_time1); } error = pariah_basic_packet( server, rawpkt, end ); if ( ! error ) { // N.B. pariah always sends a rules and players packet int requests = server->n_requests; server->next_rule = ""; server->retry1 = n_retries; server->retry2 = 0; server->n_requests = requests; // would produce wrong ping } break; case 0x11: // Game info pariah_rule_packet( server, rawpkt, end ); server->retry1 = 0; /* we received at least one rule packet so no need to retry. We'd get double entries otherwise. */ break; case 0x12: // Player info before = server->n_player_info; pariah_player_packet( server, rawpkt, end ); if ( before == server->n_player_info ) { error = 1; } break; default: malformed_packet(server, "Unknown packet type 0x%x", (unsigned)rawpkt[0]); break; } /* don't cleanup if we fetch server rules. We would lose * rule packets as we don't know how many we get * We do clean up if we don't fetch server rules so we don't * need to wait for timeout. */ if (error || (!get_server_rules && !get_player_info) || (!get_server_rules && server->num_players == server->n_player_info) || (server->next_rule == NULL && server->num_players == server->n_player_info)) { cleanup_qserver( server, 1 ); } } int deal_with_unrealmaster_packet( struct qserver *server, char *rawpkt, int pktlen) { if ( pktlen == 0) { cleanup_qserver( server, 1); return 0; } print_packet( server, rawpkt, pktlen); puts( "--"); return 0; } /* Returns 1 if the query is done (server may be freed) and 0 if not. */ int deal_with_halflife_packet( struct qserver *server, char *rawpkt, int pktlen) { char *pkt; char *end= &rawpkt[pktlen]; int pkt_index= 0, pkt_max= 0; char number[16]; short pkt_id; if ( server->server_name == NULL) server->ping_total+= time_delta( &packet_recv_time, &server->packet_time1); if ( pktlen < 5) return cleanup_qserver( server, 1); if ( ((rawpkt[0] != '\377' && rawpkt[0] != '\376') || rawpkt[1] != '\377' || rawpkt[2] != '\377' || rawpkt[3] != '\377') && show_errors) { unsigned int ipaddr= ntohl(server->ipaddr); fprintf( stderr, "Odd packet from server %d.%d.%d.%d:%hu, processing ...\n", (ipaddr>>24)&0xff, (ipaddr>>16)&0xff, (ipaddr>>8)&0xff, ipaddr&0xff, ntohs(server->port)); print_packet( server, rawpkt, pktlen); } if ( ((unsigned char*)rawpkt)[0] == 0xfe) { SavedData *sdata; pkt_index= ((unsigned char*)rawpkt)[8] >> 4; pkt_max= ((unsigned char*)rawpkt)[8] & 0xf; memcpy( &pkt_id, &rawpkt[4], 2); 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-9; sdata->data= (char*) malloc( pktlen-9); memcpy( sdata->data, &rawpkt[9], pktlen-9); /* combine_packets will call us recursively */ return combine_packets( server); /* fprintf( OF, "pkt_index %d pkt_max %d\n", pkt_index, pkt_max); rawpkt+= 9; pktlen-= 9; */ } /* 'info' response */ if ( rawpkt[4] == 'C' || rawpkt[4] == 'm') { if ( server->server_name != NULL) return 0; pkt= &rawpkt[5]; server->address= strdup( pkt); pkt+= strlen(pkt)+1; server->server_name= strdup( pkt); pkt+= strlen(pkt)+1; server->map_name= strdup( pkt); pkt+= strlen(pkt)+1; if ( *pkt) add_rule( server, "gamedir", pkt, NO_FLAGS); if ( *pkt && strcmp( pkt, "valve") != 0) { server->game= add_rule( server, "game", pkt, NO_FLAGS)->value; server->flags |= FLAG_DO_NOT_FREE_GAME; } pkt+= strlen(pkt)+1; if ( *pkt) add_rule( server, "gamename", pkt, NO_FLAGS); pkt+= strlen(pkt)+1; server->num_players= (unsigned int)pkt[0]; server->max_players= (unsigned int)pkt[1]; pkt+= 2; if ( pkt < end) { int protocol= *((unsigned char *)pkt); sprintf( number, "%d", protocol); add_rule( server, "protocol", number, NO_FLAGS); pkt++; } if ( rawpkt[4] == 'm') { if ( *pkt == 'd') add_rule( server, "sv_type", "dedicated", NO_FLAGS); else if ( *pkt == 'l') add_rule( server, "sv_type", "listen", NO_FLAGS); else add_rule( server, "sv_type", "?", NO_FLAGS); pkt++; if ( *pkt == 'w') add_rule( server, "sv_os", "windows", NO_FLAGS); else if ( *pkt == 'l') add_rule( server, "sv_os", "linux", NO_FLAGS); else { char str[2]= "\0"; str[0]= *pkt; add_rule( server, "sv_os", str, NO_FLAGS); } pkt++; add_rule( server, "sv_password", *pkt ? "1" : "0", NO_FLAGS); pkt++; add_rule( server, "mod", *pkt ? "1" : "0", NO_FLAGS); if ( *pkt) { int n; /* pull out the mod infomation */ pkt++; add_rule( server, "mod_info_url", pkt, NO_FLAGS); pkt+= strlen( pkt)+1; if ( *pkt) add_rule( server, "mod_download_url", pkt, NO_FLAGS); pkt+= strlen( pkt)+1; if ( *pkt) add_rule( server, "mod_detail", pkt, NO_FLAGS); pkt+= strlen( pkt)+1; n= swap_long_from_little( pkt); sprintf( number, "%d", n); add_rule( server, "modversion", number, NO_FLAGS); pkt+= 4; n= swap_long_from_little( pkt); sprintf( number, "%d", n); add_rule( server, "modsize", number, NO_FLAGS); pkt+= 4; add_rule( server, "svonly", *pkt ? "1" : "0", NO_FLAGS); pkt++; add_rule( server, "cldll", *pkt ? "1" : "0", NO_FLAGS); pkt++; if ( pkt < end) add_rule( server, "secure", *pkt ? "1" : "0", NO_FLAGS); } } if ( get_player_info && server->num_players) { int requests = server->n_requests; server->next_player_info= server->num_players-1; send_player_request_packet( server); server->n_requests = requests; // prevent wrong ping } if ( get_server_rules) { int requests = server->n_requests; server->next_rule= ""; server->retry1= n_retries; send_rule_request_packet( server); server->n_requests = requests; // prevent wrong ping } } /* 'players' response */ else if ( rawpkt[4] == 'D' && server->players == NULL) { unsigned int n= 0, temp; struct player *player; struct player **last_player= & server->players; if ( (unsigned int)rawpkt[5] > server->num_players) server->num_players= (unsigned int)rawpkt[5]; pkt= &rawpkt[6]; rawpkt[pktlen]= '\0'; while (1) { if ( *pkt != n+1) break; n++; pkt++; player= (struct player*) calloc( 1, sizeof(struct player)); player->name= strdup( pkt); pkt+= strlen(pkt)+1; memcpy( &player->frags, pkt, 4); pkt+= 4; memcpy( &temp, pkt, 4); pkt+= 4; if ( big_endian) { player->frags= swap_long( &player->frags); } player->connect_time= swap_float_from_little(&temp); *last_player= player; last_player= & player->next; } if ( n > server->num_players) server->num_players= n; server->next_player_info= server->num_players; } /* 'rules' response */ else if ( rawpkt[4] == 'E' && server->next_rule != NULL) { int n= 0; n= ((unsigned char*)rawpkt)[5] + ((unsigned char *)rawpkt)[6]*256; pkt= &rawpkt[7]; while ( n) { char *key= pkt; char *value; pkt+= strlen(pkt)+1; if ( pkt > end) break; value= pkt; pkt+= strlen(pkt)+1; if ( pkt > end) break; if ( key[0] == 's' && strcmp( key, "sv_password") == 0) add_rule( server, key, value, CHECK_DUPLICATE_RULES); else add_rule( server, key, value, NO_FLAGS); n--; } server->next_rule= NULL; } else if ( rawpkt[4] != 'E' && rawpkt[4] != 'D' && rawpkt[4] != 'm' && rawpkt[4] != 'C' && show_errors) { /* if ( pkt_count) { rawpkt-= 9; pktlen+= 9; } */ fprintf( stderr, "Odd packet from HL server %s (packet len %d)\n", server->arg, pktlen); print_packet( server, rawpkt, pktlen); } return cleanup_qserver( server, 0); } static int tribes_debug= 0; void 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]; 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)) { cleanup_qserver( server, 1); return; } if ( strncmp( rawpkt, tribes_players_reponse, sizeof( tribes_players_reponse)) != 0) { cleanup_qserver( server, 1); return; } 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: ) */ if ( tribes_debug) printf( "%.*s\n", len, pkt+1); pkt+= len+1; n_teams= *pkt++; /* number of teams */ if ( n_teams == 255) { cleanup_qserver( server, 1); return; } sprintf( buf, "%d", n_teams); add_rule( server, "numteams", buf, NO_FLAGS); len= *pkt; /* first title */ if ( tribes_debug) printf( "%.*s\n", len, pkt+1); pkt+= len+1; len= *pkt; /* second title */ if ( tribes_debug) printf( "%.*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); if ( tribes_debug) printf( "team#0 <%.*s>\n", len, pkt+1); pkt+= len+1; len= *pkt; /* team score */ if ( len <= 2 && tribes_debug) printf( "%s score len %d\n", server->arg, len); if ( len > 2) { strncpy( buf, (char*)pkt+1+3, len-3); buf[len-3]= '\0'; } else buf[0]= '\0'; teams[t]->frags= atoi( buf); if ( tribes_debug) printf( "team#0 <%.*s>\n", len-3, pkt+1+3); pkt+= len+1; } } else { len= *pkt; /* DM team? */ if ( tribes_debug) printf( "%.*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++; if ( tribes_debug) printf( "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); if ( tribes_debug) printf( "player#%d, name %.*s\n", pnum, len, pkt+1); pkt+= len+1; len= *pkt; if ( tribes_debug) printf( "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); if ( tribes_debug) printf( "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); cleanup_qserver( server, 0); } 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; } } void 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; 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) { cleanup_qserver( server, 1); return; } 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"); cleanup_qserver( server, 1); return; } 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; } else if ( pkt[0] != TRIBES2_RESPONSE_INFO) { cleanup_qserver( server, 1); return; } pkt+= 6; for ( i= 0; i < *(unsigned char *)pkt; i++) if ( !isprint(pkt[i+1])) { cleanup_qserver( server, 1); return; } 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); cleanup_qserver( server, 1); return; } 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 void 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; 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"); cleanup_qserver( server, 1); return; }; pkt += sizeof(GrPacketHead); StartFlag=pkt[0]; pkt += 1; if (StartFlag != 0x42) { server->server_name= strdup( "Unknown Start Flag"); cleanup_qserver( server, 1); return; }; /* 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"); cleanup_qserver( server, 1); return; }; /* 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"); cleanup_qserver( server, 1); return; }; 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"); cleanup_qserver(server, 1); return; }; 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); cleanup_qserver(server, 1); return; }; 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); cleanup_qserver(server, 1); return; }; /* 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); cleanup_qserver(server, 1); return; }; 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); cleanup_qserver(server, 1); return; }; 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); cleanup_qserver(server, 1); return; }; 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;inum_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); cleanup_qserver(server, 1); return; }; 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); cleanup_qserver(server, 1); return; }; 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); cleanup_qserver(server, 1); return; }; 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); cleanup_qserver(server, 1); return; }; 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;inum_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); cleanup_qserver(server, 1); return; }; add_rule( server, "restrict", pkt, NO_FLAGS); pkt += iLen; /* restrictions */ pkt += 23; /* if ( ghostrecon_debug) print_packet( pkt, GrPayloadLen); */ cleanup_qserver( server, 1); return; } 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; } } char* find_savage_game( char *gametype ) { if ( 0 == strcmp( "RTSS", gametype ) ) { return strdup( "RTSS" ); } else { return strdup( "Unknown" ); } } void deal_with_ravenshield_packet( struct qserver *server, char *rawpkt, int pktlen) { char *s, *key, *value; 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 ); } } cleanup_qserver( server, 1 ); return; } void deal_with_savage_packet( struct qserver *server, char *rawpkt, int pktlen) { char *s, *key, *value, *end; 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'; } cleanup_qserver( server, 1 ); return; } void deal_with_farcry_packet( struct qserver *server, char *rawpkt, int pktlen) { char *s, *key, *value, *end; 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'; } cleanup_qserver( server, 1 ); return; } /* postions of map name, player name (in player substring), zero-based */ #define BFRIS_MAP_POS 18 #define BFRIS_PNAME_POS 11 void 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; 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; /* see if map name is complete */ player_data_pos=0; for (i=BFRIS_MAP_POS; imap_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; /* 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; 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; iship = 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; cleanup_qserver(server, 1); return; } struct rule * add_uchar_rule( struct qserver *server, char *key, unsigned char value) { char buf[24]; sprintf( buf, "%u", (unsigned)value); return add_rule( server, key, buf, NO_FLAGS); } void deal_with_descent3_packet( struct qserver *server, char *rawpkt, int pktlen) { char *pkt; char buf[24]; 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); cleanup_qserver( server, 1); return; } /* 'info' response */ if ( rawpkt[1] == 0x1f) { if ( server->server_name != NULL) { cleanup_qserver( server, 1); return; } 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", !(pkt[4]&1)); /* randomize powerup spawn */ add_uchar_rule( server, "acccollisions", (pkt[5]&4) > 0); /* accurate collision detection */ add_uchar_rule( server, "brightships", (pkt[5]&16) > 0); /* bright player ships */ add_uchar_rule( server, "mouselook", (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", pkt[4] & ~(1+16)); add_uchar_rule( server, "x5", pkt[5] & ~(4+16)); add_uchar_rule( server, "x6", pkt[6] & ~1); if ( get_player_info && server->num_players) { server->next_player_info= 0; send_player_request_packet( server); cleanup_qserver( server, 0); return; } } /* MP_PLAYERLIST_DATA */ else if ( rawpkt[1] == 0x73) { struct player *player; struct player **last_player= & server->players; if ( server->players != NULL) { cleanup_qserver( server, 1); return; } 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); } cleanup_qserver( server, 1); return; } #define EYE_NAME_MASK 1 #define EYE_TEAM_MASK 2 #define EYE_SKIN_MASK 4 #define EYE_SCORE_MASK 8 #define EYE_PING_MASK 16 #define EYE_TIME_MASK 32 void deal_with_eye_packet( struct qserver *server, char *rawpkt, int pktlen) { char *next, *end, *value, *key; struct player **last_player; unsigned char pkt_index, pkt_max; unsigned int pkt_id; if ( pktlen < 4) { cleanup_qserver( server, 1); return; } if ( rawpkt[0] != 'E' || rawpkt[1] != 'Y' || rawpkt[2] != 'E') { cleanup_qserver( server, 1); return; } server->ping_total += time_delta( &packet_recv_time, &server->packet_time1); end= rawpkt + pktlen; pkt_index= rawpkt[3] - '0'; if ( pktlen == 1364 || pkt_index != 1) { /* fragmented packet */ SavedData *sdata; /* EYE doesn't tell us how many packets to expect. Two packets * is enough for 100+ players on a BF1942 server with standard * server rules. */ pkt_max= 2; memcpy( &pkt_id, &rawpkt[pktlen-4], 4); 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-1; sdata->pkt_max= pkt_max; sdata->pkt_id= pkt_id; if ( pkt_index == 1) { sdata->datalen= pktlen-4; } else { sdata->datalen= pktlen-8; } sdata->data= (char*) malloc( sdata->datalen); if ( NULL == sdata->data ) { fprintf( stderr, "Out of memory\n" ); cleanup_qserver( server, 1 ); return; } if ( pkt_index == 1) { memcpy( sdata->data, &rawpkt[0], sdata->datalen); } else { memcpy( sdata->data, &rawpkt[4], sdata->datalen); } /* combine_packets will call us recursively */ combine_packets( server); return; } value= dup_n1string( &rawpkt[4], end, &next); if ( value == NULL) { goto eye_protocol_error; } add_rule( server, "gamename", value, NO_VALUE_COPY); value= dup_n1string( next, end, &next); if ( value == NULL) { goto eye_protocol_error; } add_rule( server, "hostport", value, NO_VALUE_COPY); value= dup_n1string( next, end, &next); if ( value == NULL) { goto eye_protocol_error; } server->server_name= value; value= dup_n1string( next, end, &next); if ( value == NULL) { goto eye_protocol_error; } server->game= value; add_rule( server, server->type->game_rule, value, NO_FLAGS); value= dup_n1string( next, end, &next); if ( value == NULL) { goto eye_protocol_error; } server->map_name= value; value= dup_n1string( next, end, &next); if ( value == NULL) { goto eye_protocol_error; } add_rule( server, "_version", value, NO_VALUE_COPY); value= dup_n1string( next, end, &next); if ( value == NULL) { goto eye_protocol_error; } add_rule( server, "_password", value, NO_VALUE_COPY); value= dup_n1string( next, end, &next); if ( value == NULL) { goto eye_protocol_error; } server->num_players= atoi(value); free(value); value= dup_n1string( next, end, &next); if ( value == NULL) { goto eye_protocol_error; } server->max_players= atoi(value); free(value); /* rule1,value1,rule2,value2, ... empty string */ do { key= dup_n1string( next, end, &next); if ( key == NULL) { break; } else if ( key[0] == '\0') { free(key); break; } value= dup_n1string( next, end, &next); if ( value == NULL) { free(key); break; } add_rule( server, key, value, NO_VALUE_COPY | NO_KEY_COPY); } while ( 1); /* [mask1][mask2]... */ last_player= & server->players; while ( next && next < end ) { struct player *player; unsigned mask= *((unsigned char*)next); next++; if ( next >= end) { break; } if ( mask == 0) { break; } player= (struct player*) calloc( 1, sizeof(struct player)); if ( player == NULL) { break; } if ( mask & EYE_NAME_MASK) { player->name= dup_n1string( next, end, &next); //fprintf( stderr, "Player '%s'\n", player->name ); if ( player->name == NULL) { break; } } if ( mask & EYE_TEAM_MASK) { value= dup_n1string( next, end, &next); if ( value == NULL) { break; } if ( isdigit((unsigned char)value[0])) { player->team= atoi(value); free(value); } else { player->team_name= value; } } if ( mask & EYE_SKIN_MASK) { player->skin= dup_n1string( next, end, &next); if ( player->skin == NULL) { break; } } if ( mask & EYE_SCORE_MASK) { value= dup_n1string( next, end, &next); if ( value == NULL) { break; } player->score= atoi(value); player->frags= player->score; free(value); } if ( mask & EYE_PING_MASK) { value= dup_n1string( next, end, &next); if ( value == NULL) { break; } player->ping= atoi(value); free(value); } if ( mask & EYE_TIME_MASK) { value= dup_n1string( next, end, &next); if ( value == NULL) { break; } player->connect_time= atoi(value); free(value); } *last_player= player; last_player= & player->next; //fprintf( stderr, "Player '%s'\n", player->name ); } cleanup_qserver( server, 1); return; eye_protocol_error: cleanup_qserver( server, 1); } static const char hl2_statusresponse[] = "\xFF\xFF\xFF\xFF\x49"; static const char hl2_playersresponse[] = "\xFF\xFF\xFF\xFF\x44"; static const char hl2_rulesresponse[] = "\xFF\xFF\xFF\xFF\x45"; static const int hl2_response_size = sizeof(hl2_statusresponse) - 1; #define HL2_STATUS 1 #define HL2_PLAYERS 2 #define HL2_RULES 3 void deal_with_hl2_packet( struct qserver *server, char *rawpkt, int pktlen) { char *ptr = rawpkt; char *end = rawpkt + pktlen; char temp[512]; int type = 0; unsigned char protocolver = 0; int n_sent = 0; 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); } // Check if correct reply if ( pktlen < hl2_response_size ) { malformed_packet(server, "short response type"); cleanup_qserver( server, 1); return; } else { if ( 0 == memcmp( hl2_statusresponse, ptr, hl2_response_size ) ) { if ( pktlen < hl2_response_size + 20 ) { malformed_packet(server, "short packet"); cleanup_qserver( server, 1); return; } type = HL2_STATUS; } else if ( 0 == memcmp( hl2_playersresponse, ptr, hl2_response_size ) ) { type = HL2_PLAYERS; } else if ( 0 == memcmp( hl2_rulesresponse, ptr, hl2_response_size ) ) { type = HL2_RULES; } else { malformed_packet(server, "unknown response"); cleanup_qserver( server, 1); return; } } // header ptr += hl2_response_size; switch( type ) { case HL2_STATUS: // protocol version protocolver = *ptr; ptr++; debug(2, "protocol: 0x%02X", protocolver ); // Commented out till out of beta /* if( '\x02' != protocolver ) { malformed_packet(server, "protocol version != 0x02"); cleanup_qserver( server, 1); return; } */ server->protocol_version = protocolver; sprintf( temp, "%d", protocolver ); add_rule( server, "protocol", temp, NO_FLAGS); // server name server->server_name = strdup( ptr ); ptr += strlen( ptr ) + 1; // map server->map_name = strdup( ptr ); ptr += strlen( ptr ) + 1; // gamedir server->game = strdup( ptr ); add_rule( server, "gamedir", ptr, NO_FLAGS ); ptr += strlen( ptr ) + 1; // description add_rule( server, "description", ptr, NO_FLAGS ); ptr += strlen( ptr ) + 1; // appid ptr += 2; // num players server->num_players = *ptr; ptr++; // max players server->max_players = *ptr; ptr++; // bot players sprintf( temp, "%hhu", (*ptr) ); add_rule( server, "bot_players", temp, NO_FLAGS ); ptr++; // dedicated if ( 'd' == *ptr ) { add_rule( server, "sv_type", "dedicated", NO_FLAGS ); } else if ( 'l' == *ptr ) { add_rule( server, "sv_type", "listen", NO_FLAGS ); } else { char tmp[2] = { *ptr, '\0' }; add_rule( server, "sv_type", tmp, NO_FLAGS ); } ptr++; // OS if ( 'l' == *ptr ) { add_rule( server, "sv_os", "linux", NO_FLAGS ); } else if ( 'w' == *ptr ) { add_rule( server, "sv_os", "windows", NO_FLAGS ); } else { char tmp[2] = { *ptr, '\0' }; add_rule( server, "sv_os", tmp, NO_FLAGS ); } ptr++; // passworded add_rule( server, "sv_password", *ptr ? "1" : "0", NO_FLAGS); ptr++; // secure add_rule( server, "secure", *ptr ? "1" : "0", NO_FLAGS); ptr++; // send the other request packets if wanted if ( get_server_rules ) { int requests = server->n_requests; send_rule_request_packet( server ); server->n_requests = requests; // prevent wrong ping n_sent++; } else if ( get_player_info ) { int requests = server->n_requests; send_player_request_packet( server ) ; server->n_requests = requests; // prevent wrong ping n_sent++; } break; case HL2_RULES: // num_players ptr++; // max_players ptr++; while ( ptr < end ) { char *var = ptr; char *val; ptr += strlen( var ) + 1; val = ptr; ptr += strlen( val ) + 1; add_rule( server, var, val, NO_FLAGS ); } if ( get_player_info ) { send_player_request_packet( server ) ; n_sent++; } break; case HL2_PLAYERS: // num_players ptr++; while ( ptr < end ) { struct player *player = add_player( server, server->n_player_info ); // player no ptr++; // name player->name = strdup( ptr ); ptr += strlen( ptr ) + 1; // frags player->frags = swap_long_from_little( ptr ); ptr += 4; // time player->connect_time = swap_float_from_little( ptr ); ptr += 4; } break; default: malformed_packet( server, "unknown response" ); cleanup_qserver( server, 1); return; } if ( 0 == n_sent ) { cleanup_qserver( server, 1 ); } return; } void deal_with_gamespy_master_response( struct qserver *server, char *rawpkt, int pktlen) { if ( pktlen == 0) { int len= server->saved_data.datalen; char *data= server->saved_data.data; char *ip, *portstr; unsigned int ipaddr; unsigned short port; int master_pkt_max; server->server_name= GAMESPY_MASTER_NAME; master_pkt_max= (len / 20) * 6; server->master_pkt= (char*) malloc( master_pkt_max); server->master_pkt_len= 0; while ( len) { for ( ; len && *data == '\\'; data++, len--) ; if ( len < 3) break; if ( data[0] == 'i' && data[1] == 'p' && data[2] == '\\') { data+= 3; len-= 3; ip= data; portstr= NULL; for ( ; len && *data != '\\'; data++, len--) { if ( *data == ':') { portstr= data+1; *data= '\0'; } } if ( len == 0) break; *data++= '\0'; len--; ipaddr= inet_addr( ip); if ( portstr) port= htons( (unsigned short)atoi( portstr)); else port= htons( 28000); /* ## default port */ if ( server->master_pkt_len >= master_pkt_max) { master_pkt_max+= 20*6; server->master_pkt= (char*) realloc( server->master_pkt, master_pkt_max); } memcpy( server->master_pkt + server->master_pkt_len, &ipaddr, 4); memcpy( server->master_pkt + server->master_pkt_len + 4, &port, 2); server->master_pkt_len+= 6; } else for ( ; len && *data != '\\'; data++, len--) ; } server->n_servers= server->master_pkt_len / 6; server->next_player_info= -1; server->retry1= 0; cleanup_qserver( server, 1); return; } if (! server->saved_data.data) server->saved_data.data= (char*)malloc( pktlen); else server->saved_data.data= (char*)realloc( server->saved_data.data, server->saved_data.datalen + pktlen); memcpy( server->saved_data.data + server->saved_data.datalen, rawpkt, pktlen); server->saved_data.datalen+= pktlen; } /* Misc utility functions */ char * strndup( const char *string, size_t len) { char *result; result= (char*) malloc( len+1); memcpy( result, string, len); result[len]= '\0'; return result; } unsigned int swap_long( void *l) { unsigned char *b= (unsigned char *) l; return b[0] + (b[1]<<8) + (b[2]<<16) + (b[3]<<24); } unsigned short swap_short( void *l) { unsigned char *b= (unsigned char *) l; return (unsigned short)b[0] | (b[1]<<8); } unsigned int swap_long_from_little( void *l) { unsigned char *b= (unsigned char *) l; unsigned int result; if ( little_endian) memcpy( &result, l, 4); else result= (unsigned int)b[0] + (b[1]<<8) + (b[2]<<16) + (b[3]<<24); return result; } float swap_float_from_little( void *f ) { union { int i; float fl; } temp; temp.i = swap_long_from_little(f); return temp.fl; }; unsigned short swap_short_from_little( void *l) { unsigned char *b= (unsigned char *) l; unsigned short result; if ( little_endian) memcpy( &result, l, 2); else result= (unsigned short)b[0] | ((unsigned short)b[1]<<8); return result; } /** write four byte to buf */ void put_long_little(unsigned val, char* buf) { buf[0] = val & 0xFF; buf[1] = (val >> 8) & 0xFF; buf[2] = (val >> 16) & 0xFF; buf[3] = (val >> 24) & 0xFF; } #define MAXSTRLEN 2048 char * xml_escape( char *string) { static char _buf[4][MAXSTRLEN+8]; static int _buf_index= 0; char *result, *b, *end; unsigned char c; if ( string == NULL) return ""; result= &_buf[_buf_index][0]; _buf_index= (_buf_index+1) % 4; end= &result[MAXSTRLEN]; b= result; for ( ; *string && b < end; string++) { c= *string; switch ( c) { case '&': *b++= '&'; *b++= 'a'; *b++= 'm'; *b++= 'p'; *b++= ';'; continue; case '<': *b++= '&'; *b++= 'l'; *b++= 't'; *b++= ';'; continue; case '>': *b++= '&'; *b++= 'g'; *b++= 't'; *b++= ';'; continue; default: break; } if ( ! name_xforms) { *b++= c; } else if ( xml_encoding == ENCODING_LATIN_1) { if ( isprint(c)) *b++= c; else b+= sprintf( b, "&#%u;", c); } else if ( xml_encoding == ENCODING_UTF_8) { if ( (c & 0x80) == 0) *b++= c; else { *b++= 0xC0 | (0x03 & (c >> 6)) ; *b++= 0x80 | (0x3F & c) ; } } } *b= '\0'; return result; } static char *quake3_escape_colors[8] = { "black", "red", "green", "yellow", "blue", "cyan", "magenta", "white" }; static char *sof_colors[32] = { "FFFFFF","FFFFFF","FF0000","00FF00","FFFF00","0000FF","FF00FF","00FFFF", "000000","7F7F7F","702D07","7F0000","007F00","FFFFFF","007F7F","00007F", "564D28","4C5E36","370B65","005572","54647E","1E2A63","66097B","705E61", "980053","960018","702D07","54492A","61A997","CB8F39","CF8316","FF8020" }; char * xform_name( char *string, struct qserver *server) { static char _buf1[2048], _buf2[2048]; static char *_q= &_buf1[0]; unsigned char *s= (unsigned char*) string; char *q; int is_server_name = (string == server->server_name); int font_tag= 0; _q= _q == _buf1 ? _buf2 : _buf1; q= _q; if ( s == NULL) { q[0]= '?'; q[1]= '\0'; return _q; } if ( ! name_xforms) { strcpy( _q, string); return _q; } if ( (hex_player_names && !is_server_name) || (hex_server_names && is_server_name)) { for ( ; *s; s++, q+= 2) { sprintf( q, "%02x", *s); } *q= '\0'; return _q; } if ( server->type->flags & TF_QUAKE3_NAMES) { for ( ; *s; s++) { if ( *s == '^' && *(s+1) != '^') { if ( *(s+1) == '\0') { break; } if ( html_names == 1) { q+= sprintf( q, "%s", font_tag?"":"", quake3_escape_colors[ *(s+1) & 0x7]); s++; font_tag= 1; } else if ( strip_carets) { s++; } else { *q++= *s; } } else { int inc = html_entity( *s, q ); if ( 0 != inc ) { q += inc; } else if ( isprint(*s)) { *q++= *s; } else if ( *s == '\033') { } else if ( *s == 0x80) { *q++= '('; } else if ( *s == 0x81) { *q++= '='; } else if ( *s == 0x82) { *q++= ')'; } else if ( *s == 0x10 || *s == 0x90) { *q++= '['; } else if ( *s == 0x11 || *s == 0x91) { *q++= ']'; } else if ( *s >= 0x92 && *s <= 0x9a) { *q++= *s - 98; } else if ( *s >= 0xa0 && *s <= 0xe0) { *q++= *s - 128; } else if ( *s >= 0xe1 && *s <= 0xfa) { *q++= *s - 160; } else if ( *s >= 0xfb && *s <= 0xfe) { *q++= *s - 128; } } } *q= '\0'; } else if ( !is_server_name && (server->type->flags & TF_TRIBES2_NAMES)) { for ( ; *s; s++) { int inc = html_entity( *s, q ); if ( 0 != inc ) { q += inc; continue; } else if ( isprint(*s)) { *q++= *s; continue; } if ( html_names == 1 && s[1] != '\0') { char *font_color; switch( *s) { case 0x8: font_color= "white"; break; /* normal */ case 0xb: font_color= "yellow"; break; /* tribe tag */ case 0xc: font_color= "blue"; break; /* alias */ case 0xe: font_color= "green"; break; /* bot */ default: font_color= NULL; } if ( font_color) { q+= sprintf( q, "%s", font_tag?"":"", font_color); font_tag= 1; } } } *q= '\0'; } else if ( server->type->flags & TF_U2_NAMES ) { for ( ; *s; s++) { if ( 0 == memcmp( s, "^\1", 2 ) ) { // xmp Color follows s += 2; q += u2xmp_html_color( (unsigned char)*s, q, &font_tag ); } else if ( 0 == memcmp( s, "\x1b", 1 ) ) { // Color follows s += 1; q += ut2k4_html_color( s, q, &font_tag ); s += 2; } else { int inc = html_entity( *s, q ); if ( 0 != inc ) { q += inc; } else if ( isprint(*s)) { *q++= *s; } else if ( 0xa0 == *s ) { *q++= ' '; } } } *q = '\0'; } else if ( server->type->flags & TF_TM_NAMES ) { int open = 0; for ( ; *s; s++) { if ( *s == '$' ) { s++; switch ( *s ) { case 'i': case 'I': // italic if ( 1 == html_names ) { strcat( q, "" ); q += 32; open++; } break; case 's': case 'S': // shadowed break; case 'w': case 'W': // wide break; case 'n': case 'N': // narrow break; case 'm': case 'M': // normal if ( 1 == html_names ) { strcat( q, "" ); q += 32; open++; } break; case 'g': case 'G': // default color strcat( q, "" ); q += 26; open++; break; case 'z': case 'Z': // reset all while ( open ) { strcat( q, "" ); q += 7; open--; } open = 0; break; case 't': case 'T': // capitalise if ( 1 == html_names ) { strcat( q, "" ); q += 40; open++; } break; case '$': // literal $ *q++ = '$'; break; default: // color if ( 1 == html_names ) { sprintf( q, "", *s, *s, *(s+1), *(s+1), *(s+2), *(s+2) ); q += 27; open++; } if ( *(s+1) ) { s++; } if ( *(s+1) ) { s++; } break; } } else { *q++ = *s; } } while ( open ) { strcat( q, "" ); q += 7; open--; } *q = '\0'; } else if ( !is_server_name || server->type->flags & TF_SOF_NAMES ) { // Catch all for NOT is_server_name OR TF_SOF_NAMES // The may not be the intention but is needed for q1 at least for ( ; *s; s++) { int inc = html_entity( *s, q ); if ( 0 != inc ) { q += inc; continue; } if ( *s < ' ' ) { if ( html_names == 1) { q+= sprintf( q, "%s", font_tag?"":"", sof_colors[ *(s) ]); font_tag= 1; } } else if ( isprint(*s)) { *q++= *s; } // ## more fixes below; double check against real sof servers else if ( *s >= 0xa0) { *q++= *s & 0x7f; } else if ( *s >= 0x92 && *s < 0x9c) { *q++= '0' + (*s - 0x92); } else if ( *s >= 0x12 && *s < 0x1c) { *q++= '0' + (*s - 0x12); } else if ( *s == 0x90 || *s == 0x10) { *q++= '['; } else if ( *s == 0x91 || *s == 0x11) { *q++= ']'; } else if ( *s == 0xa || *s == 0xc || *s == 0xd) { *q++= ']'; } } *q= '\0'; } else { strcpy( _q, string); } if ( font_tag ) { q+= sprintf( q, ""); } return _q; } int u2xmp_html_color( short color, char *dest, int *font_tag ) { if ( 1 == html_names ) { int len = sprintf( dest, "%s", *font_tag ? "" : "", unreal_rgb_colors[ color - 1 ] ); *font_tag = 1; return len; } return 0; } int ut2k4_html_color( const unsigned char *color, char *dest, int *font_tag ) { if ( 1 == html_names ) { int len = sprintf( dest, "%s", *font_tag ? "" : "", color[0], color[1], color[2] ); *font_tag = 1; return len; } return 0; } int is_default_rule( struct rule *rule) { if ( strcmp( rule->name, "sv_maxspeed") == 0) return strcmp( rule->value, Q_DEFAULT_SV_MAXSPEED) == 0; if ( strcmp( rule->name, "sv_friction") == 0) return strcmp( rule->value, Q_DEFAULT_SV_FRICTION) == 0; if ( strcmp( rule->name, "sv_gravity") == 0) return strcmp( rule->value, Q_DEFAULT_SV_GRAVITY) == 0; if ( strcmp( rule->name, "noexit") == 0) return strcmp( rule->value, Q_DEFAULT_NOEXIT) == 0; if ( strcmp( rule->name, "teamplay") == 0) return strcmp( rule->value, Q_DEFAULT_TEAMPLAY) == 0; if ( strcmp( rule->name, "timelimit") == 0) return strcmp( rule->value, Q_DEFAULT_TIMELIMIT) == 0; if ( strcmp( rule->name, "fraglimit") == 0) return strcmp( rule->value, Q_DEFAULT_FRAGLIMIT) == 0; return 0; } int html_entity( const char c, char *dest ) { if ( html_mode ) { switch( c ) { case '<': strcpy( dest, "<" ); return 4; case '>': strcpy( dest, ">" ); return 4; case '&': strcpy( dest, "&" ); return 5; default: break; } } return 0; } char * strherror( int h_err) { static char msg[100]; switch (h_err) { case HOST_NOT_FOUND: return "host not found"; case TRY_AGAIN: return "try again"; case NO_RECOVERY: return "no recovery"; case NO_ADDRESS: return "no address"; default: sprintf( msg, "%d", h_err); return msg; } } int time_delta( struct timeval *later, struct timeval *past) { if ( later->tv_usec < past->tv_usec) { later->tv_sec--; later->tv_usec+= 1000000; } return (later->tv_sec - past->tv_sec) * 1000 + (later->tv_usec - past->tv_usec) / 1000; } int connection_refused() { #ifdef _WIN32 return WSAGetLastError() == WSAECONNABORTED; #else return errno == ECONNREFUSED; #endif } void set_non_blocking( int fd) { #ifdef _WIN32 int one= 1; ioctlsocket( fd, FIONBIO, (unsigned long*)&one); #else #ifdef O_NONBLOCK fcntl( fd, F_SETFL, O_NONBLOCK); #else fcntl( fd, F_SETFL, O_NDELAY); #endif // O_NONBLOCK #endif // _WIN32 } char * quake_color( int color) { static char *colors[] = { "White", /* 0 */ "Brown", /* 1 */ "Lavender", /* 2 */ "Khaki", /* 3 */ "Red", /* 4 */ "Lt Brown", /* 5 */ "Peach", /* 6 */ "Lt Peach", /* 7 */ "Purple", /* 8 */ "Dk Purple", /* 9 */ "Tan", /* 10 */ "Green", /* 11 */ "Yellow", /* 12 */ "Blue", /* 13 */ "Blue", /* 14 */ "Blue" /* 15 */ }; static char *rgb_colors[] = { "#ffffff", /* 0 */ "#8b4513", /* 1 */ "#e6e6fa", /* 2 */ "#f0e68c", /* 3 */ "#ff0000", /* 4 */ "#deb887", /* 5 */ "#eecbad", /* 6 */ "#ffdab9", /* 7 */ "#9370db", /* 8 */ "#5d478b", /* 9 */ "#d2b48c", /* 10 */ "#00ff00", /* 11 */ "#ffff00", /* 12 */ "#0000ff", /* 13 */ "#0000ff", /* 14 */ "#0000ff" /* 15 */ }; static char *color_nr[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15" }; if ( color_names ) { if ( color_names == 1) { return colors[color&0xf]; } else { return rgb_colors[color&0xf]; } } else { return color_nr[color&0xf]; } } const char * unreal_color( int color ) { if ( color_names ) { if ( color_names == 1) { return unreal_colors[color&0xf]; } else { return unreal_rgb_colors[color&0xf]; } } return (char*)(unsigned long)color; } #define FMT_HOUR_1 "%2dh" #define FMT_HOUR_2 "%dh" #define FMT_MINUTE_1 "%2dm" #define FMT_MINUTE_2 "%dm" #define FMT_SECOND_1 "%2ds" #define FMT_SECOND_2 "%ds" char * play_time( int seconds, int show_seconds) { static char time_string[24]; if ( time_format == CLOCK_TIME) { char *fmt_hour= show_seconds==2 ? FMT_HOUR_2 : FMT_HOUR_1; char *fmt_minute= show_seconds==2 ? FMT_MINUTE_2 : FMT_MINUTE_1; char *fmt_second= show_seconds==2 ? FMT_SECOND_2 : FMT_SECOND_1; time_string[0]= '\0'; if ( seconds/3600) sprintf( time_string, fmt_hour, seconds/3600); else if ( show_seconds < 2) strcat( time_string, " "); if ( (seconds%3600)/60 || seconds/3600) sprintf( time_string+strlen(time_string), fmt_minute, (seconds%3600)/60); else if ( ! show_seconds) sprintf( time_string+strlen(time_string), " 0m"); else if ( show_seconds < 2) strcat( time_string, " "); if ( show_seconds) sprintf( time_string+strlen(time_string), fmt_second, seconds%60); } else if ( time_format == STOPWATCH_TIME) { if ( show_seconds) sprintf( time_string, "%02d:%02d:%02d", seconds/3600, (seconds%3600)/60, seconds % 60); else sprintf( time_string, "%02d:%02d", seconds/3600, (seconds%3600)/60); } else sprintf( time_string, "%d", seconds); return time_string; } char * ping_time( int ms) { static char time_string[24]; if ( ms < 1000) sprintf( time_string, "%dms", ms); else if ( ms < 1000000) sprintf( time_string, "%ds", ms/1000); else sprintf( time_string, "%dm", ms/1000/60); return time_string; } int count_bits( int n) { int b= 0; for ( ; n; n>>=1) if ( n&1) b++; return b; } int strcmp_withnull( char *one, char *two) { if ( one == NULL && two == NULL) return 0; if ( one != NULL && two == NULL) return -1; if ( one == NULL) return 1; return strcasecmp( one, two); } /* * Sorting functions */ void sort_servers( struct qserver **array, int size) { quicksort( (void**)array, 0, size-1, (int (*)(void*,void*)) server_compare); } void sort_players( struct qserver *server) { struct player **array, *player, *last_team= NULL, **next; int np, i; if ( server->num_players == 0 || server->players == NULL) return; player= server->players; for ( ; player != NULL && player->number == TRIBES_TEAM; ) { last_team= player; player= player->next; } if ( player == NULL) return; array= (struct player **) malloc( sizeof(struct player *) * server->num_players); for ( np= 0; player != NULL && np < server->num_players; np++) { array[np]= player; player= player->next; } quicksort( (void**)array, 0, np-1, (int (*)(void*,void*)) player_compare); if ( last_team) next= &last_team->next; else next= &server->players; for ( i= 0; i < np; i++) { *next= array[i]; array[i]->next= NULL; next= &array[i]->next; } free( array); } int server_compare( struct qserver *one, struct qserver *two) { int rc; char *key= sort_keys; for ( ; *key; key++) { switch( *key) { case 'g': rc= strcmp_withnull( one->game, two->game); if ( rc) return rc; break; case 'p': if ( one->n_requests == 0) return two->n_requests; else if ( two->n_requests == 0) return -1; rc= one->ping_total/one->n_requests - two->ping_total/two->n_requests; if ( rc) return rc; break; case 'i': if ( one->ipaddr > two->ipaddr) return 1; else if ( one->ipaddr < two->ipaddr) return -1; else if ( one->port > two->port) return 1; else if ( one->port < two->port) return -1; break; case 'h': rc= strcmp_withnull( one->host_name, two->host_name); if ( rc) return rc; break; case 'n': rc= two->num_players - one->num_players; if ( rc) return rc; break; } } return 0; } int type_option_compare( server_type *one, server_type *two ) { return strcmp_withnull( one->type_option, two->type_option ); } int type_string_compare( server_type *one, server_type *two ) { return strcmp_withnull( one->type_string, two->type_string ); } int player_compare( struct player *one, struct player *two) { int rc; char *key= sort_keys; for ( ; *key; key++) { switch( *key) { case 'P': rc= one->ping - two->ping; if ( rc) return rc; break; case 'F': rc= two->frags - one->frags; if ( rc) return rc; break; case 'S': rc= two->score - one->score; if ( rc) return rc; break; case 'T': rc= one->team - two->team; if ( rc) return rc; rc = strcmp_withnull( one->team_name, two->team_name ); if ( rc ) return rc; break; case 'N': rc= strcmp_withnull( one->name, two->name ); if ( rc ) return rc; return one->number - two->number; break; } } return 0; } void quicksort( void **array, int i, int j, int (*compare)(void*,void*)) { int q= 0; if ( i < j) { q = qpartition(array,i,j, compare); quicksort(array,i,q, compare); quicksort(array,q+1,j, compare); } } int qpartition( void **array, int a, int b, int (*compare)(void*,void*)) { /* this is our comparison point. when we are done splitting this array into 2 parts, we want all the elements on the left side to be less then or equal to this, all the elements on the right side need to be greater then or equal to this */ void *z; /* indicies into the array to sort. Used to calculate a partition point */ int i = a-1; int j = b+1; /* temp pointer used to swap two array elements */ void * tmp = NULL; z = array[a]; while (1) { /* move the right indice over until the value of that array elem is less than or equal to z. Stop if we hit the left side of the array (ie, j == a); */ do { j--; } while( j > a && compare(array[j],z) > 0); /* move the left indice over until the value of that array elem is greater than or equal to z, or until we hit the right side of the array (ie i == j) */ do { i++; } while( i <= j && compare(array[i],z) < 0); /* if i is less then j, we need to switch those two array elements, if not then we are done partitioning this array section */ if(i < j) { tmp = array[i]; array[i] = array[j]; array[j] = tmp; } else return j; } } // vim: sw=8 ts=8 noet