ref: 0cd2ccb2e2bc82eba1dc0a56c287f66a91fa1329
dir: /server/sv_user.c/
/* Copyright (C) 1997-2001 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // sv_user.c -- server code for moving users #include <u.h> #include <libc.h> #include <stdio.h> #include "../q_shared.h" edict_t *sv_player; /* ============================================================ USER STRINGCMD EXECUTION sv_client and sv_player will be valid. ============================================================ */ /* ================== SV_BeginDemoServer ================== */ void SV_BeginDemoserver (void) { char name[MAX_OSPATH]; Com_sprintf (name, sizeof(name), "demos/%s", sv.name); FS_FOpenFile (name, &sv.demofile); if (!sv.demofile) Com_Error (ERR_DROP, "Couldn't open %s\n", name); } /* ================ SV_New_f Sends the first message from the server to a connected client. This will be sent on the initial connection and upon each server load. ================ */ void SV_New_f (void) { char *gamedir; int playernum; edict_t *ent; Com_DPrintf ("New() from %s\n", sv_client->name); if (sv_client->state != cs_connected) { Com_Printf ("New not valid -- already spawned\n"); return; } // demo servers just dump the file message if (sv.state == ss_demo) { SV_BeginDemoserver (); return; } // // serverdata needs to go over for all types of servers // to make sure the protocol is right, and to set the gamedir // gamedir = Cvar_VariableString ("gamedir"); // send the serverdata MSG_WriteByte (&sv_client->netchan.message, svc_serverdata); MSG_WriteLong (&sv_client->netchan.message, PROTOCOL_VERSION); MSG_WriteLong (&sv_client->netchan.message, svs.spawncount); MSG_WriteByte (&sv_client->netchan.message, sv.attractloop); MSG_WriteString (&sv_client->netchan.message, gamedir); if (sv.state == ss_cinematic || sv.state == ss_pic) playernum = -1; else playernum = sv_client - svs.clients; MSG_WriteShort (&sv_client->netchan.message, playernum); // send full levelname MSG_WriteString (&sv_client->netchan.message, sv.configstrings[CS_NAME]); // // game server // if (sv.state == ss_game) { // set up the entity for the client ent = EDICT_NUM(playernum+1); ent->s.number = playernum+1; sv_client->edict = ent; memset (&sv_client->lastcmd, 0, sizeof(sv_client->lastcmd)); // begin fetching configstrings MSG_WriteByte (&sv_client->netchan.message, svc_stufftext); MSG_WriteString (&sv_client->netchan.message, va("cmd configstrings %i 0\n",svs.spawncount) ); } } /* ================== SV_Configstrings_f ================== */ void SV_Configstrings_f (void) { int start; Com_DPrintf ("Configstrings() from %s\n", sv_client->name); if (sv_client->state != cs_connected) { Com_Printf ("configstrings not valid -- already spawned\n"); return; } // handle the case of a level changing while a client was connecting if ( atoi(Cmd_Argv(1)) != svs.spawncount ) { Com_Printf ("SV_Configstrings_f from different level\n"); SV_New_f (); return; } start = atoi(Cmd_Argv(2)); // write a packet full of data while ( sv_client->netchan.message.cursize < MAX_MSGLEN/2 && start < MAX_CONFIGSTRINGS) { if (sv.configstrings[start][0]) { MSG_WriteByte (&sv_client->netchan.message, svc_configstring); MSG_WriteShort (&sv_client->netchan.message, start); MSG_WriteString (&sv_client->netchan.message, sv.configstrings[start]); } start++; } // send next command if (start == MAX_CONFIGSTRINGS) { MSG_WriteByte (&sv_client->netchan.message, svc_stufftext); MSG_WriteString (&sv_client->netchan.message, va("cmd baselines %i 0\n",svs.spawncount) ); } else { MSG_WriteByte (&sv_client->netchan.message, svc_stufftext); MSG_WriteString (&sv_client->netchan.message, va("cmd configstrings %i %i\n",svs.spawncount, start) ); } } /* ================== SV_Baselines_f ================== */ void SV_Baselines_f (void) { int start; entity_state_t nullstate; entity_state_t *base; Com_DPrintf ("Baselines() from %s\n", sv_client->name); if (sv_client->state != cs_connected) { Com_Printf ("baselines not valid -- already spawned\n"); return; } // handle the case of a level changing while a client was connecting if ( atoi(Cmd_Argv(1)) != svs.spawncount ) { Com_Printf ("SV_Baselines_f from different level\n"); SV_New_f (); return; } start = atoi(Cmd_Argv(2)); memset (&nullstate, 0, sizeof(nullstate)); // write a packet full of data while ( sv_client->netchan.message.cursize < MAX_MSGLEN/2 && start < MAX_EDICTS) { base = &sv.baselines[start]; if (base->modelindex || base->sound || base->effects) { MSG_WriteByte (&sv_client->netchan.message, svc_spawnbaseline); MSG_WriteDeltaEntity (&nullstate, base, &sv_client->netchan.message, true, true); } start++; } // send next command if (start == MAX_EDICTS) { MSG_WriteByte (&sv_client->netchan.message, svc_stufftext); MSG_WriteString (&sv_client->netchan.message, va("precache %i\n", svs.spawncount) ); } else { MSG_WriteByte (&sv_client->netchan.message, svc_stufftext); MSG_WriteString (&sv_client->netchan.message, va("cmd baselines %i %i\n",svs.spawncount, start) ); } } /* ================== SV_Begin_f ================== */ void SV_Begin_f (void) { Com_DPrintf ("Begin() from %s\n", sv_client->name); // handle the case of a level changing while a client was connecting if ( atoi(Cmd_Argv(1)) != svs.spawncount ) { Com_Printf ("SV_Begin_f from different level\n"); SV_New_f (); return; } sv_client->state = cs_spawned; // call the game begin function ge->ClientBegin (sv_player); Cbuf_InsertFromDefer (); } //============================================================================= /* ================== SV_NextDownload_f ================== */ void SV_NextDownload_f (void) { int r; int percent; int size; if (!sv_client->download) return; r = sv_client->downloadsize - sv_client->downloadcount; if (r > 1024) r = 1024; MSG_WriteByte (&sv_client->netchan.message, svc_download); MSG_WriteShort (&sv_client->netchan.message, r); sv_client->downloadcount += r; size = sv_client->downloadsize; if (!size) size = 1; percent = sv_client->downloadcount*100/size; MSG_WriteByte (&sv_client->netchan.message, percent); SZ_Write (&sv_client->netchan.message, sv_client->download + sv_client->downloadcount - r, r); if (sv_client->downloadcount != sv_client->downloadsize) return; FS_FreeFile (sv_client->download); sv_client->download = NULL; } /* ================== SV_BeginDownload_f ================== */ void SV_BeginDownload_f(void) { char *name; extern cvar_t *allow_download; extern cvar_t *allow_download_players; extern cvar_t *allow_download_models; extern cvar_t *allow_download_sounds; extern cvar_t *allow_download_maps; extern int file_from_pak; // ZOID did file come from pak? int offset = 0; name = Cmd_Argv(1); if (Cmd_Argc() > 2) offset = atoi(Cmd_Argv(2)); // downloaded offset // hacked by zoid to allow more conrol over download // first off, no .. or global allow check if (strstr (name, "..") || !allow_download->value // leading dot is no good || *name == '.' // leading slash bad as well, must be in subdir || *name == '/' // next up, skin check || (strncmp(name, "players/", 6) == 0 && !allow_download_players->value) // now models || (strncmp(name, "models/", 6) == 0 && !allow_download_models->value) // now sounds || (strncmp(name, "sound/", 6) == 0 && !allow_download_sounds->value) // now maps (note special case for maps, must not be in pak) || (strncmp(name, "maps/", 6) == 0 && !allow_download_maps->value) // MUST be in a subdirectory || !strstr (name, "/") ) { // don't allow anything with .. path MSG_WriteByte (&sv_client->netchan.message, svc_download); MSG_WriteShort (&sv_client->netchan.message, -1); MSG_WriteByte (&sv_client->netchan.message, 0); return; } if (sv_client->download) FS_FreeFile (sv_client->download); sv_client->downloadsize = FS_LoadFile (name, (void **)&sv_client->download); sv_client->downloadcount = offset; if (offset > sv_client->downloadsize) sv_client->downloadcount = sv_client->downloadsize; if (!sv_client->download // special check for maps, if it came from a pak file, don't allow // download ZOID || (strncmp(name, "maps/", 5) == 0 && file_from_pak)) { Com_DPrintf ("Couldn't download %s to %s\n", name, sv_client->name); if (sv_client->download) { FS_FreeFile (sv_client->download); sv_client->download = NULL; } MSG_WriteByte (&sv_client->netchan.message, svc_download); MSG_WriteShort (&sv_client->netchan.message, -1); MSG_WriteByte (&sv_client->netchan.message, 0); return; } SV_NextDownload_f (); Com_DPrintf ("Downloading %s to %s\n", name, sv_client->name); } //============================================================================ /* ================= SV_Disconnect_f The client is going to disconnect, so remove the connection immediately ================= */ void SV_Disconnect_f (void) { // SV_EndRedirect (); SV_DropClient (sv_client); } /* ================== SV_ShowServerinfo_f Dumps the serverinfo info string ================== */ void SV_ShowServerinfo_f (void) { Info_Print (Cvar_Serverinfo()); } void SV_Nextserver (void) { char *v; //ZOID, ss_pic can be nextserver'd in coop mode if (sv.state == ss_game || (sv.state == ss_pic && !Cvar_VariableValue("coop"))) return; // can't nextserver while playing a normal game svs.spawncount++; // make sure another doesn't sneak in v = Cvar_VariableString ("nextserver"); if (!v[0]) Cbuf_AddText ("killserver\n"); else { Cbuf_AddText (v); Cbuf_AddText ("\n"); } Cvar_Set ("nextserver",""); } /* ================== SV_Nextserver_f A cinematic has completed or been aborted by a client, so move to the next server, ================== */ void SV_Nextserver_f (void) { if ( atoi(Cmd_Argv(1)) != svs.spawncount ) { Com_DPrintf ("Nextserver() from wrong level, from %s\n", sv_client->name); return; // leftover from last server } Com_DPrintf ("Nextserver() from %s\n", sv_client->name); SV_Nextserver (); } typedef struct { char *name; void (*func) (void); } ucmd_t; ucmd_t ucmds[] = { // auto issued {"new", SV_New_f}, {"configstrings", SV_Configstrings_f}, {"baselines", SV_Baselines_f}, {"begin", SV_Begin_f}, {"nextserver", SV_Nextserver_f}, {"disconnect", SV_Disconnect_f}, // issued by hand at client consoles {"info", SV_ShowServerinfo_f}, {"download", SV_BeginDownload_f}, {"nextdl", SV_NextDownload_f}, {NULL, NULL} }; /* ================== SV_ExecuteUserCommand ================== */ void SV_ExecuteUserCommand (char *s) { ucmd_t *u; Cmd_TokenizeString (s, true); sv_player = sv_client->edict; // SV_BeginRedirect (RD_CLIENT); for (u=ucmds ; u->name ; u++) if (!strcmp (Cmd_Argv(0), u->name) ) { u->func (); break; } if (!u->name && sv.state == ss_game) ge->ClientCommand (sv_player); // SV_EndRedirect (); } /* =========================================================================== USER CMD EXECUTION =========================================================================== */ void SV_ClientThink (client_t *cl, usercmd_t *cmd) { cl->commandMsec -= cmd->msec; if (cl->commandMsec < 0 && sv_enforcetime->value ) { Com_DPrintf ("commandMsec underflow from %s\n", cl->name); return; } ge->ClientThink (cl->edict, cmd); } #define MAX_STRINGCMDS 8 /* =================== SV_ExecuteClientMessage The current net_message is parsed for the given client =================== */ void SV_ExecuteClientMessage (client_t *cl) { int c; char *s; usercmd_t nullcmd; usercmd_t oldest, oldcmd, newcmd; int net_drop; int stringCmdCount; int checksum, calculatedChecksum; int checksumIndex; qboolean move_issued; int lastframe; sv_client = cl; sv_player = sv_client->edict; // only allow one move command move_issued = false; stringCmdCount = 0; while (1) { if (net_message.readcount > net_message.cursize) { Com_Printf ("SV_ReadClientMessage: badread\n"); SV_DropClient (cl); return; } c = MSG_ReadByte (&net_message); if (c == -1) break; switch (c) { default: Com_Printf ("SV_ReadClientMessage: unknown command char\n"); SV_DropClient (cl); return; case clc_nop: break; case clc_userinfo: strncpy (cl->userinfo, MSG_ReadString (&net_message), sizeof(cl->userinfo)-1); SV_UserinfoChanged (cl); break; case clc_move: if (move_issued) return; // someone is trying to cheat... move_issued = true; checksumIndex = net_message.readcount; checksum = MSG_ReadByte (&net_message); lastframe = MSG_ReadLong (&net_message); if (lastframe != cl->lastframe) { cl->lastframe = lastframe; if (cl->lastframe > 0) { cl->frame_latency[cl->lastframe&(LATENCY_COUNTS-1)] = svs.realtime - cl->frames[cl->lastframe & UPDATE_MASK].senttime; } } memset (&nullcmd, 0, sizeof(nullcmd)); MSG_ReadDeltaUsercmd (&net_message, &nullcmd, &oldest); MSG_ReadDeltaUsercmd (&net_message, &oldest, &oldcmd); MSG_ReadDeltaUsercmd (&net_message, &oldcmd, &newcmd); if ( cl->state != cs_spawned ) { cl->lastframe = -1; break; } // if the checksum fails, ignore the rest of the packet calculatedChecksum = COM_BlockSequenceCRCByte ( net_message.data + checksumIndex + 1, net_message.readcount - checksumIndex - 1, cl->netchan.incoming_sequence); if (calculatedChecksum != checksum) { Com_DPrintf ("Failed command checksum for %s (%d != %d)/%d\n", cl->name, calculatedChecksum, checksum, cl->netchan.incoming_sequence); return; } if (!sv_paused->value) { net_drop = cl->netchan.dropped; if (net_drop < 20) { //if (net_drop > 2) // Com_Printf ("drop %i\n", net_drop); while (net_drop > 2) { SV_ClientThink (cl, &cl->lastcmd); net_drop--; } if (net_drop > 1) SV_ClientThink (cl, &oldest); if (net_drop > 0) SV_ClientThink (cl, &oldcmd); } SV_ClientThink (cl, &newcmd); } cl->lastcmd = newcmd; break; case clc_stringcmd: s = MSG_ReadString (&net_message); // malicious users may try using too many string commands if (++stringCmdCount < MAX_STRINGCMDS) SV_ExecuteUserCommand (s); if (cl->state == cs_zombie) return; // disconnect command break; } } }