#include "indian.hpp"
#include "../inc/netface.hpp"      // net interface structures to the engine will use
#include "netfile.hpp"
#include "engine.hpp"

// these are the names of the fifos to create in tmp
// that communitcate with the engine

#define DIN_NAME "/tmp/.abuse_ndrv_in"
#define DOUT_NAME "/tmp/.abuse_ndrv_out"

// the lock files is used in case a previous net driver is already running

#define DLOCK_NAME "/tmp/.abuse_ndrv_lock"

#ifdef __sgi
#define next_process() sginap(0)
#else
#define next_process() usleep(10)
#endif


void net_watch();
void setup_ports(int comm_port, int game_port);

int no_security=0;
int driver_out_fd,driver_in_fd;
int shm_seg_id=-1;
void *shm_addr=(void *)-1;  // shmat returns -1 on failure
base_memory_struct *base;   // points to shm_addr
int comm_fd=-1,             // listening socket for commincation port
    game_fd=-1;
char net_server[256];   // if -net option, fetch all files from "active server"
int stand_alone=0;          // if we are running this stand-alone (not interfacing with the engine)

fd_set master_set;
fd_set master_write_set;    // set a socket here if you detect a write_full

join_struct *join_array;      // points to an array of possible joining clients in shared memory
int game_server_fd=-1,        // connection to server created by join_game()
    game_server_data_fd=-1;

int packet_port;              // port used to send 'lossy' game data

void clean_up()      // on exit unattach all shared memory links
{  
  base->input_state=INPUT_NET_DEAD;
  fprintf(stderr,"net driver : cleaning up\n");
  if (shm_seg_id!=-1)
    shmctl(shm_seg_id,IPC_RMID,NULL);

  if (shm_addr!=(void *)-1) 
  {
    shmdt((char *)shm_addr);
    shm_addr=(void *)-1;
  }

  if (game_fd>0) close(game_fd);
  if (comm_fd>0) close(comm_fd);

  unlink(DIN_NAME);
  unlink(DOUT_NAME);
  unlink(DLOCK_NAME);
}

#ifdef __sgi
void die(...)
#else
void die(int why)
#endif
{ 
  fprintf(stderr,"dieing\n");
  clean_up();
  exit(0);
}


void mdie(char *reason)
{
  fprintf(stderr,"net driver : %s\n",reason);
  die(0);
}

void comm_failed()  // general communication failure with engine
{
  fprintf(stderr,"net driver : Error occured while trying to communicate with the engine\n");
  clean_up();
  exit(0);
}


