/*
 * gstat.c
 *
 * gstat 1.0
 * by Aaron Levinson
 * alevinsn@uiuc.edu
 * http://www.ews.uiuc.edu/~alevinsn/
 *
 * Derived from qstat.c, by Steve Jankowski (steve@activesw.com).
 *
 * The following is from qstat.c :
 *
 *    Thanks to Per Hammer for the OS/2 patches (per@mindbend.demon.co.uk)
 *
 *    Inspired by QuakePing by Len Norton
 *
 *    Copyright 1996,1997 by Steve Jankowski
 *
 *       Permission granted to use this software for any purpose you desire
 *       provided that existing copyright notices are retained verbatim in all
 *       copies and derived works.
 *
 * End qstat.c information.
 *
 * Copyright 1998 by Aaron Levinson
 *
 *    Permission granted to use this software for any purpose you desire
 *    provided that existing copyright notices are retained verbatim in all
 *    copies and derived works.
 *
 */

#define VERSION "1.0"

/* OS/2 defines */
#ifdef __OS2__
#define BSD_SELECT
#endif

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <math.h>

#if (defined(unix) || defined(__unix) || defined(__unix__))
#define _ISUNIX
#endif /* unix, __unix, not sure where __unix is found though */

#if (defined(_AIX) || defined(aix) || defined(ibm) || defined(_IBMR2) || defined(ibm032))
#ifndef aix
#define aix
#endif /* aix */
#define _ISUNIX
#endif /* unix appears to not be defined for AIX */

#ifdef _ISUNIX

#if (defined(__hpux__) || defined(__hpux) || defined(hpux))
#ifndef hpux
#define hpux
#endif

#ifndef _XPG4_EXTENDED /* defined in SVR4 compatible versions of HP-UX */
#ifndef _XPG2
#define _XPG2
#endif /* this is for at least one version of SVR3 UNIX (HP-UX)  *
        * to bring out PATH_MAX in limits.h                      */
#endif /* _XPG4_EXTENDED */

#endif /* hpux */

#include <limits.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <setjmp.h>
#include <signal.h>
#include "gstat.h"

#ifdef hpux
extern int h_errno;
#endif

#ifndef PATH_MAX

#if defined(MAXPATHLEN)
#define PATH_MAX MAXPATHLEN
#elif defined(_POSIX_PATH_MAX)
#define PATH_MAX _POSIX_PATH_MAX
#else
#define PATH_MAX 512
#error Needed to define PATH_MAX.
#endif /* MAXPATHLEN */

#endif /* Since PATH_MAX is not official part of Standard Library */


char *getCommandLine(char *exec) {
  FILE *wPipe;
  char cmdstring[100];
  static char cmdpath[PATH_MAX+1];

  if (exec == ((char *) 0))
    return ((char *) 0);
  sprintf(cmdstring, "which %s", exec);
  if ((wPipe = popen(cmdstring, "r")) == ((FILE *) 0))
    return ((char *) 0);
  if (fgets(cmdpath, PATH_MAX, wPipe) == ((char *) 0)) {
    pclose(wPipe);
    return ((char *) 0);
  }
  if (cmdpath[strlen(cmdpath)-1] == '\n')
    cmdpath[strlen(cmdpath)-1] = '\0';
  pclose(wPipe);
  return cmdpath;
}

#define INVALID_SOCKET -1
#define SOCKET_ERROR -1

#ifndef INADDR_NONE
#define INADDR_NONE ~0
#endif /* INADDR_NONE, should have been in <netinet/in.h> */

#define sockerr()	errno
#define SEP_CHAR '/'

/* OSF/1 actually disables recv() and send() in <sys/socket.h> *
   according to Unix Network Programming by Stevens.           */
#ifdef __osf__
#undef recv
#undef send
#define recv(a,b,c,d) recvfrom(a,b,c,d,0,0)
#define send(a,b,c,d) sendto(a,b,c,d,0,0)
#endif /* __osf__ */

#endif /* _ISUNIX */

#if (defined(_WIN32) || defined(WIN32))

#ifndef WIN32
#define WIN32
#endif

#ifndef _POSIX_
#define _POSIX_
#endif /* _POSIX_ */

#include <windows.h>
#include <limits.h>
#include <winsock.h>
#include <sys/timeb.h>
#include <time.h>
#include <direct.h>
#include "gstat.h"

#define close(a) closesocket(a)
int gettimeofday(struct timeval *now, void *blah) {
  struct timeb timeb;
  ftime(&timeb);
  now->tv_sec = timeb.time;
  now->tv_usec = (unsigned int)timeb.millitm * 1000;
  return 0;
}

char *getCommandLine(char *exec) {
  static char cmdpath[PATH_MAX+1];

  if (GetModuleFileName(NULL, cmdpath, PATH_MAX) == 0)
    return ((char *) 0);

  return cmdpath;
}

#define sockerr()	WSAGetLastError()
#ifndef EADDRINUSE
#define EADDRINUSE	WSAEADDRINUSE
#endif /* EADDRINUSE */

#define SEP_CHAR '\\'

#endif /* WIN32 */

#ifdef __OS2__
#include <limits.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netdb.h>
#include <utils.h>
#include "gstat.h"
 
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#define close(a)        soclose(a)
#define SEP_CHAR '\\'

#endif /* __OS2__ */

#ifndef FD_SETSIZE
#define FD_SETSIZE 64
#endif

#ifndef PATH_MAX
#define PATH_MAX 512
#error Needed to define PATH_MAX
#endif /* Since PATH_MAX is not official part of Standard Library */

typedef void Sigfunc(int); /* used in signal */

/* adding following function because at least one version of SunOS *
 * does not include difftime, must explicity define NO_DIFFTIME    */

#ifdef NO_DIFFTIME
double difftime(time_t time1, time_t time0) {
  return((double) time1 - (double) time0);
}
#endif


/* 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 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 raw_display = 0;
char *raw_delimiter = "\t";
int player_address = 0;
int hex_player_names = 0;
int hexen2 = 0;
int max_simultaneous = MAXFD_DEFAULT;
int raw_arg = 0;
int progress = 0;
int progress_host = 0;
int showpacketon = 0;
int just_num_servers = 0; /* for master servers only */
int just_num_players = 0; /* for master servers only */
int print_servers_anyway = 0; /* for master servers only */
int num_servers_total = 0;
int num_servers_returned = 0;
int num_servers_timed_out = 0;
int num_servers_down = 0;
int num_servers_total_theoretical = 0;
int total_num_players = 0;
char master_ip[22];

#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;

qserver *servers = NULL;
QWPack qwserverstatus;
int connected = 0;
double run_timeout = 0;
time_t start_time;

char *DOWN = "DOWN";
char *SYSERROR = "SYSERROR";
char *TIMEOUT = "TIMEOUT";
char *MASTER = "MASTER";
char *SERVERERROR = "ERROR";
char *NOSKIN = "";

int display_prefix = 0;
char *current_filename;
int current_fileline;


/* MODIFY HERE
 * Change these functions to display however you want
 */
