Maelstrom: Ported to SDL3_net

From 259cc038c5b9db6e53c61f05d39680970c504afb Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 28 Nov 2025 17:15:44 -0800
Subject: [PATCH] Ported to SDL3_net

---
 CMakeLists.txt           |  39 +++--
 Data/UI/lobby.xml        |  20 +--
 external/SDL_net         |   2 +-
 game/Maelstrom_Globals.h |   1 -
 game/game.cpp            |   6 +-
 game/gameinfo.cpp        |  16 ++-
 game/init.cpp            |  17 ++-
 game/lobby.cpp           | 299 ++++++++++++++++++++++++++++++---------
 game/lobby.h             |   7 -
 game/main.cpp            |   4 -
 game/netplay.cpp         | 137 ++++++++----------
 game/netplay.h           |   9 +-
 game/packet.h            |  72 ++++++++--
 game/protocol.h          |   5 +
 utils/array.h            |  15 +-
 15 files changed, 438 insertions(+), 211 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index b325d310..39511f66 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -9,17 +9,6 @@ else()
     set(GAME_INSTALLDIR "${CMAKE_INSTALL_PREFIX}/games/${PROJECT_NAME}")
 endif()
 
-if(MSVC)
-    add_compile_options(-W3)
-else()
-    add_compile_options(-Wall)
-    add_compile_options(-Wextra)
-    add_compile_options(-Wno-cast-function-type)
-    add_compile_options(-Wno-sign-compare)
-    add_compile_options(-Wno-unused-function)
-    add_compile_options(-Wno-unused-parameter)
-endif()
-
 # set the output directory for built objects.
 # This makes sure that the dynamic library goes into the build directory automatically.
 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/$<CONFIGURATION>")
@@ -141,9 +130,35 @@ set(MAELSTROM_SOURCES
 )
 
 add_executable(Maelstrom WIN32 ${MAELSTROM_SOURCES})