main(int argc, char **argv)
{
  int i;
  strcpy(default_fs_name,"");          // initially no default file server
  strcpy(net_server,"");

  for (i=1;i<argc;i++)
    if (!strcmp(argv[i],"-bastard"))   // this bypasses filename security features
    {
      fprintf(stderr,"Warning : Security measures bypassed (-bastard)\n");
      no_security=1;
    }


  // make sure this program was run by the abuse engine
  if (argc<2 || strcmp(argv[1],"runme"))   
  { 
    stand_alone=1;
    fprintf(stderr,"%s is normally run by abuse, running stand-alone file server\n"
	           "Server will be killed by running abuse\n",argv[0]);
  }


  // see if we are already running, if so kill old driver
  FILE *fp=fopen(DLOCK_NAME,"rb");
  if (fp)
  {
    int pid;
    if (fscanf(fp,"%d",&pid)==1)
    {
      struct stat st;
      char proc_path[50];
      sprintf(proc_path,"/proc/%d",pid);
      if (!stat(proc_path,&st))
      {
	fprintf(stderr,"net driver : warning, %s already running, attempting to kill...\n",argv[0]);
	if (kill(pid,SIGKILL))
	{
	  fprintf(stderr,"net driver : unable to kill process %d, cannot run net-abuse\n",pid);
	  fclose(fp);
	  return 0;
	}
	fprintf(stderr,"killed process %d\n",pid);
      }
    }
    fclose(fp);
    unlink(DLOCK_NAME);    
  }


  unlink(DIN_NAME);    // remove any previous files if they exsists
  unlink(DOUT_NAME);


  if (!stand_alone)
  {
    if (mkfifo(DIN_NAME,S_IRWXU | S_IRWXG | S_IRWXO))
    { perror("Net driver : unable to make fifo in /tmp");
      return 0;
    }
    chmod(DIN_NAME,S_IRWXU | S_IRWXG | S_IRWXO);   // just to be sure umask doesn't screw us

    if (mkfifo(DOUT_NAME,S_IRWXU | S_IRWXG | S_IRWXO))
    { perror("Net driver : unable to make fifo in /tmp");
      return 0;
    }
    chmod(DOUT_NAME,S_IRWXU | S_IRWXG | S_IRWXO);

    int i,no_fork=0;
    for (i=1;i<argc;i++)
    if (!strcmp(argv[i],"-no_fork"))    // use this to debug easier
      no_fork=1;
    
    if (!no_fork)      // use this for debugging
    {
      int child_pid=fork();
      if (child_pid)
      {
	FILE *fp=fopen(DLOCK_NAME,"wb");
	if (!fp)
	{ 
	  fprintf(stderr,"Unable to open %s for writing, killing child\n",DLOCK_NAME);
	  kill(child_pid,SIGKILL);
	  return 0;
	}
	fprintf(fp,"%d\n",child_pid);
	fclose(fp);
	printf("%d\n",child_pid);         // tell parent the sound driver's process number
	return 0;                         // exit, child will continue
      }
    }  

    driver_out_fd=open(DOUT_NAME,O_RDWR);  // open the pipe
    if (driver_out_fd<0)
    { perror(DOUT_NAME); 
      exit(1);
    }

    driver_in_fd=open(DIN_NAME,O_RDWR);
    if (driver_in_fd<0)
    { perror(DIN_NAME); 
      exit(1);
    }
  } else driver_in_fd=driver_out_fd=-1;



  int catch_sigs[]={SIGHUP,SIGINT,SIGQUIT,SIGILL,SIGABRT,
		    SIGIOT,SIGFPE,SIGKILL,SIGUSR1,SIGSEGV,
		    SIGUSR2,SIGPIPE,SIGTERM,SIGCHLD,
		    SIGCONT,SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU,-1};

  for (i=0;catch_sigs[i]!=-1;i++)     // catch all signals in case we get
    signal(catch_sigs[i],die);            // interrupted before we remove shmid


/*  struct sigaction sa;
  memset(&sa,0,sizeof(sa));
  sa.sa_flags=SA_RESTART|SA_RESETHAND;
  sa.sa_handler=send_sig;
  sigaction(SIGUSR2,&sa,NULL); */


  int alloc_size=sizeof(join_struct)*MAX_JOINERS+
                 sizeof(base_memory_struct);

  shm_seg_id=shmget(IPC_PRIVATE,alloc_size,IPC_CREAT | 0777);
  if (shm_seg_id==-1)
    mdie("Unable to allocate shared memory");


  shm_addr=shmat(shm_seg_id,NULL,0);  // attach as read/write
  if (shm_addr==(void *)-1) 
    mdie("could not attach shm seg");

  base=(base_memory_struct *)shm_addr;

  base->join_list=real2shm(join_struct,NULL);
  base->mem_lock=0;
  base->calc_crcs=0;
  base->get_lsf=0;
  base->wait_reload=0;
  base->need_reload=0;
  base->input_state=INPUT_COLLECTING;
  base->current_tick=0;
  base->packet.packet_reset();



  if (!stand_alone)
  {
    // see if we can attach this memory with the abuse engine
    if (write(driver_out_fd,&shm_seg_id,sizeof(shm_seg_id))!=sizeof(shm_seg_id))
      comm_failed();

    // wait for engine to ack it has attached
    uchar ack=0;
    if (read(driver_in_fd,&ack,1)!=1 || ack!=1)
      comm_failed();
  }

  
  if (shmctl(shm_seg_id,IPC_RMID,NULL))  // remove the shm id
    mdie("could not remove shm id");

  shm_seg_id=-1;                      // mark as not allocated


  int comm_port=DEFAULT_COMM_PORT;
  int game_port=-1;
  for (i=1;i<argc-1;i++)
    if (!strcmp(argv[i],"-port"))
    {
      comm_port=atoi(argv[i+1]);
      if (game_port==-1)
        game_port=comm_port+1;
    }
    else if (!strcmp(argv[i],"-game_port"))
      game_port=atoi(argv[i+1]);
    else if (!strcmp(argv[i],"-net"))
    { strcpy(net_server,argv[i+1]); }

  if (game_port==-1) game_port=DEFAULT_GAME_PORT+1;

  setup_ports(comm_port,game_port);


  net_watch();                        // now go into infinite block/read/process cycle

  return 0;
}

client *first_client=NULL;


