File: server\sv_user.c

    1 /*
    2 Copyright (C) 1997-2001 Id Software, Inc.
    3 
    4 This program is free software; you can redistribute it and/or
    5 modify it under the terms of the GNU General Public License
    6 as published by the Free Software Foundation; either version 2
    7 of the License, or (at your option) any later version.
    8 
    9 This program is distributed in the hope that it will be useful,
   10 but WITHOUT ANY WARRANTY; without even the implied warranty of
   11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
   12 
   13 See the GNU General Public License for more details.
   14 
   15 You should have received a copy of the GNU General Public License
   16 along with this program; if not, write to the Free Software
   17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   18 
   19 */
   20 // sv_user.c -- server code for moving users
   21 
   22 #include "server.h"
   23 
   24 edict_t *sv_player;
   25 
   26 /*
   27 ============================================================
   28 
   29 USER STRINGCMD EXECUTION
   30 
   31 sv_client and sv_player will be valid.
   32 ============================================================
   33 */
   34 
   35 /*
   36 ==================
   37 SV_BeginDemoServer
   38 ==================
   39 */
   40 void SV_BeginDemoserver (void)
   41 {
   42         char            name[MAX_OSPATH];
   43 
   44         Com_sprintf (name, sizeof(name), "demos/%s", sv.name);
   45         FS_FOpenFile (name, &sv.demofile);
   46         if (!sv.demofile)
   47                 Com_Error (ERR_DROP, "Couldn't open %s\n", name);
   48 }
   49 
   50 /*
   51 ================
   52 SV_New_f
   53 
   54 Sends the first message from the server to a connected client.
   55 This will be sent on the initial connection and upon each server load.
   56 ================
   57 */
   58 void SV_New_f (void)
   59 {
   60         char            *gamedir;
   61         int                     playernum;
   62         edict_t         *ent;
   63 
   64         Com_DPrintf ("New() from %s\n", sv_client->name);
   65 
   66         if (sv_client->state != cs_connected)
   67         {
   68                 Com_Printf ("New not valid -- already spawned\n");
   69                 return;
   70         }
   71 
   72         // demo servers just dump the file message
   73         if (sv.state == ss_demo)
   74         {
   75                 SV_BeginDemoserver ();
   76                 return;
   77         }
   78 
   79         //
   80         // serverdata needs to go over for all types of servers
   81         // to make sure the protocol is right, and to set the gamedir
   82         //
   83         gamedir = Cvar_VariableString ("gamedir");
   84 
   85         // send the serverdata
   86         MSG_WriteByte (&sv_client->netchan.message, svc_serverdata);
   87         MSG_WriteLong (&sv_client->netchan.message, PROTOCOL_VERSION);
   88         MSG_WriteLong (&sv_client->netchan.message, svs.spawncount);
   89         MSG_WriteByte (&sv_client->netchan.message, sv.attractloop);
   90         MSG_WriteString (&sv_client->netchan.message, gamedir);
   91 
   92         if (sv.state == ss_cinematic || sv.state == ss_pic)
   93                 playernum = -1;
   94         else
   95                 playernum = sv_client - svs.clients;
   96         MSG_WriteShort (&sv_client->netchan.message, playernum);
   97 
   98         // send full levelname
   99         MSG_WriteString (&sv_client->netchan.message, sv.configstrings[CS_NAME]);
  100 
  101         //
  102         // game server
  103         // 
  104         if (sv.state == ss_game)
  105         {
  106                 // set up the entity for the client
  107                 ent = EDICT_NUM(playernum+1);
  108                 ent->s.number = playernum+1;
  109                 sv_client->edict = ent;
  110                 memset (&sv_client->lastcmd, 0, sizeof(sv_client->lastcmd));
  111 
  112                 // begin fetching configstrings
  113                 MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
  114                 MSG_WriteString (&sv_client->netchan.message, va("cmd configstrings %i 0\n",svs.spawncount) );
  115         }
  116 
  117 }
  118 
  119 /*
  120 ==================
  121 SV_Configstrings_f
  122 ==================
  123 */
  124 void SV_Configstrings_f (void)
  125 {
  126         int                     start;
  127 
  128         Com_DPrintf ("Configstrings() from %s\n", sv_client->name);
  129 
  130         if (sv_client->state != cs_connected)
  131         {
  132                 Com_Printf ("configstrings not valid -- already spawned\n");
  133                 return;
  134         }
  135 
  136         // handle the case of a level changing while a client was connecting
  137         if ( atoi(Cmd_Argv(1)) != svs.spawncount )
  138         {
  139                 Com_Printf ("SV_Configstrings_f from different level\n");
  140                 SV_New_f ();
  141                 return;
  142         }
  143         
  144         start = atoi(Cmd_Argv(2));
  145 
  146         // write a packet full of data
  147 
  148         while ( sv_client->netchan.message.cursize < MAX_MSGLEN/2 
  149                 && start < MAX_CONFIGSTRINGS)
  150         {
  151                 if (sv.configstrings[start][0])
  152                 {
  153                         MSG_WriteByte (&sv_client->netchan.message, svc_configstring);
  154                         MSG_WriteShort (&sv_client->netchan.message, start);
  155                         MSG_WriteString (&sv_client->netchan.message, sv.configstrings[start]);
  156                 }
  157                 start++;
  158         }
  159 
  160         // send next command
  161 
  162         if (start == MAX_CONFIGSTRINGS)
  163         {
  164                 MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
  165                 MSG_WriteString (&sv_client->netchan.message, va("cmd baselines %i 0\n",svs.spawncount) );
  166         }
  167         else
  168         {
  169                 MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
  170                 MSG_WriteString (&sv_client->netchan.message, va("cmd configstrings %i %i\n",svs.spawncount, start) );
  171         }
  172 }
  173 
  174 /*
  175 ==================
  176 SV_Baselines_f
  177 ==================
  178 */
  179 void SV_Baselines_f (void)
  180 {
  181         int             start;
  182         entity_state_t  nullstate;
  183         entity_state_t  *base;
  184 
  185         Com_DPrintf ("Baselines() from %s\n", sv_client->name);
  186 
  187         if (sv_client->state != cs_connected)
  188         {
  189                 Com_Printf ("baselines not valid -- already spawned\n");
  190                 return;
  191         }
  192         
  193         // handle the case of a level changing while a client was connecting
  194         if ( atoi(Cmd_Argv(1)) != svs.spawncount )
  195         {
  196                 Com_Printf ("SV_Baselines_f from different level\n");
  197                 SV_New_f ();
  198                 return;
  199         }
  200         
  201         start = atoi(Cmd_Argv(2));
  202 
  203         memset (&nullstate, 0, sizeof(nullstate));
  204 
  205         // write a packet full of data
  206 
  207         while ( sv_client->netchan.message.cursize <  MAX_MSGLEN/2
  208                 && start < MAX_EDICTS)
  209         {
  210                 base = &sv.baselines[start];
  211                 if (base->modelindex || base->sound || base->effects)
  212                 {
  213                         MSG_WriteByte (&sv_client->netchan.message, svc_spawnbaseline);
  214                         MSG_WriteDeltaEntity (&nullstate, base, &sv_client->netchan.message, true, true);
  215                 }
  216                 start++;
  217         }
  218 
  219         // send next command
  220 
  221         if (start == MAX_EDICTS)
  222         {
  223                 MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
  224                 MSG_WriteString (&sv_client->netchan.message, va("precache %i\n", svs.spawncount) );
  225         }
  226         else
  227         {
  228                 MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
  229                 MSG_WriteString (&sv_client->netchan.message, va("cmd baselines %i %i\n",svs.spawncount, start) );
  230         }
  231 }
  232 
  233 /*
  234 ==================
  235 SV_Begin_f
  236 ==================
  237 */
  238 void SV_Begin_f (void)
  239 {
  240         Com_DPrintf ("Begin() from %s\n", sv_client->name);
  241 
  242         // handle the case of a level changing while a client was connecting
  243         if ( atoi(Cmd_Argv(1)) != svs.spawncount )
  244         {
  245                 Com_Printf ("SV_Begin_f from different level\n");
  246                 SV_New_f ();
  247                 return;
  248         }
  249 
  250         sv_client->state = cs_spawned;
  251         
  252         // call the game begin function
  253         ge->ClientBegin (sv_player);
  254 
  255         Cbuf_InsertFromDefer ();
  256 }
  257 
  258 //=============================================================================
  259 
  260 /*
  261 ==================
  262 SV_NextDownload_f
  263 ==================
  264 */
  265 void SV_NextDownload_f (void)
  266 {
  267         int             r;
  268         int             percent;
  269         int             size;
  270 
  271         if (!sv_client->download)
  272                 return;
  273 
  274         r = sv_client->downloadsize - sv_client->downloadcount;
  275         if (r > 1024)
  276                 r = 1024;
  277 
  278         MSG_WriteByte (&sv_client->netchan.message, svc_download);
  279         MSG_WriteShort (&sv_client->netchan.message, r);
  280 
  281         sv_client->downloadcount += r;
  282         size = sv_client->downloadsize;
  283         if (!size)
  284                 size = 1;
  285         percent = sv_client->downloadcount*100/size;
  286         MSG_WriteByte (&sv_client->netchan.message, percent);
  287         SZ_Write (&sv_client->netchan.message,
  288                 sv_client->download + sv_client->downloadcount - r, r);
  289 
  290         if (sv_client->downloadcount != sv_client->downloadsize)
  291                 return;
  292 
  293         FS_FreeFile (sv_client->download);
  294         sv_client->download = NULL;
  295 }
  296 
  297 /*
  298 ==================
  299 SV_BeginDownload_f
  300 ==================
  301 */
  302 void SV_BeginDownload_f(void)
  303 {
  304         char    *name;
  305         extern  cvar_t *allow_download;
  306         extern  cvar_t *allow_download_players;
  307         extern  cvar_t *allow_download_models;
  308         extern  cvar_t *allow_download_sounds;
  309         extern  cvar_t *allow_download_maps;
  310         extern  int             file_from_pak; // ZOID did file come from pak?
  311         int offset = 0;
  312 
  313         name = Cmd_Argv(1);
  314 
  315         if (Cmd_Argc() > 2)
  316                 offset = atoi(Cmd_Argv(2)); // downloaded offset
  317 
  318         // hacked by zoid to allow more conrol over download
  319         // first off, no .. or global allow check
  320         if (strstr (name, "..") || !allow_download->value
  321                 // leading dot is no good
  322                 || *name == '.' 
  323                 // leading slash bad as well, must be in subdir
  324                 || *name == '/'
  325                 // next up, skin check
  326                 || (strncmp(name, "players/", 6) == 0 && !allow_download_players->value)
  327                 // now models
  328                 || (strncmp(name, "models/", 6) == 0 && !allow_download_models->value)
  329                 // now sounds
  330                 || (strncmp(name, "sound/", 6) == 0 && !allow_download_sounds->value)
  331                 // now maps (note special case for maps, must not be in pak)
  332                 || (strncmp(name, "maps/", 6) == 0 && !allow_download_maps->value)
  333                 // MUST be in a subdirectory    
  334                 || !strstr (name, "/") )        
  335         {       // don't allow anything with .. path
  336                 MSG_WriteByte (&sv_client->netchan.message, svc_download);
  337                 MSG_WriteShort (&sv_client->netchan.message, -1);
  338                 MSG_WriteByte (&sv_client->netchan.message, 0);
  339                 return;
  340         }
  341 
  342 
  343         if (sv_client->download)
  344                 FS_FreeFile (sv_client->download);
  345 
  346         sv_client->downloadsize = FS_LoadFile (name, (void **)&sv_client->download);
  347         sv_client->downloadcount = offset;
  348 
  349         if (offset > sv_client->downloadsize)
  350                 sv_client->downloadcount = sv_client->downloadsize;
  351 
  352         if (!sv_client->download
  353                 // special check for maps, if it came from a pak file, don't allow
  354                 // download  ZOID
  355                 || (strncmp(name, "maps/", 5) == 0 && file_from_pak))
  356         {
  357                 Com_DPrintf ("Couldn't download %s to %s\n", name, sv_client->name);
  358                 if (sv_client->download) {
  359                         FS_FreeFile (sv_client->download);
  360                         sv_client->download = NULL;
  361                 }
  362 
  363                 MSG_WriteByte (&sv_client->netchan.message, svc_download);
  364                 MSG_WriteShort (&sv_client->netchan.message, -1);
  365                 MSG_WriteByte (&sv_client->netchan.message, 0);
  366                 return;
  367         }
  368 
  369         SV_NextDownload_f ();
  370         Com_DPrintf ("Downloading %s to %s\n", name, sv_client->name);
  371 }
  372 
  373 
  374 
  375 //============================================================================
  376 
  377 
  378 /*
  379 =================
  380 SV_Disconnect_f
  381 
  382 The client is going to disconnect, so remove the connection immediately
  383 =================
  384 */
  385 void SV_Disconnect_f (void)
  386 {
  387 //      SV_EndRedirect ();
  388         SV_DropClient (sv_client);      
  389 }
  390 
  391 
  392 /*
  393 ==================
  394 SV_ShowServerinfo_f
  395 
  396 Dumps the serverinfo info string
  397 ==================
  398 */
  399 void SV_ShowServerinfo_f (void)
  400 {
  401         Info_Print (Cvar_Serverinfo());
  402 }
  403 
  404 
  405 void SV_Nextserver (void)
  406 {
  407         char    *v;
  408 
  409         //ZOID, ss_pic can be nextserver'd in coop mode
  410         if (sv.state == ss_game || (sv.state == ss_pic && !Cvar_VariableValue("coop")))
  411                 return;         // can't nextserver while playing a normal game
  412 
  413         svs.spawncount++;       // make sure another doesn't sneak in
  414         v = Cvar_VariableString ("nextserver");
  415         if (!v[0])
  416                 Cbuf_AddText ("killserver\n");
  417         else
  418         {
  419                 Cbuf_AddText (v);
  420                 Cbuf_AddText ("\n");
  421         }
  422         Cvar_Set ("nextserver","");
  423 }
  424 
  425 /*
  426 ==================
  427 SV_Nextserver_f
  428 
  429 A cinematic has completed or been aborted by a client, so move
  430 to the next server,
  431 ==================
  432 */
  433 void SV_Nextserver_f (void)
  434 {
  435         if ( atoi(Cmd_Argv(1)) != svs.spawncount ) {
  436                 Com_DPrintf ("Nextserver() from wrong level, from %s\n", sv_client->name);
  437                 return;         // leftover from last server
  438         }
  439 
  440         Com_DPrintf ("Nextserver() from %s\n", sv_client->name);
  441 
  442         SV_Nextserver ();
  443 }
  444 
  445 typedef struct
  446 {
  447         char    *name;
  448         void    (*func) (void);
  449 } ucmd_t;
  450 
  451 ucmd_t ucmds[] =
  452 {
  453         // auto issued
  454         {"new", SV_New_f},
  455         {"configstrings", SV_Configstrings_f},
  456         {"baselines", SV_Baselines_f},
  457         {"begin", SV_Begin_f},
  458 
  459         {"nextserver", SV_Nextserver_f},
  460 
  461         {"disconnect", SV_Disconnect_f},
  462 
  463         // issued by hand at client consoles    
  464         {"info", SV_ShowServerinfo_f},
  465 
  466         {"download", SV_BeginDownload_f},
  467         {"nextdl", SV_NextDownload_f},
  468 
  469         {NULL, NULL}
  470 };
  471 
  472 /*
  473 ==================
  474 SV_ExecuteUserCommand
  475 ==================
  476 */
  477 void SV_ExecuteUserCommand (char *s)
  478 {
  479         ucmd_t  *u;
  480         
  481         Cmd_TokenizeString (s, true);
  482         sv_player = sv_client->edict;
  483 
  484 //      SV_BeginRedirect (RD_CLIENT);
  485 
  486         for (u=ucmds ; u->name ; u++)
  487                 if (!strcmp (Cmd_Argv(0), u->name) )
  488                 {
  489                         u->func ();
  490                         break;
  491                 }
  492 
  493         if (!u->name && sv.state == ss_game)
  494                 ge->ClientCommand (sv_player);
  495 
  496 //      SV_EndRedirect ();
  497 }
  498 
  499 /*
  500 ===========================================================================
  501 
  502 USER CMD EXECUTION
  503 
  504 ===========================================================================
  505 */
  506 
  507 
  508 
  509 void SV_ClientThink (client_t *cl, usercmd_t *cmd)
  510 
  511 {
  512         cl->commandMsec -= cmd->msec;
  513 
  514         if (cl->commandMsec < 0 && sv_enforcetime->value )
  515         {
  516                 Com_DPrintf ("commandMsec underflow from %s\n", cl->name);
  517                 return;
  518         }
  519 
  520         ge->ClientThink (cl->edict, cmd);
  521 }
  522 
  523 
  524 
  525 #define MAX_STRINGCMDS  8
  526 /*
  527 ===================
  528 SV_ExecuteClientMessage
  529 
  530 The current net_message is parsed for the given client
  531 ===================
  532 */
  533 void SV_ExecuteClientMessage (client_t *cl)
  534 {
  535         int             c;
  536         char    *s;
  537 
  538         usercmd_t       nullcmd;
  539         usercmd_t       oldest, oldcmd, newcmd;
  540         int             net_drop;
  541         int             stringCmdCount;
  542         int             checksum, calculatedChecksum;
  543         int             checksumIndex;
  544         qboolean        move_issued;
  545         int             lastframe;
  546 
  547         sv_client = cl;
  548         sv_player = sv_client->edict;
  549 
  550         // only allow one move command
  551         move_issued = false;
  552         stringCmdCount = 0;
  553 
  554         while (1)
  555         {
  556                 if (net_message.readcount > net_message.cursize)
  557                 {
  558                         Com_Printf ("SV_ReadClientMessage: badread\n");
  559                         SV_DropClient (cl);
  560                         return;
  561                 }       
  562 
  563                 c = MSG_ReadByte (&net_message);
  564                 if (c == -1)
  565                         break;
  566                                 
  567                 switch (c)
  568                 {
  569                 default:
  570                         Com_Printf ("SV_ReadClientMessage: unknown command char\n");
  571                         SV_DropClient (cl);
  572                         return;
  573                                                 
  574                 case clc_nop:
  575                         break;
  576 
  577                 case clc_userinfo:
  578                         strncpy (cl->userinfo, MSG_ReadString (&net_message), sizeof(cl->userinfo)-1);
  579                         SV_UserinfoChanged (cl);
  580                         break;
  581 
  582                 case clc_move:
  583                         if (move_issued)
  584                                 return;         // someone is trying to cheat...
  585 
  586                         move_issued = true;
  587                         checksumIndex = net_message.readcount;
  588                         checksum = MSG_ReadByte (&net_message);
  589                         lastframe = MSG_ReadLong (&net_message);
  590                         if (lastframe != cl->lastframe) {
  591                                 cl->lastframe = lastframe;
  592                                 if (cl->lastframe > 0) {
  593                                         cl->frame_latency[cl->lastframe&(LATENCY_COUNTS-1)] = 
  594                                                 svs.realtime - cl->frames[cl->lastframe & UPDATE_MASK].senttime;
  595                                 }
  596                         }
  597 
  598                         memset (&nullcmd, 0, sizeof(nullcmd));
  599                         MSG_ReadDeltaUsercmd (&net_message, &nullcmd, &oldest);
  600                         MSG_ReadDeltaUsercmd (&net_message, &oldest, &oldcmd);
  601                         MSG_ReadDeltaUsercmd (&net_message, &oldcmd, &newcmd);
  602 
  603                         if ( cl->state != cs_spawned )
  604                         {
  605                                 cl->lastframe = -1;
  606                                 break;
  607                         }
  608 
  609                         // if the checksum fails, ignore the rest of the packet
  610                         calculatedChecksum = COM_BlockSequenceCRCByte (
  611                                 net_message.data + checksumIndex + 1,
  612                                 net_message.readcount - checksumIndex - 1,
  613                                 cl->netchan.incoming_sequence);
  614 
  615                         if (calculatedChecksum != checksum)
  616                         {
  617                                 Com_DPrintf ("Failed command checksum for %s (%d != %d)/%d\n", 
  618                                         cl->name, calculatedChecksum, checksum, 
  619                                         cl->netchan.incoming_sequence);
  620                                 return;
  621                         }
  622 
  623                         if (!sv_paused->value)
  624                         {
  625                                 net_drop = cl->netchan.dropped;
  626                                 if (net_drop < 20)
  627                                 {
  628 
  629 //if (net_drop > 2)
  630 
  631 //      Com_Printf ("drop %i\n", net_drop);
  632                                         while (net_drop > 2)
  633                                         {
  634                                                 SV_ClientThink (cl, &cl->lastcmd);
  635 
  636                                                 net_drop--;
  637                                         }
  638                                         if (net_drop > 1)
  639                                                 SV_ClientThink (cl, &oldest);
  640 
  641                                         if (net_drop > 0)
  642                                                 SV_ClientThink (cl, &oldcmd);
  643 
  644                                 }
  645                                 SV_ClientThink (cl, &newcmd);
  646                         }
  647 
  648                         cl->lastcmd = newcmd;
  649                         break;
  650 
  651                 case clc_stringcmd:     
  652                         s = MSG_ReadString (&net_message);
  653 
  654                         // malicious users may try using too many string commands
  655                         if (++stringCmdCount < MAX_STRINGCMDS)
  656                                 SV_ExecuteUserCommand (s);
  657 
  658                         if (cl->state == cs_zombie)
  659                                 return; // disconnect command
  660                         break;
  661                 }
  662         }
  663 }
  664 
  665