#include "server.h"
netadr_t master_adr[MAX_MASTERS];
client_t *sv_client;
cvar_t *sv_paused;
cvar_t *sv_timedemo;
cvar_t *sv_enforcetime;
cvar_t *timeout; cvar_t *zombietime;
cvar_t *rcon_password;
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;
cvar_t *maxclients; cvar_t *sv_showclamp;
cvar_t *hostname;
cvar_t *public_server;
cvar_t *sv_reconnect_limit;
sqlite3 *sv_database;
void Master_Shutdown(void);
void SV_DropClient(client_t *drop) {
MSG_WriteByte(&drop->netchan.message, svc_disconnect);
if(drop->state == cs_spawned) {
ge->ClientDisconnect(drop->edict);
}
if(drop->download) {
FS_FreeFile(drop->download);
drop->download = NULL;
}
drop->state = cs_zombie; drop->name[0] = 0;
}
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; strcpy(status + statusLength, player);
statusLength += playerLength;
}
}
return status;
}
void SVC_Status(void) {
Netchan_OutOfBandPrint(NS_SERVER, net_from, "print\n%s", SV_StatusString());
#if 0#endif
}
void SVC_Ack(void) { Com_Printf("Ping acknowledge from %s\n", NET_AdrToString(net_from)); }
void SVC_Info(void) {
char string[64];
int i, count;
int version;
if(maxclients->value == 1)
return;
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);
}
void SVC_Ping(void) { Netchan_OutOfBandPrint(NS_SERVER, net_from, "ack"); }
void SVC_GetChallenge(void) {
int i;
int oldest;
int oldestTime;
oldest = 0;
oldestTime = 0x7fffffff;
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) {
svs.challenges[oldest].challenge = rand() & 0x7fff;
svs.challenges[oldest].adr = net_from;
svs.challenges[oldest].time = curtime;
i = oldest;
}
Netchan_OutOfBandPrint(NS_SERVER, net_from, "challenge %i", svs.challenges[i].challenge);
}
SQL_QUERY(GetCharactersForAccountUUID, sv_database,
"SELECT rowid, name, configstring FROM characters WHERE account_uuid = ?", bind_text(0, account_uuid),
column_int64(0, row_id), column_text(1, name), column_text(2, configstring))
static void character_row(void *ud, int64_t rowid, const unsigned char *name, const unsigned char *configstring) {
Com_Printf("%llu %s %s\n", rowid, name, configstring);
}
static void SVC_EnterLobby(void) {
char account_uuid[MAX_QPATH];
sprintf(account_uuid, sizeof(account_uuid), "local");
if(GetCharactersForAccountUUID(account_uuid, character_row, NULL) == 0) {
Com_Printf("no characters for account %s\n", account_uuid);
}
}
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;
Info_SetValueForKey(userinfo, "ip", NET_AdrToString(net_from));
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;
}
}
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; 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));
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;
}
}
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:
*newcl = temp;
sv_client = newcl;
edictnum = (newcl - svs.clients) + 1;
ent = EDICT_NUM(edictnum);
newcl->edict = ent;
newcl->challenge = challenge;
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;
}
strncpy(newcl->userinfo, userinfo, sizeof(newcl->userinfo) - 1);
SV_UserinfoChanged(newcl);
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; 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;
}
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();
}
void SV_ConnectionlessPacket(void) {
char *s;
char *c;
MSG_BeginReading(&net_message);
MSG_ReadLong(&net_message);
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, "lobby"))
SVC_EnterLobby();
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);
}
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#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#else
cl->ping = total / count;
#endif
cl->edict->client->ping = cl->ping;
}
}
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; }
}
bool SV_ReadPacket(netadr_t from, const void *buffer, uint32_t length) {
net_from = from;
net_message.data = (void *)buffer;
net_message.cursize = length;
if(*(int *)net_message.data == -1) {
SV_ConnectionlessPacket();
return true;
}
MSG_BeginReading(&net_message);
MSG_ReadLong(&net_message); MSG_ReadLong(&net_message); int qport = MSG_ReadShort(&net_message) & 0xffff;
client_t *cl;
int i;
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)) { if(cl->state != cs_zombie) {
cl->lastmessage = svs.realtime; SV_ExecuteClientMessage(cl);
}
}
break;
}
net_message.data = net_message_buffer;
return i != maxclients->value;
}
void SV_ReadPackets(void) {
while(NET_GetPacket(NS_SERVER, &net_from, &net_message) && SV_ReadPacket(net_from, net_message.data, net_message.cursize)) ;
}
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++) {
if(cl->lastmessage > svs.realtime)
cl->lastmessage = svs.realtime;
if(cl->state == cs_zombie && cl->lastmessage < zombiepoint) {
cl->state = cs_free; continue;
}
if((cl->state == cs_connected || cl->state == cs_spawned) && cl->lastmessage < droppoint) {
SV_BroadcastPrintf(PRINT_HIGH, "%s timed out\n", cl->name);
SV_DropClient(cl);
cl->state = cs_free; }
}
}
void SV_PrepWorldFrame(void) {
edict_t *ent;
int i;
for(i = 0; i < ge->num_edicts; i++, ent++) {
ent = EDICT_NUM(i);
ent->s.event = 0;
}
}
void SV_RunGameFrame(void) {
if(host_speeds->value)
time_before_game = Sys_Milliseconds();
sv.framenum++;
sv.time = sv.framenum * 100;
if(!sv_paused->value || maxclients->value > 1) {
ge->RunFrame();
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();
}
void SV_Frame(int msec) {
time_before_game = time_after_game = 0;
if(!svs.initialized)
return;
svs.realtime += msec;
rand();
SV_CheckTimeouts();
SV_ReadPackets();
if(!sv_timedemo->value && svs.realtime < sv.time) {
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;
}
SV_CalcPings();
SV_GiveMsec();
SV_RunGameFrame();
SV_SendClientMessages();
SV_RecordDemoMessage();
Master_Heartbeat();
SV_PrepWorldFrame();
}
#define HEARTBEAT_SECONDS 300
void Master_Heartbeat(void) {
char *string;
int i;
if(!dedicated->value)
return;
if(!public_server->value)
return;
if(svs.last_heartbeat > svs.realtime)
svs.last_heartbeat = svs.realtime;
if(svs.realtime - svs.last_heartbeat < HEARTBEAT_SECONDS * 1000)
return;
svs.last_heartbeat = svs.realtime;
string = SV_StatusString();
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);
}
}
void Master_Shutdown(void) {
int i;
if(!dedicated->value)
return;
if(!public_server->value)
return;
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");
}
}
void SV_UserinfoChanged(client_t *cl) {
char *val;
int i;
ge->ClientUserinfoChanged(cl->edict, cl->userinfo);
strncpy(cl->name, Info_ValueForKey(cl->userinfo, "name"), sizeof(cl->name) - 1);
for(i = 0; i < sizeof(cl->name); i++)
cl->name[i] &= 127;
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;
val = Info_ValueForKey(cl->userinfo, "msg");
if(strlen(val)) {
cl->messagelevel = atoi(val);
}
}
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", "0", 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);
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_reconnect_limit = Cvar_Get("sv_reconnect_limit", "3", CVAR_ARCHIVE);
SZ_Init(&net_message, net_message_buffer, sizeof(net_message_buffer));
Cbuf_AddText("sv_reload_database\n");
}
void SV_FinalMessage(char *message, bool 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);
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);
}
void SV_Shutdown(char *finalmsg, bool reconnect) {
if(svs.clients)
SV_FinalMessage(finalmsg, reconnect);
Master_Shutdown();
SV_ShutdownGameProgs();
if(sv.demofile)
fclose(sv.demofile);
memset(&sv, 0, sizeof(sv));
Com_SetServerState(sv.state);
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));
}