void setup_ports(int comm_port, int game_port)
{
  sockaddr_in host;


  game_fd=socket(AF_INET,SOCK_DGRAM,0);
  if (game_fd==-1)
    mdie("net driver : could not create a socket.  (too many open files?)");

  int zz;
  if (setsockopt(game_fd,SOL_SOCKET,SO_REUSEADDR,(char *)&zz,sizeof(zz))<0)
    mdie("could not set socket option reuseaddr");

//  if (fcntl(game_fd,F_SETFL,FNDELAY)==-1)
//    mdie("cound not set udp socket to non-blocking");

  
  packet_port=game_port;   // save this port, so we can send it on joining a game
  memset( (char*) &host,0, sizeof(host));
  host.sin_family = AF_INET;
  host.sin_port = lstl(game_port);
  host.sin_addr.s_addr = lltl (INADDR_ANY);
  if (bind(game_fd, (struct sockaddr *) &host, sizeof(sockaddr_in))==-1)
  {
    fprintf(stderr,"net driver : could not bind socket to port %d\n",game_port);
    die(0);
  }


  // the comminication socket is a STREAM
  comm_fd=socket(AF_INET,SOCK_STREAM,0);
  if (comm_fd==-1)
    mdie("net driver : could not create a socket.  (too many open files?)");


  memset( (char*) &host,0, sizeof(host));
  host.sin_family = AF_INET;
  host.sin_port = lstl(comm_port);
  host.sin_addr.s_addr = lltl (INADDR_ANY);
  if (bind(comm_fd, (struct sockaddr *) &host, sizeof(sockaddr_in))==-1)
  {
    fprintf(stderr,"net driver : could not bind socket to port %d",comm_port);
    die(0);
  }

  if (listen(comm_fd,5)==-1)
  {
    fprintf(stderr,"net driver : could not listen to socket on port %d\n",comm_port);    
    die(0);
  }




}


void delete_client(client *c)
{
  FD_CLR(c->socket_fd,&master_set);   // don't listen to this client anymore

}


inline int aquire_mem_lock()
{
  if (base->mem_lock==0 || base->mem_lock==1)
  {
    base->mem_lock=1;
    if (base->mem_lock==1)
      return 1;
  }
  return 0;
}

class crc_waiter
{
  public :
  int socket_fd;
  crc_waiter *next;
  crc_waiter(int fd, crc_waiter *Next)
  { 
    FD_SET(fd,&master_set);   // set in case socket dies
    socket_fd=fd;
    next=Next;
  } ;
  ~crc_waiter() 
  { 
    close(socket_fd); 
    FD_CLR(socket_fd,&master_set);
  }    
} *crc_wait_list=NULL;

class lsf_waiter
{
  public :
  int socket_fd;
  lsf_waiter *next;
  lsf_waiter(int fd, lsf_waiter *Next)
  { 
    FD_SET(fd,&master_set);   // set in case socket dies
    socket_fd=fd;
    next=Next;
  } ;
  ~lsf_waiter() 
  { 
    close(socket_fd); 
    FD_CLR(socket_fd,&master_set);
  }    
} *lsf_wait_list=NULL;


int connect_to_server(char *&server_name, int def_port, int stream_type, int force_port)
{
  char name[256],*np;
  np=name;
  while (*server_name && *server_name!=':' && *server_name!='/')
    *(np++)=*(server_name)++;
  *np=0;
  if (*server_name==':')
  {
    server_name++;
    char port[256],*p;
    p=port;
    while (*server_name && *server_name!='/')
      *(p++)=*(server_name++);
    *p=0;
    int x;
    if (!force_port)
    {
      if (sscanf(port,"%d",&x)==1) def_port=x;
      else return -1;
    }
  }

  if (*server_name=='/') server_name++;

  if (local_address(name))    // returns 1 if server name is ourself
  {
    fprintf(stderr,"cannot connect to %s, is a local address\n");
    return -1;
  }
 
  int socket_fd=socket(AF_INET,stream_type,0);
  if (socket_fd<0) 
  {
    fprintf(stderr,"unable to create socket (too many open files?)\n");
    return -1;
  }

 
  hostent *hp=gethostbyname(name);
  if (!hp)
  { 
    fprintf(stderr,"unable to locate server named '%s'\n",name);
    close(socket_fd); 
    return 0;
  }
  

  sockaddr_in host;
  memset( (char*) &host,0, sizeof(host));
  host.sin_family = AF_INET;
  host.sin_port = lstl(def_port);
  host.sin_addr.s_addr = lltl (INADDR_ANY);
  memcpy(&host.sin_addr,hp->h_addr,hp->h_length);
    
  if (connect(socket_fd, (struct sockaddr *) &host, sizeof(host))==-1)
  { 
    fprintf(stderr,"unable to connect\n");
    close(socket_fd);
    return -1;
  }

  return socket_fd;
}

int get_lsf(char *name)  // contact remot host and ask for lisp startup file filename
{
  char *name_start=name;
  int fd=connect_to_server(name);
  if (fd<0) return 0;
  uchar ctype=CLIENT_LSF_WAITER;
  if (write(fd,&ctype,1)!=1) { close(fd); return 0; } 
  uchar len;
  if (read(fd,&len,1)!=1 || len==0) { close(fd); return 0; }
  if (read(fd,name_start,len)!=len) { close(fd); return 0; }
  close(fd);
  return 1;  
}

