📄 sv_main.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 *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_reconnect_limit; // minimum seconds between connect messages
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;
}
drop->state = cs_zombie; // become free in a few seconds
drop->name[0] = 0;
}
/*
==============================================================================
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;
strcpy (status, Cvar_Serverinfo());
strcat (status, "\n");
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
strcpy (status + statusLength, player);
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)
Com_sprintf (string, sizeof(string), "%s: wrong version\n", hostname->string, sizeof(string));
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;
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));
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;
}
Com_Printf ("%s:reconnect\n", NET_AdrToString (adr));
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
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;
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 )
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -