Maelstrom: Removed the global lobby server

From 391f63ac31719d908171b894562de3c48906a048 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 27 Nov 2025 14:42:40 -0800
Subject: [PATCH] Removed the global lobby server

We don't want multiplayer games to be reliant on a third party service
---
 Data/UI/lobby.xml  |   4 -
 MaelstromLobby.cpp | 373 ---------------------------------------------
 game/lobby.cpp     | 142 +----------------
 game/lobby.h       |   4 -
 game/protocol.h    |  61 --------
 5 files changed, 3 insertions(+), 581 deletions(-)
 delete mode 100644 MaelstromLobby.cpp

diff --git a/Data/UI/lobby.xml b/Data/UI/lobby.xml
index 05803551..08e97426 100644
--- a/Data/UI/lobby.xml
+++ b/Data/UI/lobby.xml
@@ -19,10 +19,6 @@
 			</Elements>
 		</DialogRadioGroup>
 
-		<DialogCheckbox name="globalGame" bindValue="Network.GlobalGame" text="Global">
-			<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT" x="290" y="39"/>
-		</DialogCheckbox>
-
 		<Area name="gamelist">
 			<Elements>
 				<DialogLabel name="ping_label">
diff --git a/MaelstromLobby.cpp b/MaelstromLobby.cpp
deleted file mode 100644
index 47bfcabd..00000000
--- a/MaelstromLobby.cpp
+++ /dev/null
@@ -1,373 +0,0 @@
-/*
-    Maelstrom: Open Source version of the classic game by Ambrosia Software
-    Copyright (C) 1997-2011  Sam Lantinga
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-    Sam Lantinga
-    slouken@libsdl.org
-*/
-
-#include <stdio.h>
-#include <stdarg.h>
-
-#include "SDL_net.h"
-
-#include "game/packet.h"
-#include "game/protocol.h"
-#include "utils/array.h"
-
-
-// We'll let games stick around for 10 seconds before aging them out
-#define GAME_LIFETIME	10000
-
-static void log(const char *fmt, ...)
-{
-	va_list ap;
-
-	va_start(ap, fmt);
-	vfprintf(stdout, fmt, ap);
-	fflush(stdout);
-}
-
-class AddressList : public array<IPaddress>
-{
-public:
-	AddressList() : array<IPaddress>() { }
-
-	bool ReadFromPacket(DynamicPacket &packet) {
-		Uint8 count;
-		IPaddress address;
-
-		if (!packet.Read(count) || !count) {
-			return false;
-		}
-
-		clear();
-		SDL_zero(address);
-		for (Uint8 i = 0; i < count; ++i) {
-			if (!packet.Read(address.host)) {
-				return false;
-			}
-			if (!packet.Read(address.port)) {
-				return false;
-			}
-			add(address);
-		}
-
-		// Add the address that we saw this packet come from
-		if (!find(packet.address)) {
-			insert(packet.address, 0);
-		}
-
-		return true;
-	}
-
-	void WriteToPacket(DynamicPacket &packet) {
-		packet.Write((Uint8)length());
-		for (int i = 0; i < length(); ++i) {
-			packet.Write(m_data[i].host);
-			packet.Write(m_data[i].port);
-		}
-	}
-
-	bool operator ==(const AddressList &rhs) const {
-		if (length() != rhs.length()) {
-			return false;
-		}
-		for (int i = 0; i < length(); ++i) {
-			if (m_data[i] != rhs.m_data[i]) {
-				return false;
-			}
-		}
-		return true;
-	}
-};
-
-class Game
-{
-public:
-	Game() {
-		m_timestamp = 0;
-		m_list = 0;
-		m_prev = 0;
-		m_next = 0;
-	}
-
-	bool ReadFromPacket(DynamicPacket &packet) {
-		return m_addresses.ReadFromPacket(packet);
-	}
-
-	void WriteToPacket(DynamicPacket &packet) {
-		m_addresses.WriteToPacket(packet);
-	}
-
-	bool operator ==(const Game &rhs) {
-		return m_addresses == rhs.m_addresses;
-	}
-
-	bool TimedOut(Uint32 now) {
-		return (now - m_timestamp) > GAME_LIFETIME;
-	}
-
-	void Refresh() {
-		m_timestamp = SDL_GetTicks();
-	}
-
-	const IPaddress *Address() const {
-		return &m_addresses[0];
-	}
-
-	void Link(Game *&list) {
-		Unlink();
-		if (list) {
-			list->m_prev = this;
-		}
-		m_next = list;
-		list = this;
-		m_list = &list;
-	}
-
-	void Unlink() {
-		if (m_prev) {
-			m_prev->m_next = m_next;
-		} else if (m_list) {
-			*m_list = m_next;
-		}
-		if (m_next) {
-			m_next->m_prev = m_prev;
-		}
-		m_list = 0;
-		m_prev = 0;
-		m_next = 0;
-	}
-
-	Game *Prev() {
-		return m_prev;
-	}
-	Game *Next() {
-		return m_next;
-	}
-
-protected:
-	Uint32 m_timestamp;
-	AddressList m_addresses;
-	Game **m_list;
-	Game *m_prev, *m_next;
-};
-
-class GameList
-{
-public:
-	GameList() {
-		m_sock = NULL;
-		m_list = 0;
-		m_free = 0;
-	}
-
-	~GameList() {
-		Game *game;
-
-		while (m_list) {
-			game = m_list;
-			game->Unlink();
-			delete game;
-		}
-		while (m_free) {
-			game = m_free;
-			game->Unlink();
-			delete game;
-		}
-	}
-
-	void SetSocket(UDPsocket sock) {
-		m_sock = sock;
-	}
-
-	void ProcessList() {
-		Game *game, *next;
-		Uint32 now;
-
-		now = SDL_GetTicks();
-		game = m_list;
-		while (game) {
-			next = game->Next();
-			if (game->TimedOut(now)) {
-				log("Expiring game from %s:%d\n",
-					SDLNet_ResolveIP(game->Address()),
-					SDL_SwapBE16(game->Address()->port));
-				game->Link(m_free);
-			}
-			game = next;
-		}
-	}
-
-	void ProcessPacket(DynamicPacket &packet) {
-		Uint8 cmd;
-
-		if (!packet.Read(cmd)) {
-			return;
-		}
-		if (cmd != LOBBY_MSG) {
-			return;
-		}
-		if (!packet.Read(cmd)) {
-			return;
-		}
-
-		switch (cmd) {
-			case LOBBY_ANNOUNCE_GAME:
-				ProcessAnnounceGame(packet);
-				break;
-			case LOBBY_REMOVE_GAME:
-				ProcessRemoveGame(packet);
-				break;
-			case LOBBY_REQUEST_GAME_SERVERS:
-				ProcessRequestGames(packet);
-				break;
-		}
-	}
-
-	void ProcessAnnounceGame(DynamicPacket &packet) {
-		Game *newGame;
-
-		if (m_free) {
-			newGame = m_free;
-		} else {
-			newGame = new Game;
-		}
-		if (!newGame->ReadFromPacket(packet)) {
-			log("Invalid game from %s:%d\n",
-				SDLNet_ResolveIP(&packet.address),
-				SDL_SwapBE16(packet.address.port));
-
-			newGame->Link(m_free);
-			return;
-		}
-
-		for (Game *game = m_list; game; game = game->Next()) {
-			if (*game == *newGame) {
-				//log("Refreshing game from %s:%d\n",
-				//	SDLNet_ResolveIP(&packet.address),
-				//	SDL_SwapBE16(packet.address.port));
-
-				game->Refresh();
-				newGame->Link(m_free);
-				return;
-			}
-		}
-
-		log("Adding game from %s:%d\n",
-			SDLNet_ResolveIP(&packet.address),
-			SDL_SwapBE16(packet.address.port));
-
-		newGame->Refresh();
-		newGame->Link(m_list);
-	}
-
-	void ProcessRemoveGame(DynamicPacket &packet) {
-		Game *newGame;
-
-		if (m_free) {
-			newGame = m_free;
-		} else {
-			newGame = new Game;
-		}
-		if (!newGame->ReadFromPacket(packet)) {
-			newGame->Link(m_free);
-			return;
-		}
-
-		for (Game *game = m_list; game; game = game->Next()) {
-			if (*game == *newGame) {
-				log("Removing game from %s:%d\n",
-					SDLNet_ResolveIP(&packet.address),
-					SDL_SwapBE16(packet.address.port));
-				game->Link(m_free);
-				break;
-			}
-		}
-		newGame->Link(m_free);
-	}
-
-	void ProcessRequestGames(DynamicPacket &packet) {
-		AddressList addresses;
-		Game *game;
-
-		if (!addresses.ReadFromPacket(packet)) {
-			return;
-		}
-
-		// Send back a list of all games
-		int mark;
-		m_reply.StartLobbyMessage(LOBBY_GAME_SERVERS);
-		mark = m_reply.Tell(); 
-		m_reply.Write((Uint8)0);
-		int count;
-		for (game = m_list; game; game = game->Next()) {
-			game->WriteToPacket(m_reply);
-			++count;
-			if (count == 255) {
-				// That's it, I'm cutting you off...
-				break;
-			}
-		}
-		m_reply.Seek(mark);
-		m_reply.Write((Uint8)count);
-		m_reply.address = packet.address;
-		SDLNet_UDP_Send(m_sock, -1, &m_reply);
-
-		// Send the requesting player to all game servers
-		m_reply.StartLobbyMessage(LOBBY_ANNOUNCE_PLAYER);
-		addresses.WriteToPacket(m_reply);
-		for (game = m_list; game; game = game->Next()) {
-			m_reply.address = *game->Address();
-			SDLNet_UDP_Send(m_sock, -1, &m_reply);
-		}
-	}
-
-protected:
-	UDPsocket m_sock;
-	Game *m_list;
-	Game *m_free;
-	DynamicPacket m_reply;
-};
-
-int main(int argc, char *argv[])
-{
-	UDPsocket sock;
-	DynamicPacket packet;
-	GameList games;
-
-	sock = SDLNet_UDP_Open(LOBBY_PORT);
-	if (!sock) {
-		fprintf(stderr, "Couldn't create socket on port %d: %s\n",
-			LOBBY_PORT, SDL_GetError());
-		exit(1);
-	}
-	games.SetSocket(sock);
-
-	for ( ; ; ) {
-		games.ProcessList();
-
-		while (SDLNet_UDP_Recv(sock, &packet)) {
-			games.ProcessPacket(packet);
-			packet.Reset();
-		}
-
-		SDL_Delay(100);
-	}
-	SDLNet_UDP_Close(sock);
-}
diff --git a/game/lobby.cpp b/game/lobby.cpp
index 6ecc49fb..97dcf1b4 100644
--- a/game/lobby.cpp
+++ b/game/lobby.cpp
@@ -30,13 +30,6 @@
 #include "netplay.h"
 #include "game.h"
 