+
+if(MSVC)
+    target_compile_options(Maelstrom PRIVATE -W3)
+else()
+    target_compile_options(Maelstrom PRIVATE -Wall)
+    target_compile_options(Maelstrom PRIVATE -Wextra)
+    target_compile_options(Maelstrom PRIVATE -Wno-cast-function-type)
+    target_compile_options(Maelstrom PRIVATE -Wno-sign-compare)
+    target_compile_options(Maelstrom PRIVATE -Wno-unused-function)
+    target_compile_options(Maelstrom PRIVATE -Wno-unused-parameter)
+endif()
+
+if(NOT WIN32)
+  check_c_source_compiles("
+      #include <ifaddrs.h>
+      int main() {
+        struct ifaddrs* ifap;
+        getifaddrs(&ifap);
+        return 0;
+      }
+    " HAVE_GETIFADDRS)
+  if(HAVE_GETIFADDRS)
+    target_compile_definitions(Maelstrom PRIVATE HAVE_GETIFADDRS)
+  endif()
+endif()
+
 target_link_libraries(Maelstrom PRIVATE SDLmac)
 target_link_libraries(Maelstrom PRIVATE SDL3::SDL3)
-target_link_libraries(Maelstrom PRIVATE SDL2_net::SDL2_net-static)
+target_link_libraries(Maelstrom PRIVATE SDL3_net::SDL3_net)
 
 install(TARGETS Maelstrom DESTINATION "${GAME_INSTALLDIR}")
 install(DIRECTORY Data DESTINATION "${GAME_INSTALLDIR}")
diff --git a/Data/UI/lobby.xml b/Data/UI/lobby.xml
index 5514b7d2..191dbf90 100644
--- a/Data/UI/lobby.xml
+++ b/Data/UI/lobby.xml
@@ -32,10 +32,10 @@
 						<DialogButton name="join" closeDialog="false" text="Join" default="true">
 							<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT"/>
 						</DialogButton>
-						<DialogLabel name="name" text="This is a name">
+						<DialogLabel name="name">
 							<Anchor anchorFrom="LEFT" anchorTo="RIGHT" anchor="join" x="16"/>
 						</DialogLabel>
-						<DialogLabel name="host" text="bwg101.corp.pacbell.net">
+						<DialogLabel name="host">
 							<Anchor anchorFrom="LEFT" anchorTo="RIGHT" anchor="join" x="120"/>
 						</DialogLabel>
 						<Image name="ping1" image="ping1">
@@ -57,10 +57,10 @@
 						<DialogButton name="join" closeDialog="false" text="Join">
 							<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT"/>
 						</DialogButton>
-						<DialogLabel name="name" text="This is a name">
+						<DialogLabel name="name">
 							<Anchor anchorFrom="LEFT" anchorTo="RIGHT" anchor="join" x="16"/>
 						</DialogLabel>
-						<DialogLabel name="host" text="bwg101.corp.pacbell.net">
+						<DialogLabel name="host">
 							<Anchor anchorFrom="LEFT" anchorTo="RIGHT" anchor="join" x="120"/>
 						</DialogLabel>
 						<Image name="ping1" image="ping1">
@@ -82,10 +82,10 @@
 						<DialogButton name="join" closeDialog="false" text="Join">
 							<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT"/>
 						</DialogButton>
-						<DialogLabel name="name" text="This is a name">
+						<DialogLabel name="name">
 							<Anchor anchorFrom="LEFT" anchorTo="RIGHT" anchor="join" x="16"/>
 						</DialogLabel>
-						<DialogLabel name="host" text="bwg101.corp.pacbell.net">
+						<DialogLabel name="host">
 							<Anchor anchorFrom="LEFT" anchorTo="RIGHT" anchor="join" x="120"/>
 						</DialogLabel>
 						<Image name="ping1" image="ping1">
@@ -107,10 +107,10 @@
 						<DialogButton name="join" closeDialog="false" text="Join">
 							<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT"/>
 						</DialogButton>
-						<DialogLabel name="name" text="This is a name">
+						<DialogLabel name="name">
 							<Anchor anchorFrom="LEFT" anchorTo="RIGHT" anchor="join" x="16"/>
 						</DialogLabel>
-						<DialogLabel name="host" text="bwg101.corp.pacbell.net">
+						<DialogLabel name="host">
 							<Anchor anchorFrom="LEFT" anchorTo="RIGHT" anchor="join" x="120"/>
 						</DialogLabel>
 						<Image name="ping1" image="ping1">
@@ -132,10 +132,10 @@
 						<DialogButton name="join" closeDialog="false" text="Join">
 							<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT"/>
 						</DialogButton>
-						<DialogLabel name="name" text="This is a name">
+						<DialogLabel name="name">
 							<Anchor anchorFrom="LEFT" anchorTo="RIGHT" anchor="join" x="16"/>
 						</DialogLabel>
-						<DialogLabel name="host" text="bwg101.corp.pacbell.net">
+						<DialogLabel name="host">
 							<Anchor anchorFrom="LEFT" anchorTo="RIGHT" anchor="join" x="120"/>
 						</DialogLabel>
 						<Image name="ping1" image="ping1">
diff --git a/external/SDL_net b/external/SDL_net
index 3064cfe8..56a77f31 160000
--- a/external/SDL_net
+++ b/external/SDL_net
@@ -1 +1 @@
-Subproject commit 3064cfe8bb62d27063e4ad313036d0e612ca9033
+Subproject commit 56a77f3137e3ea208c95a0269466a314c3eb8800
diff --git a/game/Maelstrom_Globals.h b/game/Maelstrom_Globals.h
index 1dad8efb..129e53c6 100644
--- a/game/Maelstrom_Globals.h
+++ b/game/Maelstrom_Globals.h
@@ -24,7 +24,6 @@
 #define _Maelstrom_Globals_h
 
 #include <SDL3/SDL.h>
-#include <SDL_net.h>
 
 #include "../screenlib/SDL_FrameBuf.h"
 #include "../screenlib/UIManager.h"
diff --git a/game/game.cpp b/game/game.cpp
index a14db4ce..14acd2a3 100644
--- a/game/game.cpp
+++ b/game/game.cpp
@@ -108,6 +108,8 @@ static bool SetupPlayers(void)
 
 void NewGame(void)
 {
+	InitNetData();
+
 	/* Start the replay */
 	gReplay.HandleNewGame();
 
@@ -1055,9 +1057,11 @@ GamePanelDelegate::NextWave()
 void
 GamePanelDelegate::GameOver()
 {
-	ui->ShowPanel(PANEL_GAMEOVER);
+	CloseSocket();
 
 	QuitPlayerControls();
+
+	ui->ShowPanel(PANEL_GAMEOVER);
 }
 
 /* ----------------------------------------------------------------- */
diff --git a/game/gameinfo.cpp b/game/gameinfo.cpp
index 2e3bc94d..a9e95def 100644
--- a/game/gameinfo.cpp
+++ b/game/gameinfo.cpp
@@ -324,9 +324,9 @@ GameInfo::RemoveNode(Uint32 nodeID)
 	i = 0;
 	while (i < GetNumNodes()) {
 		if (nodeID == nodes[i].nodeID) {
-			SDL_memcpy(&nodes[i], &nodes[i+1],
-					(MAX_NODES-i-1)*sizeof(nodes[i]));
-			SDL_zero(nodes[MAX_NODES-1]);
+			for (int j = i; j < (GetNumNodes() - 1); ++j) {
+				nodes[j] = nodes[j + 1];
+			}
 			--numNodes;
 		} else {
 			++i;
@@ -543,13 +543,15 @@ GameInfo::UpdateUI(GameInfoPlayer *player)
 			player->UI.name->Show();
 			player->UI.name->SetText(player->name);
 			player->UI.host->Show();
-			player->UI.host->SetText(SDLNet_ResolveIP(&node->address));
+			player->UI.host->SetText(NET_GetAddressString(node->address.host));
 		}
 	}
 
-	char name[128];
-	SDL_snprintf(name, sizeof(name), "control%d", player->controlMask);
-	player->UI.control->SetImage(name);
+	if (player->UI.control) {
+		char name[128];
+		SDL_snprintf(name, sizeof(name), "control%d", player->controlMask);
+		player->UI.control->SetImage(name);
+	}
 
 	if (player->UI.desc) {
 		const char *desc = NULL;
diff --git a/game/init.cpp b/game/init.cpp
index 2f32cf9f..05689f03 100644
--- a/game/init.cpp
+++ b/game/init.cpp
@@ -740,6 +740,7 @@ void CleanUp(void)
 		delete prefs;
 		prefs = NULL;
 	}
+	NET_Quit();
 	SDL_Quit();
 }
 
@@ -748,7 +749,16 @@ void CleanUp(void)
 int DoInitializations(Uint32 window_flags)
 {
 	int w, h;
-	SDL_Surface *icon;
+	SDL_Surface* icon;
+
+	if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMEPAD)) {
+		error("Couldn't initialize SDL: %s\n", SDL_GetError());
+		return(-1);
+	}
+	if (!NET_Init()) {
+		error("Couldn't initialize SDL_net: %s\n", SDL_GetError());
+		return(-1);
+	}
 
 	// -- Initialize some variables
 	gLastHigh = -1;
