1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 1998-2000 by DooM Legacy Team.
4 // Copyright (C) 1999-2020 by Sonic Team Junior.
5 // Copyright (C)      2020 by James R.
6 //
7 // This program is free software distributed under the
8 // terms of the GNU General Public License, version 2.
9 // See the 'LICENSE' file for more details.
10 //-----------------------------------------------------------------------------
11 /// \file  mserv.c
12 /// \brief Commands used to communicate with the master server
13 
14 #if !defined (UNDER_CE)
15 #include <time.h>
16 #endif
17 
18 #include "doomstat.h"
19 #include "doomdef.h"
20 #include "command.h"
21 #include "i_threads.h"
22 #include "mserv.h"
23 #include "m_menu.h"
24 #include "z_zone.h"
25 
26 #ifdef MASTERSERVER
27 
28 static int     MSId;
29 static int     MSRegisteredId = -1;
30 
31 static boolean MSRegistered;
32 static boolean MSInProgress;
33 static boolean MSUpdateAgain;
34 
35 static time_t  MSLastPing;
36 
37 #ifdef HAVE_THREADS
38 static I_mutex MSMutex;
39 static I_cond  MSCond;
40 
41 #  define Lock_state()   I_lock_mutex  (&MSMutex)
42 #  define Unlock_state() I_unlock_mutex (MSMutex)
43 #else/*HAVE_THREADS*/
44 #  define Lock_state()
45 #  define Unlock_state()
46 #endif/*HAVE_THREADS*/
47 
48 #ifndef NONET
49 static void Command_Listserv_f(void);
50 #endif
51 
52 #endif/*MASTERSERVER*/
53 
54 static void Update_parameters (void);
55 
56 static void MasterServer_OnChange(void);
57 
58 static CV_PossibleValue_t masterserver_update_rate_cons_t[] = {
59 	{2,  "MIN"},
60 	{60, "MAX"},
61 	{0,NULL}
62 };
63 
64 consvar_t cv_masterserver = CVAR_INIT ("masterserver", "https://mb.srb2.org/MS/0", CV_SAVE|CV_CALL, NULL, MasterServer_OnChange);
65 consvar_t cv_servername = CVAR_INIT ("servername", "SRB2 server", CV_SAVE|CV_NETVAR|CV_CALL|CV_NOINIT, NULL, Update_parameters);
66 
67 consvar_t cv_masterserver_update_rate = CVAR_INIT ("masterserver_update_rate", "15", CV_SAVE|CV_CALL|CV_NOINIT, masterserver_update_rate_cons_t, Update_parameters);
68 
69 INT16 ms_RoomId = -1;
70 
71 #if defined (MASTERSERVER) && defined (HAVE_THREADS)
72 int           ms_QueryId;
73 I_mutex       ms_QueryId_mutex;
74 
75 msg_server_t *ms_ServerList;
76 I_mutex       ms_ServerList_mutex;
77 #endif
78 
79 UINT16 current_port = 0;
80 
81 // Room list is an external variable now.
82 // Avoiding having to get info ten thousand times...
83 msg_rooms_t room_list[NUM_LIST_ROOMS+1]; // +1 for easy test
84 
85 /** Adds variables and commands relating to the master server.
86   *
87   * \sa cv_masterserver, cv_servername,
88   *     Command_Listserv_f
89   */
AddMServCommands(void)90 void AddMServCommands(void)
91 {
92 #ifndef NONET
93 	CV_RegisterVar(&cv_masterserver);
94 	CV_RegisterVar(&cv_masterserver_update_rate);
95 	CV_RegisterVar(&cv_masterserver_timeout);
96 	CV_RegisterVar(&cv_masterserver_debug);
97 	CV_RegisterVar(&cv_masterserver_token);
98 	CV_RegisterVar(&cv_servername);
99 #ifdef MASTERSERVER
100 	COM_AddCommand("listserv", Command_Listserv_f);
101 #endif
102 #endif
103 }
104 
105 #ifdef MASTERSERVER
106 
WarnGUI(void)107 static void WarnGUI (void)
108 {
109 #ifdef HAVE_THREADS
110 	I_lock_mutex(&m_menu_mutex);
111 #endif
112 	M_StartMessage(M_GetText("There was a problem connecting to\nthe Master Server\n\nCheck the console for details.\n"), NULL, MM_NOTHING);
113 #ifdef HAVE_THREADS
114 	I_unlock_mutex(m_menu_mutex);
115 #endif
116 }
117 
118 #define NUM_LIST_SERVER MAXSERVERLIST
GetShortServersList(INT32 room,int id)119 msg_server_t *GetShortServersList(INT32 room, int id)
120 {
121 	msg_server_t *server_list;
122 
123 	// +1 for easy test
124 	server_list = malloc(( NUM_LIST_SERVER + 1 ) * sizeof *server_list);
125 
126 	if (HMS_fetch_servers(server_list, room, id))
127 		return server_list;
128 	else
129 	{
130 		free(server_list);
131 		WarnGUI();
132 		return NULL;
133 	}
134 }
135 
GetRoomsList(boolean hosting,int id)136 INT32 GetRoomsList(boolean hosting, int id)
137 {
138 	if (HMS_fetch_rooms( ! hosting, id))
139 		return 1;
140 	else
141 	{
142 		WarnGUI();
143 		return -1;
144 	}
145 }
146 
147 #ifdef UPDATE_ALERT
GetMODVersion(int id)148 char *GetMODVersion(int id)
149 {
150 	char *buffer;
151 	int c;
152 
153 	(void)id;
154 
155 	buffer = malloc(16);
156 
157 	c = HMS_compare_mod_version(buffer, 16);
158 
159 #ifdef HAVE_THREADS
160 	I_lock_mutex(&ms_QueryId_mutex);
161 	{
162 		if (id != ms_QueryId)
163 			c = -1;
164 	}
165 	I_unlock_mutex(ms_QueryId_mutex);
166 #endif
167 
168 	if (c > 0)
169 		return buffer;
170 	else
171 	{
172 		free(buffer);
173 
174 		if (! c)
175 			WarnGUI();
176 
177 		return NULL;
178 	}
179 }
180 
181 // Console only version of the above (used before game init)
GetMODVersion_Console(void)182 void GetMODVersion_Console(void)
183 {
184 	char buffer[16];
185 
186 	if (HMS_compare_mod_version(buffer, sizeof buffer) > 0)
187 		I_Error(UPDATE_ALERT_STRING_CONSOLE, VERSIONSTRING, buffer);
188 }
189 #endif
190 
191 #ifndef NONET
192 /** Gets a list of game servers. Called from console.
193   */
Command_Listserv_f(void)194 static void Command_Listserv_f(void)
195 {
196 	CONS_Printf(M_GetText("Retrieving server list...\n"));
197 
198 	{
199 		HMS_list_servers();
200 	}
201 }
202 #endif
203 
204 static void
Finish_registration(void)205 Finish_registration (void)
206 {
207 	int registered;
208 
209 	CONS_Printf("Registering this server on the master server...\n");
210 
211 	registered = HMS_register();
212 
213 	Lock_state();
214 	{
215 		MSRegistered = registered;
216 		MSRegisteredId = MSId;
217 
218 		time(&MSLastPing);
219 	}
220 	Unlock_state();
221 
222 	if (registered)
223 		CONS_Printf("Master server registration successful.\n");
224 }
225 
226 static void
Finish_update(void)227 Finish_update (void)
228 {
229 	int registered;
230 	int done;
231 
232 	Lock_state();
233 	{
234 		registered = MSRegistered;
235 		MSUpdateAgain = false;/* this will happen anyway */
236 	}
237 	Unlock_state();
238 
239 	if (registered)
240 	{
241 		if (HMS_update())
242 		{
243 			Lock_state();
244 			{
245 				time(&MSLastPing);
246 				MSRegistered = true;
247 			}
248 			Unlock_state();
249 
250 			CONS_Printf("Updated master server listing.\n");
251 		}
252 		else
253 			Finish_registration();
254 	}
255 	else
256 		Finish_registration();
257 
258 	Lock_state();
259 	{
260 		done = ! MSUpdateAgain;
261 
262 		if (done)
263 			MSInProgress = false;
264 	}
265 	Unlock_state();
266 
267 	if (! done)
268 		Finish_update();
269 }
270 
271 static void
Finish_unlist(void)272 Finish_unlist (void)
273 {
274 	int registered;
275 
276 	Lock_state();
277 	{
278 		registered = MSRegistered;
279 	}
280 	Unlock_state();
281 
282 	if (registered)
283 	{
284 		CONS_Printf("Removing this server from the master server...\n");
285 
286 		if (HMS_unlist())
287 			CONS_Printf("Server deregistration request successfully sent.\n");
288 
289 		Lock_state();
290 		{
291 			MSRegistered = false;
292 		}
293 		Unlock_state();
294 
295 #ifdef HAVE_THREADS
296 		I_wake_all_cond(&MSCond);
297 #endif
298 	}
299 
300 	Lock_state();
301 	{
302 		if (MSId == MSRegisteredId)
303 			MSId++;
304 	}
305 	Unlock_state();
306 }
307 
308 #ifdef HAVE_THREADS
309 static int *
Server_id(void)310 Server_id (void)
311 {
312 	int *id;
313 	id = malloc(sizeof *id);
314 	Lock_state();
315 	{
316 		*id = MSId;
317 	}
318 	Unlock_state();
319 	return id;
320 }
321 
322 static int *
New_server_id(void)323 New_server_id (void)
324 {
325 	int *id;
326 	id = malloc(sizeof *id);
327 	Lock_state();
328 	{
329 		*id = ++MSId;
330 		I_wake_all_cond(&MSCond);
331 	}
332 	Unlock_state();
333 	return id;
334 }
335 
336 static void
Register_server_thread(int * id)337 Register_server_thread (int *id)
338 {
339 	int same;
340 
341 	Lock_state();
342 	{
343 		/* wait for previous unlist to finish */
344 		while (*id == MSId && MSRegistered)
345 			I_hold_cond(&MSCond, MSMutex);
346 
347 		same = ( *id == MSId );/* it could have been a while */
348 	}
349 	Unlock_state();
350 
351 	if (same)/* it could have been a while */
352 		Finish_registration();
353 
354 	free(id);
355 }
356 
357 static void
Update_server_thread(int * id)358 Update_server_thread (int *id)
359 {
360 	int same;
361 
362 	Lock_state();
363 	{
364 		same = ( *id == MSRegisteredId );
365 	}
366 	Unlock_state();
367 
368 	if (same)
369 		Finish_update();
370 
371 	free(id);
372 }
373 
374 static void
Unlist_server_thread(int * id)375 Unlist_server_thread (int *id)
376 {
377 	int same;
378 
379 	Lock_state();
380 	{
381 		same = ( *id == MSRegisteredId );
382 	}
383 	Unlock_state();
384 
385 	if (same)
386 		Finish_unlist();
387 
388 	free(id);
389 }
390 
391 static void
Change_masterserver_thread(char * api)392 Change_masterserver_thread (char *api)
393 {
394 	Lock_state();
395 	{
396 		while (MSRegistered)
397 			I_hold_cond(&MSCond, MSMutex);
398 	}
399 	Unlock_state();
400 
401 	HMS_set_api(api);
402 }
403 #endif/*HAVE_THREADS*/
404 
RegisterServer(void)405 void RegisterServer(void)
406 {
407 #ifdef MASTERSERVER
408 #ifdef HAVE_THREADS
409 	I_spawn_thread(
410 			"register-server",
411 			(I_thread_fn)Register_server_thread,
412 			New_server_id()
413 	);
414 #else
415 	Finish_registration();
416 #endif
417 #endif/*MASTERSERVER*/
418 }
419 
UpdateServer(void)420 static void UpdateServer(void)
421 {
422 #ifdef HAVE_THREADS
423 	I_spawn_thread(
424 			"update-server",
425 			(I_thread_fn)Update_server_thread,
426 			Server_id()
427 	);
428 #else
429 	Finish_update();
430 #endif
431 }
432 
UnregisterServer(void)433 void UnregisterServer(void)
434 {
435 #ifdef MASTERSERVER
436 #ifdef HAVE_THREADS
437 	I_spawn_thread(
438 			"unlist-server",
439 			(I_thread_fn)Unlist_server_thread,
440 			Server_id()
441 	);
442 #else
443 	Finish_unlist();
444 #endif
445 #endif/*MASTERSERVER*/
446 }
447 
448 static boolean
Online(void)449 Online (void)
450 {
451 	return ( serverrunning && ms_RoomId > 0 );
452 }
453 
SendPingToMasterServer(void)454 static inline void SendPingToMasterServer(void)
455 {
456 	int ready;
457 	time_t now;
458 
459 	if (Online())
460 	{
461 		time(&now);
462 
463 		Lock_state();
464 		{
465 			ready = (
466 					MSRegisteredId == MSId &&
467 					! MSInProgress &&
468 					now >= ( MSLastPing + 60 * cv_masterserver_update_rate.value )
469 			);
470 
471 			if (ready)
472 				MSInProgress = true;
473 		}
474 		Unlock_state();
475 
476 		if (ready)
477 			UpdateServer();
478 	}
479 }
480 
MasterClient_Ticker(void)481 void MasterClient_Ticker(void)
482 {
483 #ifdef MASTERSERVER
484 	SendPingToMasterServer();
485 #endif
486 }
487 
488 static void
Set_api(const char * api)489 Set_api (const char *api)
490 {
491 #ifdef HAVE_THREADS
492 	I_spawn_thread(
493 			"change-masterserver",
494 			(I_thread_fn)Change_masterserver_thread,
495 			strdup(api)
496 	);
497 #else
498 	HMS_set_api(strdup(api));
499 #endif
500 }
501 
502 #endif/*MASTERSERVER*/
503 
504 static void
Update_parameters(void)505 Update_parameters (void)
506 {
507 #ifdef MASTERSERVER
508 	int registered;
509 	int delayed;
510 
511 	if (Online())
512 	{
513 		Lock_state();
514 		{
515 			delayed = MSInProgress;
516 
517 			if (delayed)/* do another update after the current one */
518 				MSUpdateAgain = true;
519 			else
520 				registered = MSRegistered;
521 		}
522 		Unlock_state();
523 
524 		if (! delayed && registered)
525 			UpdateServer();
526 	}
527 #endif/*MASTERSERVER*/
528 }
529 
MasterServer_OnChange(void)530 static void MasterServer_OnChange(void)
531 {
532 #ifdef MASTERSERVER
533 	UnregisterServer();
534 
535 	/*
536 	TODO: remove this for v2, it's just a hack
537 	for those coming in with an old config.
538 	*/
539 	if (
540 			! cv_masterserver.changed &&
541 			strcmp(cv_masterserver.string, "ms.srb2.org:28900") == 0
542 	){
543 		CV_StealthSet(&cv_masterserver, cv_masterserver.defaultvalue);
544 	}
545 
546 	Set_api(cv_masterserver.string);
547 
548 	if (Online())
549 		RegisterServer();
550 #endif/*MASTERSERVER*/
551 }
552