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