void display_server(qserver *server) {
  char name[100], *prefix;

  if (raw_display) {
    raw_display_server(server);
    return;
  }

  if (server->type == Q_SERVER)
    prefix = "QS  ";
  else if (server->type == H2_SERVER)
    prefix = "H2S ";
  else if (server->type == QW_SERVER)
    prefix = "QW  ";
  else if (server->type == QW_MASTER)
    prefix = "QWM ";
  else if (server->type == Q2_MASTER)
    prefix = "Q2M ";
  else if (server->type == Q2_SERVER)
    prefix = "Q2  ";

  if (server->server_name == DOWN) {
    printf("%s%-16s %10s\n", (display_prefix)?prefix:"",
           (hostname_lookup) ? server->host_name : server->arg, DOWN);
    return;
  }
  if (server->server_name == TIMEOUT) {
    if (!up_servers_only)
      printf("%s%-16s no response\n", (display_prefix)?prefix:"",
             (hostname_lookup) ? server->host_name : server->arg);
    return;
  }

  if (server->type == QW_MASTER || server->type == Q2_MASTER) {
    display_qwmaster(server, prefix);
    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) {
    printf("%s%-22s ERROR <%s>\n",
           (display_prefix)?prefix:"",
           (hostname_lookup) ? server->host_name : server->arg,
           server->error);
    return;
  }

  if (new_style) {
    char *game = get_qw_game(server);
    printf("%s%-22s %2d/%2d %8s %6d / %1d  %*s %s\n",
           (display_prefix)?prefix:"",
           (hostname_lookup) ? server->host_name : server->arg,
           server->num_players, server->max_players,
           (server->map_name) ? server->map_name : "?",
           server->ping_total/server->n_requests,
           server->n_retries,
           (server->type == QW_SERVER || server->type == Q2_SERVER) ? 9 : 0, game,
           (server->server_name) ? server->server_name : "?");
    if (get_server_rules)
      display_server_rules(server);
    if (get_player_info)
      display_player_info(server);
  }
  else {
    sprintf(name, "\"%s\"", server->server_name);
    printf("%-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->ping_total/server->n_requests);
  }
}

void display_qwmaster(qserver *server, char *prefix) {
  if (progress_host)
    printf("\n");
  if (server->error != NULL)
    printf("%s%-17s ERROR <%s>\n", prefix, 
           (hostname_lookup) ? server->host_name : server->arg,
           server->error);
  else
    printf("%s%-17s %d servers %6d / %1d%s", prefix, 
           (hostname_lookup) ? server->host_name : server->arg,
           server->n_servers,
           server->ping_total/server->n_requests,
           server->n_retries,
           (just_num_servers && just_num_players && !progress &&
            !print_servers_anyway) ? "   " : "\n");
}

void display_qwmaster_players(void) {
  printf("%s %-17s %d players\n", "QWM", master_ip, total_num_players);
}

void display_header(void) {
  if (display_prefix)
    printf("%13s %20s %6s %13s    %1s\n", "ADDRESS", "PLAYERS", "MAP",
           "RESPONSE TIME", "NAME");
  else
    printf("%10s %20s %6s %13s    %1s\n", "ADDRESS", "PLAYERS", "MAP",
           "RESPONSE TIME", "NAME");
}

void display_server_rules(qserver *server) {
  rule *rules;
  int printed = 0;
  for (rules = server->rules ; rules != NULL; rules = rules->next) {
    if ((server->type != Q_SERVER && server->type != H2_SERVER) ||
        !is_default_rule(rules)) {
      printf("%c%s=%s", (printed)?',':'\t', rules->name, rules->value);
      printed++;
    }
  }
  if (printed)
    puts("");
}

void display_player_info(qserver *server) {
  if (server->type == Q_SERVER || server->type == H2_SERVER)
    display_q_player_info(server);
  else if (server->type == QW_SERVER)
    display_qw_player_info(server);
  else if (server->type == Q2_SERVER)
    display_q2_player_info(server);
}

void display_q_player_info(qserver *server) {
  char fmt[128];
  player *players;

  strcpy(fmt, "\t#%-2d %3d frags %9s ");

  if (color_names)
    strcat(fmt, "%9s:%-9s ");
  else
    strcat(fmt, "%2d:%-2d ");
  if (player_address)
    strcat(fmt, "%22s ");
  else
    strcat(fmt, "%s");
  strcat(fmt, "%s\n");

  for (players = server->players; players != NULL; players = players->next) {
    printf(fmt,
           players->number,
           players->frags,
           play_time(players->connect_time,1),
           quake_color(players->shirt_color),
           quake_color(players->pants_color),
           (player_address)?players->address:"",
           escape((unsigned char *) players->name));
  }
}

void display_qw_player_info(qserver *server) {
  char fmt[128];
  player *players;

  strcpy(fmt, "\t#%-6d %3d frags %6s@%-5s %8s");

  if (color_names)
    strcat(fmt, "%10s:%-9s ");
  else
    strcat(fmt, "%2d:%-2d ");
  strcat(fmt, "%s\n");

  for (players = server->players; players != NULL; players = players->next) {
    printf(fmt,
           players->number,
           players->frags,
           play_time(players->connect_time,0),
           ping_time(players->ping),
           players->skin,
           quake_color(players->shirt_color),
           quake_color(players->pants_color),
           escape((unsigned char *) players->name));
  }
}

void display_q2_player_info(qserver *server) {
  player *players;

  for (players = server->players; players != NULL; players = players->next) {
    printf("\t%3d frags %8s  %s\n", 
           players->frags,
           ping_time(players->ping),
           escape((unsigned char *) players->name));
  }
}

char *get_qw_game(qserver *server) {
  rule *rules;
  rules = server->rules;
  for ( ; rules != NULL; rules = rules->next)
    if (strcmp(rules->name, "*gamedir") == 0 ||
        strcmp(rules->name, "game") == 0)
      return rules->value;
  return "";
}

/* Raw output for web master types
 */

#define RDL raw_delimiter

void raw_display_server(qserver *server) {
  char *prefix;
  if (server->type == Q_SERVER)
    prefix = "QS";
  else if (server->type == H2_SERVER)
    prefix = "H2S";
  else if (server->type == QW_SERVER)
    prefix = "QW";
  else if (server->type == QW_MASTER)
    prefix = "QWM";
  else if (server->type == Q2_MASTER)
    prefix = "Q2M";
  else if (server->type == Q2_SERVER)
    prefix = "Q2";

  if (server->server_name == DOWN) {
    if (!up_servers_only)
      printf("%s" "%.*s%.*s" "%s%s" "%s%s\n\n",
             prefix,
             raw_arg, RDL, raw_arg, server->arg,
             RDL, (hostname_lookup)?server->host_name:server->arg,
             RDL, DOWN);
    return;
  }
  if (server->server_name == TIMEOUT) {
    if (!up_servers_only)
      printf("%s" "%.*s%.*s" "%s%s" "%s%s\n\n",
             prefix,
             raw_arg, RDL, raw_arg, server->arg,
             RDL, (hostname_lookup)?server->host_name:server->arg,
             RDL, TIMEOUT);
    return;
  }

  if (server->error != NULL) {
    printf("%s" "%.*s%.*s" "%s%s" "%s%s" "%s%s",
           prefix,
           raw_arg, RDL, raw_arg, server->arg,
           RDL, (hostname_lookup) ? server->host_name : server->arg,
           RDL, "ERROR",
           RDL, server->error);
  }
  else if (server->type == Q_SERVER || server->type == H2_SERVER) {
    printf("%s" "%.*s%.*s" "%s%s" "%s%s" "%s%s" "%s%d" "%s%s" "%s%d" "%s%d" "%s%d" "%s%d",
           prefix,
           raw_arg, RDL, raw_arg, server->arg,
           RDL, (hostname_lookup) ? server->host_name : server->arg,
           RDL, escape((unsigned char *) server->server_name),
           RDL, server->address,
           RDL, server->protocol_version,
           RDL, server->map_name,
           RDL, server->max_players,
           RDL, server->num_players,
           RDL, server->ping_total/server->n_requests,
           RDL, server->n_retries
           );
  }
  else if (server->type == QW_SERVER || server->type == Q2_SERVER) {
    printf("%s" "%.*s%.*s" "%s%s" "%s%s" "%s%s" "%s%d" "%s%d" "%s%d" "%s%d",
           prefix,
           raw_arg, RDL, raw_arg, server->arg,
           RDL, (hostname_lookup) ? server->host_name : server->arg,
           RDL, escape((unsigned char *) server->server_name),
           RDL, (server->map_name) ? server->map_name : "?",
           RDL, server->max_players,
           RDL, server->num_players,
           RDL, server->ping_total/server->n_requests,
           RDL, server->n_retries
           );
  }
  else if (server->type == QW_MASTER || server->type == Q2_MASTER) {
    printf("%s" "%.*s%.*s" "%s%s" "%s%d",
           prefix,
           raw_arg, RDL, raw_arg, server->arg,
           RDL, (hostname_lookup) ? server->host_name : server->arg,
           RDL, server->n_servers
           );
  }
  puts("");

  if (server->type == QW_MASTER || server->type == Q2_MASTER ||
      server->error != NULL) {
    puts("");
    return;
  }

  if (get_server_rules)
    raw_display_server_rules(server);
  if (get_player_info)
    raw_display_player_info(server);
  puts("");
}

void raw_display_server_rules(qserver *server) {
  rule *rules;
  int printed = 0;
  rules = server->rules;
  for ( ; rules != NULL; rules = rules->next) {
    printf("%s%s=%s", (printed)?RDL:"", rules->name, rules->value);
    printed++;
  }
  if (server->missing_rules)
    printf("%s?", (printed)?RDL:"");
  puts("");
}

void raw_display_player_info(qserver *server) {
  if (server->type == Q_SERVER || server->type == H2_SERVER)
    raw_display_q_player_info(server);
  else if (server->type == QW_SERVER)
    raw_display_qw_player_info(server);
  else if (server->type == Q2_SERVER)
    raw_display_q2_player_info(server);
}

void raw_display_q_player_info(qserver *server) {
  char fmt[128];
  player *players;

  strcpy(fmt, "%d" "%s%s" "%s%s" "%s%d" "%s%s");
  if (color_names)
    strcat(fmt, "%s%s" "%s%s");
  else
    strcat(fmt, "%s%d" "%s%d");

  players = server->players;
  for ( ; players != NULL; players = players->next) {
    printf(fmt,
           players->number,
           RDL, escape((unsigned char *) players->name),
           RDL, players->address,
           RDL, players->frags,
           RDL, play_time(players->connect_time,1),
           RDL, quake_color(players->shirt_color),
           RDL, quake_color(players->pants_color)
           );
    puts("");
  }
}

void raw_display_qw_player_info(qserver *server) {
  char fmt[128];
  player *players;

  strcpy(fmt, "%d" "%s%s" "%s%d" "%s%s");
  if (color_names)
    strcat(fmt, "%s%s" "%s%s");
  else
    strcat(fmt, "%s%d" "%s%d");
  strcat(fmt, "%s%d" "%s%s");

  players = server->players;
  for ( ; players != NULL; players = players->next) {
    printf(fmt,
           players->number,
           RDL, escape((unsigned char *) players->name),
           RDL, players->frags,
           RDL, play_time(players->connect_time,1),
           RDL, quake_color(players->shirt_color),
           RDL, quake_color(players->pants_color),
           RDL, players->ping,
           RDL, players->skin
           );
    puts("");
  }
}

void raw_display_q2_player_info(qserver *server) {
  char fmt[128];
  player *players;

  strcpy(fmt, "%s" "%s%d" "%s%d");

  players = server->players;
  for ( ; players != NULL; players = players->next) {
    printf(fmt,
           escape((unsigned char *) players->name),
           RDL, players->frags,
           RDL, players->ping
           );
    puts("");
  }
}

void display_progress(void) {
  if (just_num_players && !print_servers_anyway)
    fprintf(stderr, "\r%d/%d (%d timed out, %d down) %d players",
            num_servers_returned+num_servers_timed_out+num_servers_down,
            num_servers_total,
            num_servers_timed_out,
            num_servers_down,
            total_num_players);
  else
    fprintf(stderr, "\r%d/%d (%d timed out, %d down)",
            num_servers_returned+num_servers_timed_out+num_servers_down,
            num_servers_total,
            num_servers_timed_out,
            num_servers_down);
}

void display_progress_got_host(int num) {
  static time_t first_time;
  double total_min;
  double total_sec;
  double difference;
  
  if (num == 1) {
    first_time = time(0);
    difference = 0;
  }
  else
    difference = difftime(time(0), first_time);

  total_sec = modf(difference/((double) 60), &total_min) * 60;
  
  fprintf(stderr, "\r%d/%d (servers done for hostname lookup), Time: %g:%s%g",
          num,
          num_servers_total_theoretical,
          total_min,
          (total_sec <= 9) ? "0" : "",
          total_sec);
}

/* ----- END MODIFICATION ----- Don't need to change anything below here. */


/* These packets are fixed size, so don't bother rebuilding them each time.
 * This only works because the packets are an even number in size.
 */
struct {
  unsigned char flag1;
  unsigned char flag2;
  unsigned short int length;
  unsigned char op_code;
  char name[6];
  unsigned char version;
} qserverinfo =
{ Q_FLAG1, Q_FLAG2, sizeof(qserverinfo), Q_CCREQ_SERVER_INFO,
  "QUAKE", Q_NET_PROTOCOL_VERSION };

struct {
  unsigned char flag1;
  unsigned char flag2;
  unsigned short length;
  unsigned char op_code;
  char name[8];
  unsigned char version;
} h2serverinfo =
{ Q_FLAG1, Q_FLAG2, sizeof(h2serverinfo), Q_CCREQ_SERVER_INFO,
  "HEXENII", HEXEN2_NET_PROTOCOL_VERSION };

struct {
  unsigned char flag1;
  unsigned char flag2;
  unsigned short length;
  unsigned char op_code;
  unsigned char player_number;
} qplayerinfo =
{ Q_FLAG1, Q_FLAG2, sizeof(qplayerinfo), Q_CCREQ_PLAYER_INFO, 0 };


/* Misc flags
 */

char *NO_SERVER_RULES = (char *) 0;
int NO_PLAYER_INFO = 0xffff;
struct timeval packet_recv_time;
int server_types = 0;
#define FORCE 1

/* Print an error message and the program usage notes
 */

void usage(char *msg, char **argv) {
  int MAX_SHORT = 5;
  int MAX_LONG = 22;
  
  printf("Usage: %s [options ...] [-retry retries] [-interval interval]\n",
         argv[0]);
  printf("\t[-raw delimiter] [-qw [host[:port]]] [-qws host[:port] [command]]\n");
  printf("\t[-h2s host[:port]] [-q2 host[:port]] [-q2s host[:port] [command]]\n");
  printf("\t[-f file] host[:port] ...\n");
  printf("Where <host> is an IP address or hostname\n");
  printf(" %-*s %-*s only servers that are up\n",
         MAX_SHORT, "-u", MAX_LONG, "--up");
  printf(" %-*s %-*s no full servers\n",
         MAX_SHORT, "-nf", MAX_LONG, "--nofull");
  printf(" %-*s %-*s no empty servers\n",
         MAX_SHORT, "-ne", MAX_LONG, "--noempty");
  printf(" %-*s %-*s names for colors\n",
         MAX_SHORT, "-cn", MAX_LONG, "--colornames");
  printf(" %-*s %-*s numbers for colors\n",
         MAX_SHORT, "-ncn", MAX_LONG, "--nocolornames");
  printf(" %-*s %-*s time in clock format (DhDDmDDs)\n",
         MAX_SHORT, "-tc", MAX_LONG, "--clocktime");
  printf(" %-*s %-*s time in stop-watch format (DD:DD:DD)\n",
         MAX_SHORT, "-tsw", MAX_LONG, "--stopwatchtime");
  printf(" %-*s %-*s time in seconds\n",
         MAX_SHORT, "-ts", MAX_LONG, "--secondstime");
  printf(" %-*s %-*s player addresses\n",
         MAX_SHORT, "-pa", MAX_LONG, "--playeraddress");
  printf(" %-*s %-*s player names in hex\n",
         MAX_SHORT, "-hpn", MAX_LONG, "--hexplayernames");
  printf(" %-*s %-*s old style display\n",
         MAX_SHORT, "-o", MAX_LONG, "--old");
  printf(" %-*s %-*s number of retries, default is %d\n",
         MAX_SHORT, "-r", MAX_LONG, "--retry", DEFAULT_RETRIES);
  printf(" %-*s %-*s interval between retries, default is %.2f sec\n",
         MAX_SHORT, "-i", MAX_LONG, "--interval",
         (DEFAULT_RETRY_INTERVAL / 1000.0));
  printf(" %-*s %-*s time in seconds before giving up\n",
         MAX_SHORT, "-t", MAX_LONG, "--timeout");
  printf(" %-*s %-*s use progress meter\n",
         MAX_SHORT, "-p", MAX_LONG, "--progress");
  printf(" %-*s %-*s set max simultaneous queries\n",
         MAX_SHORT, "-ms", MAX_LONG, "--maxsimultaneous");
  printf(" %-*s %-*s only print # of servers for a master\n",
         MAX_SHORT, "-nso", MAX_LONG, "--numserversonly");
  printf(" %-*s %-*s only print # of players for master\n",
         MAX_SHORT, "-npo", MAX_LONG, "--numplayersonly");
  printf(" %-*s %-*s print servers anyway if using above two options\n",
         MAX_SHORT, "-ps", MAX_LONG, "--printservers");
  printf(" %-*s %-*s raw format using delimiter\n",
         MAX_SHORT, "-raw", MAX_LONG, "--raw");
  printf(" %-*s %-*s read servers from file\n",
         MAX_SHORT, "-f", MAX_LONG, "--file");
  printf(" %-*s %-*s get server list from QuakeWorld master server\n",
         MAX_SHORT, "-qw", MAX_LONG, "--qwmaster");
  printf(" %-*s %-*s display stats for or admin QuakeWorld server\n",
         MAX_SHORT, "-qws", MAX_LONG, "--qwserver");
  printf(" %-*s %-*s get server list from Quake2 master server\n",
         MAX_SHORT, "-q2", MAX_LONG, "--q2server");
  printf(" %-*s %-*s display stats for or admin Quake2 server\n",
         MAX_SHORT, "-q2s", MAX_LONG, "--q2server");
  printf(" %-*s %-*s display stats for Hexen II server\n",
         MAX_SHORT, "-h2s", MAX_LONG, "--h2server");
  printf(" %-*s %-*s use Hexen II protocol\n",
         MAX_SHORT, "-hex2", MAX_LONG, "--hexen2");
  printf(" %-*s %-*s resolve host names (and display progress)\n",
         MAX_SHORT, "-H[p]", MAX_LONG, "--Hostnames[progress]");
  printf(" %-*s %-*s server rules\n", MAX_SHORT, "-R", MAX_LONG, "--Rules");
  printf(" %-*s %-*s player info\n", MAX_SHORT, "-P", MAX_LONG, "--Players");
  if (msg == ((char *) 0))
    printf("gstat version %s\n", VERSION);

  if (msg)
    fprintf(stderr, "\nError: %s", msg);

  exit(0);
}

int main(int argc, char *argv[]) {
  int pktlen, rc, maxfd;
  long pkt_data[PACKET_LEN/sizeof(long)];
  char *pkt = (char *)&pkt_data[0];
  fd_set read_fds;
  struct timeval timeout;
  int arg, n_files, i;
  int n_qw_masters, n_q2_masters, n_qw_servers, n_q2_servers, n_h2_servers;
  qserver *server;
  char **files, **qw_masters, **q2_masters, **qw_servers, **q2_servers,
    **h2_servers;
  int error_type = 0;
  char errtemp[200];
  int ret = 0;
 
#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);
  }
#endif

  if (argc == 1)
    usage(NULL,argv);

  files = (char **) malloc(sizeof(char *) * (argc/2));
  if (files == ((char **) 0)) 
    ret = 1;
  else {
    qw_masters = (char **) malloc(sizeof(char *) * (argc/2));
    if (qw_masters == ((char **) 0))
      ret = 2;
    else {
      q2_masters = (char **) malloc(sizeof(char *) * (argc/2));
      if (q2_masters == ((char **) 0))
        ret = 3;
      else {
        qw_servers = (char **) calloc(1, sizeof(char *) * (argc/2));
        if (qw_servers == ((char **) 0))
          ret = 4;
        else {
          q2_servers = (char **) calloc(1, sizeof(char *) * (argc/2));
          if (q2_servers == ((char **) 0))
            ret = 5;
          else {
            h2_servers = (char **) calloc(1, sizeof(char *) * (argc/2));
            if (h2_servers == ((char **) 0))
              ret = 5;
          }
        }
      }
    }
  }

  if (ret > 0) {
    free(files);
    if (ret > 1) {
      free(qw_masters);
      if (ret > 2) {
        free(q2_masters);
        if (ret > 3) {
          free(q2_servers);
          if (ret > 4)
            free(q2_servers);
        }
      }
    }
#ifdef WIN32
    WSACleanup();
#endif
    fprintf(stderr, "Not enough memory or disk space!\n");
  }

  n_files = 0;
  n_qw_masters = 0;
  n_q2_masters = 0;
  n_qw_servers = 0;
  n_q2_servers = 0;
  n_h2_servers = 0;

  for (arg = 1; arg < argc; arg++) {
    if (argv[arg][0] != '-')
      break;

    if (!strcmp(argv[arg], "-qw") || !strcmp(argv[arg], "--qwmaster")) {
      strcpy(qwserverstatus.prefix,"\377\377\377\377");
      strcpy(qwserverstatus.command, "status");	
      arg++;
      if (arg >= argc) {
        qw_masters[n_qw_masters++] = use_master_file(argv[0]);
        if (qw_masters[n_qw_masters-1] == ((char *) 0)) {
          error_type = -2;
          goto errorfree;
        }
        if (!strcmp(qw_masters[n_qw_masters-1], "")) {
          error_type = 3;
          goto errorfree;
        }
        strcpy(master_ip, qw_masters[n_qw_masters-1]);
      }
      else if (argv[arg][0] == '-') {
        qw_masters[n_qw_masters++] = use_master_file(argv[0]);
        if (qw_masters[n_qw_masters-1] == ((char *) 0)) {
          error_type = -2;
          goto errorfree;
        }
        if (!strcmp(qw_masters[n_qw_masters-1], "")) {
          error_type = 3;
          goto errorfree;
        }
        strcpy(master_ip, qw_masters[n_qw_masters-1]);
        arg--;
      }
      else
        qw_masters[n_qw_masters++]= argv[arg];
      strcpy(master_ip, qw_masters[n_qw_masters-1]);
    }

    else if (!strcmp(argv[arg], "-qws") ||
             !strcmp(argv[arg], "--qwserver")) {
      arg++;
      if ( arg >= argc) {
        sprintf(errtemp, "missing argument for %s\n", argv[arg-1]);
        goto errorfree;
      }
      qw_servers[n_qw_servers++]= argv[arg];
      strcpy(qwserverstatus.prefix, "\377\377\377\377");
      if (argc == (arg + 1))
        strcpy(qwserverstatus.command, "status");
      else {
        if (argv[arg+1][0] == '-')
          strcpy(qwserverstatus.command, "status");
        else {
          arg++;
          if (strlen(argv[arg] + 1) > MAX_COMMAND_LENGTH)
            fprintf(stderr, "-qws command is too long, fitting as much as possible....\n");
          strncpy(qwserverstatus.command, argv[arg], MAX_COMMAND_LENGTH);
          qwserverstatus.command[MAX_COMMAND_LENGTH] = '\0';
          showpacketon = 1;
        }
      }
    }

    else if (!strcmp(argv[arg], "-q2s") ||
             !strcmp(argv[arg], "--q2server")) {
      arg++;
      if ( arg >= argc) {
        sprintf(errtemp, "missing argument for %s\n", argv[arg-1]);
        goto errorfree;
      }
      q2_servers[n_q2_servers++]= argv[arg];
      strcpy(qwserverstatus.prefix, "\377\377\377\377");
      if (argc == (arg + 1))
        strcpy(qwserverstatus.command, "status");
      else {
        if (argv[arg+1][0] == '-')
          strcpy(qwserverstatus.command, "status");
        else {
          arg++;
          if (strlen(argv[arg] + 1) > MAX_COMMAND_LENGTH)
            fprintf(stderr, "-qws command is too long, fitting as much as possible....\n");
          strncpy(qwserverstatus.command, argv[arg], MAX_COMMAND_LENGTH);
          qwserverstatus.command[MAX_COMMAND_LENGTH] = '\0';
          showpacketon = 1;
        }
      }
    }

    else if (!strcmp(argv[arg], "-h2s") ||
             !strcmp(argv[arg], "--h2server")) {
      arg++;
      if (arg >= argc) {
        sprintf(errtemp, "missing argument for %s.\n", argv[arg-1]);
        goto errorfree;
      }
      h2_servers[n_h2_servers++] = argv[arg];
    }

    else if (!strcmp(argv[arg], "-hex2") ||
             !strcmp(argv[arg], "--hexen2") ||
             !strcmp(argv[arg], "-hexen2")) {
      hexen2 = 1;
    }

    else if (!strcmp(argv[arg], "-f") || !strcmp(argv[arg], "--file")) {
      arg++;
      if (arg >= argc) {
        sprintf(errtemp, "missing argument for %s\n", argv[arg-1]);
        goto errorfree;
      }
      files[n_files++] = argv[arg];
    }

    else if (!strcmp(argv[arg], "-H") || !strcmp(argv[arg], "--Hostnames"))
      hostname_lookup = 1;

    else if (!strcmp(argv[arg], "-Hp") ||
             !strcmp(argv[arg], "--Hostnamesprogress") ||
             !strcmp(argv[arg], "-Hprogress")) {
      hostname_lookup = 1;
      progress_host = 1;
    }

    else if (!strcmp(argv[arg], "-P") || !strcmp(argv[arg], "--Players"))
      get_player_info = 1;

    else if (!strcmp(argv[arg], "-R") || !strcmp(argv[arg], "--Rules"))
      get_server_rules = 1;

    else if (!strcmp(argv[arg], "-q2") ||
             !strcmp(argv[arg], "--q2master")) {
      arg++;
      if (arg >= argc) {
        sprintf(errtemp, "missing argument for %s\n", argv[arg-1]);
        goto errorfree;
      }
      q2_masters[n_q2_masters++] = argv[arg];
    }

    else if (!strcmp(argv[arg], "-p") ||
             !strcmp(argv[arg], "--progress") ||
             !strcmp(argv[arg], "-progress")) {
      progress = 1;
      progress_host = 1;
    }

    else if (!strcmp(argv[arg], "-nso") || 
             !strcmp(argv[arg], "--numserversonly"))
      just_num_servers = 1;

    else if (!strcmp(argv[arg], "-npo") ||
             !strcmp(argv[arg], "--numplayersonly"))
      just_num_players = 1;

    else if (!strcmp(argv[arg], "-ps") ||
             !strcmp(argv[arg], "--printservers"))
      print_servers_anyway = 1;

    else if (!strcmp(argv[arg], "-t") ||
             !strcmp(argv[arg], "--timeout") ||
             !strcmp(argv[arg], "-timeout")) {             
      arg++;
      if (arg >= argc) {
        sprintf(errtemp, "missing argument for %s\n", argv[arg-1]);
        goto errorfree;
      }
      run_timeout = atoi(argv[arg]);
      if (run_timeout <= 0) {
        sprintf(errtemp, "value for %s must be > 0\n", argv[arg-1]);
        goto errorfree;
      }
    }

    else if (!strcmp(argv[arg], "-r") ||
             !strcmp(argv[arg], "--retry") ||
             !strcmp(argv[arg], "-retry")) {
      arg++;
      if (arg >= argc) {
        sprintf(errtemp, "missing argument for %s\n", argv[arg-1]);
        goto errorfree;
      }
      n_retries = atoi(argv[arg]);
      if (n_retries <= 0) {
        error_type = 1;
        strcpy(errtemp, "retries must be greater than zero\n");
        goto errorfree;
      }
    }

    else if (!strcmp(argv[arg], "-i") ||
             !strcmp(argv[arg], "--interval") ||
             !strcmp(argv[arg], "-interval")) {
      double value = 0.0;
      arg++;
      if (arg >= argc) {
        sprintf(errtemp, "missing argument for %s\n", argv[arg-1]);
        goto errorfree;
      }
      sscanf(argv[arg], "%lf", &value);
      if (value < 0.1) {
        error_type = 1;
        strcpy(errtemp, "retry interval must be greater than 0.1\n");
        goto errorfree;
      }
      retry_interval = (int)(value * 1000);
    }

    else if (!strcmp(argv[arg], "-ms") ||
             !strcmp(argv[arg], "--maxsimultaneous") ||
             !strcmp(argv[arg], "-maxsimultaneous")) {
      arg++;
      if (arg >= argc) {
        sprintf(errtemp, "missing argument for %s\n", argv[arg-1]);
        goto errorfree;
      }
      max_simultaneous = atoi(argv[arg]);
      if (max_simultaneous <= 0) {
        sprintf(errtemp, "value for %s must be > 0\n", argv[arg-1]);
        goto errorfree;  
      }
      if (max_simultaneous > FD_SETSIZE)
        max_simultaneous = FD_SETSIZE;
    }

    else if (!strcmp(argv[arg], "-raw") || !strcmp(argv[arg], "--raw")) {
      arg++;
      if (arg >= argc) {
        sprintf(errtemp, "missing argument for %s\n", argv[arg-1]);
        goto errorfree;
      }
      raw_delimiter = argv[arg];
      if (strlen(raw_delimiter) > 1) /* user may have made a mistake */
        fprintf(stderr, "using %s for raw delimiter....\n", raw_delimiter);
      raw_display = 1;
    }

    else if (!strcmp(argv[arg], "-u") || !strcmp(argv[arg], "--up"))
      up_servers_only = 1;

    else if (!strcmp(argv[arg], "-nf") || !strcmp(argv[arg], "--nofull"))
      no_full_servers = 1;

    else if (!strcmp(argv[arg], "-ne") || !strcmp(argv[arg], "--noempty"))
      no_empty_servers = 1;

    else if (!strcmp(argv[arg], "-o") ||
             !strcmp(argv[arg], "--old") ||
             !strcmp(argv[arg], "-old"))
      new_style = 0;

    else if (!strcmp(argv[arg], "-ncn") ||
             !strcmp(argv[arg], "--nocolornames")) {
      color_names = 0;
    }

    else if (!strcmp(argv[arg], "-cn") ||
             !strcmp(argv[arg], "--colornames")) {
      color_names = 1;
    }

    else if (!strcmp(argv[arg], "-tc") || !strcmp(argv[arg], "--clocktime")) {
      time_format = CLOCK_TIME;
    }

    else if (!strcmp(argv[arg], "-tsw") ||
             !strcmp(argv[arg], "--stopwatchtime")) {
      time_format = STOPWATCH_TIME;
    }

    else if (!strcmp(argv[arg], "-ts") ||
             !strcmp(argv[arg], "--secondstime")) {
      time_format = SECONDS;
    }

    else if (!strcmp(argv[arg], "-pa") ||
             !strcmp(argv[arg], "--playeraddress")) {
      player_address = 1;
    }

    else if (!strcmp(argv[arg], "-hpn") ||
             !strcmp(argv[arg], "--hexplayernames")) {
      hex_player_names = 1;
    }

    /* not sure what this is for, but leaving it in */
    else if (!strcmp(argv[arg], "-raw-arg")) {
      raw_arg = 1000;
    }

    else {
      sprintf(errtemp,"unknown option \"%s\"\n", argv[arg]);
      goto errorfree;
    }
  }

  if ((n_qw_masters == 0) && (n_q2_masters == 0)) {
    just_num_servers = 0;
    just_num_players = 0;
    print_servers_anyway = 0;
  }
  
  if (print_servers_anyway)
    /* redundant if set to 1 */
    just_num_servers = 0;

  if (just_num_servers && !just_num_players)
    /* no progress for only printing num servers */
    progress = 0;

  if ((just_num_servers || just_num_players) && !print_servers_anyway)
    /* if you aren't printing out the servers, no reason to lookup hosts */
    hostname_lookup = 0;
  
  start_time = time(0);

  for (i = 0; i < n_files; i++)
    if ((ret = add_file(files[i])) <= -2) {
      error_type = ret;
      goto errorfree;
    }
  for ( ; arg < argc; arg++)
    if ((ret = add_qserver(argv[arg], (hexen2)?H2_SERVER:Q_SERVER)) <= -2) {
      error_type = ret;
      goto errorfree;
    }
  for (i = 0; i < n_h2_servers; i++)
    if ((ret = add_qserver(h2_servers[i], H2_SERVER)) <= -2) {
      error_type = ret;
      goto errorfree;
    }
  for (i = 0; i < n_qw_servers; i++)
    if ((ret = add_qserver(qw_servers[i], QW_SERVER)) <= -2) {
      error_type = ret;
      goto errorfree;
    }
  for (i = 0; i < n_q2_servers; i++)
    if ((ret = add_qserver(q2_servers[i], Q2_SERVER)) <= -2) {
      error_type = ret;
      goto errorfree;
    }
  for (i = 0; i < n_qw_masters; i++)
    if ((ret = add_qserver(qw_masters[i], QW_MASTER)) <= -2) {
      error_type = ret;
      goto errorfree;
    }
  for (i = 0; i < n_q2_masters; i++)
    if ((ret = add_qserver(q2_masters[i], Q2_MASTER)) <= -2) {
      error_type = ret;
      goto errorfree;
    }

  if (servers == NULL) {
    strcpy(errtemp, "nothing to do!\n");
    goto errorfree;
  }

  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 ((server_types & QW_MASTER || server_types & Q2_MASTER)
      || count_bits(server_types) > 1)
    display_prefix = 1;

  if (new_style && !raw_display && !showpacketon)
    if ((!just_num_players && !just_num_servers) || print_servers_anyway)
      display_header();

  qserverinfo.length = htons(qserverinfo.length);
  h2serverinfo.length = htons(h2serverinfo.length);
  qplayerinfo.length = htons(qplayerinfo.length);

  bind_sockets();

  while (connected) {
    FD_ZERO(&read_fds);
    maxfd = set_fds(&read_fds);

    if (progress) {
      if ((num_servers_total <= 1) && ((n_qw_masters > 0) ||
		       (n_q2_masters > 0)))
        ;
      else
        display_progress();
    }
    get_next_timeout(&timeout);
    rc = select(maxfd+1, &read_fds, NULL, NULL, &timeout);

    if (rc == 0) {
      if (run_timeout && difftime(time(0), start_time) >= run_timeout) {
        FREE(files);
        FREE(qw_masters);
        FREE(qw_servers);
        FREE(q2_servers);
        FREE(h2_servers);
        FREE(q2_masters);
        return 0;
      }
      send_packets();
      bind_sockets();
      continue;
    }
    if (rc == SOCKET_ERROR) {
      perror("select");
      error_type = -1;
      goto errorfree;
    }

    gettimeofday(&packet_recv_time, NULL);
    for (server = servers; server != NULL; server = server->next) {
      if (server->fd == -1)
        continue;
      if (!FD_ISSET(server->fd, &read_fds))
        continue;
      if ((pktlen = recv(server->fd, pkt, sizeof(pkt_data)-1, 0)) ==
          SOCKET_ERROR) {

        if (connection_refused()) {
          server->server_name = DOWN;
          close(server->fd);
          server->fd = -1;
          num_servers_down++;
          if (!up_servers_only) {
            if ((server->type == QW_SERVER || server->type == Q2_SERVER) &&
	(!print_servers_anyway && just_num_players))
              ;
            else
              display_server(server);
          }
          connected--;
        }
        continue;
      }

      if (pktlen != (sizeof(pkt_data)-1))
        /* because q2 and q packets don't end with '\0'! */
        pkt[pktlen] = '\0';

      if (deal_with_packet(server, pkt, pktlen) <= -2) {
        error_type = -1;
        goto errorfree;
      }
    }
    if (run_timeout && difftime(time(0), start_time) >= run_timeout) {
      FREE(files);
      FREE(qw_masters);
      FREE(qw_servers);
      FREE(q2_servers);
      FREE(h2_servers);
      FREE(q2_masters);
      freeQserver(servers);
      return 0;
    }
    if (!connected)
      bind_sockets();
  }

  if (progress) {
    if ((num_servers_total <= 1) && ((n_qw_masters > 0) ||
		     (n_q2_masters > 0)))
      ;
    else
      display_progress();
    printf("\n");
  }

  if (just_num_players) {
    if (just_num_servers)
        printf("%d players\n", total_num_players);
    else
      display_qwmaster_players();
  }

  FREE(files);
  FREE(qw_masters);
  FREE(qw_servers);
  FREE(q2_servers);
  FREE(q2_masters);
  FREE(h2_servers);
  freeQserver(servers);
#ifdef WIN32
  WSACleanup();
#endif
  return 0;

 errorfree:
  FREE(files);
  FREE(qw_masters);
  FREE(qw_servers);
  FREE(q2_servers);
  FREE(q2_masters);
  FREE(h2_servers);
  freeQserver(servers);
#ifdef WIN32
  WSACleanup();
#endif
  if (error_type == -2) /* memory error */
    fprintf(stderr, "Not enough memory or disk space!\n");
  if (error_type == -3) /* some timeout error */
    fprintf(stderr, "Something timed out....\n");
  if (error_type == 1 || error_type == 2) { /* other error */
    fprintf(stderr, errtemp);
    strcpy(errtemp,"");
  }
  if (error_type == 0 || error_type == 2) /* usage error */
    usage(errtemp, argv);
  if (error_type < 0)
    return(-1);
  return(1);
}