int join_game(char *server_name)   // ask remote server for entry into game
{
  char sn_start[256];
  strcpy(sn_start,server_name);

  int fd=connect_to_server(server_name);
  uchar ctype=CLIENT_ABUSE;
  if (write(fd,&ctype,1)!=1) { close(fd); return 0; } 

  // send server out game port
  ushort port=lstl(packet_port);
  if (write(fd,&port,2)!=2) { close(fd); return 0; }

  // read server's game port
  if (read(fd,&port,2)!=2) { close(fd); return 0; }
  port=lstl(port);

  ushort cnum;
  if (read(fd,&cnum,2)!=2 || cnum==0) { close(fd); return 0; }
  cnum=lstl(cnum);

 
  game_server_fd=fd;

  server_name=sn_start;
  game_server_data_fd=connect_to_server(server_name,port,SOCK_DGRAM,1);
  if (game_server_data_fd<0) mdie("could not udp-connect to server");
  FD_SET(game_server_fd,&master_set);

  return cnum;
}

void join_new_players()  // during this section we are giving mem_lock by engine
{
  client *c=first_client;

  for (;c;c=c->next)     // tell all the clients to reload
  {
    if (!c->has_joined)
    {
      ushort cnum=lstl(c->client_id);
      if (write(c->socket_fd,&cnum,2)!=2) { c->delete_me=1; }
      c->wait_reload=1;
      c->has_joined=1;
    } else if (!c->delete_me)
    {
      uchar reload=CLCMD_RELOAD;
      if (write(c->socket_fd,&reload,1)!=1) { c->delete_me=1; }
      c->wait_reload=1;
    }
  }

  base->join_list=NULL;  // all joiners have been added
 
}

int waiting_server_input=1;

void add_client_input(char *buf, int size, client *c)
{
  base->packet.add_to_packet(buf,size);

  if (c)
  {
    c->wait_input=0;
  }
  else 
  {
    FD_SET(game_fd,&master_set);   // we are ready to accept other client's game data, so add the udp socket to the select list
    waiting_server_input=0;
  }

  int got_all=1;  
  for (c=first_client;c;c=c->next)
    if (c->wait_input)
      got_all=0;

  if (got_all && !waiting_server_input)
  {   
    base->packet.calc_checksum();

    for (c=first_client;c;c=c->next)      // setup for next time, wait for all the input
    {
      c->wait_input=1;      
      send(c->data_fd,base->packet.data,base->packet.packet_size()+base->packet.packet_prefix_size(),0);
    }

    base->input_state=INPUT_PROCESSING; // tell engine to start processing
    FD_CLR(game_fd,&master_set);        // don't listen to this socket until we are prepared to read next tick's game data

    waiting_server_input=1;
  }
}

void get_input_from_server()
{
  int size=read(game_fd,base->packet.data,1024);
  if (size<=0)
  { mdie("read <= 0 bytes from server"); }
  if (base->packet.packet_size()+base->packet.packet_prefix_size()==size &&     // did we read the whole packet?
      base->packet.tick_received()==base->current_tick)    // if this was the correct tick packet, then tell server to go on
    base->input_state=INPUT_PROCESSING;    
}

