mirror of
https://github.com/blendogames/thirtyflightsofloving.git
synced 2024-11-15 00:41:21 +00:00
1230 lines
29 KiB
C
1230 lines
29 KiB
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.
|
|
|
|
*/
|
|
|
|
#include "server.h"
|
|
|
|
netadr_t master_adr[MAX_MASTERS]; // address of group servers
|
|
|
|
client_t *sv_client; // current client
|
|
|
|
cvar_t *sv_paused;
|
|
cvar_t *sv_timedemo;
|
|
|
|
cvar_t *sv_enforcetime;
|
|
|
|
cvar_t *timeout; // seconds without any message
|
|
cvar_t *zombietime; // seconds to sink messages after disconnect
|
|
|
|
cvar_t *rcon_password; // password for remote server commands
|
|
|
|
cvar_t *allow_download;
|
|
cvar_t *allow_download_players;
|
|
cvar_t *allow_download_models;
|
|
cvar_t *allow_download_sounds;
|
|
cvar_t *allow_download_maps;
|
|
cvar_t *allow_download_pics;
|
|
cvar_t *allow_download_textures;
|
|
// Knightmare- whether to allow downloading 24-bit textures
|
|
cvar_t *allow_download_textures_24bit;
|
|
|
|
cvar_t *sv_downloadserver; // from R1Q2
|
|
|
|
cvar_t *sv_baselines_maxlen; // Knightmare- max packet size for connect messages
|
|
cvar_t *sv_limit_msglen; // Knightmare- whether to use MAX_MSGLEN_MP for multiplayer games
|
|
|
|
cvar_t *sv_airaccelerate;
|
|
|
|
cvar_t *sv_noreload; // don't reload level state when reentering
|
|
|
|
cvar_t *maxclients; // FIXME: rename sv_maxclients
|
|
cvar_t *sv_showclamp;
|
|
|
|
cvar_t *hostname;
|
|
cvar_t *public_server; // should heartbeats be sent
|
|
|
|
cvar_t *sv_iplimit; // r1ch: max connections from a single IP (prevent DoS)
|
|
|
|
cvar_t *sv_reconnect_limit; // minimum seconds between connect messages
|
|
|
|
cvar_t *sv_entfile; // whether to use .ent file
|
|
|
|
void Master_Shutdown (void);
|
|
|
|
|
|
//============================================================================
|
|
|
|
|
|
/*
|
|
=====================
|
|
SV_DropClient
|
|
|
|
Called when the player is totally leaving the server, either willingly
|
|
or unwillingly. This is NOT called if the entire server is quiting
|
|
or crashing.
|
|
=====================
|
|
*/
|
|
void SV_DropClient (client_t *drop)
|
|
{
|
|
// add the disconnect
|
|
MSG_WriteByte (&drop->netchan.message, svc_disconnect);
|
|
|
|
if (drop->state == cs_spawned)
|
|
{
|
|
// call the prog function for removing a client
|
|
// this will remove the body, among other things
|
|
ge->ClientDisconnect (drop->edict);
|
|
}
|
|
|
|
if (drop->download)
|
|
{
|
|
FS_FreeFile (drop->download);
|
|
drop->download = NULL;
|
|
}
|
|
|
|
// r1ch: fix for mods that don't clean score
|
|
if (drop->edict && drop->edict->client)
|
|
drop->edict->client->ps.stats[STAT_FRAGS] = 0;
|
|
|
|
drop->state = cs_zombie; // become free in a few seconds
|
|
drop->name[0] = 0;
|
|
}
|
|
|
|
|
|
//Knightmare added
|
|
/*
|
|
=====================
|
|
GetClientFromAdr
|
|
|
|
Given an netadr_t, returns the matching client.
|
|
=====================
|
|
*/
|
|
client_t *GetClientFromAdr (netadr_t address)
|
|
{
|
|
client_t *cl;
|
|
int i;
|
|
qboolean found = false;
|
|
|
|
for (i = 0; i < maxclients->value; i++)
|
|
{
|
|
cl = &svs.clients[i];
|
|
if (NET_CompareBaseAdr(cl->netchan.remote_address, address)) {
|
|
found = true; break; }
|
|
}
|
|
if (found)
|
|
return cl;
|
|
else // don't return non-matching client
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
=====================
|
|
SV_DropClientFromAdr
|
|
|
|
Calls SV_DropClient, takes netadr_t instead of client pointer.
|
|
=====================
|
|
*/
|
|
void SV_DropClientFromAdr (netadr_t address)
|
|
{ // adapted Pat Aftermoon's simplified version of this
|
|
client_t *drop = GetClientFromAdr(address);
|
|
|
|
if (!drop) return; // make sure we have a client to drop
|
|
|
|
SV_BroadcastPrintf (PRINT_HIGH, "dropping client %s\n", drop->name);
|
|
|
|
SV_DropClient (drop);
|
|
|
|
drop->state = cs_free; // don't bother with zombie state
|
|
}
|
|
// end Knightmare
|
|
|
|
/*
|
|
=====================
|
|
SV_KickClient
|
|
|
|
From R1Q2
|
|
=====================
|
|
*/
|
|
void SV_KickClient (client_t *cl, const char *reason, const char *cprintf)
|
|
{
|
|
if (reason && cl->state == cs_spawned && cl->name[0])
|
|
SV_BroadcastPrintf (PRINT_HIGH, "%s was dropped: %s\n", cl->name, reason);
|
|
if (cprintf)
|
|
SV_ClientPrintf (cl, PRINT_HIGH, "%s", cprintf);
|
|
Com_Printf ("Dropping %s, %s.\n", cl->name, reason ? reason : "SV_KickClient");
|
|
SV_DropClient (cl);
|
|
}
|
|
|
|
|
|
/*
|
|
=====================
|
|
SV_CleanClient
|
|
|
|
From R1Q2
|
|
r1ch: this does the final cleaning up of a client after zombie state.
|
|
=====================
|
|
*/
|
|
void SV_CleanClient (client_t *drop)
|
|
{
|
|
if (drop->download)
|
|
{
|
|
Z_Free (drop->download);
|
|
drop->download = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
CONNECTIONLESS COMMANDS
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
/*
|
|
===============
|
|
SV_StatusString
|
|
|
|
Builds the string that is sent as heartbeats and status replies
|
|
===============
|
|
*/
|
|
char *SV_StatusString (void)
|
|
{
|
|
char player[1024];
|
|
static char status[MAX_MSGLEN - 16];
|
|
int i;
|
|
client_t *cl;
|
|
int statusLength;
|
|
int playerLength;
|
|
|
|
// strncpy (status, Cvar_Serverinfo());
|
|
// strncat (status, "\n");
|
|
Q_strncpyz (status, Cvar_Serverinfo(), sizeof(status));
|
|
Q_strncatz (status, "\n", sizeof(status));
|
|
statusLength = strlen(status);
|
|
|
|
for (i=0 ; i<maxclients->value ; i++)
|
|
{
|
|
cl = &svs.clients[i];
|
|
if (cl->state == cs_connected || cl->state == cs_spawned )
|
|
{
|
|
Com_sprintf (player, sizeof(player), "%i %i \"%s\"\n",
|
|
cl->edict->client->ps.stats[STAT_FRAGS], cl->ping, cl->name);
|
|
playerLength = strlen(player);
|
|
if (statusLength + playerLength >= sizeof(status) )
|
|
break; // can't hold any more
|
|
// strncpy (status + statusLength, player);
|
|
Q_strncpyz (status + statusLength, player, sizeof(status));
|
|
statusLength += playerLength;
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
================
|
|
SVC_Status
|
|
|
|
Responds with all the info that qplug or qspy can see
|
|
================
|
|
*/
|
|
void SVC_Status (void)
|
|
{
|
|
Netchan_OutOfBandPrint (NS_SERVER, net_from, "print\n%s", SV_StatusString());
|
|
#if 0
|
|
Com_BeginRedirect (RD_PACKET, sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect);
|
|
Com_Printf (SV_StatusString());
|
|
Com_EndRedirect ();
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
================
|
|
SVC_Ack
|
|
|
|
================
|
|
*/
|
|
void SVC_Ack (void)
|
|
{
|
|
Com_Printf ("Ping acknowledge from %s\n", NET_AdrToString(net_from));
|
|
}
|
|
|
|
/*
|
|
================
|
|
SVC_Info
|
|
|
|
Responds with short info for broadcast scans
|
|
The second parameter should be the current protocol version number.
|
|
================
|
|
*/
|
|
void SVC_Info (void)
|
|
{
|
|
char string[64];
|
|
int i, count;
|
|
int version;
|
|
|
|
if (maxclients->value == 1)
|
|
return; // ignore in single player
|
|
|
|
version = atoi (Cmd_Argv(1));
|
|
|
|
if (version != PROTOCOL_VERSION)
|
|
{ // According to r1ch, this can be used to make servers endlessly ping each other
|
|
// Com_sprintf (string, sizeof(string), "%s: wrong version\n", hostname->string, sizeof(string));
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
count = 0;
|
|
for (i=0 ; i<maxclients->value ; i++)
|
|
if (svs.clients[i].state >= cs_connected)
|
|
count++;
|
|
|
|
Com_sprintf (string, sizeof(string), "%16s %8s %2i/%2i\n", hostname->string, sv.name, count, (int)maxclients->value);
|
|
}
|
|
|
|
Netchan_OutOfBandPrint (NS_SERVER, net_from, "info\n%s", string);
|
|
}
|
|
|
|
/*
|
|
================
|
|
SVC_Ping
|
|
|
|
Just responds with an acknowledgement
|
|
================
|
|
*/
|
|
void SVC_Ping (void)
|
|
{
|
|
Netchan_OutOfBandPrint (NS_SERVER, net_from, "ack");
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
SVC_GetChallenge
|
|
|
|
Returns a challenge number that can be used
|
|
in a subsequent client_connect command.
|
|
We do this to prevent denial of service attacks that
|
|
flood the server with invalid connection IPs. With a
|
|
challenge, they must give a valid IP address.
|
|
=================
|
|
*/
|
|
void SVC_GetChallenge (void)
|
|
{
|
|
int i;
|
|
int oldest;
|
|
int oldestTime;
|
|
|
|
oldest = 0;
|
|
oldestTime = 0x7fffffff;
|
|
|
|
// see if we already have a challenge for this ip
|
|
for (i = 0 ; i < MAX_CHALLENGES ; i++)
|
|
{
|
|
if (NET_CompareBaseAdr (net_from, svs.challenges[i].adr))
|
|
break;
|
|
if (svs.challenges[i].time < oldestTime)
|
|
{
|
|
oldestTime = svs.challenges[i].time;
|
|
oldest = i;
|
|
}
|
|
}
|
|
|
|
if (i == MAX_CHALLENGES)
|
|
{
|
|
// overwrite the oldest
|
|
svs.challenges[oldest].challenge = rand() & 0x7fff;
|
|
svs.challenges[oldest].adr = net_from;
|
|
svs.challenges[oldest].time = curtime;
|
|
i = oldest;
|
|
}
|
|
|
|
// send it back
|
|
Netchan_OutOfBandPrint (NS_SERVER, net_from, "challenge %i", svs.challenges[i].challenge);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SVC_DirectConnect
|
|
|
|
A connection request that did not come from the master
|
|
==================
|
|
*/
|
|
void SVC_DirectConnect (void)
|
|
{
|
|
char userinfo[MAX_INFO_STRING];
|
|
netadr_t adr;
|
|
int i;
|
|
client_t *cl, *newcl;
|
|
client_t temp;
|
|
edict_t *ent;
|
|
int edictnum;
|
|
int version;
|
|
int qport;
|
|
int challenge;
|
|
int previousclients; // rich: connection limit per IP
|
|
|
|
adr = net_from;
|
|
|
|
Com_DPrintf ("SVC_DirectConnect ()\n");
|
|
|
|
version = atoi(Cmd_Argv(1));
|
|
if (version != PROTOCOL_VERSION)
|
|
{
|
|
Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nServer is version %4.2f.\n", VERSION);
|
|
Com_DPrintf (" rejected connect from version %i\n", version);
|
|
return;
|
|
}
|
|
|
|
qport = atoi(Cmd_Argv(2));
|
|
|
|
challenge = atoi(Cmd_Argv(3));
|
|
|
|
// r1ch: limit connections from a single IP
|
|
previousclients = 0;
|
|
for (i=0,cl=svs.clients; i<(int)maxclients->value; i++,cl++)
|
|
{
|
|
if (cl->state == cs_free)
|
|
continue;
|
|
if (NET_CompareBaseAdr (adr, cl->netchan.remote_address))
|
|
{
|
|
// r1ch: zombies are less dangerous
|
|
if (cl->state == cs_zombie)
|
|
previousclients++;
|
|
else
|
|
previousclients += 2;
|
|
}
|
|
}
|
|
if (previousclients >= (int)sv_iplimit->value * 2)
|
|
{
|
|
Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nToo many connections from your host.\n");
|
|
Com_DPrintf (" too many connections\n");
|
|
return;
|
|
}
|
|
// end r1ch fix
|
|
|
|
strncpy (userinfo, Cmd_Argv(4), sizeof(userinfo)-1);
|
|
userinfo[sizeof(userinfo) - 1] = 0;
|
|
|
|
// force the IP key/value pair so the game can filter based on ip
|
|
Info_SetValueForKey (userinfo, "ip", NET_AdrToString(net_from));
|
|
|
|
// attractloop servers are ONLY for local clients
|
|
if (sv.attractloop)
|
|
{
|
|
if (!NET_IsLocalAddress (adr))
|
|
{
|
|
Com_Printf ("Remote connect in attract loop. Ignored.\n");
|
|
Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nConnection refused.\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// see if the challenge is valid
|
|
if (!NET_IsLocalAddress (adr))
|
|
{
|
|
for (i=0 ; i<MAX_CHALLENGES ; i++)
|
|
{
|
|
if (NET_CompareBaseAdr (net_from, svs.challenges[i].adr))
|
|
{
|
|
if (challenge == svs.challenges[i].challenge)
|
|
break; // good
|
|
Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nBad challenge.\n");
|
|
return;
|
|
}
|
|
}
|
|
if (i == MAX_CHALLENGES)
|
|
{
|
|
Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nNo challenge for address.\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
newcl = &temp;
|
|
memset (newcl, 0, sizeof(client_t));
|
|
|
|
// if there is already a slot for this ip, reuse it
|
|
for (i=0,cl=svs.clients ; i<maxclients->value ; i++,cl++)
|
|
{
|
|
if (cl->state == cs_free)
|
|
continue;
|
|
if (NET_CompareBaseAdr (adr, cl->netchan.remote_address)
|
|
&& ( cl->netchan.qport == qport
|
|
|| adr.port == cl->netchan.remote_address.port ) )
|
|
{
|
|
if (!NET_IsLocalAddress (adr) && (svs.realtime - cl->lastconnect) < ((int)sv_reconnect_limit->value * 1000))
|
|
{
|
|
Com_DPrintf ("%s:reconnect rejected : too soon\n", NET_AdrToString (adr));
|
|
return;
|
|
}
|
|
// r1ch: !! fix nasty bug where non-disconnected clients (from dropped disconnect
|
|
// packets) could be overwritten!
|
|
if (cl->state != cs_zombie)
|
|
{
|
|
Com_DPrintf (" client already found\n");
|
|
// If we legitly get here, spoofed udp isn't possible (passed challenge) and client addr/port combo
|
|
// is exactly the same, so we can assume its really a dropped/crashed client. i hope...
|
|
Com_Printf ("Dropping %s, ghost reconnect\n", cl->name);
|
|
SV_DropClient (cl);
|
|
}
|
|
// end r1ch fix
|
|
|
|
Com_Printf ("%s:reconnect\n", NET_AdrToString (adr));
|
|
|
|
SV_CleanClient (cl); // r1ch: clean up last client data
|
|
|
|
newcl = cl;
|
|
goto gotnewcl;
|
|
}
|
|
}
|
|
|
|
// find a client slot
|
|
newcl = NULL;
|
|
for (i=0,cl=svs.clients ; i<maxclients->value ; i++,cl++)
|
|
{
|
|
if (cl->state == cs_free)
|
|
{
|
|
newcl = cl;
|
|
break;
|
|
}
|
|
}
|
|
if (!newcl)
|
|
{
|
|
Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nServer is full.\n");
|
|
Com_DPrintf ("Rejected a connection.\n");
|
|
return;
|
|
}
|
|
|
|
gotnewcl:
|
|
// build a new connection
|
|
// accept the new client
|
|
// this is the only place a client_t is ever initialized
|
|
*newcl = temp;
|
|
sv_client = newcl;
|
|
edictnum = (newcl-svs.clients)+1;
|
|
ent = EDICT_NUM(edictnum);
|
|
newcl->edict = ent;
|
|
newcl->challenge = challenge; // save challenge for checksumming
|
|
|
|
// get the game a chance to reject this connection or modify the userinfo
|
|
if (!(ge->ClientConnect (ent, userinfo)))
|
|
{
|
|
if (*Info_ValueForKey (userinfo, "rejmsg"))
|
|
Netchan_OutOfBandPrint (NS_SERVER, adr, "print\n%s\nConnection refused.\n",
|
|
Info_ValueForKey (userinfo, "rejmsg"));
|
|
else
|
|
Netchan_OutOfBandPrint (NS_SERVER, adr, "print\nConnection refused.\n" );
|
|
Com_DPrintf ("Game rejected a connection.\n");
|
|
return;
|
|
}
|
|
|
|
// parse some info from the info strings
|
|
strncpy (newcl->userinfo, userinfo, sizeof(newcl->userinfo)-1);
|
|
SV_UserinfoChanged (newcl);
|
|
|
|
// send the connect packet to the client
|
|
// r1ch: note we could ideally send this twice but it prints unsightly message on original client.
|
|
if (sv_downloadserver->string[0])
|
|
Netchan_OutOfBandPrint (NS_SERVER, adr, "client_connect dlserver=%s", sv_downloadserver->string);
|
|
else
|
|
Netchan_OutOfBandPrint (NS_SERVER, adr, "client_connect");
|
|
|
|
Netchan_Setup (NS_SERVER, &newcl->netchan , adr, qport);
|
|
|
|
newcl->state = cs_connected;
|
|
|
|
SZ_Init (&newcl->datagram, newcl->datagram_buf, sizeof(newcl->datagram_buf) );
|
|
newcl->datagram.allowoverflow = true;
|
|
newcl->lastmessage = svs.realtime; // don't timeout
|
|
newcl->lastconnect = svs.realtime;
|
|
}
|
|
|
|
int Rcon_Validate (void)
|
|
{
|
|
if (!strlen (rcon_password->string))
|
|
return 0;
|
|
|
|
if (strcmp (Cmd_Argv(1), rcon_password->string) )
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
SVC_RemoteCommand
|
|
|
|
A client issued an rcon command.
|
|
Shift down the remaining args
|
|
Redirect all printfs
|
|
===============
|
|
*/
|
|
void SVC_RemoteCommand (void)
|
|
{
|
|
int i;
|
|
char remaining[1024];
|
|
|
|
i = Rcon_Validate ();
|
|
|
|
if (i == 0)
|
|
Com_Printf ("Bad rcon from %s:\n%s\n", NET_AdrToString (net_from), net_message.data+4);
|
|
else
|
|
Com_Printf ("Rcon from %s:\n%s\n", NET_AdrToString (net_from), net_message.data+4);
|
|
|
|
Com_BeginRedirect (RD_PACKET, sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect);
|
|
|
|
if (!Rcon_Validate ())
|
|
{
|
|
Com_Printf ("Bad rcon_password.\n");
|
|
}
|
|
else
|
|
{
|
|
remaining[0] = 0;
|
|
|
|
for (i=2 ; i<Cmd_Argc() ; i++)
|
|
{
|
|
strcat (remaining, Cmd_Argv(i) );
|
|
strcat (remaining, " ");
|
|
}
|
|
|
|
Cmd_ExecuteString (remaining);
|
|
}
|
|
|
|
Com_EndRedirect ();
|
|
}
|
|
|
|
/*
|
|
=================
|
|
SV_ConnectionlessPacket
|
|
|
|
A connectionless packet has four leading 0xff
|
|
characters to distinguish it from a game channel.
|
|
Clients that are in the game can still send
|
|
connectionless packets.
|
|
=================
|
|
*/
|
|
void SV_ConnectionlessPacket (void)
|
|
{
|
|
char *s;
|
|
char *c;
|
|
|
|
// r1ch fix: make sure we never talk to ourselves
|
|
// if (NET_IsLocalAddress (net_from.ip[0] == 127) && !NET_IsLocalHost(net_from) && ShortSwap(net_from.port) == server_port)
|
|
/* if ( (net_from.ip[0] == 127) && (net_from.type != NA_LOOPBACK) && (ShortSwap(net_from.port) == server_port) )
|
|
{
|
|
Com_DPrintf ("dropped %d byte connectionless packet from self! (spoofing attack?)\n", net_message.cursize);
|
|
return;
|
|
}*/
|
|
|
|
MSG_BeginReading (&net_message);
|
|
MSG_ReadLong (&net_message); // skip the -1 marker
|
|
|
|
s = MSG_ReadStringLine (&net_message);
|
|
|
|
Cmd_TokenizeString (s, false);
|
|
|
|
c = Cmd_Argv(0);
|
|
Com_DPrintf ("Packet %s : %s\n", NET_AdrToString(net_from), c);
|
|
|
|
if (!strcmp(c, "ping"))
|
|
SVC_Ping ();
|
|
else if (!strcmp(c, "ack"))
|
|
SVC_Ack ();
|
|
else if (!strcmp(c,"status"))
|
|
SVC_Status ();
|
|
else if (!strcmp(c,"info"))
|
|
SVC_Info ();
|
|
else if (!strcmp(c,"getchallenge"))
|
|
SVC_GetChallenge ();
|
|
else if (!strcmp(c,"connect"))
|
|
SVC_DirectConnect ();
|
|
else if (!strcmp(c, "rcon"))
|
|
SVC_RemoteCommand ();
|
|
else
|
|
Com_Printf ("bad connectionless packet from %s:\n%s\n"
|
|
, NET_AdrToString (net_from), s);
|
|
}
|
|
|
|
|
|
//============================================================================
|
|
|
|
/*
|
|
===================
|
|
SV_CalcPings
|
|
|
|
Updates the cl->ping variables
|
|
===================
|
|
*/
|
|
void SV_CalcPings (void)
|
|
{
|
|
int i, j;
|
|
client_t *cl;
|
|
int total, count;
|
|
|
|
for (i=0 ; i<maxclients->value ; i++)
|
|
{
|
|
cl = &svs.clients[i];
|
|
if (cl->state != cs_spawned )
|
|
continue;
|
|
|
|
#if 0
|
|
if (cl->lastframe > 0)
|
|
cl->frame_latency[sv.framenum&(LATENCY_COUNTS-1)] = sv.framenum - cl->lastframe + 1;
|
|
else
|
|
cl->frame_latency[sv.framenum&(LATENCY_COUNTS-1)] = 0;
|
|
#endif
|
|
|
|
total = 0;
|
|
count = 0;
|
|
for (j=0 ; j<LATENCY_COUNTS ; j++)
|
|
{
|
|
if (cl->frame_latency[j] > 0)
|
|
{
|
|
count++;
|
|
total += cl->frame_latency[j];
|
|
}
|
|
}
|
|
if (!count)
|
|
cl->ping = 0;
|
|
else
|
|
#if 0
|
|
cl->ping = total*100/count - 100;
|
|
#else
|
|
cl->ping = total / count;
|
|
#endif
|
|
|
|
// let the game dll know about the ping
|
|
cl->edict->client->ping = cl->ping;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
SV_GiveMsec
|
|
|
|
Every few frames, gives all clients an allotment of milliseconds
|
|
for their command moves. If they exceed it, assume cheating.
|
|
===================
|
|
*/
|
|
void SV_GiveMsec (void)
|
|
{
|
|
int i;
|
|
client_t *cl;
|
|
|
|
if (sv.framenum & 15)
|
|
return;
|
|
|
|
for (i=0 ; i<maxclients->value ; i++)
|
|
{
|
|
cl = &svs.clients[i];
|
|
if (cl->state == cs_free )
|
|
continue;
|
|
|
|
cl->commandMsec = 1800; // 1600 + some slop
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
SV_ReadPackets
|
|
=================
|
|
*/
|
|
void SV_ReadPackets (void)
|
|
{
|
|
int i;
|
|
client_t *cl;
|
|
int qport;
|
|
|
|
while (NET_GetPacket (NS_SERVER, &net_from, &net_message))
|
|
{
|
|
// check for connectionless packet (0xffffffff) first
|
|
if (*(int *)net_message.data == -1)
|
|
{
|
|
SV_ConnectionlessPacket ();
|
|
continue;
|
|
}
|
|
|
|
// read the qport out of the message so we can fix up
|
|
// stupid address translating routers
|
|
MSG_BeginReading (&net_message);
|
|
MSG_ReadLong (&net_message); // sequence number
|
|
MSG_ReadLong (&net_message); // sequence number
|
|
qport = MSG_ReadShort (&net_message) & 0xffff;
|
|
|
|
// check for packets from connected clients
|
|
for (i=0, cl=svs.clients ; i<maxclients->value ; i++,cl++)
|
|
{
|
|
if (cl->state == cs_free)
|
|
continue;
|
|
if (!NET_CompareBaseAdr (net_from, cl->netchan.remote_address))
|
|
continue;
|
|
if (cl->netchan.qport != qport)
|
|
continue;
|
|
if (cl->netchan.remote_address.port != net_from.port)
|
|
{
|
|
Com_Printf ("SV_ReadPackets: fixing up a translated port\n");
|
|
cl->netchan.remote_address.port = net_from.port;
|
|
}
|
|
|
|
if (Netchan_Process(&cl->netchan, &net_message))
|
|
{ // this is a valid, sequenced packet, so process it
|
|
if (cl->state != cs_zombie)
|
|
{
|
|
cl->lastmessage = svs.realtime; // don't timeout
|
|
SV_ExecuteClientMessage (cl);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (i != maxclients->value)
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_CheckTimeouts
|
|
|
|
If a packet has not been received from a client for timeout->value
|
|
seconds, drop the conneciton. Server frames are used instead of
|
|
realtime to avoid dropping the local client while debugging.
|
|
|
|
When a client is normally dropped, the client_t goes into a zombie state
|
|
for a few seconds to make sure any final reliable message gets resent
|
|
if necessary
|
|
==================
|
|
*/
|
|
void SV_CheckTimeouts (void)
|
|
{
|
|
int i;
|
|
client_t *cl;
|
|
int droppoint;
|
|
int zombiepoint;
|
|
|
|
droppoint = svs.realtime - 1000*timeout->value;
|
|
zombiepoint = svs.realtime - 1000*zombietime->value;
|
|
|
|
for (i=0,cl=svs.clients ; i<maxclients->value ; i++,cl++)
|
|
{
|
|
// message times may be wrong across a changelevel
|
|
if (cl->lastmessage > svs.realtime)
|
|
cl->lastmessage = svs.realtime;
|
|
|
|
if (cl->state == cs_zombie
|
|
&& cl->lastmessage < zombiepoint)
|
|
{
|
|
SV_CleanClient (cl); // r1ch fix: make sure client is cleaned up
|
|
cl->state = cs_free; // can now be reused
|
|
continue;
|
|
}
|
|
if ( (cl->state == cs_connected || cl->state == cs_spawned)
|
|
&& cl->lastmessage < droppoint)
|
|
{
|
|
// r1ch fix: only message if they spawned (less spam plz)
|
|
if (cl->state == cs_spawned && cl->name[0])
|
|
SV_BroadcastPrintf (PRINT_HIGH, "%s timed out\n", cl->name);
|
|
SV_DropClient (cl);
|
|
cl->state = cs_free; // don't bother with zombie state
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
SV_PrepWorldFrame
|
|
|
|
This has to be done before the world logic, because
|
|
player processing happens outside RunWorldFrame
|
|
================
|
|
*/
|
|
void SV_PrepWorldFrame (void)
|
|
{
|
|
edict_t *ent;
|
|
int i;
|
|
|
|
for (i=0 ; i<ge->num_edicts ; i++, ent++)
|
|
{
|
|
ent = EDICT_NUM(i);
|
|
// events only last for a single message
|
|
ent->s.event = 0;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
SV_RunGameFrame
|
|
=================
|
|
*/
|
|
void SV_RunGameFrame (void)
|
|
{
|
|
if (host_speeds->value)
|
|
time_before_game = Sys_Milliseconds ();
|
|
|
|
// we always need to bump framenum, even if we
|
|
// don't run the world, otherwise the delta
|
|
// compression can get confused when a client
|
|
// has the "current" frame
|
|
sv.framenum++;
|
|
sv.time = sv.framenum*100;
|
|
|
|
// don't run if paused
|
|
if (!sv_paused->value || maxclients->value > 1)
|
|
{
|
|
ge->RunFrame ();
|
|
|
|
// never get more than one tic behind
|
|
if (sv.time < svs.realtime)
|
|
{
|
|
if (sv_showclamp->value)
|
|
Com_Printf ("sv highclamp\n");
|
|
svs.realtime = sv.time;
|
|
}
|
|
}
|
|
|
|
if (host_speeds->value)
|
|
time_after_game = Sys_Milliseconds ();
|
|
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_Frame
|
|
|
|
==================
|
|
*/
|
|
void SV_Frame (int msec)
|
|
{
|
|
time_before_game = time_after_game = 0;
|
|
|
|
// if server is not active, do nothing
|
|
if (!svs.initialized)
|
|
return;
|
|
|
|
svs.realtime += msec;
|
|
|
|
// keep the random time dependent
|
|
rand ();
|
|
|
|
// check timeouts
|
|
SV_CheckTimeouts ();
|
|
|
|
// get packets from clients
|
|
SV_ReadPackets ();
|
|
|
|
// move autonomous things around if enough time has passed
|
|
if (!sv_timedemo->value && svs.realtime < sv.time)
|
|
{
|
|
// never let the time get too far off
|
|
if (sv.time - svs.realtime > 100)
|
|
{
|
|
if (sv_showclamp->value)
|
|
Com_Printf ("sv lowclamp\n");
|
|
svs.realtime = sv.time - 100;
|
|
}
|
|
NET_Sleep(sv.time - svs.realtime);
|
|
return;
|
|
}
|
|
|
|
// update ping based on the last known frame from all clients
|
|
SV_CalcPings ();
|
|
|
|
// give the clients some timeslices
|
|
SV_GiveMsec ();
|
|
|
|
// let everything in the world think and move
|
|
SV_RunGameFrame ();
|
|
|
|
// send messages back to the clients that had packets read this frame
|
|
SV_SendClientMessages ();
|
|
|
|
// save the entire world state if recording a serverdemo
|
|
SV_RecordDemoMessage ();
|
|
|
|
// send a heartbeat to the master if needed
|
|
Master_Heartbeat ();
|
|
|
|
// clear teleport flags, etc for next frame
|
|
SV_PrepWorldFrame ();
|
|
|
|
}
|
|
|
|
//============================================================================
|
|
|
|
/*
|
|
================
|
|
Master_Heartbeat
|
|
|
|
Send a message to the master every few minutes to
|
|
let it know we are alive, and log information
|
|
================
|
|
*/
|
|
#define HEARTBEAT_SECONDS 300
|
|
void Master_Heartbeat (void)
|
|
{
|
|
char *string;
|
|
int i;
|
|
|
|
// pgm post 3.19 change, cvar pointer not validated before dereferencing
|
|
if (!dedicated || !dedicated->value)
|
|
return; // only dedicated servers send heartbeats
|
|
|
|
// pgm post 3.19 change, cvar pointer not validated before dereferencing
|
|
if (!public_server || !public_server->value)
|
|
return; // a private dedicated game
|
|
|
|
// check for time wraparound
|
|
if (svs.last_heartbeat > svs.realtime)
|
|
svs.last_heartbeat = svs.realtime;
|
|
|
|
if (svs.realtime - svs.last_heartbeat < HEARTBEAT_SECONDS*1000)
|
|
return; // not time to send yet
|
|
|
|
svs.last_heartbeat = svs.realtime;
|
|
|
|
// send the same string that we would give for a status OOB command
|
|
string = SV_StatusString();
|
|
|
|
// send to group master
|
|
for (i=0 ; i<MAX_MASTERS ; i++)
|
|
if (master_adr[i].port)
|
|
{
|
|
Com_Printf ("Sending heartbeat to %s\n", NET_AdrToString (master_adr[i]));
|
|
Netchan_OutOfBandPrint (NS_SERVER, master_adr[i], "heartbeat\n%s", string);
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Master_Shutdown
|
|
|
|
Informs all masters that this server is going down
|
|
=================
|
|
*/
|
|
void Master_Shutdown (void)
|
|
{
|
|
int i;
|
|
|
|
// pgm post3.19 change, cvar pointer not validated before dereferencing
|
|
if (!dedicated || !dedicated->value)
|
|
return; // only dedicated servers send heartbeats
|
|
|
|
// pgm post3.19 change, cvar pointer not validated before dereferencing
|
|
if (!public_server || !public_server->value)
|
|
return; // a private dedicated game
|
|
|
|
// send to group master
|
|
for (i=0 ; i<MAX_MASTERS ; i++)
|
|
if (master_adr[i].port)
|
|
{
|
|
if (i > 0)
|
|
Com_Printf ("Sending heartbeat to %s\n", NET_AdrToString (master_adr[i]));
|
|
Netchan_OutOfBandPrint (NS_SERVER, master_adr[i], "shutdown");
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
|
|
|
|
/*
|
|
=================
|
|
SV_UserinfoChanged
|
|
|
|
Pull specific info from a newly changed userinfo string
|
|
into a more C freindly form.
|
|
=================
|
|
*/
|
|
void SV_UserinfoChanged (client_t *cl)
|
|
{
|
|
char *val;
|
|
int i;
|
|
|
|
// call prog code to allow overrides
|
|
ge->ClientUserinfoChanged (cl->edict, cl->userinfo);
|
|
|
|
// name for C code
|
|
strncpy (cl->name, Info_ValueForKey (cl->userinfo, "name"), sizeof(cl->name)-1);
|
|
// mask off high bit
|
|
for (i=0 ; i<sizeof(cl->name) ; i++)
|
|
cl->name[i] &= 127;
|
|
|
|
// rate command
|
|
val = Info_ValueForKey (cl->userinfo, "rate");
|
|
if (strlen(val))
|
|
{
|
|
i = atoi(val);
|
|
cl->rate = i;
|
|
if (cl->rate < 100)
|
|
cl->rate = 100;
|
|
if (cl->rate > 15000)
|
|
cl->rate = 15000;
|
|
}
|
|
else
|
|
cl->rate = 5000;
|
|
|
|
// msg command
|
|
val = Info_ValueForKey (cl->userinfo, "msg");
|
|
if (strlen(val))
|
|
{
|
|
cl->messagelevel = atoi(val);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//============================================================================
|
|
|
|
/*
|
|
===============
|
|
SV_Init
|
|
|
|
Only called at quake2.exe startup, not for each game
|
|
===============
|
|
*/
|
|
void SV_Init (void)
|
|
{
|
|
SV_InitOperatorCommands ();
|
|
|
|
rcon_password = Cvar_Get ("rcon_password", "", 0);
|
|
Cvar_Get ("skill", "1", 0);
|
|
Cvar_Get ("deathmatch", "0", CVAR_LATCH);
|
|
Cvar_Get ("coop", "0", CVAR_LATCH);
|
|
Cvar_Get ("dmflags", va("%i", DF_INSTANT_ITEMS), CVAR_SERVERINFO);
|
|
Cvar_Get ("fraglimit", "0", CVAR_SERVERINFO);
|
|
Cvar_Get ("timelimit", "0", CVAR_SERVERINFO);
|
|
Cvar_Get ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH);
|
|
Cvar_Get ("protocol", va("%i", PROTOCOL_VERSION), CVAR_SERVERINFO|CVAR_NOSET);;
|
|
maxclients = Cvar_Get ("maxclients", "1", CVAR_SERVERINFO | CVAR_LATCH);
|
|
hostname = Cvar_Get ("hostname", "noname", CVAR_SERVERINFO | CVAR_ARCHIVE);
|
|
timeout = Cvar_Get ("timeout", "125", 0);
|
|
zombietime = Cvar_Get ("zombietime", "2", 0);
|
|
sv_showclamp = Cvar_Get ("showclamp", "0", 0);
|
|
sv_paused = Cvar_Get ("paused", "0", 0);
|
|
sv_timedemo = Cvar_Get ("timedemo", "0", 0);
|
|
sv_enforcetime = Cvar_Get ("sv_enforcetime", "0", 0);
|
|
allow_download = Cvar_Get ("allow_download", "1", CVAR_ARCHIVE);
|
|
allow_download_players = Cvar_Get ("allow_download_players", "0", CVAR_ARCHIVE);
|
|
allow_download_models = Cvar_Get ("allow_download_models", "1", CVAR_ARCHIVE);
|
|
allow_download_sounds = Cvar_Get ("allow_download_sounds", "1", CVAR_ARCHIVE);
|
|
allow_download_maps = Cvar_Get ("allow_download_maps", "1", CVAR_ARCHIVE);
|
|
allow_download_pics = Cvar_Get ("allow_download_pics", "1", CVAR_ARCHIVE);
|
|
allow_download_textures = Cvar_Get ("allow_download_textures", "1", CVAR_ARCHIVE);
|
|
// Knightmare- whether to allow downloading 24-bit textures
|
|
allow_download_textures_24bit = Cvar_Get ("allow_download_textures_24bit", "0", CVAR_ARCHIVE);
|
|
|
|
sv_downloadserver = Cvar_Get ("sv_downloadserver", "", 0); // r1ch: http dl server
|
|
|
|
sv_baselines_maxlen = Cvar_Get ("sv_baselines_maxlen", "1200", 0); // Knightmare- max packet size for connect messages
|
|
sv_limit_msglen = Cvar_Get ("sv_limit_msglen", "1", 0); // Knightmare- whether to use MAX_MSGLEN_MP for multiplayer games
|
|
|
|
sv_noreload = Cvar_Get ("sv_noreload", "0", 0);
|
|
|
|
sv_airaccelerate = Cvar_Get("sv_airaccelerate", "0", CVAR_LATCH);
|
|
|
|
public_server = Cvar_Get ("public", "0", 0);
|
|
|
|
sv_iplimit = Cvar_Get ("sv_iplimit", "3", 0); // r1ch: limit connections per ip address (stop zombie dos/flood)
|
|
|
|
sv_reconnect_limit = Cvar_Get ("sv_reconnect_limit", "3", CVAR_ARCHIVE);
|
|
|
|
sv_entfile = Cvar_Get ("sv_entfile", "1", CVAR_ARCHIVE); // whether to use .ent file
|
|
|
|
SZ_Init (&net_message, net_message_buffer, sizeof(net_message_buffer));
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_FinalMessage
|
|
|
|
Used by SV_Shutdown to send a final message to all
|
|
connected clients before the server goes down. The messages are sent immediately,
|
|
not just stuck on the outgoing message list, because the server is going
|
|
to totally exit after returning from this function.
|
|
==================
|
|
*/
|
|
void SV_FinalMessage (char *message, qboolean reconnect)
|
|
{
|
|
int i;
|
|
client_t *cl;
|
|
|
|
SZ_Clear (&net_message);
|
|
MSG_WriteByte (&net_message, svc_print);
|
|
MSG_WriteByte (&net_message, PRINT_HIGH);
|
|
MSG_WriteString (&net_message, message);
|
|
|
|
if (reconnect)
|
|
MSG_WriteByte (&net_message, svc_reconnect);
|
|
else
|
|
MSG_WriteByte (&net_message, svc_disconnect);
|
|
|
|
// send it twice
|
|
// stagger the packets to crutch operating system limited buffers
|
|
|
|
for (i=0, cl = svs.clients ; i<maxclients->value ; i++, cl++)
|
|
if (cl->state >= cs_connected)
|
|
Netchan_Transmit (&cl->netchan, net_message.cursize
|
|
, net_message.data);
|
|
|
|
for (i=0, cl = svs.clients ; i<maxclients->value ; i++, cl++)
|
|
if (cl->state >= cs_connected)
|
|
Netchan_Transmit (&cl->netchan, net_message.cursize
|
|
, net_message.data);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
================
|
|
SV_Shutdown
|
|
|
|
Called when each game quits,
|
|
before Sys_Quit or Sys_Error
|
|
================
|
|
*/
|
|
void SV_Shutdown (char *finalmsg, qboolean reconnect)
|
|
{
|
|
if (svs.clients)
|
|
SV_FinalMessage (finalmsg, reconnect);
|
|
|
|
Master_Shutdown ();
|
|
SV_ShutdownGameProgs ();
|
|
|
|
// free current level
|
|
if (sv.demofile)
|
|
FS_FCloseFile (sv.demofile);
|
|
memset (&sv, 0, sizeof(sv));
|
|
Com_SetServerState (sv.state);
|
|
|
|
// free server static data
|
|
if (svs.clients)
|
|
Z_Free (svs.clients);
|
|
if (svs.client_entities)
|
|
Z_Free (svs.client_entities);
|
|
if (svs.demofile)
|
|
fclose (svs.demofile);
|
|
memset (&svs, 0, sizeof(svs));
|
|
}
|
|
|