UFO: Alien Invasion
Loading...
Searching...
No Matches
sv_main.cpp
Go to the documentation of this file.
1
5
6/*
7All original material Copyright (C) 2002-2025 UFO: Alien Invasion.
8
9Original file from Quake 2 v3.21: quake2-2.31/server/sv_main.c
10Copyright (C) 1997-2001 Id Software, Inc.
11
12This program is free software; you can redistribute it and/or
13modify it under the terms of the GNU General Public License
14as published by the Free Software Foundation; either version 2
15of the License, or (at your option) any later version.
16
17This program is distributed in the hope that it will be useful,
18but WITHOUT ANY WARRANTY; without even the implied warranty of
19MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
20
21See the GNU General Public License for more details.
22
23You should have received a copy of the GNU General Public License
24along with this program; if not, write to the Free Software
25Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26
27*/
28
29#include "server.h"
30#include "sv_log.h"
31#include "../ports/system.h"
33#include "../shared/thread.h"
34
44static cvar_t* sv_timeout; /* seconds without any message */
45
54
56
57typedef struct leakyBucket_s {
58 char node[64];
59
61 signed char burst;
62
63 long hash;
64
65 struct leakyBucket_s* prev;
66 struct leakyBucket_s* next;
68
69/* This is deliberately quite large to make it more of an effort to DoS */
70#define MAX_BUCKETS 16384
71#define MAX_HASHES 1024
72
76
78{
80 Com_Error(ERR_FATAL, "Invalid config string index given: %i", index);
81
82 return sv->configstrings[index];
83}
84
86{
88 Com_Error(ERR_FATAL, "Invalid config string index given: %i", index);
89
90 switch (index) {
91 case CS_ENTITYSTRING:
93 case CS_TILES:
95 case CS_POSITIONS:
97 case CS_MODELS:
99 case CS_PLAYERNAMES:
101 case CS_GENERAL:
103 default:
104 return MAX_TOKEN_CHARS;
105 }
106}
107
109{
110 return atoi(SV_GetConfigString(index));
111}
112
113char* SV_SetConfigString (int index, ...)
114{
115 va_list ap;
116 const char* value;
117
119 Com_Error(ERR_FATAL, "Invalid config string index given: %i", index);
120
121 va_start(ap, index);
122
123 switch (index) {
124 case CS_LIGHTMAP:
125 case CS_MAPCHECKSUM:
126 case CS_UFOCHECKSUM:
127 case CS_OBJECTAMOUNT:
128 value = va("%i", va_arg(ap, int));
129 break;
130 default:
131 value = va_arg(ap, char*);
132 break;
133 }
134
135 /* change the string in sv
136 * there may be overflows in i==CS_TILES - but thats ok
137 * see definition of configstrings and MAX_TILESTRINGS */
138 if (index == CS_TILES || index == CS_POSITIONS)
139 Q_strncpyz(sv->configstrings[index], value, MAX_TOKEN_CHARS * MAX_TILESTRINGS);
140 else
141 Q_strncpyz(sv->configstrings[index], value, sizeof(sv->configstrings[index]));
142
143 va_end(ap);
144
145 return sv->configstrings[index];
146}
147
153{
154 client_t* endOfClients = &svs.clients[sv_maxclients->integer];
155 client_t* client;
156
157 if (!sv_maxclients->integer)
158 return nullptr;
159
160 if (!lastClient)
161 return svs.clients;
162 assert(lastClient >= svs.clients);
163 assert(lastClient < endOfClients);
164
165 client = lastClient;
166
167 client++;
168 if (client >= endOfClients)
169 return nullptr;
170 else
171 return client;
172}
173
175{
176 return &svs.clients[index];
177}
178
184void SV_DropClient (client_t* drop, const char* message)
185{
186 /* add the disconnect */
187 dbuffer msg(2 + strlen(message));
189 NET_WriteString(&msg, message);
190 NET_WriteMsg(drop->stream, msg);
191 SV_BroadcastPrintf(PRINT_CHAT, "%s was dropped from the server - reason: %s\n", drop->name, message);
192
193 if (drop->state == cs_spawned || drop->state == cs_spawning) {
194 /* call the prog function for removing a client */
195 /* this will remove the body, among other things */
196 const ScopedMutex scopedMutex(svs.serverMutex);
197 svs.ge->ClientDisconnect(*drop->player);
198 }
199
201 drop->stream = nullptr;
202
203 drop->player->setInUse(false);
205 drop->name[0] = 0;
206
207 if (svs.abandon) {
208 int count = 0;
209 client_t* cl = nullptr;
210 while ((cl = SV_GetNextClient(cl)) != nullptr)
211 if (cl->state >= cs_connected)
212 count++;
213 if (count == 0)
214 svs.killserver = true;
215 }
216}
217
218/*
219==============================================================================
220CONNECTIONLESS COMMANDS
221==============================================================================
222*/
223
227static leakyBucket_t* SVC_BucketForAddress (struct net_stream& address, int burst, int period)
228{
229 char node[64];
230 NET_StreamPeerToName(&address, node, sizeof(node), false);
231
232 const long hash = Com_HashKey(node, MAX_HASHES);
233 const int now = Sys_Milliseconds();
234
235 for (leakyBucket_t* bucket = bucketHashes[hash]; bucket; bucket = bucket->next) {
236 if (!Q_streq(bucket->node, node))
237 continue;
238
239 return bucket;
240 }
241
242 for (int i = 0; i < MAX_BUCKETS; i++) {
243 leakyBucket_t* bucket = &buckets[i];
244 const int interval = now - bucket->lastTime;
245
246 /* Reclaim expired buckets */
247 if (bucket->lastTime > 0 && (interval > (burst * period) || interval < 0)) {
248 if (bucket->prev != nullptr) {
249 bucket->prev->next = bucket->next;
250 } else {
251 bucketHashes[bucket->hash] = bucket->next;
252 }
253
254 if (bucket->next != nullptr) {
255 bucket->next->prev = bucket->prev;
256 }
257
258 OBJZERO(*bucket);
259 }
260
261 if (Q_strnull(bucket->node)) {
262 Q_strncpyz(bucket->node, node, sizeof(bucket->node));
263 bucket->lastTime = now;
264 bucket->burst = 0;
265 bucket->hash = hash;
266
267 /* Add to the head of the relevant hash chain */
268 bucket->next = bucketHashes[hash];
269 if (bucketHashes[hash] != nullptr) {
270 bucketHashes[hash]->prev = bucket;
271 }
272
273 bucket->prev = nullptr;
274 bucketHashes[hash] = bucket;
275
276 return bucket;
277 }
278 }
279
280 /* Couldn't allocate a bucket for this address */
281 return nullptr;
282}
283
284static bool SVC_RateLimit (leakyBucket_t* bucket, int burst = 10, int period = 100)
285{
286 if (bucket == nullptr)
287 return true;
288
289 const int now = Sys_Milliseconds();
290 const int interval = now - bucket->lastTime;
291 const int expired = interval / period;
292 const int expiredRemainder = interval % period;
293
294 if (expired > bucket->burst) {
295 bucket->burst = 0;
296 bucket->lastTime = now;
297 } else {
298 bucket->burst -= expired;
299 bucket->lastTime = now - expiredRemainder;
300 }
301
302 if (bucket->burst < burst) {
303 bucket->burst++;
304 return false;
305 }
306
307 return true;
308}
309
313static bool SVC_RateLimitAddress (struct net_stream& from, int burst = 10, int period = 1000)
314{
315 leakyBucket_t* bucket = SVC_BucketForAddress(from, burst, period);
316 return SVC_RateLimit(bucket, burst, period);
317}
318
323static void SVC_TeamInfo (struct net_stream* s)
324{
325 if (SVC_RateLimitAddress(*s)) {
326 Com_DPrintf(DEBUG_SERVER, "SVC_TeamInfo: rate limit from %s exceeded, dropping request\n", NET_StreamToString(s));
327 return;
328 }
329
330 /* Allow getinfo to be DoSed relatively easily, but prevent excess outbound bandwidth usage when being flooded inbound */
332 Com_DPrintf(DEBUG_SERVER, "SVC_TeamInfo: rate limit exceeded, dropping request\n");
333 return;
334 }
335
336 char infoGlobal[MAX_INFO_STRING] = "";
337 Info_SetValueForKey(infoGlobal, sizeof(infoGlobal), "sv_teamplay", Cvar_GetString("sv_teamplay"));
338 Info_SetValueForKey(infoGlobal, sizeof(infoGlobal), "sv_maxteams", Cvar_GetString("sv_maxteams"));
339 Info_SetValueForKey(infoGlobal, sizeof(infoGlobal), "sv_maxplayersperteam", Cvar_GetString("sv_maxplayersperteam"));
340
341 dbuffer msg;
342 NET_WriteByte(&msg, svc_oob);
343 NET_WriteRawString(&msg, "teaminfo\n");
344 NET_WriteString(&msg, infoGlobal);
345
346 client_t* cl = nullptr;
347 while ((cl = SV_GetNextClient(cl)) != nullptr) {
348 if (cl->state < cs_connected)
349 continue;
350 char infoPlayer[MAX_INFO_STRING] = "";
351 /* show players that already have a team with their teamnum */
352 int teamId = svs.ge->ClientGetTeamNum(*cl->player);
353 if (!teamId)
354 teamId = TEAM_NO_ACTIVE;
355 Info_SetValueForKeyAsInteger(infoPlayer, sizeof(infoPlayer), "cl_team", teamId);
356 Info_SetValueForKeyAsInteger(infoPlayer, sizeof(infoPlayer), "cl_ready", svs.ge->ClientIsReady(cl->player));
357 Info_SetValueForKey(infoPlayer, sizeof(infoPlayer), "cl_name", cl->name);
358 NET_WriteString(&msg, infoPlayer);
359 }
360
361 NET_WriteByte(&msg, 0);
362
363 NET_WriteMsg(s, msg);
364}
365
370static void SVC_Status (struct net_stream* s)
371{
372 if (SVC_RateLimitAddress(*s)) {
373 Com_DPrintf(DEBUG_SERVER, "SVC_Status: rate limit from %s exceeded, dropping request\n", NET_StreamToString(s));
374 return;
375 }
376
377 /* Allow getstatus to be DoSed relatively easily, but prevent excess outbound bandwidth usage when being flooded inbound */
378 if (SVC_RateLimit(&outboundLeakyBucket, 10, 100)) {
379 Com_DPrintf(DEBUG_SERVER, "SVC_Status: rate limit exceeded, dropping request\n");
380 return;
381 }
382
383 dbuffer msg;
384 NET_WriteByte(&msg, svc_oob);
386 char info[MAX_INFO_STRING];
387 NET_WriteRawString(&msg, Cvar_Serverinfo(info, sizeof(info)));
388 NET_WriteRawString(&msg, "\n");
389
390 client_t* cl = nullptr;
391 while ((cl = SV_GetNextClient(cl)) != nullptr) {
392 if (cl->state <= cs_free)
393 continue;
394
395 char player[1024];
396 Com_sprintf(player, sizeof(player), "%i \"%s\"\n", svs.ge->ClientGetTeamNum(*cl->player), cl->name);
397 NET_WriteRawString(&msg, player);
398 }
399
400 NET_WriteMsg(s, msg);
401}
402
411static void SVC_Info (struct net_stream* s)
412{
413 if (SVC_RateLimitAddress(*s)) {
414 Com_DPrintf(DEBUG_SERVER, "SVC_Info: rate limit from %s exceeded, dropping request\n", NET_StreamToString(s));
415 return;
416 }
417
418 /* Allow getinfo to be DoSed relatively easily, but prevent excess outbound bandwidth usage when being flooded inbound */
420 Com_DPrintf(DEBUG_SERVER, "SVC_Info: rate limit exceeded, dropping request\n");
421 return;
422 }
423
424 if (sv_maxclients->integer == 1) {
425 Com_DPrintf(DEBUG_SERVER, "Ignore info string in singleplayer mode\n");
426 return; /* ignore in single player */
427 }
428
429 const int version = atoi(Cmd_Argv(1));
430 if (version != PROTOCOL_VERSION) {
431 char string[MAX_VAR];
432 Com_sprintf(string, sizeof(string), "%s: wrong version (client: %i, host: %i)\n", sv_hostname->string, version, PROTOCOL_VERSION);
433 NET_OOB_Printf(s, SV_CMD_PRINT "\n%s", string);
434 return;
435 }
436
437 int count = 0;
438
439 client_t* cl = nullptr;
440 while ((cl = SV_GetNextClient(cl)) != nullptr)
441 if (cl->state >= cs_spawning)
442 count++;
443
444 char infostring[MAX_INFO_STRING];
445 infostring[0] = '\0';
446 Info_SetValueForKey(infostring, sizeof(infostring), "sv_protocol", DOUBLEQUOTE(PROTOCOL_VERSION));
447 Info_SetValueForKey(infostring, sizeof(infostring), "sv_hostname", sv_hostname->string);
448 Info_SetValueForKey(infostring, sizeof(infostring), "sv_dedicated", sv_dedicated->string);
449 Info_SetValueForKey(infostring, sizeof(infostring), "sv_gametype", sv_gametype->string);
450 Info_SetValueForKey(infostring, sizeof(infostring), "sv_mapname", sv->name);
451 Info_SetValueForKeyAsInteger(infostring, sizeof(infostring), "clients", count);
452 Info_SetValueForKey(infostring, sizeof(infostring), "sv_maxclients", sv_maxclients->string);
453 Info_SetValueForKey(infostring, sizeof(infostring), "sv_version", UFO_VERSION);
454 NET_OOB_Printf(s, SV_CMD_INFO "\n%s", infostring);
455}
456
457
463{
464 Com_DPrintf(DEBUG_SERVER, "SVC_DirectConnect()\n");
465
466 if (sv->started || sv->spawned) {
467 Com_Printf("rejected connect because match is already running\n");
469 return;
470 }
471
472 char buf[256];
473 const char* peername = NET_StreamPeerToName(stream, buf, sizeof(buf), false);
474
475 const int version = atoi(Cmd_Argv(1));
476 if (version != PROTOCOL_VERSION) {
477 Com_Printf("rejected connect from version %i - %s\n", version, peername);
479 return;
480 }
481
482 char userinfo[MAX_INFO_STRING];
483 Q_strncpyz(userinfo, Cmd_Argv(2), sizeof(userinfo));
484
485 if (Q_strnull(userinfo)) { /* catch empty userinfo */
486 Com_Printf("Empty userinfo from %s\n", peername);
488 return;
489 }
490
491 if (strchr(userinfo, '\xFF')) { /* catch end of message in string exploit */
492 Com_Printf("Illegal userinfo contained xFF from %s\n", peername);
494 return;
495 }
496
497 if (strlen(Info_ValueForKey(userinfo, "ip"))) { /* catch spoofed ips */
498 Com_Printf("Illegal userinfo contained ip from %s\n", peername);
500 return;
501 }
502
503 /* force the IP key/value pair so the game can filter based on ip */
504 Info_SetValueForKey(userinfo, sizeof(userinfo), "ip", peername);
505
506 /* find a client slot */
507 client_t* cl = nullptr;
508 while ((cl = SV_GetNextClient(cl)) != nullptr)
509 if (cl->state == cs_free)
510 break;
511 if (cl == nullptr) {
513 Com_Printf("Rejected a connection - server is full.\n");
514 return;
515 }
516
517 /* build a new connection - accept the new client
518 * this is the only place a client_t is ever initialized */
519 OBJZERO(*cl);
520 const int playernum = cl - SV_GetClient(0);
521 SrvPlayer* player = PLAYER_NUM(playernum);
522 cl->player = player;
523 cl->player->setNum(playernum);
524
525 bool connected;
526 {
527 const ScopedMutex scopedMutex(svs.serverMutex);
528 connected = svs.ge->ClientConnect(player, userinfo, sizeof(userinfo));
529 }
530
531 /* get the game a chance to reject this connection or modify the userinfo */
532 if (!connected) {
533 const char* rejmsg = Info_ValueForKey(userinfo, "rejmsg");
534 if (rejmsg[0] != '\0') {
536 Com_Printf("Game rejected a connection from %s. Reason: %s\n", peername, rejmsg);
537 } else {
539 Com_Printf("Game rejected a connection from %s.\n", peername);
540 }
541 return;
542 }
543
544 /* new player */
545 cl->player->setInUse(true);
546 cl->lastmessage = svs.realtime;
547
548 /* parse some info from the info strings */
549 Q_strncpyz(cl->userinfo, userinfo, sizeof(cl->userinfo));
551
552 /* send the connect packet to the client */
553 if (sv_http_downloadserver->string[0])
555 else
557
559
560 Q_strncpyz(cl->peername, peername, sizeof(cl->peername));
561 cl->stream = stream;
563}
564
569static inline bool Rcon_Validate (const char* password)
570{
571 /* no rcon access */
572 if (Q_strnull(rcon_password->string))
573 return false;
574
575 /* password not valid */
576 if (!Q_streq(password, rcon_password->string))
577 return false;
578
579 return true;
580}
581
586{
587 char buf[64];
588 const char* peername = NET_StreamPeerToName(stream, buf, sizeof(buf), false);
589
590 /* Prevent using rcon as an amplifier and make dictionary attacks impractical */
592 Com_DPrintf(DEBUG_SERVER, "SVC_RemoteCommand: rate limit from %s exceeded, dropping request\n", peername);
593 return;
594 }
595
596 const bool valid = Rcon_Validate(Cmd_Argv(1));
597 if (!valid) {
598 static leakyBucket_t bucket;
599 /* Make DoS via rcon impractical */
600 if (SVC_RateLimit(&bucket, 10, 1000)) {
601 Com_DPrintf(DEBUG_SERVER, "SVC_RemoteCommand: rate limit exceeded, dropping request\n");
602 return;
603 }
604
605 Com_Printf("Bad rcon from %s with password: '%s'\n", peername, Cmd_Argv(1));
606 } else {
607 Com_Printf("Rcon from %s\n", peername);
608 }
609
610 static char sv_outputbuf[1024];
611 Com_BeginRedirect(stream, sv_outputbuf, sizeof(sv_outputbuf));
612
613 if (!valid) {
614 /* inform the client */
616 } else {
617 char remaining[1024] = "";
618 int i;
619
620 /* execute the rcon commands */
621 for (i = 2; i < Cmd_Argc(); i++) {
622 Q_strcat(remaining, sizeof(remaining), "%s ", Cmd_Argv(i));
623 }
624
625 /* execute the string */
626 Cmd_ExecuteString("%s", remaining);
627 }
628
630}
631
639{
640 char s[512];
641
642 NET_ReadStringLine(msg, s, sizeof(s));
643 Cmd_TokenizeString(s, false, false);
644
645 const char* c = Cmd_Argv(0);
646 Com_DPrintf(DEBUG_SERVER, "Packet : %s\n", c);
647
648 if (Q_streq(c, SV_CMD_TEAMINFO)) {
650 } else if (Q_streq(c, SV_CMD_INFO)) {
652 } else if (Q_streq(c, SV_CMD_STATUS)) {
654 } else if (Q_streq(c, SV_CMD_CONNECT)) {
656 } else if (Q_streq(c, SV_CMD_RCON)) {
658 } else {
659 char buf[256];
660 Com_Printf("Bad connectionless packet from %s:\n%s\n", NET_StreamPeerToName(stream, buf, sizeof(buf), true), s);
661 }
662}
663
669void SV_ReadPacket (struct net_stream* s)
670{
671 client_t* cl = static_cast<client_t* >(NET_StreamGetData(s));
672 dbuffer* msg;
673
674 while ((msg = NET_ReadMsg(s))) {
675 const int cmd = NET_ReadByte(msg);
676
677 if (cmd == clc_oob)
679 else if (cl)
680 SV_ExecuteClientMessage(cl, cmd, msg);
681 else {
683 s = nullptr;
684 }
685
686 delete msg;
687 }
688}
689
690#define HEARTBEAT_SECONDS 30
691
693
698static int Master_HeartbeatThread (void* data)
699{
700 char url[512];
701 Com_sprintf(url, sizeof(url), "%s/ufo/masterserver.php?heartbeat&port=%s", masterserver_url->string, port->string);
702
703 /* send to master */
704 Com_Printf("sending heartbeat\n");
705 HTTP_GetURL(url, nullptr);
706
708 return 0;
709}
710
714static void Master_Heartbeat (void)
715{
716 if (!sv_dedicated || !sv_dedicated->integer)
717 return; /* only dedicated servers send heartbeats */
718
719 if (!sv_public || !sv_public->integer)
720 return; /* a private dedicated game */
721
722 /* check for time wraparound */
723 if (svs.lastHeartbeat > svs.realtime)
724 svs.lastHeartbeat = svs.realtime;
725
726 if (svs.realtime - svs.lastHeartbeat < HEARTBEAT_SECONDS * 1000)
727 return; /* not time to send yet */
728
729 svs.lastHeartbeat = svs.realtime;
730
731 if (masterServerHeartBeatThread != nullptr)
732 SDL_WaitThread(masterServerHeartBeatThread, nullptr);
733
735}
736
742static void SV_CheckSpawnSoldiers (void)
743{
744 /* already started? */
745 if (sv->spawned)
746 return;
747
748 client_t* cl = nullptr;
749 while ((cl = SV_GetNextClient(cl)) != nullptr) {
750 /* all players must be connected and all of them must have set
751 * the ready flag */
752 if (cl->state != cs_began || !cl->player->isReady())
753 return;
754 }
755
756 sv->spawned = true;
757
758 cl = nullptr;
759 while ((cl = SV_GetNextClient(cl)) != nullptr)
760 if (cl->state != cs_free)
762}
763
764static void SV_CheckStartMatch (void)
765{
766 client_t* cl;
767
768 if (!sv->spawned || sv->started)
769 return;
770
771 if (sv_maxclients->integer > 1) {
772 cl = nullptr;
773 while ((cl = SV_GetNextClient(cl)) != nullptr) {
774 /* all players must have their actors spawned */
775 if (cl->state != cs_spawned)
776 return;
777 }
778 } else if (SV_GetClient(0)->state != cs_spawned) {
779 /* in single player mode we must have received the 'spawnsoldiers' */
780 return;
781 }
782
783 sv->started = true;
784
785 cl = nullptr;
786 while ((cl = SV_GetNextClient(cl)) != nullptr)
787 if (cl->state != cs_free)
789}
790
791#define PING_SECONDS 5
792
793static void SV_PingPlayers (void)
794{
795 /* check for time wraparound */
796 if (svs.lastPing > svs.realtime)
797 svs.lastPing = svs.realtime;
798
799 if (svs.realtime - svs.lastPing < PING_SECONDS * 1000)
800 return; /* not time to send yet */
801
802 svs.lastPing = svs.realtime;
803 client_t* cl = nullptr;
804 while ((cl = SV_GetNextClient(cl)) != nullptr) {
805 if (cl->state == cs_free)
806 continue;
807 dbuffer msg(1);
808 NET_WriteByte(&msg, svc_ping);
809 NET_WriteMsg(cl->stream, msg);
810 }
811}
812
813static void SV_CheckTimeouts (void)
814{
815 const int droppoint = svs.realtime - 1000 * sv_timeout->integer;
816
817 if (sv_maxclients->integer == 1)
818 return;
819
820 client_t* cl = nullptr;
821 while ((cl = SV_GetNextClient(cl)) != nullptr) {
822 if (cl->state == cs_free)
823 continue;
824
825 /* might be invalid across a mapchange */
826 if (cl->lastmessage > svs.realtime)
827 cl->lastmessage = svs.realtime;
828
829 if (cl->lastmessage > 0 && cl->lastmessage < droppoint)
830 SV_DropClient(cl, "timed out");
831 }
832}
833
837void SV_Frame (int now, void* data)
838{
840
841 /* change the gametype even if no server is running (e.g. the first time) */
842 if (sv_dedicated->integer && sv_gametype->modified) {
844 sv_gametype->modified = false;
845 }
846
847 if (sv_dedicated->integer) {
848 const char* s;
849 do {
850 s = Sys_ConsoleInput();
851 if (s)
852 Cbuf_AddText("%s\n", s);
853 } while (s);
854 }
855
856 /* if server is not active, do nothing */
857 if (!svs.initialized) {
858#ifdef DEDICATED_ONLY
859 Com_Printf("Starting next map from the mapcycle\n");
861#endif
862 return;
863 }
864
865 svs.realtime = now;
866
867 /* if time is about to hit the 32nd bit, kick all clients
868 * and clear sv.time, rather
869 * than checking for negative time wraparound everywhere.
870 * 2giga-milliseconds = 23 days, so it won't be too often */
871 if (svs.realtime > 0x70000000) {
872 SV_Map(true, sv->name, sv->assembly);
873 return;
874 }
875
876 /* keep the random time dependent */
877 rand();
878
882
883 if (!sv_threads->integer)
885 else
886 /* signal the game frame thread to wake up */
887 SDL_CondSignal(svs.gameFrameCond);
889
890 /* next map in the cycle */
891 if (sv->endgame && sv_maxclients->integer > 1)
893
894 /* send a heartbeat to the master if needed */
897
898 /* server is empty - so shutdown */
899 if (svs.abandon && svs.killserver)
900 SV_Shutdown("Server disconnected.", false);
901}
902
906static void Master_Shutdown (void)
907{
908 if (!sv_dedicated || !sv_dedicated->integer)
909 return; /* only dedicated servers send heartbeats */
910
911 if (!sv_public || !sv_public->integer)
912 return; /* a private dedicated game */
913
914 /* send to master */
915 HTTP_GetURL(va("%s/ufo/masterserver.php?shutdown&port=%s", masterserver_url->string, port->string), nullptr);
916}
917
922{
923 /* call prog code to allow overrides */
924 {
925 const ScopedMutex scopedMutex(svs.serverMutex);
926 svs.ge->ClientUserinfoChanged(*cl->player, cl->userinfo);
927 }
928
929 /* name of the player */
930 Q_strncpyz(cl->name, Info_ValueForKey(cl->userinfo, "cl_name"), sizeof(cl->name));
931 /* mask off high bit */
932 for (int i = 0; i < sizeof(cl->name); i++)
933 cl->name[i] &= 127;
934
935 /* msg command */
936 cl->messagelevel = Info_IntegerForKey(cl->userinfo, "cl_msg");
937
938 Com_DPrintf(DEBUG_SERVER, "SV_UserinfoChanged: Changed userinfo for player %s\n", cl->name);
939}
940
942{
943 const int max = MAX_ACTIVETEAM;
944 return Cvar_AssertValue(cvar, 1, max, true);
945}
946
948{
949 return &sv->mapData;
950}
951
953{
954 return &sv->mapTiles;
955}
956
960void SV_Init (void)
961{
962 Com_Printf("\n------ server initialization -------\n");
963
964 sv_genericPool = Mem_CreatePool("Server: Generic");
965
966 OBJZERO(svs);
967
969
971
972 rcon_password = Cvar_Get("rcon_password", "", CVAR_ARCHIVE);
973 Cvar_Get("sv_cheats", "0", CVAR_SERVERINFO | CVAR_LATCH);
975 /* this cvar will become a latched cvar when you start the server */
976 sv_maxclients = Cvar_Get("sv_maxclients", "1", CVAR_SERVERINFO, "Max. connected clients");
977 sv_hostname = Cvar_Get("sv_hostname", "noname", CVAR_SERVERINFO | CVAR_ARCHIVE, "The name of the server that is displayed in the serverlist");
978 sv_http_downloadserver = Cvar_Get("sv_http_downloadserver", "", CVAR_ARCHIVE, "URL to a location where clients can download game content over HTTP");
979 sv_enablemorale = Cvar_Get("sv_enablemorale", "1", CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_LATCH, "Enable morale changes in multiplayer");
980 sv_maxsoldiersperteam = Cvar_Get("sv_maxsoldiersperteam", "4", CVAR_ARCHIVE | CVAR_SERVERINFO, "Max. amount of soldiers per team (see sv_maxsoldiersperplayer and sv_teamplay)");
981 sv_maxsoldiersperplayer = Cvar_Get("sv_maxsoldiersperplayer", DOUBLEQUOTE(MAX_ACTIVETEAM), CVAR_ARCHIVE | CVAR_SERVERINFO, "Max. amount of soldiers each player can control (see maxsoldiers and sv_teamplay)");
982 Cvar_SetCheckFunction("sv_maxsoldiersperplayer", SV_CheckMaxSoldiersPerPlayer);
983
984 sv_dumpmapassembly = Cvar_Get("sv_dumpmapassembly", "0", CVAR_ARCHIVE, "Dump map assembly information to game console");
985
986 sv_threads = Cvar_Get("sv_threads", "1", CVAR_LATCH | CVAR_ARCHIVE, "Run the server threaded");
987 sv_rma = Cvar_Get("sv_rma", "2", 0, "1 = old algorithm, 2 = new algorithm");
988 sv_rmadisplaythemap = Cvar_Get("sv_rmadisplaythemap", "0", 0, "Activate rma problem output");
989 sv_public = Cvar_Get("sv_public", "1", 0, "Should heartbeats be send to the masterserver");
990 sv_reconnect_limit = Cvar_Get("sv_reconnect_limit", "3", CVAR_ARCHIVE, "Minimum seconds between connect messages");
991 sv_timeout = Cvar_Get("sv_timeout", "200", CVAR_ARCHIVE, "Seconds until a client times out");
992
994 SV_LogInit();
995}
996
1002static void SV_FinalMessage (const char* message, bool reconnect)
1003{
1004 client_t* cl;
1005 dbuffer msg(2 + strlen(message));
1006
1007 if (reconnect)
1009 else
1011 NET_WriteString(&msg, message);
1012
1013 cl = nullptr;
1014 while ((cl = SV_GetNextClient(cl)) != nullptr)
1015 if (cl->state >= cs_connected) {
1016 NET_WriteConstMsg(cl->stream, msg);
1017 NET_StreamFinished(cl->stream);
1018 cl->stream = nullptr;
1019 }
1020
1021 /* make sure, that this is send */
1022 NET_Wait(0);
1023}
1024
1030void SV_Clear (void)
1031{
1034}
1035
1042void SV_Shutdown (const char* finalmsg, bool reconnect)
1043{
1044 if (!svs.initialized)
1045 return;
1046
1047 if (svs.clients)
1048 SV_FinalMessage(finalmsg, reconnect);
1049
1050 Com_Printf("Shutdown server: %s\n", finalmsg);
1051
1054
1055 NET_DatagramSocketClose(svs.netDatagramSocket);
1056 SV_Stop();
1057
1058 for (int i = 0; i < sv->numSVModels; i++) {
1059 sv_model_t* model = &sv->svModels[i];
1060 Mem_Free(model->name);
1061 }
1062
1063 /* free current level */
1064 OBJZERO(*sv);
1065
1066 /* free server static data */
1067 Mem_Free(svs.clients);
1068
1069 SDL_DestroyMutex(svs.serverMutex);
1070
1071 OBJZERO(svs);
1072
1073 /* maybe we shut down before we init - e.g. in case of an error */
1074 if (sv_maxclients)
1075 sv_maxclients->flags &= ~CVAR_LATCH;
1076
1077 if (sv_mapname)
1078 sv_mapname->flags &= ~CVAR_NOSET;
1079}
1080
1086{
1087 svs.abandon = true;
1088 /* pretend server is already dead, otherwise clients may try and reconnect */
1090}
1091
1097{
1098 int count = 0;
1099 client_t* cl;
1100
1101 if (!svs.initialized)
1102 return 0;
1103
1104 cl = nullptr;
1105 while ((cl = SV_GetNextClient(cl)) != nullptr) {
1106 if (cl->state != cs_spawned)
1107 continue;
1108
1109 count++;
1110 }
1111
1112 return count;
1113}
const char * Sys_ConsoleInput(void)
clientBattleScape_t cl
void setInUse(bool inuse)
Definition game.h:55
void Cmd_ExecuteString(const char *text,...)
A complete command line has been parsed, so try to execute it.
Definition cmd.cpp:1007
const char * Cmd_Argv(int arg)
Returns a given argument.
Definition cmd.cpp:516
void Cmd_TokenizeString(const char *text, bool macroExpand, bool replaceWhitespaces)
Parses the given string into command line tokens.
Definition cmd.cpp:565
int Cmd_Argc(void)
Return the number of arguments of the current command. "command parameter" will result in a argc of 2...
Definition cmd.cpp:505
void Cbuf_AddText(const char *format,...)
Adds command text at the end of the buffer.
Definition cmd.cpp:126
void Com_SetServerState(int state)
Definition common.cpp:587
cvar_t * port
Definition common.cpp:58
void Com_ReadFromPipe(void)
Read whatever is in com_pipefile, if anything, and execute it.
Definition common.cpp:1278
void Com_DPrintf(int level, const char *fmt,...)
A Com_Printf that only shows up if the "developer" cvar is set.
Definition common.cpp:440
cvar_t * sv_dedicated
Definition common.cpp:51
void Com_Error(int code, const char *fmt,...)
Definition common.cpp:459
void Com_SetGameType(void)
Definition common.cpp:832
void Com_Printf(const char *const fmt,...)
Definition common.cpp:428
cvar_t * masterserver_url
Definition common.cpp:57
void Com_BeginRedirect(struct net_stream *stream, char *buffer, int buffersize)
Redirect packets/output from server to client.
Definition common.cpp:350
bool Com_CheckConfigStringIndex(int index)
Definition common.cpp:877
void Com_EndRedirect(void)
End the redirection of packets/output.
Definition common.cpp:367
cvar_t * sv_gametype
Definition common.cpp:56
cvar_t * sv_maxclients
Definition g_main.cpp:43
@ clc_oob
Definition common.h:182
#define PROTOCOL_VERSION
Definition common.h:134
@ svc_reconnect
Definition common.h:150
@ svc_ping
Definition common.h:148
@ svc_oob
Definition common.h:156
@ svc_disconnect
Definition common.h:149
#define UFO_VERSION
Definition common.h:36
#define ERR_FATAL
Definition common.h:210
bool Cvar_SetCheckFunction(const char *varName, bool(*check)(cvar_t *cvar))
Set a checker function for cvar values.
Definition cvar.cpp:139
bool Cvar_AssertValue(cvar_t *cvar, float minVal, float maxVal, bool shouldBeIntegral)
Checks cvar values.
Definition cvar.cpp:161
cvar_t * Cvar_Get(const char *var_name, const char *var_value, int flags, const char *desc)
Init or return a cvar.
Definition cvar.cpp:342
const char * Cvar_GetString(const char *varName)
Returns the value of cvar as string.
Definition cvar.cpp:210
const char * Cvar_Serverinfo(char *info, size_t infoSize)
Returns an info string containing all the CVAR_SERVERINFO cvars.
Definition cvar.cpp:977
#define CVAR_SERVERINFO
Definition cvar.h:42
#define CVAR_ARCHIVE
Definition cvar.h:40
#define CVAR_NOSET
Definition cvar.h:43
#define CVAR_LATCH
Definition cvar.h:44
#define PRINT_CHAT
Definition defines.h:106
#define MAX_ACTIVETEAM
Definition defines.h:41
#define MAX_TOKEN_CHARS
Definition defines.h:372
#define DEBUG_SERVER
Definition defines.h:60
#define MAX_MODELS
Definition defines.h:100
cvar_t * sv_enablemorale
Definition g_main.cpp:55
cvar_t * sv_maxsoldiersperplayer
Definition g_main.cpp:54
cvar_t * sv_maxsoldiersperteam
Definition g_main.cpp:53
cvar_t * password
Definition g_main.cpp:67
bool HTTP_GetURL(const char *url, http_callback_t callback, void *userdata, const char *postfields)
Downloads the given url and return the data to the callback (optional).
Definition http.cpp:384
const char * Info_ValueForKey(const char *s, const char *key)
Searches the string for the given key and returns the associated value, or an empty string.
void Info_SetValueForKey(char *s, const size_t size, const char *key, const char *value)
Adds a new entry into string with given value.
void Info_SetValueForKeyAsInteger(char *s, const size_t size, const char *key, const int value)
int Info_IntegerForKey(const char *s, const char *key)
#define MAX_INFO_STRING
Definition infostring.h:36
voidpf stream
Definition ioapi.h:42
voidpf void * buf
Definition ioapi.h:42
#define Mem_Free(ptr)
Definition mem.h:35
#define Mem_CreatePool(name)
Definition mem.h:32
#define Mem_PoolAllocType(type, pool)
Definition mem.h:43
void SV_Stop(void)
Definition net.cpp:1026
dbuffer * NET_ReadMsg(struct net_stream *s)
Reads messages from the network channel and adds them to the dbuffer where you can use the NET_Read* ...
Definition net.cpp:774
void NET_StreamSetData(struct net_stream *s, void *data)
Definition net.cpp:805
void * NET_StreamGetData(struct net_stream *s)
Definition net.cpp:800
void NET_StreamFinished(struct net_stream *s)
Call NET_StreamFinished to mark the stream as uninteresting, but to finish sending any data in the bu...
Definition net.cpp:832
void NET_StreamFree(struct net_stream *s)
Call NET_StreamFree to dump the whole thing right now.
Definition net.cpp:817
const char * NET_StreamToString(struct net_stream *s)
Returns the numerical representation of a net_stream.
Definition net.cpp:859
void NET_Wait(int timeout)
Definition net.cpp:423
void NET_DatagramSocketClose(struct datagram_socket *s)
Definition net.cpp:1182
const char * NET_StreamPeerToName(struct net_stream *s, char *dst, int len, bool appendPort)
Definition net.cpp:872
int NET_ReadStringLine(dbuffer *buf, char *string, size_t length)
Definition netpack.cpp:328
void NET_WriteRawString(dbuffer *buf, const char *str)
Skip the zero string terminal character. If you need it, use NET_WriteString.
Definition netpack.cpp:71
void NET_WriteMsg(struct net_stream *s, dbuffer &buf)
Enqueue the buffer in the net stream for ONE client.
Definition netpack.cpp:569
int NET_ReadByte(dbuffer *buf)
Reads a byte from the netchannel.
Definition netpack.cpp:234
void NET_WriteByte(dbuffer *buf, byte c)
Definition netpack.cpp:39
void NET_WriteConstMsg(struct net_stream *s, const dbuffer &buf)
Enqueue the buffer in the net stream for MULTIPLE clients.
Definition netpack.cpp:588
void NET_OOB_Printf(struct net_stream *s, const char *format,...)
Out of band print.
Definition netpack.cpp:548
void NET_WriteString(dbuffer *buf, const char *str)
Definition netpack.cpp:59
#define CS_ENTITYSTRING
Definition q_shared.h:324
#define MAX_CLIENTS
Definition q_shared.h:299
#define CL_CMD_CLIENT_CONNECT
Definition q_shared.h:606
#define MAX_ENTITYSTRINGS
Definition q_shared.h:301
#define MAX_TILESTRINGS
Definition q_shared.h:298
#define REJ_CONNECTION_REFUSED
Definition q_shared.h:586
#define CS_PLAYERNAMES
Definition q_shared.h:328
#define BAD_RCON_PASSWORD
Definition q_shared.h:581
#define CS_TILES
Definition q_shared.h:325
#define CS_LIGHTMAP
Definition q_shared.h:321
#define SV_CMD_PRINT
Definition q_shared.h:593
#define CS_GENERAL
Definition q_shared.h:329
#define CS_POSITIONS
Definition q_shared.h:326
#define CL_SPAWNSOLDIERS
Definition q_shared.h:602
#define REJ_SERVER_FULL
Definition q_shared.h:583
#define SV_CMD_INFO
Definition q_shared.h:592
#define REJ_SERVER_VERSION_MISMATCH
Definition q_shared.h:584
#define SV_CMD_RCON
Definition q_shared.h:589
#define SV_CMD_STATUS
Definition q_shared.h:590
#define SV_CMD_TEAMINFO
Definition q_shared.h:591
#define SV_CMD_CONNECT
Definition q_shared.h:588
#define MAX_GENERAL
Definition q_shared.h:300
#define CL_STARTMATCH
Definition q_shared.h:603
#define CS_OBJECTAMOUNT
Definition q_shared.h:320
#define CS_MAPCHECKSUM
Definition q_shared.h:312
#define CS_MODELS
Definition q_shared.h:327
#define TEAM_NO_ACTIVE
Definition q_shared.h:60
#define REJ_GAME_ALREADY_STARTED
Definition q_shared.h:585
#define CS_UFOCHECKSUM
Definition q_shared.h:319
static wrapCache_t * hash[MAX_WRAP_HASH]
Definition r_font.cpp:86
QGL_EXTERN GLuint count
Definition r_gl.h:99
QGL_EXTERN GLsizei const GLvoid * data
Definition r_gl.h:89
QGL_EXTERN GLuint index
Definition r_gl.h:110
QGL_EXTERN GLint i
Definition r_gl.h:113
Main server include file.
cvar_t * sv_mapname
Definition sv_main.cpp:53
memPool_t * sv_genericPool
Definition sv_main.cpp:55
void SV_MapcycleClear(void)
Empty the mapcycle list.
cvar_t * sv_rmadisplaythemap
display a character graphic of the tiles placed when RMA2 reaches a dead end.
Definition sv_main.cpp:50
void SV_Map(bool day, const char *levelstring, const char *assembly, bool verbose=true)
Change the server to a new map, taking all connected clients along with it.
Definition sv_init.cpp:113
void SV_MapcycleInit(void)
void SV_ShutdownGameProgs(void)
Called when either the entire server is being killed, or it is changing to a different game directory...
Definition sv_game.cpp:681
@ cs_spawned
Definition server.h:145
@ cs_connected
Definition server.h:141
@ cs_free
Definition server.h:140
@ cs_spawning
Definition server.h:142
@ cs_began
Definition server.h:143
void SV_InitOperatorCommands(void)
Definition sv_ccmds.cpp:542
cvar_t * sv_threads
Definition sv_main.cpp:48
#define PLAYER_NUM(n)
Definition server.h:137
void SV_NextMapcycle(void)
Start the next map in the cycle.
@ ss_dead
Definition server.h:96
void SV_RunGameFrame(void)
Calls the G_RunFrame function from game api let everything in the world think and move.
Definition sv_game.cpp:736
serverInstanceGame_t * sv
Definition sv_init.cpp:36
void SV_SetClientState(client_t *client, client_state_t state)
Set the client state.
Definition sv_user.cpp:36
void void void SV_BroadcastPrintf(int level, const char *fmt,...) __attribute__((format(__printf__
#define SV_SetConfigString(index, value)
Definition server.h:188
serverInstanceStatic_t svs
Definition sv_init.cpp:35
cvar_t * sv_public
Definition sv_main.cpp:52
void SV_ClientCommand(client_t *client, const char *fmt,...) __attribute__((format(__printf__
cvar_t * sv_rma
Definition sv_main.cpp:49
void void void void SV_ExecuteClientMessage(client_t *cl, int cmd, dbuffer *msg)
The current net_message is parsed for the given client.
Definition sv_user.cpp:235
cvar_t * sv_dumpmapassembly
Definition sv_main.cpp:47
#define Q_streq(a, b)
Definition shared.h:136
bool Q_strnull(const char *string)
Definition shared.h:138
#define DOUBLEQUOTE(x)
Definition shared.h:90
#define OBJZERO(obj)
Definition shared.h:178
#define MAX_VAR
Definition shared.h:36
unsigned int Com_HashKey(const char *name, int hashsize)
returns hash key for a string
Definition shared.cpp:336
void Q_strncpyz(char *dest, const char *src, size_t destsize)
Safe strncpy that ensures a trailing zero.
Definition shared.cpp:457
void Q_strcat(char *dest, size_t destsize, const char *format,...)
Safely (without overflowing the destination buffer) concatenates two strings.
Definition shared.cpp:475
bool Com_sprintf(char *dest, size_t size, const char *fmt,...)
copies formatted string with buffer-size checking
Definition shared.cpp:494
const char * va(const char *format,...)
does a varargs printf into a temp buffer, so I don't need to have varargs versions of all text functi...
Definition shared.cpp:410
player_t * player
Definition server.h:158
char name[32]
Definition server.h:159
client_state_t state
Definition server.h:156
struct net_stream * stream
Definition server.h:163
This is a cvar definition. Cvars can be user modified and used in our menus e.g.
Definition cvar.h:71
struct leakyBucket_s * next
Definition sv_main.cpp:66
char node[64]
Definition sv_main.cpp:58
struct leakyBucket_s * prev
Definition sv_main.cpp:65
signed char burst
Definition sv_main.cpp:61
Struct that is only valid for one map. It's deleted on every map load.
Definition server.h:106
static mesh models (none-animated) can have a server side flag set to be clipped for pathfinding
Definition server.h:47
char * name
Definition server.h:50
void SV_LogInit(void)
Definition sv_log.cpp:71
void SV_LogShutdown(void)
Definition sv_log.cpp:78
void SV_LogHandleOutput(void)
Handle the log output from the main thread by reading the strings from the dbuffer the game lib threa...
Definition sv_log.cpp:44
game lib logging handling
char * SV_GetConfigString(int index)
Definition sv_main.cpp:77
static leakyBucket_t * SVC_BucketForAddress(struct net_stream &address, int burst, int period)
Find or allocate a bucket for an address.
Definition sv_main.cpp:227
static leakyBucket_t * bucketHashes[MAX_HASHES]
Definition sv_main.cpp:74
static int Master_HeartbeatThread(void *data)
Send a message to the master every few minutes to let it know we are alive, and log information.
Definition sv_main.cpp:698
static void Master_Shutdown(void)
Informs all masters that this server is going down.
Definition sv_main.cpp:906
static bool Rcon_Validate(const char *password)
Checks whether the remote connection is allowed (rcon_password must be set on the server) - and verif...
Definition sv_main.cpp:569
static cvar_t * sv_http_downloadserver
Definition sv_main.cpp:37
client_t * SV_GetClient(int index)
Definition sv_main.cpp:174
static void SVC_TeamInfo(struct net_stream *s)
Responds with teaminfo such as free team num.
Definition sv_main.cpp:323
static void SVC_RemoteCommand(struct net_stream *stream)
A client issued an rcon command. Shift down the remaining args. Redirect all printfs.
Definition sv_main.cpp:585
static leakyBucket_t buckets[MAX_BUCKETS]
Definition sv_main.cpp:73
static cvar_t * rcon_password
Definition sv_main.cpp:36
static void SVC_Info(struct net_stream *s)
Responds with short info for broadcast scans.
Definition sv_main.cpp:411
void SV_ReadPacket(struct net_stream *s)
Definition sv_main.cpp:669
static bool SVC_RateLimit(leakyBucket_t *bucket, int burst=10, int period=100)
Definition sv_main.cpp:284
static void SV_PingPlayers(void)
Definition sv_main.cpp:793
static void SVC_DirectConnect(struct net_stream *stream)
A connection request that did not come from the master.
Definition sv_main.cpp:462
static void SV_ConnectionlessPacket(struct net_stream *stream, dbuffer *msg)
Handles a connectionless message from a client.
Definition sv_main.cpp:638
int SV_GetConfigStringLength(int index)
Definition sv_main.cpp:85
static cvar_t * sv_timeout
Definition sv_main.cpp:44
#define MAX_BUCKETS
Definition sv_main.cpp:70
int SV_GetConfigStringInteger(int index)
Definition sv_main.cpp:108
#define PING_SECONDS
Definition sv_main.cpp:791
mapData_t * SV_GetMapData(void)
Definition sv_main.cpp:947
static void SV_CheckSpawnSoldiers(void)
If all connected clients have set their ready flag the server will spawn the clients and that change ...
Definition sv_main.cpp:742
void SV_ShutdownWhenEmpty(void)
Will eventually shutdown the server once all clients have disconnected.
Definition sv_main.cpp:1085
mapTiles_t * SV_GetMapTiles(void)
Definition sv_main.cpp:952
static void SV_FinalMessage(const char *message, bool reconnect)
Used by SV_Shutdown to send a final message to all connected clients before the server goes down.
Definition sv_main.cpp:1002
static void SV_CheckStartMatch(void)
Definition sv_main.cpp:764
void SV_Shutdown(const char *finalmsg, bool reconnect)
Called when each game quits, before Sys_Quit or Sys_Error.
Definition sv_main.cpp:1042
int SV_CountPlayers(void)
Returns the number of spawned players.
Definition sv_main.cpp:1096
static void SV_CheckTimeouts(void)
Definition sv_main.cpp:813
static bool SVC_RateLimitAddress(struct net_stream &from, int burst=10, int period=1000)
Rate limit for a particular address.
Definition sv_main.cpp:313
client_t * SV_GetNextClient(client_t *lastClient)
Iterates through clients.
Definition sv_main.cpp:152
void SV_Frame(int now, void *data)
Definition sv_main.cpp:837
static bool SV_CheckMaxSoldiersPerPlayer(cvar_t *cvar)
Definition sv_main.cpp:941
void SV_Clear(void)
Cleanup when the whole game process is shutting down.
Definition sv_main.cpp:1030
void SV_DropClient(client_t *drop, const char *message)
Called when the player is totally leaving the server, either willingly or unwillingly....
Definition sv_main.cpp:184
void SV_Init(void)
Only called once at startup, not for each game.
Definition sv_main.cpp:960
static void SVC_Status(struct net_stream *s)
Responds with all the info that the server browser can see.
Definition sv_main.cpp:370
#define HEARTBEAT_SECONDS
Definition sv_main.cpp:690
void SV_UserinfoChanged(client_t *cl)
Pull specific info from a newly changed userinfo string into a more C friendly form.
Definition sv_main.cpp:921
static SDL_Thread * masterServerHeartBeatThread
Definition sv_main.cpp:692
static cvar_t * sv_reconnect_limit
Definition sv_main.cpp:43
static cvar_t * sv_hostname
Definition sv_main.cpp:41
#define MAX_HASHES
Definition sv_main.cpp:71
static void Master_Heartbeat(void)
Definition sv_main.cpp:714
static leakyBucket_t outboundLeakyBucket
Definition sv_main.cpp:75
System specific stuff.
int Sys_Milliseconds(void)
SDL_Thread * Com_CreateThread(int(*fn)(void *), const char *name, void *data=nullptr)
Definition thread.h:5