void process_engine_command()
{
  uchar cmd;
  if (read(driver_in_fd,&cmd,1)!=1) { mdie("could not read command from engine"); }
  switch (cmd)
  {
    case EGCMD_DIE :
    {
      if (!write(driver_out_fd,&cmd,1)) { mdie("could not write block ack1"); }  // send something to unblock engine
      mdie("received die command");
    } break;

    case NFCMD_BLOCK :
    {      
      if (!write(driver_out_fd,&cmd,1)) { mdie("could not write block ack1"); }  // send something to unblock engine
      if (!read(driver_in_fd,&cmd,1)) { mdie("could not read block ack1"); }  // send something to block ourself
    } break;

    case NFCMD_INPUT_MISSING :    // try to fetch the input via a loss-less net protocol
    {
      unsigned char pk[2]={CLCMD_REQUEST_RESEND,base->packet.tick_received()};

      if (net_server[0])   // if we are connected to a server ask sever to resend
      {
        if (write(game_server_fd,pk,2)!=2) { mdie("attept to re-fetch input failed"); }
	fprintf(stderr,"sending retry request to server (%d)\n",pk[1]);
      } else
      {
	client *c=first_client;
	for (;c;c=c->next)
	{
	  if (!c->delete_me && c->wait_input)
	  {
	    fprintf(stderr,"sending retry request to client (%d)\n",pk[1]);
	    if (write(c->socket_fd,pk,2)!=2) 
	      c->delete_me=1;
	  }
	  if (c->delete_me) fprintf(stderr,"delete this client!\n");
	}
      }
      if (!write(driver_out_fd,&cmd,1)) { mdie("could not write block ack1"); }  // send something to unblock engine
    } break;

    case NFCMD_SEND_INPUT :
    {
      base->packet.set_tick_received(base->current_tick);
      base->input_state=INPUT_COLLECTING;  
      if (!net_server[0])
      {
        add_client_input(NULL,0,NULL);
      }
      else
      {
	base->packet.calc_checksum();
        send(game_server_data_fd,base->packet.data,base->packet.packet_size()+base->packet.packet_prefix_size(),0);
      }
      if (!write(driver_out_fd,&cmd,1)) { mdie("could not write send ack1"); }  // send something to unblock engine
      if (!read(driver_in_fd,&cmd,1)) { mdie("could not read send ack2"); }	// read something to block ourselves for engine
    } break;

    case NFCMD_RELOADED :
    {
      if (game_server_fd>0)
      {
	uchar ok=CLCMD_RELOADED;
        if (!write(game_server_fd,&ok,1)) { mdie("could not send join_ok msg"); }	
	next_process();
      }
    } break;

    case NFCMD_PROCESS_ENTRIES :  // server is telling us the player has been added into the game
    {
      join_new_players();
    } break;

    case NFCMD_REQUEST_ENTRY :
    {
      uchar len;
      char name[256];
      if (read(driver_in_fd,&len,1)!=1) { mdie("could not read server name length"); }
      if (read(driver_in_fd,name,len)!=len) { mdie("could not read server name"); }
      strcpy(net_server,name);
      ushort success=join_game(name);
      if (write(driver_out_fd,&success,2)!=2) mdie("cound not send lsf read failure");      
      next_process();
    } break;

    case NFCMD_REQUEST_LSF :
    {
      uchar len;
      char name[256];
      if (read(driver_in_fd,&len,1)!=1) { mdie("could not read lsf name length"); }
      if (read(driver_in_fd,name,len)!=len) { mdie("could not read lsf name"); }
      if (!get_lsf(name))
      {
	len=0;
        if (write(driver_out_fd,&len,1)!=1) mdie("cound not send lsf read failure");
      } else
      {
	len=strlen(name)+1;
	if (write(driver_out_fd,&len,1)!=1) mdie("cound not send lsf name len");
	if (write(driver_out_fd,name,len)!=len) mdie("cound not send lsf name");
      }     
      next_process();
    } break;

    case NFCMD_PROCESS_LSF :
    {
      uchar len,name[256];
      if (read(driver_in_fd,&len,1)!=1) { mdie("could not read lsf name length"); }
      if (read(driver_in_fd,name,len)!=len) { mdie("could not read lsf name"); }
      while (lsf_wait_list)
      {
	lsf_waiter *c=lsf_wait_list;
	lsf_wait_list=lsf_wait_list->next;
	uchar status=1;
	write(c->socket_fd,&len,1);
	write(c->socket_fd,name,len);
	delete c;
      }
      next_process();
    } break;

    case NFCMD_CRCS_CALCED : 
    {
      while (crc_wait_list)
      {
	crc_waiter *c=crc_wait_list;
	crc_wait_list=crc_wait_list->next;
	uchar status=1;
	write(c->socket_fd,&status,1);
	delete c;
      }
      next_process();
    } break;

    case NFCMD_SET_FS :
    {
      uchar size;
      char sn[256];
      if (read(driver_in_fd,&size,1)!=1) mdie("could not read filename length");
      if (read(driver_in_fd,sn,size)!=size) mdie("could not read server name");
      strcpy(default_fs_name,sn);
      size=fetch_crcs(sn);  // return success
      if (write(driver_out_fd,&size,1)!=1) mdie("could not send ok to engine");
      next_process();
    } break;    

    case NFCMD_OPEN :
    {
      uchar size[2];
      char filename[300],mode[20],*fn;
      fn=filename;
      if (read(driver_in_fd,size,2)!=2) mdie("could not read fd on open");
      if (read(driver_in_fd,filename,size[0])!=size[0]) mdie("incomplete filename");
      if (read(driver_in_fd,mode,size[1])!=size[1]) mdie("incomplete mode string");
      
      int fd=open_file(fn,mode);
      if (fd==-2)
      {
	uchar st[2];
	st[0]=NF_OPEN_LOCAL_FILE;
	st[1]=strlen(fn)+1;
	if (write(driver_out_fd,st,2)!=2) comm_failed();
	int size=write(driver_out_fd,fn,st[1]);
	if (size!=st[1]) comm_failed();

	if (size!=st[1]) comm_failed();
      } else if (fd==-1)
      {
	uchar st=NF_OPEN_FAILED;
	if (write(driver_out_fd,&st,1)!=1) comm_failed(); 
      } else
      {
	uchar st=NF_OPEN_REMOTE_FILE;
	if (write(driver_out_fd,&st,1)!=1) comm_failed(); 	
	if (write(driver_out_fd,&fd,sizeof(fd))!=sizeof(fd)) comm_failed(); 	
      }
      next_process();
    } break;
    case NFCMD_CLOSE :
    case NFCMD_SIZE :
    case NFCMD_TELL :
    case NFCMD_SEEK :
    case NFCMD_READ :
    {
      int fd;
      if (read(driver_in_fd,&fd,sizeof(fd))!=sizeof(fd)) comm_failed();
      remote_file *rf=find_rfile(fd);
      if (!rf) 
	mdie("bad fd for engine command");

      switch (cmd)
      {
	case NFCMD_CLOSE : 
	{ 
	  unlink_remote_file(rf);
	  delete rf; 
	  uchar st=1;
	  if (write(driver_out_fd,&st,1)!=1) comm_failed(); 	
	} break;
	case NFCMD_SIZE  :
	{
	  if (write(driver_out_fd,&rf->size,sizeof(rf->size))!=sizeof(rf->size)) comm_failed(); 		  
	} break;
	case NFCMD_TELL :
	{
	  long offset=rf->unbuffered_tell();
	  if (write(driver_out_fd,&offset,sizeof(offset))!=sizeof(offset)) comm_failed();  
	} break;
	case NFCMD_SEEK :
	{
	  long offset;
	  if (read(driver_in_fd,&offset,sizeof(offset))!=sizeof(offset)) comm_failed();
	  offset=rf->unbuffered_seek(offset);
	  if (write(driver_out_fd,&offset,sizeof(offset))!=sizeof(offset)) comm_failed();  
	} break;
	case NFCMD_READ :
	{
	  long size;
	  if (read(driver_in_fd,&size,sizeof(size))!=sizeof(size)) comm_failed();
	  rf->unbuffered_read(driver_out_fd,size);
	} break;
      }
      next_process();
    } break;    
    default :
    { fprintf(stderr,"net driver : unknown net command %d\n",cmd); die(0); }
  }   
}