@@ -763,11 +773,6 @@ int DoInitializations(Uint32 window_flags)
 	// -- Load our controls
 	LoadControls();
 
-	if ( !SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO|SDL_INIT_GAMEPAD) ) {
-		error("Couldn't initialize SDL: %s\n", SDL_GetError());
-		return(-1);
-	}
-
 	/* Load the Maelstrom icon */
 	icon = SDL_LoadSurface_IO(OpenRead("icon.png"), true);
 	if ( icon == NULL ) {
diff --git a/game/lobby.cpp b/game/lobby.cpp
index f6e7c23c..5d442f55 100644
--- a/game/lobby.cpp
+++ b/game/lobby.cpp
@@ -20,7 +20,6 @@
     slouken@libsdl.org
 */
 
-#include "SDL_net.h"
 #include "Maelstrom_Globals.h"
 #include "../screenlib/UIElement.h"
 #include "../screenlib/UIElementRadio.h"
@@ -29,6 +28,18 @@
 #include "netplay.h"
 #include "game.h"
 
+#ifdef SDL_PLATFORM_WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <winsock.h>
+#include <iphlpapi.h>
+#endif
+
+#ifdef HAVE_GETIFADDRS
+#include <ifaddrs.h>
+#include <netinet/in.h>
+#endif
+
 
 class SelectControlCallback : public UIClickCallback
 {
@@ -130,16 +141,8 @@ bool
 LobbyDialogDelegate::OnLoad()
 {
 	int i, count;
-	IPaddress addresses[32];
 	char name[32];
 
-	// Get the addresses for this machine
-	count = SDLNet_GetLocalAddresses(addresses, SDL_arraysize(addresses));
-	m_addresses.clear();
-	for (i = 0; i < count; ++i) {
-		m_addresses.add(addresses[i]);
-	}
-
 	m_hostOrJoin = m_dialog->GetElement<UIElementRadioGroup>("hostOrJoin");
 	if (!m_hostOrJoin) {
 		SDL_Log("Warning: Couldn't find radio group 'hostOrJoin'");
@@ -222,10 +225,8 @@ LobbyDialogDelegate::OnHide()
 		NewGame();
 	} else {
 		SetState(STATE_NONE);
+		CloseSocket();
 	}
-
-	// Shut down networking
-	HaltNetData();
 }
 
 void
@@ -252,11 +253,17 @@ LobbyDialogDelegate::OnPoll()
 	}
 
 	// See if there are any packets on the network
-	m_packet.Reset();
-	while (SDLNet_UDP_Recv(gNetFD, &m_packet)) {
-		ProcessPacket(m_packet);
-		m_packet.Reset();
+	NET_Datagram *datagram;
+	DynamicPacket packet;
+	while (NET_ReceiveDatagram(gSocket, &datagram) && datagram) {
+		packet.data = datagram->buf;
+		packet.len = datagram->buflen;
+		packet.address.host = datagram->addr;
+		packet.address.port = datagram->port;
+		ProcessPacket(packet);
+		packet.Reset();
 	}
+	packet.address.host = nullptr;
 
 	// Do this after processing packets in case a pong was pending
 	if (!m_lastPing || (now - m_lastPing) > PING_INTERVAL) {
@@ -268,16 +275,12 @@ LobbyDialogDelegate::OnPoll()
 void
 LobbyDialogDelegate::SetHostOrJoin(void*, int value)
 {
-	// Remove the game before shutting down the network
-	if (m_state == STATE_HOSTING) {
-		RemoveGame();
-	}
-
 	// This is called when the lobby switches from hosting to joining
-	HaltNetData();
+	CloseSocket();
 
 	if (value > 0) {
-		if (InitNetData(value == HOST_GAME) < 0) {
+		bool hosting = (value == HOST_GAME);
+		if (CreateSocket(hosting) < 0) {
 			m_hostOrJoin->SetValue(2);
 			return;
 		}
@@ -346,8 +349,10 @@ LobbyDialogDelegate::UpdateUI()
 	}
 	if (m_state == STATE_HOSTING) {
 		m_playButton->SetDisabled(false);
+		m_deathmatch->SetDisabled(false);
 	} else {
 		m_playButton->SetDisabled(true);
+		m_deathmatch->SetDisabled(true);
 	}
 }
 
@@ -357,9 +362,6 @@ LobbyDialogDelegate::SetState(LOBBY_STATE state)
 	int i;
 
 	// Handle any state transitions here
-	if (m_state == STATE_HOSTING) {
-		RemoveGame();
-	}
 	if (m_state == STATE_HOSTING) {
 		if (m_controlDropdown) {
 			m_controlDropdown->Hide();
@@ -473,17 +475,198 @@ LobbyDialogDelegate::CheckPings()
 
 		for (int i = 0; i < m_game.GetNumNodes(); ++i) {
 			if (m_game.IsNetworkNode(i)) {
-				m_packet.address = m_game.GetNode(i)->address;
-				
-				SDLNet_UDP_Send(gNetFD, -1, &m_packet);
+				IPaddress address = m_game.GetNode(i)->address;
+				NET_SendDatagram(gSocket, address.host, address.port, m_packet.data, m_packet.len);
 			}
 		}
 	}
 }
 
-void
-LobbyDialogDelegate::RemoveGame()
+#ifdef HAVE_GETIFADDRS
+static Uint32 SockAddrToUint32(struct sockaddr* a)
+{
+	return ((a) && (a->sa_family == AF_INET)) ? SDL_Swap32BE(((struct sockaddr_in*)a)->sin_addr.s_addr) : 0;
+}
+#endif
+
+#if defined(SDL_PLATFORM_WIN32) || defined(HAVE_GETIFADDRS)
+// convert a numeric IP address into its string representation
+static void Inet_NtoA(Uint32 addr, char *ipbuf, size_t maxlen)
+{
+	SDL_snprintf(ipbuf, maxlen, "%u.%u.%u.%u", (addr >> 24) & 0xFF, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, (addr >> 0) & 0xFF);
+}
+
+// convert a string representation of an IP address into its numeric equivalent
+static Uint32 Inet_AtoN(const char* buf)
+{
+	// net_server inexplicably doesn't have this function; so I'll just fake it
+	Uint32 ret = 0;
+	int shift = 24;  // fill out the MSB first
+	bool startQuad = true;
+	while ((shift >= 0) && (*buf))
+	{
+		if (startQuad)
+		{
+			unsigned char quad = (unsigned char)atoi(buf);
+			ret |= (((Uint32)quad) << shift);
+			shift -= 8;
+		}
+		startQuad = (*buf == '.');
+		buf++;
+	}
+	return ret;
+}
+#endif // SDL_PLATFORM_WIN32 || HAVE_GETIFADDRS
+
+static bool NET_SendDatagramBroadcast(NET_DatagramSocket *sock, Uint16 port, const void* buf, int buflen)
 {
+#ifdef SDL_PLATFORM_WIN32
+	// Windows XP style implementation
+	HMODULE hiphlpapi = LoadLibraryA("Iphlpapi.dll");
+	typedef DWORD (WINAPI *GetIpAddrTable_t)(PMIB_IPADDRTABLE pIpAddrTable, PULONG pdwSize, BOOL bOrder);
+	GetIpAddrTable_t GetIpAddrTableFunc = (GetIpAddrTable_t)GetProcAddress(hiphlpapi, "GetIpAddrTable");
+	typedef ULONG (WINAPI *GetAdaptersInfo_t)(PIP_ADAPTER_INFO AdapterInfo, PULONG SizePointer);
+	GetAdaptersInfo_t GetAdaptersInfoFunc = (GetAdaptersInfo_t)GetProcAddress(hiphlpapi, "GetAdaptersInfo");
+
+	// Adapted from example code at http://msdn2.microsoft.com/en-us/library/aa365917.aspx
+	// Now get Windows' IPv4 addresses table.  Once again, we gotta call GetIpAddrTable()
+	// multiple times in order to deal with potential race conditions properly.
+	MIB_IPADDRTABLE* ipTable = NULL;
+	{
+		ULONG iptablelen = 0;
+		for (int i = 0; i < 5; i++)
+		{
+			DWORD ipRet = GetIpAddrTableFunc(ipTable, &iptablelen, false);
+			if (ipRet == ERROR_INSUFFICIENT_BUFFER)
+			{
+				free(ipTable);  // in case we had previously allocated it
+				ipTable = (MIB_IPADDRTABLE*)malloc(iptablelen);
+			}
+			else if (ipRet == NO_ERROR) break;
+			else
+			{
+				free(ipTable);
+				ipTable = NULL;
+				break;
+			}
+		}
+	}
+
+	if (ipTable)
+	{
+		// Try to get the Adapters-info table, so we can given useful names to the IP
+		// addresses we are returning.  Gotta call GetAdaptersInfo() up to 5 times to handle
+		// the potential race condition between the size-query call and the get-data call.
+		// I love a well-designed API :^P
+		IP_ADAPTER_INFO* pAdapterInfo = NULL;
+		{
+			ULONG bufLen = 0;
+			for (int i = 0; i < 5; i++)
+			{
+				DWORD apRet = GetAdaptersInfoFunc(pAdapterInfo, &bufLen);
+				if (apRet == ERROR_BUFFER_OVERFLOW)
+				{
+					free(pAdapterInfo);  // in case we had previously allocated it
+					pAdapterInfo = (IP_ADAPTER_INFO*)malloc(bufLen);
+				}
+				else if (apRet == ERROR_SUCCESS) break;
+				else
+				{
+					free(pAdapterInfo);
+					pAdapterInfo = NULL;
+					break;
+				}
+			}
+		}
+
+		for (DWORD i = 0; i < ipTable->dwNumEntries; i++)
+		{
+			const MIB_IPADDRROW& row = ipTable->table[i];
+
+			// Now lookup the appropriate adaptor-name in the pAdaptorInfos, if we can find it
+			const char* name = NULL;
+			const char* desc = NULL;
+			if (pAdapterInfo)
+			{
+				IP_ADAPTER_INFO* next = pAdapterInfo;
+				while ((next) && (name == NULL))
+				{
+					IP_ADDR_STRING* ipAddr = &next->IpAddressList;
+					while (ipAddr)
+					{
+						if (Inet_AtoN(ipAddr->IpAddress.String) == SDL_Swap32BE(row.dwAddr))
+						{
+							name = next->AdapterName;
+							desc = next->Description;
+							break;
+						}
+						ipAddr = ipAddr->Next;
+					}
+					next = next->Next;
+				}
+			}
+			char namebuf[128];
+			if (name == NULL)
+			{
+				SDL_snprintf(namebuf, sizeof(namebuf), "unnamed-%i", i);
+				name = namebuf;
+			}
+
+			Uint32 ipAddr = SDL_Swap32BE(row.dwAddr);
+			Uint32 netmask = SDL_Swap32BE(row.dwMask);
+			Uint32 baddr = ipAddr & netmask;
+			if (row.dwBCastAddr) baddr |= ~netmask;
+
+			char ifaAddrStr[32];  Inet_NtoA(ipAddr, ifaAddrStr, sizeof(ifaAddrStr));
+			char maskAddrStr[32]; Inet_NtoA(netmask, maskAddrStr, sizeof(maskAddrStr));
+			char dstAddrStr[32];  Inet_NtoA(baddr, dstAddrStr, sizeof(dstAddrStr));
+			//SDL_Log("  Found interface:  name=[%s] desc=[%s] address=[%s] netmask=[%s] broadcastAddr=[%s]\n", name, desc ? desc : "unavailable", ifaAddrStr, maskAddrStr, dstAddrStr);
+
+			NET_Address *address = NET_ResolveHostname(dstAddrStr);
+			NET_WaitUntilResolved(address, -1);
+			NET_SendDatagram(sock, address, port, buf, buflen);
+			NET_UnrefAddress(address);
+		}
+
+		free(pAdapterInfo);
+		free(ipTable);
+	}
+	return true;
+#elif defined(HAVE_GETIFADDRS)
+	// BSD-style implementation
+	struct ifaddrs* ifap;
+	if (getifaddrs(&ifap) == 0)
+	{
+		struct ifaddrs* p = ifap;
+		while (p)
+		{
+			Uint32 ifaAddr = SockAddrToUint32(p->ifa_addr);
+			Uint32 maskAddr = SockAddrToUint32(p->ifa_netmask);
+			Uint32 dstAddr = SockAddrToUint32(p->ifa_dstaddr);
+			if (ifaAddr > 0)
+			{
+				char ifaAddrStr[32];  Inet_NtoA(ifaAddr, ifaAddrStr, sizeof(ifaAddrStr));
+				char maskAddrStr[32]; Inet_NtoA(maskAddr, maskAddrStr, sizeof(maskAddrStr));
+				char dstAddrStr[32];  Inet_NtoA(dstAddr, dstAddrStr, sizeof(dstAddrStr));
+				//SDL_Log("  Found interface:  name=[%s] desc=[%s] address=[%s] netmask=[%s] broadcastAddr=[%s]\n", p->ifa_name, "unavailable", ifaAddrStr, maskAddrStr, dstAddrStr);
+
+				NET_Address* address = NET_ResolveHostname(dstAddrStr);
+				NET_WaitUntilResolved(address, -1);
+				NET_SendDatagram(sock, address, port, buf, buflen);
+				NET_UnrefAddress(address);
+			}
+			p = p->ifa_next;
+		}
+		freeifaddrs(ifap);
+	}
+	return true;
+#else
+	NET_Address *address = NET_ResolveHostname("255.255.255.255");
+	NET_WaitUntilResolved(address, -1);
+	bool result = NET_SendDatagram(sock, address, port, buf, buflen);
+	NET_UnrefAddress(address);
+	return result;
+#endif
 }
 
 void
@@ -492,9 +675,8 @@ LobbyDialogDelegate::GetGameList()
 	// 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);
+
+	NET_SendDatagramBroadcast(gSocket, NETPLAY_PORT, m_packet.data, m_packet.len);
 }
 
 void
@@ -502,8 +684,9 @@ LobbyDialogDelegate::GetGameInfo()
 {
 	m_packet.StartLobbyMessage(LOBBY_REQUEST_GAME_INFO);
 	m_packet.Write((Uint32)SDL_GetTicks());
-	m_packet.address = m_game.GetHost()->address;
-	SDLNet_UDP_Send(gNetFD, -1, &m_packet);
+
+	IPaddress address = m_game.GetHost()->address;
+	NET_SendDatagram(gSocket, address.host, address.port, m_packet.data, m_packet.len);
 }
 
 void
@@ -521,9 +704,9 @@ LobbyDialogDelegate::SendJoinRequest()
 	m_packet.Write(m_game.gameID);
 	m_packet.Write(m_game.localID);
 	m_packet.Write(prefs->GetString(PREFERENCES_HANDLE));
-	m_packet.address = m_game.GetHost()->address;
 
-	SDLNet_UDP_Send(gNetFD, -1, &m_packet);
+	IPaddress address = m_game.GetHost()->address;
+	NET_SendDatagram(gSocket, address.host, address.port, m_packet.data, m_packet.len);
 }
 
 void
@@ -532,9 +715,9 @@ LobbyDialogDelegate::SendLeaveRequest()
 	m_packet.StartLobbyMessage(LOBBY_REQUEST_LEAVE);
 	m_packet.Write(m_game.gameID);
 	m_packet.Write(m_game.localID);
-	m_packet.address = m_game.GetHost()->address;
 
-	SDLNet_UDP_Send(gNetFD, -1, &m_packet);
+	IPaddress address = m_game.GetHost()->address;
+	NET_SendDatagram(gSocket, address.host, address.port, m_packet.data, m_packet.len);
 }
 
 void
@@ -550,9 +733,8 @@ LobbyDialogDelegate::SendKick(int index)
 	m_packet.StartLobbyMessage(LOBBY_KICK);
 	m_packet.Write(m_game.gameID);
 	m_packet.Write(node->nodeID);
-	m_packet.address = node->address;
 
-	SDLNet_UDP_Send(gNetFD, -1, &m_packet);
+	NET_SendDatagram(gSocket, node->address.host, node->address.port, m_packet.data, m_packet.len);
 
 	// Now remove them from the game list
 	m_game.RemoveNode(node->nodeID);
@@ -570,26 +752,12 @@ LobbyDialogDelegate::ClearGameList()
 	m_gameList.clear();
 }
 
