1 /*
2 ===========================================================================
3
4 Return to Castle Wolfenstein multiplayer GPL Source Code
5 Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
6
7 This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (RTCW MP Source Code).
8
9 RTCW MP Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 RTCW MP Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with RTCW MP Source Code. If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below.
23
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25
26 ===========================================================================
27 */
28
29 // cl_main.c -- client main loop
30
31 #include "client.h"
32 #include <limits.h>
33
34 #ifdef __linux__
35 #include <sys/stat.h>
36 #endif
37
38 #include "../sys/sys_local.h"
39 #include "../sys/sys_loadlib.h"
40
41 #ifdef USE_MUMBLE
42 #include "libmumblelink.h"
43 #endif
44
45 #ifdef USE_MUMBLE
46 cvar_t *cl_useMumble;
47 cvar_t *cl_mumbleScale;
48 #endif
49
50 #ifdef USE_VOIP
51 cvar_t *cl_voipUseVAD;
52 cvar_t *cl_voipVADThreshold;
53 cvar_t *cl_voipSend;
54 cvar_t *cl_voipSendTarget;
55 cvar_t *cl_voipGainDuringCapture;
56 cvar_t *cl_voipCaptureMult;
57 cvar_t *cl_voipShowMeter;
58 cvar_t *cl_voipProtocol;
59 cvar_t *cl_voip;
60 #endif
61
62 #ifdef USE_RENDERER_DLOPEN
63 cvar_t *cl_renderer;
64 #endif
65
66 cvar_t *cl_wavefilerecord;
67 cvar_t *cl_nodelta;
68 cvar_t *cl_debugMove;
69
70 cvar_t *cl_noprint;
71 #ifdef UPDATE_SERVER_NAME
72 cvar_t *cl_motd;
73 #endif
74 cvar_t *cl_autoupdate; // DHM - Nerve
75
76 cvar_t *rcon_client_password;
77 cvar_t *rconAddress;
78
79 cvar_t *cl_timeout;
80 cvar_t *cl_maxpackets;
81 cvar_t *cl_packetdup;
82 cvar_t *cl_timeNudge;
83 cvar_t *cl_showTimeDelta;
84 cvar_t *cl_freezeDemo;
85
86 cvar_t *cl_showPing;
87
88 cvar_t *cl_shownet = NULL; // NERVE - SMF - This is referenced in msg.c and we need to make sure it is NULL
89 cvar_t *cl_shownuments; // DHM - Nerve
90 cvar_t *cl_visibleClients; // DHM - Nerve
91 cvar_t *cl_showSend;
92 cvar_t *cl_showServerCommands; // NERVE - SMF
93 cvar_t *cl_timedemo;
94 cvar_t *cl_timedemoLog;
95 cvar_t *cl_autoRecordDemo;
96 cvar_t *cl_aviFrameRate;
97 cvar_t *cl_aviMotionJpeg;
98 cvar_t *cl_avidemo;
99 cvar_t *cl_forceavidemo;
100
101 cvar_t *cl_freelook;
102 cvar_t *cl_sensitivity;
103
104 cvar_t *cl_mouseAccel;
105 cvar_t *cl_mouseAccelOffset;
106 cvar_t *cl_mouseAccelStyle;
107 cvar_t *cl_showMouseRate;
108
109 cvar_t *m_pitch;
110 cvar_t *m_yaw;
111 cvar_t *m_forward;
112 cvar_t *m_side;
113 cvar_t *m_filter;
114
115 cvar_t *j_pitch;
116 cvar_t *j_yaw;
117 cvar_t *j_forward;
118 cvar_t *j_side;
119 cvar_t *j_up;
120 cvar_t *j_pitch_axis;
121 cvar_t *j_yaw_axis;
122 cvar_t *j_forward_axis;
123 cvar_t *j_side_axis;
124 cvar_t *j_up_axis;
125
126 cvar_t *cl_activeAction;
127
128 cvar_t *cl_motdString;
129
130 cvar_t *cl_allowDownload;
131 cvar_t *cl_conXOffset;
132 cvar_t *cl_inGameVideo;
133
134 cvar_t *cl_serverStatusResendTime;
135 cvar_t *cl_missionStats;
136 cvar_t *cl_waitForFire;
137
138 // NERVE - SMF - localization
139 cvar_t *cl_language;
140 cvar_t *cl_debugTranslation;
141 // -NERVE - SMF
142 // DHM - Nerve :: Auto-Update
143 cvar_t *cl_updateavailable;
144 cvar_t *cl_updatefiles;
145 // DHM - Nerve
146
147 cvar_t *cl_lanForcePackets;
148
149 cvar_t *cl_guid;
150 cvar_t *cl_guidServerUniq;
151
152 cvar_t *cl_consoleKeys;
153
154 cvar_t *cl_rate;
155
156 clientActive_t cl;
157 clientConnection_t clc;
158 clientStatic_t cls;
159 vm_t *cgvm;
160
161 char cl_reconnectArgs[MAX_OSPATH];
162 char cl_oldGame[MAX_QPATH];
163 qboolean cl_oldGameSet;
164
165 // Structure containing functions exported from refresh DLL
166 refexport_t re;
167 #ifdef USE_RENDERER_DLOPEN
168 static void *rendererLib = NULL;
169 #endif
170
171 ping_t cl_pinglist[MAX_PINGREQUESTS];
172
173 typedef struct serverStatus_s
174 {
175 char string[BIG_INFO_STRING];
176 netadr_t address;
177 int time, startTime;
178 qboolean pending;
179 qboolean print;
180 qboolean retrieved;
181 } serverStatus_t;
182
183 serverStatus_t cl_serverStatusList[MAX_SERVERSTATUSREQUESTS];
184
185 // DHM - Nerve :: Have we heard from the auto-update server this session?
186 qboolean autoupdateChecked;
187 qboolean autoupdateStarted;
188 // TTimo : moved from char* to array (was getting the char* from va(), broke on big downloads)
189 char autoupdateFilename[MAX_QPATH];
190 // "updates" shifted from -7
191 #define AUTOUPDATE_DIR "ni]Zm^l"
192 #define AUTOUPDATE_DIR_SHIFT 7
193
194 static int noGameRestart = qfalse;
195
196 extern void SV_BotFrame( int time );
197 void CL_CheckForResend( void );
198 void CL_ShowIP_f( void );
199 void CL_ServerStatus_f( void );
200 void CL_ServerStatusResponse( netadr_t from, msg_t *msg );
201 void CL_SaveTranslations_f( void );
202 void CL_LoadTranslations_f( void );
203
204 /*
205 ===============
206 CL_CDDialog
207
208 Called by Com_Error when a cd is needed
209 ===============
210 */
CL_CDDialog(void)211 void CL_CDDialog( void ) {
212 cls.cddialog = qtrue; // start it next frame
213 }
214
215 #ifdef USE_MUMBLE
216 static
CL_UpdateMumble(void)217 void CL_UpdateMumble(void)
218 {
219 vec3_t pos, forward, up;
220 float scale = cl_mumbleScale->value;
221 float tmp;
222
223 if(!cl_useMumble->integer)
224 return;
225
226 // !!! FIXME: not sure if this is even close to correct.
227 AngleVectors( cl.snap.ps.viewangles, forward, NULL, up);
228
229 pos[0] = cl.snap.ps.origin[0] * scale;
230 pos[1] = cl.snap.ps.origin[2] * scale;
231 pos[2] = cl.snap.ps.origin[1] * scale;
232
233 tmp = forward[1];
234 forward[1] = forward[2];
235 forward[2] = tmp;
236
237 tmp = up[1];
238 up[1] = up[2];
239 up[2] = tmp;
240
241 if(cl_useMumble->integer > 1) {
242 fprintf(stderr, "%f %f %f, %f %f %f, %f %f %f\n",
243 pos[0], pos[1], pos[2],
244 forward[0], forward[1], forward[2],
245 up[0], up[1], up[2]);
246 }
247
248 mumble_update_coordinates(pos, forward, up);
249 }
250 #endif
251
252
253 #ifdef USE_VOIP
254 static
CL_UpdateVoipIgnore(const char * idstr,qboolean ignore)255 void CL_UpdateVoipIgnore(const char *idstr, qboolean ignore)
256 {
257 if ((*idstr >= '0') && (*idstr <= '9')) {
258 const int id = atoi(idstr);
259 if ((id >= 0) && (id < MAX_CLIENTS)) {
260 clc.voipIgnore[id] = ignore;
261 CL_AddReliableCommand(va("voip %s %d",
262 ignore ? "ignore" : "unignore", id), qfalse);
263 Com_Printf("VoIP: %s ignoring player #%d\n",
264 ignore ? "Now" : "No longer", id);
265 return;
266 }
267 }
268 Com_Printf("VoIP: invalid player ID#\n");
269 }
270
271 static
CL_UpdateVoipGain(const char * idstr,float gain)272 void CL_UpdateVoipGain(const char *idstr, float gain)
273 {
274 if ((*idstr >= '0') && (*idstr <= '9')) {
275 const int id = atoi(idstr);
276 if (gain < 0.0f)
277 gain = 0.0f;
278 if ((id >= 0) && (id < MAX_CLIENTS)) {
279 clc.voipGain[id] = gain;
280 Com_Printf("VoIP: player #%d gain now set to %f\n", id, gain);
281 }
282 }
283 }
284
CL_Voip_f(void)285 void CL_Voip_f( void )
286 {
287 const char *cmd = Cmd_Argv(1);
288 const char *reason = NULL;
289
290 if (clc.state != CA_ACTIVE)
291 reason = "Not connected to a server";
292 else if (!clc.voipCodecInitialized)
293 reason = "Voip codec not initialized";
294 else if (!clc.voipEnabled)
295 reason = "Server doesn't support VoIP";
296 else if (!clc.demoplaying && (Cvar_VariableValue("g_gametype") == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")))
297 reason = "running in single-player mode";
298
299 if (reason != NULL) {
300 Com_Printf("VoIP: command ignored: %s\n", reason);
301 return;
302 }
303
304 if (strcmp(cmd, "ignore") == 0) {
305 CL_UpdateVoipIgnore(Cmd_Argv(2), qtrue);
306 } else if (strcmp(cmd, "unignore") == 0) {
307 CL_UpdateVoipIgnore(Cmd_Argv(2), qfalse);
308 } else if (strcmp(cmd, "gain") == 0) {
309 if (Cmd_Argc() > 3) {
310 CL_UpdateVoipGain(Cmd_Argv(2), atof(Cmd_Argv(3)));
311 } else if (Q_isanumber(Cmd_Argv(2))) {
312 int id = atoi(Cmd_Argv(2));
313 if (id >= 0 && id < MAX_CLIENTS) {
314 Com_Printf("VoIP: current gain for player #%d "
315 "is %f\n", id, clc.voipGain[id]);
316 } else {
317 Com_Printf("VoIP: invalid player ID#\n");
318 }
319 } else {
320 Com_Printf("usage: voip gain <playerID#> [value]\n");
321 }
322 } else if (strcmp(cmd, "muteall") == 0) {
323 Com_Printf("VoIP: muting incoming voice\n");
324 CL_AddReliableCommand("voip muteall", qfalse);
325 clc.voipMuteAll = qtrue;
326 } else if (strcmp(cmd, "unmuteall") == 0) {
327 Com_Printf("VoIP: unmuting incoming voice\n");
328 CL_AddReliableCommand("voip unmuteall", qfalse);
329 clc.voipMuteAll = qfalse;
330 } else {
331 Com_Printf("usage: voip [un]ignore <playerID#>\n"
332 " voip [un]muteall\n"
333 " voip gain <playerID#> [value]\n");
334 }
335 }
336
337
338 static
CL_VoipNewGeneration(void)339 void CL_VoipNewGeneration(void)
340 {
341 // don't have a zero generation so new clients won't match, and don't
342 // wrap to negative so MSG_ReadLong() doesn't "fail."
343 clc.voipOutgoingGeneration++;
344 if (clc.voipOutgoingGeneration <= 0)
345 clc.voipOutgoingGeneration = 1;
346 clc.voipPower = 0.0f;
347 clc.voipOutgoingSequence = 0;
348
349 opus_encoder_ctl(clc.opusEncoder, OPUS_RESET_STATE);
350 }
351
352 /*
353 ===============
354 CL_VoipParseTargets
355
356 sets clc.voipTargets according to cl_voipSendTarget
357 Generally we don't want who's listening to change during a transmission,
358 so this is only called when the key is first pressed
359 ===============
360 */
CL_VoipParseTargets(void)361 void CL_VoipParseTargets(void)
362 {
363 const char *target = cl_voipSendTarget->string;
364 char *end;
365 int val;
366
367 Com_Memset(clc.voipTargets, 0, sizeof(clc.voipTargets));
368 clc.voipFlags &= ~VOIP_SPATIAL;
369
370 while(target)
371 {
372 while(*target == ',' || *target == ' ')
373 target++;
374
375 if(!*target)
376 break;
377
378 if(isdigit(*target))
379 {
380 val = strtol(target, &end, 10);
381 target = end;
382 }
383 else
384 {
385 if(!Q_stricmpn(target, "all", 3))
386 {
387 Com_Memset(clc.voipTargets, ~0, sizeof(clc.voipTargets));
388 return;
389 }
390 if(!Q_stricmpn(target, "spatial", 7))
391 {
392 clc.voipFlags |= VOIP_SPATIAL;
393 target += 7;
394 continue;
395 }
396 else
397 {
398 if(!Q_stricmpn(target, "attacker", 8))
399 {
400 val = VM_Call(cgvm, CG_LAST_ATTACKER);
401 target += 8;
402 }
403 else if(!Q_stricmpn(target, "crosshair", 9))
404 {
405 val = VM_Call(cgvm, CG_CROSSHAIR_PLAYER);
406 target += 9;
407 }
408 else
409 {
410 while(*target && *target != ',' && *target != ' ')
411 target++;
412
413 continue;
414 }
415
416 if(val < 0)
417 continue;
418 }
419 }
420
421 if(val < 0 || val >= MAX_CLIENTS)
422 {
423 Com_Printf(S_COLOR_YELLOW "WARNING: VoIP "
424 "target %d is not a valid client "
425 "number\n", val);
426
427 continue;
428 }
429
430 clc.voipTargets[val / 8] |= 1 << (val % 8);
431 }
432 }
433
434 /*
435 ===============
436 CL_CaptureVoip
437
438 Record more audio from the hardware if required and encode it into Opus
439 data for later transmission.
440 ===============
441 */
442 static
CL_CaptureVoip(void)443 void CL_CaptureVoip(void)
444 {
445 const float audioMult = cl_voipCaptureMult->value;
446 const qboolean useVad = (cl_voipUseVAD->integer != 0);
447 qboolean initialFrame = qfalse;
448 qboolean finalFrame = qfalse;
449
450 #if USE_MUMBLE
451 // if we're using Mumble, don't try to handle VoIP transmission ourselves.
452 if (cl_useMumble->integer)
453 return;
454 #endif
455
456 // If your data rate is too low, you'll get Connection Interrupted warnings
457 // when VoIP packets arrive, even if you have a broadband connection.
458 // This might work on rates lower than 25000, but for safety's sake, we'll
459 // just demand it. Who doesn't have at least a DSL line now, anyhow? If
460 // you don't, you don't need VoIP. :)
461 if (cl_voip->modified || cl_rate->modified) {
462 if ((cl_voip->integer) && (cl_rate->integer < 25000)) {
463 Com_Printf(S_COLOR_YELLOW "Your network rate is too slow for VoIP.\n");
464 Com_Printf("Set 'Data Rate' to 'LAN/Cable/xDSL' in 'Setup/System/Network'.\n");
465 Com_Printf("Until then, VoIP is disabled.\n");
466 Cvar_Set("cl_voip", "0");
467 }
468 Cvar_Set("cl_voipProtocol", cl_voip->integer ? "opus" : "");
469 cl_voip->modified = qfalse;
470 cl_rate->modified = qfalse;
471 }
472
473 if (!clc.voipCodecInitialized)
474 return; // just in case this gets called at a bad time.
475
476 if (clc.voipOutgoingDataSize > 0)
477 return; // packet is pending transmission, don't record more yet.
478
479 if (cl_voipUseVAD->modified) {
480 Cvar_Set("cl_voipSend", (useVad) ? "1" : "0");
481 cl_voipUseVAD->modified = qfalse;
482 }
483
484 if ((useVad) && (!cl_voipSend->integer))
485 Cvar_Set("cl_voipSend", "1"); // lots of things reset this.
486
487 if (cl_voipSend->modified) {
488 qboolean dontCapture = qfalse;
489 if (clc.state != CA_ACTIVE)
490 dontCapture = qtrue; // not connected to a server.
491 else if (!clc.voipEnabled)
492 dontCapture = qtrue; // server doesn't support VoIP.
493 else if (clc.demoplaying)
494 dontCapture = qtrue; // playing back a demo.
495 else if ( cl_voip->integer == 0 )
496 dontCapture = qtrue; // client has VoIP support disabled.
497 else if ( audioMult == 0.0f )
498 dontCapture = qtrue; // basically silenced incoming audio.
499
500 cl_voipSend->modified = qfalse;
501
502 if(dontCapture)
503 {
504 Cvar_Set("cl_voipSend", "0");
505 return;
506 }
507
508 if (cl_voipSend->integer) {
509 initialFrame = qtrue;
510 } else {
511 finalFrame = qtrue;
512 }
513 }
514
515 // try to get more audio data from the sound card...
516
517 if (initialFrame) {
518 S_MasterGain(Com_Clamp(0.0f, 1.0f, cl_voipGainDuringCapture->value));
519 S_StartCapture();
520 CL_VoipNewGeneration();
521 CL_VoipParseTargets();
522 }
523
524 if ((cl_voipSend->integer) || (finalFrame)) { // user wants to capture audio?
525 int samples = S_AvailableCaptureSamples();
526 const int packetSamples = (finalFrame) ? VOIP_MAX_FRAME_SAMPLES : VOIP_MAX_PACKET_SAMPLES;
527
528 // enough data buffered in audio hardware to process yet?
529 if (samples >= packetSamples) {
530 // audio capture is always MONO16.
531 static int16_t sampbuffer[VOIP_MAX_PACKET_SAMPLES];
532 float voipPower = 0.0f;
533 int voipFrames;
534 int i, bytes;
535
536 if (samples > VOIP_MAX_PACKET_SAMPLES)
537 samples = VOIP_MAX_PACKET_SAMPLES;
538
539 // !!! FIXME: maybe separate recording from encoding, so voipPower
540 // !!! FIXME: updates faster than 4Hz?
541
542 samples -= samples % VOIP_MAX_FRAME_SAMPLES;
543 if (samples != 120 && samples != 240 && samples != 480 && samples != 960 && samples != 1920 && samples != 2880 ) {
544 Com_Printf("Voip: bad number of samples %d\n", samples);
545 return;
546 }
547 voipFrames = samples / VOIP_MAX_FRAME_SAMPLES;
548
549 S_Capture(samples, (byte *) sampbuffer); // grab from audio card.
550
551 // check the "power" of this packet...
552 for (i = 0; i < samples; i++) {
553 const float flsamp = (float) sampbuffer[i];
554 const float s = fabs(flsamp);
555 voipPower += s * s;
556 sampbuffer[i] = (int16_t) ((flsamp) * audioMult);
557 }
558
559 // encode raw audio samples into Opus data...
560 bytes = opus_encode(clc.opusEncoder, sampbuffer, samples,
561 (unsigned char *) clc.voipOutgoingData,
562 sizeof (clc.voipOutgoingData));
563 if ( bytes <= 0 ) {
564 Com_DPrintf("VoIP: Error encoding %d samples\n", samples);
565 bytes = 0;
566 }
567
568 clc.voipPower = (voipPower / (32768.0f * 32768.0f *
569 ((float) samples))) * 100.0f;
570
571 if ((useVad) && (clc.voipPower < cl_voipVADThreshold->value)) {
572 CL_VoipNewGeneration(); // no "talk" for at least 1/4 second.
573 } else {
574 clc.voipOutgoingDataSize = bytes;
575 clc.voipOutgoingDataFrames = voipFrames;
576
577 Com_DPrintf("VoIP: Send %d frames, %d bytes, %f power\n",
578 voipFrames, bytes, clc.voipPower);
579
580 #if 0
581 static FILE *encio = NULL;
582 if (encio == NULL) encio = fopen("voip-outgoing-encoded.bin", "wb");
583 if (encio != NULL) { fwrite(clc.voipOutgoingData, bytes, 1, encio); fflush(encio); }
584 static FILE *decio = NULL;
585 if (decio == NULL) decio = fopen("voip-outgoing-decoded.bin", "wb");
586 if (decio != NULL) { fwrite(sampbuffer, voipFrames * VOIP_MAX_FRAME_SAMPLES * 2, 1, decio); fflush(decio); }
587 #endif
588 }
589 }
590 }
591
592 // User requested we stop recording, and we've now processed the last of
593 // any previously-buffered data. Pause the capture device, etc.
594 if (finalFrame) {
595 S_StopCapture();
596 S_MasterGain(1.0f);
597 clc.voipPower = 0.0f; // force this value so it doesn't linger.
598 }
599 }
600 #endif
601
602 /*
603 =======================================================================
604
605 CLIENT RELIABLE COMMAND COMMUNICATION
606
607 =======================================================================
608 */
609
610 /*
611 ======================
612 CL_AddReliableCommand
613
614 The given command will be transmitted to the server, and is gauranteed to
615 not have future usercmd_t executed before it is executed
616 ======================
617 */
CL_AddReliableCommand(const char * cmd,qboolean isDisconnectCmd)618 void CL_AddReliableCommand(const char *cmd, qboolean isDisconnectCmd)
619 {
620 int unacknowledged = clc.reliableSequence - clc.reliableAcknowledge;
621
622 // if we would be losing an old command that hasn't been acknowledged,
623 // we must drop the connection
624 // also leave one slot open for the disconnect command in this case.
625
626 if ((isDisconnectCmd && unacknowledged > MAX_RELIABLE_COMMANDS) ||
627 (!isDisconnectCmd && unacknowledged >= MAX_RELIABLE_COMMANDS))
628 {
629 if(com_errorEntered)
630 return;
631 else
632 Com_Error(ERR_DROP, "Client command overflow");
633 }
634
635 Q_strncpyz(clc.reliableCommands[++clc.reliableSequence & (MAX_RELIABLE_COMMANDS - 1)],
636 cmd, sizeof(*clc.reliableCommands));
637 }
638
639 /*
640 =======================================================================
641
642 CLIENT SIDE DEMO RECORDING
643
644 =======================================================================
645 */
646
647 /*
648 ====================
649 CL_WriteDemoMessage
650
651 Dumps the current net message, prefixed by the length
652 ====================
653 */
CL_WriteDemoMessage(msg_t * msg,int headerBytes)654 void CL_WriteDemoMessage( msg_t *msg, int headerBytes ) {
655 int len, swlen;
656
657 // write the packet sequence
658 len = clc.serverMessageSequence;
659 swlen = LittleLong( len );
660 FS_Write( &swlen, 4, clc.demofile );
661
662 // skip the packet sequencing information
663 len = msg->cursize - headerBytes;
664 swlen = LittleLong( len );
665 FS_Write( &swlen, 4, clc.demofile );
666 FS_Write( msg->data + headerBytes, len, clc.demofile );
667 }
668
669
670 /*
671 ====================
672 CL_StopRecording_f
673
674 stop recording a demo
675 ====================
676 */
CL_StopRecord_f(void)677 void CL_StopRecord_f( void ) {
678 int len;
679
680 if ( !clc.demorecording ) {
681 Com_Printf( "Not recording a demo.\n" );
682 return;
683 }
684
685 // finish up
686 len = -1;
687 FS_Write( &len, 4, clc.demofile );
688 FS_Write( &len, 4, clc.demofile );
689 FS_FCloseFile( clc.demofile );
690 clc.demofile = 0;
691 clc.demorecording = qfalse;
692 Com_Printf( "Stopped demo.\n" );
693 }
694
695 /*
696 ==================
697 CL_DemoFilename
698 ==================
699 */
CL_DemoFilename(int number,char * fileName,int fileNameSize)700 void CL_DemoFilename( int number, char *fileName, int fileNameSize ) {
701 int a,b,c,d;
702
703 if(number < 0 || number > 9999)
704 number = 9999;
705
706 a = number / 1000;
707 number -= a * 1000;
708 b = number / 100;
709 number -= b * 100;
710 c = number / 10;
711 number -= c * 10;
712 d = number;
713
714 Com_sprintf( fileName, fileNameSize, "demo%i%i%i%i"
715 , a, b, c, d );
716 }
717
718 /*
719 ====================
720 CL_Record_f
721
722 record <demoname>
723
724 Begins recording a demo from the current position
725 ====================
726 */
727 static char demoName[MAX_QPATH]; // compiler bug workaround
CL_Record_f(void)728 void CL_Record_f( void ) {
729 char name[MAX_OSPATH];
730 byte bufData[MAX_MSGLEN];
731 msg_t buf;
732 int i;
733 int len;
734 entityState_t *ent;
735 entityState_t nullstate;
736 char *s;
737
738 if ( Cmd_Argc() > 2 ) {
739 Com_Printf( "record <demoname>\n" );
740 return;
741 }
742
743 if ( clc.demorecording ) {
744 Com_Printf( "Already recording.\n" );
745 return;
746 }
747
748 if ( clc.state != CA_ACTIVE ) {
749 Com_Printf( "You must be in a level to record.\n" );
750 return;
751 }
752
753 // sync 0 doesn't prevent recording, so not forcing it off .. everyone does g_sync 1 ; record ; g_sync 0 ..
754 if ( NET_IsLocalAddress( clc.serverAddress ) && !Cvar_VariableValue( "g_synchronousClients" ) ) {
755 Com_Printf (S_COLOR_YELLOW "WARNING: You should set 'g_synchronousClients 1' for smoother demo recording\n");
756 }
757
758 if ( Cmd_Argc() == 2 ) {
759 s = Cmd_Argv( 1 );
760 Q_strncpyz( demoName, s, sizeof( demoName ) );
761 #ifdef LEGACY_PROTOCOL
762 if(clc.compat)
763 Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_legacyprotocol->integer);
764 else
765 #endif
766 Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_protocol->integer);
767 } else {
768 int number;
769
770 // scan for a free demo name
771 for ( number = 0 ; number <= 9999 ; number++ ) {
772 CL_DemoFilename( number, demoName, sizeof( demoName ) );
773 #ifdef LEGACY_PROTOCOL
774 if(clc.compat)
775 Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_legacyprotocol->integer);
776 else
777 #endif
778 Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_protocol->integer);
779
780 if (!FS_FileExists(name))
781 break; // file doesn't exist
782 }
783 }
784
785 // open the demo file
786 Com_Printf( "recording to %s.\n", name );
787 clc.demofile = FS_FOpenFileWrite( name );
788 if ( !clc.demofile ) {
789 Com_Printf( "ERROR: couldn't open.\n" );
790 return;
791 }
792 clc.demorecording = qtrue;
793 Q_strncpyz( clc.demoName, demoName, sizeof( clc.demoName ) );
794
795 // don't start saving messages until a non-delta compressed message is received
796 clc.demowaiting = qtrue;
797
798 // write out the gamestate message
799 MSG_Init( &buf, bufData, sizeof( bufData ) );
800 MSG_Bitstream( &buf );
801
802 // NOTE, MRE: all server->client messages now acknowledge
803 MSG_WriteLong( &buf, clc.reliableSequence );
804
805 MSG_WriteByte( &buf, svc_gamestate );
806 MSG_WriteLong( &buf, clc.serverCommandSequence );
807
808 // configstrings
809 for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) {
810 if ( !cl.gameState.stringOffsets[i] ) {
811 continue;
812 }
813 s = cl.gameState.stringData + cl.gameState.stringOffsets[i];
814 MSG_WriteByte( &buf, svc_configstring );
815 MSG_WriteShort( &buf, i );
816 MSG_WriteBigString( &buf, s );
817 }
818
819 // baselines
820 Com_Memset( &nullstate, 0, sizeof( nullstate ) );
821 for ( i = 0; i < MAX_GENTITIES ; i++ ) {
822 ent = &cl.entityBaselines[i];
823 if ( !ent->number ) {
824 continue;
825 }
826 MSG_WriteByte( &buf, svc_baseline );
827 MSG_WriteDeltaEntity( &buf, &nullstate, ent, qtrue );
828 }
829
830 MSG_WriteByte( &buf, svc_EOF );
831
832 // finished writing the gamestate stuff
833
834 // write the client num
835 MSG_WriteLong( &buf, clc.clientNum );
836 // write the checksum feed
837 MSG_WriteLong( &buf, clc.checksumFeed );
838
839 // finished writing the client packet
840 MSG_WriteByte( &buf, svc_EOF );
841
842 // write it to the demo file
843 len = LittleLong( clc.serverMessageSequence - 1 );
844 FS_Write( &len, 4, clc.demofile );
845
846 len = LittleLong( buf.cursize );
847 FS_Write( &len, 4, clc.demofile );
848 FS_Write( buf.data, buf.cursize, clc.demofile );
849
850 // the rest of the demo file will be copied from net messages
851 }
852
853 /*
854 =======================================================================
855
856 CLIENT SIDE DEMO PLAYBACK
857
858 =======================================================================
859 */
860
861 /*
862 =================
863 CL_DemoFrameDurationSDev
864 =================
865 */
CL_DemoFrameDurationSDev(void)866 static float CL_DemoFrameDurationSDev( void )
867 {
868 int i;
869 int numFrames;
870 float mean = 0.0f;
871 float variance = 0.0f;
872
873 if( ( clc.timeDemoFrames - 1 ) > MAX_TIMEDEMO_DURATIONS )
874 numFrames = MAX_TIMEDEMO_DURATIONS;
875 else
876 numFrames = clc.timeDemoFrames - 1;
877
878 for( i = 0; i < numFrames; i++ )
879 mean += clc.timeDemoDurations[ i ];
880 mean /= numFrames;
881
882 for( i = 0; i < numFrames; i++ )
883 {
884 float x = clc.timeDemoDurations[ i ];
885
886 variance += ( ( x - mean ) * ( x - mean ) );
887 }
888 variance /= numFrames;
889
890 return sqrt( variance );
891 }
892
893 /*
894 =================
895 CL_DemoCompleted
896 =================
897 */
CL_DemoCompleted(void)898 void CL_DemoCompleted( void )
899 {
900 char buffer[ MAX_STRING_CHARS ];
901
902 if( cl_timedemo && cl_timedemo->integer )
903 {
904 int time;
905
906 time = Sys_Milliseconds() - clc.timeDemoStart;
907 if( time > 0 )
908 {
909 // Millisecond times are frame durations:
910 // minimum/average/maximum/std deviation
911 Com_sprintf( buffer, sizeof( buffer ),
912 "%i frames %3.1f seconds %3.1f fps %d.0/%.1f/%d.0/%.1f ms\n",
913 clc.timeDemoFrames,
914 time/1000.0,
915 clc.timeDemoFrames*1000.0 / time,
916 clc.timeDemoMinDuration,
917 time / (float)clc.timeDemoFrames,
918 clc.timeDemoMaxDuration,
919 CL_DemoFrameDurationSDev( ) );
920 Com_Printf( "%s", buffer );
921
922 // Write a log of all the frame durations
923 if( cl_timedemoLog && strlen( cl_timedemoLog->string ) > 0 )
924 {
925 int i;
926 int numFrames;
927 fileHandle_t f;
928
929 if( ( clc.timeDemoFrames - 1 ) > MAX_TIMEDEMO_DURATIONS )
930 numFrames = MAX_TIMEDEMO_DURATIONS;
931 else
932 numFrames = clc.timeDemoFrames - 1;
933
934 f = FS_FOpenFileWrite( cl_timedemoLog->string );
935 if( f )
936 {
937 FS_Printf( f, "# %s", buffer );
938
939 for( i = 0; i < numFrames; i++ )
940 FS_Printf( f, "%d\n", clc.timeDemoDurations[ i ] );
941
942 FS_FCloseFile( f );
943 Com_Printf( "%s written\n", cl_timedemoLog->string );
944 }
945 else
946 {
947 Com_Printf( "Couldn't open %s for writing\n",
948 cl_timedemoLog->string );
949 }
950 }
951 }
952 }
953
954 CL_Disconnect( qtrue );
955 CL_NextDemo();
956 }
957
958 /*
959 =================
960 CL_ReadDemoMessage
961 =================
962 */
CL_ReadDemoMessage(void)963 void CL_ReadDemoMessage( void ) {
964 int r;
965 msg_t buf;
966 byte bufData[ MAX_MSGLEN ];
967 int s;
968
969 if ( !clc.demofile ) {
970 CL_DemoCompleted();
971 return;
972 }
973
974 // get the sequence number
975 r = FS_Read( &s, 4, clc.demofile );
976 if ( r != 4 ) {
977 CL_DemoCompleted();
978 return;
979 }
980 clc.serverMessageSequence = LittleLong( s );
981
982 // init the message
983 MSG_Init( &buf, bufData, sizeof( bufData ) );
984
985 // get the length
986 r = FS_Read( &buf.cursize, 4, clc.demofile );
987 if ( r != 4 ) {
988 CL_DemoCompleted();
989 return;
990 }
991 buf.cursize = LittleLong( buf.cursize );
992 if ( buf.cursize == -1 ) {
993 CL_DemoCompleted();
994 return;
995 }
996 if ( buf.cursize > buf.maxsize ) {
997 Com_Error( ERR_DROP, "CL_ReadDemoMessage: demoMsglen > MAX_MSGLEN" );
998 }
999 r = FS_Read( buf.data, buf.cursize, clc.demofile );
1000 if ( r != buf.cursize ) {
1001 Com_Printf( "Demo file was truncated.\n" );
1002 CL_DemoCompleted();
1003 return;
1004 }
1005
1006 clc.lastPacketTime = cls.realtime;
1007 buf.readcount = 0;
1008 CL_ParseServerMessage( &buf );
1009 }
1010
1011 /*
1012 ====================
1013 CL_WalkDemoExt
1014 ====================
1015 */
CL_WalkDemoExt(char * arg,char * name,int * demofile)1016 static int CL_WalkDemoExt(char *arg, char *name, int *demofile)
1017 {
1018 int i = 0;
1019 *demofile = 0;
1020
1021 #ifdef LEGACY_PROTOCOL
1022 if(com_legacyprotocol->integer > 0)
1023 {
1024 Com_sprintf(name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, com_legacyprotocol->integer);
1025 FS_FOpenFileRead(name, demofile, qtrue);
1026
1027 if (*demofile)
1028 {
1029 Com_Printf("Demo file: %s\n", name);
1030 return com_legacyprotocol->integer;
1031 }
1032 }
1033
1034 if(com_protocol->integer != com_legacyprotocol->integer)
1035 #endif
1036 {
1037 Com_sprintf(name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, com_protocol->integer);
1038 FS_FOpenFileRead(name, demofile, qtrue);
1039
1040 if (*demofile)
1041 {
1042 Com_Printf("Demo file: %s\n", name);
1043 return com_protocol->integer;
1044 }
1045 }
1046
1047 Com_Printf("Not found: %s\n", name);
1048
1049 while(demo_protocols[i])
1050 {
1051 #ifdef LEGACY_PROTOCOL
1052 if(demo_protocols[i] == com_legacyprotocol->integer)
1053 continue;
1054 #endif
1055 if(demo_protocols[i] == com_protocol->integer)
1056 continue;
1057
1058 Com_sprintf (name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, demo_protocols[i]);
1059 FS_FOpenFileRead( name, demofile, qtrue );
1060 if (*demofile)
1061 {
1062 Com_Printf("Demo file: %s\n", name);
1063
1064 return demo_protocols[i];
1065 }
1066 else
1067 Com_Printf("Not found: %s\n", name);
1068 i++;
1069 }
1070
1071 return -1;
1072 }
1073
1074 /*
1075 ====================
1076 CL_CompleteDemoName
1077 ====================
1078 */
CL_CompleteDemoName(char * args,int argNum)1079 static void CL_CompleteDemoName( char *args, int argNum )
1080 {
1081 if( argNum == 2 )
1082 {
1083 char demoExt[ 16 ];
1084
1085 Com_sprintf(demoExt, sizeof(demoExt), ".%s%d", DEMOEXT, com_protocol->integer);
1086 Field_CompleteFilename( "demos", demoExt, qtrue, qtrue );
1087 }
1088 }
1089
1090 /*
1091 ====================
1092 CL_PlayDemo_f
1093
1094 demo <demoname>
1095
1096 ====================
1097 */
CL_PlayDemo_f(void)1098 void CL_PlayDemo_f( void ) {
1099 char name[MAX_OSPATH];
1100 char arg[MAX_OSPATH];
1101 char *ext_test;
1102 int protocol, i;
1103 char retry[MAX_OSPATH];
1104
1105 if (Cmd_Argc() != 2) {
1106 Com_Printf ("demo <demoname>\n");
1107 return;
1108 }
1109
1110 // make sure a local server is killed
1111 // 2 means don't force disconnect of local client
1112 Cvar_Set( "sv_killserver", "2" );
1113
1114 // open the demo file
1115 Q_strncpyz( arg, Cmd_Argv(1), sizeof( arg ) );
1116
1117 CL_Disconnect( qtrue );
1118
1119 // check for an extension .DEMOEXT_?? (?? is protocol)
1120 ext_test = strrchr(arg, '.');
1121
1122 if(ext_test && !Q_stricmpn(ext_test + 1, DEMOEXT, ARRAY_LEN(DEMOEXT) - 1))
1123 {
1124 protocol = atoi(ext_test + ARRAY_LEN(DEMOEXT));
1125
1126 for(i = 0; demo_protocols[i]; i++)
1127 {
1128 if(demo_protocols[i] == protocol)
1129 break;
1130 }
1131
1132 if(demo_protocols[i] || protocol == com_protocol->integer
1133 #ifdef LEGACY_PROTOCOL
1134 || protocol == com_legacyprotocol->integer
1135 #endif
1136 )
1137 {
1138 Com_sprintf(name, sizeof(name), "demos/%s", arg);
1139 FS_FOpenFileRead(name, &clc.demofile, qtrue);
1140 }
1141 else
1142 {
1143 int len;
1144
1145 Com_Printf("Protocol %d not supported for demos\n", protocol);
1146 len = ext_test - arg;
1147
1148 if(len >= ARRAY_LEN(retry))
1149 len = ARRAY_LEN(retry) - 1;
1150
1151 Q_strncpyz(retry, arg, len + 1);
1152 retry[len] = '\0';
1153 protocol = CL_WalkDemoExt(retry, name, &clc.demofile);
1154 }
1155 }
1156 else
1157 protocol = CL_WalkDemoExt(arg, name, &clc.demofile);
1158
1159 if (!clc.demofile) {
1160 Com_Error( ERR_DROP, "couldn't open %s", name);
1161 return;
1162 }
1163 Q_strncpyz( clc.demoName, arg, sizeof( clc.demoName ) );
1164
1165 Con_Close();
1166
1167 clc.state = CA_CONNECTED;
1168 clc.demoplaying = qtrue;
1169 Q_strncpyz( clc.servername, arg, sizeof( clc.servername ) );
1170
1171 #ifdef LEGACY_PROTOCOL
1172 if(protocol <= com_legacyprotocol->integer)
1173 clc.compat = qtrue;
1174 else
1175 clc.compat = qfalse;
1176 #endif
1177
1178 // read demo messages until connected
1179 while ( clc.state >= CA_CONNECTED && clc.state < CA_PRIMED ) {
1180 CL_ReadDemoMessage();
1181 }
1182 // don't get the first snapshot this frame, to prevent the long
1183 // time from the gamestate load from messing causing a time skip
1184 clc.firstDemoFrameSkipped = qfalse;
1185 }
1186
1187 /*
1188 ====================
1189 CL_StartDemoLoop
1190
1191 Closing the main menu will restart the demo loop
1192 ====================
1193 */
CL_StartDemoLoop(void)1194 void CL_StartDemoLoop( void ) {
1195 // start the demo loop again
1196 Cbuf_AddText( "d1\n" );
1197 Key_SetCatcher( 0 );
1198 }
1199
1200 /*
1201 ==================
1202 CL_NextDemo
1203
1204 Called when a demo or cinematic finishes
1205 If the "nextdemo" cvar is set, that command will be issued
1206 ==================
1207 */
CL_NextDemo(void)1208 void CL_NextDemo( void ) {
1209 char v[MAX_STRING_CHARS];
1210
1211 Q_strncpyz( v, Cvar_VariableString( "nextdemo" ), sizeof( v ) );
1212 v[MAX_STRING_CHARS - 1] = 0;
1213 Com_DPrintf( "CL_NextDemo: %s\n", v );
1214 if ( !v[0] ) {
1215 return;
1216 }
1217
1218 Cvar_Set( "nextdemo","" );
1219 Cbuf_AddText( v );
1220 Cbuf_AddText( "\n" );
1221 Cbuf_Execute();
1222 }
1223
1224 /*
1225 ====================
1226
1227 Wave file saving functions
1228
1229 ====================
1230
1231
1232 void CL_WriteWaveOpen() {
1233 // we will just save it as a 16bit stereo 22050kz pcm file
1234 clc.wavefile = FS_FOpenFileWrite( "demodata.pcm" );
1235 clc.wavetime = -1;
1236 }
1237
1238 void CL_WriteWaveClose() {
1239 // and we're outta here
1240 FS_FCloseFile( clc.wavefile );
1241 }
1242
1243 extern int s_soundtime;
1244 extern portable_samplepair_t *paintbuffer;
1245
1246 void CL_WriteWaveFilePacket() {
1247 int total, i;
1248 if ( clc.wavetime == -1 ) {
1249 clc.wavetime = s_soundtime;
1250 return;
1251 }
1252
1253 total = s_soundtime - clc.wavetime;
1254 clc.wavetime = s_soundtime;
1255
1256 for ( i = 0; i < total; i++ ) {
1257 int parm;
1258 short out;
1259 parm = ( paintbuffer[i].left ) >> 8;
1260 if ( parm > 32767 ) {
1261 parm = 32767;
1262 }
1263 if ( parm < -32768 ) {
1264 parm = -32768;
1265 }
1266 out = parm;
1267 FS_Write( &out, 2, clc.wavefile );
1268 parm = ( paintbuffer[i].right ) >> 8;
1269 if ( parm > 32767 ) {
1270 parm = 32767;
1271 }
1272 if ( parm < -32768 ) {
1273 parm = -32768;
1274 }
1275 out = parm;
1276 FS_Write( &out, 2, clc.wavefile );
1277 }
1278 }
1279 */
1280
1281
1282 //======================================================================
1283
1284 /*
1285 =====================
1286 CL_ShutdownAll
1287 =====================
1288 */
CL_ShutdownAll(qboolean shutdownRef)1289 void CL_ShutdownAll(qboolean shutdownRef)
1290 {
1291 if(CL_VideoRecording())
1292 CL_CloseAVI();
1293
1294 if(clc.demorecording)
1295 CL_StopRecord_f();
1296
1297 #ifdef USE_CURL
1298 CL_cURL_Shutdown();
1299 #endif
1300 // clear sounds
1301 S_DisableSounds();
1302 // shutdown CGame
1303 CL_ShutdownCGame();
1304 // shutdown UI
1305 CL_ShutdownUI();
1306
1307 // shutdown the renderer
1308 if(shutdownRef)
1309 CL_ShutdownRef();
1310 else if(re.Shutdown)
1311 re.Shutdown(qfalse); // don't destroy window or context
1312
1313 cls.uiStarted = qfalse;
1314 cls.cgameStarted = qfalse;
1315 cls.rendererStarted = qfalse;
1316 cls.soundRegistered = qfalse;
1317 }
1318
1319 /*
1320 =================
1321 CL_ClearMemory
1322
1323 Called by Com_GameRestart
1324 =================
1325 */
CL_ClearMemory(qboolean shutdownRef)1326 void CL_ClearMemory(qboolean shutdownRef)
1327 {
1328 // shutdown all the client stuff
1329 CL_ShutdownAll(shutdownRef);
1330
1331 // if not running a server clear the whole hunk
1332 if ( !com_sv_running || !com_sv_running->integer ) {
1333 // clear the whole hunk
1334 Hunk_Clear();
1335 // clear collision map data
1336 CM_ClearMap();
1337 } else {
1338 // clear all the client data on the hunk
1339 Hunk_ClearToMark();
1340 }
1341 }
1342
1343 /*
1344 =================
1345 CL_FlushMemory
1346
1347 Called by CL_MapLoading, CL_Connect_f, CL_PlayDemo_f, and CL_ParseGamestate the only
1348 ways a client gets into a game
1349 Also called by Com_Error
1350 =================
1351 */
CL_FlushMemory(void)1352 void CL_FlushMemory(void)
1353 {
1354 CL_ClearMemory(qfalse);
1355 CL_StartHunkUsers(qfalse);
1356 }
1357
1358 /*
1359 =====================
1360 CL_MapLoading
1361
1362 A local server is starting to load a map, so update the
1363 screen to let the user know about it, then dump all client
1364 memory on the hunk from cgame, ui, and renderer
1365 =====================
1366 */
CL_MapLoading(void)1367 void CL_MapLoading( void ) {
1368 if ( com_dedicated->integer ) {
1369 clc.state = CA_DISCONNECTED;
1370 Key_SetCatcher( KEYCATCH_CONSOLE );
1371 return;
1372 }
1373
1374 if ( !com_cl_running->integer ) {
1375 return;
1376 }
1377
1378 Con_Close();
1379 Key_SetCatcher( 0 );
1380
1381 // if we are already connected to the local host, stay connected
1382 if ( clc.state >= CA_CONNECTED && !Q_stricmp( clc.servername, "localhost" ) ) {
1383 clc.state = CA_CONNECTED; // so the connect screen is drawn
1384 Com_Memset( cls.updateInfoString, 0, sizeof( cls.updateInfoString ) );
1385 Com_Memset( clc.serverMessage, 0, sizeof( clc.serverMessage ) );
1386 Com_Memset( &cl.gameState, 0, sizeof( cl.gameState ) );
1387 clc.lastPacketSentTime = -9999;
1388 SCR_UpdateScreen();
1389 } else {
1390 // clear nextmap so the cinematic shutdown doesn't execute it
1391 Cvar_Set( "nextmap", "" );
1392 CL_Disconnect( qtrue );
1393 Q_strncpyz( clc.servername, "localhost", sizeof(clc.servername) );
1394 clc.state = CA_CHALLENGING; // so the connect screen is drawn
1395 Key_SetCatcher( 0 );
1396 SCR_UpdateScreen();
1397 clc.connectTime = -RETRANSMIT_TIMEOUT;
1398 NET_StringToAdr( clc.servername, &clc.serverAddress, NA_UNSPEC);
1399 // we don't need a challenge on the localhost
1400
1401 CL_CheckForResend();
1402 }
1403 }
1404
1405 /*
1406 =====================
1407 CL_ClearState
1408
1409 Called before parsing a gamestate
1410 =====================
1411 */
CL_ClearState(void)1412 void CL_ClearState( void ) {
1413
1414 // S_StopAllSounds();
1415
1416 memset( &cl, 0, sizeof( cl ) );
1417 }
1418
1419 /*
1420 ====================
1421 CL_UpdateGUID
1422
1423 update cl_guid using QKEY_FILE and optional prefix
1424 ====================
1425 */
CL_UpdateGUID(const char * prefix,int prefix_len)1426 static void CL_UpdateGUID( const char *prefix, int prefix_len )
1427 {
1428 #if !defined( USE_PBMD5 )
1429 fileHandle_t f;
1430 int len;
1431
1432 len = FS_SV_FOpenFileRead( QKEY_FILE, &f );
1433 FS_FCloseFile( f );
1434
1435 if( len != QKEY_SIZE )
1436 Cvar_Set( "cl_guid", "" );
1437 else
1438 Cvar_Set( "cl_guid", Com_MD5File( QKEY_FILE, QKEY_SIZE,
1439 prefix, prefix_len ) );
1440 #else
1441 if ( !Q_stricmp( cl_cdkey, " " ) )
1442 {
1443 Cvar_Set( "cl_guid", "NO_GUID" );
1444
1445 return;
1446 }
1447
1448 if ( !Q_stricmp( cl_guid->string, "unknown" ) )
1449 Cvar_Set( "cl_guid", Com_PBMD5File( cl_cdkey ) );
1450 else
1451 return;
1452 #endif
1453 }
1454
CL_OldGame(void)1455 static void CL_OldGame(void)
1456 {
1457 if(cl_oldGameSet)
1458 {
1459 // change back to previous fs_game
1460 cl_oldGameSet = qfalse;
1461 Cvar_Set2("fs_game", cl_oldGame, qtrue);
1462 FS_ConditionalRestart(clc.checksumFeed, qfalse);
1463 }
1464 }
1465
1466 /*
1467 =====================
1468 CL_Disconnect
1469
1470 Called when a connection, demo, or cinematic is being terminated.
1471 Goes from a connected state to either a menu state or a console state
1472 Sends a disconnect message to the server
1473 This is also called on Com_Error and Com_Quit, so it shouldn't cause any errors
1474 =====================
1475 */
CL_Disconnect(qboolean showMainMenu)1476 void CL_Disconnect( qboolean showMainMenu ) {
1477 if ( !com_cl_running || !com_cl_running->integer ) {
1478 return;
1479 }
1480
1481 // shutting down the client so enter full screen ui mode
1482 Cvar_Set( "r_uiFullScreen", "1" );
1483
1484 if ( clc.demorecording ) {
1485 CL_StopRecord_f();
1486 }
1487
1488 if ( clc.download ) {
1489 FS_FCloseFile( clc.download );
1490 clc.download = 0;
1491 }
1492 *clc.downloadTempName = *clc.downloadName = 0;
1493 Cvar_Set( "cl_downloadName", "" );
1494
1495 #ifdef USE_MUMBLE
1496 if (cl_useMumble->integer && mumble_islinked()) {
1497 Com_Printf("Mumble: Unlinking from Mumble application\n");
1498 mumble_unlink();
1499 }
1500 #endif
1501
1502 #ifdef USE_VOIP
1503 if (cl_voipSend->integer) {
1504 int tmp = cl_voipUseVAD->integer;
1505 cl_voipUseVAD->integer = 0; // disable this for a moment.
1506 clc.voipOutgoingDataSize = 0; // dump any pending VoIP transmission.
1507 Cvar_Set("cl_voipSend", "0");
1508 CL_CaptureVoip(); // clean up any state...
1509 cl_voipUseVAD->integer = tmp;
1510 }
1511
1512 if (clc.voipCodecInitialized) {
1513 int i;
1514 opus_encoder_destroy(clc.opusEncoder);
1515 for (i = 0; i < MAX_CLIENTS; i++) {
1516 opus_decoder_destroy(clc.opusDecoder[i]);
1517 }
1518 clc.voipCodecInitialized = qfalse;
1519 }
1520 Cmd_RemoveCommand ("voip");
1521 #endif
1522
1523 if ( clc.demofile ) {
1524 FS_FCloseFile( clc.demofile );
1525 clc.demofile = 0;
1526 }
1527
1528 if ( uivm && showMainMenu ) {
1529 VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE );
1530 }
1531
1532 SCR_StopCinematic();
1533 S_ClearSoundBuffer();
1534
1535 // send a disconnect message to the server
1536 // send it a few times in case one is dropped
1537 if ( clc.state >= CA_CONNECTED ) {
1538 CL_AddReliableCommand("disconnect", qtrue);
1539 CL_WritePacket();
1540 CL_WritePacket();
1541 CL_WritePacket();
1542 }
1543
1544 // Remove pure paks
1545 FS_PureServerSetLoadedPaks("", "");
1546 FS_PureServerSetReferencedPaks( "", "" );
1547
1548 CL_ClearState();
1549
1550 // wipe the client connection
1551 Com_Memset( &clc, 0, sizeof( clc ) );
1552
1553 clc.state = CA_DISCONNECTED;
1554
1555 // allow cheats locally
1556 Cvar_Set( "sv_cheats", "1" );
1557
1558 // not connected to a pure server anymore
1559 cl_connectedToPureServer = qfalse;
1560
1561 #ifdef USE_VOIP
1562 // not connected to voip server anymore.
1563 clc.voipEnabled = qfalse;
1564 #endif
1565
1566 // Stop recording any video
1567 if( CL_VideoRecording( ) ) {
1568 // Finish rendering current frame
1569 SCR_UpdateScreen( );
1570 CL_CloseAVI( );
1571 }
1572
1573 CL_UpdateGUID( NULL, 0 );
1574
1575 if(!noGameRestart)
1576 CL_OldGame();
1577 else
1578 noGameRestart = qfalse;
1579 }
1580
1581 /*
1582 ===================
1583 CL_ForwardCommandToServer
1584
1585 adds the current command line as a clientCommand
1586 things like godmode, noclip, etc, are commands directed to the server,
1587 so when they are typed in at the console, they will need to be forwarded.
1588 ===================
1589 */
CL_ForwardCommandToServer(const char * string)1590 void CL_ForwardCommandToServer( const char *string ) {
1591 char *cmd;
1592
1593 cmd = Cmd_Argv( 0 );
1594
1595 // ignore key up commands
1596 if ( cmd[0] == '-' ) {
1597 return;
1598 }
1599
1600 if ( clc.demoplaying || clc.state < CA_CONNECTED || cmd[0] == '+' ) {
1601 Com_Printf ("Unknown command \"%s" S_COLOR_WHITE "\"\n", cmd);
1602 return;
1603 }
1604
1605 if ( Cmd_Argc() > 1 ) {
1606 CL_AddReliableCommand(string, qfalse);
1607 } else {
1608 CL_AddReliableCommand(cmd, qfalse);
1609 }
1610 }
1611
1612 /*
1613 ===================
1614 CL_RequestMotd
1615
1616 ===================
1617 */
CL_RequestMotd(void)1618 void CL_RequestMotd( void ) {
1619 #ifdef UPDATE_SERVER_NAME
1620 char info[MAX_INFO_STRING];
1621
1622 if ( !cl_motd->integer ) {
1623 return;
1624 }
1625 Com_Printf( "Resolving %s\n", UPDATE_SERVER_NAME );
1626 if ( !NET_StringToAdr( UPDATE_SERVER_NAME, &cls.updateServer, NA_IP ) ) {
1627 Com_Printf( "Couldn't resolve address\n" );
1628 return;
1629 }
1630 cls.updateServer.port = BigShort( PORT_UPDATE );
1631 Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", UPDATE_SERVER_NAME,
1632 cls.updateServer.ip[0], cls.updateServer.ip[1],
1633 cls.updateServer.ip[2], cls.updateServer.ip[3],
1634 BigShort( cls.updateServer.port ) );
1635
1636 info[0] = 0;
1637 Com_sprintf( cls.updateChallenge, sizeof( cls.updateChallenge ), "%i", (int)( ( ( (unsigned int)rand() << 16 ) ^ (unsigned int)rand() ) ^ Com_Milliseconds() ) );
1638
1639 Info_SetValueForKey( info, "challenge", cls.updateChallenge );
1640 Info_SetValueForKey( info, "renderer", cls.glconfig.renderer_string );
1641 Info_SetValueForKey( info, "version", com_version->string );
1642
1643 NET_OutOfBandPrint( NS_CLIENT, cls.updateServer, "getmotd \"%s\"\n", info );
1644 #endif
1645 }
1646
1647 /*
1648 ===================
1649 CL_RequestAuthorization
1650
1651 Authorization server protocol
1652 -----------------------------
1653
1654 All commands are text in Q3 out of band packets (leading 0xff 0xff 0xff 0xff).
1655
1656 Whenever the client tries to get a challenge from the server it wants to
1657 connect to, it also blindly fires off a packet to the authorize server:
1658
1659 getKeyAuthorize <challenge> <cdkey>
1660
1661 cdkey may be "demo"
1662
1663
1664 #OLD The authorize server returns a:
1665 #OLD
1666 #OLD keyAthorize <challenge> <accept | deny>
1667 #OLD
1668 #OLD A client will be accepted if the cdkey is valid and it has not been used by any other IP
1669 #OLD address in the last 15 minutes.
1670
1671
1672 The server sends a:
1673
1674 getIpAuthorize <challenge> <ip>
1675
1676 The authorize server returns a:
1677
1678 ipAuthorize <challenge> <accept | deny | demo | unknown >
1679
1680 A client will be accepted if a valid cdkey was sent by that ip (only) in the last 15 minutes.
1681 If no response is received from the authorize server after two tries, the client will be let
1682 in anyway.
1683 ===================
1684 */
1685 #ifndef STANDALONE
1686 #ifdef USE_AUTHORIZE_SERVER
CL_RequestAuthorization(void)1687 void CL_RequestAuthorization( void ) {
1688 char nums[64];
1689 int i, j, l;
1690 cvar_t *fs;
1691
1692 if ( !cls.authorizeServer.port ) {
1693 Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME );
1694 if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &cls.authorizeServer, NA_IP ) ) {
1695 Com_Printf( "Couldn't resolve address\n" );
1696 return;
1697 }
1698
1699 cls.authorizeServer.port = BigShort( PORT_AUTHORIZE );
1700 Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME,
1701 cls.authorizeServer.ip[0], cls.authorizeServer.ip[1],
1702 cls.authorizeServer.ip[2], cls.authorizeServer.ip[3],
1703 BigShort( cls.authorizeServer.port ) );
1704 }
1705 if ( cls.authorizeServer.type == NA_BAD ) {
1706 return;
1707 }
1708
1709 // only grab the alphanumeric values from the cdkey, to avoid any dashes or spaces
1710 j = 0;
1711 l = strlen( cl_cdkey );
1712 if ( l > 32 ) {
1713 l = 32;
1714 }
1715 for ( i = 0 ; i < l ; i++ ) {
1716 if ( ( cl_cdkey[i] >= '0' && cl_cdkey[i] <= '9' )
1717 || ( cl_cdkey[i] >= 'a' && cl_cdkey[i] <= 'z' )
1718 || ( cl_cdkey[i] >= 'A' && cl_cdkey[i] <= 'Z' ) ) {
1719 nums[j] = cl_cdkey[i];
1720 j++;
1721 }
1722 }
1723 nums[j] = 0;
1724
1725 fs = Cvar_Get( "cl_anonymous", "0", CVAR_INIT | CVAR_SYSTEMINFO );
1726
1727 NET_OutOfBandPrint(NS_CLIENT, cls.authorizeServer, "getKeyAuthorize %i %s", fs->integer, nums );
1728 }
1729 #endif
1730 #endif
1731
1732 /*
1733 ======================================================================
1734
1735 CONSOLE COMMANDS
1736
1737 ======================================================================
1738 */
1739
1740 /*
1741 ==================
1742 CL_ForwardToServer_f
1743 ==================
1744 */
CL_ForwardToServer_f(void)1745 void CL_ForwardToServer_f( void ) {
1746 if ( clc.state != CA_ACTIVE || clc.demoplaying ) {
1747 Com_Printf( "Not connected to a server.\n" );
1748 return;
1749 }
1750
1751 // don't forward the first argument
1752 if ( Cmd_Argc() > 1 ) {
1753 CL_AddReliableCommand(Cmd_Args(), qfalse);
1754 }
1755 }
1756
1757 /*
1758 ==================
1759 CL_Disconnect_f
1760 ==================
1761 */
CL_Disconnect_f(void)1762 void CL_Disconnect_f( void ) {
1763 SCR_StopCinematic();
1764 Cvar_Set("ui_singlePlayerActive", "0");
1765 if ( clc.state != CA_DISCONNECTED && clc.state != CA_CINEMATIC ) {
1766 Com_Error( ERR_DISCONNECT, "Disconnected from server" );
1767 }
1768 }
1769
1770
1771 /*
1772 ================
1773 CL_Reconnect_f
1774
1775 ================
1776 */
CL_Reconnect_f(void)1777 void CL_Reconnect_f( void ) {
1778 if ( !strlen( cl_reconnectArgs ) )
1779 return;
1780 Cvar_Set("ui_singlePlayerActive", "0");
1781 Cbuf_AddText( va("connect %s\n", cl_reconnectArgs ) );
1782 }
1783
1784 /*
1785 ================
1786 CL_Connect_f
1787
1788 ================
1789 */
CL_Connect_f(void)1790 void CL_Connect_f( void ) {
1791 char server[MAX_OSPATH];
1792 const char *serverString;
1793 int argc = Cmd_Argc();
1794 netadrtype_t family = NA_UNSPEC;
1795
1796 if ( argc != 2 && argc != 3 ) {
1797 Com_Printf( "usage: connect [-4|-6] server\n");
1798 return;
1799 }
1800
1801 if(argc == 2)
1802 Q_strncpyz( server, Cmd_Argv(1), sizeof( server ) );
1803 else
1804 {
1805 if(!strcmp(Cmd_Argv(1), "-4"))
1806 family = NA_IP;
1807 else if(!strcmp(Cmd_Argv(1), "-6"))
1808 family = NA_IP6;
1809 else
1810 Com_Printf( "warning: only -4 or -6 as address type understood.\n");
1811
1812 Q_strncpyz( server, Cmd_Argv(2), sizeof( server ) );
1813 }
1814
1815 // save arguments for reconnect
1816 Q_strncpyz( cl_reconnectArgs, Cmd_Args(), sizeof( cl_reconnectArgs ) );
1817
1818 Cvar_Set("ui_singlePlayerActive", "0");
1819
1820 S_StopAllSounds(); // NERVE - SMF
1821
1822 // starting to load a map so we get out of full screen ui mode
1823 Cvar_Set( "r_uiFullScreen", "0" );
1824
1825 // fire a message off to the motd server
1826 CL_RequestMotd();
1827
1828 // clear any previous "server full" type messages
1829 clc.serverMessage[0] = 0;
1830
1831 if ( com_sv_running->integer && !strcmp( server, "localhost" ) ) {
1832 // if running a local server, kill it
1833 SV_Shutdown( "Server quit" );
1834 }
1835
1836 // make sure a local server is killed
1837 Cvar_Set( "sv_killserver", "1" );
1838 SV_Frame( 0 );
1839
1840 noGameRestart = qtrue;
1841 CL_Disconnect( qtrue );
1842 Con_Close();
1843
1844 Q_strncpyz( clc.servername, server, sizeof(clc.servername) );
1845
1846 if (!NET_StringToAdr(clc.servername, &clc.serverAddress, family) ) {
1847 Com_Printf( "Bad server address\n" );
1848 clc.state = CA_DISCONNECTED;
1849 return;
1850 }
1851 if ( clc.serverAddress.port == 0 ) {
1852 clc.serverAddress.port = BigShort( PORT_SERVER );
1853 }
1854
1855 serverString = NET_AdrToStringwPort(clc.serverAddress);
1856
1857 Com_Printf( "%s resolved to %s\n", clc.servername, serverString);
1858
1859 if( cl_guidServerUniq->integer )
1860 CL_UpdateGUID( serverString, strlen( serverString ) );
1861 else
1862 CL_UpdateGUID( NULL, 0 );
1863
1864 // if we aren't playing on a lan, we need to authenticate
1865 // with the cd key
1866 if(NET_IsLocalAddress(clc.serverAddress))
1867 clc.state = CA_CHALLENGING;
1868 else
1869 {
1870 clc.state = CA_CONNECTING;
1871
1872 // Set a client challenge number that ideally is mirrored back by the server.
1873 clc.challenge = ( ( (unsigned int)rand() << 16 ) ^ (unsigned int)rand() ) ^ Com_Milliseconds();
1874 }
1875
1876 // show_bug.cgi?id=507
1877 // prepare to catch a connection process that would turn bad
1878 Cvar_Set( "com_errorDiagnoseIP", NET_AdrToString( clc.serverAddress ) );
1879 // ATVI Wolfenstein Misc #439
1880 // we need to setup a correct default for this, otherwise the first val we set might reappear
1881 Cvar_Set( "com_errorMessage", "" );
1882
1883 Key_SetCatcher( 0 );
1884 clc.connectTime = -99999; // CL_CheckForResend() will fire immediately
1885 clc.connectPacketCount = 0;
1886
1887 // server connection string
1888 Cvar_Set( "cl_currentServerAddress", server );
1889
1890 // NERVE - SMF - reset some cvars
1891 Cvar_Set( "mp_playerType", "0" );
1892 Cvar_Set( "mp_currentPlayerType", "0" );
1893 Cvar_Set( "mp_weapon", "0" );
1894 Cvar_Set( "mp_team", "0" );
1895 Cvar_Set( "mp_currentTeam", "0" );
1896
1897 Cvar_Set( "ui_limboOptions", "0" );
1898 Cvar_Set( "ui_limboPrevOptions", "0" );
1899 Cvar_Set( "ui_limboObjective", "0" );
1900 // -NERVE - SMF
1901
1902 }
1903
1904 #define MAX_RCON_MESSAGE 1024
1905
1906 /*
1907 ==================
1908 CL_CompleteRcon
1909 ==================
1910 */
CL_CompleteRcon(char * args,int argNum)1911 static void CL_CompleteRcon( char *args, int argNum )
1912 {
1913 if( argNum == 2 )
1914 {
1915 // Skip "rcon "
1916 char *p = Com_SkipTokens( args, 1, " " );
1917
1918 if( p > args )
1919 Field_CompleteCommand( p, qtrue, qtrue );
1920 }
1921 }
1922
1923 /*
1924 =====================
1925 CL_Rcon_f
1926
1927 Send the rest of the command line over as
1928 an unconnected command.
1929 =====================
1930 */
CL_Rcon_f(void)1931 void CL_Rcon_f( void ) {
1932 char message[MAX_RCON_MESSAGE];
1933 netadr_t to;
1934
1935 if ( !rcon_client_password->string[0] ) {
1936 Com_Printf( "You must set 'rconpassword' before\n"
1937 "issuing an rcon command.\n" );
1938 return;
1939 }
1940
1941 message[0] = -1;
1942 message[1] = -1;
1943 message[2] = -1;
1944 message[3] = -1;
1945 message[4] = 0;
1946
1947 Q_strcat (message, MAX_RCON_MESSAGE, "rcon ");
1948
1949 Q_strcat (message, MAX_RCON_MESSAGE, rcon_client_password->string);
1950 Q_strcat (message, MAX_RCON_MESSAGE, " ");
1951
1952 Q_strcat (message, MAX_RCON_MESSAGE, Cmd_Cmd()+5);
1953
1954 if ( clc.state >= CA_CONNECTED ) {
1955 to = clc.netchan.remoteAddress;
1956 } else {
1957 if ( !strlen( rconAddress->string ) ) {
1958 Com_Printf( "You must either be connected,\n"
1959 "or set the 'rconAddress' cvar\n"
1960 "to issue rcon commands\n" );
1961
1962 return;
1963 }
1964 NET_StringToAdr (rconAddress->string, &to, NA_UNSPEC);
1965 if ( to.port == 0 ) {
1966 to.port = BigShort( PORT_SERVER );
1967 }
1968 }
1969
1970 NET_SendPacket( NS_CLIENT, strlen( message ) + 1, message, to );
1971 cls.rconAddress = to;
1972 }
1973
1974 /*
1975 =================
1976 CL_SendPureChecksums
1977 =================
1978 */
CL_SendPureChecksums(void)1979 void CL_SendPureChecksums( void ) {
1980 char cMsg[MAX_INFO_VALUE];
1981
1982 // if we are pure we need to send back a command with our referenced pk3 checksums
1983 Com_sprintf(cMsg, sizeof(cMsg), "cp %d %s", cl.serverId, FS_ReferencedPakPureChecksums());
1984
1985 CL_AddReliableCommand(cMsg, qfalse);
1986 }
1987
1988 /*
1989 =================
1990 CL_ResetPureClientAtServer
1991 =================
1992 */
CL_ResetPureClientAtServer(void)1993 void CL_ResetPureClientAtServer( void ) {
1994 CL_AddReliableCommand("vdr", qfalse);
1995 }
1996
1997 /*
1998 =================
1999 CL_Vid_Restart_f
2000
2001 Restart the video subsystem
2002
2003 we also have to reload the UI and CGame because the renderer
2004 doesn't know what graphics to reload
2005 =================
2006 */
CL_Vid_Restart_f(void)2007 void CL_Vid_Restart_f( void ) {
2008
2009 // RF, don't show percent bar, since the memory usage will just sit at the same level anyway
2010 Cvar_Set( "com_expectedhunkusage", "-1" );
2011
2012 // Settings may have changed so stop recording now
2013 if( CL_VideoRecording( ) ) {
2014 CL_CloseAVI( );
2015 }
2016
2017 if(clc.demorecording)
2018 CL_StopRecord_f();
2019
2020 // don't let them loop during the restart
2021 S_StopAllSounds();
2022
2023 if(!FS_ConditionalRestart(clc.checksumFeed, qtrue))
2024 {
2025 // if not running a server clear the whole hunk
2026 if(com_sv_running->integer)
2027 {
2028 // clear all the client data on the hunk
2029 Hunk_ClearToMark();
2030 }
2031 else
2032 {
2033 // clear the whole hunk
2034 Hunk_Clear();
2035 }
2036
2037 // shutdown the UI
2038 CL_ShutdownUI();
2039 // shutdown the CGame
2040 CL_ShutdownCGame();
2041 // shutdown the renderer and clear the renderer interface
2042 CL_ShutdownRef();
2043 // client is no longer pure untill new checksums are sent
2044 CL_ResetPureClientAtServer();
2045 // clear pak references
2046 FS_ClearPakReferences( FS_UI_REF | FS_CGAME_REF );
2047 // reinitialize the filesystem if the game directory or checksum has changed
2048
2049 S_BeginRegistration(); // all sound handles are now invalid
2050
2051 cls.rendererStarted = qfalse;
2052 cls.uiStarted = qfalse;
2053 cls.cgameStarted = qfalse;
2054 cls.soundRegistered = qfalse;
2055 autoupdateChecked = qfalse;
2056
2057 // unpause so the cgame definately gets a snapshot and renders a frame
2058 Cvar_Set( "cl_paused", "0" );
2059
2060 // initialize the renderer interface
2061 CL_InitRef();
2062
2063 // startup all the client stuff
2064 CL_StartHunkUsers(qfalse);
2065
2066 // start the cgame if connected
2067 if(clc.state > CA_CONNECTED && clc.state != CA_CINEMATIC)
2068 {
2069 cls.cgameStarted = qtrue;
2070 CL_InitCGame();
2071 // send pure checksums
2072 CL_SendPureChecksums();
2073 }
2074
2075 }
2076 }
2077
2078 /*
2079 =================
2080 CL_UI_Restart_f
2081
2082 Restart the ui subsystem
2083 =================
2084 */
CL_UI_Restart_f(void)2085 void CL_UI_Restart_f( void ) { // NERVE - SMF
2086 // shutdown the UI
2087 CL_ShutdownUI();
2088
2089 autoupdateChecked = qfalse;
2090
2091 // init the UI
2092 CL_InitUI();
2093 }
2094
2095 /*
2096 =================
2097 CL_Snd_Shutdown
2098
2099 Shut down the sound subsystem
2100 =================
2101 */
CL_Snd_Shutdown(void)2102 void CL_Snd_Shutdown(void)
2103 {
2104 S_Shutdown();
2105 cls.soundStarted = qfalse;
2106 }
2107
2108 /*
2109 =================
2110 CL_Snd_Restart_f
2111
2112 Restart the sound subsystem
2113 The cgame and game must also be forced to restart because
2114 handles will be invalid
2115 =================
2116 */
CL_Snd_Restart_f(void)2117 void CL_Snd_Restart_f(void)
2118 {
2119 CL_Snd_Shutdown();
2120 // sound will be reinitialized by vid_restart
2121 CL_Vid_Restart_f();
2122 }
2123
2124 /*
2125 ==================
2126 CL_PK3List_f
2127 ==================
2128 */
CL_OpenedPK3List_f(void)2129 void CL_OpenedPK3List_f( void ) {
2130 Com_Printf( "Opened PK3 Names: %s\n", FS_LoadedPakNames() );
2131 }
2132
2133 /*
2134 ==================
2135 CL_PureList_f
2136 ==================
2137 */
CL_ReferencedPK3List_f(void)2138 void CL_ReferencedPK3List_f( void ) {
2139 Com_Printf( "Referenced PK3 Names: %s\n", FS_ReferencedPakNames() );
2140 }
2141
2142 /*
2143 ==================
2144 CL_Configstrings_f
2145 ==================
2146 */
CL_Configstrings_f(void)2147 void CL_Configstrings_f( void ) {
2148 int i;
2149 int ofs;
2150
2151 if ( clc.state != CA_ACTIVE ) {
2152 Com_Printf( "Not connected to a server.\n" );
2153 return;
2154 }
2155
2156 for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) {
2157 ofs = cl.gameState.stringOffsets[ i ];
2158 if ( !ofs ) {
2159 continue;
2160 }
2161 Com_Printf( "%4i: %s\n", i, cl.gameState.stringData + ofs );
2162 }
2163 }
2164
2165 /*
2166 ==============
2167 CL_Clientinfo_f
2168 ==============
2169 */
CL_Clientinfo_f(void)2170 void CL_Clientinfo_f( void ) {
2171 Com_Printf( "--------- Client Information ---------\n" );
2172 Com_Printf( "state: %i\n", clc.state );
2173 Com_Printf( "Server: %s\n", clc.servername );
2174 Com_Printf( "User info settings:\n" );
2175 Info_Print( Cvar_InfoString( CVAR_USERINFO ) );
2176 Com_Printf( "--------------------------------------\n" );
2177 }
2178
2179
2180 //====================================================================
2181
2182 /*
2183 =================
2184 CL_DownloadsComplete
2185
2186 Called when all downloading has been completed
2187 =================
2188 */
CL_DownloadsComplete(void)2189 void CL_DownloadsComplete( void ) {
2190
2191 #ifndef _WIN32
2192 char *fs_write_path;
2193 #endif
2194 char *fn;
2195
2196 // DHM - Nerve :: Auto-update (not finished yet)
2197 if ( autoupdateStarted ) {
2198
2199 if ( strlen( autoupdateFilename ) > 4 ) {
2200 #ifdef _WIN32
2201 // win32's Sys_StartProcess prepends the current dir
2202 fn = va( "%s/%s", FS_ShiftStr( AUTOUPDATE_DIR, AUTOUPDATE_DIR_SHIFT ), autoupdateFilename );
2203 #else
2204 fs_write_path = Cvar_VariableString( "fs_homepath" );
2205 fn = FS_BuildOSPath( fs_write_path, FS_ShiftStr( AUTOUPDATE_DIR, AUTOUPDATE_DIR_SHIFT ), autoupdateFilename );
2206 #ifdef __linux__
2207 Sys_Chmod( fn, S_IXUSR );
2208 #endif
2209 #endif
2210 Sys_StartProcess( fn, qtrue );
2211 }
2212
2213 autoupdateStarted = qfalse;
2214 CL_Disconnect( qtrue );
2215 return;
2216 }
2217
2218 #ifdef USE_CURL
2219 // if we downloaded with cURL
2220 if(clc.cURLUsed) {
2221 clc.cURLUsed = qfalse;
2222 CL_cURL_Shutdown();
2223 if( clc.cURLDisconnected ) {
2224 if(clc.downloadRestart) {
2225 FS_Restart(clc.checksumFeed);
2226 clc.downloadRestart = qfalse;
2227 }
2228 clc.cURLDisconnected = qfalse;
2229 CL_Reconnect_f();
2230 return;
2231 }
2232 }
2233 #endif
2234
2235 // if we downloaded files we need to restart the file system
2236 if ( clc.downloadRestart ) {
2237 clc.downloadRestart = qfalse;
2238
2239 FS_Restart( clc.checksumFeed ); // We possibly downloaded a pak, restart the file system to load it
2240
2241 // inform the server so we get new gamestate info
2242 CL_AddReliableCommand( "donedl", qfalse );
2243
2244 // by sending the donedl command we request a new gamestate
2245 // so we don't want to load stuff yet
2246 return;
2247 }
2248
2249 // let the client game init and load data
2250 clc.state = CA_LOADING;
2251
2252 Com_EventLoop();
2253
2254 // if the gamestate was changed by calling Com_EventLoop
2255 // then we loaded everything already and we don't want to do it again.
2256 if ( clc.state != CA_LOADING ) {
2257 return;
2258 }
2259
2260 // starting to load a map so we get out of full screen ui mode
2261 Cvar_Set( "r_uiFullScreen", "0" );
2262
2263 // flush client memory and start loading stuff
2264 // this will also (re)load the UI
2265 // if this is a local client then only the client part of the hunk
2266 // will be cleared, note that this is done after the hunk mark has been set
2267 CL_FlushMemory();
2268
2269 // initialize the CGame
2270 cls.cgameStarted = qtrue;
2271 CL_InitCGame();
2272
2273 // set pure checksums
2274 CL_SendPureChecksums();
2275
2276 CL_WritePacket();
2277 CL_WritePacket();
2278 CL_WritePacket();
2279 }
2280
2281 /*
2282 =================
2283 CL_BeginDownload
2284
2285 Requests a file to download from the server. Stores it in the current
2286 game directory.
2287 =================
2288 */
CL_BeginDownload(const char * localName,const char * remoteName)2289 void CL_BeginDownload( const char *localName, const char *remoteName ) {
2290
2291 Com_DPrintf( "***** CL_BeginDownload *****\n"
2292 "Localname: %s\n"
2293 "Remotename: %s\n"
2294 "****************************\n", localName, remoteName );
2295
2296 Q_strncpyz( clc.downloadName, localName, sizeof( clc.downloadName ) );
2297 Com_sprintf( clc.downloadTempName, sizeof( clc.downloadTempName ), "%s.tmp", localName );
2298
2299 // Set so UI gets access to it
2300 Cvar_Set( "cl_downloadName", remoteName );
2301 Cvar_Set( "cl_downloadSize", "0" );
2302 Cvar_Set( "cl_downloadCount", "0" );
2303 Cvar_SetValue( "cl_downloadTime", cls.realtime );
2304
2305 clc.downloadBlock = 0; // Starting new file
2306 clc.downloadCount = 0;
2307
2308 CL_AddReliableCommand( va( "download %s", remoteName ), qfalse );
2309 }
2310
2311 /*
2312 =================
2313 CL_NextDownload
2314
2315 A download completed or failed
2316 =================
2317 */
CL_NextDownload(void)2318 void CL_NextDownload( void ) {
2319 char *s;
2320 char *remoteName, *localName;
2321 qboolean useCURL = qfalse;
2322
2323 // A download has finished, check whether this matches a referenced checksum
2324 if( *clc.downloadName && !autoupdateStarted ) {
2325 char *zippath = FS_BuildOSPath(Cvar_VariableString("fs_homepath"), clc.downloadName, "");
2326 zippath[strlen(zippath)-1] = '\0';
2327
2328 if(!FS_CompareZipChecksum(zippath))
2329 Com_Error(ERR_DROP, "Incorrect checksum for file: %s", clc.downloadName);
2330 }
2331
2332 *clc.downloadTempName = *clc.downloadName = 0;
2333 Cvar_Set("cl_downloadName", "");
2334
2335 // We are looking to start a download here
2336 if ( *clc.downloadList ) {
2337 s = clc.downloadList;
2338
2339 // format is:
2340 // @remotename@localname@remotename@localname, etc.
2341
2342 if ( *s == '@' ) {
2343 s++;
2344 }
2345 remoteName = s;
2346
2347 if ( ( s = strchr( s, '@' ) ) == NULL ) {
2348 CL_DownloadsComplete();
2349 return;
2350 }
2351
2352 *s++ = 0;
2353 localName = s;
2354 if ( ( s = strchr( s, '@' ) ) != NULL ) {
2355 *s++ = 0;
2356 } else {
2357 s = localName + strlen( localName ); // point at the nul byte
2358
2359 }
2360
2361 #ifdef USE_CURL
2362 if(!(cl_allowDownload->integer & DLF_NO_REDIRECT)) {
2363 if(clc.sv_allowDownload & DLF_NO_REDIRECT) {
2364 Com_Printf("WARNING: server does not "
2365 "allow download redirection "
2366 "(sv_allowDownload is %d)\n",
2367 clc.sv_allowDownload);
2368 }
2369 else if(!*clc.sv_dlURL) {
2370 Com_Printf("WARNING: server allows "
2371 "download redirection, but does not "
2372 "have sv_dlURL set\n");
2373 }
2374 else if(!CL_cURL_Init()) {
2375 Com_Printf("WARNING: could not load "
2376 "cURL library\n");
2377 }
2378 else {
2379 CL_cURL_BeginDownload(localName, va("%s/%s",
2380 clc.sv_dlURL, remoteName));
2381 useCURL = qtrue;
2382 }
2383 }
2384 else if(!(clc.sv_allowDownload & DLF_NO_REDIRECT)) {
2385 Com_Printf("WARNING: server allows download "
2386 "redirection, but it disabled by client "
2387 "configuration (cl_allowDownload is %d)\n",
2388 cl_allowDownload->integer);
2389 }
2390 #endif /* USE_CURL */
2391 if(!useCURL) {
2392 if((cl_allowDownload->integer & DLF_NO_UDP)) {
2393 Com_Error(ERR_DROP, "UDP Downloads are "
2394 "disabled on your client. "
2395 "(cl_allowDownload is %d)",
2396 cl_allowDownload->integer);
2397 return;
2398 }
2399 else {
2400 CL_BeginDownload( localName, remoteName );
2401 }
2402 }
2403 clc.downloadRestart = qtrue;
2404
2405 // move over the rest
2406 memmove( clc.downloadList, s, strlen( s ) + 1 );
2407
2408 return;
2409 }
2410
2411 CL_DownloadsComplete();
2412 }
2413
2414 /*
2415 =================
2416 CL_InitDownloads
2417
2418 After receiving a valid game state, we valid the cgame and local zip files here
2419 and determine if we need to download them
2420 =================
2421 */
CL_InitDownloads(void)2422 void CL_InitDownloads( void ) {
2423 #ifndef PRE_RELEASE_DEMO
2424 char missingfiles[1024];
2425 char *dir = FS_ShiftStr( AUTOUPDATE_DIR, AUTOUPDATE_DIR_SHIFT );
2426
2427 if ( autoupdateStarted && NET_CompareAdr( cls.autoupdateServer, clc.serverAddress ) ) {
2428 if ( strlen( cl_updatefiles->string ) > 4 ) {
2429 Q_strncpyz( autoupdateFilename, cl_updatefiles->string, sizeof( autoupdateFilename ) );
2430 Q_strncpyz( clc.downloadList, va( "@%s/%s@%s/%s", dir, cl_updatefiles->string, dir, cl_updatefiles->string ), MAX_INFO_STRING );
2431 clc.state = CA_CONNECTED;
2432 CL_NextDownload();
2433 return;
2434 }
2435 } else {
2436 if ( !(cl_allowDownload->integer & DLF_ENABLE) ) {
2437 // autodownload is disabled on the client
2438 // but it's possible that some referenced files on the server are missing
2439
2440 // whatever autodownlad configuration, store missing files in a cvar, use later in the ui maybe
2441 if ( FS_ComparePaks( missingfiles, sizeof( missingfiles ), qfalse ) ) {
2442 Cvar_Set( "com_missingFiles", missingfiles );
2443 } else {
2444 Cvar_Set( "com_missingFiles", "" );
2445 }
2446 Com_Printf( "\nWARNING: You are missing some files referenced by the server:\n%s"
2447 "You might not be able to join the game\n"
2448 "Go to the setting menu to turn on autodownload, or get the file elsewhere\n\n", missingfiles );
2449 }
2450 else if ( FS_ComparePaks( clc.downloadList, sizeof( clc.downloadList ), qtrue ) ) {
2451 // this gets printed to UI, i18n
2452 Com_Printf( CL_TranslateStringBuf( "Need paks: %s\n" ), clc.downloadList );
2453
2454 if ( *clc.downloadList ) {
2455 // if autodownloading is not enabled on the server
2456 clc.state = CA_CONNECTED;
2457
2458 *clc.downloadTempName = *clc.downloadName = 0;
2459 Cvar_Set( "cl_downloadName", "" );
2460
2461 CL_NextDownload();
2462 return;
2463 }
2464 }
2465 }
2466 #endif
2467
2468 CL_DownloadsComplete();
2469 }
2470
2471 /*
2472 =================
2473 CL_CheckForResend
2474
2475 Resend a connect message if the last one has timed out
2476 =================
2477 */
CL_CheckForResend(void)2478 void CL_CheckForResend( void ) {
2479 int port;
2480 char info[MAX_INFO_STRING];
2481 char data[MAX_INFO_STRING + 10];
2482
2483 // don't send anything if playing back a demo
2484 if ( clc.demoplaying ) {
2485 return;
2486 }
2487
2488 // resend if we haven't gotten a reply yet
2489 if ( clc.state != CA_CONNECTING && clc.state != CA_CHALLENGING ) {
2490 return;
2491 }
2492
2493 if ( cls.realtime - clc.connectTime < RETRANSMIT_TIMEOUT ) {
2494 return;
2495 }
2496
2497 clc.connectTime = cls.realtime; // for retransmit requests
2498 clc.connectPacketCount++;
2499
2500
2501 switch ( clc.state ) {
2502 case CA_CONNECTING:
2503 // requesting a challenge .. IPv6 users always get in as authorize server supports no ipv6.
2504 #ifndef STANDALONE
2505 #ifdef USE_AUTHORIZE_SERVER
2506 if (!com_standalone->integer && clc.serverAddress.type == NA_IP && !Sys_IsLANAddress( clc.serverAddress ) )
2507 CL_RequestAuthorization();
2508 #endif
2509 #endif
2510
2511 // The challenge request shall be followed by a client challenge so no malicious server can hijack this connection.
2512 // Add the gamename so the server knows we're running the correct game or can reject the client
2513 // with a meaningful message
2514 Com_sprintf(data, sizeof(data), "getchallenge %d %s", clc.challenge, com_gamename->string);
2515
2516 NET_OutOfBandPrint(NS_CLIENT, clc.serverAddress, "%s", data);
2517 break;
2518
2519 case CA_CHALLENGING:
2520 // sending back the challenge
2521 port = Cvar_VariableValue( "net_qport" );
2522
2523 Q_strncpyz( info, Cvar_InfoString( CVAR_USERINFO ), sizeof( info ) );
2524
2525 #ifdef LEGACY_PROTOCOL
2526 if(com_legacyprotocol->integer == com_protocol->integer)
2527 clc.compat = qtrue;
2528
2529 if(clc.compat)
2530 Info_SetValueForKey(info, "protocol", va("%i", com_legacyprotocol->integer));
2531 else
2532 #endif
2533 Info_SetValueForKey( info, "protocol", va("%i", com_protocol->integer ) );
2534 Info_SetValueForKey( info, "qport", va( "%i", port ) );
2535 Info_SetValueForKey( info, "challenge", va( "%i", clc.challenge ) );
2536
2537 Com_sprintf( data, sizeof(data), "connect \"%s\"", info );
2538 NET_OutOfBandData( NS_CLIENT, clc.serverAddress, (byte *) data, strlen ( data ) );
2539 // the most current userinfo has been sent, so watch for any
2540 // newer changes to userinfo variables
2541 cvar_modifiedFlags &= ~CVAR_USERINFO;
2542 break;
2543
2544 default:
2545 Com_Error( ERR_FATAL, "CL_CheckForResend: bad clc.state" );
2546 }
2547 }
2548
2549 /*
2550 ===================
2551 CL_MotdPacket
2552
2553 ===================
2554 */
CL_MotdPacket(netadr_t from)2555 void CL_MotdPacket( netadr_t from ) {
2556 #ifdef UPDATE_SERVER_NAME
2557 char *challenge;
2558 char *info;
2559
2560 // if not from our server, ignore it
2561 if ( !NET_CompareAdr( from, cls.updateServer ) ) {
2562 return;
2563 }
2564
2565 info = Cmd_Argv( 1 );
2566
2567 // check challenge
2568 challenge = Info_ValueForKey( info, "challenge" );
2569 if ( strcmp( challenge, cls.updateChallenge ) ) {
2570 return;
2571 }
2572
2573 challenge = Info_ValueForKey( info, "motd" );
2574
2575 Q_strncpyz( cls.updateInfoString, info, sizeof( cls.updateInfoString ) );
2576 Cvar_Set( "cl_motdString", challenge );
2577 #endif
2578 }
2579
2580 /*
2581 ===================
2582 CL_PrintPackets
2583 an OOB message from server, with potential markups
2584 print OOB are the only messages we handle markups in
2585 [err_dialog]: used to indicate that the connection should be aborted
2586 no further information, just do an error diagnostic screen afterwards
2587 [err_prot]: HACK. This is a protocol error. The client uses a custom
2588 protocol error message (client sided) in the diagnostic window.
2589 The space for the error message on the connection screen is limited
2590 to 256 chars.
2591 ===================
2592 */
CL_PrintPacket(netadr_t from,msg_t * msg)2593 void CL_PrintPacket( netadr_t from, msg_t *msg ) {
2594 char *s;
2595 s = MSG_ReadBigString( msg );
2596 if ( !Q_stricmpn( s, "[err_dialog]", 12 ) ) {
2597 Q_strncpyz( clc.serverMessage, s + 12, sizeof( clc.serverMessage ) );
2598 Cvar_Set( "com_errorMessage", clc.serverMessage );
2599 } else if ( !Q_stricmpn( s, "[err_prot]", 10 ) ) {
2600 Q_strncpyz( clc.serverMessage, s + 10, sizeof( clc.serverMessage ) );
2601 Cvar_Set( "com_errorMessage", CL_TranslateStringBuf( PROTOCOL_MISMATCH_ERROR_LONG ) );
2602 } else {
2603 Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) );
2604 }
2605 Com_Printf( "%s", clc.serverMessage );
2606 }
2607
2608 /*
2609 ===================
2610 CL_InitServerInfo
2611 ===================
2612 */
CL_InitServerInfo(serverInfo_t * server,netadr_t * address)2613 void CL_InitServerInfo( serverInfo_t *server, netadr_t *address ) {
2614 server->adr = *address;
2615 server->clients = 0;
2616 server->hostName[0] = '\0';
2617 server->mapName[0] = '\0';
2618 server->maxClients = 0;
2619 server->maxPing = 0;
2620 server->minPing = 0;
2621 server->ping = -1;
2622 server->game[0] = '\0';
2623 server->gameType = 0;
2624 server->netType = 0;
2625 server->allowAnonymous = 0;
2626 server->friendlyFire = 0; // NERVE - SMF
2627 server->maxlives = 0; // NERVE - SMF
2628 server->tourney = 0; // NERVE - SMF
2629 server->punkbuster = 0; // DHM - Nerve
2630 server->gameName[0] = '\0'; // Arnout
2631 server->antilag = 0;
2632 server->g_humanplayers = 0;
2633 server->g_needpass = 0;
2634 }
2635
2636 #define MAX_SERVERSPERPACKET 256
2637
2638 /*
2639 ===================
2640 CL_ServersResponsePacket
2641 ===================
2642 */
CL_ServersResponsePacket(const netadr_t * from,msg_t * msg,qboolean extended)2643 void CL_ServersResponsePacket( const netadr_t* from, msg_t *msg, qboolean extended ) {
2644 int i, j, count, total;
2645 netadr_t addresses[MAX_SERVERSPERPACKET];
2646 int numservers;
2647 byte* buffptr;
2648 byte* buffend;
2649
2650 Com_Printf( "CL_ServersResponsePacket from %s\n", NET_AdrToStringwPort( *from ) );
2651
2652 if ( cls.numglobalservers == -1 ) {
2653 // state to detect lack of servers or lack of response
2654 cls.numglobalservers = 0;
2655 cls.numGlobalServerAddresses = 0;
2656 }
2657
2658 // parse through server response string
2659 numservers = 0;
2660 buffptr = msg->data;
2661 buffend = buffptr + msg->cursize;
2662
2663 // advance to initial token
2664 do
2665 {
2666 if(*buffptr == '\\' || (extended && *buffptr == '/'))
2667 break;
2668
2669 buffptr++;
2670 } while (buffptr < buffend);
2671
2672 while (buffptr + 1 < buffend)
2673 {
2674 // IPv4 address
2675 if (*buffptr == '\\')
2676 {
2677 buffptr++;
2678
2679 if (buffend - buffptr < sizeof(addresses[numservers].ip) + sizeof(addresses[numservers].port) + 1)
2680 break;
2681
2682 for(i = 0; i < sizeof(addresses[numservers].ip); i++)
2683 addresses[numservers].ip[i] = *buffptr++;
2684
2685 addresses[numservers].type = NA_IP;
2686 }
2687 // IPv6 address, if it's an extended response
2688 else if (extended && *buffptr == '/')
2689 {
2690 buffptr++;
2691
2692 if (buffend - buffptr < sizeof(addresses[numservers].ip6) + sizeof(addresses[numservers].port) + 1)
2693 break;
2694
2695 for(i = 0; i < sizeof(addresses[numservers].ip6); i++)
2696 addresses[numservers].ip6[i] = *buffptr++;
2697
2698 addresses[numservers].type = NA_IP6;
2699 addresses[numservers].scope_id = from->scope_id;
2700 }
2701 else
2702 // syntax error!
2703 break;
2704
2705 // parse out port
2706 addresses[numservers].port = (*buffptr++) << 8;
2707 addresses[numservers].port += *buffptr++;
2708 addresses[numservers].port = BigShort( addresses[numservers].port );
2709
2710 // syntax check
2711 if (*buffptr != '\\' && *buffptr != '/')
2712 break;
2713
2714 numservers++;
2715
2716 if (numservers >= MAX_SERVERSPERPACKET)
2717 break;
2718 }
2719
2720 count = cls.numglobalservers;
2721
2722 for (i = 0; i < numservers && count < MAX_GLOBAL_SERVERS; i++) {
2723 // build net address
2724 serverInfo_t *server = &cls.globalServers[count];
2725
2726 // Tequila: It's possible to have sent many master server requests. Then
2727 // we may receive many times the same addresses from the master server.
2728 // We just avoid to add a server if it is still in the global servers list.
2729 for (j = 0; j < count; j++)
2730 {
2731 if (NET_CompareAdr(cls.globalServers[j].adr, addresses[i]))
2732 break;
2733 }
2734
2735 if (j < count)
2736 continue;
2737
2738 CL_InitServerInfo( server, &addresses[i] );
2739 // advance to next slot
2740 count++;
2741 }
2742
2743 // if getting the global list
2744 if ( count >= MAX_GLOBAL_SERVERS && cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS )
2745 {
2746 // if we couldn't store the servers in the main list anymore
2747 for (; i < numservers && cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS; i++)
2748 {
2749 // just store the addresses in an additional list
2750 cls.globalServerAddresses[cls.numGlobalServerAddresses++] = addresses[i];
2751 }
2752 }
2753
2754 cls.numglobalservers = count;
2755 total = count + cls.numGlobalServerAddresses;
2756
2757 Com_Printf( "%d servers parsed (total %d)\n", numservers, total );
2758 }
2759
2760 /*
2761 =================
2762 CL_ConnectionlessPacket
2763
2764 Responses to broadcasts, etc
2765 =================
2766 */
CL_ConnectionlessPacket(netadr_t from,msg_t * msg)2767 void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) {
2768 char *s;
2769 char *c;
2770 int challenge = 0;
2771
2772 MSG_BeginReadingOOB( msg );
2773 MSG_ReadLong( msg ); // skip the -1
2774
2775 s = MSG_ReadStringLine( msg );
2776
2777 Cmd_TokenizeString( s );
2778
2779 c = Cmd_Argv( 0 );
2780
2781 Com_DPrintf ("CL packet %s: %s\n", NET_AdrToStringwPort(from), c);
2782
2783 // challenge from the server we are connecting to
2784 if (!Q_stricmp(c, "challengeResponse"))
2785 {
2786 char *strver;
2787 int ver;
2788
2789 if (clc.state != CA_CONNECTING)
2790 {
2791 Com_DPrintf("Unwanted challenge response received. Ignored.\n");
2792 return;
2793 }
2794
2795 c = Cmd_Argv( 3 );
2796 if(*c)
2797 challenge = atoi(c);
2798
2799 strver = Cmd_Argv( 4 );
2800 if(*strver)
2801 {
2802 ver = atoi(strver);
2803
2804 if(ver != com_protocol->integer)
2805 {
2806 #ifdef LEGACY_PROTOCOL
2807 if(com_legacyprotocol->integer > 0)
2808 {
2809 // Server is ioq3 but has a different protocol than we do.
2810 // Fall back to idq3 protocol.
2811 clc.compat = qtrue;
2812
2813 Com_Printf(S_COLOR_YELLOW "Warning: Server reports protocol version %d, "
2814 "we have %d. Trying legacy protocol %d.\n",
2815 ver, com_protocol->integer, com_legacyprotocol->integer);
2816 }
2817 else
2818 #endif
2819 {
2820 Com_Printf(S_COLOR_YELLOW "Warning: Server reports protocol version %d, we have %d. "
2821 "Trying anyways.\n", ver, com_protocol->integer);
2822 }
2823 }
2824 }
2825 #ifdef LEGACY_PROTOCOL
2826 else
2827 clc.compat = qtrue;
2828
2829 if(clc.compat)
2830 {
2831 if(!NET_CompareAdr(from, clc.serverAddress))
2832 {
2833 // This challenge response is not coming from the expected address.
2834 // Check whether we have a matching client challenge to prevent
2835 // connection hi-jacking.
2836
2837 if(!*c || challenge != clc.challenge)
2838 {
2839 Com_DPrintf("Challenge response received from unexpected source. Ignored.\n");
2840 return;
2841 }
2842 }
2843 }
2844 else
2845 #endif
2846 {
2847 if(!*c || challenge != clc.challenge)
2848 {
2849 Com_Printf("Bad challenge for challengeResponse. Ignored.\n");
2850 return;
2851 }
2852 }
2853
2854 // start sending challenge response instead of challenge request packets
2855 clc.challenge = atoi(Cmd_Argv(1));
2856 if ( Cmd_Argc() > 2 ) {
2857 clc.onlyVisibleClients = atoi( Cmd_Argv( 2 ) ); // DHM - Nerve
2858 } else {
2859 clc.onlyVisibleClients = 0;
2860 }
2861 clc.state = CA_CHALLENGING;
2862 clc.connectPacketCount = 0;
2863 clc.connectTime = -99999;
2864
2865 // take this address as the new server address. This allows
2866 // a server proxy to hand off connections to multiple servers
2867 clc.serverAddress = from;
2868 Com_DPrintf ("challengeResponse: %d\n", clc.challenge);
2869 return;
2870 }
2871
2872 // server connection
2873 if ( !Q_stricmp( c, "connectResponse" ) ) {
2874 if ( clc.state >= CA_CONNECTED ) {
2875 Com_Printf( "Dup connect received. Ignored.\n" );
2876 return;
2877 }
2878 if ( clc.state != CA_CHALLENGING ) {
2879 Com_Printf( "connectResponse packet while not connecting. Ignored.\n" );
2880 return;
2881 }
2882 if ( !NET_CompareAdr( from, clc.serverAddress ) ) {
2883 Com_Printf( "connectResponse from wrong address. Ignored.\n" );
2884 return;
2885 }
2886
2887 #ifdef LEGACY_PROTOCOL
2888 if(!clc.compat)
2889 #endif
2890 {
2891 c = Cmd_Argv(1);
2892
2893 if(*c)
2894 challenge = atoi(c);
2895 else
2896 {
2897 Com_Printf("Bad connectResponse received. Ignored.\n");
2898 return;
2899 }
2900
2901 if(challenge != clc.challenge)
2902 {
2903 Com_Printf("ConnectResponse with bad challenge received. Ignored.\n");
2904 return;
2905 }
2906 }
2907
2908 // DHM - Nerve :: If we have completed a connection to the Auto-Update server...
2909 if ( autoupdateChecked && NET_CompareAdr( cls.autoupdateServer, clc.serverAddress ) ) {
2910 // Mark the client as being in the process of getting an update
2911 if ( cl_updateavailable->integer ) {
2912 autoupdateStarted = qtrue;
2913 }
2914 }
2915
2916 #ifdef LEGACY_PROTOCOL
2917 Netchan_Setup(NS_CLIENT, &clc.netchan, from, Cvar_VariableValue("net_qport"),
2918 clc.challenge, clc.compat);
2919 #else
2920 Netchan_Setup(NS_CLIENT, &clc.netchan, from, Cvar_VariableValue("net_qport"),
2921 clc.challenge, qfalse);
2922 #endif
2923
2924 clc.state = CA_CONNECTED;
2925 clc.lastPacketSentTime = -9999; // send first packet immediately
2926 return;
2927 }
2928
2929 // server responding to an info broadcast
2930 if ( !Q_stricmp( c, "infoResponse" ) ) {
2931 CL_ServerInfoPacket( from, msg );
2932 return;
2933 }
2934
2935 // server responding to a get playerlist
2936 if ( !Q_stricmp( c, "statusResponse" ) ) {
2937 CL_ServerStatusResponse( from, msg );
2938 return;
2939 }
2940
2941 // echo request from server
2942 if ( !Q_stricmp( c, "echo" ) ) {
2943 #ifdef UPDATE_SERVER
2944 NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv( 1 ) );
2945 #else
2946 // NOTE: we may have to add exceptions for auth and update servers
2947 if ( NET_CompareAdr( from, clc.serverAddress ) || NET_CompareAdr( from, cls.rconAddress ) ) {
2948 NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv(1) );
2949 }
2950 #endif
2951 return;
2952 }
2953
2954 // cd check
2955 if ( !Q_stricmp( c, "keyAuthorize" ) ) {
2956 // we don't use these now, so dump them on the floor
2957 return;
2958 }
2959
2960 // global MOTD from id
2961 if ( !Q_stricmp( c, "motd" ) ) {
2962 CL_MotdPacket( from );
2963 return;
2964 }
2965
2966 // echo request from server
2967 if ( !Q_stricmp( c, "print" ) ) {
2968 #ifdef UPDATE_SERVER
2969 s = MSG_ReadString( msg );
2970
2971 Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) );
2972 Com_Printf( "%s", s );
2973 #else
2974 // NOTE: we may have to add exceptions for auth and update servers
2975 if ( NET_CompareAdr( from, clc.serverAddress ) || NET_CompareAdr( from, cls.rconAddress ) ) {
2976 s = MSG_ReadString( msg );
2977
2978 Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) );
2979 Com_Printf( "%s", s );
2980 }
2981 #endif
2982 return;
2983 }
2984
2985 // DHM - Nerve :: Auto-update server response message
2986 if ( !Q_stricmp( c, "updateResponse" ) ) {
2987 CL_UpdateInfoPacket( from );
2988 return;
2989 }
2990
2991 // list of servers sent back by a master server (classic)
2992 if ( !Q_strncmp( c, "getserversResponse", 18 ) ) {
2993 CL_ServersResponsePacket( &from, msg, qfalse );
2994 return;
2995 }
2996
2997 // list of servers sent back by a master server (extended)
2998 if ( !Q_strncmp(c, "getserversExtResponse", 21) ) {
2999 CL_ServersResponsePacket( &from, msg, qtrue );
3000 return;
3001 }
3002
3003 Com_DPrintf( "Unknown connectionless packet command.\n" );
3004 }
3005
3006 /*
3007 =================
3008 CL_PacketEvent
3009
3010 A packet has arrived from the main event loop
3011 =================
3012 */
CL_PacketEvent(netadr_t from,msg_t * msg)3013 void CL_PacketEvent( netadr_t from, msg_t *msg ) {
3014 int headerBytes;
3015
3016 clc.lastPacketTime = cls.realtime;
3017
3018 if ( msg->cursize >= 4 && *(int *)msg->data == -1 ) {
3019 CL_ConnectionlessPacket( from, msg );
3020 return;
3021 }
3022
3023 if ( clc.state < CA_CONNECTED ) {
3024 return; // can't be a valid sequenced packet
3025 }
3026
3027 if ( msg->cursize < 4 ) {
3028 Com_Printf ("%s: Runt packet\n", NET_AdrToStringwPort( from ));
3029 return;
3030 }
3031
3032 //
3033 // packet from server
3034 //
3035 if ( !NET_CompareAdr( from, clc.netchan.remoteAddress ) ) {
3036 Com_DPrintf( "%s:sequenced packet without connection\n"
3037 , NET_AdrToStringwPort( from ) );
3038 // FIXME: send a client disconnect?
3039 return;
3040 }
3041
3042 if ( !CL_Netchan_Process( &clc.netchan, msg ) ) {
3043 return; // out of order, duplicated, etc
3044 }
3045
3046 // the header is different lengths for reliable and unreliable messages
3047 headerBytes = msg->readcount;
3048
3049 // track the last message received so it can be returned in
3050 // client messages, allowing the server to detect a dropped
3051 // gamestate
3052 clc.serverMessageSequence = LittleLong( *(int *)msg->data );
3053
3054 clc.lastPacketTime = cls.realtime;
3055 CL_ParseServerMessage( msg );
3056
3057 //
3058 // we don't know if it is ok to save a demo message until
3059 // after we have parsed the frame
3060 //
3061 if ( clc.demorecording && !clc.demowaiting ) {
3062 CL_WriteDemoMessage( msg, headerBytes );
3063 }
3064 }
3065
3066 /*
3067 ==================
3068 CL_CheckTimeout
3069
3070 ==================
3071 */
CL_CheckTimeout(void)3072 void CL_CheckTimeout( void ) {
3073 //
3074 // check timeout
3075 //
3076 if ( ( !CL_CheckPaused() || !sv_paused->integer )
3077 && clc.state >= CA_CONNECTED && clc.state != CA_CINEMATIC
3078 && cls.realtime - clc.lastPacketTime > cl_timeout->value * 1000 ) {
3079 if ( ++cl.timeoutcount > 5 ) { // timeoutcount saves debugger
3080 Com_Printf( "\nServer connection timed out.\n" );
3081 CL_Disconnect( qtrue );
3082 return;
3083 }
3084 } else {
3085 cl.timeoutcount = 0;
3086 }
3087 }
3088
3089 /*
3090 ==================
3091 CL_CheckPaused
3092 Check whether client has been paused.
3093 ==================
3094 */
CL_CheckPaused(void)3095 qboolean CL_CheckPaused(void)
3096 {
3097 // if cl_paused->modified is set, the cvar has only been changed in
3098 // this frame. Keep paused in this frame to ensure the server doesn't
3099 // lag behind.
3100 if(cl_paused->integer || cl_paused->modified)
3101 return qtrue;
3102
3103 return qfalse;
3104 }
3105
3106
3107 //============================================================================
3108
3109 /*
3110 ==================
3111 CL_CheckUserinfo
3112
3113 ==================
3114 */
CL_CheckUserinfo(void)3115 void CL_CheckUserinfo( void ) {
3116 // don't add reliable commands when not yet connected
3117 if(clc.state < CA_CONNECTED)
3118 return;
3119
3120 // don't overflow the reliable command buffer when paused
3121 if(CL_CheckPaused())
3122 return;
3123
3124 // send a reliable userinfo update if needed
3125 if ( cvar_modifiedFlags & CVAR_USERINFO ) {
3126 cvar_modifiedFlags &= ~CVAR_USERINFO;
3127 CL_AddReliableCommand(va("userinfo \"%s\"", Cvar_InfoString( CVAR_USERINFO ) ), qfalse);
3128 }
3129 }
3130
3131 /*
3132 ==================
3133 CL_Frame
3134
3135 ==================
3136 */
CL_Frame(int msec)3137 void CL_Frame( int msec ) {
3138
3139 if ( !com_cl_running->integer ) {
3140 return;
3141 }
3142
3143 #ifdef USE_CURL
3144 if(clc.downloadCURLM) {
3145 CL_cURL_PerformDownload();
3146 // we can't process frames normally when in disconnected
3147 // download mode since the ui vm expects clc.state to be
3148 // CA_CONNECTED
3149 if(clc.cURLDisconnected) {
3150 cls.realFrametime = msec;
3151 cls.frametime = msec;
3152 cls.realtime += cls.frametime;
3153 SCR_UpdateScreen();
3154 S_Update();
3155 Con_RunConsole();
3156 cls.framecount++;
3157 return;
3158 }
3159 }
3160 #endif
3161
3162 if ( cls.cddialog ) {
3163 // bring up the cd error dialog if needed
3164 cls.cddialog = qfalse;
3165 VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NEED_CD );
3166 } else if ( clc.state == CA_DISCONNECTED && !( Key_GetCatcher( ) & KEYCATCH_UI )
3167 && !com_sv_running->integer && uivm ) {
3168 // if disconnected, bring up the menu
3169 S_StopAllSounds();
3170 VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN );
3171 }
3172
3173 // if recording an avi, lock to a fixed fps
3174 if ( ( CL_VideoRecording( ) && cl_aviFrameRate->integer && msec ) || ( cl_avidemo->integer && msec ) ) {
3175 // save the current screen
3176 if ( clc.state == CA_ACTIVE || cl_forceavidemo->integer ) {
3177 if ( cl_avidemo->integer ) { // Legacy (screenshot) method
3178 Cbuf_ExecuteText( EXEC_NOW, "screenshot silent\n" );
3179
3180 // fixed time for next frame
3181 msec = ( 1000 / cl_avidemo->integer ) * com_timescale->value;
3182 if ( msec == 0 ) {
3183 msec = 1;
3184 }
3185 } else { // ioquake3 method
3186 float fps = MIN(cl_aviFrameRate->value * com_timescale->value, 1000.0f);
3187 float frameDuration = MAX(1000.0f / fps, 1.0f) + clc.aviVideoFrameRemainder;
3188
3189 CL_TakeVideoFrame( );
3190
3191 msec = (int)frameDuration;
3192 clc.aviVideoFrameRemainder = frameDuration - msec;
3193 }
3194 }
3195 }
3196
3197 if( cl_autoRecordDemo->integer ) {
3198 if( clc.state == CA_ACTIVE && !clc.demorecording && !clc.demoplaying ) {
3199 // If not recording a demo, and we should be, start one
3200 qtime_t now;
3201 char *nowString;
3202 char *p;
3203 char mapName[ MAX_QPATH ];
3204 char serverName[ MAX_OSPATH ];
3205
3206 Com_RealTime( &now );
3207 nowString = va( "%04d%02d%02d%02d%02d%02d",
3208 1900 + now.tm_year,
3209 1 + now.tm_mon,
3210 now.tm_mday,
3211 now.tm_hour,
3212 now.tm_min,
3213 now.tm_sec );
3214
3215 Q_strncpyz( serverName, clc.servername, MAX_OSPATH );
3216 // Replace the ":" in the address as it is not a valid
3217 // file name character
3218 p = strstr( serverName, ":" );
3219 if( p ) {
3220 *p = '.';
3221 }
3222
3223 Q_strncpyz( mapName, COM_SkipPath( cl.mapname ), sizeof( cl.mapname ) );
3224 COM_StripExtension(mapName, mapName, sizeof(mapName));
3225
3226 Cbuf_ExecuteText( EXEC_NOW,
3227 va( "record %s-%s-%s", nowString, serverName, mapName ) );
3228 }
3229 else if( clc.state != CA_ACTIVE && clc.demorecording ) {
3230 // Recording, but not CA_ACTIVE, so stop recording
3231 CL_StopRecord_f( );
3232 }
3233 }
3234
3235 // save the msec before checking pause
3236 cls.realFrametime = msec;
3237
3238 // decide the simulation time
3239 cls.frametime = msec;
3240
3241 cls.realtime += cls.frametime;
3242
3243 if ( cl_timegraph->integer ) {
3244 SCR_DebugGraph ( cls.realFrametime * 0.25 );
3245 }
3246
3247 // see if we need to update any userinfo
3248 CL_CheckUserinfo();
3249
3250 // if we haven't gotten a packet in a long time,
3251 // drop the connection
3252 CL_CheckTimeout();
3253
3254 // send intentions now
3255 CL_SendCmd();
3256
3257 // resend a connection request if necessary
3258 CL_CheckForResend();
3259
3260 // decide on the serverTime to render
3261 CL_SetCGameTime();
3262
3263 // update the screen
3264 SCR_UpdateScreen();
3265
3266 // update audio
3267 S_Update();
3268
3269 #ifdef USE_VOIP
3270 CL_CaptureVoip();
3271 #endif
3272
3273 #ifdef USE_MUMBLE
3274 CL_UpdateMumble();
3275 #endif
3276
3277 // advance local effects for next frame
3278 SCR_RunCinematic();
3279
3280 Con_RunConsole();
3281
3282 cls.framecount++;
3283 }
3284
3285
3286 //============================================================================
3287 // Ridah, startup-caching system
3288 typedef struct {
3289 char name[MAX_QPATH];
3290 int hits;
3291 int lastSetIndex;
3292 } cacheItem_t;
3293 typedef enum {
3294 CACHE_SOUNDS,
3295 CACHE_MODELS,
3296 CACHE_IMAGES,
3297
3298 CACHE_NUMGROUPS
3299 } cacheGroup_t;
3300 static cacheItem_t cacheGroups[CACHE_NUMGROUPS] = {
3301 {{'s','o','u','n','d',0}, CACHE_SOUNDS},
3302 {{'m','o','d','e','l',0}, CACHE_MODELS},
3303 {{'i','m','a','g','e',0}, CACHE_IMAGES},
3304 };
3305 #define MAX_CACHE_ITEMS 4096
3306 #define CACHE_HIT_RATIO 0.75 // if hit on this percentage of maps, it'll get cached
3307
3308 static int cacheIndex;
3309 static cacheItem_t cacheItems[CACHE_NUMGROUPS][MAX_CACHE_ITEMS];
3310
CL_Cache_StartGather_f(void)3311 static void CL_Cache_StartGather_f( void ) {
3312 cacheIndex = 0;
3313 memset( cacheItems, 0, sizeof( cacheItems ) );
3314
3315 Cvar_Set( "cl_cacheGathering", "1" );
3316 }
3317
CL_Cache_UsedFile_f(void)3318 static void CL_Cache_UsedFile_f( void ) {
3319 char groupStr[MAX_QPATH];
3320 char itemStr[MAX_QPATH];
3321 int i,group;
3322 cacheItem_t *item;
3323
3324 if ( Cmd_Argc() < 2 ) {
3325 Com_Error( ERR_DROP, "usedfile without enough parameters\n" );
3326 return;
3327 }
3328
3329 strcpy( groupStr, Cmd_Argv( 1 ) );
3330
3331 strcpy( itemStr, Cmd_Argv( 2 ) );
3332 for ( i = 3; i < Cmd_Argc(); i++ ) {
3333 strcat( itemStr, " " );
3334 strcat( itemStr, Cmd_Argv( i ) );
3335 }
3336 Q_strlwr( itemStr );
3337
3338 // find the cache group
3339 for ( i = 0; i < CACHE_NUMGROUPS; i++ ) {
3340 if ( !Q_strncmp( groupStr, cacheGroups[i].name, MAX_QPATH ) ) {
3341 break;
3342 }
3343 }
3344 if ( i == CACHE_NUMGROUPS ) {
3345 Com_Error( ERR_DROP, "usedfile without a valid cache group\n" );
3346 return;
3347 }
3348
3349 // see if it's already there
3350 group = i;
3351 for ( i = 0, item = cacheItems[group]; i < MAX_CACHE_ITEMS; i++, item++ ) {
3352 if ( !item->name[0] ) {
3353 // didn't find it, so add it here
3354 Q_strncpyz( item->name, itemStr, MAX_QPATH );
3355 if ( cacheIndex > 9999 ) { // hack, but yeh
3356 item->hits = cacheIndex;
3357 } else {
3358 item->hits++;
3359 }
3360 item->lastSetIndex = cacheIndex;
3361 break;
3362 }
3363 if ( item->name[0] == itemStr[0] && !Q_strncmp( item->name, itemStr, MAX_QPATH ) ) {
3364 if ( item->lastSetIndex != cacheIndex ) {
3365 item->hits++;
3366 item->lastSetIndex = cacheIndex;
3367 }
3368 break;
3369 }
3370 }
3371 }
3372
CL_Cache_SetIndex_f(void)3373 static void CL_Cache_SetIndex_f( void ) {
3374 if ( Cmd_Argc() < 2 ) {
3375 Com_Error( ERR_DROP, "setindex needs an index\n" );
3376 return;
3377 }
3378
3379 cacheIndex = atoi( Cmd_Argv( 1 ) );
3380 }
3381
CL_Cache_MapChange_f(void)3382 static void CL_Cache_MapChange_f( void ) {
3383 cacheIndex++;
3384 }
3385
CL_Cache_EndGather_f(void)3386 static void CL_Cache_EndGather_f( void ) {
3387 // save the frequently used files to the cache list file
3388 int i, j, handle, cachePass;
3389 char filename[MAX_QPATH];
3390
3391 cachePass = (int)floor( (float)cacheIndex * CACHE_HIT_RATIO );
3392
3393 for ( i = 0; i < CACHE_NUMGROUPS; i++ ) {
3394 Q_strncpyz( filename, cacheGroups[i].name, MAX_QPATH );
3395 Q_strcat( filename, MAX_QPATH, ".cache" );
3396
3397 handle = FS_FOpenFileWrite( filename );
3398
3399 for ( j = 0; j < MAX_CACHE_ITEMS; j++ ) {
3400 // if it's a valid filename, and it's been hit enough times, cache it
3401 if ( cacheItems[i][j].hits >= cachePass && strstr( cacheItems[i][j].name, "/" ) ) {
3402 FS_Write( cacheItems[i][j].name, strlen( cacheItems[i][j].name ), handle );
3403 FS_Write( "\n", 1, handle );
3404 }
3405 }
3406
3407 FS_FCloseFile( handle );
3408 }
3409
3410 Cvar_Set( "cl_cacheGathering", "0" );
3411 }
3412
3413 // done.
3414 //============================================================================
3415
3416 /*
3417 ================
3418 CL_SetRecommended_f
3419 ================
3420 */
CL_SetRecommended_f(void)3421 void CL_SetRecommended_f( void ) {
3422 Com_SetRecommended();
3423 }
3424
3425 /*
3426 ================
3427 CL_RefPrintf
3428
3429 DLL glue
3430 ================
3431 */
CL_RefPrintf(int print_level,const char * fmt,...)3432 static __attribute__ ((format (printf, 2, 3))) void QDECL CL_RefPrintf( int print_level, const char *fmt, ...) {
3433 va_list argptr;
3434 char msg[MAXPRINTMSG];
3435
3436 va_start( argptr,fmt );
3437 Q_vsnprintf( msg, sizeof ( msg ), fmt, argptr );
3438 va_end( argptr );
3439
3440 if ( print_level == PRINT_ALL ) {
3441 Com_Printf( "%s", msg );
3442 } else if ( print_level == PRINT_WARNING ) {
3443 Com_Printf( S_COLOR_YELLOW "%s", msg ); // yellow
3444 } else if ( print_level == PRINT_DEVELOPER ) {
3445 Com_DPrintf( S_COLOR_RED "%s", msg ); // red
3446 }
3447 }
3448
3449 /*
3450 ============
3451 CL_ShutdownRef
3452 ============
3453 */
CL_ShutdownRef(void)3454 void CL_ShutdownRef( void ) {
3455 if ( re.Shutdown ) {
3456 re.Shutdown( qtrue );
3457 }
3458
3459 memset( &re, 0, sizeof( re ) );
3460
3461 #ifdef USE_RENDERER_DLOPEN
3462 if ( rendererLib ) {
3463 Sys_UnloadLibrary( rendererLib );
3464 rendererLib = NULL;
3465 }
3466 #endif
3467 }
3468
3469 /*
3470 ============
3471 CL_InitRenderer
3472 ============
3473 */
CL_InitRenderer(void)3474 void CL_InitRenderer( void ) {
3475 // this sets up the renderer and calls R_Init
3476 re.BeginRegistration( &cls.glconfig );
3477
3478 // load character sets
3479 cls.charSetShader = re.RegisterShader( "gfx/2d/hudchars" );
3480 cls.whiteShader = re.RegisterShader( "white" );
3481 cls.consoleShader = re.RegisterShader( "console-16bit" ); // JPW NERVE shader works with 16bit
3482 cls.consoleShader2 = re.RegisterShader( "console2-16bit" ); // JPW NERVE same
3483 g_console_field_width = cls.glconfig.vidWidth / SMALLCHAR_WIDTH - 2;
3484 g_consoleField.widthInChars = g_console_field_width;
3485 }
3486
3487 /*
3488 ============================
3489 CL_StartHunkUsers
3490
3491 After the server has cleared the hunk, these will need to be restarted
3492 This is the only place that any of these functions are called from
3493 ============================
3494 */
CL_StartHunkUsers(qboolean rendererOnly)3495 void CL_StartHunkUsers( qboolean rendererOnly ) {
3496 if ( !com_cl_running ) {
3497 return;
3498 }
3499
3500 if ( !com_cl_running->integer ) {
3501 return;
3502 }
3503
3504 if ( !cls.rendererStarted ) {
3505 cls.rendererStarted = qtrue;
3506 CL_InitRenderer();
3507 }
3508
3509 if ( rendererOnly ) {
3510 return;
3511 }
3512
3513 if ( !cls.soundStarted ) {
3514 cls.soundStarted = qtrue;
3515 S_Init();
3516 }
3517
3518 if ( !cls.soundRegistered ) {
3519 cls.soundRegistered = qtrue;
3520 S_BeginRegistration();
3521 }
3522
3523 if( com_dedicated->integer ) {
3524 return;
3525 }
3526
3527 if ( !cls.uiStarted ) {
3528 cls.uiStarted = qtrue;
3529 CL_InitUI();
3530 }
3531 }
3532
CL_ScaledMilliseconds(void)3533 int CL_ScaledMilliseconds( void ) {
3534 return Sys_Milliseconds() * com_timescale->value;
3535 }
3536
3537 // DHM - Nerve
CL_CheckAutoUpdate(void)3538 void CL_CheckAutoUpdate( void ) {
3539 int validServerNum = 0;
3540 int i = 0, rnd = 0;
3541 netadr_t temp;
3542 char *servername;
3543
3544 if ( !cl_autoupdate->integer ) {
3545 return;
3546 }
3547
3548 // Only check once per session
3549 if ( autoupdateChecked ) {
3550 return;
3551 }
3552
3553 srand( Com_Milliseconds() );
3554
3555 // Find out how many update servers have valid DNS listings
3556 for ( i = 0; i < MAX_AUTOUPDATE_SERVERS; i++ ) {
3557 if ( NET_StringToAdr( cls.autoupdateServerNames[i], &temp, NA_UNSPEC ) ) {
3558 validServerNum++;
3559 }
3560 }
3561
3562 // Pick a random server
3563 if ( validServerNum > 1 ) {
3564 rnd = rand() % validServerNum;
3565 } else {
3566 rnd = 0;
3567 }
3568
3569 servername = cls.autoupdateServerNames[rnd];
3570
3571 Com_DPrintf( "Resolving AutoUpdate Server... " );
3572 if ( !NET_StringToAdr( servername, &cls.autoupdateServer, NA_UNSPEC ) ) {
3573 Com_DPrintf( "Couldn't resolve first address, trying default..." );
3574
3575 // Fall back to the first one
3576 if ( !NET_StringToAdr( cls.autoupdateServerNames[0], &cls.autoupdateServer, NA_UNSPEC ) ) {
3577 Com_DPrintf( "Failed to resolve any Auto-update servers.\n" );
3578 autoupdateChecked = qtrue;
3579 return;
3580 }
3581 }
3582 cls.autoupdateServer.port = BigShort( PORT_SERVER );
3583 Com_DPrintf( "%i.%i.%i.%i:%i\n", cls.autoupdateServer.ip[0], cls.autoupdateServer.ip[1],
3584 cls.autoupdateServer.ip[2], cls.autoupdateServer.ip[3],
3585 BigShort( cls.autoupdateServer.port ) );
3586
3587 NET_OutOfBandPrint( NS_CLIENT, cls.autoupdateServer, "getUpdateInfo \"%s\" \"%s\"-\"%s\"\n", Q3_VERSION, OS_STRING, ARCH_STRING );
3588
3589 CL_RequestMotd();
3590
3591 autoupdateChecked = qtrue;
3592 }
3593
CL_GetAutoUpdate(void)3594 void CL_GetAutoUpdate( void ) {
3595
3596 // Don't try and get an update if we haven't checked for one
3597 if ( !autoupdateChecked ) {
3598 return;
3599 }
3600
3601 // Make sure there's a valid update file to request
3602 if ( strlen( cl_updatefiles->string ) < 5 ) {
3603 return;
3604 }
3605
3606 Com_DPrintf( "Connecting to auto-update server...\n" );
3607
3608 S_StopAllSounds(); // NERVE - SMF
3609
3610 // starting to load a map so we get out of full screen ui mode
3611 Cvar_Set( "r_uiFullScreen", "0" );
3612
3613 // clear any previous "server full" type messages
3614 clc.serverMessage[0] = 0;
3615
3616 if ( com_sv_running->integer ) {
3617 // if running a local server, kill it
3618 SV_Shutdown( "Server quit\n" );
3619 }
3620
3621 // make sure a local server is killed
3622 Cvar_Set( "sv_killserver", "1" );
3623 SV_Frame( 0 );
3624
3625 CL_Disconnect( qtrue );
3626 Con_Close();
3627
3628 Q_strncpyz( clc.servername, "Auto-Updater", sizeof( clc.servername ) );
3629
3630 if ( cls.autoupdateServer.type == NA_BAD ) {
3631 Com_Printf( "Bad server address\n" );
3632 clc.state = CA_DISCONNECTED;
3633 return;
3634 }
3635
3636 // Copy auto-update server address to Server connect address
3637 memcpy( &clc.serverAddress, &cls.autoupdateServer, sizeof( netadr_t ) );
3638
3639 Com_DPrintf( "%s resolved to %i.%i.%i.%i:%i\n", clc.servername,
3640 clc.serverAddress.ip[0], clc.serverAddress.ip[1],
3641 clc.serverAddress.ip[2], clc.serverAddress.ip[3],
3642 BigShort( clc.serverAddress.port ) );
3643
3644 clc.state = CA_CONNECTING;
3645
3646 Key_SetCatcher( 0 );
3647 clc.connectTime = -99999; // CL_CheckForResend() will fire immediately
3648 clc.connectPacketCount = 0;
3649
3650 // server connection string
3651 Cvar_Set( "cl_currentServerAddress", "Auto-Updater" );
3652 }
3653 // DHM - Nerve
3654
3655 /*
3656 ============
3657 CL_RefMalloc
3658 ============
3659 */
3660 #ifdef ZONE_DEBUG
CL_RefMallocDebug(int size,char * label,char * file,int line)3661 void *CL_RefMallocDebug( int size, char *label, char *file, int line ) {
3662 return Z_TagMallocDebug( size, TAG_RENDERER, label, file, line );
3663 }
3664 #else
CL_RefMalloc(int size)3665 void *CL_RefMalloc( int size ) {
3666 return Z_TagMalloc( size, TAG_RENDERER );
3667 }
3668 #endif
3669
3670 /*
3671 ============
3672 CL_RefTagFree
3673 ============
3674 */
CL_RefTagFree(void)3675 void CL_RefTagFree( void ) {
3676 Z_FreeTags( TAG_RENDERER );
3677 return;
3678 }
3679
3680 /*
3681 ============
3682 CL_InitRef
3683 ============
3684 */
CL_InitRef(void)3685 void CL_InitRef( void ) {
3686 refimport_t ri;
3687 refexport_t *ret;
3688 #ifdef USE_RENDERER_DLOPEN
3689 GetRefAPI_t GetRefAPI;
3690 char dllName[MAX_OSPATH];
3691 #endif
3692
3693 Com_Printf( "----- Initializing Renderer ----\n" );
3694
3695 #ifdef USE_RENDERER_DLOPEN
3696 cl_renderer = Cvar_Get("cl_renderer", "opengl1", CVAR_ARCHIVE | CVAR_LATCH);
3697
3698 Com_sprintf(dllName, sizeof(dllName), "renderer_mp_%s_" ARCH_STRING DLL_EXT, cl_renderer->string);
3699
3700 if(!(rendererLib = Sys_LoadDll(dllName, qfalse)) && strcmp(cl_renderer->string, cl_renderer->resetString))
3701 {
3702 Com_Printf("failed:\n\"%s\"\n", Sys_LibraryError());
3703 Cvar_ForceReset("cl_renderer");
3704
3705 Com_sprintf(dllName, sizeof(dllName), "renderer_mp_opengl1_" ARCH_STRING DLL_EXT);
3706 rendererLib = Sys_LoadDll(dllName, qfalse);
3707 }
3708
3709 if(!rendererLib)
3710 {
3711 Com_Printf("failed:\n\"%s\"\n", Sys_LibraryError());
3712 Com_Error(ERR_FATAL, "Failed to load renderer");
3713 }
3714
3715 GetRefAPI = Sys_LoadFunction(rendererLib, "GetRefAPI");
3716 if(!GetRefAPI)
3717 {
3718 Com_Error(ERR_FATAL, "Can't load symbol GetRefAPI: '%s'", Sys_LibraryError());
3719 }
3720 #endif
3721
3722 ri.Cmd_AddCommand = Cmd_AddCommand;
3723 ri.Cmd_RemoveCommand = Cmd_RemoveCommand;
3724 ri.Cmd_Argc = Cmd_Argc;
3725 ri.Cmd_Argv = Cmd_Argv;
3726 ri.Cmd_ExecuteText = Cbuf_ExecuteText;
3727 ri.Printf = CL_RefPrintf;
3728 ri.Error = Com_Error;
3729 ri.Milliseconds = CL_ScaledMilliseconds;
3730 #ifdef ZONE_DEBUG
3731 ri.Z_MallocDebug = CL_RefMallocDebug;
3732 #else
3733 ri.Z_Malloc = CL_RefMalloc;
3734 #endif
3735 ri.Free = Z_Free;
3736 ri.Tag_Free = CL_RefTagFree;
3737 ri.Hunk_Clear = Hunk_ClearToMark;
3738 #ifdef HUNK_DEBUG
3739 ri.Hunk_AllocDebug = Hunk_AllocDebug;
3740 #else
3741 ri.Hunk_Alloc = Hunk_Alloc;
3742 #endif
3743 ri.Hunk_AllocateTempMemory = Hunk_AllocateTempMemory;
3744 ri.Hunk_FreeTempMemory = Hunk_FreeTempMemory;
3745
3746 ri.CM_ClusterPVS = CM_ClusterPVS;
3747 ri.CM_DrawDebugSurface = CM_DrawDebugSurface;
3748 ri.FS_ReadFile = FS_ReadFile;
3749 ri.FS_FreeFile = FS_FreeFile;
3750 ri.FS_WriteFile = FS_WriteFile;
3751 ri.FS_FreeFileList = FS_FreeFileList;
3752 ri.FS_ListFiles = FS_ListFiles;
3753 ri.FS_FileIsInPAK = FS_FileIsInPAK;
3754 ri.FS_FileExists = FS_FileExists;
3755 ri.Cvar_Get = Cvar_Get;
3756 ri.Cvar_Set = Cvar_Set;
3757 ri.Cvar_SetValue = Cvar_SetValue;
3758 ri.Cvar_CheckRange = Cvar_CheckRange;
3759 ri.Cvar_VariableIntegerValue = Cvar_VariableIntegerValue;
3760
3761 // cinematic stuff
3762
3763 ri.CIN_UploadCinematic = CIN_UploadCinematic;
3764 ri.CIN_PlayCinematic = CIN_PlayCinematic;
3765 ri.CIN_RunCinematic = CIN_RunCinematic;
3766
3767 ri.CL_WriteAVIVideoFrame = CL_WriteAVIVideoFrame;
3768
3769 ri.IN_Init = IN_Init;
3770 ri.IN_Shutdown = IN_Shutdown;
3771 ri.IN_Restart = IN_Restart;
3772
3773 ri.ftol = Q_ftol;
3774
3775 ri.Sys_SetEnv = Sys_SetEnv;
3776 ri.Sys_GLimpSafeInit = Sys_GLimpSafeInit;
3777 ri.Sys_GLimpInit = Sys_GLimpInit;
3778 ri.Sys_LowPhysicalMemory = Sys_LowPhysicalMemory;
3779
3780 ret = GetRefAPI( REF_API_VERSION, &ri );
3781
3782 if ( !ret ) {
3783 Com_Error( ERR_FATAL, "Couldn't initialize refresh" );
3784 }
3785
3786 re = *ret;
3787
3788 Com_Printf( "---- Renderer Initialization Complete ----\n" );
3789
3790 // unpause so the cgame definately gets a snapshot and renders a frame
3791 Cvar_Set( "cl_paused", "0" );
3792 }
3793
3794 // RF, trap manual client damage commands so users can't issue them manually
CL_ClientDamageCommand(void)3795 void CL_ClientDamageCommand( void ) {
3796 // do nothing
3797 }
3798
3799 #if defined (__i386__)
3800 #define BIN_STRING "x86"
3801 #endif
3802
3803 // NERVE - SMF
CL_startSingleplayer_f(void)3804 void CL_startSingleplayer_f( void ) {
3805 char binName[MAX_OSPATH];
3806
3807 #if defined(_WIN64) || defined(__WIN64__)
3808 Com_sprintf(binName, sizeof(binName), "ioWolfSP." ARCH_STRING ".exe");
3809 Sys_StartProcess( binName, qtrue );
3810 #elif defined(_WIN32) || defined(__WIN32__)
3811 Com_sprintf(binName, sizeof(binName), "ioWolfSP." BIN_STRING ".exe");
3812 Sys_StartProcess( binName, qtrue );
3813 #elif defined(__i386__) && (!defined(_WIN32) || !defined(__WIN32__))
3814 Com_sprintf(binName, sizeof(binName), "./iowolfsp." BIN_STRING );
3815 Sys_StartProcess( binName, qtrue );
3816 #else
3817 Com_sprintf(binName, sizeof(binName), "./iowolfsp." ARCH_STRING );
3818 Sys_StartProcess( binName, qtrue );
3819 #endif
3820 }
3821
CL_SaveTranslations_f(void)3822 void CL_SaveTranslations_f( void ) {
3823 CL_SaveTransTable( "scripts/translation.cfg", qfalse );
3824 }
3825
CL_SaveNewTranslations_f(void)3826 void CL_SaveNewTranslations_f( void ) {
3827 char fileName[512];
3828
3829 if ( Cmd_Argc() != 2 ) {
3830 Com_Printf( "usage: SaveNewTranslations <filename>\n" );
3831 return;
3832 }
3833
3834 strcpy( fileName, va( "translations/%s.cfg", Cmd_Argv( 1 ) ) );
3835
3836 CL_SaveTransTable( fileName, qtrue );
3837 }
3838
CL_LoadTranslations_f(void)3839 void CL_LoadTranslations_f( void ) {
3840 CL_ReloadTranslation();
3841 }
3842 // -NERVE - SMF
3843
3844 //===========================================================================================
3845
3846
3847 /*
3848 ===============
3849 CL_Video_f
3850
3851 video
3852 video [filename]
3853 ===============
3854 */
CL_Video_f(void)3855 void CL_Video_f( void )
3856 {
3857 char filename[ MAX_OSPATH ];
3858 int i, last;
3859
3860 if( !clc.demoplaying )
3861 {
3862 Com_Printf( "The video command can only be used when playing back demos\n" );
3863 return;
3864 }
3865
3866 if( Cmd_Argc( ) == 2 )
3867 {
3868 // explicit filename
3869 Com_sprintf( filename, MAX_OSPATH, "videos/%s.avi", Cmd_Argv( 1 ) );
3870 }
3871 else
3872 {
3873 // scan for a free filename
3874 for( i = 0; i <= 9999; i++ )
3875 {
3876 int a, b, c, d;
3877
3878 last = i;
3879
3880 a = last / 1000;
3881 last -= a * 1000;
3882 b = last / 100;
3883 last -= b * 100;
3884 c = last / 10;
3885 last -= c * 10;
3886 d = last;
3887
3888 Com_sprintf( filename, MAX_OSPATH, "videos/video%d%d%d%d.avi", a, b, c, d );
3889
3890 if( !FS_FileExists( filename ) )
3891 break; // file doesn't exist
3892 }
3893
3894 if( i > 9999 )
3895 {
3896 Com_Printf( S_COLOR_RED "ERROR: no free file names to create video\n" );
3897 return;
3898 }
3899 }
3900
3901 CL_OpenAVIForWriting( filename );
3902 }
3903
3904 /*
3905 ===============
3906 CL_StopVideo_f
3907 ===============
3908 */
CL_StopVideo_f(void)3909 void CL_StopVideo_f( void )
3910 {
3911 CL_CloseAVI( );
3912 }
3913
3914 /*
3915 ===============
3916 CL_GenerateQKey
3917
3918 test to see if a valid QKEY_FILE exists. If one does not, try to generate
3919 it by filling it with 2048 bytes of random data.
3920 ===============
3921 */
CL_GenerateQKey(void)3922 static void CL_GenerateQKey(void)
3923 {
3924 int len = 0;
3925 unsigned char buff[ QKEY_SIZE ];
3926 fileHandle_t f;
3927
3928 len = FS_SV_FOpenFileRead( QKEY_FILE, &f );
3929 FS_FCloseFile( f );
3930 if( len == QKEY_SIZE ) {
3931 Com_Printf( "RTCWKEY found.\n" );
3932 return;
3933 }
3934 else {
3935 if( len > 0 ) {
3936 Com_Printf( "RTCWKEY file size != %d, regenerating\n",
3937 QKEY_SIZE );
3938 }
3939
3940 Com_Printf( "RTCWKEY building random string\n" );
3941 Com_RandomBytes( buff, sizeof(buff) );
3942
3943 f = FS_SV_FOpenFileWrite( QKEY_FILE );
3944 if( !f ) {
3945 Com_Printf( "RTCWKEY could not open %s for write\n",
3946 QKEY_FILE );
3947 return;
3948 }
3949 FS_Write( buff, sizeof(buff), f );
3950 FS_FCloseFile( f );
3951 Com_Printf( "RTCWKEY generated\n" );
3952 }
3953 }
3954
3955 /*
3956 ====================
3957 CL_Init
3958 ====================
3959 */
CL_Init(void)3960 void CL_Init( void ) {
3961 Com_Printf( "----- Client Initialization -----\n" );
3962
3963 Con_Init();
3964
3965 if(!com_fullyInitialized)
3966 {
3967 CL_ClearState();
3968 clc.state = CA_DISCONNECTED; // no longer CA_UNINITIALIZED
3969 cl_oldGameSet = qfalse;
3970 }
3971
3972 cls.realtime = 0;
3973
3974 CL_InitInput();
3975
3976 //
3977 // register our variables
3978 //
3979 cl_noprint = Cvar_Get( "cl_noprint", "0", 0 );
3980 #ifdef UPDATE_SERVER_NAME
3981 cl_motd = Cvar_Get( "cl_motd", "1", 0 );
3982 #endif
3983 cl_autoupdate = Cvar_Get( "cl_autoupdate", "0", CVAR_ARCHIVE );
3984
3985 cl_timeout = Cvar_Get( "cl_timeout", "200", 0 );
3986
3987 cl_wavefilerecord = Cvar_Get( "cl_wavefilerecord", "0", CVAR_TEMP );
3988
3989 cl_timeNudge = Cvar_Get( "cl_timeNudge", "0", CVAR_TEMP );
3990 cl_shownet = Cvar_Get( "cl_shownet", "0", CVAR_TEMP );
3991 cl_shownuments = Cvar_Get( "cl_shownuments", "0", CVAR_TEMP );
3992 cl_visibleClients = Cvar_Get( "cl_visibleClients", "0", CVAR_TEMP );
3993 cl_showServerCommands = Cvar_Get( "cl_showServerCommands", "0", 0 );
3994 cl_showSend = Cvar_Get( "cl_showSend", "0", CVAR_TEMP );
3995 cl_showTimeDelta = Cvar_Get( "cl_showTimeDelta", "0", CVAR_TEMP );
3996 cl_freezeDemo = Cvar_Get( "cl_freezeDemo", "0", CVAR_TEMP );
3997 rcon_client_password = Cvar_Get( "rconPassword", "", CVAR_TEMP );
3998 cl_activeAction = Cvar_Get( "activeAction", "", CVAR_TEMP );
3999
4000 cl_timedemo = Cvar_Get( "timedemo", "0", 0 );
4001 cl_timedemoLog = Cvar_Get ("cl_timedemoLog", "", CVAR_ARCHIVE);
4002 cl_autoRecordDemo = Cvar_Get ("cl_autoRecordDemo", "0", CVAR_ARCHIVE);
4003 cl_aviFrameRate = Cvar_Get ("cl_aviFrameRate", "25", CVAR_ARCHIVE);
4004 cl_aviMotionJpeg = Cvar_Get ("cl_aviMotionJpeg", "1", CVAR_ARCHIVE);
4005 cl_avidemo = Cvar_Get( "cl_avidemo", "0", 0 );
4006 cl_forceavidemo = Cvar_Get( "cl_forceavidemo", "0", 0 );
4007
4008 rconAddress = Cvar_Get( "rconAddress", "", 0 );
4009
4010 cl_yawspeed = Cvar_Get( "cl_yawspeed", "140", CVAR_ARCHIVE );
4011 cl_pitchspeed = Cvar_Get( "cl_pitchspeed", "140", CVAR_ARCHIVE );
4012 cl_anglespeedkey = Cvar_Get( "cl_anglespeedkey", "1.5", 0 );
4013
4014 cl_maxpackets = Cvar_Get( "cl_maxpackets", "38", CVAR_ARCHIVE );
4015 cl_packetdup = Cvar_Get( "cl_packetdup", "1", CVAR_ARCHIVE );
4016
4017 cl_showPing = Cvar_Get( "cl_showPing", "0", CVAR_ARCHIVE );
4018
4019 cl_run = Cvar_Get( "cl_run", "1", CVAR_ARCHIVE );
4020 cl_sensitivity = Cvar_Get( "sensitivity", "5", CVAR_ARCHIVE );
4021 cl_mouseAccel = Cvar_Get( "cl_mouseAccel", "0", CVAR_ARCHIVE );
4022 cl_freelook = Cvar_Get( "cl_freelook", "1", CVAR_ARCHIVE );
4023
4024 // 0: legacy mouse acceleration
4025 // 1: new implementation
4026 cl_mouseAccelStyle = Cvar_Get( "cl_mouseAccelStyle", "0", CVAR_ARCHIVE );
4027 // offset for the power function (for style 1, ignored otherwise)
4028 // this should be set to the max rate value
4029 cl_mouseAccelOffset = Cvar_Get( "cl_mouseAccelOffset", "5", CVAR_ARCHIVE );
4030 Cvar_CheckRange(cl_mouseAccelOffset, 0.001f, 50000.0f, qfalse);
4031
4032 cl_showMouseRate = Cvar_Get( "cl_showmouserate", "0", 0 );
4033
4034 cl_allowDownload = Cvar_Get( "cl_allowDownload", "1", CVAR_ARCHIVE );
4035 #ifdef USE_CURL_DLOPEN
4036 cl_cURLLib = Cvar_Get("cl_cURLLib", DEFAULT_CURL_LIB, CVAR_ARCHIVE | CVAR_PROTECTED);
4037 #endif
4038
4039 // init autoswitch so the ui will have it correctly even
4040 // if the cgame hasn't been started
4041 // -NERVE - SMF - disabled autoswitch by default
4042 Cvar_Get( "cg_autoswitch", "0", CVAR_ARCHIVE );
4043
4044 // Rafael - particle switch
4045 Cvar_Get( "cg_wolfparticles", "1", CVAR_ARCHIVE );
4046 // done
4047
4048 cl_conXOffset = Cvar_Get( "cl_conXOffset", "0", 0 );
4049 cl_inGameVideo = Cvar_Get( "r_inGameVideo", "1", CVAR_ARCHIVE );
4050
4051 cl_serverStatusResendTime = Cvar_Get( "cl_serverStatusResendTime", "750", 0 );
4052
4053 // RF
4054 cl_recoilPitch = Cvar_Get( "cg_recoilPitch", "0", CVAR_ROM );
4055
4056 cl_bypassMouseInput = Cvar_Get( "cl_bypassMouseInput", "0", 0 ); //CVAR_ROM ); // NERVE - SMF
4057
4058 m_pitch = Cvar_Get( "m_pitch", "0.022", CVAR_ARCHIVE );
4059 m_yaw = Cvar_Get( "m_yaw", "0.022", CVAR_ARCHIVE );
4060 m_forward = Cvar_Get( "m_forward", "0.25", CVAR_ARCHIVE );
4061 m_side = Cvar_Get( "m_side", "0.25", CVAR_ARCHIVE );
4062 m_filter = Cvar_Get( "m_filter", "0", CVAR_ARCHIVE );
4063
4064 j_pitch = Cvar_Get ("j_pitch", "0.022", CVAR_ARCHIVE);
4065 j_yaw = Cvar_Get ("j_yaw", "-0.022", CVAR_ARCHIVE);
4066 j_forward = Cvar_Get ("j_forward", "-0.25", CVAR_ARCHIVE);
4067 j_side = Cvar_Get ("j_side", "0.25", CVAR_ARCHIVE);
4068 j_up = Cvar_Get ("j_up", "0", CVAR_ARCHIVE);
4069
4070 j_pitch_axis = Cvar_Get ("j_pitch_axis", "3", CVAR_ARCHIVE);
4071 j_yaw_axis = Cvar_Get ("j_yaw_axis", "2", CVAR_ARCHIVE);
4072 j_forward_axis = Cvar_Get ("j_forward_axis", "1", CVAR_ARCHIVE);
4073 j_side_axis = Cvar_Get ("j_side_axis", "0", CVAR_ARCHIVE);
4074 j_up_axis = Cvar_Get ("j_up_axis", "4", CVAR_ARCHIVE);
4075
4076 Cvar_CheckRange(j_pitch_axis, 0, MAX_JOYSTICK_AXIS-1, qtrue);
4077 Cvar_CheckRange(j_yaw_axis, 0, MAX_JOYSTICK_AXIS-1, qtrue);
4078 Cvar_CheckRange(j_forward_axis, 0, MAX_JOYSTICK_AXIS-1, qtrue);
4079 Cvar_CheckRange(j_side_axis, 0, MAX_JOYSTICK_AXIS-1, qtrue);
4080 Cvar_CheckRange(j_up_axis, 0, MAX_JOYSTICK_AXIS-1, qtrue);
4081
4082 cl_motdString = Cvar_Get( "cl_motdString", "", CVAR_ROM );
4083
4084 Cvar_Get( "cl_maxPing", "800", CVAR_ARCHIVE );
4085
4086 cl_lanForcePackets = Cvar_Get ("cl_lanForcePackets", "1", CVAR_ARCHIVE);
4087
4088 cl_guid = Cvar_Get( "cl_guid", "unknown", CVAR_USERINFO | CVAR_ROM );
4089
4090 cl_guidServerUniq = Cvar_Get ("cl_guidServerUniq", "1", CVAR_ARCHIVE);
4091
4092 // ~ and `, as keys and characters
4093 cl_consoleKeys = Cvar_Get( "cl_consoleKeys", "~ ` 0x7e 0x60", CVAR_ARCHIVE);
4094
4095 // NERVE - SMF
4096 Cvar_Get( "cg_drawCompass", "1", CVAR_ARCHIVE );
4097 Cvar_Get( "cg_drawNotifyText", "1", CVAR_ARCHIVE );
4098 Cvar_Get( "cg_quickMessageAlt", "1", CVAR_ARCHIVE );
4099 Cvar_Get( "cg_popupLimboMenu", "1", CVAR_ARCHIVE );
4100 Cvar_Get( "cg_descriptiveText", "1", CVAR_ARCHIVE );
4101 Cvar_Get( "cg_drawTeamOverlay", "2", CVAR_ARCHIVE );
4102 Cvar_Get( "cg_uselessNostalgia", "0", CVAR_ARCHIVE ); // JPW NERVE
4103 Cvar_Get( "cg_drawGun", "1", CVAR_ARCHIVE );
4104 Cvar_Get( "cg_cursorHints", "1", CVAR_ARCHIVE );
4105 Cvar_Get( "cg_voiceSpriteTime", "6000", CVAR_ARCHIVE );
4106 Cvar_Get( "cg_teamChatsOnly", "0", CVAR_ARCHIVE );
4107 Cvar_Get( "cg_noVoiceChats", "0", CVAR_ARCHIVE );
4108 Cvar_Get( "cg_noVoiceText", "0", CVAR_ARCHIVE );
4109 Cvar_Get( "cg_crosshairSize", "48", CVAR_ARCHIVE );
4110 Cvar_Get( "cg_drawCrosshair", "1", CVAR_ARCHIVE );
4111 Cvar_Get( "cg_zoomDefaultSniper", "20", CVAR_ARCHIVE );
4112 Cvar_Get( "cg_zoomstepsniper", "2", CVAR_ARCHIVE );
4113
4114 Cvar_Get( "mp_playerType", "0", 0 );
4115 Cvar_Get( "mp_currentPlayerType", "0", 0 );
4116 Cvar_Get( "mp_weapon", "0", 0 );
4117 Cvar_Get( "mp_team", "0", 0 );
4118 Cvar_Get( "mp_currentTeam", "0", 0 );
4119 // -NERVE - SMF
4120
4121 // userinfo
4122 Cvar_Get( "name", "WolfPlayer", CVAR_USERINFO | CVAR_ARCHIVE );
4123 cl_rate = Cvar_Get( "rate", "25000", CVAR_USERINFO | CVAR_ARCHIVE ); // NERVE - SMF - changed from 3000
4124 Cvar_Get( "snaps", "20", CVAR_USERINFO | CVAR_ARCHIVE );
4125 Cvar_Get( "model", "multi", CVAR_USERINFO | CVAR_ARCHIVE );
4126 Cvar_Get( "head", "default", CVAR_USERINFO | CVAR_ARCHIVE );
4127 Cvar_Get( "color", "4", CVAR_USERINFO | CVAR_ARCHIVE );
4128 Cvar_Get( "handicap", "100", CVAR_USERINFO | CVAR_ARCHIVE );
4129 Cvar_Get( "sex", "male", CVAR_USERINFO | CVAR_ARCHIVE );
4130 Cvar_Get( "cl_anonymous", "0", CVAR_USERINFO | CVAR_ARCHIVE );
4131
4132 Cvar_Get( "password", "", CVAR_USERINFO );
4133 Cvar_Get( "cg_predictItems", "1", CVAR_USERINFO | CVAR_ARCHIVE );
4134
4135 #ifdef USE_MUMBLE
4136 cl_useMumble = Cvar_Get ("cl_useMumble", "0", CVAR_ARCHIVE | CVAR_LATCH);
4137 cl_mumbleScale = Cvar_Get ("cl_mumbleScale", "0.0254", CVAR_ARCHIVE);
4138 #endif
4139
4140 #ifdef USE_VOIP
4141 cl_voipSend = Cvar_Get ("cl_voipSend", "0", 0);
4142 cl_voipSendTarget = Cvar_Get ("cl_voipSendTarget", "spatial", 0);
4143 cl_voipGainDuringCapture = Cvar_Get ("cl_voipGainDuringCapture", "0.2", CVAR_ARCHIVE);
4144 cl_voipCaptureMult = Cvar_Get ("cl_voipCaptureMult", "2.0", CVAR_ARCHIVE);
4145 cl_voipUseVAD = Cvar_Get ("cl_voipUseVAD", "0", CVAR_ARCHIVE);
4146 cl_voipVADThreshold = Cvar_Get ("cl_voipVADThreshold", "0.25", CVAR_ARCHIVE);
4147 cl_voipShowMeter = Cvar_Get ("cl_voipShowMeter", "1", CVAR_ARCHIVE);
4148
4149 cl_voip = Cvar_Get ("cl_voip", "1", CVAR_ARCHIVE);
4150 Cvar_CheckRange( cl_voip, 0, 1, qtrue );
4151 cl_voipProtocol = Cvar_Get ("cl_voipProtocol", cl_voip->integer ? "opus" : "", CVAR_USERINFO | CVAR_ROM);
4152 #endif
4153
4154 //----(SA) added
4155 Cvar_Get( "cg_autoactivate", "1", CVAR_USERINFO | CVAR_ARCHIVE );
4156 //----(SA) end
4157
4158 // cgame might not be initialized before menu is used
4159 Cvar_Get( "cg_viewsize", "100", CVAR_ARCHIVE );
4160 // Make sure cg_stereoSeparation is zero as that variable is deprecated and should not be used anymore.
4161 Cvar_Get ("cg_stereoSeparation", "0", CVAR_ROM);
4162
4163 Cvar_Get( "cg_autoReload", "1", CVAR_ARCHIVE | CVAR_USERINFO );
4164
4165 cl_missionStats = Cvar_Get( "g_missionStats", "0", CVAR_ROM );
4166 cl_waitForFire = Cvar_Get( "cl_waitForFire", "0", CVAR_ROM );
4167
4168 // NERVE - SMF - localization
4169 cl_language = Cvar_Get( "cl_language", "0", CVAR_ARCHIVE );
4170 cl_debugTranslation = Cvar_Get( "cl_debugTranslation", "0", 0 );
4171 // -NERVE - SMF
4172
4173 // DHM - Nerve :: Auto-update
4174 cl_updateavailable = Cvar_Get( "cl_updateavailable", "0", CVAR_ROM );
4175 cl_updatefiles = Cvar_Get( "cl_updatefiles", "", CVAR_ROM );
4176
4177 Q_strncpyz( cls.autoupdateServerNames[0], AUTOUPDATE_SERVER1_NAME, MAX_QPATH );
4178 Q_strncpyz( cls.autoupdateServerNames[1], AUTOUPDATE_SERVER2_NAME, MAX_QPATH );
4179 Q_strncpyz( cls.autoupdateServerNames[2], AUTOUPDATE_SERVER3_NAME, MAX_QPATH );
4180 Q_strncpyz( cls.autoupdateServerNames[3], AUTOUPDATE_SERVER4_NAME, MAX_QPATH );
4181 Q_strncpyz( cls.autoupdateServerNames[4], AUTOUPDATE_SERVER5_NAME, MAX_QPATH );
4182 // DHM - Nerve
4183
4184 //
4185 // register our commands
4186 //
4187 Cmd_AddCommand( "cmd", CL_ForwardToServer_f );
4188 Cmd_AddCommand( "configstrings", CL_Configstrings_f );
4189 Cmd_AddCommand( "clientinfo", CL_Clientinfo_f );
4190 Cmd_AddCommand( "snd_restart", CL_Snd_Restart_f );
4191 Cmd_AddCommand( "vid_restart", CL_Vid_Restart_f );
4192 Cmd_AddCommand( "ui_restart", CL_UI_Restart_f ); // NERVE - SMF
4193 Cmd_AddCommand( "disconnect", CL_Disconnect_f );
4194 Cmd_AddCommand( "record", CL_Record_f );
4195 Cmd_AddCommand( "demo", CL_PlayDemo_f );
4196 Cmd_SetCommandCompletionFunc( "demo", CL_CompleteDemoName );
4197 Cmd_AddCommand( "cinematic", CL_PlayCinematic_f );
4198 Cmd_AddCommand( "stoprecord", CL_StopRecord_f );
4199 Cmd_AddCommand( "connect", CL_Connect_f );
4200 Cmd_AddCommand( "reconnect", CL_Reconnect_f );
4201 Cmd_AddCommand( "localservers", CL_LocalServers_f );
4202 Cmd_AddCommand( "globalservers", CL_GlobalServers_f );
4203 Cmd_AddCommand( "rcon", CL_Rcon_f );
4204 Cmd_SetCommandCompletionFunc( "rcon", CL_CompleteRcon );
4205 Cmd_AddCommand( "ping", CL_Ping_f );
4206 Cmd_AddCommand( "serverstatus", CL_ServerStatus_f );
4207 Cmd_AddCommand( "showip", CL_ShowIP_f );
4208 Cmd_AddCommand( "fs_openedList", CL_OpenedPK3List_f );
4209 Cmd_AddCommand( "fs_referencedList", CL_ReferencedPK3List_f );
4210 Cmd_AddCommand ("video", CL_Video_f );
4211 Cmd_AddCommand ("stopvideo", CL_StopVideo_f );
4212
4213 // Ridah, startup-caching system
4214 Cmd_AddCommand( "cache_startgather", CL_Cache_StartGather_f );
4215 Cmd_AddCommand( "cache_usedfile", CL_Cache_UsedFile_f );
4216 Cmd_AddCommand( "cache_setindex", CL_Cache_SetIndex_f );
4217 Cmd_AddCommand( "cache_mapchange", CL_Cache_MapChange_f );
4218 Cmd_AddCommand( "cache_endgather", CL_Cache_EndGather_f );
4219
4220 Cmd_AddCommand( "updatehunkusage", CL_UpdateLevelHunkUsage );
4221 Cmd_AddCommand( "updatescreen", SCR_UpdateScreen );
4222 // done.
4223 Cmd_AddCommand( "SaveTranslations", CL_SaveTranslations_f ); // NERVE - SMF - localization
4224 Cmd_AddCommand( "SaveNewTranslations", CL_SaveNewTranslations_f ); // NERVE - SMF - localization
4225 Cmd_AddCommand( "LoadTranslations", CL_LoadTranslations_f ); // NERVE - SMF - localization
4226 // NERVE - SMF - don't do this in multiplayer
4227 // RF, add this command so clients can't bind a key to send client damage commands to the server
4228 // Cmd_AddCommand( "cld", CL_ClientDamageCommand );
4229
4230 Cmd_AddCommand( "startSingleplayer", CL_startSingleplayer_f ); // NERVE - SMF
4231
4232 Cmd_AddCommand( "setRecommended", CL_SetRecommended_f );
4233
4234 CL_InitRef();
4235
4236 SCR_Init();
4237
4238 // Cbuf_Execute();
4239
4240 Cvar_Set( "cl_running", "1" );
4241
4242 // DHM - Nerve
4243 autoupdateChecked = qfalse;
4244 autoupdateStarted = qfalse;
4245
4246 CL_InitTranslation(); // NERVE - SMF - localization
4247
4248 CL_GenerateQKey();
4249 CL_UpdateGUID( NULL, 0 );
4250
4251 Com_Printf( "----- Client Initialization Complete -----\n" );
4252 }
4253
4254
4255 /*
4256 ===============
4257 CL_Shutdown
4258
4259 ===============
4260 */
CL_Shutdown(char * finalmsg,qboolean disconnect,qboolean quit)4261 void CL_Shutdown( char *finalmsg, qboolean disconnect, qboolean quit ) {
4262 static qboolean recursive = qfalse;
4263
4264 // check whether the client is running at all.
4265 if(!(com_cl_running && com_cl_running->integer))
4266 return;
4267
4268 Com_Printf( "----- Client Shutdown (%s) -----\n", finalmsg );
4269
4270 if ( recursive ) {
4271 Com_Printf( "WARNING: Recursive shutdown\n" );
4272 return;
4273 }
4274 recursive = qtrue;
4275
4276 noGameRestart = quit;
4277
4278 if(disconnect)
4279 CL_Disconnect(qtrue);
4280
4281 CL_ClearMemory(qtrue);
4282 CL_Snd_Shutdown();
4283
4284 Cmd_RemoveCommand( "cmd" );
4285 Cmd_RemoveCommand( "configstrings" );
4286 Cmd_RemoveCommand ("clientinfo");
4287 Cmd_RemoveCommand( "snd_restart" );
4288 Cmd_RemoveCommand( "vid_restart" );
4289 Cmd_RemoveCommand( "ui_restart" );
4290 Cmd_RemoveCommand( "disconnect" );
4291 Cmd_RemoveCommand( "record" );
4292 Cmd_RemoveCommand( "demo" );
4293 Cmd_RemoveCommand( "cinematic" );
4294 Cmd_RemoveCommand( "stoprecord" );
4295 Cmd_RemoveCommand( "connect" );
4296 Cmd_RemoveCommand ("reconnect");
4297 Cmd_RemoveCommand( "localservers" );
4298 Cmd_RemoveCommand( "globalservers" );
4299 Cmd_RemoveCommand( "rcon" );
4300 Cmd_RemoveCommand( "ping" );
4301 Cmd_RemoveCommand( "serverstatus" );
4302 Cmd_RemoveCommand( "showip" );
4303 Cmd_RemoveCommand ("fs_openedList");
4304 Cmd_RemoveCommand ("fs_referencedList");
4305 Cmd_RemoveCommand( "model" );
4306 Cmd_RemoveCommand ("video");
4307 Cmd_RemoveCommand ("stopvideo");
4308
4309 // Ridah, startup-caching system
4310 Cmd_RemoveCommand( "cache_startgather" );
4311 Cmd_RemoveCommand( "cache_usedfile" );
4312 Cmd_RemoveCommand( "cache_setindex" );
4313 Cmd_RemoveCommand( "cache_mapchange" );
4314 Cmd_RemoveCommand( "cache_endgather" );
4315
4316 Cmd_RemoveCommand( "updatehunkusage" );
4317 // done.
4318
4319 Cmd_RemoveCommand( "updatescreen" );
4320 Cmd_RemoveCommand( "SaveTranslations" ); // NERVE - SMF - localization
4321 Cmd_RemoveCommand( "SaveNewTranslations" ); // NERVE - SMF - localization
4322 Cmd_RemoveCommand( "LoadTranslations" ); // NERVE - SMF - localization
4323 Cmd_RemoveCommand( "startSingleplayer" ); // NERVE - SMF
4324 Cmd_RemoveCommand( "setRecommended" );
4325
4326 CL_ShutdownInput();
4327 Con_Shutdown();
4328
4329 Cvar_Set( "cl_running", "0" );
4330
4331 recursive = qfalse;
4332
4333 memset( &cls, 0, sizeof( cls ) );
4334 Key_SetCatcher( 0 );
4335
4336 Com_Printf( "-----------------------\n" );
4337 }
4338
4339
CL_SetServerInfo(serverInfo_t * server,const char * info,int ping)4340 static void CL_SetServerInfo( serverInfo_t *server, const char *info, int ping ) {
4341 if ( server ) {
4342 if ( info ) {
4343 server->clients = atoi( Info_ValueForKey( info, "clients" ) );
4344 Q_strncpyz( server->hostName,Info_ValueForKey( info, "hostname" ), MAX_NAME_LENGTH );
4345 Q_strncpyz( server->mapName, Info_ValueForKey( info, "mapname" ), MAX_NAME_LENGTH );
4346 server->maxClients = atoi( Info_ValueForKey( info, "sv_maxclients" ) );
4347 Q_strncpyz( server->game,Info_ValueForKey( info, "game" ), MAX_NAME_LENGTH );
4348 server->gameType = atoi( Info_ValueForKey( info, "gametype" ) );
4349 server->netType = atoi( Info_ValueForKey( info, "nettype" ) );
4350 server->minPing = atoi( Info_ValueForKey( info, "minping" ) );
4351 server->maxPing = atoi( Info_ValueForKey( info, "maxping" ) );
4352 server->allowAnonymous = atoi( Info_ValueForKey( info, "sv_allowAnonymous" ) );
4353 server->friendlyFire = atoi( Info_ValueForKey( info, "friendlyFire" ) ); // NERVE - SMF
4354 server->maxlives = atoi( Info_ValueForKey( info, "maxlives" ) ); // NERVE - SMF
4355 server->tourney = atoi( Info_ValueForKey( info, "tourney" ) ); // NERVE - SMF
4356 server->punkbuster = atoi( Info_ValueForKey( info, "punkbuster" ) ); // DHM - Nerve
4357 Q_strncpyz( server->gameName, Info_ValueForKey( info, "gamename" ), MAX_NAME_LENGTH ); // Arnout
4358 server->antilag = atoi( Info_ValueForKey( info, "g_antilag" ) );
4359 server->g_humanplayers = atoi( Info_ValueForKey( info, "g_humanplayers" ) );
4360 server->g_needpass = atoi( Info_ValueForKey( info, "g_needpass" ) );
4361 }
4362 server->ping = ping;
4363 }
4364 }
4365
CL_SetServerInfoByAddress(netadr_t from,const char * info,int ping)4366 static void CL_SetServerInfoByAddress( netadr_t from, const char *info, int ping ) {
4367 int i;
4368
4369 for ( i = 0; i < MAX_OTHER_SERVERS; i++ ) {
4370 if ( NET_CompareAdr( from, cls.localServers[i].adr ) ) {
4371 CL_SetServerInfo( &cls.localServers[i], info, ping );
4372 }
4373 }
4374
4375 for ( i = 0; i < MAX_GLOBAL_SERVERS; i++ ) {
4376 if ( NET_CompareAdr( from, cls.globalServers[i].adr ) ) {
4377 CL_SetServerInfo( &cls.globalServers[i], info, ping );
4378 }
4379 }
4380
4381 for ( i = 0; i < MAX_OTHER_SERVERS; i++ ) {
4382 if ( NET_CompareAdr( from, cls.favoriteServers[i].adr ) ) {
4383 CL_SetServerInfo( &cls.favoriteServers[i], info, ping );
4384 }
4385 }
4386
4387 }
4388
4389 /*
4390 ===================
4391 CL_ServerInfoPacket
4392 ===================
4393 */
CL_ServerInfoPacket(netadr_t from,msg_t * msg)4394 void CL_ServerInfoPacket( netadr_t from, msg_t *msg ) {
4395 int i, type;
4396 char info[MAX_INFO_STRING];
4397 char *infoString;
4398 int prot;
4399 char *gamename;
4400 qboolean gameMismatch;
4401
4402 infoString = MSG_ReadString( msg );
4403
4404 // if this isn't the correct gamename, ignore it
4405 gamename = Info_ValueForKey( infoString, "gamename" );
4406
4407 #ifdef LEGACY_PROTOCOL
4408 // gamename is optional for legacy protocol
4409 if (com_legacyprotocol->integer && !*gamename)
4410 gameMismatch = qfalse;
4411 else
4412 #endif
4413 gameMismatch = !*gamename || strcmp(gamename, com_gamename->string) != 0;
4414
4415 if (gameMismatch)
4416 {
4417 Com_DPrintf( "Game mismatch in info packet: %s\n", infoString );
4418 return;
4419 }
4420
4421 // if this isn't the correct protocol version, ignore it
4422 prot = atoi( Info_ValueForKey( infoString, "protocol" ) );
4423
4424 if(prot != com_protocol->integer
4425 #ifdef LEGACY_PROTOCOL
4426 && prot != com_legacyprotocol->integer
4427 #endif
4428 )
4429 {
4430 Com_DPrintf( "Different protocol info packet: %s\n", infoString );
4431 return;
4432 }
4433
4434 // iterate servers waiting for ping response
4435 for ( i = 0; i < MAX_PINGREQUESTS; i++ )
4436 {
4437 if ( cl_pinglist[i].adr.port && !cl_pinglist[i].time && NET_CompareAdr( from, cl_pinglist[i].adr ) ) {
4438 // calc ping time
4439 cl_pinglist[i].time = Sys_Milliseconds() - cl_pinglist[i].start;
4440 Com_DPrintf( "ping time %dms from %s\n", cl_pinglist[i].time, NET_AdrToString( from ) );
4441
4442 // save of info
4443 Q_strncpyz( cl_pinglist[i].info, infoString, sizeof( cl_pinglist[i].info ) );
4444
4445 // tack on the net type
4446 // NOTE: make sure these types are in sync with the netnames strings in the UI
4447 switch ( from.type )
4448 {
4449 case NA_BROADCAST:
4450 case NA_IP:
4451 type = 1;
4452 break;
4453 case NA_IP6:
4454 type = 2;
4455 break;
4456 default:
4457 type = 0;
4458 break;
4459 }
4460 Info_SetValueForKey( cl_pinglist[i].info, "nettype", va( "%d", type ) );
4461 CL_SetServerInfoByAddress( from, infoString, cl_pinglist[i].time );
4462
4463 return;
4464 }
4465 }
4466
4467 // if not just sent a local broadcast or pinging local servers
4468 if ( cls.pingUpdateSource != AS_LOCAL ) {
4469 return;
4470 }
4471
4472 for ( i = 0 ; i < MAX_OTHER_SERVERS ; i++ ) {
4473 // empty slot
4474 if ( cls.localServers[i].adr.port == 0 ) {
4475 break;
4476 }
4477
4478 // avoid duplicate
4479 if ( NET_CompareAdr( from, cls.localServers[i].adr ) ) {
4480 return;
4481 }
4482 }
4483
4484 if ( i == MAX_OTHER_SERVERS ) {
4485 Com_DPrintf( "MAX_OTHER_SERVERS hit, dropping infoResponse\n" );
4486 return;
4487 }
4488
4489 // add this to the list
4490 cls.numlocalservers = i + 1;
4491 CL_InitServerInfo( &cls.localServers[i], &from );
4492
4493 Q_strncpyz( info, MSG_ReadString( msg ), MAX_INFO_STRING );
4494 if ( strlen( info ) ) {
4495 if ( info[strlen( info ) - 1] != '\n' ) {
4496 Q_strcat( info, sizeof(info), "\n" );
4497 }
4498 Com_Printf( "%s: %s", NET_AdrToStringwPort( from ), info );
4499 }
4500 }
4501
4502 /*
4503 ===================
4504 CL_UpdateInfoPacket
4505 ===================
4506 */
CL_UpdateInfoPacket(netadr_t from)4507 void CL_UpdateInfoPacket( netadr_t from ) {
4508
4509 if ( cls.autoupdateServer.type == NA_BAD ) {
4510 Com_DPrintf( "CL_UpdateInfoPacket: Auto-Updater has bad address\n" );
4511 return;
4512 }
4513
4514 Com_DPrintf( "Auto-Updater resolved to %i.%i.%i.%i:%i\n",
4515 cls.autoupdateServer.ip[0], cls.autoupdateServer.ip[1],
4516 cls.autoupdateServer.ip[2], cls.autoupdateServer.ip[3],
4517 BigShort( cls.autoupdateServer.port ) );
4518
4519 if ( !NET_CompareAdr( from, cls.autoupdateServer ) ) {
4520 Com_DPrintf( "CL_UpdateInfoPacket: Received packet from %i.%i.%i.%i:%i\n",
4521 from.ip[0], from.ip[1], from.ip[2], from.ip[3],
4522 BigShort( from.port ) );
4523 return;
4524 }
4525
4526 Cvar_Set( "cl_updateavailable", Cmd_Argv( 1 ) );
4527
4528 if ( !Q_stricmp( cl_updateavailable->string, "1" ) ) {
4529 Cvar_Set( "cl_updatefiles", Cmd_Argv( 2 ) );
4530 VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_WM_AUTOUPDATE );
4531 }
4532 }
4533 // DHM - Nerve
4534
4535 /*
4536 ===================
4537 CL_GetServerStatus
4538 ===================
4539 */
CL_GetServerStatus(netadr_t from)4540 serverStatus_t *CL_GetServerStatus( netadr_t from ) {
4541 int i, oldest, oldestTime;
4542
4543 for ( i = 0; i < MAX_SERVERSTATUSREQUESTS; i++ ) {
4544 if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) {
4545 return &cl_serverStatusList[i];
4546 }
4547 }
4548 for ( i = 0; i < MAX_SERVERSTATUSREQUESTS; i++ ) {
4549 if ( cl_serverStatusList[i].retrieved ) {
4550 return &cl_serverStatusList[i];
4551 }
4552 }
4553 oldest = -1;
4554 oldestTime = 0;
4555 for ( i = 0; i < MAX_SERVERSTATUSREQUESTS; i++ ) {
4556 if ( oldest == -1 || cl_serverStatusList[i].startTime < oldestTime ) {
4557 oldest = i;
4558 oldestTime = cl_serverStatusList[i].startTime;
4559 }
4560 }
4561 return &cl_serverStatusList[oldest];
4562 }
4563
4564 /*
4565 ===================
4566 CL_ServerStatus
4567 ===================
4568 */
CL_ServerStatus(char * serverAddress,char * serverStatusString,int maxLen)4569 int CL_ServerStatus( char *serverAddress, char *serverStatusString, int maxLen ) {
4570 int i;
4571 netadr_t to;
4572 serverStatus_t *serverStatus;
4573
4574 // if no server address then reset all server status requests
4575 if ( !serverAddress ) {
4576 for ( i = 0; i < MAX_SERVERSTATUSREQUESTS; i++ ) {
4577 cl_serverStatusList[i].address.port = 0;
4578 cl_serverStatusList[i].retrieved = qtrue;
4579 }
4580 return qfalse;
4581 }
4582 // get the address
4583 if ( !NET_StringToAdr( serverAddress, &to, NA_UNSPEC) ) {
4584 return qfalse;
4585 }
4586 serverStatus = CL_GetServerStatus( to );
4587 // if no server status string then reset the server status request for this address
4588 if ( !serverStatusString ) {
4589 serverStatus->retrieved = qtrue;
4590 return qfalse;
4591 }
4592
4593 // if this server status request has the same address
4594 if ( NET_CompareAdr( to, serverStatus->address ) ) {
4595 // if we received a response for this server status request
4596 if ( !serverStatus->pending ) {
4597 Q_strncpyz( serverStatusString, serverStatus->string, maxLen );
4598 serverStatus->retrieved = qtrue;
4599 serverStatus->startTime = 0;
4600 return qtrue;
4601 }
4602 // resend the request regularly
4603 else if ( serverStatus->startTime < Com_Milliseconds() - cl_serverStatusResendTime->integer ) {
4604 serverStatus->print = qfalse;
4605 serverStatus->pending = qtrue;
4606 serverStatus->retrieved = qfalse;
4607 serverStatus->time = 0;
4608 serverStatus->startTime = Com_Milliseconds();
4609 NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" );
4610 return qfalse;
4611 }
4612 }
4613 // if retrieved
4614 else if ( serverStatus->retrieved ) {
4615 serverStatus->address = to;
4616 serverStatus->print = qfalse;
4617 serverStatus->pending = qtrue;
4618 serverStatus->retrieved = qfalse;
4619 serverStatus->startTime = Com_Milliseconds();
4620 serverStatus->time = 0;
4621 NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" );
4622 return qfalse;
4623 }
4624 return qfalse;
4625 }
4626
4627 /*
4628 ===================
4629 CL_ServerStatusResponse
4630 ===================
4631 */
CL_ServerStatusResponse(netadr_t from,msg_t * msg)4632 void CL_ServerStatusResponse( netadr_t from, msg_t *msg ) {
4633 char *s;
4634 char info[MAX_INFO_STRING];
4635 int i, l, score, ping;
4636 int len;
4637 serverStatus_t *serverStatus;
4638
4639 serverStatus = NULL;
4640 for ( i = 0; i < MAX_SERVERSTATUSREQUESTS; i++ ) {
4641 if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) {
4642 serverStatus = &cl_serverStatusList[i];
4643 break;
4644 }
4645 }
4646 // if we didn't request this server status
4647 if ( !serverStatus ) {
4648 return;
4649 }
4650
4651 s = MSG_ReadStringLine( msg );
4652
4653 len = 0;
4654 Com_sprintf( &serverStatus->string[len], sizeof( serverStatus->string ) - len, "%s", s );
4655
4656 if ( serverStatus->print ) {
4657 Com_Printf( "Server settings:\n" );
4658 // print cvars
4659 while ( *s ) {
4660 for ( i = 0; i < 2 && *s; i++ ) {
4661 if ( *s == '\\' ) {
4662 s++;
4663 }
4664 l = 0;
4665 while ( *s ) {
4666 info[l++] = *s;
4667 if ( l >= MAX_INFO_STRING - 1 ) {
4668 break;
4669 }
4670 s++;
4671 if ( *s == '\\' ) {
4672 break;
4673 }
4674 }
4675 info[l] = '\0';
4676 if ( i ) {
4677 Com_Printf( "%s\n", info );
4678 } else {
4679 Com_Printf( "%-24s", info );
4680 }
4681 }
4682 }
4683 }
4684
4685 len = strlen( serverStatus->string );
4686 Com_sprintf( &serverStatus->string[len], sizeof( serverStatus->string ) - len, "\\" );
4687
4688 if ( serverStatus->print ) {
4689 Com_Printf( "\nPlayers:\n" );
4690 Com_Printf( "num: score: ping: name:\n" );
4691 }
4692 for ( i = 0, s = MSG_ReadStringLine( msg ); *s; s = MSG_ReadStringLine( msg ), i++ ) {
4693
4694 len = strlen( serverStatus->string );
4695 Com_sprintf( &serverStatus->string[len], sizeof( serverStatus->string ) - len, "\\%s", s );
4696
4697 if ( serverStatus->print ) {
4698 score = ping = 0;
4699 sscanf( s, "%d %d", &score, &ping );
4700 s = strchr( s, ' ' );
4701 if ( s ) {
4702 s = strchr( s + 1, ' ' );
4703 }
4704 if ( s ) {
4705 s++;
4706 } else {
4707 s = "unknown";
4708 }
4709 Com_Printf( "%-2d %-3d %-3d %s\n", i, score, ping, s );
4710 }
4711 }
4712 len = strlen( serverStatus->string );
4713 Com_sprintf( &serverStatus->string[len], sizeof( serverStatus->string ) - len, "\\" );
4714
4715 serverStatus->time = Com_Milliseconds();
4716 serverStatus->address = from;
4717 serverStatus->pending = qfalse;
4718 if (serverStatus->print) {
4719 serverStatus->retrieved = qtrue;
4720 }
4721 }
4722
4723 /*
4724 ==================
4725 CL_LocalServers_f
4726 ==================
4727 */
CL_LocalServers_f(void)4728 void CL_LocalServers_f( void ) {
4729 char *message;
4730 int i, j;
4731 netadr_t to;
4732
4733 Com_Printf( "Scanning for servers on the local network...\n" );
4734
4735 // reset the list, waiting for response
4736 cls.numlocalservers = 0;
4737 cls.pingUpdateSource = AS_LOCAL;
4738
4739 for ( i = 0; i < MAX_OTHER_SERVERS; i++ ) {
4740 qboolean b = cls.localServers[i].visible;
4741 Com_Memset( &cls.localServers[i], 0, sizeof( cls.localServers[i] ) );
4742 cls.localServers[i].visible = b;
4743 }
4744 Com_Memset( &to, 0, sizeof( to ) );
4745
4746 // The 'xxx' in the message is a challenge that will be echoed back
4747 // by the server. We don't care about that here, but master servers
4748 // can use that to prevent spoofed server responses from invalid ip
4749 message = "\377\377\377\377getinfo xxx";
4750
4751 // send each message twice in case one is dropped
4752 for ( i = 0 ; i < 2 ; i++ ) {
4753 // send a broadcast packet on each server port
4754 // we support multiple server ports so a single machine
4755 // can nicely run multiple servers
4756 for ( j = 0 ; j < NUM_SERVER_PORTS ; j++ ) {
4757 to.port = BigShort( (short)( PORT_SERVER + j ) );
4758
4759 to.type = NA_BROADCAST;
4760 NET_SendPacket( NS_CLIENT, strlen( message ), message, to );
4761 to.type = NA_MULTICAST6;
4762 NET_SendPacket( NS_CLIENT, strlen( message ), message, to );
4763 }
4764 }
4765 }
4766
4767 /*
4768 ==================
4769 CL_GlobalServers_f
4770
4771 ioquake3 2008; added support for requesting five separate master servers using 0-4.
4772 ioquake3 2017; made master 0 fetch all master servers and 1-5 request a single master server.
4773 ==================
4774 */
CL_GlobalServers_f(void)4775 void CL_GlobalServers_f( void ) {
4776 netadr_t to;
4777 int count, i, masterNum;
4778 char command[1024], *masteraddress;
4779
4780 if ((count = Cmd_Argc()) < 3 || (masterNum = atoi(Cmd_Argv(1))) < 0 || masterNum > MAX_MASTER_SERVERS)
4781 {
4782 Com_Printf("usage: globalservers <master# 0-%d> <protocol> [keywords]\n", MAX_MASTER_SERVERS);
4783 return;
4784 }
4785
4786 // request from all master servers
4787 if ( masterNum == 0 ) {
4788 int numAddress = 0;
4789
4790 for ( i = 1; i <= MAX_MASTER_SERVERS; i++ ) {
4791 sprintf(command, "sv_master%d", i);
4792 masteraddress = Cvar_VariableString(command);
4793
4794 if(!*masteraddress)
4795 continue;
4796
4797 numAddress++;
4798
4799 Com_sprintf(command, sizeof(command), "globalservers %d %s %s\n", i, Cmd_Argv(2), Cmd_ArgsFrom(3));
4800 Cbuf_AddText(command);
4801 }
4802
4803 if ( !numAddress ) {
4804 Com_Printf( "CL_GlobalServers_f: Error: No master server addresses.\n");
4805 }
4806 return;
4807 }
4808
4809 sprintf(command, "sv_master%d", masterNum);
4810 masteraddress = Cvar_VariableString(command);
4811
4812 if(!*masteraddress)
4813 {
4814 Com_Printf( "CL_GlobalServers_f: Error: No master server address given.\n");
4815 return;
4816 }
4817
4818 // reset the list, waiting for response
4819 // -1 is used to distinguish a "no response"
4820
4821 i = NET_StringToAdr(masteraddress, &to, NA_UNSPEC);
4822
4823 if(!i)
4824 {
4825 Com_Printf( "CL_GlobalServers_f: Error: could not resolve address of master %s\n", masteraddress);
4826 return;
4827 }
4828 else if(i == 2)
4829 to.port = BigShort(PORT_MASTER);
4830
4831 Com_Printf("Requesting servers from %s (%s)...\n", masteraddress, NET_AdrToStringwPort(to));
4832
4833 cls.numglobalservers = -1;
4834 cls.pingUpdateSource = AS_GLOBAL;
4835
4836 // Use the extended query for IPv6 masters
4837 if (to.type == NA_IP6 || to.type == NA_MULTICAST6)
4838 {
4839 int v4enabled = Cvar_VariableIntegerValue("net_enabled") & NET_ENABLEV4;
4840
4841 if(v4enabled)
4842 {
4843 Com_sprintf(command, sizeof(command), "getserversExt %s %s",
4844 com_gamename->string, Cmd_Argv(2));
4845 }
4846 else
4847 {
4848 Com_sprintf(command, sizeof(command), "getserversExt %s %s ipv6",
4849 com_gamename->string, Cmd_Argv(2));
4850 }
4851 }
4852 else if ( !Q_stricmp( com_gamename->string, LEGACY_MASTER_GAMENAME ) )
4853 Com_sprintf(command, sizeof(command), "getservers %s",
4854 Cmd_Argv(2));
4855 else
4856 Com_sprintf(command, sizeof(command), "getservers %s %s",
4857 com_gamename->string, Cmd_Argv(2));
4858
4859 for (i=3; i < count; i++)
4860 {
4861 Q_strcat(command, sizeof(command), " ");
4862 Q_strcat(command, sizeof(command), Cmd_Argv(i));
4863 }
4864
4865 NET_OutOfBandPrint( NS_SERVER, to, "%s", command );
4866 }
4867
4868 /*
4869 ==================
4870 CL_GetPing
4871 ==================
4872 */
CL_GetPing(int n,char * buf,int buflen,int * pingtime)4873 void CL_GetPing( int n, char *buf, int buflen, int *pingtime ) {
4874 const char *str;
4875 int time;
4876 int maxPing;
4877
4878 if (n < 0 || n >= MAX_PINGREQUESTS || !cl_pinglist[n].adr.port)
4879 {
4880 // empty or invalid slot
4881 buf[0] = '\0';
4882 *pingtime = 0;
4883 return;
4884 }
4885
4886 str = NET_AdrToStringwPort( cl_pinglist[n].adr );
4887 Q_strncpyz( buf, str, buflen );
4888
4889 time = cl_pinglist[n].time;
4890 if ( !time ) {
4891 // check for timeout
4892 time = Sys_Milliseconds() - cl_pinglist[n].start;
4893 maxPing = Cvar_VariableIntegerValue( "cl_maxPing" );
4894 if ( maxPing < 100 ) {
4895 maxPing = 100;
4896 }
4897 if ( time < maxPing ) {
4898 // not timed out yet
4899 time = 0;
4900 }
4901 }
4902
4903 CL_SetServerInfoByAddress( cl_pinglist[n].adr, cl_pinglist[n].info, cl_pinglist[n].time );
4904
4905 *pingtime = time;
4906 }
4907
4908 /*
4909 ==================
4910 CL_GetPingInfo
4911 ==================
4912 */
CL_GetPingInfo(int n,char * buf,int buflen)4913 void CL_GetPingInfo( int n, char *buf, int buflen ) {
4914 if ( n < 0 || n >= MAX_PINGREQUESTS || !cl_pinglist[n].adr.port ) {
4915 // empty or invalid slot
4916 if ( buflen ) {
4917 buf[0] = '\0';
4918 }
4919 return;
4920 }
4921
4922 Q_strncpyz( buf, cl_pinglist[n].info, buflen );
4923 }
4924
4925 /*
4926 ==================
4927 CL_ClearPing
4928 ==================
4929 */
CL_ClearPing(int n)4930 void CL_ClearPing( int n ) {
4931 if ( n < 0 || n >= MAX_PINGREQUESTS ) {
4932 return;
4933 }
4934
4935 cl_pinglist[n].adr.port = 0;
4936 }
4937
4938 /*
4939 ==================
4940 CL_GetPingQueueCount
4941 ==================
4942 */
CL_GetPingQueueCount(void)4943 int CL_GetPingQueueCount( void ) {
4944 int i;
4945 int count;
4946 ping_t* pingptr;
4947
4948 count = 0;
4949 pingptr = cl_pinglist;
4950
4951 for ( i = 0; i < MAX_PINGREQUESTS; i++, pingptr++ ) {
4952 if ( pingptr->adr.port ) {
4953 count++;
4954 }
4955 }
4956
4957 return ( count );
4958 }
4959
4960 /*
4961 ==================
4962 CL_GetFreePing
4963 ==================
4964 */
CL_GetFreePing(void)4965 ping_t* CL_GetFreePing( void ) {
4966 ping_t* pingptr;
4967 ping_t* best;
4968 int oldest;
4969 int i;
4970 int time;
4971
4972 pingptr = cl_pinglist;
4973 for ( i = 0; i < MAX_PINGREQUESTS; i++, pingptr++ )
4974 {
4975 // find free ping slot
4976 if ( pingptr->adr.port ) {
4977 if ( !pingptr->time ) {
4978 if (Sys_Milliseconds() - pingptr->start < 500)
4979 {
4980 // still waiting for response
4981 continue;
4982 }
4983 } else if ( pingptr->time < 500 ) {
4984 // results have not been queried
4985 continue;
4986 }
4987 }
4988
4989 // clear it
4990 pingptr->adr.port = 0;
4991 return ( pingptr );
4992 }
4993
4994 // use oldest entry
4995 pingptr = cl_pinglist;
4996 best = cl_pinglist;
4997 oldest = INT_MIN;
4998 for ( i = 0; i < MAX_PINGREQUESTS; i++, pingptr++ )
4999 {
5000 // scan for oldest
5001 time = Sys_Milliseconds() - pingptr->start;
5002 if ( time > oldest ) {
5003 oldest = time;
5004 best = pingptr;
5005 }
5006 }
5007
5008 return ( best );
5009 }
5010
5011 /*
5012 ==================
5013 CL_Ping_f
5014 ==================
5015 */
CL_Ping_f(void)5016 void CL_Ping_f( void ) {
5017 netadr_t to;
5018 ping_t* pingptr;
5019 char* server;
5020 int argc;
5021 netadrtype_t family = NA_UNSPEC;
5022
5023 argc = Cmd_Argc();
5024
5025 if ( argc != 2 && argc != 3 ) {
5026 Com_Printf( "usage: ping [-4|-6] server\n");
5027 return;
5028 }
5029
5030 if(argc == 2)
5031 server = Cmd_Argv(1);
5032 else
5033 {
5034 if(!strcmp(Cmd_Argv(1), "-4"))
5035 family = NA_IP;
5036 else if(!strcmp(Cmd_Argv(1), "-6"))
5037 family = NA_IP6;
5038 else
5039 Com_Printf( "warning: only -4 or -6 as address type understood.\n");
5040
5041 server = Cmd_Argv(2);
5042 }
5043
5044 Com_Memset( &to, 0, sizeof( netadr_t ) );
5045
5046 if ( !NET_StringToAdr( server, &to, family ) ) {
5047 return;
5048 }
5049
5050 pingptr = CL_GetFreePing();
5051
5052 memcpy( &pingptr->adr, &to, sizeof( netadr_t ) );
5053 pingptr->start = Sys_Milliseconds();
5054 pingptr->time = 0;
5055
5056 CL_SetServerInfoByAddress( pingptr->adr, NULL, 0 );
5057
5058 NET_OutOfBandPrint( NS_CLIENT, to, "getinfo xxx" );
5059 }
5060
5061 /*
5062 ==================
5063 CL_UpdateVisiblePings_f
5064 ==================
5065 */
CL_UpdateVisiblePings_f(int source)5066 qboolean CL_UpdateVisiblePings_f( int source ) {
5067 int slots, i;
5068 char buff[MAX_STRING_CHARS];
5069 int pingTime;
5070 int max;
5071 qboolean status = qfalse;
5072
5073 if ( source < 0 || source > AS_FAVORITES ) {
5074 return qfalse;
5075 }
5076
5077 cls.pingUpdateSource = source;
5078
5079 slots = CL_GetPingQueueCount();
5080 if ( slots < MAX_PINGREQUESTS ) {
5081 serverInfo_t *server = NULL;
5082
5083 switch ( source ) {
5084 case AS_LOCAL:
5085 server = &cls.localServers[0];
5086 max = cls.numlocalservers;
5087 break;
5088 case AS_GLOBAL:
5089 server = &cls.globalServers[0];
5090 max = cls.numglobalservers;
5091 break;
5092 case AS_FAVORITES:
5093 server = &cls.favoriteServers[0];
5094 max = cls.numfavoriteservers;
5095 break;
5096 default:
5097 return qfalse;
5098 }
5099 for ( i = 0; i < max; i++ ) {
5100 if ( server[i].visible ) {
5101 if ( server[i].ping == -1 ) {
5102 int j;
5103
5104 if ( slots >= MAX_PINGREQUESTS ) {
5105 break;
5106 }
5107 for ( j = 0; j < MAX_PINGREQUESTS; j++ ) {
5108 if ( !cl_pinglist[j].adr.port ) {
5109 continue;
5110 }
5111 if ( NET_CompareAdr( cl_pinglist[j].adr, server[i].adr ) ) {
5112 // already on the list
5113 break;
5114 }
5115 }
5116 if ( j >= MAX_PINGREQUESTS ) {
5117 status = qtrue;
5118 for ( j = 0; j < MAX_PINGREQUESTS; j++ ) {
5119 if ( !cl_pinglist[j].adr.port ) {
5120 break;
5121 }
5122 }
5123 memcpy( &cl_pinglist[j].adr, &server[i].adr, sizeof( netadr_t ) );
5124 cl_pinglist[j].start = Sys_Milliseconds();
5125 cl_pinglist[j].time = 0;
5126 NET_OutOfBandPrint( NS_CLIENT, cl_pinglist[j].adr, "getinfo xxx" );
5127 slots++;
5128 }
5129 }
5130 // if the server has a ping higher than cl_maxPing or
5131 // the ping packet got lost
5132 else if ( server[i].ping == 0 ) {
5133 // if we are updating global servers
5134 if ( source == AS_GLOBAL ) {
5135 //
5136 if ( cls.numGlobalServerAddresses > 0 ) {
5137 // overwrite this server with one from the additional global servers
5138 cls.numGlobalServerAddresses--;
5139 CL_InitServerInfo( &server[i], &cls.globalServerAddresses[cls.numGlobalServerAddresses] );
5140 // NOTE: the server[i].visible flag stays untouched
5141 }
5142 }
5143 }
5144 }
5145 }
5146 }
5147
5148 if ( slots ) {
5149 status = qtrue;
5150 }
5151 for ( i = 0; i < MAX_PINGREQUESTS; i++ ) {
5152 if ( !cl_pinglist[i].adr.port ) {
5153 continue;
5154 }
5155 CL_GetPing( i, buff, MAX_STRING_CHARS, &pingTime );
5156 if ( pingTime != 0 ) {
5157 CL_ClearPing( i );
5158 status = qtrue;
5159 }
5160 }
5161
5162 return status;
5163 }
5164
5165 /*
5166 ==================
5167 CL_UpdateServerInfo
5168 ==================
5169 */
CL_UpdateServerInfo(int n)5170 void CL_UpdateServerInfo( int n ) {
5171 if ( !cl_pinglist[n].adr.port ) {
5172 return;
5173 }
5174
5175 CL_SetServerInfoByAddress( cl_pinglist[n].adr, cl_pinglist[n].info, cl_pinglist[n].time );
5176 }
5177
5178 /*
5179 ==================
5180 CL_ServerStatus_f
5181 ==================
5182 */
CL_ServerStatus_f(void)5183 void CL_ServerStatus_f( void ) {
5184 netadr_t to, *toptr = NULL;
5185 char *server;
5186 serverStatus_t *serverStatus;
5187 int argc;
5188 netadrtype_t family = NA_UNSPEC;
5189
5190 argc = Cmd_Argc();
5191
5192 if ( argc != 2 && argc != 3 )
5193 {
5194 if (clc.state != CA_ACTIVE || clc.demoplaying)
5195 {
5196 Com_Printf( "Not connected to a server.\n" );
5197 Com_Printf( "usage: serverstatus [-4|-6] server\n");
5198 return;
5199 }
5200
5201 toptr = &clc.serverAddress;
5202 }
5203
5204 if(!toptr)
5205 {
5206 Com_Memset( &to, 0, sizeof(netadr_t) );
5207
5208 if(argc == 2)
5209 server = Cmd_Argv(1);
5210 else
5211 {
5212 if(!strcmp(Cmd_Argv(1), "-4"))
5213 family = NA_IP;
5214 else if(!strcmp(Cmd_Argv(1), "-6"))
5215 family = NA_IP6;
5216 else
5217 Com_Printf( "warning: only -4 or -6 as address type understood.\n");
5218
5219 server = Cmd_Argv(2);
5220 }
5221
5222 toptr = &to;
5223 if ( !NET_StringToAdr( server, toptr, family ) )
5224 return;
5225 }
5226
5227 NET_OutOfBandPrint( NS_CLIENT, *toptr, "getstatus" );
5228
5229 serverStatus = CL_GetServerStatus( *toptr );
5230 serverStatus->address = *toptr;
5231 serverStatus->print = qtrue;
5232 serverStatus->pending = qtrue;
5233 }
5234
5235 /*
5236 ==================
5237 CL_ShowIP_f
5238 ==================
5239 */
CL_ShowIP_f(void)5240 void CL_ShowIP_f( void ) {
5241 Sys_ShowIP();
5242 }
5243
5244 /*
5245 =================
5246 CL_CDKeyValidate
5247 =================
5248 */
CL_CDKeyValidate(const char * key,const char * checksum)5249 qboolean CL_CDKeyValidate( const char *key, const char *checksum ) {
5250 #ifdef STANDALONE
5251 return qtrue;
5252 #else
5253 char ch;
5254 byte sum;
5255 char chs[3];
5256 int i, len;
5257
5258 len = strlen( key );
5259 if ( len != CDKEY_LEN ) {
5260 return qfalse;
5261 }
5262
5263 if ( checksum && strlen( checksum ) != CDCHKSUM_LEN ) {
5264 return qfalse;
5265 }
5266
5267 sum = 0;
5268 // for loop gets rid of conditional assignment warning
5269 for ( i = 0; i < len; i++ ) {
5270 ch = *key++;
5271 if ( ch >= 'a' && ch <= 'z' ) {
5272 ch -= 32;
5273 }
5274 switch ( ch ) {
5275 case '2':
5276 case '3':
5277 case '7':
5278 case 'A':
5279 case 'B':
5280 case 'C':
5281 case 'D':
5282 case 'G':
5283 case 'H':
5284 case 'J':
5285 case 'L':
5286 case 'P':
5287 case 'R':
5288 case 'S':
5289 case 'T':
5290 case 'W':
5291 sum = ( sum << 1 ) ^ ch;
5292 continue;
5293 default:
5294 return qfalse;
5295 }
5296 }
5297
5298 Com_sprintf( chs, sizeof( chs ), "%02x", sum );
5299
5300 if ( checksum && !Q_stricmp( chs, checksum ) ) {
5301 return qtrue;
5302 }
5303
5304 if ( !checksum ) {
5305 return qtrue;
5306 }
5307
5308 return qfalse;
5309 #endif
5310 }
5311
5312 // NERVE - SMF
5313 /*
5314 =======================
5315 CL_AddToLimboChat
5316
5317 =======================
5318 */
CL_AddToLimboChat(const char * str)5319 void CL_AddToLimboChat( const char *str ) {
5320 int len = 0;
5321 char *p;
5322 int i;
5323
5324 cl.limboChatPos = LIMBOCHAT_HEIGHT - 1;
5325
5326 // copy old strings
5327 for ( i = cl.limboChatPos; i > 0; i-- ) {
5328 strcpy( cl.limboChatMsgs[i], cl.limboChatMsgs[i - 1] );
5329 }
5330
5331 // copy new string
5332 p = cl.limboChatMsgs[0];
5333 *p = 0;
5334
5335 while ( *str ) {
5336 if ( len > LIMBOCHAT_WIDTH - 1 ) {
5337 break;
5338 }
5339
5340 if ( Q_IsColorString( str ) ) {
5341 *p++ = *str++;
5342 *p++ = *str++;
5343 continue;
5344 }
5345 *p++ = *str++;
5346 len++;
5347 }
5348 *p = 0;
5349 }
5350
5351 /*
5352 =======================
5353 CL_GetLimboString
5354
5355 =======================
5356 */
CL_GetLimboString(int index,char * buf)5357 qboolean CL_GetLimboString( int index, char *buf ) {
5358 if ( index >= LIMBOCHAT_HEIGHT ) {
5359 return qfalse;
5360 }
5361
5362 strncpy( buf, cl.limboChatMsgs[index], 140 );
5363 return qtrue;
5364 }
5365 // -NERVE - SMF
5366
5367 // NERVE - SMF - Localization code
5368 #define FILE_HASH_SIZE 1024
5369 #define MAX_VA_STRING 32000
5370 #define MAX_TRANS_STRING 4096
5371
5372 typedef struct trans_s {
5373 char original[MAX_TRANS_STRING];
5374 char translated[MAX_LANGUAGES][MAX_TRANS_STRING];
5375 struct trans_s *next;
5376 float x_offset;
5377 float y_offset;
5378 qboolean fromFile;
5379 } trans_t;
5380
5381 static trans_t* transTable[FILE_HASH_SIZE];
5382
5383 /*
5384 =======================
5385 AllocTrans
5386 =======================
5387 */
AllocTrans(char * original,char * translated[MAX_LANGUAGES])5388 static trans_t* AllocTrans( char *original, char *translated[MAX_LANGUAGES] ) {
5389 trans_t *t;
5390 int i;
5391
5392 t = malloc( sizeof( trans_t ) );
5393 memset( t, 0, sizeof( trans_t ) );
5394
5395 if ( original ) {
5396 strncpy( t->original, original, MAX_TRANS_STRING );
5397 }
5398
5399 if ( translated ) {
5400 for ( i = 0; i < MAX_LANGUAGES; i++ )
5401 strncpy( t->translated[i], translated[i], MAX_TRANS_STRING );
5402 }
5403
5404 return t;
5405 }
5406
5407 /*
5408 =======================
5409 generateHashValue
5410 =======================
5411 */
generateHashValue(const char * fname)5412 static long generateHashValue( const char *fname ) {
5413 int i;
5414 long hash;
5415 char letter;
5416
5417 hash = 0;
5418 i = 0;
5419 while ( fname[i] != '\0' ) {
5420 letter = tolower( fname[i] );
5421 hash += (long)( letter ) * ( i + 119 );
5422 i++;
5423 }
5424 hash &= ( FILE_HASH_SIZE - 1 );
5425 return hash;
5426 }
5427
5428 /*
5429 =======================
5430 LookupTrans
5431 =======================
5432 */
LookupTrans(char * original,char * translated[MAX_LANGUAGES],qboolean isLoading)5433 static trans_t* LookupTrans( char *original, char *translated[MAX_LANGUAGES], qboolean isLoading ) {
5434 trans_t *t, *newt, *prev = NULL;
5435 long hash;
5436
5437 hash = generateHashValue( original );
5438
5439 for ( t = transTable[hash]; t; prev = t, t = t->next ) {
5440 if ( !Q_stricmp( original, t->original ) ) {
5441 if ( isLoading ) {
5442 Com_DPrintf( S_COLOR_YELLOW "WARNING: Duplicate string found: \"%s\"\n", original );
5443 }
5444 return t;
5445 }
5446 }
5447
5448 newt = AllocTrans( original, translated );
5449
5450 if ( prev ) {
5451 prev->next = newt;
5452 } else {
5453 transTable[hash] = newt;
5454 }
5455
5456 if ( cl_debugTranslation->integer >= 1 && !isLoading ) {
5457 Com_Printf( "Missing translation: \'%s\'\n", original );
5458 }
5459
5460 // see if we want to save out the translation table everytime a string is added
5461 // if ( cl_debugTranslation->integer == 2 && !isLoading ) {
5462 // CL_SaveTransTable();
5463 // }
5464
5465 return newt;
5466 }
5467
5468 /*
5469 =======================
5470 CL_SaveTransTable
5471 =======================
5472 */
CL_SaveTransTable(const char * fileName,qboolean newOnly)5473 void CL_SaveTransTable( const char *fileName, qboolean newOnly ) {
5474 int bucketlen, bucketnum, maxbucketlen, avebucketlen;
5475 int untransnum, transnum;
5476 const char *buf;
5477 fileHandle_t f;
5478 trans_t *t;
5479 int i, j, len;
5480
5481 if ( cl.corruptedTranslationFile ) {
5482 Com_Printf( S_COLOR_YELLOW "WARNING: Cannot save corrupted translation file. Please reload first." );
5483 return;
5484 }
5485
5486 FS_FOpenFileByMode( fileName, &f, FS_WRITE );
5487
5488 bucketnum = 0;
5489 maxbucketlen = 0;
5490 avebucketlen = 0;
5491 transnum = 0;
5492 untransnum = 0;
5493
5494 // write out version, if one
5495 if ( strlen( cl.translationVersion ) ) {
5496 buf = va( "#version\t\t\"%s\"\n", cl.translationVersion );
5497 } else {
5498 buf = va( "#version\t\t\"1.0 01/01/01\"\n" );
5499 }
5500
5501 len = strlen( buf );
5502 FS_Write( buf, len, f );
5503
5504 // write out translated strings
5505 for ( j = 0; j < 2; j++ ) {
5506
5507 for ( i = 0; i < FILE_HASH_SIZE; i++ ) {
5508 t = transTable[i];
5509
5510 if ( !t || ( newOnly && t->fromFile ) ) {
5511 continue;
5512 }
5513
5514 bucketlen = 0;
5515
5516 for ( ; t; t = t->next ) {
5517 bucketlen++;
5518
5519 if ( strlen( t->translated[0] ) ) {
5520 if ( j ) {
5521 continue;
5522 }
5523 transnum++;
5524 } else {
5525 if ( !j ) {
5526 continue;
5527 }
5528 untransnum++;
5529 }
5530
5531 buf = va( "{\n\tenglish\t\t\"%s\"\n", t->original );
5532 len = strlen( buf );
5533 FS_Write( buf, len, f );
5534
5535 buf = va( "\tfrench\t\t\"%s\"\n", t->translated[LANGUAGE_FRENCH] );
5536 len = strlen( buf );
5537 FS_Write( buf, len, f );
5538
5539 buf = va( "\tgerman\t\t\"%s\"\n", t->translated[LANGUAGE_GERMAN] );
5540 len = strlen( buf );
5541 FS_Write( buf, len, f );
5542
5543 buf = va( "\titalian\t\t\"%s\"\n", t->translated[LANGUAGE_ITALIAN] );
5544 len = strlen( buf );
5545 FS_Write( buf, len, f );
5546
5547 buf = va( "\tspanish\t\t\"%s\"\n", t->translated[LANGUAGE_SPANISH] );
5548 len = strlen( buf );
5549 FS_Write( buf, len, f );
5550
5551 buf = va( "}\n" );
5552 len = strlen( buf );
5553 FS_Write( buf, len, f );
5554 }
5555
5556 if ( bucketlen > maxbucketlen ) {
5557 maxbucketlen = bucketlen;
5558 }
5559
5560 if ( bucketlen ) {
5561 bucketnum++;
5562 avebucketlen += bucketlen;
5563 }
5564 }
5565 }
5566
5567 Com_Printf( "Saved translation table.\nTotal = %i, Translated = %i, Untranslated = %i, aveblen = %2.2f, maxblen = %i\n",
5568 transnum + untransnum, transnum, untransnum, (float)avebucketlen / bucketnum, maxbucketlen );
5569
5570 FS_FCloseFile( f );
5571 }
5572
5573 /*
5574 =======================
5575 CL_CheckTranslationString
5576
5577 NERVE - SMF - compare formatting characters
5578 =======================
5579 */
CL_CheckTranslationString(char * original,char * translated)5580 qboolean CL_CheckTranslationString( char *original, char *translated ) {
5581 char format_org[128], format_trans[128];
5582 int len, i;
5583
5584 memset( format_org, 0, 128 );
5585 memset( format_trans, 0, 128 );
5586
5587 // generate formatting string for original
5588 len = strlen( original );
5589
5590 for ( i = 0; i < len; i++ ) {
5591 if ( original[i] != '%' ) {
5592 continue;
5593 }
5594
5595 strcat( format_org, va( "%c%c ", '%', original[i + 1] ) );
5596 }
5597
5598 // generate formatting string for translated
5599 len = strlen( translated );
5600 if ( !len ) {
5601 return qtrue;
5602 }
5603
5604 for ( i = 0; i < len; i++ ) {
5605 if ( translated[i] != '%' ) {
5606 continue;
5607 }
5608
5609 strcat( format_trans, va( "%c%c ", '%', translated[i + 1] ) );
5610 }
5611
5612 // compare
5613 len = strlen( format_org );
5614
5615 if ( len != strlen( format_trans ) ) {
5616 return qfalse;
5617 }
5618
5619 for ( i = 0; i < len; i++ ) {
5620 if ( format_org[i] != format_trans[i] ) {
5621 return qfalse;
5622 }
5623 }
5624
5625 return qtrue;
5626 }
5627
5628 /*
5629 =======================
5630 CL_LoadTransTable
5631 =======================
5632 */
CL_LoadTransTable(const char * fileName)5633 void CL_LoadTransTable( const char *fileName ) {
5634 char translated[MAX_LANGUAGES][MAX_VA_STRING];
5635 char original[MAX_VA_STRING];
5636 qboolean aborted;
5637 char *text;
5638 fileHandle_t f;
5639 char *text_p;
5640 char *token;
5641 int len, i;
5642 trans_t *t;
5643 int count;
5644
5645 count = 0;
5646 aborted = qfalse;
5647 cl.corruptedTranslationFile = qfalse;
5648
5649 len = FS_FOpenFileByMode( fileName, &f, FS_READ );
5650 if ( len <= 0 ) {
5651 return;
5652 }
5653
5654 text = malloc( len + 1 );
5655 if ( !text ) {
5656 return;
5657 }
5658
5659 FS_Read( text, len, f );
5660 text[len] = 0;
5661 FS_FCloseFile( f );
5662
5663 // parse the text
5664 text_p = text;
5665
5666 do {
5667 token = COM_Parse( &text_p );
5668 if ( Q_stricmp( "{", token ) ) {
5669 // parse version number
5670 if ( !Q_stricmp( "#version", token ) ) {
5671 token = COM_Parse( &text_p );
5672 strcpy( cl.translationVersion, token );
5673 continue;
5674 }
5675
5676 break;
5677 }
5678
5679 // english
5680 token = COM_Parse( &text_p );
5681 if ( Q_stricmp( "english", token ) ) {
5682 aborted = qtrue;
5683 break;
5684 }
5685
5686 token = COM_Parse( &text_p );
5687 strcpy( original, token );
5688
5689 if ( cl_debugTranslation->integer == 3 ) {
5690 Com_Printf( "%i Loading: \"%s\"\n", count, original );
5691 }
5692
5693 // french
5694 token = COM_Parse( &text_p );
5695 if ( Q_stricmp( "french", token ) ) {
5696 aborted = qtrue;
5697 break;
5698 }
5699
5700 token = COM_Parse( &text_p );
5701 strcpy( translated[LANGUAGE_FRENCH], token );
5702 if ( !CL_CheckTranslationString( original, translated[LANGUAGE_FRENCH] ) ) {
5703 Com_Printf( S_COLOR_YELLOW "WARNING: Translation formatting doesn't match up with English version!\n" );
5704 aborted = qtrue;
5705 break;
5706 }
5707
5708 // german
5709 token = COM_Parse( &text_p );
5710 if ( Q_stricmp( "german", token ) ) {
5711 aborted = qtrue;
5712 break;
5713 }
5714
5715 token = COM_Parse( &text_p );
5716 strcpy( translated[LANGUAGE_GERMAN], token );
5717 if ( !CL_CheckTranslationString( original, translated[LANGUAGE_GERMAN] ) ) {
5718 Com_Printf( S_COLOR_YELLOW "WARNING: Translation formatting doesn't match up with English version!\n" );
5719 aborted = qtrue;
5720 break;
5721 }
5722
5723 // italian
5724 token = COM_Parse( &text_p );
5725 if ( Q_stricmp( "italian", token ) ) {
5726 aborted = qtrue;
5727 break;
5728 }
5729
5730 token = COM_Parse( &text_p );
5731 strcpy( translated[LANGUAGE_ITALIAN], token );
5732 if ( !CL_CheckTranslationString( original, translated[LANGUAGE_ITALIAN] ) ) {
5733 Com_Printf( S_COLOR_YELLOW "WARNING: Translation formatting doesn't match up with English version!\n" );
5734 aborted = qtrue;
5735 break;
5736 }
5737
5738 // spanish
5739 token = COM_Parse( &text_p );
5740 if ( Q_stricmp( "spanish", token ) ) {
5741 aborted = qtrue;
5742 break;
5743 }
5744
5745 token = COM_Parse( &text_p );
5746 strcpy( translated[LANGUAGE_SPANISH], token );
5747 if ( !CL_CheckTranslationString( original, translated[LANGUAGE_SPANISH] ) ) {
5748 Com_Printf( S_COLOR_YELLOW "WARNING: Translation formatting doesn't match up with English version!\n" );
5749 aborted = qtrue;
5750 break;
5751 }
5752
5753 // do lookup
5754 t = LookupTrans( original, NULL, qtrue );
5755
5756 if ( t ) {
5757 t->fromFile = qtrue;
5758
5759 for ( i = 0; i < MAX_LANGUAGES; i++ )
5760 strncpy( t->translated[i], translated[i], MAX_TRANS_STRING );
5761 }
5762
5763 token = COM_Parse( &text_p );
5764
5765 // set offset if we have one
5766 if ( !Q_stricmp( "offset", token ) ) {
5767 if ( t )
5768 {
5769 token = COM_Parse( &text_p );
5770 t->x_offset = atof( token );
5771
5772 token = COM_Parse( &text_p );
5773 t->y_offset = atof( token );
5774
5775 token = COM_Parse( &text_p );
5776 }
5777 }
5778
5779 if ( Q_stricmp( "}", token ) ) {
5780 aborted = qtrue;
5781 break;
5782 }
5783
5784 count++;
5785 } while ( token );
5786
5787 if ( aborted ) {
5788 int i, line = 1;
5789
5790 for ( i = 0; i < len && ( text + i ) < text_p; i++ ) {
5791 if ( text[i] == '\n' ) {
5792 line++;
5793 }
5794 }
5795
5796 Com_Printf( S_COLOR_YELLOW "WARNING: Problem loading %s on line %i\n", fileName, line );
5797 cl.corruptedTranslationFile = qtrue;
5798 } else {
5799 Com_Printf( "Loaded %i translation strings from %s\n", count, fileName );
5800 }
5801
5802 // cleanup
5803 free( text );
5804 }
5805
5806 /*
5807 =======================
5808 CL_ReloadTranslation
5809 =======================
5810 */
CL_ReloadTranslation(void)5811 void CL_ReloadTranslation( void ) {
5812 char **fileList;
5813 int numFiles, i;
5814 char fileName[MAX_QPATH];
5815
5816 for ( i = 0; i < FILE_HASH_SIZE; i++ ) {
5817 if ( transTable[i] ) {
5818 free( transTable[i] );
5819 }
5820 }
5821
5822 memset( transTable, 0, sizeof( trans_t* ) * FILE_HASH_SIZE );
5823 CL_LoadTransTable( "scripts/translation.cfg" );
5824
5825 fileList = FS_ListFiles( "translations", ".cfg", &numFiles );
5826
5827 for ( i = 0; i < numFiles; i++ ) {
5828 Com_sprintf( fileName, sizeof (fileName), "translations/%s", fileList[i] );
5829 CL_LoadTransTable( fileName );
5830 }
5831 }
5832
5833 /*
5834 =======================
5835 CL_InitTranslation
5836 =======================
5837 */
CL_InitTranslation(void)5838 void CL_InitTranslation( void ) {
5839 char **fileList;
5840 int numFiles, i;
5841 char fileName[MAX_QPATH];
5842
5843 memset( transTable, 0, sizeof( trans_t* ) * FILE_HASH_SIZE );
5844 CL_LoadTransTable( "scripts/translation.cfg" );
5845
5846 fileList = FS_ListFiles( "translations", ".cfg", &numFiles );
5847
5848 for ( i = 0; i < numFiles; i++ ) {
5849 Com_sprintf( fileName, sizeof (fileName), "translations/%s", fileList[i] );
5850 CL_LoadTransTable( fileName );
5851 }
5852 }
5853
5854 /*
5855 =======================
5856 CL_TranslateString
5857 =======================
5858 */
CL_TranslateString(const char * string,char * dest_buffer)5859 void CL_TranslateString( const char *string, char *dest_buffer ) {
5860 int i, count, currentLanguage;
5861 trans_t *t;
5862 qboolean newline = qfalse;
5863 char *buf;
5864
5865 buf = dest_buffer;
5866 currentLanguage = cl_language->integer - 1;
5867
5868 // early bail if we only want english or bad language type
5869 if ( !string ) {
5870 strcpy( buf, "(null)" );
5871 return;
5872 } else if ( currentLanguage == -1 || currentLanguage >= MAX_LANGUAGES || !strlen( string ) ) {
5873 strcpy( buf, string );
5874 return;
5875 }
5876 // ignore newlines
5877 if ( string[strlen( string ) - 1] == '\n' ) {
5878 newline = qtrue;
5879 }
5880
5881 for ( i = 0, count = 0; string[i] != '\0'; i++ ) {
5882 if ( string[i] != '\n' ) {
5883 buf[count++] = string[i];
5884 }
5885 }
5886 buf[count] = '\0';
5887
5888 t = LookupTrans( buf, NULL, qfalse );
5889
5890 if ( t && strlen( t->translated[currentLanguage] ) ) {
5891 int offset = 0;
5892
5893 if ( cl_debugTranslation->integer >= 1 ) {
5894 buf[0] = '^';
5895 buf[1] = '1';
5896 buf[2] = '[';
5897 offset = 3;
5898 }
5899
5900 strcpy( buf + offset, t->translated[currentLanguage] );
5901
5902 if ( cl_debugTranslation->integer >= 1 ) {
5903 int len2 = strlen( buf );
5904
5905 buf[len2] = ']';
5906 buf[len2 + 1] = '^';
5907 buf[len2 + 2] = '7';
5908 buf[len2 + 3] = '\0';
5909 }
5910
5911 if ( newline ) {
5912 int len2 = strlen( buf );
5913
5914 buf[len2] = '\n';
5915 buf[len2 + 1] = '\0';
5916 }
5917 } else {
5918 int offset = 0;
5919
5920 if ( cl_debugTranslation->integer >= 1 ) {
5921 buf[0] = '^';
5922 buf[1] = '1';
5923 buf[2] = '[';
5924 offset = 3;
5925 }
5926
5927 strcpy( buf + offset, string );
5928
5929 if ( cl_debugTranslation->integer >= 1 ) {
5930 int len2 = strlen( buf );
5931 qboolean addnewline = qfalse;
5932
5933 if ( buf[len2 - 1] == '\n' ) {
5934 len2--;
5935 addnewline = qtrue;
5936 }
5937
5938 buf[len2] = ']';
5939 buf[len2 + 1] = '^';
5940 buf[len2 + 2] = '7';
5941 buf[len2 + 3] = '\0';
5942
5943 if ( addnewline ) {
5944 buf[len2 + 3] = '\n';
5945 buf[len2 + 4] = '\0';
5946 }
5947 }
5948 }
5949 }
5950
5951 /*
5952 =======================
5953 CL_TranslateStringBuf
5954 TTimo - handy, stores in a static buf, converts \n to chr(13)
5955 =======================
5956 */
CL_TranslateStringBuf(const char * string)5957 const char* CL_TranslateStringBuf( const char *string ) {
5958 char *p;
5959 int i,l;
5960 static char buf[MAX_VA_STRING];
5961 CL_TranslateString( string, buf );
5962 while ( ( p = strstr( buf, "\\n" ) ) )
5963 {
5964 *p = '\n';
5965 p++;
5966 // Com_Memcpy(p, p+1, strlen(p) ); b0rks on win32
5967 l = strlen( p );
5968 for ( i = 0; i < l; i++ )
5969 {
5970 *p = *( p + 1 );
5971 p++;
5972 }
5973 }
5974 return buf;
5975 }
5976
5977 /*
5978 =======================
5979 CL_OpenURLForCvar
5980 =======================
5981 */
CL_OpenURL(const char * url)5982 void CL_OpenURL( const char *url ) {
5983 if ( !url || !strlen( url ) ) {
5984 Com_Printf( "%s", CL_TranslateStringBuf( "invalid/empty URL\n" ) );
5985 return;
5986 }
5987 Sys_OpenURL( url, qtrue );
5988 }
5989