int process_client_command(client *c)
{
  uchar cmd;
  if (read(c->socket_fd,&cmd,1)!=1) return 0;
  switch (cmd)
  {
    case CLCMD_RELOADED :
    {
      c->wait_reload=0;
      int done=1;
      for (c=first_client;c;c=c->next)
        if (c->wait_reload) done=0;
      if (done) base->wait_reload=0;
      return 1;
    } break;
    case CLCMD_REQUEST_RESEND :
    {
      uchar tick;
      if (read(c->socket_fd,&tick,1)!=1) return 0;


      fprintf(stderr,"request for resend tick %d (game cur=%d, pack=%d, last=%d)\n",
	      tick,base->current_tick,base->packet.tick_received(),base->last_packet.tick_received());

      if (tick==base->last_packet.tick_received())
      {
	fprintf(stderr,"resending last game packet\n");
	send(c->data_fd,base->last_packet.data,base->last_packet.packet_size()+base->last_packet.packet_prefix_size(),0); 
      }
      else if (tick==base->packet.tick_received()) // asking for current tick, make sure it's collected
      {
	int got_all=!waiting_server_input;
	client *cc=first_client;
	for (;cc;cc=cc->next)
	  if (cc->wait_input) got_all=0;
	  
	if (got_all) 
	{
	  fprintf(stderr,"resending current game packet\n");
	  send(c->data_fd,base->packet.data,base->packet.packet_size()+base->packet.packet_prefix_size(),0); 
	} else fprintf(stderr,"current game packet not complete yet\n");
      }      
      return 1;
    } break;
  }
  return 0;
}



int isa_client(int client_id)    // sreach the list of active clients for this id and return 1 if found
{
  int i;
  if (client_id==0) return 1;   // the server is always a client
  client *c=first_client;
  for (;c;c=c->next)
    if (c->client_id==client_id) return 1;
  return 0;  
}

int join_game_client(int client_id)
{
}

int add_game_client(int fd, sockaddr *from)     // returns false if could not join client
{  
  ushort port;
  if (read(fd,&port,2)!=2) { close(fd);  return 0; }
  port=lstl(port);

  ushort pport=lstl(packet_port);
  if (write(fd,&pport,2)!=2) { close(fd);  return 0; }  


  int f=-1,i;
  for (i=0;f==-1 && i<MAX_JOINERS;i++)
    if (!isa_client(i)) 
      f=i;

  if (f===-1) { close(fd); return 0; }

  ushort client_id=lstl(f);
  if (write(fd,&client_id,2)!=2) { close(fd);  return 0; }    


  join_array[f].next=base->join_list;
  base->join_list=real2shm(join_struct,&join_array[f]);
  join_array[f].client_id=first_free_client; 
  
  first_client=new client(fd,f,first_client);
  memcpy(&first_client->data_address,from,sizeof(first_client->data_address));

  // data port should be one above comminication port
  first_client->data_address.sin_port = lstl(port);

  first_client->data_fd=socket(AF_INET,SOCK_DGRAM,0);

  if (first_client->data_fd==-1)
  {
    client *c=first_client;  first_client=first_client->next; delete c;
    fprintf(stderr,"net driver : could not create a socket.  (too many open files?)");
    return 0;
  }
  
  if (connect(first_client->data_fd, (struct sockaddr *) &first_client->data_address, 
	      sizeof(first_client->data_address))==-1)
  { 
    client *c=first_client;  first_client=first_client->next; delete c;
    fprintf(stderr,"unable to connect upd port\n");
    return 0;
  }


  return 1;
}


