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