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