void add_client()
{
  struct sockaddr from;
  int addr_len=sizeof(from);
  int new_fd=accept(comm_fd,&from,&addr_len);
  if (new_fd>=0)
  {
    char client_type;
    if (read(new_fd,&client_type,1)!=1) { close(new_fd); return ; }
    switch (client_type)
    {
      case CLIENT_NFS : add_nfs_client(new_fd);    break;
      case CLIENT_ABUSE : add_game_client(new_fd,&from); break;
      case CLIENT_CRC_WAITER : 
      {
	if (stand_alone)    // can't ask the engine if there is no engine
	{
	  char status=0;
	  write(new_fd,&status,1);
	  close(new_fd);
	} else
	{
	  crc_wait_list=new crc_waiter(new_fd,crc_wait_list);
	  base->calc_crcs=1;
	}
      } break;
      case CLIENT_LSF_WAITER : 
      {
	if (stand_alone)    // can't ask the engine if there is no engine
	{
	  char status=0;
	  write(new_fd,&status,1);
	  close(new_fd);
	} else
	{
	  lsf_wait_list=new lsf_waiter(new_fd,lsf_wait_list);
	  base->get_lsf=1;
	}
      } break;
    }
  }
}

void net_watch()
{
  int i;
  join_array=(join_struct *) (base+1);

  for (i=0;i<MAX_JOINERS;i++)
    join_array[i].client_id=-1;


  if (!stand_alone)
  {
    while (!aquire_mem_lock()) { next_process(); }
    base->mem_lock=0;
  }

  fd_set read_set,exception_set,write_set;
  
  FD_ZERO(&master_set);  
  FD_ZERO(&master_write_set);  
  FD_SET(comm_fd,&master_set);     // new incoming connections & nfs data
  if (net_server)
    FD_SET(game_fd,&master_set);     // new incoming connections & nfs data

  if (!stand_alone)
  {
    FD_SET(driver_in_fd,&master_set);  // request from engine
    FD_SET(driver_out_fd,&master_set); // check for error on messages to engine
  }

  while (1)
  {
    memcpy(&read_set,&master_set,sizeof(master_set));
    memcpy(&exception_set,&master_set,sizeof(master_set));
    memcpy(&write_set,&master_write_set,sizeof(master_set));


    int tsel=select(FD_SETSIZE,&read_set,&write_set,&exception_set,NULL);

    int check_rest=1;
    if (!stand_alone)
    {
      if (base->input_state==INPUT_COLLECTING)
      {
	// any game related data (udp) waiting to be read?
	if (FD_ISSET(game_fd,&read_set))
	{
	  tsel--;
	  check_rest=0;
	  net_packet scratch,*use;
	  
	  if (net_server[0]==0)    // if we are the server, read into scratch, then "add" into base
	    use=&scratch;
	  else use=&base->packet;    // otherwise read directly into base because it is a complete packet from the server

	  sockaddr_in from_addr;
	  int addr_size=sizeof(from_addr);
	  int bytes_received=recvfrom(game_fd,use->data,1024,0, (sockaddr *)&from_addr,&addr_size);

	  // make sur we got a complete packet and the packet was not a previous game tick packet
	  if (bytes_received==use->packet_size()+use->packet_prefix_size())
	  {
	    unsigned short rec_crc=use->get_checksum();
	    use->calc_checksum();
	    if (rec_crc==use->get_checksum())
	    {
	      if (base->current_tick==use->tick_received())  
	      {
		if (net_server[0])   // if we are a client, tell game to process input
		base->input_state=INPUT_PROCESSING;   // tell engine to start processing
		else
		{

		  client *f=first_client,*found=NULL;
		  for (;!found &&f;f=f->next)
		  if (!memcmp(&from_addr.sin_addr,&f->data_address.sin_addr,sizeof(from_addr.sin_addr)))
		  found=f;
		  
		  if (!found)
		  fprintf(stderr,"received data from unknown client\n");
		  else
		  add_client_input((char *)use->packet_data(),use->packet_size(),found);
		}
	      } else fprintf(stderr,"received stale packet (got %d, expected %d)\n",use->tick_received(),base->current_tick);
	    } else fprintf(stderr,"received packet with bad checksum\n");
	  } else fprintf(stderr,"received incomplete packet\n");
	}
	base->mem_lock=0;
      }

      // see if we had any errors talking to the engine
      if (FD_ISSET(driver_in_fd,&exception_set) || FD_ISSET(driver_out_fd,&exception_set))
      {
	tsel--;
	check_rest=0;
        comm_failed();
      }
      
      // see if the engine has anything to say before getting to anyone else
      if (FD_ISSET(driver_in_fd,&read_set))
      {
	tsel--;
        process_engine_command();
	check_rest=0;
      }
    }
 

    if (check_rest && aquire_mem_lock())  // we need to change shared memory, make sure server is not using it.
    {
      if (game_server_fd==-1)    // we are a server, check all client connections
      {
	client *c,*lastc=NULL;

	for (c=first_client;c;)
	{
	  int del=0;
	  if (FD_ISSET(c->socket_fd,&exception_set))  // error?
	  {
	    tsel--;
	    del=1;
	  }

	  // waiting for engine to process command buffer, don't read anymore yet
	  else if (FD_ISSET(c->socket_fd,&read_set))  // in comming commands data from client?
	  { 
	    tsel--;
	    if (process_client_command(c)==0)
	    del=1;

	    if (del)
	    {
	      if (c->wait_reload)
	      {
		int done=1;
		client *d=first_client;
		for (;d;d=d->next)                // see if this was the last client to wait on reloading
		if (d->wait_reload) done=0;
		if (done) base->wait_reload=0;
	      }

	      if (lastc) lastc->next=c->next;
	      else first_client=c->next;
	      client *cd=c; c=c->next; delete cd;
	    } else
	    {
	      lastc=c;
	      c=c->next;
	    }	 
	  } else c=c->next;
	}
      } else if (FD_ISSET(game_server_fd,&read_set))
      {
	uchar cmd;
	if (read(game_server_fd,&cmd,1)!=1) { mdie("unable to read command from server"); }
	switch (cmd)
	{
	  case CLCMD_RELOAD :
	  {
	    base->need_reload=1;
	  } break;
	  case CLCMD_REQUEST_RESEND :
	  {
	    uchar tick;
	    if (read(game_server_fd,&tick,1)!=1) { mdie("unable to read resend tick from server"); }

	    fprintf(stderr,"request for resend tick %d (game cur=%d, pack=%d, last=%d)\n",
	      tick,base->current_tick,base->packet.tick_received(),base->last_packet.tick_received());

	    if (tick==base->packet.tick_received() && !waiting_server_input)    // asking for this tick?  make sure is collected
	    {
	      fprintf(stderr,"resending client packet to server\n");
	      send(game_server_data_fd,base->packet.data,base->packet.packet_size()+base->packet.packet_prefix_size(),0); 
	    }
	  } break;
	}
      }



      if (FD_ISSET(comm_fd,&read_set))
      {
	tsel--;
        add_client();
      }

      nfs_client *nc,*last=NULL;
      for (nc=first_nfs_client;nc;)      // check for nfs request
      {

	int ok=1;

	if (FD_ISSET(nc->socket_fd,&exception_set))
	{
	  tsel--;
	  ok=0;
	  fprintf(stderr,"Killing nfs client, socket went bad\n");
	} 
	else if (nc->size_to_read)
	{
	  if (FD_ISSET(nc->socket_fd,&write_set))
	  {
	    tsel--;
	    ok=nc->send_read();
	  }
	}	    
	else if (FD_ISSET(nc->socket_fd,&read_set))
	{
	  tsel--;
	  ok=process_nfs_command(nc);    // if we couldn't process the packeted, delete the connection
	}
	    
	if (ok)
	{
	  last=nc;
	  nc=nc->next;
	} else
	{
	  if (last) last->next=nc->next;
	  else first_nfs_client=nc->next;
	  nfs_client *c=nc;
	  nc=nc->next;
	  delete c;
	}
      }

      // check for bad sockets for people waiting on crc's
      crc_waiter *crcw=crc_wait_list,*last_crcw=NULL;
      for (;crcw;)
      {
	if (FD_ISSET(crcw->socket_fd,&exception_set))
	{
	  tsel--;
	  if (last_crcw) { last_crcw->next=crcw->next; crc_waiter *cc=crcw; crcw=crcw->next; delete cc; }
	  else { crc_wait_list=crcw->next; delete crcw; crcw=crc_wait_list; }
	} else crcw=crcw->next;
      }
      if (!crc_wait_list) base->calc_crcs=0;

      // check for bad sockets for people waiting on crc's
      lsf_waiter *lsfw=lsf_wait_list,*last_lsfw=NULL;
      for (;lsfw;)
      {
	if (FD_ISSET(lsfw->socket_fd,&exception_set))
	{
	  tsel--;
	  if (last_lsfw) { last_lsfw->next=lsfw->next; lsf_waiter *cc=lsfw; lsfw=lsfw->next; delete cc; }
	  else { lsf_wait_list=lsfw->next; delete lsfw; lsfw=lsf_wait_list; }
	} else lsfw=lsfw->next;
      }
      if (!lsf_wait_list) base->get_lsf=0;

      base->mem_lock=0;

    }   
    if (tsel)
    {
//      fprintf(stderr,"%d",tsel);
      next_process();
    }

  }
}






