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