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 {CVAR_SAVE, "sv_master1", ""},
31 {CVAR_SAVE, "sv_master2", ""},
32 {CVAR_SAVE, "sv_master3", ""},
33 {CVAR_SAVE, "sv_master4", ""},
34 {0, "sv_masterextra1", "rick.cube-sol.net"},
35 {0, "sv_masterextra2", "198.88.152.4"},
36 {0, "sv_masterextra3", "68.102.242.12"}
39 static double nextheartbeattime = 0;
46 Allow (or not) NET_Heartbeat to proceed depending on various factors
49 qboolean Master_AllowHeartbeat (int priority)
51 // LordHavoc: make advertising optional
52 if (!sv_public.integer)
54 // LordHavoc: don't advertise singleplayer games
55 if (svs.maxclients < 2)
57 // if it's a state change (client connected), limit next heartbeat to no
58 // more than 30 sec in the future
59 if (priority == 1 && nextheartbeattime > realtime + 30.0)
60 nextheartbeattime = realtime + 30.0;
61 if (priority <= 1 && realtime < nextheartbeattime)
63 // limit heartbeatperiod to 30 to 270 second range,
64 // lower limit is to avoid abusing master servers with excess traffic,
65 // upper limit is to avoid timing out on the master server (which uses
67 if (sv_heartbeatperiod.value < 30)
68 Cvar_SetValueQuick(&sv_heartbeatperiod, 30);
69 if (sv_heartbeatperiod.value > 270)
70 Cvar_SetValueQuick(&sv_heartbeatperiod, 270);
71 // send a heartbeat as often as the admin wants
72 nextheartbeattime = realtime + sv_heartbeatperiod.value;
79 Master_BuildGetServers
81 Build a getservers request for a master server
84 const char* Master_BuildGetServers (void)
86 static int nextmaster = 0;
90 if (nextmaster >= (int)(sizeof (sv_masters) / sizeof (sv_masters[0])))
96 // find a non-empty master server address in the list
99 sv_master = &sv_masters[nextmaster++];
100 if (sv_master->string[0])
102 if (nextmaster >= (int)(sizeof (sv_masters) / sizeof (sv_masters[0])))
109 // Build the heartbeat
110 snprintf (request, sizeof (request), "getservers %s %u empty full\x0A", gamename, NET_PROTOCOL_VERSION);
111 SZ_Clear (&net_message);
112 MSG_WriteLong (&net_message, -1);
113 MSG_WriteString (&net_message, request);
115 net_message.cursize--; // we don't send the trailing '\0'
117 return sv_master->string;
123 Master_BuildHeartbeat
125 Build an heartbeat for a master server
128 const char* Master_BuildHeartbeat (void)
130 static int nextmaster = 0;
133 if (nextmaster >= (int)(sizeof (sv_masters) / sizeof (sv_masters[0])))
139 // find a non-empty master server address in the list
142 sv_master = &sv_masters[nextmaster++];
143 if (sv_master->string[0])
145 if (nextmaster >= (int)(sizeof (sv_masters) / sizeof (sv_masters[0])))
152 // Build the heartbeat
153 SZ_Clear (&net_message);
154 MSG_WriteLong (&net_message, -1);
155 MSG_WriteString (&net_message, "heartbeat DarkPlaces\x0A");
157 net_message.cursize--; // we don't send the trailing '\0'
159 return sv_master->string;
167 Handle the master server messages
170 int Master_HandleMessage (void)
172 const char* string = MSG_ReadString ();
174 // If it's a "getinfo" request
175 if (!strncmp (string, "getinfo", 7))
180 length = snprintf (response, sizeof (response), "infoResponse\x0A"
181 "\\gamename\\%s\\modname\\%s\\sv_maxclients\\%d"
182 "\\clients\\%d\\mapname\\%s\\hostname\\%s\\protocol\\%d",
183 gamename, com_modname, svs.maxclients, net_activeconnections,
184 sv.name, hostname.string, NET_PROTOCOL_VERSION);
186 // Too long to fit into the buffer?
187 if (length >= sizeof (response))
190 // If there was a challenge in the getinfo message
191 if (string[7] == ' ')
193 string += 8; // skip the header and the space
195 // If the challenge wouldn't fit into the buffer
196 if (length + 11 + strlen (string) >= sizeof (response))
199 sprintf (response + length, "\\challenge\\%s", string);
202 SZ_Clear (&net_message);
203 MSG_WriteLong (&net_message, -1);
204 MSG_WriteString (&net_message, response);
206 return net_message.cursize - 1;
217 Initialize the code that handles master server requests and reponses
220 void Master_Init (void)
223 Cvar_RegisterVariable (&sv_public);
224 Cvar_RegisterVariable (&sv_heartbeatperiod);
225 for (ind = 0; ind < sizeof (sv_masters) / sizeof (sv_masters[0]); ind++)
226 Cvar_RegisterVariable (&sv_masters[ind]);
232 Master_ParseServerList
234 Parse getserverResponse messages
235 Returns true if it was a valid getserversResponse
238 int Master_ParseServerList (net_landriver_t* dfunc)
244 struct qsockaddr svaddr;
248 if (developer.integer)
250 Con_Printf("Master_ParseServerList: packet received:\n");
251 SZ_HexDumpToConsole(&net_message);
254 if (net_message.cursize < 23)
257 // is the cache full?
258 if (hostCacheCount == HOSTCACHESIZE)
262 control = MSG_ReadBigLong();
266 if (MSG_ReadBytes(19, string) < 19 || memcmp(string, "getserversResponse\\", 19))
269 crtserver = servers = Z_Malloc (net_message.cursize - 23);
270 memcpy (servers , net_message.data + 23, net_message.cursize - 23);
272 // Extract the IP addresses
273 while ((crtserver[0] != 0xFF || crtserver[1] != 0xFF || crtserver[2] != 0xFF || crtserver[3] != 0xFF) && (crtserver[4] != 0 || crtserver[5] != 0))
275 // LordHavoc: FIXME: this could be much faster than converting to a string and back
276 // LordHavoc: FIXME: this code is very UDP specific, perhaps it should be part of net_udp?
277 sprintf (ipstring, "%u.%u.%u.%u:%u", crtserver[0], crtserver[1], crtserver[2], crtserver[3], (crtserver[4] << 8) | crtserver[5]);
278 dfunc->GetAddrFromName (ipstring, &svaddr);
279 Con_DPrintf("Requesting info from server %s\n", ipstring);
281 // Send a request at this address
282 SZ_Clear(&net_message);
283 MSG_WriteLong(&net_message, 0); // save space for the header, filled in later
284 MSG_WriteByte(&net_message, CCREQ_SERVER_INFO);
285 MSG_WriteString(&net_message, "QUAKE");
286 MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION);
287 *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
288 dfunc->Write(dfunc->controlSock, net_message.data, net_message.cursize, &svaddr);
289 SZ_Clear(&net_message);
293 if (crtserver[6] != '\\')