-void
-LobbyDialogDelegate::PackAddresses(DynamicPacket &packet)
-{
-	Uint16 port;
-
-	port = SDLNet_UDP_GetPeerAddress(gNetFD, -1)->port;
-
-	m_packet.Write((Uint8)m_addresses.length());
-	for (unsigned int i = 0; i < m_addresses.length(); ++i) {
-		m_packet.Write(m_addresses[i].host);
-		m_packet.Write(port);
-	}
-}
-
 void
 LobbyDialogDelegate::ProcessPacket(DynamicPacket &packet)
 {
 	Uint8 cmd;
 
-	if (!m_packet.Read(cmd)) {
+	if (!packet.Read(cmd)) {
 		return;
 	}
 	if (cmd != LOBBY_MSG) {
@@ -598,7 +766,7 @@ LobbyDialogDelegate::ProcessPacket(DynamicPacket &packet)
 		}
 		return;
 	}
-	if (!m_packet.Read(cmd)) {
+	if (!packet.Read(cmd)) {
 		return;
 	}
 
@@ -654,9 +822,8 @@ LobbyDialogDelegate::ProcessPing(DynamicPacket &packet)
 	m_reply.Write(gameID);
 	m_reply.Write(nodeID);
 	m_reply.Write(timestamp);
-	m_reply.address = packet.address;
 
-	SDLNet_UDP_Send(gNetFD, -1, &m_reply);
+	NET_SendDatagram(gSocket, packet.address.host, packet.address.port, m_reply.data, m_reply.len);
 }
 
 void
@@ -711,9 +878,8 @@ LobbyDialogDelegate::ProcessNewGame(DynamicPacket &packet)
 	m_reply.Write((Uint8)NEW_GAME_ACK);
 	m_reply.Write(m_game.gameID);
 	m_reply.Write(m_game.localID);
-	m_reply.address = packet.address;
 
-	SDLNet_UDP_Send(gNetFD, -1, &m_reply);
+	NET_SendDatagram(gSocket, packet.address.host, packet.address.port, m_reply.data, m_reply.len);
 
 	if (m_game.HasNode(packet.address)) {
 		m_playButton->OnClick();
@@ -732,9 +898,8 @@ LobbyDialogDelegate::ProcessRequestGameInfo(DynamicPacket &packet)
 	m_reply.StartLobbyMessage(LOBBY_GAME_INFO);
 	m_reply.Write(timestamp);
 	m_game.WriteToPacket(m_reply);
-	m_reply.address = packet.address;
 
-	SDLNet_UDP_Send(gNetFD, -1, &m_reply);
+	NET_SendDatagram(gSocket, packet.address.host, packet.address.port, m_reply.data, m_reply.len);
 }
 
 void
@@ -766,8 +931,8 @@ LobbyDialogDelegate::ProcessRequestJoin(DynamicPacket &packet)
 	m_game.WriteToPacket(m_reply);
 	for (int i = 0; i < m_game.GetNumNodes(); ++i) {
 		if (m_game.IsNetworkNode(i)) {
-			m_reply.address = m_game.GetNode(i)->address;
-			SDLNet_UDP_Send(gNetFD, -1, &m_reply);
+			IPaddress address = m_game.GetNode(i)->address;
+			NET_SendDatagram(gSocket, address.host, address.port, m_reply.data, m_reply.len);
 		}
 	}
 }
