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