2 Copyright (C) 2002 Mathieu Olivier
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 cvar_t sv_public = {0, "sv_public", "0"};
26 cvar_t sv_heartbeatperiod = {CVAR_SAVE, "sv_heartbeatperiod", "180"};
28 cvar_t sv_masters [] =
30 {0, "sv_masterextra1", "68.102.242.12"},
31 {CVAR_SAVE, "sv_master1", ""},
32 {CVAR_SAVE, "sv_master2", ""},
33 {CVAR_SAVE, "sv_master3", ""},
34 {CVAR_SAVE, "sv_master4", ""}
37 static double nextheartbeattime = 0;
44 Allow (or not) NET_Heartbeat to proceed depending on various factors
47 qboolean Master_AllowHeartbeat (int priority)
49 // LordHavoc: make advertising optional
50 if (!sv_public.integer)
52 // LordHavoc: don't advertise singleplayer games
53 if (svs.maxclients < 2)
55 // if it's a state change (client connected), limit next heartbeat to no
56 // more than 30 sec in the future
57 if (priority == 1 && nextheartbeattime > realtime + 30.0)
58 nextheartbeattime = realtime + 30.0;
59 if (priority <= 1 && realtime < nextheartbeattime)
61 // limit heartbeatperiod to 30 to 270 second range,
62 // lower limit is to avoid abusing master servers with excess traffic,
63 // upper limit is to avoid timing out on the master server (which uses
65 if (sv_heartbeatperiod.value < 30)
66 Cvar_SetValueQuick(&sv_heartbeatperiod, 30);
67 if (sv_heartbeatperiod.value > 270)
68 Cvar_SetValueQuick(&sv_heartbeatperiod, 270);
69 // send a heartbeat as often as the admin wants
70 nextheartbeattime = realtime + sv_heartbeatperiod.value;
77 Master_BuildGetServers
79 Build a getservers request for a master server
82 const char* Master_BuildGetServers (void)
84 static int nextmaster = 0;
88 if (nextmaster >= (int)(sizeof (sv_masters) / sizeof (sv_masters[0])))
94 sv_master = &sv_masters[nextmaster++];
96 // No master, no heartbeat
97 if (sv_master->string[0] == '\0')
103 // Build the heartbeat
104 snprintf (request, sizeof (request), "getservers %s %u empty full\x0A", gamename, NET_PROTOCOL_VERSION);
105 SZ_Clear (&net_message);
106 MSG_WriteLong (&net_message, -1);
107 MSG_WriteString (&net_message, request);
109 net_message.cursize--; // we don't send the trailing '\0'
111 return sv_master->string;
117 Master_BuildHeartbeat
119 Build an heartbeat for a master server
122 const char* Master_BuildHeartbeat (void)
124 static int nextmaster = 0;
127 if (nextmaster >= (int)(sizeof (sv_masters) / sizeof (sv_masters[0])))
133 sv_master = &sv_masters[nextmaster++];
135 // No master, no heartbeat
136 if (sv_master->string[0] == '\0')
142 // Build the heartbeat
143 SZ_Clear (&net_message);
144 MSG_WriteLong (&net_message, -1);
145 MSG_WriteString (&net_message, "heartbeat DarkPlaces\x0A");
147 net_message.cursize--; // we don't send the trailing '\0'
149 return sv_master->string;
157 Handle the master server messages
160 int Master_HandleMessage (void)
162 const char* string = MSG_ReadString ();
164 // If it's a "getinfo" request
165 if (!strncmp (string, "getinfo", 7))
170 length = snprintf (response, sizeof (response), "infoResponse\x0A"
171 "\\gamename\\%s\\modname\\%s\\sv_maxclients\\%d"
172 "\\clients\\%d\\mapname\\%s\\hostname\\%s\\protocol\\%d",
173 gamename, com_modname, svs.maxclients, net_activeconnections,
174 sv.name, hostname.string, NET_PROTOCOL_VERSION);
176 // Too long to fit into the buffer?
177 if (length >= sizeof (response))
180 // If there was a challenge in the getinfo message
181 if (string[7] == ' ')
183 string += 8; // skip the header and the space
185 // If the challenge wouldn't fit into the buffer
186 if (length + 11 + strlen (string) >= sizeof (response))
189 sprintf (response + length, "\\challenge\\%s", string);
192 SZ_Clear (&net_message);
193 MSG_WriteLong (&net_message, -1);
194 MSG_WriteString (&net_message, response);
196 return net_message.cursize - 1;
207 Initialize the code that handles master server requests and reponses
210 void Master_Init (void)
213 Cvar_RegisterVariable (&sv_public);
214 Cvar_RegisterVariable (&sv_heartbeatperiod);
215 for (ind = 0; ind < sizeof (sv_masters) / sizeof (sv_masters[0]); ind++)
216 Cvar_RegisterVariable (&sv_masters[ind]);
222 Master_ParseServerList
224 Parse getserverResponse messages
227 void Master_ParseServerList (net_landriver_t* dfunc)
233 struct qsockaddr svaddr;
236 if (net_message.cursize < (int)sizeof(int))
239 // is the cache full?
240 if (hostCacheCount == HOSTCACHESIZE)
244 control = BigLong(*((int *)net_message.data));
249 if (strncmp (net_message.data + 4, "getserversResponse\\", 19))
252 // Skip the next 19 bytes
253 MSG_ReadLong(); MSG_ReadLong(); MSG_ReadLong(); MSG_ReadLong();
254 MSG_ReadShort(); MSG_ReadByte();
256 crtserver = servers = Z_Malloc (net_message.cursize - 23);
257 memcpy (servers , net_message.data + 23, net_message.cursize - 23);
259 // Extract the IP addresses
260 while ((ipaddr = (crtserver[3] << 24) | (crtserver[2] << 16) | (crtserver[1] << 8) | crtserver[0]) != -1)
262 int port = (crtserver[5] << 8) | crtserver[4];
264 if (port == -1 || port == 0)
267 port = ((port >> 8) & 0xFF) + ((port & 0xFF) << 8);
268 sprintf (ipstring, "%u.%u.%u.%u:%hu",
269 ipaddr & 0xFF, (ipaddr >> 8) & 0xFF,
270 (ipaddr >> 16) & 0xFF, ipaddr >> 24,
272 dfunc->GetAddrFromName (ipstring, &svaddr);
274 Con_DPrintf("Requesting info from server %s\n", ipstring);
275 // Send a request at this address
276 SZ_Clear(&net_message);
277 MSG_WriteLong(&net_message, 0); // save space for the header, filled in later
278 MSG_WriteByte(&net_message, CCREQ_SERVER_INFO);
279 MSG_WriteString(&net_message, "QUAKE");
280 MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION);
281 *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
282 dfunc->Write(dfunc->controlSock, net_message.data, net_message.cursize, &svaddr);
283 SZ_Clear(&net_message);
285 if (crtserver[6] != '\\')