/* * 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 "server.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, FS_READ); 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)); if (start < 0) { start = 0; /* sku - catch negative offsets */ } /* write a packet full of data */ while (sv_client->netchan.message.cursize < MAX_MSGLEN / 2 && start < MAX_CONFIGSTRINGS) { if (sv.configstrings[start][0]) { int length; /* * sku - write configstrings that exceed MAX_QPATH in * proper-sized chunks */ length = strlen(sv.configstrings[start]); if (length > MAX_QPATH) { length = MAX_QPATH; } MSG_WriteByte(&sv_client->netchan.message, svc_configstring); MSG_WriteShort(&sv_client->netchan.message, start); /* * MSG_WriteString (&sv_client->netchan.message, * sv.configstrings[start]); */ SZ_Write(&sv_client->netchan.message, sv.configstrings[start], length); MSG_WriteByte(&sv_client->netchan.message, 0); } 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)); if (start < 0) { start = 0; } 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; 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/", 8) == 0 && !allow_download_players->value) /* now models */ || (strncmp(name, "models/", 7) == 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/", 5) == 0 && !allow_download_maps->value) /* MUST be in a subdirectory, unless a pk3 */ || (!strstr(name, "/") && strcmp(name + strlen(name) - 4, ".pk3") && strcmp(name + strlen(name) - 4, ".pk2") && strcmp(name + strlen(name) - 4, ".zip"))) { /* 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] && sv_killserver->value) 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; /******* * Security Fix... This is being set to false so that client's can't * macro expand variables on the server. It seems unlikely that a * client ever ought to need to be able to do this... * old line = Cmd_TokenizeString (s, true); *******/ Cmd_TokenizeString(s, false); 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: Q_strncpyz(cl->userinfo, MSG_ReadString(&net_message), sizeof(cl->userinfo)); 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; } } }