int add_file(char *filename) {
  FILE *file;
  char name[200];
  int type;
  int rc;

  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 1;
  }
  for ( ; fscanf(file, "%s", name) == 1; current_fileline++) {
    if (strcmp(name, "QS") == 0)
      type = Q_SERVER;
    else if (strcmp(name, "H2S") == 0)
      type = H2_SERVER;
    else if (strcmp(name, "QW") == 0)
      type = QW_SERVER;
    else if (strcmp(name, "Q2") == 0)
      type = Q2_SERVER;
    else if (strcmp(name, "QWM") == 0)
      type = QW_MASTER;
    else if (strcmp(name, "Q2M") == 0)
      type = Q2_MASTER;
    else {
      if ((rc = add_qserver(name, (hexen2)?H2_SERVER:Q_SERVER)) <= -2) {
        fclose(file);
        return rc;
      }
      continue;
    }
    if (fscanf(file, "%s", name) == 1) {
      if ((rc = add_qserver(name, type)) == -2) {
        fclose(file);
        return rc;
      }
    }
  }

  if (file != stdin)
    fclose(file);

  current_fileline = 0;
  return 0;
}

void print_file_location(void) {
  if (current_fileline != 0)
    fprintf(stderr, "%s:%d: ", current_filename?current_filename:"<stdin>",
            current_fileline);
}

