1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 */
20 // sv_user.c -- server code for moving users
21 
22 #include "server.h"
23 
24 edict_t	*sv_player;
25 
26 /*
27 ============================================================
28 
29 USER STRINGCMD EXECUTION
30 
31 sv_client and sv_player will be valid.
32 ============================================================
33 */
34 
35 /*
36 ==================
37 SV_BeginDemoServer
38 ==================
39 */
SV_BeginDemoserver(void)40 void SV_BeginDemoserver (void)
41 {
42 	char		name[MAX_OSPATH];
43 
44 	Com_sprintf (name, sizeof(name), "demos/%s", sv.name);
45 	FS_FOpenFile (name, &sv.demofile);
46 	if (!sv.demofile)
47 		Com_Error (ERR_DROP, "Couldn't open %s\n", name);
48 }
49 
50 /*
51 ================
52 SV_New_f
53 
54 Sends the first message from the server to a connected client.
55 This will be sent on the initial connection and upon each server load.
56 ================
57 */
SV_New_f(void)58 void SV_New_f (void)
59 {
60 	char		*gamedir;
61 	int			playernum;
62 	edict_t		*ent;
63 
64 	Com_DPrintf ("New() from %s\n", sv_client->name);
65 
66 	if (sv_client->state != cs_connected)
67 	{
68 		Com_Printf ("New not valid -- already spawned\n");
69 		return;
70 	}
71 
72 	// demo servers just dump the file message
73 	if (sv.state == ss_demo)
74 	{
75 		SV_BeginDemoserver ();
76 		return;
77 	}
78 
79 	//
80 	// serverdata needs to go over for all types of servers
81 	// to make sure the protocol is right, and to set the gamedir
82 	//
83 	gamedir = Cvar_VariableString ("gamedir");
84 
85 	// send the serverdata
86 	MSG_WriteByte (&sv_client->netchan.message, svc_serverdata);
87 	MSG_WriteLong (&sv_client->netchan.message, PROTOCOL_VERSION);
88 	MSG_WriteLong (&sv_client->netchan.message, svs.spawncount);
89 	MSG_WriteByte (&sv_client->netchan.message, sv.attractloop);
90 	MSG_WriteString (&sv_client->netchan.message, gamedir);
91 
92 	if (sv.state == ss_cinematic || sv.state == ss_pic)
93 		playernum = -1;
94 	else
95 		playernum = sv_client - svs.clients;
96 	MSG_WriteShort (&sv_client->netchan.message, playernum);
97 
98 	// send full levelname
99 	MSG_WriteString (&sv_client->netchan.message, sv.configstrings[CS_NAME]);
100 
101 	//
102 	// game server
103 	//
104 	if (sv.state == ss_game)
105 	{
106 		// set up the entity for the client
107 		ent = EDICT_NUM(playernum+1);
108 		ent->s.number = playernum+1;
109 		sv_client->edict = ent;
110 		memset (&sv_client->lastcmd, 0, sizeof(sv_client->lastcmd));
111 
112 		// begin fetching configstrings
113 		MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
114 		MSG_WriteString (&sv_client->netchan.message, va("cmd configstrings %i 0\n",svs.spawncount) );
115 	}
116 
117 }
118 
119 /*
120 ==================
121 SV_Configstrings_f
122 ==================
123 */
SV_Configstrings_f(void)124 void SV_Configstrings_f (void)
125 {
126 	int			start;
127 
128 	Com_DPrintf ("Configstrings() from %s\n", sv_client->name);
129 
130 	if (sv_client->state != cs_connected)
131 	{
132 		Com_Printf ("configstrings not valid -- already spawned\n");
133 		return;
134 	}
135 
136 	// handle the case of a level changing while a client was connecting
137 	if ( atoi(Cmd_Argv(1)) != svs.spawncount )
138 	{
139 		Com_Printf ("SV_Configstrings_f from different level\n");
140 		SV_New_f ();
141 		return;
142 	}
143 
144 	start = atoi(Cmd_Argv(2));
145 	if( start < 0 ) {
146 		start = 0;	// sku - catch negative offsets
147 	}
148 
149 	// write a packet full of data
150 
151 	while ( sv_client->netchan.message.cursize < MAX_MSGLEN/2
152 		&& start < MAX_CONFIGSTRINGS)
153 	{
154 		if (sv.configstrings[start][0])
155 		{
156 			int length;
157 
158 			// sku - write configstrings that exceed MAX_QPATH in proper-sized chunks
159 			length = strlen( sv.configstrings[start] );
160 			if( length > MAX_QPATH ) {
161 				length = MAX_QPATH;
162 			}
163 
164 			MSG_WriteByte (&sv_client->netchan.message, svc_configstring);
165 			MSG_WriteShort (&sv_client->netchan.message, start);
166 			SZ_Write (&sv_client->netchan.message, sv.configstrings[start], length);
167 			MSG_WriteByte (&sv_client->netchan.message, 0);
168 		}
169 		start++;
170 	}
171 
172 	// send next command
173 
174 	if (start == MAX_CONFIGSTRINGS)
175 	{
176 		MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
177 		MSG_WriteString (&sv_client->netchan.message, va("cmd baselines %i 0\n",svs.spawncount) );
178 	}
179 	else
180 	{
181 		MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
182 		MSG_WriteString (&sv_client->netchan.message, va("cmd configstrings %i %i\n",svs.spawncount, start) );
183 	}
184 }
185 
186 /*
187 ==================
188 SV_Baselines_f
189 ==================
190 */
SV_Baselines_f(void)191 void SV_Baselines_f (void)
192 {
193 	int		start;
194 	entity_state_t	nullstate;
195 	entity_state_t	*base;
196 
197 	Com_DPrintf ("Baselines() from %s\n", sv_client->name);
198 
199 	if (sv_client->state != cs_connected)
200 	{
201 		Com_Printf ("baselines not valid -- already spawned\n");
202 		return;
203 	}
204 
205 	// handle the case of a level changing while a client was connecting
206 	if ( atoi(Cmd_Argv(1)) != svs.spawncount )
207 	{
208 		Com_Printf ("SV_Baselines_f from different level\n");
209 		SV_New_f ();
210 		return;
211 	}
212 
213 	start = atoi(Cmd_Argv(2));
214 	if( start < 0 ) {
215 		start = 0;
216 	}
217 
218 	memset (&nullstate, 0, sizeof(nullstate));
219 
220 	// write a packet full of data
221 
222 	while ( sv_client->netchan.message.cursize <  MAX_MSGLEN/2
223 		&& start < MAX_EDICTS)
224 	{
225 		base = &sv.baselines[start];
226 		if (base->modelindex || base->sound || base->effects)
227 		{
228 			MSG_WriteByte (&sv_client->netchan.message, svc_spawnbaseline);
229 			MSG_WriteDeltaEntity (&nullstate, base, &sv_client->netchan.message, true, true);
230 		}
231 		start++;
232 	}
233 
234 	// send next command
235 
236 	if (start == MAX_EDICTS)
237 	{
238 		MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
239 		MSG_WriteString (&sv_client->netchan.message, va("precache %i\n", svs.spawncount) );
240 	}
241 	else
242 	{
243 		MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
244 		MSG_WriteString (&sv_client->netchan.message, va("cmd baselines %i %i\n",svs.spawncount, start) );
245 	}
246 }
247 
248 /*
249 ==================
250 SV_Begin_f
251 ==================
252 */
SV_Begin_f(void)253 void SV_Begin_f (void)
254 {
255 	Com_DPrintf ("Begin() from %s\n", sv_client->name);
256 
257 	// handle the case of a level changing while a client was connecting
258 	if ( atoi(Cmd_Argv(1)) != svs.spawncount )
259 	{
260 		Com_Printf ("SV_Begin_f from different level\n");
261 		SV_New_f ();
262 		return;
263 	}
264 
265 	sv_client->state = cs_spawned;
266 
267 	// call the game begin function
268 	ge->ClientBegin (sv_player);
269 
270 	Cbuf_InsertFromDefer ();
271 }
272 
273 //=============================================================================
274 
275 /*
276 ==================
277 SV_NextDownload_f
278 ==================
279 */
SV_NextDownload_f(void)280 void SV_NextDownload_f (void)
281 {
282 	int		r;
283 	int		percent;
284 	int		size;
285 
286 	if (!sv_client->download)
287 		return;
288 
289 	r = sv_client->downloadsize - sv_client->downloadcount;
290 	if (r > 1024)
291 		r = 1024;
292 
293 	MSG_WriteByte (&sv_client->netchan.message, svc_download);
294 	MSG_WriteShort (&sv_client->netchan.message, r);
295 
296 	sv_client->downloadcount += r;
297 	size = sv_client->downloadsize;
298 	if (!size)
299 		size = 1;
300 	percent = sv_client->downloadcount*100/size;
301 	MSG_WriteByte (&sv_client->netchan.message, percent);
302 	SZ_Write (&sv_client->netchan.message,
303 		sv_client->download + sv_client->downloadcount - r, r);
304 
305 	if (sv_client->downloadcount != sv_client->downloadsize)
306 		return;
307 
308 	FS_FreeFile (sv_client->download);
309 	sv_client->download = NULL;
310 }
311 
312 /*
313 ==================
314 SV_BeginDownload_f
315 ==================
316 */
SV_BeginDownload_f(void)317 void SV_BeginDownload_f(void)
318 {
319 	char	*name;
320 	extern	cvar_t *allow_download;
321 	extern	cvar_t *allow_download_players;
322 	extern	cvar_t *allow_download_models;
323 	extern	cvar_t *allow_download_sounds;
324 	extern	cvar_t *allow_download_maps;
325 	extern	int		file_from_pak; // ZOID did file come from pak?
326 	int offset = 0;
327 
328 	name = Cmd_Argv(1);
329 
330 	if (Cmd_Argc() > 2)
331 		offset = atoi(Cmd_Argv(2)); // downloaded offset
332 
333 	// hacked by zoid to allow more conrol over download
334 	// first off, no .. or global allow check
335 	if (strstr (name, "..") || !allow_download->value
336 		// leading dot is no good
337 		|| *name == '.'
338 		// leading slash bad as well, must be in subdir
339 		|| *name == '/'
340 		// next up, skin check
341 		|| (strncmp(name, "players/", 6) == 0 && !allow_download_players->value)
342 		// now models
343 		|| (strncmp(name, "models/", 6) == 0 && !allow_download_models->value)
344 		// now sounds
345 		|| (strncmp(name, "sound/", 6) == 0 && !allow_download_sounds->value)
346 		// now maps (note special case for maps, must not be in pak)
347 		|| (strncmp(name, "maps/", 6) == 0 && !allow_download_maps->value)
348 		// MUST be in a subdirectory
349 		|| !strstr (name, "/") )
350 	{	// don't allow anything with .. path
351 		MSG_WriteByte (&sv_client->netchan.message, svc_download);
352 		MSG_WriteShort (&sv_client->netchan.message, -1);
353 		MSG_WriteByte (&sv_client->netchan.message, 0);
354 		return;
355 	}
356 
357 
358 	if (sv_client->download)
359 		FS_FreeFile (sv_client->download);
360 
361 	sv_client->downloadsize = FS_LoadFile (name, (void **)&sv_client->download);
362 	sv_client->downloadcount = offset;
363 
364 	if (offset > sv_client->downloadsize)
365 		sv_client->downloadcount = sv_client->downloadsize;
366 
367 	if (!sv_client->download
368 		// special check for maps, if it came from a pak file, don't allow
369 		// download  ZOID
370 		|| (strncmp(name, "maps/", 5) == 0 && file_from_pak))
371 	{
372 		Com_DPrintf ("Couldn't download %s to %s\n", name, sv_client->name);
373 		if (sv_client->download) {
374 			FS_FreeFile (sv_client->download);
375 			sv_client->download = NULL;
376 		}
377 
378 		MSG_WriteByte (&sv_client->netchan.message, svc_download);
379 		MSG_WriteShort (&sv_client->netchan.message, -1);
380 		MSG_WriteByte (&sv_client->netchan.message, 0);
381 		return;
382 	}
383 
384 	SV_NextDownload_f ();
385 	Com_DPrintf ("Downloading %s to %s\n", name, sv_client->name);
386 }
387 
388 
389 
390 //============================================================================
391 
392 
393 /*
394 =================
395 SV_Disconnect_f
396 
397 The client is going to disconnect, so remove the connection immediately
398 =================
399 */
SV_Disconnect_f(void)400 void SV_Disconnect_f (void)
401 {
402 //	SV_EndRedirect ();
403 	SV_DropClient (sv_client);
404 }
405 
406 
407 /*
408 ==================
409 SV_ShowServerinfo_f
410 
411 Dumps the serverinfo info string
412 ==================
413 */
SV_ShowServerinfo_f(void)414 void SV_ShowServerinfo_f (void)
415 {
416 //	Info_Print (Cvar_Serverinfo());
417 }
418 
419 
SV_Nextserver(void)420 void SV_Nextserver (void)
421 {
422 	char	*v;
423 
424 	//ZOID, ss_pic can be nextserver'd in coop mode
425 	if (sv.state == ss_game || (sv.state == ss_pic && !Cvar_VariableValue("coop")))
426 		return;		// can't nextserver while playing a normal game
427 
428 	svs.spawncount++;	// make sure another doesn't sneak in
429 	v = Cvar_VariableString ("nextserver");
430 	if (!v[0])
431 		Cbuf_AddText ("killserver\n");
432 	else
433 	{
434 		Cbuf_AddText (v);
435 		Cbuf_AddText ("\n");
436 	}
437 	Cvar_Set ("nextserver","");
438 }
439 
440 /*
441 ==================
442 SV_Nextserver_f
443 
444 A cinematic has completed or been aborted by a client, so move
445 to the next server,
446 ==================
447 */
SV_Nextserver_f(void)448 void SV_Nextserver_f (void)
449 {
450 	if ( atoi(Cmd_Argv(1)) != svs.spawncount ) {
451 		Com_DPrintf ("Nextserver() from wrong level, from %s\n", sv_client->name);
452 		return;		// leftover from last server
453 	}
454 
455 	Com_DPrintf ("Nextserver() from %s\n", sv_client->name);
456 
457 	SV_Nextserver ();
458 }
459 
460 typedef struct
461 {
462 	char	*name;
463 	void	(*func) (void);
464 } ucmd_t;
465 
466 ucmd_t ucmds[] =
467 {
468 	// auto issued
469 	{"new", SV_New_f},
470 	{"configstrings", SV_Configstrings_f},
471 	{"baselines", SV_Baselines_f},
472 	{"begin", SV_Begin_f},
473 
474 	{"nextserver", SV_Nextserver_f},
475 
476 	{"disconnect", SV_Disconnect_f},
477 
478 	// issued by hand at client consoles
479 	{"info", SV_ShowServerinfo_f},
480 
481 	{"download", SV_BeginDownload_f},
482 	{"nextdl", SV_NextDownload_f},
483 
484 	{NULL, NULL}
485 };
486 
487 /*
488 ==================
489 SV_ExecuteUserCommand
490 ==================
491 */
SV_ExecuteUserCommand(char * s)492 void SV_ExecuteUserCommand (char *s)
493 {
494 	ucmd_t	*u;
495 
496 	/*******
497 	 * Security Fix... This is being set to false so that client's can't
498 	 * macro expand variables on the server.  It seems unlikely that a
499 	 * client ever ought to need to be able to do this...
500 	 * old line = Cmd_TokenizeString (s, true);
501 	 *******/
502 	Cmd_TokenizeString(s, false);
503 	sv_player = sv_client->edict;
504 
505 //	SV_BeginRedirect (RD_CLIENT);
506 
507 	for (u=ucmds ; u->name ; u++)
508 		if (!strcmp (Cmd_Argv(0), u->name) )
509 		{
510 			u->func ();
511 			break;
512 		}
513 
514 	if (!u->name && sv.state == ss_game)
515 		ge->ClientCommand (sv_player);
516 
517 //	SV_EndRedirect ();
518 }
519 
520 /*
521 ===========================================================================
522 
523 USER CMD EXECUTION
524 
525 ===========================================================================
526 */
527 
528 
529 
SV_ClientThink(client_t * cl,usercmd_t * cmd)530 void SV_ClientThink (client_t *cl, usercmd_t *cmd)
531 
532 {
533 	cl->commandMsec -= cmd->msec;
534 
535 	if (cl->commandMsec < 0 && sv_enforcetime->value )
536 	{
537 		Com_DPrintf ("commandMsec underflow from %s\n", cl->name);
538 		return;
539 	}
540 
541 	ge->ClientThink (cl->edict, cmd);
542 }
543 
544 
545 
546 #define	MAX_STRINGCMDS	8
547 /*
548 ===================
549 SV_ExecuteClientMessage
550 
551 The current net_message is parsed for the given client
552 ===================
553 */
SV_ExecuteClientMessage(client_t * cl)554 void SV_ExecuteClientMessage (client_t *cl)
555 {
556 	int		c;
557 	char	*s;
558 
559 	usercmd_t	nullcmd;
560 	usercmd_t	oldest, oldcmd, newcmd;
561 	int		net_drop;
562 	int		stringCmdCount;
563 	int		checksum, calculatedChecksum;
564 	int		checksumIndex;
565 	qboolean	move_issued;
566 	int		lastframe;
567 
568 	sv_client = cl;
569 	sv_player = sv_client->edict;
570 
571 	// only allow one move command
572 	move_issued = false;
573 	stringCmdCount = 0;
574 
575 	while (1)
576 	{
577 		if (net_message.readcount > net_message.cursize)
578 		{
579 			Com_Printf ("SV_ReadClientMessage: badread\n");
580 			SV_DropClient (cl);
581 			return;
582 		}
583 
584 		c = MSG_ReadByte (&net_message);
585 		if (c == -1)
586 			break;
587 
588 		switch (c)
589 		{
590 		default:
591 			Com_Printf ("SV_ReadClientMessage: unknown command char\n");
592 			SV_DropClient (cl);
593 			return;
594 
595 		case clc_nop:
596 			break;
597 
598 		case clc_userinfo:
599 			strncpy (cl->userinfo, MSG_ReadString (&net_message), sizeof(cl->userinfo)-1);
600 			SV_UserinfoChanged (cl);
601 			break;
602 
603 		case clc_move:
604 			if (move_issued)
605 				return;		// someone is trying to cheat...
606 
607 			move_issued = true;
608 			checksumIndex = net_message.readcount;
609 			checksum = MSG_ReadByte (&net_message);
610 			lastframe = MSG_ReadLong (&net_message);
611 			if (lastframe != cl->lastframe) {
612 				cl->lastframe = lastframe;
613 				if (cl->lastframe > 0) {
614 					cl->frame_latency[cl->lastframe&(LATENCY_COUNTS-1)] =
615 						svs.realtime - cl->frames[cl->lastframe & UPDATE_MASK].senttime;
616 				}
617 			}
618 
619 			memset (&nullcmd, 0, sizeof(nullcmd));
620 			MSG_ReadDeltaUsercmd (&net_message, &nullcmd, &oldest);
621 			MSG_ReadDeltaUsercmd (&net_message, &oldest, &oldcmd);
622 			MSG_ReadDeltaUsercmd (&net_message, &oldcmd, &newcmd);
623 
624 			if ( cl->state != cs_spawned )
625 			{
626 				cl->lastframe = -1;
627 				break;
628 			}
629 
630 			// if the checksum fails, ignore the rest of the packet
631 			calculatedChecksum = COM_BlockSequenceCRCByte (
632 				net_message.data + checksumIndex + 1,
633 				net_message.readcount - checksumIndex - 1,
634 				cl->netchan.incoming_sequence);
635 
636 			if (calculatedChecksum != checksum)
637 			{
638 				Com_DPrintf ("Failed command checksum for %s (%d != %d)/%d\n",
639 					cl->name, calculatedChecksum, checksum,
640 					cl->netchan.incoming_sequence);
641 				return;
642 			}
643 
644 			if (!sv_paused->value)
645 			{
646 				net_drop = cl->netchan.dropped;
647 				if (net_drop < 20)
648 				{
649 
650 //if (net_drop > 2)
651 
652 //	Com_Printf ("drop %i\n", net_drop);
653 					while (net_drop > 2)
654 					{
655 						SV_ClientThink (cl, &cl->lastcmd);
656 
657 						net_drop--;
658 					}
659 					if (net_drop > 1)
660 						SV_ClientThink (cl, &oldest);
661 
662 					if (net_drop > 0)
663 						SV_ClientThink (cl, &oldcmd);
664 
665 				}
666 				SV_ClientThink (cl, &newcmd);
667 			}
668 
669 			cl->lastcmd = newcmd;
670 			break;
671 
672 		case clc_stringcmd:
673 			s = MSG_ReadString (&net_message);
674 
675 			// malicious users may try using too many string commands
676 			if (++stringCmdCount < MAX_STRINGCMDS)
677 				SV_ExecuteUserCommand (s);
678 
679 			if (cl->state == cs_zombie)
680 				return;	// disconnect command
681 			break;
682 		}
683 	}
684 }
685 
686