@@ -832,6 +997,8 @@ LobbyDialogDelegate::ProcessGameInfo(DynamicPacket &packet)
 
 		m_game.CopyFrom(game);
 
+		UpdateUI();
+
 		if (m_state == STATE_JOINING) {
 			if (m_game.HasNode(m_game.localID)) {
 				// We successfully joined the game
diff --git a/game/lobby.h b/game/lobby.h
index 6094f3d4..dd0863d8 100644
--- a/game/lobby.h
+++ b/game/lobby.h
@@ -23,7 +23,6 @@
 #ifndef _lobby_h
 #define _lobby_h
 
-#include "SDL_net.h"
 #include "protocol.h"
 #include "packet.h"
 #include "gameinfo.h"
@@ -60,7 +59,6 @@ class LobbyDialogDelegate : public UIDialogDelegate
 	void UpdateUI();
 
 	void CheckPings();
-	void RemoveGame();
 	void GetGameList();
 	void GetGameInfo();
 	void JoinGame(GameInfo &game);
@@ -69,8 +67,6 @@ class LobbyDialogDelegate : public UIDialogDelegate
 	void ClearGameInfo();
 	void ClearGameList();
 
-	void PackAddresses(DynamicPacket &packet);
-
 	void ProcessPacket(DynamicPacket &packet);
 	void ProcessPing(DynamicPacket &packet);
 	void ProcessPong(DynamicPacket &packet);
@@ -82,9 +78,6 @@ class LobbyDialogDelegate : public UIDialogDelegate
 	void ProcessKick(DynamicPacket &packet);
 
 protected:
-	IPaddress m_globalServer;
-	array<IPaddress> m_addresses;
-
 	enum LOBBY_STATE {
 		STATE_NONE,
 		STATE_HOSTING,
diff --git a/game/main.cpp b/game/main.cpp
index a75fe0e2..42deb539 100644
--- a/game/main.cpp
+++ b/game/main.cpp
@@ -67,11 +67,7 @@ Bool	gRunning;
 // Main Menu actions:
 static void RunSinglePlayerGame()
 {
-	if (InitNetData(false) < 0) {
-		return;
-	}
 	NewGame();
-	HaltNetData();
 }
 
 static void RunReplayGame(const char *file)
diff --git a/game/netplay.cpp b/game/netplay.cpp
index ed6cfbb0..0cfe44ff 100644
--- a/game/netplay.cpp
+++ b/game/netplay.cpp
@@ -36,9 +36,8 @@
 #define NETWORK_TIMEOUT	2*FRAME_DELAY_MS
 
 
-UDPsocket gNetFD;
+NET_DatagramSocket *gSocket = nullptr;
 
-static SDLNet_SocketSet SocketSet;
 static Uint32 NextFrame;
 static bool AdvancedFrame;
 
@@ -52,7 +51,6 @@ static int           CurrOut;
 static Uint32 WaitingAcks[MAX_NODES];
 
 /* We cache one packet if the other player is ahead of us */
-static DynamicPacket Packet;
 static struct {
 	Uint32 frame;
 	DynamicPacket packet;
@@ -63,39 +61,41 @@ static DynamicPacket QueuedInput;
 static DynamicPacket FrameInput;
 
 
-int InitNetData(bool hosting)
+int CreateSocket(bool hosting)
 {
-	int i;
 	int port;
 
-	/* Initialize the networking subsystem */
-	if ( SDLNet_Init() < 0 ) {
-		error("Couldn't initialize networking: %s\n", SDLNet_GetError());
-		return(-1);
-	}
-
-	/* Oh heck, create the UDP socket here... */
+	/* Create the UDP socket */
 	if (hosting) {
 		port = NETPLAY_PORT;
 	} else {
 		port = 0;
 	}
-	gNetFD = SDLNet_UDP_Open(port);
-	if ( gNetFD == NULL ) {
-		error("Couldn't create socket bound to port %d: %s\n", port, SDLNet_GetError());
-		return(-1);
-	}
-	SocketSet = SDLNet_AllocSocketSet(1);
-	if ( SocketSet == NULL ) {
-		error("Couldn't create socket watch set\n");
+	gSocket = NET_CreateDatagramSocket(NULL, port);
+	if ( gSocket == NULL ) {
+		error("Couldn't create socket bound to port %d: %s\n", port, SDL_GetError());
 		return(-1);
 	}
-	SDLNet_UDP_AddSocket(SocketSet, gNetFD);
 
 #ifdef DEBUG_PACKETLOSS
-	SDLNet_UDP_SetPacketLoss(gNetFD, DEBUG_PACKETLOSS);
+	SDLNet_UDP_SetPacketLoss(gSocket, DEBUG_PACKETLOSS);
 #endif
 
+	return(0);
+}
+
+void CloseSocket(void)
+{
+	if (gSocket) {
+		NET_DestroyDatagramSocket(gSocket);
+		gSocket = NULL;
+	}
+}
+
+void InitNetData(void)
+{
+	int i;
+
 	/* Initialize network game variables */
 	NextFrame = 1;
 	AdvancedFrame = true;
@@ -104,28 +104,11 @@ int InitNetData(bool hosting)
 	CurrOut = 0;
 	SDL_zero(WaitingAcks);
 	for (i = 0; i < MAX_NODES; ++i) {
-		CachedPacket[i].frame = 0;
+		CachedPacket[i].frame = ~0u;
 		CachedPacket[i].packet.Reset();
 	}
 	QueuedInput.Reset();
 	FrameInput.Reset();
-
-	return(0);
-}
-
-void HaltNetData(void)
-{
-	if (SocketSet) {
-		SDLNet_FreeSocketSet(SocketSet);
-		SocketSet = NULL;
-	}
-
-	if (gNetFD) {
-		SDLNet_UDP_Close(gNetFD);
-		gNetFD = NULL;
-	}
-
-	SDLNet_Quit();
 }
 
 int CheckPlayers(void)
@@ -149,15 +132,6 @@ int CheckPlayers(void)
 		return(-1);
 	}
 
-	/* Bind all of our network nodes to the broadcast channel */
-	for (i = 0; i < gGameInfo.GetNumNodes(); ++i) {
-		if (gGameInfo.IsNetworkNode(i)) {
-			const GameInfoNode *node = gGameInfo.GetNode(i);
-			SDLNet_UDP_Bind(gNetFD, 0, &node->address);
-			SDLNet_UDP_Bind(gNetFD, 1+i, &node->address);
-		}
-	}
-
 	return(0);
 }
 
@@ -243,7 +217,8 @@ static SYNC_RESULT AwaitSync()
 #if DEBUG_NETWORK >= 2
 error("Sending packet for current frame (%ld)\r\n", NextFrame);
 #endif
-			SDLNet_UDP_Send(gNetFD, i+1, &CurrPacket);
+			const GameInfoNode *node = gGameInfo.GetNode(i);
+			NET_SendDatagram(gSocket, node->address.host, node->address.port, CurrPacket.data, CurrPacket.len);
 			WaitingAcks[i] = NodeTimeout(now);
 		}
 	}
@@ -272,7 +247,7 @@ error("Timed out waiting for frame %ld\r\n", NextFrame);
 			}
 		}
 