int add_qserver(char *arg, int type) {
  struct hostent *ent = NULL;
  struct hostent temp_ent;
  struct hostent *name_ent = NULL;
  qserver *server;
  int i, nf;
  unsigned short port;
  char **a, *colon, *arg_copy = (char *) 0, *s;
  char *h_addr_list[MAXIP];
  char *uid = (char *) 0, *password = (char *) 0;
  unsigned long ipaddr;

  if (run_timeout && difftime(time(0), start_time) >= run_timeout)
    return -2;

  if (type == Q_SERVER)
    port = DEFAULT_PORT;
  else if (type == H2_SERVER)
    port = HEXEN2_DEFAULT_PORT;
  else if (type == QW_SERVER)
    port = QW_DEFAULT_PORT;
  else if (type == QW_MASTER)
    port = QW_MASTER_DEFAULT_PORT;
  else if (type == Q2_MASTER)
    port = Q2_MASTER_DEFAULT_PORT;
  else if (type == Q2_SERVER)
    port = QUAKE2_DEFAULT_PORT;
  else
    port = DEFAULT_PORT;

  arg_copy = (char *) malloc(sizeof(char) * (strlen(arg)+1));
  if (arg_copy == ((char *) 0))
    return -2;
  strcpy(arg_copy, arg);

  colon = arg;
  for (nf = 1; *colon && (colon = strchr(colon, ':')) != NULL; nf++)
    *colon++ = '\0';

  if (nf > 1) {
    s = arg + strlen(arg)+1;
    sscanf(s, "%hu", &port);
    s += strlen(s)+1;
    if (nf > 2) {
      arg_copy[s-arg-1] = '\0';
      uid = (char *) malloc(sizeof(char) * (strlen(s) + 1));
      if (uid  == ((char *) 0)) {
        FREE(arg_copy);
        return -3;
      }
      strcpy(uid, s);
      s += strlen(s)+1;
      if (nf > 3) {
        password = (char *) malloc(sizeof(char) * (strlen(s) + 1));
        if (password == ((char *) 0)) {
          FREE(arg_copy);
          FREE(uid);
          return -2;
        }
        strcpy(password, s);
        s += strlen(s)+1;
      }
    }
  }
  if (arg_copy[strlen(arg_copy)-1] == ':')
    arg_copy[strlen(arg_copy)-1] = '\0';

  ipaddr = inet_addr(arg);
  if (ipaddr == INADDR_NONE)
    ent = gethostbyname(arg);

  if (hostname_lookup && ipaddr != INADDR_NONE)
    name_ent = gethostbyaddr((char *)&ipaddr, sizeof(ipaddr), AF_INET);

  if (!hostname_lookup && ipaddr != INADDR_NONE)
    ent = gethostbyname(arg);

  if (ent == NULL && ipaddr != INADDR_NONE) {
    /* Maybe gethostbyname() doesn't parse dotted IP addresses,
     * try building a hostent by hand.
     */
    if (ipaddr == INADDR_NONE) {
      print_file_location();
      fprintf(stderr, "%s: %s\n", arg, strherror(h_errno));
      return -1;
    }
    ent = &temp_ent;
    ent->h_name = arg;
    ent->h_aliases = NULL;
    ent->h_addrtype = 2;
    ent->h_length = 4;
    h_addr_list[0] = (char *) &ipaddr;
    h_addr_list[1] = NULL;
    ent->h_addr_list = h_addr_list;
  }
  else if (ent == NULL) {
    print_file_location();
    fprintf(stderr, "%s: %s\n", arg, strherror(h_errno));
    return -1;
  }

  server = (qserver *) calloc(1, sizeof(qserver));
  if (server == ((qserver *) 0)) {
    FREE(arg_copy);
    if (uid != ((char *) 0))
      FREE(uid);
    return -2;
  }
  server->host_name = (char *) malloc(sizeof(char) *
		      (strlen((name_ent) ? name_ent->h_name :
		              ent->h_name) + 1));
  if (server->host_name == ((char *) 0)) {
    FREE(arg_copy);
    if (uid != ((char *) 0))
      FREE(uid);
    FREE(server);
    return -2;
  }
  strcpy(server->host_name, (name_ent) ? name_ent->h_name : ent->h_name);

  server->arg = arg_copy;
  for (a = ent->h_addr_list, i= 0; *a != NULL && i < MAXIP; a++, i++) {
    memcpy(&server->ip[i].s_addr, *a, sizeof(server->ip[i].s_addr));
  }
  server->port = port;
  server->type = type;
  init_qserver(server);

  server->next = servers;
  servers = server;

  server_types |= type;

  return 0;
}

