1/* 2=========================================================================== 3Copyright (C) 1999-2005 Id Software, Inc. 4 5This file is part of Quake III Arena source code. 6 7Quake III Arena source code is free software; you can redistribute it 8and/or modify it under the terms of the GNU General Public License as 9published by the Free Software Foundation; either version 2 of the License, 10or (at your option) any later version. 11 12Quake III Arena source code is distributed in the hope that it will be 13useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15GNU General Public License for more details. 16 17You should have received a copy of the GNU General Public License 18along with Quake III Arena source code; if not, write to the Free Software 19Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20=========================================================================== 21*/ 22// cl_main.c -- client main loop 23 24#include "client.h" 25#include <limits.h> 26 27#ifdef USE_MUMBLE 28#include "libmumblelink.h" 29#endif 30 31#ifdef USE_MUMBLE 32cvar_t *cl_useMumble; 33cvar_t *cl_mumbleScale; 34#endif 35 36#ifdef USE_VOIP 37cvar_t *cl_voipUseVAD; 38cvar_t *cl_voipVADThreshold; 39cvar_t *cl_voipSend; 40cvar_t *cl_voipSendTarget; 41cvar_t *cl_voipGainDuringCapture; 42cvar_t *cl_voipCaptureMult; 43cvar_t *cl_voipShowMeter; 44cvar_t *cl_voip; 45#endif 46 47cvar_t *cl_nodelta; 48cvar_t *cl_debugMove; 49 50cvar_t *cl_noprint; 51cvar_t *cl_motd; 52 53cvar_t *rcon_client_password; 54cvar_t *rconAddress; 55 56cvar_t *cl_timeout; 57cvar_t *cl_maxpackets; 58cvar_t *cl_packetdup; 59cvar_t *cl_timeNudge; 60cvar_t *cl_showTimeDelta; 61cvar_t *cl_freezeDemo; 62 63cvar_t *cl_shownet; 64cvar_t *cl_showSend; 65cvar_t *cl_timedemo; 66cvar_t *cl_timedemoLog; 67cvar_t *cl_autoRecordDemo; 68cvar_t *cl_aviFrameRate; 69cvar_t *cl_aviMotionJpeg; 70cvar_t *cl_forceavidemo; 71 72cvar_t *cl_freelook; 73cvar_t *cl_sensitivity; 74 75cvar_t *cl_mouseAccel; 76cvar_t *cl_mouseAccelOffset; 77cvar_t *cl_mouseAccelStyle; 78cvar_t *cl_showMouseRate; 79 80cvar_t *m_pitch; 81cvar_t *m_yaw; 82cvar_t *m_forward; 83cvar_t *m_side; 84cvar_t *m_filter; 85 86cvar_t *cl_activeAction; 87 88cvar_t *cl_motdString; 89 90cvar_t *cl_allowDownload; 91cvar_t *cl_conXOffset; 92cvar_t *cl_inGameVideo; 93 94cvar_t *cl_serverStatusResendTime; 95cvar_t *cl_trn; 96 97cvar_t *cl_lanForcePackets; 98 99cvar_t *cl_guidServerUniq; 100 101cvar_t *cl_consoleKeys; 102 103cvar_t *cl_consoleType; 104cvar_t *cl_consoleColor[4]; 105 106cvar_t *cl_consoleHeight; 107 108clientActive_t cl; 109clientConnection_t clc; 110clientStatic_t cls; 111vm_t *cgvm; 112 113// Structure containing functions exported from refresh DLL 114refexport_t re; 115 116ping_t cl_pinglist[MAX_PINGREQUESTS]; 117 118typedef struct serverStatus_s 119{ 120 char string[BIG_INFO_STRING]; 121 netadr_t address; 122 int time, startTime; 123 qboolean pending; 124 qboolean print; 125 qboolean retrieved; 126} serverStatus_t; 127 128serverStatus_t cl_serverStatusList[MAX_SERVERSTATUSREQUESTS]; 129int serverStatusCount; 130 131#if defined __USEA3D && defined __A3D_GEOM 132 void hA3Dg_ExportRenderGeom (refexport_t *incoming_re); 133#endif 134 135extern void SV_BotFrame( int time ); 136void CL_CheckForResend( void ); 137void CL_ShowIP_f(void); 138void CL_ServerStatus_f(void); 139void CL_ServerStatusResponse( netadr_t from, msg_t *msg ); 140 141/* 142=============== 143CL_CDDialog 144 145Called by Com_Error when a cd is needed 146=============== 147*/ 148void CL_CDDialog( void ) { 149 cls.cddialog = qtrue; // start it next frame 150} 151 152#ifdef USE_MUMBLE 153static 154void CL_UpdateMumble(void) 155{ 156 vec3_t pos, forward, up; 157 float scale = cl_mumbleScale->value; 158 float tmp; 159 160 if(!cl_useMumble->integer) 161 return; 162 163 // !!! FIXME: not sure if this is even close to correct. 164 AngleVectors( cl.snap.ps.viewangles, forward, NULL, up); 165 166 pos[0] = cl.snap.ps.origin[0] * scale; 167 pos[1] = cl.snap.ps.origin[2] * scale; 168 pos[2] = cl.snap.ps.origin[1] * scale; 169 170 tmp = forward[1]; 171 forward[1] = forward[2]; 172 forward[2] = tmp; 173 174 tmp = up[1]; 175 up[1] = up[2]; 176 up[2] = tmp; 177 178 if(cl_useMumble->integer > 1) { 179 fprintf(stderr, "%f %f %f, %f %f %f, %f %f %f\n", 180 pos[0], pos[1], pos[2], 181 forward[0], forward[1], forward[2], 182 up[0], up[1], up[2]); 183 } 184 185 mumble_update_coordinates(pos, forward, up); 186} 187#endif 188 189 190#ifdef USE_VOIP 191static 192void CL_UpdateVoipIgnore(const char *idstr, qboolean ignore) 193{ 194 if ((*idstr >= '0') && (*idstr <= '9')) { 195 const int id = atoi(idstr); 196 if ((id >= 0) && (id < MAX_CLIENTS)) { 197 clc.voipIgnore[id] = ignore; 198 CL_AddReliableCommand(va("voip %s %d", 199 ignore ? "ignore" : "unignore", id), qfalse); 200 Com_Printf("VoIP: %s ignoring player #%d\n", 201 ignore ? "Now" : "No longer", id); 202 return; 203 } 204 } 205 Com_Printf("VoIP: invalid player ID#\n"); 206} 207 208static 209void CL_UpdateVoipGain(const char *idstr, float gain) 210{ 211 if ((*idstr >= '0') && (*idstr <= '9')) { 212 const int id = atoi(idstr); 213 if (gain < 0.0f) 214 gain = 0.0f; 215 if ((id >= 0) && (id < MAX_CLIENTS)) { 216 clc.voipGain[id] = gain; 217 Com_Printf("VoIP: player #%d gain now set to %f\n", id, gain); 218 } 219 } 220} 221 222void CL_Voip_f( void ) 223{ 224 const char *cmd = Cmd_Argv(1); 225 const char *reason = NULL; 226 227 if (cls.state != CA_ACTIVE) 228 reason = "Not connected to a server"; 229 else if (!clc.speexInitialized) 230 reason = "Speex not initialized"; 231 else if (!cl_connectedToVoipServer) 232 reason = "Server doesn't support VoIP"; 233 else if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")) 234 reason = "running in single-player mode"; 235 236 if (reason != NULL) { 237 Com_Printf("VoIP: command ignored: %s\n", reason); 238 return; 239 } 240 241 if (strcmp(cmd, "ignore") == 0) { 242 CL_UpdateVoipIgnore(Cmd_Argv(2), qtrue); 243 } else if (strcmp(cmd, "unignore") == 0) { 244 CL_UpdateVoipIgnore(Cmd_Argv(2), qfalse); 245 } else if (strcmp(cmd, "gain") == 0) { 246 if (Cmd_Argc() > 3) { 247 CL_UpdateVoipGain(Cmd_Argv(2), atof(Cmd_Argv(3))); 248 } else if (Q_isanumber(Cmd_Argv(2))) { 249 int id = atoi(Cmd_Argv(2)); 250 if (id >= 0 && id < MAX_CLIENTS) { 251 Com_Printf("VoIP: current gain for player #%d " 252 "is %f\n", id, clc.voipGain[id]); 253 } else { 254 Com_Printf("VoIP: invalid player ID#\n"); 255 } 256 } else { 257 Com_Printf("usage: voip gain <playerID#> [value]\n"); 258 } 259 } else if (strcmp(cmd, "muteall") == 0) { 260 Com_Printf("VoIP: muting incoming voice\n"); 261 CL_AddReliableCommand("voip muteall", qfalse); 262 clc.voipMuteAll = qtrue; 263 } else if (strcmp(cmd, "unmuteall") == 0) { 264 Com_Printf("VoIP: unmuting incoming voice\n"); 265 CL_AddReliableCommand("voip unmuteall", qfalse); 266 clc.voipMuteAll = qfalse; 267 } else { 268 Com_Printf("usage: voip [un]ignore <playerID#>\n" 269 " voip [un]muteall\n" 270 " voip gain <playerID#> [value]\n"); 271 } 272} 273 274 275static 276void CL_VoipNewGeneration(void) 277{ 278 // don't have a zero generation so new clients won't match, and don't 279 // wrap to negative so MSG_ReadLong() doesn't "fail." 280 clc.voipOutgoingGeneration++; 281 if (clc.voipOutgoingGeneration <= 0) 282 clc.voipOutgoingGeneration = 1; 283 clc.voipPower = 0.0f; 284 clc.voipOutgoingSequence = 0; 285} 286 287/* 288=============== 289CL_CaptureVoip 290 291Record more audio from the hardware if required and encode it into Speex 292 data for later transmission. 293=============== 294*/ 295static 296void CL_CaptureVoip(void) 297{ 298 const float audioMult = cl_voipCaptureMult->value; 299 const qboolean useVad = (cl_voipUseVAD->integer != 0); 300 qboolean initialFrame = qfalse; 301 qboolean finalFrame = qfalse; 302 303#if USE_MUMBLE 304 // if we're using Mumble, don't try to handle VoIP transmission ourselves. 305 if (cl_useMumble->integer) 306 return; 307#endif 308 309 if (!clc.speexInitialized) 310 return; // just in case this gets called at a bad time. 311 312 if (clc.voipOutgoingDataSize > 0) 313 return; // packet is pending transmission, don't record more yet. 314 315 if (cl_voipUseVAD->modified) { 316 Cvar_Set("cl_voipSend", (useVad) ? "1" : "0"); 317 cl_voipUseVAD->modified = qfalse; 318 } 319 320 if ((useVad) && (!cl_voipSend->integer)) 321 Cvar_Set("cl_voipSend", "1"); // lots of things reset this. 322 323 if (cl_voipSend->modified) { 324 qboolean dontCapture = qfalse; 325 if (cls.state != CA_ACTIVE) 326 dontCapture = qtrue; // not connected to a server. 327 else if (!cl_connectedToVoipServer) 328 dontCapture = qtrue; // server doesn't support VoIP. 329 else if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")) 330 dontCapture = qtrue; // single player game. 331 else if (clc.demoplaying) 332 dontCapture = qtrue; // playing back a demo. 333 else if ( cl_voip->integer == 0 ) 334 dontCapture = qtrue; // client has VoIP support disabled. 335 else if ( audioMult == 0.0f ) 336 dontCapture = qtrue; // basically silenced incoming audio. 337 338 cl_voipSend->modified = qfalse; 339 340 if (dontCapture) { 341 cl_voipSend->integer = 0; 342 return; 343 } 344 345 if (cl_voipSend->integer) { 346 initialFrame = qtrue; 347 } else { 348 finalFrame = qtrue; 349 } 350 } 351 352 // try to get more audio data from the sound card... 353 354 if (initialFrame) { 355 float gain = cl_voipGainDuringCapture->value; 356 if (gain < 0.0f) gain = 0.0f; else if (gain >= 1.0f) gain = 1.0f; 357 S_MasterGain(cl_voipGainDuringCapture->value); 358 S_StartCapture(); 359 CL_VoipNewGeneration(); 360 } 361 362 if ((cl_voipSend->integer) || (finalFrame)) { // user wants to capture audio? 363 int samples = S_AvailableCaptureSamples(); 364 const int mult = (finalFrame) ? 1 : 12; // 12 == 240ms of audio. 365 366 // enough data buffered in audio hardware to process yet? 367 if (samples >= (clc.speexFrameSize * mult)) { 368 // audio capture is always MONO16 (and that's what speex wants!). 369 // 2048 will cover 12 uncompressed frames in narrowband mode. 370 static int16_t sampbuffer[2048]; 371 float voipPower = 0.0f; 372 int speexFrames = 0; 373 int wpos = 0; 374 int pos = 0; 375 376 if (samples > (clc.speexFrameSize * 12)) 377 samples = (clc.speexFrameSize * 12); 378 379 // !!! FIXME: maybe separate recording from encoding, so voipPower 380 // !!! FIXME: updates faster than 4Hz? 381 382 samples -= samples % clc.speexFrameSize; 383 S_Capture(samples, (byte *) sampbuffer); // grab from audio card. 384 385 // this will probably generate multiple speex packets each time. 386 while (samples > 0) { 387 int16_t *sampptr = &sampbuffer[pos]; 388 int i, bytes; 389 390 // preprocess samples to remove noise... 391 speex_preprocess_run(clc.speexPreprocessor, sampptr); 392 393 // check the "power" of this packet... 394 for (i = 0; i < clc.speexFrameSize; i++) { 395 const float flsamp = (float) sampptr[i]; 396 const float s = fabs(flsamp); 397 voipPower += s * s; 398 sampptr[i] = (int16_t) ((flsamp) * audioMult); 399 } 400 401 // encode raw audio samples into Speex data... 402 speex_bits_reset(&clc.speexEncoderBits); 403 speex_encode_int(clc.speexEncoder, sampptr, 404 &clc.speexEncoderBits); 405 bytes = speex_bits_write(&clc.speexEncoderBits, 406 (char *) &clc.voipOutgoingData[wpos+1], 407 sizeof (clc.voipOutgoingData) - (wpos+1)); 408 assert((bytes > 0) && (bytes < 256)); 409 clc.voipOutgoingData[wpos] = (byte) bytes; 410 wpos += bytes + 1; 411 412 // look at the data for the next packet... 413 pos += clc.speexFrameSize; 414 samples -= clc.speexFrameSize; 415 speexFrames++; 416 } 417 418 clc.voipPower = (voipPower / (32768.0f * 32768.0f * 419 ((float) (clc.speexFrameSize * speexFrames)))) * 420 100.0f; 421 422 if ((useVad) && (clc.voipPower < cl_voipVADThreshold->value)) { 423 CL_VoipNewGeneration(); // no "talk" for at least 1/4 second. 424 } else { 425 clc.voipOutgoingDataSize = wpos; 426 clc.voipOutgoingDataFrames = speexFrames; 427 428 Com_DPrintf("VoIP: Send %d frames, %d bytes, %f power\n", 429 speexFrames, wpos, clc.voipPower); 430 431 #if 0 432 static FILE *encio = NULL; 433 if (encio == NULL) encio = fopen("voip-outgoing-encoded.bin", "wb"); 434 if (encio != NULL) { fwrite(clc.voipOutgoingData, wpos, 1, encio); fflush(encio); } 435 static FILE *decio = NULL; 436 if (decio == NULL) decio = fopen("voip-outgoing-decoded.bin", "wb"); 437 if (decio != NULL) { fwrite(sampbuffer, speexFrames * clc.speexFrameSize * 2, 1, decio); fflush(decio); } 438 #endif 439 } 440 } 441 } 442 443 // User requested we stop recording, and we've now processed the last of 444 // any previously-buffered data. Pause the capture device, etc. 445 if (finalFrame) { 446 S_StopCapture(); 447 S_MasterGain(1.0f); 448 clc.voipPower = 0.0f; // force this value so it doesn't linger. 449 } 450} 451#endif 452 453/* 454======================================================================= 455 456CLIENT RELIABLE COMMAND COMMUNICATION 457 458======================================================================= 459*/ 460 461/* 462====================== 463CL_AddReliableCommand 464 465The given command will be transmitted to the server, and is gauranteed to 466not have future usercmd_t executed before it is executed 467====================== 468*/ 469void CL_AddReliableCommand(const char *cmd, qboolean isDisconnectCmd) 470{ 471 int unacknowledged = clc.reliableSequence - clc.reliableAcknowledge; 472 473 // if we would be losing an old command that hasn't been acknowledged, 474 // we must drop the connection 475 // also leave one slot open for the disconnect command in this case. 476 477 if ((isDisconnectCmd && unacknowledged > MAX_RELIABLE_COMMANDS) || 478 (!isDisconnectCmd && unacknowledged >= MAX_RELIABLE_COMMANDS)) 479 { 480 if(com_errorEntered) 481 return; 482 else 483 Com_Error(ERR_DROP, "Client command overflow"); 484 } 485 486 Q_strncpyz(clc.reliableCommands[++clc.reliableSequence & (MAX_RELIABLE_COMMANDS - 1)], 487 cmd, sizeof(*clc.reliableCommands)); 488} 489 490/* 491====================== 492CL_ChangeReliableCommand 493====================== 494*/ 495void CL_ChangeReliableCommand( void ) { 496 int r, index, l; 497 498 r = clc.reliableSequence - (random() * 5); 499 index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); 500 l = strlen(clc.reliableCommands[ index ]); 501 if ( l >= MAX_STRING_CHARS - 1 ) { 502 l = MAX_STRING_CHARS - 2; 503 } 504 clc.reliableCommands[ index ][ l ] = '\n'; 505 clc.reliableCommands[ index ][ l+1 ] = '\0'; 506} 507 508/* 509======================================================================= 510 511CLIENT SIDE DEMO RECORDING 512 513======================================================================= 514*/ 515 516/* 517==================== 518CL_WriteDemoMessage 519 520Dumps the current net message, prefixed by the length 521==================== 522*/ 523 524void CL_WriteDemoMessage ( msg_t *msg, int headerBytes ) { 525 int len, swlen; 526 527 // write the packet sequence 528 len = clc.serverMessageSequence; 529 swlen = LittleLong( len ); 530 FS_Write (&swlen, 4, clc.demofile); 531 532 // skip the packet sequencing information 533 len = msg->cursize - headerBytes; 534 swlen = LittleLong(len); 535 FS_Write (&swlen, 4, clc.demofile); 536 FS_Write ( msg->data + headerBytes, len, clc.demofile ); 537} 538 539 540/* 541==================== 542CL_StopRecording_f 543 544stop recording a demo 545==================== 546*/ 547void CL_StopRecord_f( void ) { 548 int len; 549 550 if ( !clc.demorecording ) { 551 Com_Printf ("Not recording a demo.\n"); 552 return; 553 } 554 555 // finish up 556 len = -1; 557 FS_Write (&len, 4, clc.demofile); 558 FS_Write (&len, 4, clc.demofile); 559 FS_FCloseFile (clc.demofile); 560 clc.demofile = 0; 561 clc.demorecording = qfalse; 562 clc.spDemoRecording = qfalse; 563 Com_Printf ("Stopped demo.\n"); 564} 565 566/* 567================== 568CL_DemoFilename 569================== 570*/ 571void CL_DemoFilename( int number, char *fileName ) { 572 int a,b,c,d; 573 574 if(number < 0 || number > 9999) 575 number = 9999; 576 577 a = number / 1000; 578 number -= a*1000; 579 b = number / 100; 580 number -= b*100; 581 c = number / 10; 582 number -= c*10; 583 d = number; 584 585 Com_sprintf( fileName, MAX_OSPATH, "demo%i%i%i%i" 586 , a, b, c, d ); 587} 588 589/* 590==================== 591CL_Record_f 592 593record <demoname> 594 595Begins recording a demo from the current position 596==================== 597*/ 598static char demoName[MAX_QPATH]; // compiler bug workaround 599void CL_Record_f( void ) { 600 char name[MAX_OSPATH]; 601 byte bufData[MAX_MSGLEN]; 602 msg_t buf; 603 int i; 604 int len; 605 entityState_t *ent; 606 entityState_t nullstate; 607 char *s; 608 609 if ( Cmd_Argc() > 2 ) { 610 Com_Printf ("record <demoname>\n"); 611 return; 612 } 613 614 if ( clc.demorecording ) { 615 if (!clc.spDemoRecording) { 616 Com_Printf ("Already recording.\n"); 617 } 618 return; 619 } 620 621 if ( cls.state != CA_ACTIVE ) { 622 Com_Printf ("You must be in a level to record.\n"); 623 return; 624 } 625 626 // sync 0 doesn't prevent recording, so not forcing it off .. everyone does g_sync 1 ; record ; g_sync 0 .. 627 if ( NET_IsLocalAddress( clc.serverAddress ) && !Cvar_VariableValue( "g_synchronousClients" ) ) { 628 Com_Printf (S_COLOR_YELLOW "WARNING: You should set 'g_synchronousClients 1' for smoother demo recording\n"); 629 } 630 631 if ( Cmd_Argc() == 2 ) { 632 s = Cmd_Argv(1); 633 Q_strncpyz( demoName, s, sizeof( demoName ) ); 634 Com_sprintf (name, sizeof(name), "demos/%s.dm_%d", demoName, PROTOCOL_VERSION ); 635 } else { 636 int number; 637 638 // scan for a free demo name 639 for ( number = 0 ; number <= 9999 ; number++ ) { 640 CL_DemoFilename( number, demoName ); 641 Com_sprintf (name, sizeof(name), "demos/%s.dm_%d", demoName, PROTOCOL_VERSION ); 642 643 if (!FS_FileExists(name)) 644 break; // file doesn't exist 645 } 646 } 647 648 // open the demo file 649 650 Com_Printf ("recording to %s.\n", name); 651 clc.demofile = FS_FOpenFileWrite( name ); 652 if ( !clc.demofile ) { 653 Com_Printf ("ERROR: couldn't open.\n"); 654 return; 655 } 656 clc.demorecording = qtrue; 657 if (Cvar_VariableValue("ui_recordSPDemo")) { 658 clc.spDemoRecording = qtrue; 659 } else { 660 clc.spDemoRecording = qfalse; 661 } 662 663 664 Q_strncpyz( clc.demoName, demoName, sizeof( clc.demoName ) ); 665 666 // don't start saving messages until a non-delta compressed message is received 667 clc.demowaiting = qtrue; 668 669 // write out the gamestate message 670 MSG_Init (&buf, bufData, sizeof(bufData)); 671 MSG_Bitstream(&buf); 672 673 // NOTE, MRE: all server->client messages now acknowledge 674 MSG_WriteLong( &buf, clc.reliableSequence ); 675 676 MSG_WriteByte (&buf, svc_gamestate); 677 MSG_WriteLong (&buf, clc.serverCommandSequence ); 678 679 // configstrings 680 for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { 681 if ( !cl.gameState.stringOffsets[i] ) { 682 continue; 683 } 684 s = cl.gameState.stringData + cl.gameState.stringOffsets[i]; 685 MSG_WriteByte (&buf, svc_configstring); 686 MSG_WriteShort (&buf, i); 687 MSG_WriteBigString (&buf, s); 688 } 689 690 // baselines 691 Com_Memset (&nullstate, 0, sizeof(nullstate)); 692 for ( i = 0; i < MAX_GENTITIES ; i++ ) { 693 ent = &cl.entityBaselines[i]; 694 if ( !ent->number ) { 695 continue; 696 } 697 MSG_WriteByte (&buf, svc_baseline); 698 MSG_WriteDeltaEntity (&buf, &nullstate, ent, qtrue ); 699 } 700 701 MSG_WriteByte( &buf, svc_EOF ); 702 703 // finished writing the gamestate stuff 704 705 // write the client num 706 MSG_WriteLong(&buf, clc.clientNum); 707 // write the checksum feed 708 MSG_WriteLong(&buf, clc.checksumFeed); 709 710 // finished writing the client packet 711 MSG_WriteByte( &buf, svc_EOF ); 712 713 // write it to the demo file 714 len = LittleLong( clc.serverMessageSequence - 1 ); 715 FS_Write (&len, 4, clc.demofile); 716 717 len = LittleLong (buf.cursize); 718 FS_Write (&len, 4, clc.demofile); 719 FS_Write (buf.data, buf.cursize, clc.demofile); 720 721 // the rest of the demo file will be copied from net messages 722} 723 724/* 725======================================================================= 726 727CLIENT SIDE DEMO PLAYBACK 728 729======================================================================= 730*/ 731 732/* 733================= 734CL_DemoFrameDurationSDev 735================= 736*/ 737static float CL_DemoFrameDurationSDev( void ) 738{ 739 int i; 740 int numFrames; 741 float mean = 0.0f; 742 float variance = 0.0f; 743 744 if( ( clc.timeDemoFrames - 1 ) > MAX_TIMEDEMO_DURATIONS ) 745 numFrames = MAX_TIMEDEMO_DURATIONS; 746 else 747 numFrames = clc.timeDemoFrames - 1; 748 749 for( i = 0; i < numFrames; i++ ) 750 mean += clc.timeDemoDurations[ i ]; 751 mean /= numFrames; 752 753 for( i = 0; i < numFrames; i++ ) 754 { 755 float x = clc.timeDemoDurations[ i ]; 756 757 variance += ( ( x - mean ) * ( x - mean ) ); 758 } 759 variance /= numFrames; 760 761 return sqrt( variance ); 762} 763 764/* 765================= 766CL_DemoCompleted 767================= 768*/ 769void CL_DemoCompleted( void ) 770{ 771 char buffer[ MAX_STRING_CHARS ]; 772 773 if( cl_timedemo && cl_timedemo->integer ) 774 { 775 int time; 776 777 time = Sys_Milliseconds() - clc.timeDemoStart; 778 if( time > 0 ) 779 { 780 // Millisecond times are frame durations: 781 // minimum/average/maximum/std deviation 782 Com_sprintf( buffer, sizeof( buffer ), 783 "%i frames %3.1f seconds %3.1f fps %d.0/%.1f/%d.0/%.1f ms\n", 784 clc.timeDemoFrames, 785 time/1000.0, 786 clc.timeDemoFrames*1000.0 / time, 787 clc.timeDemoMinDuration, 788 time / (float)clc.timeDemoFrames, 789 clc.timeDemoMaxDuration, 790 CL_DemoFrameDurationSDev( ) ); 791 Com_Printf( "%s", buffer ); 792 793 // Write a log of all the frame durations 794 if( cl_timedemoLog && strlen( cl_timedemoLog->string ) > 0 ) 795 { 796 int i; 797 int numFrames; 798 fileHandle_t f; 799 800 if( ( clc.timeDemoFrames - 1 ) > MAX_TIMEDEMO_DURATIONS ) 801 numFrames = MAX_TIMEDEMO_DURATIONS; 802 else 803 numFrames = clc.timeDemoFrames - 1; 804 805 f = FS_FOpenFileWrite( cl_timedemoLog->string ); 806 if( f ) 807 { 808 FS_Printf( f, "# %s", buffer ); 809 810 for( i = 0; i < numFrames; i++ ) 811 FS_Printf( f, "%d\n", clc.timeDemoDurations[ i ] ); 812 813 FS_FCloseFile( f ); 814 Com_Printf( "%s written\n", cl_timedemoLog->string ); 815 } 816 else 817 { 818 Com_Printf( "Couldn't open %s for writing\n", 819 cl_timedemoLog->string ); 820 } 821 } 822 } 823 } 824 825 CL_Disconnect( qtrue ); 826 CL_NextDemo(); 827} 828 829/* 830================= 831CL_ReadDemoMessage 832================= 833*/ 834void CL_ReadDemoMessage( void ) { 835 int r; 836 msg_t buf; 837 byte bufData[ MAX_MSGLEN ]; 838 int s; 839 840 if ( !clc.demofile ) { 841 CL_DemoCompleted (); 842 return; 843 } 844 845 // get the sequence number 846 r = FS_Read( &s, 4, clc.demofile); 847 if ( r != 4 ) { 848 CL_DemoCompleted (); 849 return; 850 } 851 clc.serverMessageSequence = LittleLong( s ); 852 853 // init the message 854 MSG_Init( &buf, bufData, sizeof( bufData ) ); 855 856 // get the length 857 r = FS_Read (&buf.cursize, 4, clc.demofile); 858 if ( r != 4 ) { 859 CL_DemoCompleted (); 860 return; 861 } 862 buf.cursize = LittleLong( buf.cursize ); 863 if ( buf.cursize == -1 ) { 864 CL_DemoCompleted (); 865 return; 866 } 867 if ( buf.cursize > buf.maxsize ) { 868 Com_Error (ERR_DROP, "CL_ReadDemoMessage: demoMsglen > MAX_MSGLEN"); 869 } 870 r = FS_Read( buf.data, buf.cursize, clc.demofile ); 871 if ( r != buf.cursize ) { 872 Com_Printf( "Demo file was truncated.\n"); 873 CL_DemoCompleted (); 874 return; 875 } 876 877 clc.lastPacketTime = cls.realtime; 878 buf.readcount = 0; 879 CL_ParseServerMessage( &buf ); 880} 881 882/* 883==================== 884CL_WalkDemoExt 885==================== 886*/ 887static void CL_WalkDemoExt(char *arg, char *name, int *demofile) 888{ 889 int i = 0; 890 *demofile = 0; 891 while(demo_protocols[i]) 892 { 893 Com_sprintf (name, MAX_OSPATH, "demos/%s.dm_%d", arg, demo_protocols[i]); 894 FS_FOpenFileRead( name, demofile, qtrue ); 895 if (*demofile) 896 { 897 Com_Printf("Demo file: %s\n", name); 898 break; 899 } 900 else 901 Com_Printf("Not found: %s\n", name); 902 i++; 903 } 904} 905 906/* 907==================== 908CL_CompleteDemoName 909==================== 910*/ 911static void CL_CompleteDemoName( char *args, int argNum ) 912{ 913 if( argNum == 2 ) 914 { 915 char demoExt[ 16 ]; 916 917 Com_sprintf( demoExt, sizeof( demoExt ), ".dm_%d", PROTOCOL_VERSION ); 918 Field_CompleteFilename( "demos", demoExt, qtrue ); 919 } 920} 921 922/* 923==================== 924CL_PlayDemo_f 925 926demo <demoname> 927 928==================== 929*/ 930void CL_PlayDemo_f( void ) { 931 char name[MAX_OSPATH]; 932 char *arg, *ext_test; 933 int protocol, i; 934 char retry[MAX_OSPATH]; 935 936 if (Cmd_Argc() != 2) { 937 Com_Printf ("demo <demoname>\n"); 938 return; 939 } 940 941 // make sure a local server is killed 942 // 2 means don't force disconnect of local client 943 Cvar_Set( "sv_killserver", "2" ); 944 945 // open the demo file 946 arg = Cmd_Argv(1); 947 948 CL_Disconnect( qtrue ); 949 950 // check for an extension .dm_?? (?? is protocol) 951 ext_test = arg + strlen(arg) - 6; 952 if ((strlen(arg) > 6) && (ext_test[0] == '.') && 953 ((ext_test[1] == 'd') || (ext_test[1] == 'D')) && 954 ((ext_test[2] == 'm') || (ext_test[2] == 'M')) && 955 (ext_test[3] == '_')) 956 { 957 protocol = atoi(ext_test+4); 958 i=0; 959 while(demo_protocols[i]) 960 { 961 if (demo_protocols[i] == protocol) 962 break; 963 i++; 964 } 965 if (demo_protocols[i]) 966 { 967 Com_sprintf (name, sizeof(name), "demos/%s", arg); 968 FS_FOpenFileRead( name, &clc.demofile, qtrue ); 969 } else { 970 Com_Printf("Protocol %d not supported for demos\n", protocol); 971 Q_strncpyz(retry, arg, sizeof(retry)); 972 retry[strlen(retry)-6] = 0; 973 CL_WalkDemoExt( retry, name, &clc.demofile ); 974 } 975 } else { 976 CL_WalkDemoExt( arg, name, &clc.demofile ); 977 } 978 979 if (!clc.demofile) { 980 Com_Error( ERR_DROP, "couldn't open %s", name); 981 return; 982 } 983 Q_strncpyz( clc.demoName, Cmd_Argv(1), sizeof( clc.demoName ) ); 984 985 Con_Close(); 986 987 cls.state = CA_CONNECTED; 988 clc.demoplaying = qtrue; 989 Q_strncpyz( cls.servername, Cmd_Argv(1), sizeof( cls.servername ) ); 990 991 // read demo messages until connected 992 while ( cls.state >= CA_CONNECTED && cls.state < CA_PRIMED ) { 993 CL_ReadDemoMessage(); 994 } 995 // don't get the first snapshot this frame, to prevent the long 996 // time from the gamestate load from messing causing a time skip 997 clc.firstDemoFrameSkipped = qfalse; 998} 999 1000 1001/* 1002==================== 1003CL_StartDemoLoop 1004 1005Closing the main menu will restart the demo loop 1006==================== 1007*/ 1008void CL_StartDemoLoop( void ) { 1009 // start the demo loop again 1010 Cbuf_AddText ("d1\n"); 1011 Key_SetCatcher( 0 ); 1012} 1013 1014/* 1015================== 1016CL_NextDemo 1017 1018Called when a demo or cinematic finishes 1019If the "nextdemo" cvar is set, that command will be issued 1020================== 1021*/ 1022void CL_NextDemo( void ) { 1023 char v[MAX_STRING_CHARS]; 1024 1025 Q_strncpyz( v, Cvar_VariableString ("nextdemo"), sizeof(v) ); 1026 v[MAX_STRING_CHARS-1] = 0; 1027 Com_DPrintf("CL_NextDemo: %s\n", v ); 1028 if (!v[0]) { 1029 return; 1030 } 1031 1032 Cvar_Set ("nextdemo",""); 1033 Cbuf_AddText (v); 1034 Cbuf_AddText ("\n"); 1035 Cbuf_Execute(); 1036} 1037 1038 1039//====================================================================== 1040 1041/* 1042===================== 1043CL_ShutdownAll 1044===================== 1045*/ 1046void CL_ShutdownAll(void) { 1047 1048#ifdef USE_CURL 1049 CL_cURL_Shutdown(); 1050#endif 1051 // clear sounds 1052 S_DisableSounds(); 1053 // shutdown CGame 1054 CL_ShutdownCGame(); 1055 // shutdown UI 1056 CL_ShutdownUI(); 1057 1058 // shutdown the renderer 1059 if ( re.Shutdown ) { 1060 re.Shutdown( qfalse ); // don't destroy window or context 1061 } 1062 1063 cls.uiStarted = qfalse; 1064 cls.cgameStarted = qfalse; 1065 cls.rendererStarted = qfalse; 1066 cls.soundRegistered = qfalse; 1067} 1068 1069/* 1070================= 1071CL_FlushMemory 1072 1073Called by CL_MapLoading, CL_Connect_f, CL_PlayDemo_f, and CL_ParseGamestate the only 1074ways a client gets into a game 1075Also called by Com_Error 1076================= 1077*/ 1078void CL_FlushMemory( void ) { 1079 1080 // shutdown all the client stuff 1081 CL_ShutdownAll(); 1082 1083 // if not running a server clear the whole hunk 1084 if ( !com_sv_running->integer ) { 1085 // clear the whole hunk 1086 Hunk_Clear(); 1087 // clear collision map data 1088 CM_ClearMap(); 1089 } 1090 else { 1091 // clear all the client data on the hunk 1092 Hunk_ClearToMark(); 1093 } 1094 1095 CL_StartHunkUsers( qfalse ); 1096} 1097 1098/* 1099===================== 1100CL_MapLoading 1101 1102A local server is starting to load a map, so update the 1103screen to let the user know about it, then dump all client 1104memory on the hunk from cgame, ui, and renderer 1105===================== 1106*/ 1107void CL_MapLoading( void ) { 1108 if ( com_dedicated->integer ) { 1109 cls.state = CA_DISCONNECTED; 1110 Key_SetCatcher( KEYCATCH_CONSOLE ); 1111 return; 1112 } 1113 1114 if ( !com_cl_running->integer ) { 1115 return; 1116 } 1117 1118 Con_Close(); 1119 Key_SetCatcher( 0 ); 1120 1121 // if we are already connected to the local host, stay connected 1122 if ( cls.state >= CA_CONNECTED && !Q_stricmp( cls.servername, "localhost" ) ) { 1123 cls.state = CA_CONNECTED; // so the connect screen is drawn 1124 Com_Memset( cls.updateInfoString, 0, sizeof( cls.updateInfoString ) ); 1125 Com_Memset( clc.serverMessage, 0, sizeof( clc.serverMessage ) ); 1126 Com_Memset( &cl.gameState, 0, sizeof( cl.gameState ) ); 1127 clc.lastPacketSentTime = -9999; 1128 SCR_UpdateScreen(); 1129 } else { 1130 // clear nextmap so the cinematic shutdown doesn't execute it 1131 Cvar_Set( "nextmap", "" ); 1132 CL_Disconnect( qtrue ); 1133 Q_strncpyz( cls.servername, "localhost", sizeof(cls.servername) ); 1134 cls.state = CA_CHALLENGING; // so the connect screen is drawn 1135 Key_SetCatcher( 0 ); 1136 SCR_UpdateScreen(); 1137 clc.connectTime = -RETRANSMIT_TIMEOUT; 1138 NET_StringToAdr( cls.servername, &clc.serverAddress, NA_UNSPEC); 1139 // we don't need a challenge on the localhost 1140 1141 CL_CheckForResend(); 1142 } 1143} 1144 1145/* 1146===================== 1147CL_ClearState 1148 1149Called before parsing a gamestate 1150===================== 1151*/ 1152void CL_ClearState (void) { 1153 1154// S_StopAllSounds(); 1155 1156 Com_Memset( &cl, 0, sizeof( cl ) ); 1157} 1158 1159/* 1160==================== 1161CL_UpdateGUID 1162 1163update cl_guid using QKEY_FILE and optional prefix 1164==================== 1165*/ 1166static void CL_UpdateGUID( const char *prefix, int prefix_len ) 1167{ 1168 fileHandle_t f; 1169 int len; 1170 1171 len = FS_SV_FOpenFileRead( QKEY_FILE, &f ); 1172 FS_FCloseFile( f ); 1173 1174 if( len != QKEY_SIZE ) 1175 Cvar_Set( "cl_guid", "" ); 1176 else 1177 Cvar_Set( "cl_guid", Com_MD5File( QKEY_FILE, QKEY_SIZE, 1178 prefix, prefix_len ) ); 1179} 1180 1181 1182/* 1183===================== 1184CL_Disconnect 1185 1186Called when a connection, demo, or cinematic is being terminated. 1187Goes from a connected state to either a menu state or a console state 1188Sends a disconnect message to the server 1189This is also called on Com_Error and Com_Quit, so it shouldn't cause any errors 1190===================== 1191*/ 1192void CL_Disconnect( qboolean showMainMenu ) { 1193 if ( !com_cl_running || !com_cl_running->integer ) { 1194 return; 1195 } 1196 1197 // shutting down the client so enter full screen ui mode 1198 Cvar_Set("r_uiFullScreen", "1"); 1199 1200 if ( clc.demorecording ) { 1201 CL_StopRecord_f (); 1202 } 1203 1204 if (clc.download) { 1205 FS_FCloseFile( clc.download ); 1206 clc.download = 0; 1207 } 1208 *clc.downloadTempName = *clc.downloadName = 0; 1209 Cvar_Set( "cl_downloadName", "" ); 1210 1211#ifdef USE_MUMBLE 1212 if (cl_useMumble->integer && mumble_islinked()) { 1213 Com_Printf("Mumble: Unlinking from Mumble application\n"); 1214 mumble_unlink(); 1215 } 1216#endif 1217 1218#ifdef USE_VOIP 1219 if (cl_voipSend->integer) { 1220 int tmp = cl_voipUseVAD->integer; 1221 cl_voipUseVAD->integer = 0; // disable this for a moment. 1222 clc.voipOutgoingDataSize = 0; // dump any pending VoIP transmission. 1223 Cvar_Set("cl_voipSend", "0"); 1224 CL_CaptureVoip(); // clean up any state... 1225 cl_voipUseVAD->integer = tmp; 1226 } 1227 1228 if (clc.speexInitialized) { 1229 int i; 1230 speex_bits_destroy(&clc.speexEncoderBits); 1231 speex_encoder_destroy(clc.speexEncoder); 1232 speex_preprocess_state_destroy(clc.speexPreprocessor); 1233 for (i = 0; i < MAX_CLIENTS; i++) { 1234 speex_bits_destroy(&clc.speexDecoderBits[i]); 1235 speex_decoder_destroy(clc.speexDecoder[i]); 1236 } 1237 } 1238 Cmd_RemoveCommand ("voip"); 1239#endif 1240 1241 if ( clc.demofile ) { 1242 FS_FCloseFile( clc.demofile ); 1243 clc.demofile = 0; 1244 } 1245 1246 if ( uivm && showMainMenu ) { 1247 VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE ); 1248 } 1249 1250 SCR_StopCinematic (); 1251 S_ClearSoundBuffer(); 1252 1253 // send a disconnect message to the server 1254 // send it a few times in case one is dropped 1255 if ( cls.state >= CA_CONNECTED ) { 1256 CL_AddReliableCommand("disconnect", qtrue); 1257 CL_WritePacket(); 1258 CL_WritePacket(); 1259 CL_WritePacket(); 1260 } 1261 1262 // Remove pure paks 1263 FS_PureServerSetLoadedPaks("", ""); 1264 1265 CL_ClearState (); 1266 1267 // wipe the client connection 1268 Com_Memset( &clc, 0, sizeof( clc ) ); 1269 1270 cls.state = CA_DISCONNECTED; 1271 1272 // allow cheats locally 1273 Cvar_Set( "sv_cheats", "1" ); 1274 1275 // not connected to a pure server anymore 1276 cl_connectedToPureServer = qfalse; 1277 1278#ifdef USE_VOIP 1279 // not connected to voip server anymore. 1280 cl_connectedToVoipServer = qfalse; 1281#endif 1282 1283 // Stop recording any video 1284 if( CL_VideoRecording( ) ) { 1285 // Finish rendering current frame 1286 SCR_UpdateScreen( ); 1287 CL_CloseAVI( ); 1288 } 1289 CL_UpdateGUID( NULL, 0 ); 1290} 1291 1292 1293/* 1294=================== 1295CL_ForwardCommandToServer 1296 1297adds the current command line as a clientCommand 1298things like godmode, noclip, etc, are commands directed to the server, 1299so when they are typed in at the console, they will need to be forwarded. 1300=================== 1301*/ 1302void CL_ForwardCommandToServer( const char *string ) { 1303 char *cmd; 1304 1305 cmd = Cmd_Argv(0); 1306 1307 // ignore key up commands 1308 if ( cmd[0] == '-' ) { 1309 return; 1310 } 1311 1312 if ( clc.demoplaying || cls.state < CA_CONNECTED || cmd[0] == '+' ) { 1313 Com_Printf ("Unknown command \"%s" S_COLOR_WHITE "\"\n", cmd); 1314 return; 1315 } 1316 1317 if ( Cmd_Argc() > 1 ) { 1318 CL_AddReliableCommand(string, qfalse); 1319 } else { 1320 CL_AddReliableCommand(cmd, qfalse); 1321 } 1322} 1323 1324/* 1325=================== 1326CL_RequestMotd 1327 1328=================== 1329*/ 1330void CL_RequestMotd( void ) { 1331 char info[MAX_INFO_STRING]; 1332 1333 if ( !cl_motd->integer ) { 1334 return; 1335 } 1336 Com_Printf( "Resolving %s\n", UPDATE_SERVER_NAME ); 1337 if ( !NET_StringToAdr( UPDATE_SERVER_NAME, &cls.updateServer, NA_IP ) ) { 1338 Com_Printf( "Couldn't resolve address\n" ); 1339 return; 1340 } 1341 cls.updateServer.port = BigShort( PORT_UPDATE ); 1342 Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", UPDATE_SERVER_NAME, 1343 cls.updateServer.ip[0], cls.updateServer.ip[1], 1344 cls.updateServer.ip[2], cls.updateServer.ip[3], 1345 BigShort( cls.updateServer.port ) ); 1346 1347 info[0] = 0; 1348 1349 Com_sprintf( cls.updateChallenge, sizeof( cls.updateChallenge ), "%i", ((rand() << 16) ^ rand()) ^ Com_Milliseconds()); 1350 1351 Info_SetValueForKey( info, "challenge", cls.updateChallenge ); 1352 Info_SetValueForKey( info, "renderer", cls.glconfig.renderer_string ); 1353 Info_SetValueForKey( info, "version", com_version->string ); 1354 1355 NET_OutOfBandPrint( NS_CLIENT, cls.updateServer, "getmotd \"%s\"\n", info ); 1356} 1357 1358/* 1359=================== 1360CL_RequestAuthorization 1361 1362Authorization server protocol 1363----------------------------- 1364 1365All commands are text in Q3 out of band packets (leading 0xff 0xff 0xff 0xff). 1366 1367Whenever the client tries to get a challenge from the server it wants to 1368connect to, it also blindly fires off a packet to the authorize server: 1369 1370getKeyAuthorize <challenge> <cdkey> 1371 1372cdkey may be "demo" 1373 1374 1375#OLD The authorize server returns a: 1376#OLD 1377#OLD keyAthorize <challenge> <accept | deny> 1378#OLD 1379#OLD A client will be accepted if the cdkey is valid and it has not been used by any other IP 1380#OLD address in the last 15 minutes. 1381 1382 1383The server sends a: 1384 1385getIpAuthorize <challenge> <ip> 1386 1387The authorize server returns a: 1388 1389ipAuthorize <challenge> <accept | deny | demo | unknown > 1390 1391A client will be accepted if a valid cdkey was sent by that ip (only) in the last 15 minutes. 1392If no response is received from the authorize server after two tries, the client will be let 1393in anyway. 1394=================== 1395*/ 1396#ifndef STANDALONE 1397void CL_RequestAuthorization( void ) { 1398 char nums[64]; 1399 int i, j, l; 1400 cvar_t *fs; 1401 1402 if ( !cls.authorizeServer.port ) { 1403 Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); 1404 if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &cls.authorizeServer, NA_IP ) ) { 1405 Com_Printf( "Couldn't resolve address\n" ); 1406 return; 1407 } 1408 1409 cls.authorizeServer.port = BigShort( PORT_AUTHORIZE ); 1410 Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME, 1411 cls.authorizeServer.ip[0], cls.authorizeServer.ip[1], 1412 cls.authorizeServer.ip[2], cls.authorizeServer.ip[3], 1413 BigShort( cls.authorizeServer.port ) ); 1414 } 1415 if ( cls.authorizeServer.type == NA_BAD ) { 1416 return; 1417 } 1418 1419 // only grab the alphanumeric values from the cdkey, to avoid any dashes or spaces 1420 j = 0; 1421 l = strlen( cl_cdkey ); 1422 if ( l > 32 ) { 1423 l = 32; 1424 } 1425 for ( i = 0 ; i < l ; i++ ) { 1426 if ( ( cl_cdkey[i] >= '0' && cl_cdkey[i] <= '9' ) 1427 || ( cl_cdkey[i] >= 'a' && cl_cdkey[i] <= 'z' ) 1428 || ( cl_cdkey[i] >= 'A' && cl_cdkey[i] <= 'Z' ) 1429 ) { 1430 nums[j] = cl_cdkey[i]; 1431 j++; 1432 } 1433 } 1434 nums[j] = 0; 1435 1436 fs = Cvar_Get ("cl_anonymous", "0", CVAR_INIT|CVAR_SYSTEMINFO ); 1437 1438 NET_OutOfBandPrint(NS_CLIENT, cls.authorizeServer, "getKeyAuthorize %i %s", fs->integer, nums ); 1439} 1440#endif 1441/* 1442====================================================================== 1443 1444CONSOLE COMMANDS 1445 1446====================================================================== 1447*/ 1448 1449/* 1450================== 1451CL_ForwardToServer_f 1452================== 1453*/ 1454void CL_ForwardToServer_f( void ) { 1455 if ( cls.state != CA_ACTIVE || clc.demoplaying ) { 1456 Com_Printf ("Not connected to a server.\n"); 1457 return; 1458 } 1459 1460 // don't forward the first argument 1461 if ( Cmd_Argc() > 1 ) { 1462 CL_AddReliableCommand(Cmd_Args(), qfalse); 1463 } 1464} 1465 1466/* 1467================== 1468CL_Disconnect_f 1469================== 1470*/ 1471void CL_Disconnect_f( void ) { 1472 SCR_StopCinematic(); 1473 Cvar_Set("ui_singlePlayerActive", "0"); 1474 if ( cls.state != CA_DISCONNECTED && cls.state != CA_CINEMATIC ) { 1475 Com_Error (ERR_DISCONNECT, "Disconnected from server"); 1476 } 1477} 1478 1479 1480/* 1481================ 1482CL_Reconnect_f 1483 1484================ 1485*/ 1486void CL_Reconnect_f( void ) { 1487 if ( !strlen( cls.servername ) || !strcmp( cls.servername, "localhost" ) ) { 1488 Com_Printf( "Can't reconnect to localhost.\n" ); 1489 return; 1490 } 1491 Cvar_Set("ui_singlePlayerActive", "0"); 1492 Cbuf_AddText( va("connect %s\n", cls.servername ) ); 1493} 1494 1495/* 1496================ 1497CL_Connect_f 1498 1499================ 1500*/ 1501void CL_Connect_f( void ) { 1502 char *server; 1503 const char *serverString; 1504 int argc = Cmd_Argc(); 1505 netadrtype_t family = NA_UNSPEC; 1506 1507 if ( argc != 2 && argc != 3 ) { 1508 Com_Printf( "usage: connect [-4|-6] server\n"); 1509 return; 1510 } 1511 1512 if(argc == 2) 1513 server = Cmd_Argv(1); 1514 else 1515 { 1516 if(!strcmp(Cmd_Argv(1), "-4")) 1517 family = NA_IP; 1518 else if(!strcmp(Cmd_Argv(1), "-6")) 1519 family = NA_IP6; 1520 else 1521 Com_Printf( "warning: only -4 or -6 as address type understood.\n"); 1522 1523 server = Cmd_Argv(2); 1524 } 1525 1526 Cvar_Set("ui_singlePlayerActive", "0"); 1527 1528 // fire a message off to the motd server 1529 CL_RequestMotd(); 1530 1531 // clear any previous "server full" type messages 1532 clc.serverMessage[0] = 0; 1533 1534 if ( com_sv_running->integer && !strcmp( server, "localhost" ) ) { 1535 // if running a local server, kill it 1536 SV_Shutdown( "Server quit" ); 1537 } 1538 1539 // make sure a local server is killed 1540 Cvar_Set( "sv_killserver", "1" ); 1541 SV_Frame( 0 ); 1542 1543 CL_Disconnect( qtrue ); 1544 Con_Close(); 1545 1546 Q_strncpyz( cls.servername, server, sizeof(cls.servername) ); 1547 1548 if (!NET_StringToAdr(cls.servername, &clc.serverAddress, family) ) { 1549 Com_Printf ("Bad server address\n"); 1550 cls.state = CA_DISCONNECTED; 1551 return; 1552 } 1553 if (clc.serverAddress.port == 0) { 1554 clc.serverAddress.port = BigShort( PORT_SERVER ); 1555 } 1556 1557 serverString = NET_AdrToStringwPort(clc.serverAddress); 1558 1559 Com_Printf( "%s resolved to %s\n", cls.servername, serverString); 1560 1561 if( cl_guidServerUniq->integer ) 1562 CL_UpdateGUID( serverString, strlen( serverString ) ); 1563 else 1564 CL_UpdateGUID( NULL, 0 ); 1565 1566 // if we aren't playing on a lan, we need to authenticate 1567 // with the cd key 1568 if(NET_IsLocalAddress(clc.serverAddress)) 1569 cls.state = CA_CHALLENGING; 1570 else 1571 { 1572 cls.state = CA_CONNECTING; 1573 1574 // Set a client challenge number that ideally is mirrored back by the server. 1575 clc.challenge = ((rand() << 16) ^ rand()) ^ Com_Milliseconds(); 1576 } 1577 1578 Key_SetCatcher( 0 ); 1579 clc.connectTime = -99999; // CL_CheckForResend() will fire immediately 1580 clc.connectPacketCount = 0; 1581 1582 // server connection string 1583 Cvar_Set( "cl_currentServerAddress", server ); 1584} 1585 1586#define MAX_RCON_MESSAGE 1024 1587 1588/* 1589================== 1590CL_CompleteRcon 1591================== 1592*/ 1593static void CL_CompleteRcon( char *args, int argNum ) 1594{ 1595 if( argNum == 2 ) 1596 { 1597 // Skip "rcon " 1598 char *p = Com_SkipTokens( args, 1, " " ); 1599 1600 if( p > args ) 1601 Field_CompleteCommand( p, qtrue, qtrue ); 1602 } 1603} 1604 1605/* 1606===================== 1607CL_Rcon_f 1608 1609 Send the rest of the command line over as 1610 an unconnected command. 1611===================== 1612*/ 1613void CL_Rcon_f( void ) { 1614 char message[MAX_RCON_MESSAGE]; 1615 netadr_t to; 1616 1617 if ( !rcon_client_password->string ) { 1618 Com_Printf ("You must set 'rconpassword' before\n" 1619 "issuing an rcon command.\n"); 1620 return; 1621 } 1622 1623 message[0] = -1; 1624 message[1] = -1; 1625 message[2] = -1; 1626 message[3] = -1; 1627 message[4] = 0; 1628 1629 Q_strcat (message, MAX_RCON_MESSAGE, "rcon "); 1630 1631 Q_strcat (message, MAX_RCON_MESSAGE, rcon_client_password->string); 1632 Q_strcat (message, MAX_RCON_MESSAGE, " "); 1633 1634 // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543 1635 Q_strcat (message, MAX_RCON_MESSAGE, Cmd_Cmd()+5); 1636 1637 if ( cls.state >= CA_CONNECTED ) { 1638 to = clc.netchan.remoteAddress; 1639 } else { 1640 if (!strlen(rconAddress->string)) { 1641 Com_Printf ("You must either be connected,\n" 1642 "or set the 'rconAddress' cvar\n" 1643 "to issue rcon commands\n"); 1644 1645 return; 1646 } 1647 NET_StringToAdr (rconAddress->string, &to, NA_UNSPEC); 1648 if (to.port == 0) { 1649 to.port = BigShort (PORT_SERVER); 1650 } 1651 } 1652 1653 NET_SendPacket (NS_CLIENT, strlen(message)+1, message, to); 1654} 1655 1656/* 1657================= 1658CL_SendPureChecksums 1659================= 1660*/ 1661void CL_SendPureChecksums( void ) { 1662 char cMsg[MAX_INFO_VALUE]; 1663 1664 // if we are pure we need to send back a command with our referenced pk3 checksums 1665 Com_sprintf(cMsg, sizeof(cMsg), "cp %d %s", cl.serverId, FS_ReferencedPakPureChecksums()); 1666 1667 CL_AddReliableCommand(cMsg, qfalse); 1668} 1669 1670/* 1671================= 1672CL_ResetPureClientAtServer 1673================= 1674*/ 1675void CL_ResetPureClientAtServer( void ) { 1676 CL_AddReliableCommand("vdr", qfalse); 1677} 1678 1679/* 1680================= 1681CL_Vid_Restart_f 1682 1683Restart the video subsystem 1684 1685we also have to reload the UI and CGame because the renderer 1686doesn't know what graphics to reload 1687================= 1688*/ 1689void CL_Vid_Restart_f( void ) { 1690 1691 // Settings may have changed so stop recording now 1692 if( CL_VideoRecording( ) ) { 1693 CL_CloseAVI( ); 1694 } 1695 1696 if(clc.demorecording) 1697 CL_StopRecord_f(); 1698 1699 // don't let them loop during the restart 1700 S_StopAllSounds(); 1701 // shutdown the UI 1702 CL_ShutdownUI(); 1703 // shutdown the CGame 1704 CL_ShutdownCGame(); 1705 // shutdown the renderer and clear the renderer interface 1706 CL_ShutdownRef(); 1707 // client is no longer pure untill new checksums are sent 1708 CL_ResetPureClientAtServer(); 1709 // clear pak references 1710 FS_ClearPakReferences( FS_UI_REF | FS_CGAME_REF ); 1711 // reinitialize the filesystem if the game directory or checksum has changed 1712 FS_ConditionalRestart( clc.checksumFeed ); 1713 1714 cls.rendererStarted = qfalse; 1715 cls.uiStarted = qfalse; 1716 cls.cgameStarted = qfalse; 1717 cls.soundRegistered = qfalse; 1718 1719 // unpause so the cgame definately gets a snapshot and renders a frame 1720 Cvar_Set( "cl_paused", "0" ); 1721 1722 // if not running a server clear the whole hunk 1723 if ( !com_sv_running->integer ) { 1724 // clear the whole hunk 1725 Hunk_Clear(); 1726 } 1727 else { 1728 // clear all the client data on the hunk 1729 Hunk_ClearToMark(); 1730 } 1731 1732 // initialize the renderer interface 1733 CL_InitRef(); 1734 1735 // startup all the client stuff 1736 CL_StartHunkUsers( qfalse ); 1737 1738 // start the cgame if connected 1739 if ( cls.state > CA_CONNECTED && cls.state != CA_CINEMATIC ) { 1740 cls.cgameStarted = qtrue; 1741 CL_InitCGame(); 1742 // send pure checksums 1743 CL_SendPureChecksums(); 1744 } 1745} 1746 1747/* 1748================= 1749CL_Snd_Restart 1750 1751Restart the sound subsystem 1752================= 1753*/ 1754void CL_Snd_Restart(void) 1755{ 1756 S_Shutdown(); 1757 S_Init(); 1758} 1759 1760/* 1761================= 1762CL_Snd_Restart_f 1763 1764Restart the sound subsystem 1765The cgame and game must also be forced to restart because 1766handles will be invalid 1767================= 1768*/ 1769void CL_Snd_Restart_f(void) 1770{ 1771 CL_Snd_Restart(); 1772 CL_Vid_Restart_f(); 1773} 1774 1775 1776/* 1777================== 1778CL_PK3List_f 1779================== 1780*/ 1781void CL_OpenedPK3List_f( void ) { 1782 Com_Printf("Opened PK3 Names: %s\n", FS_LoadedPakNames()); 1783} 1784 1785/* 1786================== 1787CL_PureList_f 1788================== 1789*/ 1790void CL_ReferencedPK3List_f( void ) { 1791 Com_Printf("Referenced PK3 Names: %s\n", FS_ReferencedPakNames()); 1792} 1793 1794/* 1795================== 1796CL_Configstrings_f 1797================== 1798*/ 1799void CL_Configstrings_f( void ) { 1800 int i; 1801 int ofs; 1802 1803 if ( cls.state != CA_ACTIVE ) { 1804 Com_Printf( "Not connected to a server.\n"); 1805 return; 1806 } 1807 1808 for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { 1809 ofs = cl.gameState.stringOffsets[ i ]; 1810 if ( !ofs ) { 1811 continue; 1812 } 1813 Com_Printf( "%4i: %s\n", i, cl.gameState.stringData + ofs ); 1814 } 1815} 1816 1817/* 1818============== 1819CL_Clientinfo_f 1820============== 1821*/ 1822void CL_Clientinfo_f( void ) { 1823 Com_Printf( "--------- Client Information ---------\n" ); 1824 Com_Printf( "state: %i\n", cls.state ); 1825 Com_Printf( "Server: %s\n", cls.servername ); 1826 Com_Printf ("User info settings:\n"); 1827 Info_Print( Cvar_InfoString( CVAR_USERINFO ) ); 1828 Com_Printf( "--------------------------------------\n" ); 1829} 1830 1831 1832//==================================================================== 1833 1834/* 1835================= 1836CL_DownloadsComplete 1837 1838Called when all downloading has been completed 1839================= 1840*/ 1841void CL_DownloadsComplete( void ) { 1842 1843#ifdef USE_CURL 1844 // if we downloaded with cURL 1845 if(clc.cURLUsed) { 1846 clc.cURLUsed = qfalse; 1847 CL_cURL_Shutdown(); 1848 if( clc.cURLDisconnected ) { 1849 if(clc.downloadRestart) { 1850 FS_Restart(clc.checksumFeed); 1851 clc.downloadRestart = qfalse; 1852 } 1853 clc.cURLDisconnected = qfalse; 1854 CL_Reconnect_f(); 1855 return; 1856 } 1857 } 1858#endif 1859 1860 // if we downloaded files we need to restart the file system 1861 if (clc.downloadRestart) { 1862 clc.downloadRestart = qfalse; 1863 1864 FS_Restart(clc.checksumFeed); // We possibly downloaded a pak, restart the file system to load it 1865 1866 // inform the server so we get new gamestate info 1867 CL_AddReliableCommand("donedl", qfalse); 1868 1869 // by sending the donedl command we request a new gamestate 1870 // so we don't want to load stuff yet 1871 return; 1872 } 1873 1874 // let the client game init and load data 1875 cls.state = CA_LOADING; 1876 1877 // Pump the loop, this may change gamestate! 1878 Com_EventLoop(); 1879 1880 // if the gamestate was changed by calling Com_EventLoop 1881 // then we loaded everything already and we don't want to do it again. 1882 if ( cls.state != CA_LOADING ) { 1883 return; 1884 } 1885 1886 // starting to load a map so we get out of full screen ui mode 1887 Cvar_Set("r_uiFullScreen", "0"); 1888 1889 // flush client memory and start loading stuff 1890 // this will also (re)load the UI 1891 // if this is a local client then only the client part of the hunk 1892 // will be cleared, note that this is done after the hunk mark has been set 1893 CL_FlushMemory(); 1894 1895 // initialize the CGame 1896 cls.cgameStarted = qtrue; 1897 CL_InitCGame(); 1898 1899 // set pure checksums 1900 CL_SendPureChecksums(); 1901 1902 CL_WritePacket(); 1903 CL_WritePacket(); 1904 CL_WritePacket(); 1905} 1906 1907/* 1908================= 1909CL_BeginDownload 1910 1911Requests a file to download from the server. Stores it in the current 1912game directory. 1913================= 1914*/ 1915void CL_BeginDownload( const char *localName, const char *remoteName ) { 1916 1917 Com_DPrintf("***** CL_BeginDownload *****\n" 1918 "Localname: %s\n" 1919 "Remotename: %s\n" 1920 "****************************\n", localName, remoteName); 1921 1922 Q_strncpyz ( clc.downloadName, localName, sizeof(clc.downloadName) ); 1923 Com_sprintf( clc.downloadTempName, sizeof(clc.downloadTempName), "%s.tmp", localName ); 1924 1925 // Set so UI gets access to it 1926 Cvar_Set( "cl_downloadName", remoteName ); 1927 Cvar_Set( "cl_downloadSize", "0" ); 1928 Cvar_Set( "cl_downloadCount", "0" ); 1929 Cvar_SetValue( "cl_downloadTime", cls.realtime ); 1930 1931 clc.downloadBlock = 0; // Starting new file 1932 clc.downloadCount = 0; 1933 1934 CL_AddReliableCommand(va("download %s", remoteName), qfalse); 1935} 1936 1937/* 1938================= 1939CL_NextDownload 1940 1941A download completed or failed 1942================= 1943*/ 1944void CL_NextDownload(void) 1945{ 1946 char *s; 1947 char *remoteName, *localName; 1948 qboolean useCURL = qfalse; 1949 1950 // A download has finished, check whether this matches a referenced checksum 1951 if(*clc.downloadName) 1952 { 1953 char *zippath = FS_BuildOSPath(Cvar_VariableString("fs_homepath"), clc.downloadName, ""); 1954 zippath[strlen(zippath)-1] = '\0'; 1955 1956 if(!FS_CompareZipChecksum(zippath)) 1957 Com_Error(ERR_DROP, "Incorrect checksum for file: %s", clc.downloadName); 1958 } 1959 1960 *clc.downloadTempName = *clc.downloadName = 0; 1961 Cvar_Set("cl_downloadName", ""); 1962 1963 // We are looking to start a download here 1964 if (*clc.downloadList) { 1965 s = clc.downloadList; 1966 1967 // format is: 1968 // @remotename@localname@remotename@localname, etc. 1969 1970 if (*s == '@') 1971 s++; 1972 remoteName = s; 1973 1974 if ( (s = strchr(s, '@')) == NULL ) { 1975 CL_DownloadsComplete(); 1976 return; 1977 } 1978 1979 *s++ = 0; 1980 localName = s; 1981 if ( (s = strchr(s, '@')) != NULL ) 1982 *s++ = 0; 1983 else 1984 s = localName + strlen(localName); // point at the nul byte 1985#ifdef USE_CURL 1986 if(!(cl_allowDownload->integer & DLF_NO_REDIRECT)) { 1987 if(clc.sv_allowDownload & DLF_NO_REDIRECT) { 1988 Com_Printf("WARNING: server does not " 1989 "allow download redirection " 1990 "(sv_allowDownload is %d)\n", 1991 clc.sv_allowDownload); 1992 } 1993 else if(!*clc.sv_dlURL) { 1994 Com_Printf("WARNING: server allows " 1995 "download redirection, but does not " 1996 "have sv_dlURL set\n"); 1997 } 1998 else if(!CL_cURL_Init()) { 1999 Com_Printf("WARNING: could not load " 2000 "cURL library\n"); 2001 } 2002 else { 2003 CL_cURL_BeginDownload(localName, va("%s/%s", 2004 clc.sv_dlURL, remoteName)); 2005 useCURL = qtrue; 2006 } 2007 } 2008 else if(!(clc.sv_allowDownload & DLF_NO_REDIRECT)) { 2009 Com_Printf("WARNING: server allows download " 2010 "redirection, but it disabled by client " 2011 "configuration (cl_allowDownload is %d)\n", 2012 cl_allowDownload->integer); 2013 } 2014#endif /* USE_CURL */ 2015 if(!useCURL) { 2016 if((cl_allowDownload->integer & DLF_NO_UDP)) { 2017 Com_Error(ERR_DROP, "UDP Downloads are " 2018 "disabled on your client. " 2019 "(cl_allowDownload is %d)", 2020 cl_allowDownload->integer); 2021 return; 2022 } 2023 else { 2024 CL_BeginDownload( localName, remoteName ); 2025 } 2026 } 2027 clc.downloadRestart = qtrue; 2028 2029 // move over the rest 2030 memmove( clc.downloadList, s, strlen(s) + 1); 2031 2032 return; 2033 } 2034 2035 CL_DownloadsComplete(); 2036} 2037 2038/* 2039================= 2040CL_InitDownloads 2041 2042After receiving a valid game state, we valid the cgame and local zip files here 2043and determine if we need to download them 2044================= 2045*/ 2046void CL_InitDownloads(void) { 2047 char missingfiles[1024]; 2048 2049 if ( !(cl_allowDownload->integer & DLF_ENABLE) ) 2050 { 2051 // autodownload is disabled on the client 2052 // but it's possible that some referenced files on the server are missing 2053 if (FS_ComparePaks( missingfiles, sizeof( missingfiles ), qfalse ) ) 2054 { 2055 // NOTE TTimo I would rather have that printed as a modal message box 2056 // but at this point while joining the game we don't know wether we will successfully join or not 2057 Com_Printf( "\nWARNING: You are missing some files referenced by the server:\n%s" 2058 "You might not be able to join the game\n" 2059 "Go to the setting menu to turn on autodownload, or get the file elsewhere\n\n", missingfiles ); 2060 } 2061 } 2062 else if ( FS_ComparePaks( clc.downloadList, sizeof( clc.downloadList ) , qtrue ) ) { 2063 2064 Com_Printf("Need paks: %s\n", clc.downloadList ); 2065 2066 if ( *clc.downloadList ) { 2067 // if autodownloading is not enabled on the server 2068 cls.state = CA_CONNECTED; 2069 2070 *clc.downloadTempName = *clc.downloadName = 0; 2071 Cvar_Set( "cl_downloadName", "" ); 2072 2073 CL_NextDownload(); 2074 return; 2075 } 2076 2077 } 2078 2079 CL_DownloadsComplete(); 2080} 2081 2082/* 2083================= 2084CL_CheckForResend 2085 2086Resend a connect message if the last one has timed out 2087================= 2088*/ 2089void CL_CheckForResend( void ) { 2090 int port, i; 2091 char info[MAX_INFO_STRING]; 2092 char data[MAX_INFO_STRING]; 2093 2094 // don't send anything if playing back a demo 2095 if ( clc.demoplaying ) { 2096 return; 2097 } 2098 2099 // resend if we haven't gotten a reply yet 2100 if ( cls.state != CA_CONNECTING && cls.state != CA_CHALLENGING ) { 2101 return; 2102 } 2103 2104 if ( cls.realtime - clc.connectTime < RETRANSMIT_TIMEOUT ) { 2105 return; 2106 } 2107 2108 clc.connectTime = cls.realtime; // for retransmit requests 2109 clc.connectPacketCount++; 2110 2111 2112 switch ( cls.state ) { 2113 case CA_CONNECTING: 2114 // requesting a challenge .. IPv6 users always get in as authorize server supports no ipv6. 2115#ifndef STANDALONE 2116 if (!Cvar_VariableIntegerValue("com_standalone") && clc.serverAddress.type == NA_IP && !Sys_IsLANAddress( clc.serverAddress ) ) 2117 CL_RequestAuthorization(); 2118#endif 2119 2120 // The challenge request shall be followed by a client challenge so no malicious server can hijack this connection. 2121 Com_sprintf(data, sizeof(data), "getchallenge %d", clc.challenge); 2122 2123 NET_OutOfBandPrint(NS_CLIENT, clc.serverAddress, "%s", data); 2124 break; 2125 2126 case CA_CHALLENGING: 2127 // sending back the challenge 2128 port = Cvar_VariableValue ("net_qport"); 2129 2130 Q_strncpyz( info, Cvar_InfoString( CVAR_USERINFO ), sizeof( info ) ); 2131 Info_SetValueForKey( info, "protocol", va("%i", PROTOCOL_VERSION ) ); 2132 Info_SetValueForKey( info, "qport", va("%i", port ) ); 2133 Info_SetValueForKey( info, "challenge", va("%i", clc.challenge ) ); 2134 2135 strcpy(data, "connect "); 2136 // TTimo adding " " around the userinfo string to avoid truncated userinfo on the server 2137 // (Com_TokenizeString tokenizes around spaces) 2138 data[8] = '"'; 2139 2140 for(i=0;i<strlen(info);i++) { 2141 data[9+i] = info[i]; // + (clc.challenge)&0x3; 2142 } 2143 data[9+i] = '"'; 2144 data[10+i] = 0; 2145 2146 // NOTE TTimo don't forget to set the right data length! 2147 NET_OutOfBandData( NS_CLIENT, clc.serverAddress, (byte *) &data[0], i+10 ); 2148 // the most current userinfo has been sent, so watch for any 2149 // newer changes to userinfo variables 2150 cvar_modifiedFlags &= ~CVAR_USERINFO; 2151 break; 2152 2153 default: 2154 Com_Error( ERR_FATAL, "CL_CheckForResend: bad cls.state" ); 2155 } 2156} 2157 2158/* 2159=================== 2160CL_DisconnectPacket 2161 2162Sometimes the server can drop the client and the netchan based 2163disconnect can be lost. If the client continues to send packets 2164to the server, the server will send out of band disconnect packets 2165to the client so it doesn't have to wait for the full timeout period. 2166=================== 2167*/ 2168void CL_DisconnectPacket( netadr_t from ) { 2169 if ( cls.state < CA_AUTHORIZING ) { 2170 return; 2171 } 2172 2173 // if not from our server, ignore it 2174 if ( !NET_CompareAdr( from, clc.netchan.remoteAddress ) ) { 2175 return; 2176 } 2177 2178 // if we have received packets within three seconds, ignore it 2179 // (it might be a malicious spoof) 2180 if ( cls.realtime - clc.lastPacketTime < 3000 ) { 2181 return; 2182 } 2183 2184 // drop the connection 2185 Com_Printf( "Server disconnected for unknown reason\n" ); 2186 Cvar_Set("com_errorMessage", "Server disconnected for unknown reason\n" ); 2187 CL_Disconnect( qtrue ); 2188} 2189 2190 2191/* 2192=================== 2193CL_MotdPacket 2194 2195=================== 2196*/ 2197void CL_MotdPacket( netadr_t from ) { 2198 char *challenge; 2199 char *info; 2200 2201 // if not from our server, ignore it 2202 if ( !NET_CompareAdr( from, cls.updateServer ) ) { 2203 return; 2204 } 2205 2206 info = Cmd_Argv(1); 2207 2208 // check challenge 2209 challenge = Info_ValueForKey( info, "challenge" ); 2210 if ( strcmp( challenge, cls.updateChallenge ) ) { 2211 return; 2212 } 2213 2214 challenge = Info_ValueForKey( info, "motd" ); 2215 2216 Q_strncpyz( cls.updateInfoString, info, sizeof( cls.updateInfoString ) ); 2217 Cvar_Set( "cl_motdString", challenge ); 2218} 2219 2220/* 2221=================== 2222CL_InitServerInfo 2223=================== 2224*/ 2225void CL_InitServerInfo( serverInfo_t *server, netadr_t *address ) { 2226 server->adr = *address; 2227 server->clients = 0; 2228 server->hostName[0] = '\0'; 2229 server->mapName[0] = '\0'; 2230 server->maxClients = 0; 2231 server->maxPing = 0; 2232 server->minPing = 0; 2233 server->ping = -1; 2234 server->game[0] = '\0'; 2235 server->gameType = 0; 2236 server->netType = 0; 2237} 2238 2239#define MAX_SERVERSPERPACKET 256 2240 2241/* 2242=================== 2243CL_ServersResponsePacket 2244=================== 2245*/ 2246void CL_ServersResponsePacket( const netadr_t* from, msg_t *msg, qboolean extended ) { 2247 int i, count, total; 2248 netadr_t addresses[MAX_SERVERSPERPACKET]; 2249 int numservers; 2250 byte* buffptr; 2251 byte* buffend; 2252 2253 Com_Printf("CL_ServersResponsePacket\n"); 2254 2255 if (cls.numglobalservers == -1) { 2256 // state to detect lack of servers or lack of response 2257 cls.numglobalservers = 0; 2258 cls.numGlobalServerAddresses = 0; 2259 } 2260 2261 // parse through server response string 2262 numservers = 0; 2263 buffptr = msg->data; 2264 buffend = buffptr + msg->cursize; 2265 2266 // advance to initial token 2267 do 2268 { 2269 if(*buffptr == '\\' || (extended && *buffptr == '/')) 2270 break; 2271 2272 buffptr++; 2273 } while (buffptr < buffend); 2274 2275 while (buffptr + 1 < buffend) 2276 { 2277 // IPv4 address 2278 if (*buffptr == '\\') 2279 { 2280 buffptr++; 2281 2282 if (buffend - buffptr < sizeof(addresses[numservers].ip) + sizeof(addresses[numservers].port) + 1) 2283 break; 2284 2285 for(i = 0; i < sizeof(addresses[numservers].ip); i++) 2286 addresses[numservers].ip[i] = *buffptr++; 2287 2288 addresses[numservers].type = NA_IP; 2289 } 2290 // IPv6 address, if it's an extended response 2291 else if (extended && *buffptr == '/') 2292 { 2293 buffptr++; 2294 2295 if (buffend - buffptr < sizeof(addresses[numservers].ip6) + sizeof(addresses[numservers].port) + 1) 2296 break; 2297 2298 for(i = 0; i < sizeof(addresses[numservers].ip6); i++) 2299 addresses[numservers].ip6[i] = *buffptr++; 2300 2301 addresses[numservers].type = NA_IP6; 2302 addresses[numservers].scope_id = from->scope_id; 2303 } 2304 else 2305 // syntax error! 2306 break; 2307 2308 // parse out port 2309 addresses[numservers].port = (*buffptr++) << 8; 2310 addresses[numservers].port += *buffptr++; 2311 addresses[numservers].port = BigShort( addresses[numservers].port ); 2312 2313 // syntax check 2314 if (*buffptr != '\\' && *buffptr != '/') 2315 break; 2316 2317 numservers++; 2318 if (numservers >= MAX_SERVERSPERPACKET) 2319 break; 2320 } 2321 2322 count = cls.numglobalservers; 2323 2324 for (i = 0; i < numservers && count < MAX_GLOBAL_SERVERS; i++) { 2325 // build net address 2326 serverInfo_t *server = &cls.globalServers[count]; 2327 2328 CL_InitServerInfo( server, &addresses[i] ); 2329 // advance to next slot 2330 count++; 2331 } 2332 2333 // if getting the global list 2334 if ( count >= MAX_GLOBAL_SERVERS && cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS ) 2335 { 2336 // if we couldn't store the servers in the main list anymore 2337 for (; i < numservers && cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS; i++) 2338 { 2339 // just store the addresses in an additional list 2340 cls.globalServerAddresses[cls.numGlobalServerAddresses++] = addresses[i]; 2341 } 2342 } 2343 2344 cls.numglobalservers = count; 2345 total = count + cls.numGlobalServerAddresses; 2346 2347 Com_Printf("%d servers parsed (total %d)\n", numservers, total); 2348} 2349 2350/* 2351================= 2352CL_ConnectionlessPacket 2353 2354Responses to broadcasts, etc 2355================= 2356*/ 2357void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { 2358 char *s; 2359 char *c; 2360 2361 MSG_BeginReadingOOB( msg ); 2362 MSG_ReadLong( msg ); // skip the -1 2363 2364 s = MSG_ReadStringLine( msg ); 2365 2366 Cmd_TokenizeString( s ); 2367 2368 c = Cmd_Argv(0); 2369 2370 Com_DPrintf ("CL packet %s: %s\n", NET_AdrToStringwPort(from), c); 2371 2372 // challenge from the server we are connecting to 2373 if (!Q_stricmp(c, "challengeResponse")) 2374 { 2375 if (cls.state != CA_CONNECTING) 2376 { 2377 Com_DPrintf("Unwanted challenge response received. Ignored.\n"); 2378 return; 2379 } 2380 2381 if(!NET_CompareAdr(from, clc.serverAddress)) 2382 { 2383 // This challenge response is not coming from the expected address. 2384 // Check whether we have a matching client challenge to prevent 2385 // connection hi-jacking. 2386 2387 c = Cmd_Argv(2); 2388 2389 if(!*c || atoi(c) != clc.challenge) 2390 { 2391 Com_DPrintf("Challenge response received from unexpected source. Ignored.\n"); 2392 return; 2393 } 2394 } 2395 2396 // start sending challenge response instead of challenge request packets 2397 clc.challenge = atoi(Cmd_Argv(1)); 2398 cls.state = CA_CHALLENGING; 2399 clc.connectPacketCount = 0; 2400 clc.connectTime = -99999; 2401 2402 // take this address as the new server address. This allows 2403 // a server proxy to hand off connections to multiple servers 2404 clc.serverAddress = from; 2405 Com_DPrintf ("challengeResponse: %d\n", clc.challenge); 2406 return; 2407 } 2408 2409 // server connection 2410 if ( !Q_stricmp(c, "connectResponse") ) { 2411 if ( cls.state >= CA_CONNECTED ) { 2412 Com_Printf ("Dup connect received. Ignored.\n"); 2413 return; 2414 } 2415 if ( cls.state != CA_CHALLENGING ) { 2416 Com_Printf ("connectResponse packet while not connecting. Ignored.\n"); 2417 return; 2418 } 2419 if ( !NET_CompareAdr( from, clc.serverAddress ) ) { 2420 Com_Printf( "connectResponse from wrong address. Ignored.\n" ); 2421 return; 2422 } 2423 Netchan_Setup (NS_CLIENT, &clc.netchan, from, Cvar_VariableValue( "net_qport" ) ); 2424 cls.state = CA_CONNECTED; 2425 clc.lastPacketSentTime = -9999; // send first packet immediately 2426 return; 2427 } 2428 2429 // server responding to an info broadcast 2430 if ( !Q_stricmp(c, "infoResponse") ) { 2431 CL_ServerInfoPacket( from, msg ); 2432 return; 2433 } 2434 2435 // server responding to a get playerlist 2436 if ( !Q_stricmp(c, "statusResponse") ) { 2437 CL_ServerStatusResponse( from, msg ); 2438 return; 2439 } 2440 2441 // a disconnect message from the server, which will happen if the server 2442 // dropped the connection but it is still getting packets from us 2443 if (!Q_stricmp(c, "disconnect")) { 2444 CL_DisconnectPacket( from ); 2445 return; 2446 } 2447 2448 // echo request from server 2449 if ( !Q_stricmp(c, "echo") ) { 2450 NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv(1) ); 2451 return; 2452 } 2453 2454 // cd check 2455 if ( !Q_stricmp(c, "keyAuthorize") ) { 2456 // we don't use these now, so dump them on the floor 2457 return; 2458 } 2459 2460 // global MOTD from id 2461 if ( !Q_stricmp(c, "motd") ) { 2462 CL_MotdPacket( from ); 2463 return; 2464 } 2465 2466 // echo request from server 2467 if ( !Q_stricmp(c, "print") ) { 2468 s = MSG_ReadString( msg ); 2469 Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) ); 2470 Com_Printf( "%s", s ); 2471 return; 2472 } 2473 2474 // list of servers sent back by a master server (classic) 2475 if ( !Q_strncmp(c, "getserversResponse", 18) ) { 2476 CL_ServersResponsePacket( &from, msg, qfalse ); 2477 return; 2478 } 2479 2480 // list of servers sent back by a master server (extended) 2481 if ( !Q_strncmp(c, "getserversExtResponse", 21) ) { 2482 CL_ServersResponsePacket( &from, msg, qtrue ); 2483 return; 2484 } 2485 2486 Com_DPrintf ("Unknown connectionless packet command.\n"); 2487} 2488 2489 2490/* 2491================= 2492CL_PacketEvent 2493 2494A packet has arrived from the main event loop 2495================= 2496*/ 2497void CL_PacketEvent( netadr_t from, msg_t *msg ) { 2498 int headerBytes; 2499 2500 clc.lastPacketTime = cls.realtime; 2501 2502 if ( msg->cursize >= 4 && *(int *)msg->data == -1 ) { 2503 CL_ConnectionlessPacket( from, msg ); 2504 return; 2505 } 2506 2507 if ( cls.state < CA_CONNECTED ) { 2508 return; // can't be a valid sequenced packet 2509 } 2510 2511 if ( msg->cursize < 4 ) { 2512 Com_Printf ("%s: Runt packet\n", NET_AdrToStringwPort( from )); 2513 return; 2514 } 2515 2516 // 2517 // packet from server 2518 // 2519 if ( !NET_CompareAdr( from, clc.netchan.remoteAddress ) ) { 2520 Com_DPrintf ("%s:sequenced packet without connection\n" 2521 , NET_AdrToStringwPort( from ) ); 2522 // FIXME: send a client disconnect? 2523 return; 2524 } 2525 2526 if (!CL_Netchan_Process( &clc.netchan, msg) ) { 2527 return; // out of order, duplicated, etc 2528 } 2529 2530 // the header is different lengths for reliable and unreliable messages 2531 headerBytes = msg->readcount; 2532 2533 // track the last message received so it can be returned in 2534 // client messages, allowing the server to detect a dropped 2535 // gamestate 2536 clc.serverMessageSequence = LittleLong( *(int *)msg->data ); 2537 2538 clc.lastPacketTime = cls.realtime; 2539 CL_ParseServerMessage( msg ); 2540 2541 // 2542 // we don't know if it is ok to save a demo message until 2543 // after we have parsed the frame 2544 // 2545 if ( clc.demorecording && !clc.demowaiting ) { 2546 CL_WriteDemoMessage( msg, headerBytes ); 2547 } 2548} 2549 2550/* 2551================== 2552CL_CheckTimeout 2553 2554================== 2555*/ 2556void CL_CheckTimeout( void ) { 2557 // 2558 // check timeout 2559 // 2560 if ( ( !CL_CheckPaused() || !sv_paused->integer ) 2561 && cls.state >= CA_CONNECTED && cls.state != CA_CINEMATIC 2562 && cls.realtime - clc.lastPacketTime > cl_timeout->value*1000) { 2563 if (++cl.timeoutcount > 5) { // timeoutcount saves debugger 2564 Com_Printf ("\nServer connection timed out.\n"); 2565 CL_Disconnect( qtrue ); 2566 return; 2567 } 2568 } else { 2569 cl.timeoutcount = 0; 2570 } 2571} 2572 2573/* 2574================== 2575CL_CheckPaused 2576Check whether client has been paused. 2577================== 2578*/ 2579qboolean CL_CheckPaused(void) 2580{ 2581 // if cl_paused->modified is set, the cvar has only been changed in 2582 // this frame. Keep paused in this frame to ensure the server doesn't 2583 // lag behind. 2584 if(cl_paused->integer || cl_paused->modified) 2585 return qtrue; 2586 2587 return qfalse; 2588} 2589 2590//============================================================================ 2591 2592/* 2593================== 2594CL_CheckUserinfo 2595 2596================== 2597*/ 2598void CL_CheckUserinfo( void ) { 2599 // don't add reliable commands when not yet connected 2600 if(cls.state < CA_CHALLENGING) 2601 return; 2602 2603 // don't overflow the reliable command buffer when paused 2604 if(CL_CheckPaused()) 2605 return; 2606 2607 // send a reliable userinfo update if needed 2608 if(cvar_modifiedFlags & CVAR_USERINFO) 2609 { 2610 cvar_modifiedFlags &= ~CVAR_USERINFO; 2611 CL_AddReliableCommand(va("userinfo \"%s\"", Cvar_InfoString( CVAR_USERINFO ) ), qfalse); 2612 } 2613} 2614 2615/* 2616================== 2617CL_Frame 2618 2619================== 2620*/ 2621void CL_Frame ( int msec ) { 2622 2623 if ( !com_cl_running->integer ) { 2624 return; 2625 } 2626 2627#ifdef USE_CURL 2628 if(clc.downloadCURLM) { 2629 CL_cURL_PerformDownload(); 2630 // we can't process frames normally when in disconnected 2631 // download mode since the ui vm expects cls.state to be 2632 // CA_CONNECTED 2633 if(clc.cURLDisconnected) { 2634 cls.realFrametime = msec; 2635 cls.frametime = msec; 2636 cls.realtime += cls.frametime; 2637 SCR_UpdateScreen(); 2638 S_Update(); 2639 Con_RunConsole(); 2640 cls.framecount++; 2641 return; 2642 } 2643 } 2644#endif 2645 2646 if ( cls.cddialog ) { 2647 // bring up the cd error dialog if needed 2648 cls.cddialog = qfalse; 2649 VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NEED_CD ); 2650 } else if ( cls.state == CA_DISCONNECTED && !( Key_GetCatcher( ) & KEYCATCH_UI ) 2651 && !com_sv_running->integer && uivm ) { 2652 // if disconnected, bring up the menu 2653 S_StopAllSounds(); 2654 VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); 2655 } 2656 2657 // if recording an avi, lock to a fixed fps 2658 if ( CL_VideoRecording( ) && cl_aviFrameRate->integer && msec) { 2659 // save the current screen 2660 if ( cls.state == CA_ACTIVE || cl_forceavidemo->integer) { 2661 CL_TakeVideoFrame( ); 2662 2663 // fixed time for next frame' 2664 msec = (int)ceil( (1000.0f / cl_aviFrameRate->value) * com_timescale->value ); 2665 if (msec == 0) { 2666 msec = 1; 2667 } 2668 } 2669 } 2670 2671 if( cl_autoRecordDemo->integer ) { 2672 if( cls.state == CA_ACTIVE && !clc.demorecording && !clc.demoplaying ) { 2673 // If not recording a demo, and we should be, start one 2674 qtime_t now; 2675 char *nowString; 2676 char *p; 2677 char mapName[ MAX_QPATH ]; 2678 char serverName[ MAX_OSPATH ]; 2679 2680 Com_RealTime( &now ); 2681 nowString = va( "%04d%02d%02d%02d%02d%02d", 2682 1900 + now.tm_year, 2683 1 + now.tm_mon, 2684 now.tm_mday, 2685 now.tm_hour, 2686 now.tm_min, 2687 now.tm_sec ); 2688 2689 Q_strncpyz( serverName, cls.servername, MAX_OSPATH ); 2690 // Replace the ":" in the address as it is not a valid 2691 // file name character 2692 p = strstr( serverName, ":" ); 2693 if( p ) { 2694 *p = '.'; 2695 } 2696 2697 Q_strncpyz( mapName, COM_SkipPath( cl.mapname ), sizeof( cl.mapname ) ); 2698 COM_StripExtension(mapName, mapName, sizeof(mapName)); 2699 2700 Cbuf_ExecuteText( EXEC_NOW, 2701 va( "record %s-%s-%s", nowString, serverName, mapName ) ); 2702 } 2703 else if( cls.state != CA_ACTIVE && clc.demorecording ) { 2704 // Recording, but not CA_ACTIVE, so stop recording 2705 CL_StopRecord_f( ); 2706 } 2707 } 2708 2709 // save the msec before checking pause 2710 cls.realFrametime = msec; 2711 2712 // decide the simulation time 2713 cls.frametime = msec; 2714 2715 cls.realtime += cls.frametime; 2716 2717 if ( cl_timegraph->integer ) { 2718 SCR_DebugGraph ( cls.realFrametime * 0.25, 0 ); 2719 } 2720 2721 // see if we need to update any userinfo 2722 CL_CheckUserinfo(); 2723 2724 // if we haven't gotten a packet in a long time, 2725 // drop the connection 2726 CL_CheckTimeout(); 2727 2728 // send intentions now 2729 CL_SendCmd(); 2730 2731 // resend a connection request if necessary 2732 CL_CheckForResend(); 2733 2734 // decide on the serverTime to render 2735 CL_SetCGameTime(); 2736 2737 // update the screen 2738 SCR_UpdateScreen(); 2739 2740 // update audio 2741 S_Update(); 2742 2743#ifdef USE_VOIP 2744 CL_CaptureVoip(); 2745#endif 2746 2747#ifdef USE_MUMBLE 2748 CL_UpdateMumble(); 2749#endif 2750 2751 // advance local effects for next frame 2752 SCR_RunCinematic(); 2753 2754 Con_RunConsole(); 2755 2756 cls.framecount++; 2757} 2758 2759 2760//============================================================================ 2761 2762/* 2763================ 2764CL_RefPrintf 2765 2766DLL glue 2767================ 2768*/ 2769void QDECL CL_RefPrintf( int print_level, const char *fmt, ...) { 2770 va_list argptr; 2771 char msg[MAXPRINTMSG]; 2772 2773 va_start (argptr,fmt); 2774 Q_vsnprintf (msg, sizeof(msg), fmt, argptr); 2775 va_end (argptr); 2776 2777 if ( print_level == PRINT_ALL ) { 2778 Com_Printf ("%s", msg); 2779 } else if ( print_level == PRINT_WARNING ) { 2780 Com_Printf (S_COLOR_YELLOW "%s", msg); // yellow 2781 } else if ( print_level == PRINT_DEVELOPER ) { 2782 Com_DPrintf (S_COLOR_RED "%s", msg); // red 2783 } 2784} 2785 2786 2787 2788/* 2789============ 2790CL_ShutdownRef 2791============ 2792*/ 2793void CL_ShutdownRef( void ) { 2794 if ( !re.Shutdown ) { 2795 return; 2796 } 2797 re.Shutdown( qtrue ); 2798 Com_Memset( &re, 0, sizeof( re ) ); 2799} 2800 2801/* 2802============ 2803CL_InitRenderer 2804============ 2805*/ 2806void CL_InitRenderer( void ) { 2807 // this sets up the renderer and calls R_Init 2808 re.BeginRegistration( &cls.glconfig ); 2809 2810 // load character sets 2811 cls.charSetShader = re.RegisterShader( "gfx/2d/bigchars" ); 2812 cls.whiteShader = re.RegisterShader( "white" ); 2813 cls.consoleShader = re.RegisterShader( "console" ); 2814 g_console_field_width = cls.glconfig.vidWidth / SMALLCHAR_WIDTH - 2; 2815 g_consoleField.widthInChars = g_console_field_width; 2816} 2817 2818/* 2819============================ 2820CL_StartHunkUsers 2821 2822After the server has cleared the hunk, these will need to be restarted 2823This is the only place that any of these functions are called from 2824============================ 2825*/ 2826void CL_StartHunkUsers( qboolean rendererOnly ) { 2827 if (!com_cl_running) { 2828 return; 2829 } 2830 2831 if ( !com_cl_running->integer ) { 2832 return; 2833 } 2834 2835 if ( !cls.rendererStarted ) { 2836 cls.rendererStarted = qtrue; 2837 CL_InitRenderer(); 2838 } 2839 2840 if ( rendererOnly ) { 2841 return; 2842 } 2843 2844 if ( !cls.soundStarted ) { 2845 cls.soundStarted = qtrue; 2846 S_Init(); 2847 } 2848 2849 if ( !cls.soundRegistered ) { 2850 cls.soundRegistered = qtrue; 2851 S_BeginRegistration(); 2852 } 2853 2854 if( com_dedicated->integer ) { 2855 return; 2856 } 2857 2858 if ( !cls.uiStarted ) { 2859 cls.uiStarted = qtrue; 2860 CL_InitUI(); 2861 } 2862} 2863 2864/* 2865============ 2866CL_RefMalloc 2867============ 2868*/ 2869void *CL_RefMalloc( int size ) { 2870 return Z_TagMalloc( size, TAG_RENDERER ); 2871} 2872 2873int CL_ScaledMilliseconds(void) { 2874 return Sys_Milliseconds()*com_timescale->value; 2875} 2876 2877/* 2878============ 2879CL_InitRef 2880============ 2881*/ 2882void CL_InitRef( void ) { 2883 refimport_t ri; 2884 refexport_t *ret; 2885 2886 Com_Printf( "----- Initializing Renderer ----\n" ); 2887 2888 ri.Cmd_AddCommand = Cmd_AddCommand; 2889 ri.Cmd_RemoveCommand = Cmd_RemoveCommand; 2890 ri.Cmd_Argc = Cmd_Argc; 2891 ri.Cmd_Argv = Cmd_Argv; 2892 ri.Cmd_ExecuteText = Cbuf_ExecuteText; 2893 ri.Printf = CL_RefPrintf; 2894 ri.Error = Com_Error; 2895 ri.Milliseconds = CL_ScaledMilliseconds; 2896 ri.Malloc = CL_RefMalloc; 2897 ri.Free = Z_Free; 2898#ifdef HUNK_DEBUG 2899 ri.Hunk_AllocDebug = Hunk_AllocDebug; 2900#else 2901 ri.Hunk_Alloc = Hunk_Alloc; 2902#endif 2903 ri.Hunk_AllocateTempMemory = Hunk_AllocateTempMemory; 2904 ri.Hunk_FreeTempMemory = Hunk_FreeTempMemory; 2905 ri.CM_DrawDebugSurface = CM_DrawDebugSurface; 2906 ri.FS_ReadFile = FS_ReadFile; 2907 ri.FS_FreeFile = FS_FreeFile; 2908 ri.FS_WriteFile = FS_WriteFile; 2909 ri.FS_FreeFileList = FS_FreeFileList; 2910 ri.FS_ListFiles = FS_ListFiles; 2911 ri.FS_FileIsInPAK = FS_FileIsInPAK; 2912 ri.FS_FileExists = FS_FileExists; 2913 ri.Cvar_Get = Cvar_Get; 2914 ri.Cvar_Set = Cvar_Set; 2915 ri.Cvar_CheckRange = Cvar_CheckRange; 2916 2917 // cinematic stuff 2918 2919 ri.CIN_UploadCinematic = CIN_UploadCinematic; 2920 ri.CIN_PlayCinematic = CIN_PlayCinematic; 2921 ri.CIN_RunCinematic = CIN_RunCinematic; 2922 2923 ri.CL_WriteAVIVideoFrame = CL_WriteAVIVideoFrame; 2924 2925 ret = GetRefAPI( REF_API_VERSION, &ri ); 2926 2927#if defined __USEA3D && defined __A3D_GEOM 2928 hA3Dg_ExportRenderGeom (ret); 2929#endif 2930 2931 Com_Printf( "-------------------------------\n"); 2932 2933 if ( !ret ) { 2934 Com_Error (ERR_FATAL, "Couldn't initialize refresh" ); 2935 } 2936 2937 re = *ret; 2938 2939 // unpause so the cgame definately gets a snapshot and renders a frame 2940 Cvar_Set( "cl_paused", "0" ); 2941} 2942 2943 2944//=========================================================================================== 2945 2946 2947void CL_SetModel_f( void ) { 2948 char *arg; 2949 char name[256]; 2950 2951 arg = Cmd_Argv( 1 ); 2952 if (arg[0]) { 2953 Cvar_Set( "model", arg ); 2954 Cvar_Set( "headmodel", arg ); 2955 } else { 2956 Cvar_VariableStringBuffer( "model", name, sizeof(name) ); 2957 Com_Printf("model is set to %s\n", name); 2958 } 2959} 2960 2961 2962//=========================================================================================== 2963 2964 2965/* 2966=============== 2967CL_Video_f 2968 2969video 2970video [filename] 2971=============== 2972*/ 2973void CL_Video_f( void ) 2974{ 2975 char filename[ MAX_OSPATH ]; 2976 int i, last; 2977 2978 if( !clc.demoplaying ) 2979 { 2980 Com_Printf( "The video command can only be used when playing back demos\n" ); 2981 return; 2982 } 2983 2984 if( Cmd_Argc( ) == 2 ) 2985 { 2986 // explicit filename 2987 Com_sprintf( filename, MAX_OSPATH, "videos/%s.avi", Cmd_Argv( 1 ) ); 2988 } 2989 else 2990 { 2991 // scan for a free filename 2992 for( i = 0; i <= 9999; i++ ) 2993 { 2994 int a, b, c, d; 2995 2996 last = i; 2997 2998 a = last / 1000; 2999 last -= a * 1000; 3000 b = last / 100; 3001 last -= b * 100; 3002 c = last / 10; 3003 last -= c * 10; 3004 d = last; 3005 3006 Com_sprintf( filename, MAX_OSPATH, "videos/video%d%d%d%d.avi", 3007 a, b, c, d ); 3008 3009 if( !FS_FileExists( filename ) ) 3010 break; // file doesn't exist 3011 } 3012 3013 if( i > 9999 ) 3014 { 3015 Com_Printf( S_COLOR_RED "ERROR: no free file names to create video\n" ); 3016 return; 3017 } 3018 } 3019 3020 CL_OpenAVIForWriting( filename ); 3021} 3022 3023/* 3024=============== 3025CL_StopVideo_f 3026=============== 3027*/ 3028void CL_StopVideo_f( void ) 3029{ 3030 CL_CloseAVI( ); 3031} 3032 3033/* 3034=============== 3035CL_GenerateQKey 3036 3037test to see if a valid QKEY_FILE exists. If one does not, try to generate 3038it by filling it with 2048 bytes of random data. 3039=============== 3040*/ 3041static void CL_GenerateQKey(void) 3042{ 3043 int len = 0; 3044 unsigned char buff[ QKEY_SIZE ]; 3045 fileHandle_t f; 3046 3047 len = FS_SV_FOpenFileRead( QKEY_FILE, &f ); 3048 FS_FCloseFile( f ); 3049 if( len == QKEY_SIZE ) { 3050 Com_Printf( "QKEY found.\n" ); 3051 return; 3052 } 3053 else { 3054 if( len > 0 ) { 3055 Com_Printf( "QKEY file size != %d, regenerating\n", 3056 QKEY_SIZE ); 3057 } 3058 3059 Com_Printf( "QKEY building random string\n" ); 3060 Com_RandomBytes( buff, sizeof(buff) ); 3061 3062 f = FS_SV_FOpenFileWrite( QKEY_FILE ); 3063 if( !f ) { 3064 Com_Printf( "QKEY could not open %s for write\n", 3065 QKEY_FILE ); 3066 return; 3067 } 3068 FS_Write( buff, sizeof(buff), f ); 3069 FS_FCloseFile( f ); 3070 Com_Printf( "QKEY generated\n" ); 3071 } 3072} 3073 3074/* 3075==================== 3076CL_Init 3077==================== 3078*/ 3079void CL_Init( void ) { 3080 Com_Printf( "----- Client Initialization -----\n" ); 3081 3082 Con_Init (); 3083 3084 CL_ClearState (); 3085 3086 cls.state = CA_DISCONNECTED; // no longer CA_UNINITIALIZED 3087 3088 cls.realtime = 0; 3089 3090 CL_InitInput (); 3091 3092 // 3093 // register our variables 3094 // 3095 cl_noprint = Cvar_Get( "cl_noprint", "0", 0 ); 3096 cl_motd = Cvar_Get ("cl_motd", "1", 0); 3097 3098 cl_timeout = Cvar_Get ("cl_timeout", "200", 0); 3099 3100 cl_timeNudge = Cvar_Get ("cl_timeNudge", "0", CVAR_TEMP ); 3101 cl_shownet = Cvar_Get ("cl_shownet", "0", CVAR_TEMP ); 3102 cl_showSend = Cvar_Get ("cl_showSend", "0", CVAR_TEMP ); 3103 cl_showTimeDelta = Cvar_Get ("cl_showTimeDelta", "0", CVAR_TEMP ); 3104 cl_freezeDemo = Cvar_Get ("cl_freezeDemo", "0", CVAR_TEMP ); 3105 rcon_client_password = Cvar_Get ("rconPassword", "", CVAR_TEMP ); 3106 cl_activeAction = Cvar_Get( "activeAction", "", CVAR_TEMP ); 3107 3108 cl_timedemo = Cvar_Get ("timedemo", "0", 0); 3109 cl_timedemoLog = Cvar_Get ("cl_timedemoLog", "", CVAR_ARCHIVE); 3110 cl_autoRecordDemo = Cvar_Get ("cl_autoRecordDemo", "0", CVAR_ARCHIVE); 3111 cl_aviFrameRate = Cvar_Get ("cl_aviFrameRate", "25", CVAR_ARCHIVE); 3112 cl_aviMotionJpeg = Cvar_Get ("cl_aviMotionJpeg", "1", CVAR_ARCHIVE); 3113 cl_forceavidemo = Cvar_Get ("cl_forceavidemo", "0", 0); 3114 3115 rconAddress = Cvar_Get ("rconAddress", "", 0); 3116 3117 //cl_yawspeed = Cvar_Get ("cl_yawspeed", "140", CVAR_ARCHIVE); 3118 //cl_pitchspeed = Cvar_Get ("cl_pitchspeed", "140", CVAR_ARCHIVE); 3119 cl_anglespeedkey = Cvar_Get ("cl_anglespeedkey", "1.5", 0); 3120 3121 cl_maxpackets = Cvar_Get ("cl_maxpackets", "30", CVAR_ARCHIVE ); 3122 cl_packetdup = Cvar_Get ("cl_packetdup", "1", CVAR_ARCHIVE ); 3123 3124 cl_run = Cvar_Get ("cl_run", "1", CVAR_ARCHIVE); 3125 cl_sensitivity = Cvar_Get ("sensitivity", "5", CVAR_ARCHIVE); 3126 cl_mouseAccel = Cvar_Get ("cl_mouseAccel", "0", CVAR_ARCHIVE); 3127 cl_freelook = Cvar_Get( "cl_freelook", "1", CVAR_ARCHIVE ); 3128 3129 // 0: legacy mouse acceleration 3130 // 1: new implementation 3131 cl_mouseAccelStyle = Cvar_Get( "cl_mouseAccelStyle", "0", CVAR_ARCHIVE ); 3132 // offset for the power function (for style 1, ignored otherwise) 3133 // this should be set to the max rate value 3134 cl_mouseAccelOffset = Cvar_Get( "cl_mouseAccelOffset", "5", CVAR_ARCHIVE ); 3135 3136 cl_showMouseRate = Cvar_Get ("cl_showmouserate", "0", 0); 3137 3138 cl_allowDownload = Cvar_Get ("cl_allowDownload", "0", CVAR_ARCHIVE); 3139#ifdef USE_CURL 3140 cl_cURLLib = Cvar_Get("cl_cURLLib", DEFAULT_CURL_LIB, CVAR_ARCHIVE); 3141#endif 3142 3143 cl_conXOffset = Cvar_Get ("cl_conXOffset", "0", 0); 3144#ifdef MACOS_X 3145 // In game video is REALLY slow in Mac OS X right now due to driver slowness 3146 cl_inGameVideo = Cvar_Get ("r_inGameVideo", "0", CVAR_ARCHIVE); 3147#else 3148 cl_inGameVideo = Cvar_Get ("r_inGameVideo", "1", CVAR_ARCHIVE); 3149#endif 3150 3151 cl_serverStatusResendTime = Cvar_Get ("cl_serverStatusResendTime", "750", 0); 3152 3153 // init autoswitch so the ui will have it correctly even 3154 // if the cgame hasn't been started 3155 Cvar_Get ("cg_autoswitch", "1", CVAR_ARCHIVE); 3156 3157 m_pitch = Cvar_Get ("m_pitch", "0.022", CVAR_ARCHIVE); 3158 m_yaw = Cvar_Get ("m_yaw", "0.022", CVAR_ARCHIVE); 3159 m_forward = Cvar_Get ("m_forward", "0.25", CVAR_ARCHIVE); 3160 m_side = Cvar_Get ("m_side", "0.25", CVAR_ARCHIVE); 3161#ifdef MACOS_X 3162 // Input is jittery on OS X w/o this 3163 m_filter = Cvar_Get ("m_filter", "1", CVAR_ARCHIVE); 3164#else 3165 m_filter = Cvar_Get ("m_filter", "0", CVAR_ARCHIVE); 3166#endif 3167 3168 cl_motdString = Cvar_Get( "cl_motdString", "", CVAR_ROM ); 3169 3170 Cvar_Get( "cl_maxPing", "800", CVAR_ARCHIVE ); 3171 3172 cl_lanForcePackets = Cvar_Get ("cl_lanForcePackets", "1", CVAR_ARCHIVE); 3173 3174 cl_guidServerUniq = Cvar_Get ("cl_guidServerUniq", "1", CVAR_ARCHIVE); 3175 3176 // ~ and `, as keys and characters 3177 cl_consoleKeys = Cvar_Get( "cl_consoleKeys", "~ ` 0x7e 0x60", CVAR_ARCHIVE); 3178 3179 cl_consoleType = Cvar_Get( "cl_consoleType", "0", CVAR_ARCHIVE ); 3180 cl_consoleColor[0] = Cvar_Get( "cl_consoleColorRed", "1", CVAR_ARCHIVE ); 3181 cl_consoleColor[1] = Cvar_Get( "cl_consoleColorGreen", "0", CVAR_ARCHIVE ); 3182 cl_consoleColor[2] = Cvar_Get( "cl_consoleColorBlue", "0", CVAR_ARCHIVE ); 3183 cl_consoleColor[3] = Cvar_Get( "cl_consoleColorAlpha", "0.8", CVAR_ARCHIVE ); 3184 3185 cl_consoleHeight = Cvar_Get("cl_consoleHeight", "0.5", CVAR_ARCHIVE); 3186 3187 // userinfo 3188 Cvar_Get ("name", "UnnamedPlayer", CVAR_USERINFO | CVAR_ARCHIVE ); 3189 Cvar_Get ("rate", "25000", CVAR_USERINFO | CVAR_ARCHIVE ); 3190 Cvar_Get ("snaps", "20", CVAR_USERINFO | CVAR_ARCHIVE ); 3191 Cvar_Get ("model", "sarge", CVAR_USERINFO | CVAR_ARCHIVE ); 3192 Cvar_Get ("headmodel", "sarge", CVAR_USERINFO | CVAR_ARCHIVE ); 3193 Cvar_Get ("team_model", "james", CVAR_USERINFO | CVAR_ARCHIVE ); 3194 Cvar_Get ("team_headmodel", "*james", CVAR_USERINFO | CVAR_ARCHIVE ); 3195 Cvar_Get ("g_redTeam", "Stroggs", CVAR_SERVERINFO | CVAR_ARCHIVE); 3196 Cvar_Get ("g_blueTeam", "Pagans", CVAR_SERVERINFO | CVAR_ARCHIVE); 3197 Cvar_Get ("color1", "4", CVAR_USERINFO | CVAR_ARCHIVE ); 3198 Cvar_Get ("color2", "5", CVAR_USERINFO | CVAR_ARCHIVE ); 3199 Cvar_Get ("handicap", "100", CVAR_USERINFO | CVAR_ARCHIVE ); 3200 Cvar_Get ("teamtask", "0", CVAR_USERINFO ); 3201 Cvar_Get ("sex", "male", CVAR_USERINFO | CVAR_ARCHIVE ); 3202 Cvar_Get ("cl_anonymous", "0", CVAR_USERINFO | CVAR_ARCHIVE ); 3203 3204 Cvar_Get ("password", "", CVAR_USERINFO); 3205 Cvar_Get ("cg_predictItems", "1", CVAR_USERINFO | CVAR_ARCHIVE ); 3206 3207#ifdef USE_MUMBLE 3208 cl_useMumble = Cvar_Get ("cl_useMumble", "0", CVAR_ARCHIVE | CVAR_LATCH); 3209 cl_mumbleScale = Cvar_Get ("cl_mumbleScale", "0.0254", CVAR_ARCHIVE); 3210#endif 3211 3212#ifdef USE_VOIP 3213 cl_voipSend = Cvar_Get ("cl_voipSend", "0", 0); 3214 cl_voipSendTarget = Cvar_Get ("cl_voipSendTarget", "all", 0); 3215 cl_voipGainDuringCapture = Cvar_Get ("cl_voipGainDuringCapture", "0.2", CVAR_ARCHIVE); 3216 cl_voipCaptureMult = Cvar_Get ("cl_voipCaptureMult", "2.0", CVAR_ARCHIVE); 3217 cl_voipUseVAD = Cvar_Get ("cl_voipUseVAD", "0", CVAR_ARCHIVE); 3218 cl_voipVADThreshold = Cvar_Get ("cl_voipVADThreshold", "0.25", CVAR_ARCHIVE); 3219 cl_voipShowMeter = Cvar_Get ("cl_voipShowMeter", "1", CVAR_ARCHIVE); 3220 3221 // This is a protocol version number. 3222 cl_voip = Cvar_Get ("cl_voip", "1", CVAR_USERINFO | CVAR_ARCHIVE | CVAR_LATCH); 3223 Cvar_CheckRange( cl_voip, 0, 1, qtrue ); 3224 3225 // If your data rate is too low, you'll get Connection Interrupted warnings 3226 // when VoIP packets arrive, even if you have a broadband connection. 3227 // This might work on rates lower than 25000, but for safety's sake, we'll 3228 // just demand it. Who doesn't have at least a DSL line now, anyhow? If 3229 // you don't, you don't need VoIP. :) 3230 if ((cl_voip->integer) && (Cvar_VariableIntegerValue("rate") < 25000)) { 3231 Com_Printf("Your network rate is too slow for VoIP.\n"); 3232 Com_Printf("Set 'Data Rate' to 'LAN/Cable/xDSL' in 'Setup/System/Network' and restart.\n"); 3233 Com_Printf("Until then, VoIP is disabled.\n"); 3234 Cvar_Set("cl_voip", "0"); 3235 } 3236#endif 3237 3238 3239 // cgame might not be initialized before menu is used 3240 Cvar_Get ("cg_viewsize", "100", CVAR_ARCHIVE ); 3241 // Make sure cg_stereoSeparation is zero as that variable is deprecated and should not be used anymore. 3242 Cvar_Get ("cg_stereoSeparation", "0", CVAR_ROM); 3243 3244 // 3245 // register our commands 3246 // 3247 Cmd_AddCommand ("cmd", CL_ForwardToServer_f); 3248 Cmd_AddCommand ("configstrings", CL_Configstrings_f); 3249 Cmd_AddCommand ("clientinfo", CL_Clientinfo_f); 3250 Cmd_AddCommand ("snd_restart", CL_Snd_Restart_f); 3251 Cmd_AddCommand ("vid_restart", CL_Vid_Restart_f); 3252 Cmd_AddCommand ("disconnect", CL_Disconnect_f); 3253 Cmd_AddCommand ("record", CL_Record_f); 3254 Cmd_AddCommand ("demo", CL_PlayDemo_f); 3255 Cmd_SetCommandCompletionFunc( "demo", CL_CompleteDemoName ); 3256 Cmd_AddCommand ("cinematic", CL_PlayCinematic_f); 3257 Cmd_AddCommand ("stoprecord", CL_StopRecord_f); 3258 Cmd_AddCommand ("connect", CL_Connect_f); 3259 Cmd_AddCommand ("reconnect", CL_Reconnect_f); 3260 Cmd_AddCommand ("localservers", CL_LocalServers_f); 3261 Cmd_AddCommand ("globalservers", CL_GlobalServers_f); 3262 Cmd_AddCommand ("rcon", CL_Rcon_f); 3263 Cmd_SetCommandCompletionFunc( "rcon", CL_CompleteRcon ); 3264 Cmd_AddCommand ("ping", CL_Ping_f ); 3265 Cmd_AddCommand ("serverstatus", CL_ServerStatus_f ); 3266 Cmd_AddCommand ("showip", CL_ShowIP_f ); 3267 Cmd_AddCommand ("fs_openedList", CL_OpenedPK3List_f ); 3268 Cmd_AddCommand ("fs_referencedList", CL_ReferencedPK3List_f ); 3269 Cmd_AddCommand ("model", CL_SetModel_f ); 3270 Cmd_AddCommand ("video", CL_Video_f ); 3271 Cmd_AddCommand ("stopvideo", CL_StopVideo_f ); 3272 CL_InitRef(); 3273 3274 SCR_Init (); 3275 3276// Cbuf_Execute (); 3277 3278 Cvar_Set( "cl_running", "1" ); 3279 3280 CL_GenerateQKey(); 3281 Cvar_Get( "cl_guid", "", CVAR_USERINFO | CVAR_ROM ); 3282 CL_UpdateGUID( NULL, 0 ); 3283 3284 Com_Printf( "----- Client Initialization Complete -----\n" ); 3285} 3286 3287 3288/* 3289=============== 3290CL_Shutdown 3291 3292=============== 3293*/ 3294void CL_Shutdown( char *finalmsg ) { 3295 static qboolean recursive = qfalse; 3296 3297 // check whether the client is running at all. 3298 if(!(com_cl_running && com_cl_running->integer)) 3299 return; 3300 3301 Com_Printf( "----- Client Shutdown (%s) -----\n", finalmsg ); 3302 3303 if ( recursive ) { 3304 Com_Printf( "WARNING: Recursive shutdown\n" ); 3305 return; 3306 } 3307 recursive = qtrue; 3308 3309 CL_Disconnect( qtrue ); 3310 3311 S_Shutdown(); 3312 CL_ShutdownRef(); 3313 3314 CL_ShutdownUI(); 3315 3316 Cmd_RemoveCommand ("cmd"); 3317 Cmd_RemoveCommand ("configstrings"); 3318 Cmd_RemoveCommand ("userinfo"); 3319 Cmd_RemoveCommand ("snd_restart"); 3320 Cmd_RemoveCommand ("vid_restart"); 3321 Cmd_RemoveCommand ("disconnect"); 3322 Cmd_RemoveCommand ("record"); 3323 Cmd_RemoveCommand ("demo"); 3324 Cmd_RemoveCommand ("cinematic"); 3325 Cmd_RemoveCommand ("stoprecord"); 3326 Cmd_RemoveCommand ("connect"); 3327 Cmd_RemoveCommand ("localservers"); 3328 Cmd_RemoveCommand ("globalservers"); 3329 Cmd_RemoveCommand ("rcon"); 3330 Cmd_RemoveCommand ("ping"); 3331 Cmd_RemoveCommand ("serverstatus"); 3332 Cmd_RemoveCommand ("showip"); 3333 Cmd_RemoveCommand ("model"); 3334 Cmd_RemoveCommand ("video"); 3335 Cmd_RemoveCommand ("stopvideo"); 3336 3337 Cvar_Set( "cl_running", "0" ); 3338 3339 recursive = qfalse; 3340 3341 Com_Memset( &cls, 0, sizeof( cls ) ); 3342 Key_SetCatcher( 0 ); 3343 3344 Com_Printf( "-----------------------\n" ); 3345 3346} 3347 3348static void CL_SetServerInfo(serverInfo_t *server, const char *info, int ping) { 3349 if (server) { 3350 if (info) { 3351 server->clients = atoi(Info_ValueForKey(info, "clients")); 3352 Q_strncpyz(server->hostName,Info_ValueForKey(info, "hostname"), MAX_NAME_LENGTH); 3353 Q_strncpyz(server->mapName, Info_ValueForKey(info, "mapname"), MAX_NAME_LENGTH); 3354 server->maxClients = atoi(Info_ValueForKey(info, "sv_maxclients")); 3355 Q_strncpyz(server->game,Info_ValueForKey(info, "game"), MAX_NAME_LENGTH); 3356 server->gameType = atoi(Info_ValueForKey(info, "gametype")); 3357 server->netType = atoi(Info_ValueForKey(info, "nettype")); 3358 server->minPing = atoi(Info_ValueForKey(info, "minping")); 3359 server->maxPing = atoi(Info_ValueForKey(info, "maxping")); 3360 server->g_humanplayers = atoi(Info_ValueForKey(info, "g_humanplayers")); 3361 server->g_needpass = atoi(Info_ValueForKey(info, "g_needpass")); 3362 } 3363 server->ping = ping; 3364 } 3365} 3366 3367static void CL_SetServerInfoByAddress(netadr_t from, const char *info, int ping) { 3368 int i; 3369 3370 for (i = 0; i < MAX_OTHER_SERVERS; i++) { 3371 if (NET_CompareAdr(from, cls.localServers[i].adr)) { 3372 CL_SetServerInfo(&cls.localServers[i], info, ping); 3373 } 3374 } 3375 3376 for (i = 0; i < MAX_GLOBAL_SERVERS; i++) { 3377 if (NET_CompareAdr(from, cls.globalServers[i].adr)) { 3378 CL_SetServerInfo(&cls.globalServers[i], info, ping); 3379 } 3380 } 3381 3382 for (i = 0; i < MAX_OTHER_SERVERS; i++) { 3383 if (NET_CompareAdr(from, cls.favoriteServers[i].adr)) { 3384 CL_SetServerInfo(&cls.favoriteServers[i], info, ping); 3385 } 3386 } 3387 3388} 3389 3390/* 3391=================== 3392CL_ServerInfoPacket 3393=================== 3394*/ 3395void CL_ServerInfoPacket( netadr_t from, msg_t *msg ) { 3396 int i, type; 3397 char info[MAX_INFO_STRING]; 3398 char *infoString; 3399 int prot; 3400 3401 infoString = MSG_ReadString( msg ); 3402 3403 // if this isn't the correct protocol version, ignore it 3404 prot = atoi( Info_ValueForKey( infoString, "protocol" ) ); 3405 if ( prot != PROTOCOL_VERSION ) { 3406 Com_DPrintf( "Different protocol info packet: %s\n", infoString ); 3407 return; 3408 } 3409 3410 // iterate servers waiting for ping response 3411 for (i=0; i<MAX_PINGREQUESTS; i++) 3412 { 3413 if ( cl_pinglist[i].adr.port && !cl_pinglist[i].time && NET_CompareAdr( from, cl_pinglist[i].adr ) ) 3414 { 3415 // calc ping time 3416 cl_pinglist[i].time = Sys_Milliseconds() - cl_pinglist[i].start; 3417 Com_DPrintf( "ping time %dms from %s\n", cl_pinglist[i].time, NET_AdrToString( from ) ); 3418 3419 // save of info 3420 Q_strncpyz( cl_pinglist[i].info, infoString, sizeof( cl_pinglist[i].info ) ); 3421 3422 // tack on the net type 3423 // NOTE: make sure these types are in sync with the netnames strings in the UI 3424 switch (from.type) 3425 { 3426 case NA_BROADCAST: 3427 case NA_IP: 3428 type = 1; 3429 break; 3430 case NA_IP6: 3431 type = 2; 3432 break; 3433 default: 3434 type = 0; 3435 break; 3436 } 3437 Info_SetValueForKey( cl_pinglist[i].info, "nettype", va("%d", type) ); 3438 CL_SetServerInfoByAddress(from, infoString, cl_pinglist[i].time); 3439 3440 return; 3441 } 3442 } 3443 3444 // if not just sent a local broadcast or pinging local servers 3445 if (cls.pingUpdateSource != AS_LOCAL) { 3446 return; 3447 } 3448 3449 for ( i = 0 ; i < MAX_OTHER_SERVERS ; i++ ) { 3450 // empty slot 3451 if ( cls.localServers[i].adr.port == 0 ) { 3452 break; 3453 } 3454 3455 // avoid duplicate 3456 if ( NET_CompareAdr( from, cls.localServers[i].adr ) ) { 3457 return; 3458 } 3459 } 3460 3461 if ( i == MAX_OTHER_SERVERS ) { 3462 Com_DPrintf( "MAX_OTHER_SERVERS hit, dropping infoResponse\n" ); 3463 return; 3464 } 3465 3466 // add this to the list 3467 cls.numlocalservers = i+1; 3468 cls.localServers[i].adr = from; 3469 cls.localServers[i].clients = 0; 3470 cls.localServers[i].hostName[0] = '\0'; 3471 cls.localServers[i].mapName[0] = '\0'; 3472 cls.localServers[i].maxClients = 0; 3473 cls.localServers[i].maxPing = 0; 3474 cls.localServers[i].minPing = 0; 3475 cls.localServers[i].ping = -1; 3476 cls.localServers[i].game[0] = '\0'; 3477 cls.localServers[i].gameType = 0; 3478 cls.localServers[i].netType = from.type; 3479 3480 Q_strncpyz( info, MSG_ReadString( msg ), MAX_INFO_STRING ); 3481 if (strlen(info)) { 3482 if (info[strlen(info)-1] != '\n') { 3483 strncat(info, "\n", sizeof(info) - 1); 3484 } 3485 Com_Printf( "%s: %s", NET_AdrToStringwPort( from ), info ); 3486 } 3487} 3488 3489/* 3490=================== 3491CL_GetServerStatus 3492=================== 3493*/ 3494serverStatus_t *CL_GetServerStatus( netadr_t from ) { 3495 serverStatus_t *serverStatus; 3496 int i, oldest, oldestTime; 3497 3498 serverStatus = NULL; 3499 for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { 3500 if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) { 3501 return &cl_serverStatusList[i]; 3502 } 3503 } 3504 for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { 3505 if ( cl_serverStatusList[i].retrieved ) { 3506 return &cl_serverStatusList[i]; 3507 } 3508 } 3509 oldest = -1; 3510 oldestTime = 0; 3511 for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { 3512 if (oldest == -1 || cl_serverStatusList[i].startTime < oldestTime) { 3513 oldest = i; 3514 oldestTime = cl_serverStatusList[i].startTime; 3515 } 3516 } 3517 if (oldest != -1) { 3518 return &cl_serverStatusList[oldest]; 3519 } 3520 serverStatusCount++; 3521 return &cl_serverStatusList[serverStatusCount & (MAX_SERVERSTATUSREQUESTS-1)]; 3522} 3523 3524/* 3525=================== 3526CL_ServerStatus 3527=================== 3528*/ 3529int CL_ServerStatus( char *serverAddress, char *serverStatusString, int maxLen ) { 3530 int i; 3531 netadr_t to; 3532 serverStatus_t *serverStatus; 3533 3534 // if no server address then reset all server status requests 3535 if ( !serverAddress ) { 3536 for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { 3537 cl_serverStatusList[i].address.port = 0; 3538 cl_serverStatusList[i].retrieved = qtrue; 3539 } 3540 return qfalse; 3541 } 3542 // get the address 3543 if ( !NET_StringToAdr( serverAddress, &to, NA_UNSPEC) ) { 3544 return qfalse; 3545 } 3546 serverStatus = CL_GetServerStatus( to ); 3547 // if no server status string then reset the server status request for this address 3548 if ( !serverStatusString ) { 3549 serverStatus->retrieved = qtrue; 3550 return qfalse; 3551 } 3552 3553 // if this server status request has the same address 3554 if ( NET_CompareAdr( to, serverStatus->address) ) { 3555 // if we recieved an response for this server status request 3556 if (!serverStatus->pending) { 3557 Q_strncpyz(serverStatusString, serverStatus->string, maxLen); 3558 serverStatus->retrieved = qtrue; 3559 serverStatus->startTime = 0; 3560 return qtrue; 3561 } 3562 // resend the request regularly 3563 else if ( serverStatus->startTime < Com_Milliseconds() - cl_serverStatusResendTime->integer ) { 3564 serverStatus->print = qfalse; 3565 serverStatus->pending = qtrue; 3566 serverStatus->retrieved = qfalse; 3567 serverStatus->time = 0; 3568 serverStatus->startTime = Com_Milliseconds(); 3569 NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); 3570 return qfalse; 3571 } 3572 } 3573 // if retrieved 3574 else if ( serverStatus->retrieved ) { 3575 serverStatus->address = to; 3576 serverStatus->print = qfalse; 3577 serverStatus->pending = qtrue; 3578 serverStatus->retrieved = qfalse; 3579 serverStatus->startTime = Com_Milliseconds(); 3580 serverStatus->time = 0; 3581 NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); 3582 return qfalse; 3583 } 3584 return qfalse; 3585} 3586 3587/* 3588=================== 3589CL_ServerStatusResponse 3590=================== 3591*/ 3592void CL_ServerStatusResponse( netadr_t from, msg_t *msg ) { 3593 char *s; 3594 char info[MAX_INFO_STRING]; 3595 int i, l, score, ping; 3596 int len; 3597 serverStatus_t *serverStatus; 3598 3599 serverStatus = NULL; 3600 for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { 3601 if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) { 3602 serverStatus = &cl_serverStatusList[i]; 3603 break; 3604 } 3605 } 3606 // if we didn't request this server status 3607 if (!serverStatus) { 3608 return; 3609 } 3610 3611 s = MSG_ReadStringLine( msg ); 3612 3613 len = 0; 3614 Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "%s", s); 3615 3616 if (serverStatus->print) { 3617 Com_Printf("Server settings:\n"); 3618 // print cvars 3619 while (*s) { 3620 for (i = 0; i < 2 && *s; i++) { 3621 if (*s == '\\') 3622 s++; 3623 l = 0; 3624 while (*s) { 3625 info[l++] = *s; 3626 if (l >= MAX_INFO_STRING-1) 3627 break; 3628 s++; 3629 if (*s == '\\') { 3630 break; 3631 } 3632 } 3633 info[l] = '\0'; 3634 if (i) { 3635 Com_Printf("%s\n", info); 3636 } 3637 else { 3638 Com_Printf("%-24s", info); 3639 } 3640 } 3641 } 3642 } 3643 3644 len = strlen(serverStatus->string); 3645 Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\"); 3646 3647 if (serverStatus->print) { 3648 Com_Printf("\nPlayers:\n"); 3649 Com_Printf("score: ping: name:\n"); 3650 } 3651 for (i = 0, s = MSG_ReadStringLine( msg ); *s; s = MSG_ReadStringLine( msg ), i++) { 3652 3653 len = strlen(serverStatus->string); 3654 Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\%s", s); 3655 3656 if (serverStatus->print) { 3657 score = ping = 0; 3658 sscanf(s, "%d %d", &score, &ping); 3659 s = strchr(s, ' '); 3660 if (s) 3661 s = strchr(s+1, ' '); 3662 if (s) 3663 s++; 3664 else 3665 s = "unknown"; 3666 Com_Printf("%-2d %-3d %-3d %s\n", i, score, ping, s ); 3667 } 3668 } 3669 len = strlen(serverStatus->string); 3670 Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\"); 3671 3672 serverStatus->time = Com_Milliseconds(); 3673 serverStatus->address = from; 3674 serverStatus->pending = qfalse; 3675 if (serverStatus->print) { 3676 serverStatus->retrieved = qtrue; 3677 } 3678} 3679 3680/* 3681================== 3682CL_LocalServers_f 3683================== 3684*/ 3685void CL_LocalServers_f( void ) { 3686 char *message; 3687 int i, j; 3688 netadr_t to; 3689 3690 Com_Printf( "Scanning for servers on the local network...\n"); 3691 3692 // reset the list, waiting for response 3693 cls.numlocalservers = 0; 3694 cls.pingUpdateSource = AS_LOCAL; 3695 3696 for (i = 0; i < MAX_OTHER_SERVERS; i++) { 3697 qboolean b = cls.localServers[i].visible; 3698 Com_Memset(&cls.localServers[i], 0, sizeof(cls.localServers[i])); 3699 cls.localServers[i].visible = b; 3700 } 3701 Com_Memset( &to, 0, sizeof( to ) ); 3702 3703 // The 'xxx' in the message is a challenge that will be echoed back 3704 // by the server. We don't care about that here, but master servers 3705 // can use that to prevent spoofed server responses from invalid ip 3706 message = "\377\377\377\377getinfo xxx"; 3707 3708 // send each message twice in case one is dropped 3709 for ( i = 0 ; i < 2 ; i++ ) { 3710 // send a broadcast packet on each server port 3711 // we support multiple server ports so a single machine 3712 // can nicely run multiple servers 3713 for ( j = 0 ; j < NUM_SERVER_PORTS ; j++ ) { 3714 to.port = BigShort( (short)(PORT_SERVER + j) ); 3715 3716 to.type = NA_BROADCAST; 3717 NET_SendPacket( NS_CLIENT, strlen( message ), message, to ); 3718 to.type = NA_MULTICAST6; 3719 NET_SendPacket( NS_CLIENT, strlen( message ), message, to ); 3720 } 3721 } 3722} 3723 3724/* 3725================== 3726CL_GlobalServers_f 3727================== 3728*/ 3729void CL_GlobalServers_f( void ) { 3730 netadr_t to; 3731 int count, i, masterNum; 3732 char command[1024], *masteraddress; 3733 char *cmdname; 3734 3735 if ((count = Cmd_Argc()) < 3 || (masterNum = atoi(Cmd_Argv(1))) < 0 || masterNum > 4) 3736 { 3737 Com_Printf( "usage: globalservers <master# 0-4> <protocol> [keywords]\n"); 3738 return; 3739 } 3740 3741 sprintf(command, "sv_master%d", masterNum + 1); 3742 masteraddress = Cvar_VariableString(command); 3743 3744 if(!*masteraddress) 3745 { 3746 Com_Printf( "CL_GlobalServers_f: Error: No master server address given.\n"); 3747 return; 3748 } 3749 3750 // reset the list, waiting for response 3751 // -1 is used to distinguish a "no response" 3752 3753 i = NET_StringToAdr(masteraddress, &to, NA_UNSPEC); 3754 3755 if(!i) 3756 { 3757 Com_Printf( "CL_GlobalServers_f: Error: could not resolve address of master %s\n", masteraddress); 3758 return; 3759 } 3760 else if(i == 2) 3761 to.port = BigShort(PORT_MASTER); 3762 3763 Com_Printf("Requesting servers from master %s...\n", masteraddress); 3764 3765 cls.numglobalservers = -1; 3766 cls.pingUpdateSource = AS_GLOBAL; 3767 3768 // Use the extended query for IPv6 masters 3769 if (to.type == NA_IP6 || to.type == NA_MULTICAST6) 3770 { 3771 cmdname = "getserversExt " GAMENAME_FOR_MASTER; 3772 3773 // TODO: test if we only have an IPv6 connection. If it's the case, 3774 // request IPv6 servers only by appending " ipv6" to the command 3775 } 3776 else 3777 cmdname = "getservers"; 3778 Com_sprintf( command, sizeof(command), "%s %s", cmdname, Cmd_Argv(2) ); 3779 3780 for (i=3; i < count; i++) 3781 { 3782 Q_strcat(command, sizeof(command), " "); 3783 Q_strcat(command, sizeof(command), Cmd_Argv(i)); 3784 } 3785 3786 NET_OutOfBandPrint( NS_SERVER, to, "%s", command ); 3787} 3788 3789 3790/* 3791================== 3792CL_GetPing 3793================== 3794*/ 3795void CL_GetPing( int n, char *buf, int buflen, int *pingtime ) 3796{ 3797 const char *str; 3798 int time; 3799 int maxPing; 3800 3801 if (!cl_pinglist[n].adr.port) 3802 { 3803 // empty slot 3804 buf[0] = '\0'; 3805 *pingtime = 0; 3806 return; 3807 } 3808 3809 str = NET_AdrToStringwPort( cl_pinglist[n].adr ); 3810 Q_strncpyz( buf, str, buflen ); 3811 3812 time = cl_pinglist[n].time; 3813 if (!time) 3814 { 3815 // check for timeout 3816 time = Sys_Milliseconds() - cl_pinglist[n].start; 3817 maxPing = Cvar_VariableIntegerValue( "cl_maxPing" ); 3818 if( maxPing < 100 ) { 3819 maxPing = 100; 3820 } 3821 if (time < maxPing) 3822 { 3823 // not timed out yet 3824 time = 0; 3825 } 3826 } 3827 3828 CL_SetServerInfoByAddress(cl_pinglist[n].adr, cl_pinglist[n].info, cl_pinglist[n].time); 3829 3830 *pingtime = time; 3831} 3832 3833/* 3834================== 3835CL_UpdateServerInfo 3836================== 3837*/ 3838void CL_UpdateServerInfo( int n ) 3839{ 3840 if (!cl_pinglist[n].adr.port) 3841 { 3842 return; 3843 } 3844 3845 CL_SetServerInfoByAddress(cl_pinglist[n].adr, cl_pinglist[n].info, cl_pinglist[n].time ); 3846} 3847 3848/* 3849================== 3850CL_GetPingInfo 3851================== 3852*/ 3853void CL_GetPingInfo( int n, char *buf, int buflen ) 3854{ 3855 if (!cl_pinglist[n].adr.port) 3856 { 3857 // empty slot 3858 if (buflen) 3859 buf[0] = '\0'; 3860 return; 3861 } 3862 3863 Q_strncpyz( buf, cl_pinglist[n].info, buflen ); 3864} 3865 3866/* 3867================== 3868CL_ClearPing 3869================== 3870*/ 3871void CL_ClearPing( int n ) 3872{ 3873 if (n < 0 || n >= MAX_PINGREQUESTS) 3874 return; 3875 3876 cl_pinglist[n].adr.port = 0; 3877} 3878 3879/* 3880================== 3881CL_GetPingQueueCount 3882================== 3883*/ 3884int CL_GetPingQueueCount( void ) 3885{ 3886 int i; 3887 int count; 3888 ping_t* pingptr; 3889 3890 count = 0; 3891 pingptr = cl_pinglist; 3892 3893 for (i=0; i<MAX_PINGREQUESTS; i++, pingptr++ ) { 3894 if (pingptr->adr.port) { 3895 count++; 3896 } 3897 } 3898 3899 return (count); 3900} 3901 3902/* 3903================== 3904CL_GetFreePing 3905================== 3906*/ 3907ping_t* CL_GetFreePing( void ) 3908{ 3909 ping_t* pingptr; 3910 ping_t* best; 3911 int oldest; 3912 int i; 3913 int time; 3914 3915 pingptr = cl_pinglist; 3916 for (i=0; i<MAX_PINGREQUESTS; i++, pingptr++ ) 3917 { 3918 // find free ping slot 3919 if (pingptr->adr.port) 3920 { 3921 if (!pingptr->time) 3922 { 3923 if (Sys_Milliseconds() - pingptr->start < 500) 3924 { 3925 // still waiting for response 3926 continue; 3927 } 3928 } 3929 else if (pingptr->time < 500) 3930 { 3931 // results have not been queried 3932 continue; 3933 } 3934 } 3935 3936 // clear it 3937 pingptr->adr.port = 0; 3938 return (pingptr); 3939 } 3940 3941 // use oldest entry 3942 pingptr = cl_pinglist; 3943 best = cl_pinglist; 3944 oldest = INT_MIN; 3945 for (i=0; i<MAX_PINGREQUESTS; i++, pingptr++ ) 3946 { 3947 // scan for oldest 3948 time = Sys_Milliseconds() - pingptr->start; 3949 if (time > oldest) 3950 { 3951 oldest = time; 3952 best = pingptr; 3953 } 3954 } 3955 3956 return (best); 3957} 3958 3959/* 3960================== 3961CL_Ping_f 3962================== 3963*/ 3964void CL_Ping_f( void ) { 3965 netadr_t to; 3966 ping_t* pingptr; 3967 char* server; 3968 int argc; 3969 netadrtype_t family = NA_UNSPEC; 3970 3971 argc = Cmd_Argc(); 3972 3973 if ( argc != 2 && argc != 3 ) { 3974 Com_Printf( "usage: ping [-4|-6] server\n"); 3975 return; 3976 } 3977 3978 if(argc == 2) 3979 server = Cmd_Argv(1); 3980 else 3981 { 3982 if(!strcmp(Cmd_Argv(1), "-4")) 3983 family = NA_IP; 3984 else if(!strcmp(Cmd_Argv(1), "-6")) 3985 family = NA_IP6; 3986 else 3987 Com_Printf( "warning: only -4 or -6 as address type understood.\n"); 3988 3989 server = Cmd_Argv(2); 3990 } 3991 3992 Com_Memset( &to, 0, sizeof(netadr_t) ); 3993 3994 if ( !NET_StringToAdr( server, &to, family ) ) { 3995 return; 3996 } 3997 3998 pingptr = CL_GetFreePing(); 3999 4000 memcpy( &pingptr->adr, &to, sizeof (netadr_t) ); 4001 pingptr->start = Sys_Milliseconds(); 4002 pingptr->time = 0; 4003 4004 CL_SetServerInfoByAddress(pingptr->adr, NULL, 0); 4005 4006 NET_OutOfBandPrint( NS_CLIENT, to, "getinfo xxx" ); 4007} 4008 4009/* 4010================== 4011CL_UpdateVisiblePings_f 4012================== 4013*/ 4014qboolean CL_UpdateVisiblePings_f(int source) { 4015 int slots, i; 4016 char buff[MAX_STRING_CHARS]; 4017 int pingTime; 4018 int max; 4019 qboolean status = qfalse; 4020 4021 if (source < 0 || source > AS_FAVORITES) { 4022 return qfalse; 4023 } 4024 4025 cls.pingUpdateSource = source; 4026 4027 slots = CL_GetPingQueueCount(); 4028 if (slots < MAX_PINGREQUESTS) { 4029 serverInfo_t *server = NULL; 4030 4031 max = (source == AS_GLOBAL) ? MAX_GLOBAL_SERVERS : MAX_OTHER_SERVERS; 4032 switch (source) { 4033 case AS_LOCAL : 4034 server = &cls.localServers[0]; 4035 max = cls.numlocalservers; 4036 break; 4037 case AS_GLOBAL : 4038 server = &cls.globalServers[0]; 4039 max = cls.numglobalservers; 4040 break; 4041 case AS_FAVORITES : 4042 server = &cls.favoriteServers[0]; 4043 max = cls.numfavoriteservers; 4044 break; 4045 default: 4046 return qfalse; 4047 } 4048 for (i = 0; i < max; i++) { 4049 if (server[i].visible) { 4050 if (server[i].ping == -1) { 4051 int j; 4052 4053 if (slots >= MAX_PINGREQUESTS) { 4054 break; 4055 } 4056 for (j = 0; j < MAX_PINGREQUESTS; j++) { 4057 if (!cl_pinglist[j].adr.port) { 4058 continue; 4059 } 4060 if (NET_CompareAdr( cl_pinglist[j].adr, server[i].adr)) { 4061 // already on the list 4062 break; 4063 } 4064 } 4065 if (j >= MAX_PINGREQUESTS) { 4066 status = qtrue; 4067 for (j = 0; j < MAX_PINGREQUESTS; j++) { 4068 if (!cl_pinglist[j].adr.port) { 4069 break; 4070 } 4071 } 4072 memcpy(&cl_pinglist[j].adr, &server[i].adr, sizeof(netadr_t)); 4073 cl_pinglist[j].start = Sys_Milliseconds(); 4074 cl_pinglist[j].time = 0; 4075 NET_OutOfBandPrint( NS_CLIENT, cl_pinglist[j].adr, "getinfo xxx" ); 4076 slots++; 4077 } 4078 } 4079 // if the server has a ping higher than cl_maxPing or 4080 // the ping packet got lost 4081 else if (server[i].ping == 0) { 4082 // if we are updating global servers 4083 if (source == AS_GLOBAL) { 4084 // 4085 if ( cls.numGlobalServerAddresses > 0 ) { 4086 // overwrite this server with one from the additional global servers 4087 cls.numGlobalServerAddresses--; 4088 CL_InitServerInfo(&server[i], &cls.globalServerAddresses[cls.numGlobalServerAddresses]); 4089 // NOTE: the server[i].visible flag stays untouched 4090 } 4091 } 4092 } 4093 } 4094 } 4095 } 4096 4097 if (slots) { 4098 status = qtrue; 4099 } 4100 for (i = 0; i < MAX_PINGREQUESTS; i++) { 4101 if (!cl_pinglist[i].adr.port) { 4102 continue; 4103 } 4104 CL_GetPing( i, buff, MAX_STRING_CHARS, &pingTime ); 4105 if (pingTime != 0) { 4106 CL_ClearPing(i); 4107 status = qtrue; 4108 } 4109 } 4110 4111 return status; 4112} 4113 4114/* 4115================== 4116CL_ServerStatus_f 4117================== 4118*/ 4119void CL_ServerStatus_f(void) { 4120 netadr_t to, *toptr = NULL; 4121 char *server; 4122 serverStatus_t *serverStatus; 4123 int argc; 4124 netadrtype_t family = NA_UNSPEC; 4125 4126 argc = Cmd_Argc(); 4127 4128 if ( argc != 2 && argc != 3 ) 4129 { 4130 if (cls.state != CA_ACTIVE || clc.demoplaying) 4131 { 4132 Com_Printf ("Not connected to a server.\n"); 4133 Com_Printf( "usage: serverstatus [-4|-6] server\n"); 4134 return; 4135 } 4136 4137 toptr = &clc.serverAddress; 4138 } 4139 4140 if(!toptr) 4141 { 4142 Com_Memset( &to, 0, sizeof(netadr_t) ); 4143 4144 if(argc == 2) 4145 server = Cmd_Argv(1); 4146 else 4147 { 4148 if(!strcmp(Cmd_Argv(1), "-4")) 4149 family = NA_IP; 4150 else if(!strcmp(Cmd_Argv(1), "-6")) 4151 family = NA_IP6; 4152 else 4153 Com_Printf( "warning: only -4 or -6 as address type understood.\n"); 4154 4155 server = Cmd_Argv(2); 4156 } 4157 4158 toptr = &to; 4159 if ( !NET_StringToAdr( server, toptr, family ) ) 4160 return; 4161 } 4162 4163 NET_OutOfBandPrint( NS_CLIENT, *toptr, "getstatus" ); 4164 4165 serverStatus = CL_GetServerStatus( *toptr ); 4166 serverStatus->address = *toptr; 4167 serverStatus->print = qtrue; 4168 serverStatus->pending = qtrue; 4169} 4170 4171/* 4172================== 4173CL_ShowIP_f 4174================== 4175*/ 4176void CL_ShowIP_f(void) { 4177 Sys_ShowIP(); 4178} 4179 4180#ifndef STANDALONE 4181/* 4182================= 4183bool CL_CDKeyValidate 4184================= 4185*/ 4186qboolean CL_CDKeyValidate( const char *key, const char *checksum ) { 4187 return qtrue; 4188} 4189#endif 4190