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