#ifdef _ISUNIX
static jmp_buf env_alrm;

static void interrupt_gethost(int signo) {
  longjmp(env_alrm, 1);
}
#endif

int add_qserver_byaddr(unsigned long ipaddr,
	       unsigned short port, int type, int num) {
  char *temparg;  
  char arg[36];
  qserver *server;
  struct hostent *name_ent = (struct hostent *) 0;

  if (run_timeout && difftime(time(0), start_time) >= run_timeout)
    return -3;

  server = (qserver *) calloc(1, sizeof(qserver));
  if (server == ((qserver *) 0))
    return -2;

  server->ip[0].s_addr = htonl(ipaddr);

  temparg = inet_ntoa(server->ip[0]);
  if (temparg == ((char *) 0)) {
    FREE(server);
    return -2;
  }

  sprintf(arg, "%s:%hu", temparg, port);
  server->arg = (char *) malloc(sizeof(char) * (strlen(arg) + 1));
  if (server->arg == ((char *) 0)) {
    FREE(server);
    return -2;
  }
  strcpy(server->arg, arg);

  if (hostname_lookup) {
#ifdef WIN32
    HANDLE handle;
    HWND hwnd;
    static char hostInfo[MAXGETHOSTSTRUCT];
    time_t currTime;
    
    strcpy(hostInfo, "");
    hwnd = GetFocus();
    
    handle = WSAAsyncGetHostByAddr(hwnd, WM_USER+1, (char *)&server->ip[0],
		   sizeof(struct in_addr), AF_INET, hostInfo,
		   MAXGETHOSTSTRUCT);
    
    currTime = time(0);
    while ((difftime(time(0), currTime)) < (double) DEFAULT_HOST_LOOKUP_TIME) {
      if (strcmp(hostInfo, ""))
        break;
    }
    
    if (!strcmp(hostInfo, ""))
      WSACancelAsyncRequest(handle);
    else
      name_ent = (struct hostent *)hostInfo;
#elif defined(_ISUNIX)
    Sigfunc *sigfunc;

    if ( (sigfunc = signal(SIGALRM, interrupt_gethost)) != SIG_ERR) {
      if (setjmp(env_alrm) == 0) { /* is non-zero if got alarm */
        if (alarm(DEFAULT_HOST_LOOKUP_TIME) != 0)
          fprintf(stderr, "An alarm was already set.\n");
      
        name_ent = gethostbyaddr((char *)&server->ip[0],
		 sizeof(struct in_addr), AF_INET);
        alarm(0); /* turn off alarm */
        signal(SIGALRM, sigfunc); /* restore previous signal handler */
      }
    }
#else
    name_ent = gethostbyaddr((char *)&server->ip[0],
	             sizeof(struct in_addr), AF_INET);
#endif
    if (progress_host)
      display_progress_got_host(num+1);
  }
    
  if (name_ent != ((struct hostent *) 0))
    if (strlen(name_ent->h_name) < 5)
      name_ent = (struct hostent *) 0;
  
  server->host_name = (char *) malloc (sizeof(char) *
		       strlen(name_ent !=
		              ((struct hostent *) 0) ?
		              name_ent->h_name : arg) + 1);
  if (server->host_name == ((char *) 0)) {
    FREE(server->arg);
    FREE(server);
    return -2;
  }
  strcpy(server->host_name, name_ent ? name_ent->h_name : arg);

  server->port = port;
  server->type = type;
  init_qserver(server);

  server->next = servers;
  servers = server;
  return 0;
}

void init_qserver(qserver *server) {
  /*server->server_name = NULL;
    server->map_name = NULL;
    server->num_players = 0; */
  server->fd = INVALID_SOCKET;
  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->error = NULL; */

  server->next_rule = (get_server_rules) ? "" : NO_SERVER_RULES;
  server->next_player_info = (get_player_info) ? 0 : NO_PLAYER_INFO;

  /*server->n_player_info = 0;
  server->players = NULL;
  server->n_rules = 0;
  server->rules = NULL;
  server->missing_rules = 0; */

  num_servers_total++;
}

/* Functions for binding sockets to Quake servers
 */
int bind_qserver(qserver *server) {
  struct sockaddr_in addr;

  if ((server->fd = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET) {
    if (sockerr() == EMFILE) {
      server->fd = -1;
      return -1;
    }
    perror("socket" );
    server->server_name = SYSERROR;
    return -1;
  }

  addr.sin_family = AF_INET;
  addr.sin_port = htons(0);
  addr.sin_addr.s_addr = htonl(INADDR_ANY);
  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;
  }

  addr.sin_family = AF_INET;
  addr.sin_port = htons(server->port);
  addr.sin_addr = server->ip[0];
  memset(&(addr.sin_zero), 0, sizeof(addr.sin_zero) );

  if (connect(server->fd, (struct sockaddr *)&addr, sizeof(addr)) ==
      SOCKET_ERROR) {
    perror("connect");
    server->server_name = SYSERROR;
    close(server->fd);
    server->fd = -1;
    return -1;
  }
  return 0;
}

void bind_sockets() {
  qserver *server = servers;

  while (server != NULL) {
    if (server->server_name == NULL && server->fd == -1 &&
        connected < max_simultaneous) {
      if (bind_qserver(server) == 0) {
        send_server_request_packet(server);
        connected++;
      }
    }
    server = server->next;
  }
}


/* Functions for sending packets
 */
void send_packets() {
  qserver *server = servers;
  struct timeval now;

  gettimeofday(&now, NULL);

  for ( ; server != NULL; server = server->next) {
    if (server->fd == -1)
      continue;
    if (server->server_name == NULL) {
      if (server->retry1 != n_retries &&
          time_delta(&now, &server->packet_time1) <
          (retry_interval*(n_retries-server->retry1+1)))
        continue;
      if (!server->retry1) {
        if (showpacketon)
          printf("\n%s server %s did return anything.\n", (server->type == QW_SERVER) ?
	 "QW" : "Q2", server->arg);
        cleanup_qserver(server, 0);
        continue;
      }
      send_server_request_packet(server);
      continue;
    }
    if (server->next_rule != NO_SERVER_RULES) {
      if (server->retry1 != n_retries &&
          time_delta(&now, &server->packet_time1) <
          (retry_interval*(n_retries-server->retry1+1)))
        continue;
      if (!server->retry1) {
        server->next_rule = NO_SERVER_RULES;
        server->missing_rules = 1;
        cleanup_qserver(server, 0);
        continue;
      }
      send_rule_request_packet(server);
    }
    if (server->next_player_info < server->num_players) {
      if (server->retry2 != n_retries &&
          time_delta(&now, &server->packet_time2) <
          (retry_interval*(n_retries-server->retry2+1)))
        continue;
      if (!server->retry2) {
        server->next_player_info++;
        if (server->next_player_info >= server->num_players) {
          cleanup_qserver(server, 0);
          continue;
        }
        server->retry2 = n_retries;
      }
      send_player_request_packet(server);
    }
  }
}

