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