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