/* Send the first packet to a server
 */
int send_server_request_packet(qserver *server) {
  if (server->type == Q_SERVER || server->type == H2_SERVER)
    return send_qserver_request_packet(server);
  else if (server->type == QW_SERVER || server->type == Q2_SERVER)
    return send_qwserver_request_packet(server);
  else if (server->type == QW_MASTER || server->type == Q2_MASTER)
    return send_qwmaster_request_packet(server);
  else
    return -1;
}

/* First packet for a normal Quake server
 */
int send_qserver_request_packet(qserver *server) {
  int rc;
  if (server->type == H2_SERVER)
    rc = send(server->fd, (const char *)&h2serverinfo,
              sizeof(h2serverinfo), 0);
  else
    rc = send(server->fd, (const char *)&qserverinfo,
              sizeof(qserverinfo), 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++;
  return 0;
}

/* First packet for a QuakeWorld server
 */
int send_qwserver_request_packet(qserver *server) {
  int rc;
  rc = send(server->fd, (const char *)&qwserverstatus,
            sizeof(qwserverstatus), 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++;
  return 0;
}

/* First packet for a QuakeWorld master server
 */
int send_qwmaster_request_packet(qserver *server) {
  int rc;
  char pkt[QW_REQUEST_LENGTH];

  memset(pkt, 0, sizeof(pkt));
  sprintf(pkt, "%c%c", QW_GET_SERVERS, QW_NEWLINE);

  rc = send(server->fd, pkt, sizeof(pkt), 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++;
  return 0;
}

int send_rule_request_packet(qserver *server) {
  struct qpacket request = {Q_FLAG1,Q_FLAG2,0,Q_CCREQ_RULE_INFO,""};
  int rc, len;

  strcpy((char *)request.data, server->next_rule);
  len = Q_HEADER_SIZE + strlen((char *)request.data) + 1;
  request.length = htons((unsigned short)len);
#if (defined(aix) || defined(linux))
  rc = send(server->fd, (const char *)&request, (size_t) len, 0);
#else
  rc = send(server->fd, (const char *)&request, len, 0);
#endif
  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++;
  return 0;
}

int send_player_request_packet(qserver *server) {
  int rc;

  qplayerinfo.player_number = (unsigned char) server->next_player_info;
  rc = send(server->fd, (const char *)&qplayerinfo, sizeof(qplayerinfo), 0);
  if (rc == SOCKET_ERROR)
    perror("send");
  if (server->retry2 == n_retries) {
    gettimeofday(&server->packet_time2, NULL);
    server->n_requests++;
  }
  else
    server->n_retries++;
  server->retry2--;
  server->n_packets++;
  return 0;
}

/* Functions for figuring timeouts and when to give up
 */
void cleanup_qserver(qserver *server, int force) {
  int close_it = force;
  if (server->server_name == NULL) {
    server->server_name = TIMEOUT;
    close_it = 1;
    num_servers_timed_out++;
  }
  else if (server->type == QW_SERVER || server->type == QW_MASTER ||
           server->type == Q2_SERVER || server->type == Q2_MASTER)
    close_it = 1;
  else if (server->next_rule == NO_SERVER_RULES &&
           server->next_player_info >= server->num_players)
    close_it = 1;

  if (close_it) {
    close(server->fd);
    server->fd = -1;
    connected--;
    if (server->server_name != TIMEOUT)
      num_servers_returned++;

    if (!showpacketon) {
      if ((server->type == QW_MASTER || server->type == Q2_MASTER) &&
          (just_num_servers || !just_num_players))
        display_server(server);
      else if ((server->type == QW_SERVER || server->type == Q2_SERVER) &&
               (print_servers_anyway || !just_num_players))
        display_server(server);
      else if (server->type == Q_SERVER || server->type == H2_SERVER)
        display_server(server);

      if (just_num_players)
        total_num_players += server->num_players;
    }
  }
}

void get_next_timeout(struct timeval *timeout) {
  qserver *server = servers;
  struct timeval now;
  int diff1, diff2, diff, smallest = retry_interval;
  gettimeofday(&now, NULL);
  for ( ; server != NULL; server = server->next) {
    if (server->fd == -1)
      continue;
    diff2 = 0xffff;
    diff1 = 0xffff;
    if (server->server_name == NULL)
      diff1 = retry_interval*(n_retries-server->retry1+1) -
        time_delta(&now, &server->packet_time1);
    else {
      if (server->next_rule != NO_SERVER_RULES)
        diff1 = retry_interval*(n_retries-server->retry1+1) -
          time_delta(&now, &server->packet_time1);
      if (server->next_player_info < server->num_players)
        diff2 = retry_interval*(n_retries-server->retry2+1) -
          time_delta(&now, &server->packet_time2);
    }
    diff = (diff1<diff2)?diff1:diff2;
    if (diff < smallest)
      smallest = diff;
  }
  if (smallest < 10)
    smallest = 10;
  timeout->tv_sec = smallest / 1000;
  timeout->tv_usec = (smallest % 1000) * 1000;
}

int set_fds(fd_set *fds) {
  qserver *server = servers;
  int maxfd = 1;
  while (server != NULL) {
    if (server->fd != -1) {
      FD_SET(server->fd, fds);
      if (server->fd > maxfd)
        maxfd = server->fd;
    }
    server = server->next;
  }
  return maxfd;
}


/* Functions for handling response packets
 */
int deal_with_packet(qserver *server, char *rawpkt, int pktlen) {
  
  if (server->type == QW_SERVER || server->type == Q2_SERVER)
    return(deal_with_qw_packet(server, rawpkt, pktlen));
  else if (server->type == QW_MASTER || server->type == Q2_MASTER)
    return(deal_with_qwmaster_packet(server, rawpkt, pktlen));
  else if (server->type == Q_SERVER || server->type == H2_SERVER)
    return(deal_with_q_packet(server, rawpkt, pktlen));
  else /* invalid server type */
    return 1;
}

/* Packet from normal Quake server
 */
int deal_with_q_packet(qserver *server, char *rawpkt, int pktlen) {
  struct qpacket pkt;
  int rc = 0;

  if (pktlen >= (sizeof(struct qpacket) - QPACKET_DATA_SIZE)) {
    /* make sure rawpkt is large enough */
    pkt.flag1 = (unsigned char) rawpkt[0];
    pkt.flag2 = (unsigned char) rawpkt[1];
    pkt.length = (unsigned short) ((((unsigned short) rawpkt[2]) << 8) + (unsigned short) rawpkt[3]);
    pkt.op_code = rawpkt[4];
    if (pktlen > (sizeof(struct qpacket) - QPACKET_DATA_SIZE))
      memcpy(pkt.data, rawpkt+5, (size_t) (pktlen-5));
    else
      /* just in case the crap that's already there causes problems */
      pkt.data[0] = '\0';
  }
  else
    return -2;
  
  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 -1;
  }

  switch (pkt.op_code) {
  case Q_CCREP_ACCEPT:
  case Q_CCREP_REJECT:
    return 0;
  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_SIZE);
    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_SIZE);
    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_SIZE);
    break;
  case Q_CCREQ_CONNECT:
  case Q_CCREQ_SERVER_INFO:
  case Q_CCREQ_PLAYER_INFO:
  case Q_CCREQ_RULE_INFO:
  default:
    return rc;
  }

  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);
  return rc;
}

/* Packet from QuakeWorld server
 */
int deal_with_qw_packet(qserver *server, char *rawpkt, int pktlen) {
  char *command;

  server->ping_total += time_delta(&packet_recv_time,
		   &server->packet_time1);

  if (pktlen < 5)
    return -1;
  if (rawpkt[0] != '\377' || rawpkt[1] != '\377' ||
      rawpkt[2] != '\377' || rawpkt[3] != '\377') {
    fprintf(stderr, "Odd packet from %s server, processing anyway ...\n",
            (server->type == QW_SERVER) ? "QW" : "Q2");
    print_packet(rawpkt, pktlen);
  }
  if (rawpkt[4] == 'n') {
    server->type = QW_SERVER;
    if (showpacketon) {
      if ((pktlen-5) == 0) {
        if ( (command = getNthWordInString(qwserverstatus.command, 3, 1)) != ((char *) 0))
          printf("Rcon command \"%s\" only returns an empty string.\n", command);
        else
          printf("\"%s\" only returns an empty string.\n", qwserverstatus.command);
      }
      else {
        if (strlen(rawpkt+5) < (size_t) (pktlen-5-1))
          /* last character in QW packet is '\0' (not in Q2) */
          print_packet(rawpkt+5, pktlen-5);
        else
          printf("%s\n", rawpkt+5);
      }
      cleanup_qserver(server, FORCE);
      return 0;
    }
    else
      return(deal_with_q1qw_packet(server, rawpkt, pktlen));
  }
  else if (strncmp(&rawpkt[4], "print\n", 6) == 0) {
    server->type = Q2_SERVER;
    if (showpacketon) {
      if ((pktlen-10) == 0) {
        if ( (command = getNthWordInString(qwserverstatus.command, 3, 1)) != ((char *) 0))
          printf("Rcon command \"%s\" only returns an empty string.\n", command);
        else
          printf("\"%s\" only returns an empty string.\n", qwserverstatus.command);
      }
      else {
        if (strlen(rawpkt+10) < (size_t) (pktlen-10))
          print_packet(rawpkt+10, pktlen-10);
        else
          printf("%s\n", rawpkt+10);
      }
      cleanup_qserver(server, FORCE);
      return 0;
    }
    else
      return(deal_with_q1qw_packet(server, rawpkt, pktlen));
  }
  else {
    fprintf(stderr, "Odd packet from %s server, skipping ...\n",
            (server->type == QW_SERVER) ? "QW" : "Q2");
    if (strlen(rawpkt+5) < (size_t) (pktlen-5))
      print_packet(rawpkt+5, pktlen-5);
    else
      printf("%s\n", rawpkt+5);
    cleanup_qserver(server, 0);
    return 1;
  }
}

