sv_user.c (15042B)
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