-		int ready = SDLNet_CheckSockets(SocketSet, NextTimeout(now));
+		int ready = NET_WaitUntilInputAvailable((void**)&gSocket, 1, NextTimeout(now));
 		if (ready < 0) {
 			error("Network error: SDLNet_CheckSockets()\r\n");
 			return SYNC_NETERROR;
@@ -282,15 +257,18 @@ error("Timed out waiting for frame %ld\r\n", NextFrame);
 		}
 
 		/* We are guaranteed that there is data here */
-		Packet.Reset();
-		if ( SDLNet_UDP_Recv(gNetFD, &Packet) <= 0 ) {
-			error("Network error: SDLNet_UDP_Recv()\r\n");
+		NET_Datagram *datagram;
+		DynamicPacket packet;
+		if (!NET_ReceiveDatagram(gSocket, &datagram) || !datagram) {
+			error("Network error: NET_ReceiveDatagram()\r\n");
 			return SYNC_NETERROR;
 		}
 
 		/* We have a packet! */
+		packet.data = datagram->buf;
+		packet.len = datagram->buflen;
 		Uint8 cmd;
-		if (!Packet.Read(cmd)) {
+		if (!packet.Read(cmd)) {
 			error("Received short packet\r\n");
 			continue;
 		}
@@ -304,11 +282,11 @@ error("LOBBY_MSG packet\r\n");
 #if DEBUG_NETWORK >= 2
 error("NEW_GAME packet\r\n");
 #endif
-			Packet.Reset();
-			Packet.Write((Uint8)NEW_GAME_ACK);
-			Packet.Write(gGameInfo.gameID);
-			Packet.Write(gGameInfo.localID);
-			SDLNet_UDP_Send(gNetFD, -1, &Packet);
+			DynamicPacket reply;
+			reply.Write((Uint8)NEW_GAME_ACK);
+			reply.Write(gGameInfo.gameID);
+			reply.Write(gGameInfo.localID);
+			NET_SendDatagram(gSocket, datagram->addr, datagram->port, reply.data, reply.len);
 			continue;
 		}
 		if (cmd == NEW_GAME_ACK) {
@@ -324,7 +302,7 @@ error("NEW_GAME_ACK packet\r\n");
 
 		Uint32 gameID;
 		Uint32 nodeID;
-		if (!Packet.Read(gameID) || !Packet.Read(nodeID)) {
+		if (!packet.Read(gameID) || !packet.Read(nodeID)) {
 			error("Received short packet\r\n");
 			continue;
 		}
@@ -339,7 +317,7 @@ error("NEW_GAME_ACK packet\r\n");
 		}
 
 		/* Check the frame number */
-		if (!Packet.Read(frame)) {
+		if (!packet.Read(frame)) {
 			error("Received short packet\r\n");
 			continue;
 		}
@@ -356,7 +334,7 @@ error("Ignoring duplicate packet for frame %lu from node %d\r\n", frame, index);
 			}
 
 			/* Do a consistency check!! */
-			if (!ProcessSync(index, Packet)) {
+			if (!ProcessSync(index, packet)) {
 				return SYNC_CORRUPT;
 			}
 			WaitingAcks[index] = 0;
@@ -365,8 +343,7 @@ error("Ignoring duplicate packet for frame %lu from node %d\r\n", frame, index);
 #if DEBUG_NETWORK >= 1
 error("Transmitting packet for old frame (%lu)\r\n", frame);
 #endif
-			LastPacket.address = Packet.address;
-			SDLNet_UDP_Send(gNetFD, -1, &LastPacket);
+			NET_SendDatagram(gSocket, datagram->addr, datagram->port, LastPacket.data, LastPacket.len);
 		} else if (frame == (NextFrame+1)) {
 #if DEBUG_NETWORK >= 1
 error("Received packet for next frame! (%lu, current = %lu)\r\n",
@@ -375,12 +352,11 @@ error("Received packet for next frame! (%lu, current = %lu)\r\n",
 			/* Cache this frame for next round */
 			CachedPacket[index].frame = frame;
 			CachedPacket[index].packet.Reset();
-			CachedPacket[index].packet.Write(Packet);
+			CachedPacket[index].packet.Write(packet);
 			CachedPacket[index].packet.Seek(0);
 
 			/* Let the node know we're still waiting */
-			CurrPacket.address = Packet.address;
-			SDLNet_UDP_Send(gNetFD, -1, &CurrPacket);
+			NET_SendDatagram(gSocket, datagram->addr, datagram->port, CurrPacket.data, CurrPacket.len);
 		}
 #if DEBUG_NETWORK >= 1
 else
@@ -478,7 +454,12 @@ int Se

(Patch may be truncated, please check the link at the top of this post.)