int deal_with_q1qw_packet(qserver *server, char *rawpkt, int pktlen) {
  char *key, *value, *end;
  rule *rules;
  player *players;
  int len, rc, odd = 0, complete = 0, num;
  char *pkt;

  if (server->type == QW_SERVER)
    pkt = &rawpkt[5];
  else
    pkt = &rawpkt[10];

  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');
      if (((size_t) (end-pkt)) < (size_t) 1)
        break; /* sometimes packet gets messed up */
      value = (char *) malloc(sizeof(char) * (end-pkt+1));
      if (value == ((char *) 0))
        return -2;
      memcpy(value, pkt, (size_t) (end-pkt));
      value[end-pkt] = '\0';
      pkt = end;
      if (strcmp(key, "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)
        server->max_players = atoi(value);
      else {
        rules = (rule *) malloc(sizeof(rule));
        if (rules == ((rule *) 0)) {
          FREE(value);
          return -2;
        }
        rules->name = (char *) malloc (sizeof(char) * (strlen(key) + 1));
        if (rules->name == ((char *) 0)) {
          FREE(value);
          FREE(rules);
          return -2;
        }
        strcpy(rules->name, key);
        rules->value = value;
        rules->next = server->rules;
        server->rules = rules;
        server->n_rules++;
      }
    }
    else if (*pkt == '\n') {
      pkt++;
      if (*pkt == '\0')
        break;
      players = (player *) calloc(1, sizeof(player));
      if (players == ((player *) 0))
        return -2;
      if (server->type == QW_SERVER) {
        rc = sscanf(pkt, "%d %d %d %d %n", &players->number, &players->frags,
	    &players->connect_time, &players->ping, &len);
        num = 4;
      }
      else if (server->type == Q2_SERVER) {
        rc = sscanf(pkt, "%d %d %n", &players->frags, &players->ping, &len);
        num = 2;
      }
      if (rc != num) {
        char *nl;	/* assume it's an error packet */
        FREE(players);
        server->error = (char *) malloc(sizeof(char) * (pktlen+1));
        if (server->error == ((char *) 0))
          return -2;
        nl = strchr(pkt, '\n');
        if (nl != NULL) {
          strncpy(server->error, pkt, (size_t) (nl-pkt));
          server->error[nl-pkt] = '\0';
        }
        else
          strcpy(server->error, pkt);
        server->server_name = SERVERERROR;
        complete = 1;
        break;
      }

      if (server->type == QW_SERVER)
        players->connect_time *= 60;

      pkt += len;

      if (*pkt != '"') break;
      pkt++;
      end = strchr(pkt, '"');
      if (end == NULL) break;
      players->name = (char *) malloc(sizeof(char) * (end-pkt+1));
      if (players->name == ((char *) 0)) {
        FREE(players);
        return -2;
      }
      memcpy(players->name, pkt, (size_t) (end-pkt));
      players->name[end-pkt] = '\0';

      if (server->type == QW_SERVER) {
        pkt = end+2;
      
        if (*pkt != '"') break;
        pkt++;
        end = strchr(pkt, '"');
        if (end == NULL) break;
        players->skin = (char *) malloc(sizeof(char) * (end-pkt+1));
        if (players->skin == ((char *) 0)) {
          FREE(players->name);
          FREE(players);
          return -2;
        }
        memcpy(players->skin, pkt, (size_t) (end-pkt));
        players->skin[end-pkt] = '\0';
        pkt = end+2;
        
        sscanf(pkt, "%d %d%n", &players->shirt_color, &players->pants_color,
               &len);
        pkt += len;
      }
      else if (server->type == Q2_SERVER) {
        pkt = end+1;

        players->skin = NOSKIN;
        players->shirt_color = -1;
        players->pants_color = -1;
      }
      
      players->next = server->players;
      server->players = players;
      server->num_players++;
    }
    else
      pkt++;
    complete = 1;
  }

  if (!odd && !complete)
    print_packet(rawpkt, pktlen);

  cleanup_qserver(server, 0);
  return 0;
}

/* Packet from QuakeWorld master server
 */
int deal_with_qwmaster_packet(qserver *server,
	              char *rawpkt, int pktlen) {
  unsigned long ipaddr;
  unsigned short port;
  int i, offset = 2;
  int rc;

  server->ping_total += time_delta(&packet_recv_time,
		   &server->packet_time1);

  if (rawpkt[0] == QW_NACK) {
    server->error = (char *) malloc(sizeof(char) * (strlen(&rawpkt[2]) + 1));
    if (server->error == ((char *) 0))
      return -2;
    strcpy(server->error, &rawpkt[2]);
    server->server_name = SERVERERROR;
    cleanup_qserver(server, 1);
    return 0;
  }

  if (pktlen > 4) {
    if (rawpkt[0] == '\xff' || rawpkt[1] == '\xff' ||
        rawpkt[2] == '\xff' || rawpkt[3] == '\xff') {
      rawpkt += 4;	/* QW 1.5 */
      pktlen -= 4;
      offset = 0;
    }
  }
  else
    return 1;

  if (!(rawpkt[0] == QW_SERVERS  && rawpkt[1] == QW_NEWLINE)) {
    fprintf(stderr, "Odd packet from QW master, processing anyway ...\n");
    print_packet(rawpkt, pktlen);
  }

  rawpkt[pktlen] = '\0';
  server->n_servers = 0;
  num_servers_total_theoretical = (pktlen-offset-2)/6;

  for (i = 2; i < pktlen-offset; i += 6) {
    memcpy(&ipaddr, &rawpkt[i], 4);
    memcpy(&port, &rawpkt[i+4], 2);
    ipaddr = ntohl(ipaddr);
    port = ntohs(port);
    if (!just_num_servers || just_num_players)
      if ((rc = add_qserver_byaddr(ipaddr, port, QW_SERVER, server->n_servers))
          <= -2)
        return rc;
    server->n_servers++;
  }
  bind_sockets();
  server->server_name = MASTER;

  cleanup_qserver(server, 0);
  return 0;
}