-// Update the game list every 3 seconds
-#define GLOBAL_SERVER_HOST	"obelix.dreamhost.com"
-//#define GLOBAL_SERVER_HOST	"localhost"
-
-// Define this if you want local broadcast in addition to the global server
-#define LOBBY_BROADCAST
-
 
 class SelectControlCallback : public UIClickCallback
 {
@@ -151,11 +144,6 @@ LobbyDialogDelegate::OnLoad()
 	IPaddress addresses[32];
 	char name[32];
 
-	// Get the address of the global server
-	if (SDLNet_ResolveHost(&m_globalServer, GLOBAL_SERVER_HOST, LOBBY_PORT) < 0) {
-		fprintf(stderr, "Warning: Couldn't resolve global server host %s\n", GLOBAL_SERVER_HOST);
-	}
-
 	// Get the addresses for this machine
 	count = SDLNet_GetLocalAddresses(addresses, SDL_arraysize(addresses));
 	m_addresses.clear();
@@ -170,17 +158,6 @@ LobbyDialogDelegate::OnLoad()
 	}
 	m_hostOrJoin->SetValueCallback(this, &LobbyDialogDelegate::SetHostOrJoin);
 
-	m_globalGame = m_dialog->GetElement<UIElementCheckbox>("globalGame");
-	if (!m_globalGame) {
-		fprintf(stderr, "Warning: Couldn't find checkbox 'globalGame'\n");
-		return false;
-	}
-	if (m_globalServer.host == INADDR_NONE) {
-		m_globalGame->SetChecked(false);
-		m_globalGame->SetDisabled(true);
-	}
-	m_globalGame->SetClickCallback(this, &LobbyDialogDelegate::GlobalGameChanged);
-
 	m_deathmatch = m_dialog->GetElement<UIElementRadioGroup>("deathmatch");
 	if (!m_deathmatch) {
 		fprintf(stderr, "Warning: Couldn't find radio group 'deathmatch'\n");
@@ -273,7 +250,7 @@ LobbyDialogDelegate::OnPoll()
 	if (!m_lastRefresh ||
 	    (now - m_lastRefresh) > PING_INTERVAL) {
 		if (m_state == STATE_HOSTING) {
-			AdvertiseGame();
+			// Nothing to do
 		} else if (m_state == STATE_LISTING) {
 			GetGameList();
 		} else if (m_state == STATE_JOINING) {
@@ -302,7 +279,7 @@ void
 LobbyDialogDelegate::SetHostOrJoin(void*, int value)
 {
 	// Remove the game before shutting down the network
-	if (m_state == STATE_HOSTING && m_globalGame->IsChecked()) {
+	if (m_state == STATE_HOSTING) {
 		RemoveGame();
 	}
 
@@ -331,20 +308,6 @@ LobbyDialogDelegate::SetHostOrJoin(void*, int value)
 	}
 }
 
-void
-LobbyDialogDelegate::GlobalGameChanged(void*)
-{
-	m_lastRefresh = 0;
-
-	if (!m_globalGame->IsChecked()) {
-		if (m_state == STATE_HOSTING) {
-			RemoveGame();
-		} else {
-			ClearGameList();
-		}
-	}
-}
-
 void
 LobbyDialogDelegate::SetDeathmatch(void*, int value)
 {
@@ -408,7 +371,7 @@ LobbyDialogDelegate::SetState(LOBBY_STATE state)
 	int i;
 
 	// Handle any state transitions here
-	if (m_state == STATE_HOSTING && m_globalGame->IsChecked()) {
+	if (m_state == STATE_HOSTING) {
 		RemoveGame();
 	}
 	if (m_state == STATE_HOSTING) {
@@ -532,47 +495,20 @@ LobbyDialogDelegate::CheckPings()
 	}
 }
 
-void
-LobbyDialogDelegate::AdvertiseGame()
-{
-	if (m_globalGame->IsChecked()) {
-		m_packet.StartLobbyMessage(LOBBY_ANNOUNCE_GAME);
-		PackAddresses(m_packet);
-		m_packet.address = m_globalServer;
-
-		SDLNet_UDP_Send(gNetFD, -1, &m_packet);
-	}
-}
-
 void
 LobbyDialogDelegate::RemoveGame()
 {
-	m_packet.StartLobbyMessage(LOBBY_REMOVE_GAME);
-	PackAddresses(m_packet);
-	m_packet.address = m_globalServer;
-
-	SDLNet_UDP_Send(gNetFD, -1, &m_packet);
 }
 
 void
 LobbyDialogDelegate::GetGameList()
 {
-	if (m_globalGame->IsChecked()) {
-		m_packet.StartLobbyMessage(LOBBY_REQUEST_GAME_SERVERS);
-		PackAddresses(m_packet);
-		m_packet.address = m_globalServer;
-
-		SDLNet_UDP_Send(gNetFD, -1, &m_packet);
-	}
-
-#ifdef LOBBY_BROADCAST
 	// Get game info for local games
 	m_packet.StartLobbyMessage(LOBBY_REQUEST_GAME_INFO);
 	m_packet.Write((Uint32)SDL_GetTicks());
 	m_packet.address.host = INADDR_BROADCAST;
 	m_packet.address.port = SDL_Swap16BE(NETPLAY_PORT);
 	SDLNet_UDP_Send(gNetFD, -1, &m_packet);
-#endif
 }
 
 void
@@ -681,17 +617,6 @@ LobbyDialogDelegate::ProcessPacket(DynamicPacket &packet)
 	}
 
 	if (m_state == STATE_HOSTING) {
-		if (cmd == LOBBY_ANNOUNCE_PLAYER) {
-			if (m_globalGame->IsChecked()) {
-				ProcessAnnouncePlayer(packet);
-			}
-			return;
-		}
-
-		if (m_game.IsFull() && !m_game.HasNode(packet.address)) {
-			return;
-		}
-
 		if (cmd == LOBBY_PING) {
 			ProcessPing(packet);
 		} else if (cmd == LOBBY_PONG) {
@@ -707,15 +632,6 @@ LobbyDialogDelegate::ProcessPacket(DynamicPacket &packet)
 
 	}
 
-	if (m_state == STATE_LISTING) {
-		if (cmd == LOBBY_GAME_SERVERS) {
-			if (m_globalGame->IsChecked()) {
-				ProcessGameServerList(packet);
-			}
-			return;
-		}
-	}
-
 	// These packets we handle in all the join states
 	if (cmd == LOBBY_PING) {
 		ProcessPing(packet);
@@ -818,29 +734,6 @@ LobbyDialogDelegate::ProcessNewGame(DynamicPacket &packet)
 	}
 }
 
-void
-LobbyDialogDelegate::ProcessAnnouncePlayer(DynamicPacket &packet)
-{
-	Uint8 count;
-	IPaddress address;
-
-	// Open the firewall so this player can contact us.
-	m_reply.StartLobbyMessage(LOBBY_OPEN_FIREWALL);
-
-	if (!packet.Read(count)) {
-		return;
-	}
-	for (Uint8 i = 0; i < count; ++i) {
-		if (!packet.Read(address.host) ||
-		    !packet.Read(address.port)) {
-			return;
-		}
-		m_reply.address = address;
-		
-		SDLNet_UDP_Send(gNetFD, -1, &m_reply);
-	}
-}
-
 void
 LobbyDialogDelegate::ProcessRequestGameInfo(DynamicPacket &packet)
 {
@@ -985,32 +878,3 @@ LobbyDialogDelegate::ProcessKick(DynamicPacket &packet)
 
 	SetState(STATE_LISTING);
 }
-
-void
-LobbyDialogDelegate::ProcessGameServerList(DynamicPacket &packet)
-{
-	Uint8 serverCount, count;
-	IPaddress address;
-
-	// Request game information from the servers
-	m_reply.StartLobbyMessage(LOBBY_REQUEST_GAME_INFO);
-	m_reply.Write((Uint32)SDL_GetTicks());
-
-	if (!packet.Read(serverCount)) {
-		return;
-	}
-	for (Uint8 i = 0; i < serverCount; ++i) {
-		if (!packet.Read(count)) {
-			return;
-		}
-		for (Uint8 j = 0; j < count; ++j) {
-			if (!packet.Read(address.host) ||
-			    !packet.Read(address.port)) {
-				return;
-			}
-			m_reply.address = address;
-			
-			SDLNet_UDP_Send(gNetFD, -1, &m_reply);
-		}
-	}
-}
diff --git a/game/lobby.h b/game/lobby.h
index 434259b6..bc0adaa7 100644
--- a/game/lobby.h
+++ b/game/lobby.h
@@ -61,7 +61,6 @@ class LobbyDialogDelegate : public UIDialogDelegate
 	void UpdateUI();
 
 	void CheckPings();
-	void AdvertiseGame();
 	void RemoveGame();
 	void GetGameList();
 	void GetGameInfo();
@@ -77,11 +76,9 @@ class LobbyDialogDelegate : public UIDialogDelegate
 	void ProcessPing(DynamicPacket &packet);
 	void ProcessPong(DynamicPacket &packet);
 	void ProcessNewGame(DynamicPacket &packet);
-	void ProcessAnnouncePlayer(DynamicPacket &packet);
 	void ProcessRequestGameInfo(DynamicPacket &packet);
 	void ProcessRequestJoin(DynamicPacket &packet);
 	void ProcessRequestLeave(DynamicPacket &packet);
-	void ProcessGameServerList(DynamicPacket &packet);
 	void ProcessGameInfo(DynamicPacket &packet);
 	void ProcessKick(DynamicPacket &packet);
 
@@ -108,7 +105,6 @@ class LobbyDialogDelegate : public UIDialogDelegate
 	DynamicPacket m_packet, m_reply;
 
 	UIElementRadioGroup *m_hostOrJoin;
-	UIElementCheckbox *m_globalGame;
 	UIElementRadioGroup *m_deathmatch;
 	UIElement *m_gameListArea;
 	UIElement *m_gameListElements[5];
diff --git a/game/protocol.h b/game/protocol.h
index 58e685fe..f7de2c1e 100644
--- a/game/protocol.h
+++ b/game/protocol.h
@@ -37,67 +37,6 @@
 enum LobbyProtocol {
 	LOBBY_NONE,
 
-	/**********/
-	/* Messages between the hosting game and the lobby server */
-
-	LOBBY_ANNOUNCE_GAME = 1,
-	/* Sent by the hosting game to the lobby server
-	   This is sent periodically to keep the entry refreshed, since
-	   the server will automatically age out entries after 30 seconds.
-
-		Uint8 numaddresses
-		{
-			Uint32 host;
-			Uint16 port;
-		} addresses[]
-	 */
-
-	LOBBY_REMOVE_GAME,
-	/* Sent by the hosting game to the lobby server
-	   This is sent when the game is no longer available to join.
-
-		Uint8 numaddresses
-		{
-			Uint32 host;
-			Uint16 port;
-		} addresses[]
-	 */
-
-	LOBBY_ANNOUNCE_PLAYER,
-	/* Sent by the lobby server when a player requests the game list.
-	   This allows the hosting game to send a packet to the player
-	   requesting to join, opening the firewall for them.
-
-		Uint8 numaddresses
-		{
-			Uint32 host;
-			Uint16 port;
-		} addresses[]
-	*/
-
-	/**********/
-	/* Messages between the joining game and the lobby server */
-
-	LOBBY_REQUEST_GAME_SERVERS = 10,
-	/* Sent by the joining game, containing a list of it's addresses
-
-		Uint8 numaddresses
-		{
-			Uint32 host;
-			Uint16 port;
-		} addresses[]
-	 */
-
-	LOBBY_GAME_SERVERS,
-	/* Sent by the lobby server containing all the current game addresses
-
-		Uint8 numaddresses
-		{
-			Uint32 host;
-			Uint16 port;
-		} addresses[]
-	*/
-
 	/**********/
 	/* Messages between the joining game and the hosting game */