int server_info_packet(qserver *server,
	       struct qpacket *pkt, int datalen) {
  int off = 0;

  /* ignore duplicate packets */
  if (server->server_name != NULL)
    return 0;

  server->address = (char *) malloc(sizeof(char) *
		    (strlen((char *)&pkt->data[off]) + 1));
  if (server->address == ((char *) 0))
    return -2;
  strcpy(server->address, (char *)&pkt->data[off]);
  off += strlen(server->address) + 1;
  if (off >= datalen)
    return -1;

  server->server_name = (char *) malloc(sizeof(char) *
		        (strlen((char *)&pkt->data[off]) + 1));
  if (server->server_name == ((char *) 0))
    return -2;
  strcpy(server->server_name, (char *)&pkt->data[off]);;
  off += strlen(server->server_name) + 1;
  if (off >= datalen)
    return -1;

  server->map_name = (char *) malloc(sizeof(char) *
		     (strlen((char *)&pkt->data[off]) + 1));
  if (server->map_name == ((char *) 0))
    return -2;
  strcpy(server->map_name, (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(qserver *server,
	       struct qpacket *pkt, int datalen) {
  char *name, *address;
  int off, colors, frags, connect_time, player_number;
  player *players, *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;

  players = (player *) malloc(sizeof(player));
  if (players == ((player *) 0))
    return -2;
  players->number = player_number;
  players->name = (char *) malloc(sizeof(char) * (strlen(name) + 1));
  if (players->name == ((char *) 0)) {
    FREE(players);
    return -2;
  }
  strcpy(players->name, name);

  players->address = (char *) malloc (sizeof(char) * (strlen(address) + 1));
  if (players->address == ((char *) 0)) {
    FREE(players->name);
    FREE(players);
    return -2;
  }
  strcpy(players->address, address);
  
  players->connect_time = connect_time;
  players->frags = frags;
  players->shirt_color = colors>>4;
  players->pants_color = colors&0xf;
  players->next = NULL;

  if (last == NULL)
    server->players = players;
  else
    last->next = players;

  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(qserver *server,
	     struct qpacket *pkt, int datalen) {
  int off = 0;
  rule *rules, *last;
  char *name, *value;

  /* Straggler packet after we've already given up fetching rules */
  if (server->next_rule == NO_SERVER_RULES)
    return 0;

  if (ntohs(pkt->length) == Q_HEADER_SIZE) {
    server->next_rule = NO_SERVER_RULES;
    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;

  rules = (rule *) malloc(sizeof(rule));
  if (rules == ((rule *) 0))
    return -2;
  rules->name = (char *) malloc(sizeof(char) * (strlen(name) + 1));
  if (rules->name == ((char *) 0)) {
    FREE(rules);
    return -2;
  }
  strcpy(rules->name, name);
  rules->value = (char *) malloc(sizeof(char) * (strlen(value) + 1));
  if (rules->value == ((char *) 0)) {
    FREE(rules->name);
    FREE(rules);
    return -2;
  }
  strcpy(rules->value, value);
  rules->next = NULL;

  if (last == NULL)
    server->rules = rules;
  else
    last->next = rules;

  server->n_rules++;
  server->next_rule = rules->name;
  server->retry1 = n_retries;
  send_rule_request_packet(server);

  return 0;
}


/* Misc utility functions
 */

char *escape(unsigned char *string) {
  static char _q[256];

  char *q = _q;

  if (string == NULL) {
    q[0] = '?';
    q[1] = '\0';
    return _q;
  }

  if (hex_player_names) {
    for ( ; *string; string++, q += 2)
      sprintf(q, "%02x", *string);
    *q = '\0';
    return _q;
  }

  for ( ; *string; string++) {
    if (isprint(*string)) {
      *q++ = *string;
      continue;
    }

    if (*string >= 0xa0)
      *q++ = (char) ((int) *string & 0x7f);
    else if (*string >= 0x92 && *string < 0x9c)
      *q++ = (char) ('0' + (*string - '\x92'));
    else if (*string >= 0x12 && *string < 0x1c)
      *q++ = (char) ('0' + (*string - '\x12'));
    else if (*string == 0x90 || *string == 0x10)
      *q++ = '[';
    else if (*string == 0x91 || *string == 0x11)
      *q++ = ']';
    else if (*string == 0xa || *string == 0xc || *string == 0xd)
      *q++ = ']';
  }
  *q = '\0';
  return _q;
}

int is_default_rule(rule *rules) {
  if (strcmp(rules->name, "sv_maxspeed") == 0)
    return strcmp(rules->value, Q_DEFAULT_SV_MAXSPEED) == 0;
  if (strcmp(rules->name, "sv_friction") == 0)
    return strcmp(rules->value, Q_DEFAULT_SV_FRICTION) == 0;
  if (strcmp(rules->name, "sv_gravity") == 0)
    return strcmp(rules->value, Q_DEFAULT_SV_GRAVITY) == 0;
  if (strcmp(rules->name, "noexit") == 0)
    return strcmp(rules->value, Q_DEFAULT_NOEXIT) == 0;
  if (strcmp(rules->name, "teamplay") == 0)
    return strcmp(rules->value, Q_DEFAULT_TEAMPLAY) == 0;
  if (strcmp(rules->name, "timelimit") == 0)
    return strcmp(rules->value, Q_DEFAULT_TIMELIMIT) == 0;
  if (strcmp(rules->name, "fraglimit") == 0)
    return strcmp(rules->value, Q_DEFAULT_FRAGLIMIT) == 0;
  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(void) {
#ifdef _ISUNIX
  return errno == ECONNREFUSED;
#elif defined(WIN32)
  return WSAGetLastError() == WSAECONNABORTED;
#else
  return 1; /* OS/2... who cares?! */
#endif
}

void print_packet(char *buf, int buflen) {
  int i;
  for (i = 0; i < buflen; i++) {
    if (isprint(buf[i])) fprintf(stderr, "%c", buf[i]);
    else fprintf(stderr, " %02x ", (unsigned char)buf[i]);
  }
  fprintf(stderr,"\n");
}

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 */
  };

  if (color_names)
    return colors[color&0xf];
  else
    return (char *)color;
}

char *play_time(int seconds, int show_seconds) {
  static char time_string[24];
  if (time_format == CLOCK_TIME) {
    time_string[0] = '\0';
    if (seconds/3600)
      sprintf(time_string, "%2dh", seconds/3600);
    else
      strcat(time_string, "   ");
    if ((seconds%3600)/60 || seconds/3600)
      sprintf(time_string+strlen(time_string), "%2dm",
              (seconds%3600)/60);
    else if (!show_seconds)
      sprintf(time_string+strlen(time_string), " 0m");
    else
      strcat(time_string, "   ");
    if (show_seconds)
      sprintf(time_string+strlen(time_string), "%2ds", 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;
}

/* 
void add_to_array(char ***array, int *n, char *string) {
  int i;
  if (*array == NULL) {
    *n = 10;
    *array = (char **) calloc(1, sizeof(char *) * *n);
  }
  for (i = 0; i < *n; i++)
    if ((*array)[i] == NULL) {
      (*array)[i] = string;
      return;
    }
  *n *= 2;
  *array = (char **) realloc(*array, sizeof(char *) * *n);
  memset(&(*array)[i], 0, sizeof(char *) * i);
  (*array)[i] = string;
}
*/

int count_bits(int n) {
  int b = 0;
  for ( ; n; n>>=1)
    if (n&1)
      b++;
  return b;
}

char* use_master_file(char *execpath) {
  FILE *master_dat;
  static char master[201];
  char *end, path[PATH_MAX+1], path2[PATH_MAX+1], *tPath, entered[11];
  int n, master_num, length = 0, ret, samedir = 0, not_entered = 0;
  size_t i;
  int num_periods = 0, num_colons = 0;
  QWMasterD *master_list;
  struct stat buf;

  if (execpath != ((char *) 0)) { /* check directory gstat is in for file */
    strcpy(path, execpath);
    end = strrchr(path, SEP_CHAR);
    if (end == ((char *) 0)) { /* first try to get executable's command line */
      if ( (tPath = getCommandLine(path)) == ((char *) 0))
        ret = -1;
      else {
        strcpy(path, tPath);
        end = strrchr(path, SEP_CHAR);
        if (end == ((char *) 0)) /* not sure how this could happen, but.... */
          ret = -1;
        else { /* check if current working directory is the same */
          *end = '\0';
          if ( (getcwd(path2, PATH_MAX)) == ((char *) 0)) {
            fprintf(stderr, "A directory could not be read.\n");
            return "";
          }
          /* standardize cwd if necessary */
          if ((path2[strlen(path2)-1] == SEP_CHAR) && (strlen(path2) != 1))
            path2[strlen(path2)-1] = '\0';
          
          if (!strcmp(path, path2)) {
            samedir = 1; /* cwd is the same as path of executable */
            ret = -1;
          }
          else { /* cwd and path of exec are not the same, check path for master.dat */
            sprintf(end, "%c%s", SEP_CHAR, MASTER_FILE);
            ret = stat(path, &buf);
          }
        }
      }
    }
    else {
      end++;
      strcpy(end, MASTER_FILE);
      ret = stat(path, &buf);
    }
  }

  if (ret == -1) {
    if (stat(MASTER_FILE, &buf) == -1) {
      if ((execpath != ((char *) 0)) && !samedir) {
        end = strrchr(path, SEP_CHAR);
        *end = '\0';
        fprintf(stderr, "%s could not be found in %s\nor the current directory.\n",
	MASTER_FILE, path);
      }
      else
        fprintf(stderr, "%s could not be found.\n", MASTER_FILE);
      return "";
    }
  }

  if (ret == -1)
    /* check current directory for master.dat */
    master_dat = fopen(MASTER_FILE, "r"); 
  else
    master_dat = fopen(path, "r");

  if (master_dat == NULL) {
    if (ret == -1)
      fprintf(stderr, "%s could not be read, most likely because permissions are not set\ncorrectly for it.\n", MASTER_FILE);
    else
      fprintf(stderr, "%s could not be read,\nmost likely because permissions are not set correctly for it.\n", path);
    return "";
  }
  else {
    n = 0;
    while (fgets(master, 200, master_dat) != NULL) {
      if (strlen(master) > 10)
        length++;
    }
    if (length == 0) {
      fprintf(stderr, "Empty %s!\n", MASTER_FILE);
      fclose(master_dat);
      return ((char *) 0);
    }
    
    rewind(master_dat);

    master_list = (QWMasterD *) calloc ((size_t) length, sizeof(QWMasterD));
    if (master_list == ((QWMasterD *) 0)) {
      fprintf(stderr, "Not enough memory or disk space!\n");
      fclose(master_dat);
      return ((char *) 0);
    }
  
    while (fgets(master, 200, master_dat) != NULL) {
      end = strchr(master, '|');
      if (end != ((char *) 0))
        *end= '\0';

      strncpy(master_list[n].ipaddress, master,
              sizeof(master_list[n].ipaddress)-1);
      master_list[n].ipaddress[strlen(master_list[n].ipaddress)] = '\0';

      if (end != ((char *) 0)) {
        end++;
        if (*end != '\0')
          strcpy(master_list[n].name, end);
      }
      /* now inspect IP address to see if it is "correct" */
      if (!isdigit((master_list[n].ipaddress)[0]))
        n--;
      else {
        for (i = 0; i < strlen(master_list[n].ipaddress); i++) {
          if (!isdigit((master_list[n].ipaddress)[i])) {
            if ((master_list[n].ipaddress)[i] == '.') {
              num_periods++;
              if (num_periods > 3) {
	n--;
	break;
              }
              if (!isdigit((master_list[n].ipaddress)[i-1])) {
	n--;
	break;
              }
            }
            else if ((master_list[n].ipaddress)[i] == ':') {
              num_colons++;
              if ((num_periods != 3) || (num_colons > 1)) {
	n--;
	break;
              }
              if (!isdigit((master_list[n].ipaddress)[i-1])) {
	n--;
	break;
              }
            }
            else {
              n--;
              break;
            }
          }
        }
        n++;
      }
      num_periods = 0;
      num_colons = 0;
    }

    if ((n < length) && (n != 0))
      fprintf(stderr, "There are incorrecly formatted entries in %s.\n",
              MASTER_FILE);

    if (n == 0) {
      fprintf(stderr, "All the entries in %s are formatted incorrectly!\n",
              MASTER_FILE);
      free(master_list);
      fclose(master_dat);
      return "";
    }

    length = n;
    
    printf("\n%19s%17s\n-------------------------------------------\n",
           "Master IP", "Name");
    for (n = 0; n < length; n++)
      printf("%3.1i. %-23s%s", n+1, master_list[n].ipaddress,
             master_list[n].name);

    printf("\n\nEnter number corresponding to master server: ");
    scanf("%10s", entered);

    for (i = 0; i < strlen(entered); i++) {
      if (!isdigit(entered[i])) {
        i = 0;
        break;
      }
    }
    if (i != 0) {
      master_num = atoi(entered);
      not_entered = 0;
    }
    else
      not_entered = 1;
    
    while (not_entered || master_num <= 0 || master_num > length) {
      if (not_entered)
        printf("What you entered is not valid!\nEnter number corresponding to master server: ");
      else {
        if (master_num > length)
          printf("Number too high!\nEnter number corresponding to master server: ");
        else
          printf("Number too low!\nEnter number corresponding to master server: ");
      }
      scanf("%10s", entered);
      for (i = 0; i < strlen(entered); i++) {
        if (!isdigit(entered[i])) {
          i = 0;
          break;
        }
      }
      if (i != 0) {
        master_num = atoi(entered);
        not_entered = 0;
      }
      else
        not_entered = 1;
    }

    fclose(master_dat);
    strcpy(master,master_list[master_num-1].ipaddress);
    FREE(master_list);
    return(master);
  }
}

void freeQserver(qserver *server) {
  qserver *tmpServer, *tmpServer2;

  for (tmpServer = server; tmpServer != (qserver *) 0;
       tmpServer = tmpServer2) {
    if (tmpServer->players != ((player *) 0))
      freePlayer(tmpServer->players);
    if (tmpServer->rules != ((rule *) 0))
      freeRule(tmpServer->rules);
    if (tmpServer->arg != ((char *) 0))
      FREE(tmpServer->arg);
    if (tmpServer->host_name != ((char *) 0))
      FREE(tmpServer->host_name);
    if (tmpServer->error != ((char *) 0))
      FREE(tmpServer->error);
    /* server_name is a special case */
    if (tmpServer->server_name != ((char *) 0))
      if ((tmpServer->server_name != DOWN) &&
          (tmpServer->server_name != TIMEOUT) &&
          (tmpServer->server_name != SYSERROR) &&
          (tmpServer->server_name != MASTER) &&
          (tmpServer->server_name != SERVERERROR))
        FREE(tmpServer->server_name);
    if (tmpServer->address != ((char *) 0))
      FREE(tmpServer->address);
    if (tmpServer->map_name != ((char *) 0))
      FREE(tmpServer->map_name);
    /* next->rule never is allocated any memory on its own
       if (tmpServer->next_rule != ((char *) 0))
       FREE(tmpServer->next_rule);
    */
    
    tmpServer2 = tmpServer->next;
    FREE(tmpServer);
  }
}

void freeRule(rule *rules) {
  rule *tempRule, *tempRule2;

  for (tempRule = rules; tempRule != (rule *) 0; tempRule = tempRule2) {
    if (tempRule->value != ((char *) 0))
      FREE(tempRule->value);
    if (tempRule->name != ((char *) 0))
      FREE(tempRule->name);

    tempRule2 = tempRule->next;
    FREE(tempRule);
  }
}

void freePlayer(player *players) {
  player *tempPlayer, *tempPlayer2;

  for (tempPlayer = players; tempPlayer != (player *) 0;
       tempPlayer = tempPlayer2) {
    if (tempPlayer->name != ((char *) 0))
      FREE(tempPlayer->name);
    if (tempPlayer->address != ((char *) 0))
      FREE(tempPlayer->address);
    if (tempPlayer->skin != ((char *) 0))
      if (tempPlayer->skin != NOSKIN)
        FREE(tempPlayer->skin);
	
    tempPlayer2 = tempPlayer->next;
    FREE(tempPlayer);
  }
}

char *getNthWordInString(char *str, int num, int dir) {
  int gotAlphaNum = 0, numWords = 0;
  int i, mlen;

  if (str == ((char *) 0))
    return str;

  mlen = strlen(str)-1;

  if (dir) { /* forward search */
    for (i = 0; i < mlen+1; i++) {
      if ((numWords == (num-1)) && (str[i] != ' '))
        break;
      if (str[i] == ' ') { /* only deal with regular spaces */
        if (gotAlphaNum) {
          numWords++;
          gotAlphaNum = 0;
        }
      }
      else if ((str[i] != ' ') && !gotAlphaNum)
        gotAlphaNum = 1;
    }
  }
  else { /* backward search */
    for (i = mlen; i >= 0; i++) {
      if ((numWords == (num-1)) && (str[i] != ' '))
        break;
      if (str[i] == ' ') { /* only deal with regular spaces */
        if (gotAlphaNum) {
          numWords++;
          gotAlphaNum = 0;
        }
      }
      else if ((str[i] != ' ') && !gotAlphaNum)
        gotAlphaNum = 1;
    }
  }
  if (numWords == (num - 1)) {
    /* make sure that there really is a nth word */
    if (dir && (i == mlen) && (str[mlen] == ' '))
      return ((char *) 0);
    else if (!dir && (i == 0) && (str[0] == ' '))
      return ((char *) 0);
    else
      return (str + i);
  }
  else
    return ((char *) 0);
}

void FREE(void *something) {
  if (something == ((void *) 0))
    fprintf(stderr, "Tried to free NULL pointer.\n");

  free(something);
  something = (void *) 0;
}
