diff --git a/3rdParty/asio/CMakeLists.txt b/3rdParty/asio/CMakeLists.txt
deleted file mode 100644
index f71e282f5bd..00000000000
--- a/3rdParty/asio/CMakeLists.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-include(FetchContent_MakeAvailableExcludeFromAll)
-
-include(FetchContent)
-FetchContent_Declare(asio
-    URL https://github.com/diasurgical/asio/archive/b0239654fc40a21663430d926a50bbd6c22bb2e9.zip
-    URL_HASH MD5=005bab46794975df4c7bbc3f33808f0b
-)
-FetchContent_MakeAvailableExcludeFromAll(asio)
-
-add_library(asio INTERFACE)
-target_include_directories(asio INTERFACE ${asio_SOURCE_DIR}/asio/include)
diff --git a/3rdParty/libsodium/CMakeLists.txt b/3rdParty/libsodium/CMakeLists.txt
deleted file mode 100644
index 240f9cbc783..00000000000
--- a/3rdParty/libsodium/CMakeLists.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-if(NOT DEVILUTIONX_SYSTEM_LIBSODIUM)
-  include(FetchContent_MakeAvailableExcludeFromAll)
-
-  set(SODIUM_MINIMAL ON)
-  set(SODIUM_ENABLE_BLOCKING_RANDOM OFF)
-  set(SODIUM_DISABLE_TESTS ON)
-
-  if(DEVILUTIONX_STATIC_LIBSODIUM)
-    set(BUILD_SHARED_LIBS OFF)
-  else()
-    set(BUILD_SHARED_LIBS ON)
-  endif()
-  include(FetchContent)
-  FetchContent_Declare(sodium
-      GIT_REPOSITORY https://github.com/robinlinden/libsodium-cmake.git
-      GIT_TAG a8ac4509b22b84d6c2eb7d7448f08678e4a67da6
-  )
-  FetchContent_MakeAvailableExcludeFromAll(sodium)
-endif()
diff --git a/3rdParty/libzt/CMakeLists.txt b/3rdParty/libzt/CMakeLists.txt
deleted file mode 100644
index 11ef0fd751f..00000000000
--- a/3rdParty/libzt/CMakeLists.txt
+++ /dev/null
@@ -1,55 +0,0 @@
-include(FetchContent_MakeAvailableExcludeFromAll)
-
-include(FetchContent)
-FetchContent_Declare(libzt
-  GIT_REPOSITORY https://github.com/diasurgical/libzt.git
-  GIT_TAG 2607962e3b2c1def68479f7dc382c7508c367365)
-FetchContent_MakeAvailableExcludeFromAll(libzt)
-
-# External library, ignore all warnings
-target_compile_options(zto_obj PRIVATE -w)
-target_compile_options(libnatpmp_obj PRIVATE -w)
-target_compile_options(libzt_obj PRIVATE -w)
-target_compile_options(lwip_obj PRIVATE -w)
-target_compile_options(miniupnpc_obj PRIVATE -w)
-target_compile_options(zt-static PRIVATE -w)
-
-if(NOT CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
-  target_compile_options(zto_obj PRIVATE -fpermissive)
-  target_compile_options(libzt_obj PRIVATE -fpermissive)
-  target_compile_options(zt-static PRIVATE -fpermissive)
-endif()
-
-target_include_directories(zt-static INTERFACE
-  "${libzt_SOURCE_DIR}/include"
-  "${libzt_SOURCE_DIR}/src"
-  "${libzt_SOURCE_DIR}/ext/lwip/src/include")
-
-if(WIN32)
-  target_include_directories(zt-static INTERFACE
-    "${libzt_SOURCE_DIR}/ext/lwip-contrib/ports/win32/include")
-else()
-  target_include_directories(zt-static INTERFACE
-    "${libzt_SOURCE_DIR}/ext/lwip-contrib/ports/unix/port/include")
-endif()
-
-if(MINGW_CROSS)
-  option(MINGW_STDTHREADS_GENERATE_STDHEADERS "" ON)
-
-  FetchContent_Declare(mingw-std-threads
-    GIT_REPOSITORY https://github.com/meganz/mingw-std-threads
-    GIT_TAG bee085c0a6cb32c59f0b55c7bba976fe6dcfca7f)
-  FetchContent_MakeAvailableExcludeFromAll(mingw-std-threads)
-
-  target_compile_definitions(libnatpmp_obj PRIVATE -D_WIN32_WINNT=0x601 -DSTATICLIB)
-  target_compile_definitions(zto_obj PRIVATE -D_WIN32_WINNT=0x601 -DZT_SALSA20_SSE=0)
-  target_compile_definitions(libzt_obj PRIVATE -D_WIN32_WINNT=0x601)
-  target_link_libraries(libzt_obj PRIVATE mingw_stdthreads)
-  target_link_libraries(zt-static PUBLIC iphlpapi shlwapi wsock32 ws2_32 wininet mingw_stdthreads)
-  target_include_directories(zt-static INTERFACE "${libzt_SOURCE_DIR}/include/mingw-fixes")
-endif()
-
-if(MSVC)
-  target_compile_definitions(libnatpmp_obj PRIVATE -DSTATICLIB)
-  target_link_libraries(zt-static PUBLIC iphlpapi shlwapi wsock32 ws2_32 wininet)
-endif()
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7fcfe9a5acf..91f2543ebcc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -25,14 +25,8 @@ option(PIE "Generate position-independent code" OFF)
 option(DIST "Dynamically link only glibc and SDL2" OFF)
 option(BINARY_RELEASE "Enable options for binary release" OFF)
 option(NIGHTLY_BUILD "Enable options for nightly build" OFF)
-option(NONET "Disable network support" OFF)
 option(NOSOUND "Disable sound support" OFF)
 
-if(NOT NONET)
-  option(DISABLE_TCP "Disable TCP multiplayer option" OFF)
-  option(DISABLE_ZERO_TIER "Disable ZeroTier multiplayer option" OFF)
-endif()
-
 option(DISABLE_STREAMING_MUSIC "Disable streaming music (to work around broken platform implementations)" OFF)
 mark_as_advanced(DISABLE_STREAMING_MUSIC)
 option(DISABLE_STREAMING_SOUNDS "Disable streaming sounds (to work around broken platform implementations)" OFF)
@@ -63,12 +57,6 @@ if(NOT NOSOUND)
                         "DEVILUTIONX_SYSTEM_SDL_AUDIOLIB AND NOT DIST" ON)
 endif()
 
-if(NOT NONET)
-  option(DEVILUTIONX_SYSTEM_LIBSODIUM "Use system-provided libsodium" ON)
-  cmake_dependent_option(DEVILUTIONX_STATIC_LIBSODIUM "Link static libsodium" OFF
-                        "DEVILUTIONX_SYSTEM_LIBSODIUM AND NOT DIST" ON)
-endif()
-
 option(DEVILUTIONX_SYSTEM_LIBFMT "Use system-provided libfmt" ON)
 cmake_dependent_option(DEVILUTIONX_STATIC_LIBFMT "Link static libfmt" OFF
                        "DEVILUTIONX_SYSTEM_LIBFMT AND NOT DIST" ON)
@@ -163,15 +151,6 @@ set(THREADS_PREFER_PTHREAD_FLAG ON)
 
 find_package(Threads REQUIRED)
 
-if(NOT NONET)
-  if(DEVILUTIONX_SYSTEM_LIBSODIUM)
-    set(sodium_USE_STATIC_LIBS ${DEVILUTIONX_STATIC_LIBSODIUM})
-    find_package(sodium REQUIRED)
-  else()
-    add_subdirectory(3rdParty/libsodium)
-  endif()
-endif()
-
 if(DEVILUTIONX_SYSTEM_LIBFMT)
   find_package(fmt 7.0.0 QUIET)
   if(fmt_FOUND)
@@ -219,10 +198,6 @@ else()
 endif()
 find_package(SDL2_ttf REQUIRED)
 
-if(NOT NONET AND NOT DISABLE_TCP)
-  add_subdirectory(3rdParty/asio)
-endif()
-
 add_subdirectory(3rdParty/SDL_image)
 
 if(NOT NOSOUND)
@@ -384,10 +359,10 @@ set(libdevilutionx_SRCS
   Source/panels/mainpanel.cpp
   Source/dvlnet/abstract_net.cpp
   Source/dvlnet/base.cpp
-  Source/dvlnet/cdwrap.cpp
+  Source/dvlnet/cdwrap.cpp  
   Source/dvlnet/frame_queue.cpp
   Source/dvlnet/loopback.cpp
-  Source/dvlnet/packet.cpp
+  Source/dvlnet/packet.cpp  
   Source/storm/storm.cpp
   
   Source/storm/storm_net.cpp
@@ -407,20 +382,6 @@ else()
     Source/utils/soundsample.cpp)
 endif()
 
-if(NOT NONET)
-  if(NOT DISABLE_TCP)
-    list(APPEND libdevilutionx_SRCS
-      Source/dvlnet/tcp_client.cpp
-      Source/dvlnet/tcp_server.cpp)
-  endif()
-  if(NOT DISABLE_ZERO_TIER)
-    list(APPEND libdevilutionx_SRCS
-      Source/dvlnet/protocol_zt.cpp
-      Source/dvlnet/zerotier_native.cpp
-      Source/dvlnet/zerotier_lwip.cpp)
-  endif()
-endif()
-
 set(BIN_TARGET devilutionx)
 
 add_library(libdevilutionx OBJECT ${libdevilutionx_SRCS})
@@ -475,31 +436,17 @@ if(WIN32)
   target_link_libraries(libdevilutionx PUBLIC find_steam_game)
 endif()
 
-if(NOT NONET)
-  if(NOT DISABLE_TCP)
-    target_link_libraries(libdevilutionx PUBLIC asio)
-  endif()
-  target_link_libraries(libdevilutionx PUBLIC sodium)
-endif()
-
 target_link_libraries(libdevilutionx PUBLIC fmt::fmt)
 target_link_libraries(libdevilutionx PUBLIC PNG::PNG)
 
 genex_for_option(DEBUG)
 target_compile_definitions(libdevilutionx PUBLIC "$<${DEBUG_GENEX}:_DEBUG>")
 
-if(NOT NONET AND NOT DISABLE_TCP)
-  target_compile_definitions(libdevilutionx PUBLIC ASIO_STANDALONE)
-endif()
-
 # Defines without value
 foreach(
   def_name
   NOSOUND
-  NONET
   PREFILL_PLAYER_NAME
-  DISABLE_TCP
-  DISABLE_ZERO_TIER
   DISABLE_STREAMING_MUSIC
   DISABLE_STREAMING_SOUNDS
   GPERF
@@ -760,8 +707,3 @@ if(CPACK)
   set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
   include(CPack)
 endif()
-
-if(NOT NONET AND NOT DISABLE_ZERO_TIER)
-  add_subdirectory(3rdParty/libzt)
-  target_link_libraries(libdevilutionx PUBLIC zt-static)
-endif()
diff --git a/Source/DiabloUI/selconn.cpp b/Source/DiabloUI/selconn.cpp
index e4df2f6d310..66b65be626f 100644
--- a/Source/DiabloUI/selconn.cpp
+++ b/Source/DiabloUI/selconn.cpp
@@ -29,14 +29,6 @@ void SelconnLoad()
 {
 	LoadBackgroundArt("ui_art\\selconn.pcx");
 
-#ifndef NONET
-#ifndef DISABLE_ZERO_TIER
-	vecConnItems.push_back(std::make_unique<UiListItem>("Zerotier", SELCONN_ZT));
-#endif
-#ifndef DISABLE_TCP
-	vecConnItems.push_back(std::make_unique<UiListItem>("Client-Server (TCP)", SELCONN_TCP));
-#endif
-#endif
 	vecConnItems.push_back(std::make_unique<UiListItem>("Loopback", SELCONN_LOOPBACK));
 
 	UiAddBackground(&vecSelConnDlg);
diff --git a/Source/dvlnet/abstract_net.cpp b/Source/dvlnet/abstract_net.cpp
index a68b1aeed9d..874591f3b8d 100644
--- a/Source/dvlnet/abstract_net.cpp
+++ b/Source/dvlnet/abstract_net.cpp
@@ -1,16 +1,6 @@
 #include "dvlnet/abstract_net.h"
 
 #include "utils/stubs.h"
-#ifndef NONET
-#include "dvlnet/base_protocol.h"
-#include "dvlnet/cdwrap.h"
-#ifndef DISABLE_ZERO_TIER
-#include "dvlnet/protocol_zt.h"
-#endif
-#ifndef DISABLE_TCP
-#include "dvlnet/tcp_client.h"
-#endif
-#endif
 #include "dvlnet/loopback.h"
 #include "storm/storm.h"
 
@@ -19,24 +9,7 @@ namespace net {
 
 std::unique_ptr<abstract_net> abstract_net::MakeNet(provider_t provider)
 {
-#ifdef NONET
 	return std::make_unique<loopback>();
-#else
-	switch (provider) {
-#ifndef DISABLE_TCP
-	case SELCONN_TCP:
-		return std::make_unique<cdwrap<tcp_client>>();
-#endif
-#ifndef DISABLE_ZERO_TIER
-	case SELCONN_ZT:
-		return std::make_unique<cdwrap<base_protocol<protocol_zt>>>();
-#endif
-	case SELCONN_LOOPBACK:
-		return std::make_unique<loopback>();
-	default:
-		ABORT();
-	}
-#endif
 }
 
 } // namespace net
diff --git a/Source/dvlnet/packet.cpp b/Source/dvlnet/packet.cpp
index 5cd894d3342..c57cfce6b79 100644
--- a/Source/dvlnet/packet.cpp
+++ b/Source/dvlnet/packet.cpp
@@ -3,10 +3,6 @@
 namespace devilution {
 namespace net {
 
-#ifndef NONET
-static constexpr bool DisableEncryption = false;
-#endif
-
 const char *packet_type_to_string(uint8_t packetType)
 {
 	switch (packetType) {
@@ -154,31 +150,10 @@ void packet_in::Decrypt()
 		ABORT();
 	if (have_decrypted)
 		return;
-#ifndef NONET
-	if (!DisableEncryption) {
-		if (encrypted_buffer.size() < crypto_secretbox_NONCEBYTES
-		        + crypto_secretbox_MACBYTES
-		        + sizeof(packet_type) + 2 * sizeof(plr_t))
-			throw packet_exception();
-		auto pktlen = (encrypted_buffer.size()
-		    - crypto_secretbox_NONCEBYTES
-		    - crypto_secretbox_MACBYTES);
-		decrypted_buffer.resize(pktlen);
-		int status = crypto_secretbox_open_easy(
-		    decrypted_buffer.data(),
-		    encrypted_buffer.data() + crypto_secretbox_NONCEBYTES,
-		    encrypted_buffer.size() - crypto_secretbox_NONCEBYTES,
-		    encrypted_buffer.data(),
-		    key.data());
-		if (status != 0)
-			throw packet_exception();
-	} else
-#endif
-	{
-		if (encrypted_buffer.size() < sizeof(packet_type) + 2 * sizeof(plr_t))
-			throw packet_exception();
-		decrypted_buffer = encrypted_buffer;
-	}
+	
+	if (encrypted_buffer.size() < sizeof(packet_type) + 2 * sizeof(plr_t))
+		throw packet_exception();
+	decrypted_buffer = encrypted_buffer;
 
 	process_data();
 
@@ -194,48 +169,12 @@ void packet_out::Encrypt()
 
 	process_data();
 
-#ifndef NONET
-	if (!DisableEncryption) {
-		auto lenCleartext = encrypted_buffer.size();
-		encrypted_buffer.insert(encrypted_buffer.begin(),
-		    crypto_secretbox_NONCEBYTES, 0);
-		encrypted_buffer.insert(encrypted_buffer.end(),
-		    crypto_secretbox_MACBYTES, 0);
-		randombytes_buf(encrypted_buffer.data(), crypto_secretbox_NONCEBYTES);
-		int status = crypto_secretbox_easy(
-		    encrypted_buffer.data() + crypto_secretbox_NONCEBYTES,
-		    encrypted_buffer.data() + crypto_secretbox_NONCEBYTES,
-		    lenCleartext,
-		    encrypted_buffer.data(),
-		    key.data());
-		if (status != 0)
-			ABORT();
-	}
-#endif
 	have_encrypted = true;
 }
 
 packet_factory::packet_factory(std::string pw)
 {
-#ifndef NONET
-	if (sodium_init() < 0)
-		ABORT();
-	pw.resize(std::min<std::size_t>(pw.size(), crypto_pwhash_argon2id_PASSWD_MAX));
-	pw.resize(std::max<std::size_t>(pw.size(), crypto_pwhash_argon2id_PASSWD_MIN), 0);
-	std::string salt("W9bE9dQgVaeybwr2");
-	salt.resize(crypto_pwhash_argon2id_SALTBYTES, 0);
-	int status = crypto_pwhash(
-	    key.data(),
-	    crypto_secretbox_KEYBYTES,
-	    pw.data(),
-	    pw.size(),
-	    reinterpret_cast<const unsigned char *>(salt.data()),
-	    3 * crypto_pwhash_argon2id_OPSLIMIT_MIN,
-	    2 * crypto_pwhash_argon2id_MEMLIMIT_MIN,
-	    crypto_pwhash_ALG_ARGON2ID13);
-	if (status != 0)
-		ABORT();
-#endif
+
 }
 
 } // namespace net
diff --git a/Source/dvlnet/packet.h b/Source/dvlnet/packet.h
index 07adc3578ac..378ee028d97 100644
--- a/Source/dvlnet/packet.h
+++ b/Source/dvlnet/packet.h
@@ -5,9 +5,6 @@
 #include <memory>
 #include <array>
 #include <cstring>
-#ifndef NONET
-#include <sodium.h>
-#endif
 
 #include "dvlnet/abstract_net.h"
 #include "utils/stubs.h"
@@ -35,12 +32,9 @@ typedef uint8_t plr_t;
 typedef uint32_t cookie_t;
 typedef int turn_t;      // change int to something else in devilution code later
 typedef int leaveinfo_t; // also change later
-#ifndef NONET
-typedef std::array<unsigned char, crypto_secretbox_KEYBYTES> key_t;
-#else
+
 // Stub out the key_t defintion as we're not doing any encryption.
 using key_t = uint8_t;
-#endif
 
 static constexpr plr_t PLR_MASTER = 0xFE;
 static constexpr plr_t PLR_BROADCAST = 0xFF;
diff --git a/Source/dvlnet/protocol_zt.cpp b/Source/dvlnet/protocol_zt.cpp
deleted file mode 100644
index 6e8655900e8..00000000000
--- a/Source/dvlnet/protocol_zt.cpp
+++ /dev/null
@@ -1,312 +0,0 @@
-#include "dvlnet/protocol_zt.h"
-
-#include <random>
-
-#include <SDL.h>
-
-#include <lwip/igmp.h>
-#include <lwip/mld6.h>
-#include <lwip/sockets.h>
-#include <lwip/tcpip.h>
-
-#include "dvlnet/zerotier_native.h"
-#include "utils/log.hpp"
-
-namespace devilution {
-namespace net {
-
-protocol_zt::protocol_zt()
-{
-	zerotier_network_start();
-}
-
-void protocol_zt::set_nonblock(int fd)
-{
-	static_assert(O_NONBLOCK == 1, "O_NONBLOCK == 1 not satisfied");
-	auto mode = lwip_fcntl(fd, F_GETFL, 0);
-	mode |= O_NONBLOCK;
-	lwip_fcntl(fd, F_SETFL, mode);
-}
-
-void protocol_zt::set_nodelay(int fd)
-{
-	const int yes = 1;
-	lwip_setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&yes, sizeof(yes));
-}
-
-void protocol_zt::set_reuseaddr(int fd)
-{
-	const int yes = 1;
-	lwip_setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
-}
-
-bool protocol_zt::network_online()
-{
-	if (!zerotier_network_ready())
-		return false;
-
-	struct sockaddr_in6 in6 {
-	};
-	in6.sin6_port = htons(default_port);
-	in6.sin6_family = AF_INET6;
-	in6.sin6_addr = in6addr_any;
-
-	if (fd_udp == -1) {
-		fd_udp = lwip_socket(AF_INET6, SOCK_DGRAM, 0);
-		set_reuseaddr(fd_udp);
-		auto ret = lwip_bind(fd_udp, (struct sockaddr *)&in6, sizeof(in6));
-		if (ret < 0) {
-			Log("lwip, (udp) bind: {}", strerror(errno));
-			throw protocol_exception();
-		}
-		set_nonblock(fd_udp);
-	}
-	if (fd_tcp == -1) {
-		fd_tcp = lwip_socket(AF_INET6, SOCK_STREAM, 0);
-		set_reuseaddr(fd_tcp);
-		auto r1 = lwip_bind(fd_tcp, (struct sockaddr *)&in6, sizeof(in6));
-		if (r1 < 0) {
-			Log("lwip, (tcp) bind: {}", strerror(errno));
-			throw protocol_exception();
-		}
-		auto r2 = lwip_listen(fd_tcp, 10);
-		if (r2 < 0) {
-			Log("lwip, listen: {}", strerror(errno));
-			throw protocol_exception();
-		}
-		set_nonblock(fd_tcp);
-		set_nodelay(fd_tcp);
-	}
-	return true;
-}
-
-bool protocol_zt::send(const endpoint &peer, const buffer_t &data)
-{
-	peer_list[peer].send_queue.push_back(frame_queue::MakeFrame(data));
-	return true;
-}
-
-bool protocol_zt::send_oob(const endpoint &peer, const buffer_t &data) const
-{
-	struct sockaddr_in6 in6 {
-	};
-	in6.sin6_port = htons(default_port);
-	in6.sin6_family = AF_INET6;
-	std::copy(peer.addr.begin(), peer.addr.end(), in6.sin6_addr.s6_addr);
-	lwip_sendto(fd_udp, data.data(), data.size(), 0, (const struct sockaddr *)&in6, sizeof(in6));
-	return true;
-}
-
-bool protocol_zt::send_oob_mc(const buffer_t &data) const
-{
-	endpoint mc;
-	std::copy(dvl_multicast_addr, dvl_multicast_addr + 16, mc.addr.begin());
-	return send_oob(mc, data);
-}
-
-bool protocol_zt::send_queued_peer(const endpoint &peer)
-{
-	if (peer_list[peer].fd == -1) {
-		peer_list[peer].fd = lwip_socket(AF_INET6, SOCK_STREAM, 0);
-		set_nodelay(peer_list[peer].fd);
-		set_nonblock(peer_list[peer].fd);
-		struct sockaddr_in6 in6 {
-		};
-		in6.sin6_port = htons(default_port);
-		in6.sin6_family = AF_INET6;
-		std::copy(peer.addr.begin(), peer.addr.end(), in6.sin6_addr.s6_addr);
-		lwip_connect(peer_list[peer].fd, (const struct sockaddr *)&in6, sizeof(in6));
-	}
-	while (!peer_list[peer].send_queue.empty()) {
-		auto len = peer_list[peer].send_queue.front().size();
-		auto r = lwip_send(peer_list[peer].fd, peer_list[peer].send_queue.front().data(), len, 0);
-		if (r < 0) {
-			// handle error
-			return false;
-		}
-		if (decltype(len)(r) < len) {
-			// partial send
-			auto it = peer_list[peer].send_queue.front().begin();
-			peer_list[peer].send_queue.front().erase(it, it + r);
-			return true;
-		}
-		if (decltype(len)(r) == len) {
-			peer_list[peer].send_queue.pop_front();
-		} else {
-			throw protocol_exception();
-		}
-	}
-	return true;
-}
-
-bool protocol_zt::recv_peer(const endpoint &peer)
-{
-	unsigned char buf[PKTBUF_LEN];
-	while (true) {
-		auto len = lwip_recv(peer_list[peer].fd, buf, sizeof(buf), 0);
-		if (len >= 0) {
-			peer_list[peer].recv_queue.Write(buffer_t(buf, buf + len));
-		} else {
-			return errno == EAGAIN || errno == EWOULDBLOCK;
-		}
-	}
-}
-
-bool protocol_zt::send_queued_all()
-{
-	for (auto &peer : peer_list) {
-		if (!send_queued_peer(peer.first)) {
-			// handle error?
-		}
-	}
-	return true;
-}
-
-bool protocol_zt::recv_from_peers()
-{
-	for (auto &peer : peer_list) {
-		if (peer.second.fd != -1) {
-			if (!recv_peer(peer.first)) {
-				disconnect_queue.push_back(peer.first);
-			}
-		}
-	}
-	return true;
-}
-
-bool protocol_zt::recv_from_udp()
-{
-	unsigned char buf[PKTBUF_LEN];
-	struct sockaddr_in6 in6 {
-	};
-	socklen_t addrlen = sizeof(in6);
-	auto len = lwip_recvfrom(fd_udp, buf, sizeof(buf), 0, (struct sockaddr *)&in6, &addrlen);
-	if (len < 0)
-		return false;
-	buffer_t data(buf, buf + len);
-	endpoint ep;
-	std::copy(in6.sin6_addr.s6_addr, in6.sin6_addr.s6_addr + 16, ep.addr.begin());
-	oob_recv_queue.emplace_back(ep, std::move(data));
-	return true;
-}
-
-bool protocol_zt::accept_all()
-{
-	struct sockaddr_in6 in6 {
-	};
-	socklen_t addrlen = sizeof(in6);
-	while (true) {
-		auto newfd = lwip_accept(fd_tcp, (struct sockaddr *)&in6, &addrlen);
-		if (newfd < 0)
-			break;
-		endpoint ep;
-		std::copy(in6.sin6_addr.s6_addr, in6.sin6_addr.s6_addr + 16, ep.addr.begin());
-		if (peer_list[ep].fd != -1) {
-			Log("protocol_zt::accept_all: WARNING: overwriting connection");
-			lwip_close(peer_list[ep].fd);
-		}
-		set_nonblock(newfd);
-		set_nodelay(newfd);
-		peer_list[ep].fd = newfd;
-	}
-	return true;
-}
-
-bool protocol_zt::recv(endpoint &peer, buffer_t &data)
-{
-	accept_all();
-	send_queued_all();
-	recv_from_peers();
-	recv_from_udp();
-
-	if (!oob_recv_queue.empty()) {
-		peer = oob_recv_queue.front().first;
-		data = oob_recv_queue.front().second;
-		oob_recv_queue.pop_front();
-		return true;
-	}
-
-	for (auto &p : peer_list) {
-		if (p.second.recv_queue.PacketReady()) {
-			peer = p.first;
-			data = p.second.recv_queue.ReadPacket();
-			return true;
-		}
-	}
-	return false;
-}
-
-bool protocol_zt::get_disconnected(endpoint &peer)
-{
-	if (!disconnect_queue.empty()) {
-		peer = disconnect_queue.front();
-		disconnect_queue.pop_front();
-		return true;
-	}
-	return false;
-}
-
-void protocol_zt::disconnect(const endpoint &peer)
-{
-	if (peer_list.count(peer) != 0) {
-		if (peer_list[peer].fd != -1) {
-			if (lwip_close(peer_list[peer].fd) < 0) {
-				Log("lwip_close: {}", strerror(errno));
-			}
-		}
-		peer_list.erase(peer);
-	}
-}
-
-void protocol_zt::close_all()
-{
-	if (fd_tcp != -1) {
-		lwip_close(fd_tcp);
-		fd_tcp = -1;
-	}
-	if (fd_udp != -1) {
-		lwip_close(fd_udp);
-		fd_udp = -1;
-	}
-	for (auto &peer : peer_list) {
-		if (peer.second.fd != -1)
-			lwip_close(peer.second.fd);
-	}
-	peer_list.clear();
-}
-
-protocol_zt::~protocol_zt()
-{
-	close_all();
-}
-
-void protocol_zt::endpoint::from_string(const std::string &str)
-{
-	ip_addr_t a;
-	if (ipaddr_aton(str.c_str(), &a) == 0)
-		return;
-	if (!IP_IS_V6_VAL(a))
-		return;
-	const auto *r = reinterpret_cast<const unsigned char *>(a.u_addr.ip6.addr);
-	std::copy(r, r + 16, addr.begin());
-}
-
-uint64_t protocol_zt::current_ms()
-{
-	return 0;
-}
-
-std::string protocol_zt::make_default_gamename()
-{
-	std::string ret;
-	std::string allowedChars = "abcdefghkopqrstuvwxyz";
-	std::random_device rd;
-	std::uniform_int_distribution<int> dist(0, allowedChars.size() - 1);
-	for (int i = 0; i < 5; ++i) {
-		ret += allowedChars.at(dist(rd));
-	}
-	return ret;
-}
-
-} // namespace net
-} // namespace devilution
diff --git a/Source/dvlnet/protocol_zt.h b/Source/dvlnet/protocol_zt.h
deleted file mode 100644
index b316246a76b..00000000000
--- a/Source/dvlnet/protocol_zt.h
+++ /dev/null
@@ -1,111 +0,0 @@
-#pragma once
-
-#include <string>
-#include <set>
-#include <atomic>
-#include <deque>
-#include <map>
-#include <exception>
-#include <array>
-#include <algorithm>
-
-#include "dvlnet/frame_queue.h"
-
-namespace devilution {
-namespace net {
-
-class protocol_exception : public std::exception {
-public:
-	const char *what() const throw() override
-	{
-		return "Protocol error";
-	}
-};
-
-class protocol_zt {
-public:
-	class endpoint {
-	public:
-		std::array<unsigned char, 16> addr = {};
-
-		explicit operator bool() const
-		{
-			auto empty = std::array<unsigned char, 16> {};
-			return (addr != empty);
-		}
-
-		bool operator==(const endpoint &rhs) const
-		{
-			return addr == rhs.addr;
-		}
-
-		bool operator!=(const endpoint &rhs) const
-		{
-			return !(*this == rhs);
-		}
-
-		bool operator<(const endpoint &rhs) const
-		{
-			return addr < rhs.addr;
-		}
-
-		buffer_t serialize() const
-		{
-			return buffer_t(addr.begin(), addr.end());
-		}
-
-		void unserialize(const buffer_t &buf)
-		{
-			if (buf.size() != 16)
-				throw protocol_exception();
-			std::copy(buf.begin(), buf.end(), addr.begin());
-		}
-
-		void from_string(const std::string &str);
-	};
-
-	protocol_zt();
-	~protocol_zt();
-	void disconnect(const endpoint &peer);
-	bool send(const endpoint &peer, const buffer_t &data);
-	bool send_oob(const endpoint &peer, const buffer_t &data) const;
-	bool send_oob_mc(const buffer_t &data) const;
-	bool recv(endpoint &peer, buffer_t &data);
-	bool get_disconnected(endpoint &peer);
-	bool network_online();
-	static std::string make_default_gamename();
-
-private:
-	static constexpr uint32_t PKTBUF_LEN = 65536;
-	static constexpr uint16_t default_port = 6112;
-
-	struct peer_state {
-		int fd = -1;
-		std::deque<buffer_t> send_queue;
-		frame_queue recv_queue;
-	};
-
-	std::deque<std::pair<endpoint, buffer_t>> oob_recv_queue;
-	std::deque<endpoint> disconnect_queue;
-
-	std::map<endpoint, peer_state> peer_list;
-	int fd_tcp = -1;
-	int fd_udp = -1;
-
-	static uint64_t current_ms();
-	void close_all();
-
-	static void set_nonblock(int fd);
-	static void set_nodelay(int fd);
-	static void set_reuseaddr(int fd);
-
-	bool send_queued_peer(const endpoint &peer);
-	bool recv_peer(const endpoint &peer);
-	bool send_queued_all();
-	bool recv_from_peers();
-	bool recv_from_udp();
-	bool accept_all();
-};
-
-} // namespace net
-} // namespace devilution
diff --git a/Source/dvlnet/tcp_client.cpp b/Source/dvlnet/tcp_client.cpp
deleted file mode 100644
index e04336db5a6..00000000000
--- a/Source/dvlnet/tcp_client.cpp
+++ /dev/null
@@ -1,140 +0,0 @@
-#include "dvlnet/tcp_client.h"
-#include "options.h"
-
-#include <SDL.h>
-#include <exception>
-#include <functional>
-#include <memory>
-#include <sodium.h>
-#include <sstream>
-#include <stdexcept>
-#include <system_error>
-
-#include <asio/connect.hpp>
-
-namespace devilution {
-namespace net {
-
-int tcp_client::create(std::string addrstr, std::string passwd)
-{
-	try {
-		auto port = sgOptions.Network.nPort;
-		local_server = std::make_unique<tcp_server>(ioc, addrstr, port, passwd);
-		return join(local_server->LocalhostSelf(), passwd);
-	} catch (std::system_error &e) {
-		SDL_SetError("%s", e.what());
-		return -1;
-	}
-}
-
-int tcp_client::join(std::string addrstr, std::string passwd)
-{
-	constexpr int MsSleep = 10;
-	constexpr int NoSleep = 250;
-
-	setup_password(passwd);
-	try {
-		std::stringstream port;
-		port << sgOptions.Network.nPort;
-		asio::connect(sock, resolver.resolve(addrstr, port.str()));
-		asio::ip::tcp::no_delay option(true);
-		sock.set_option(option);
-	} catch (std::exception &e) {
-		SDL_SetError("%s", e.what());
-		return -1;
-	}
-	StartReceive();
-	{
-		randombytes_buf(reinterpret_cast<unsigned char *>(&cookie_self),
-		    sizeof(cookie_t));
-		auto pkt = pktfty->make_packet<PT_JOIN_REQUEST>(PLR_BROADCAST,
-		    PLR_MASTER, cookie_self,
-		    game_init_info);
-		send(*pkt);
-		for (auto i = 0; i < NoSleep; ++i) {
-			try {
-				poll();
-			} catch (const std::runtime_error &e) {
-				SDL_SetError("%s", e.what());
-				return -1;
-			}
-			if (plr_self != PLR_BROADCAST)
-				break; // join successful
-			SDL_Delay(MsSleep);
-		}
-	}
-	if (plr_self == PLR_BROADCAST) {
-		SDL_SetError("%s", "Unable to connect");
-		return -1;
-	}
-
-	return plr_self;
-}
-
-void tcp_client::poll()
-{
-	ioc.poll();
-}
-
-void tcp_client::HandleReceive(const asio::error_code &error, size_t bytesRead)
-{
-	if (error) {
-		// error in recv from server
-		// returning and doing nothing should be the same
-		// as if all connections to other clients were lost
-		return;
-	}
-	if (bytesRead == 0) {
-		throw std::runtime_error("error: read 0 bytes from server");
-	}
-	recv_buffer.resize(bytesRead);
-	recv_queue.Write(std::move(recv_buffer));
-	recv_buffer.resize(frame_queue::max_frame_size);
-	while (recv_queue.PacketReady()) {
-		auto pkt = pktfty->make_packet(recv_queue.ReadPacket());
-		RecvLocal(*pkt);
-	}
-	StartReceive();
-}
-
-void tcp_client::StartReceive()
-{
-	sock.async_receive(
-	    asio::buffer(recv_buffer),
-	    std::bind(&tcp_client::HandleReceive, this, std::placeholders::_1, std::placeholders::_2));
-}
-
-void tcp_client::HandleSend(const asio::error_code &error, size_t bytesSent)
-{
-	// empty for now
-}
-
-void tcp_client::send(packet &pkt)
-{
-	auto frame = std::make_unique<buffer_t>(frame_queue::MakeFrame(pkt.Data()));
-	auto buf = asio::buffer(*frame);
-	asio::async_write(sock, buf, [this, frame = std::move(frame)](const asio::error_code &error, size_t bytesSent) {
-		HandleSend(error, bytesSent);
-	});
-}
-
-bool tcp_client::SNetLeaveGame(int type)
-{
-	auto ret = base::SNetLeaveGame(type);
-	poll();
-	if (local_server != nullptr)
-		local_server->Close();
-	sock.close();
-	return ret;
-}
-
-std::string tcp_client::make_default_gamename()
-{
-	return std::string(sgOptions.Network.szBindAddress);
-}
-
-tcp_client::~tcp_client()
-    = default;
-
-} // namespace net
-} // namespace devilution
diff --git a/Source/dvlnet/tcp_client.h b/Source/dvlnet/tcp_client.h
deleted file mode 100644
index 6fa2c49760c..00000000000
--- a/Source/dvlnet/tcp_client.h
+++ /dev/null
@@ -1,47 +0,0 @@
-#pragma once
-
-#include <string>
-#include <memory>
-#include <asio/ts/buffer.hpp>
-#include <asio/ts/internet.hpp>
-#include <asio/ts/io_context.hpp>
-#include <asio/ts/net.hpp>
-
-#include "dvlnet/packet.h"
-#include "dvlnet/frame_queue.h"
-#include "dvlnet/base.h"
-#include "dvlnet/tcp_server.h"
-
-namespace devilution {
-namespace net {
-
-class tcp_client : public base {
-public:
-	int create(std::string addrstr, std::string passwd);
-	int join(std::string addrstr, std::string passwd);
-
-	virtual void poll();
-	virtual void send(packet &pkt);
-
-	virtual bool SNetLeaveGame(int type);
-
-	virtual ~tcp_client();
-
-	virtual std::string make_default_gamename();
-
-private:
-	frame_queue recv_queue;
-	buffer_t recv_buffer = buffer_t(frame_queue::max_frame_size);
-
-	asio::io_context ioc;
-	asio::ip::tcp::resolver resolver = asio::ip::tcp::resolver(ioc);
-	asio::ip::tcp::socket sock = asio::ip::tcp::socket(ioc);
-	std::unique_ptr<tcp_server> local_server; // must be declared *after* ioc
-
-	void HandleReceive(const asio::error_code &error, size_t bytesRead);
-	void StartReceive();
-	void HandleSend(const asio::error_code &error, size_t bytesSent);
-};
-
-} // namespace net
-} // namespace devilution
diff --git a/Source/dvlnet/tcp_server.cpp b/Source/dvlnet/tcp_server.cpp
deleted file mode 100644
index 1bc840f6e6c..00000000000
--- a/Source/dvlnet/tcp_server.cpp
+++ /dev/null
@@ -1,225 +0,0 @@
-#include "dvlnet/tcp_server.h"
-
-#include <chrono>
-#include <functional>
-#include <memory>
-#include <utility>
-
-#include "dvlnet/base.h"
-#include "utils/log.hpp"
-
-namespace devilution {
-namespace net {
-
-tcp_server::tcp_server(asio::io_context &ioc, const std::string &bindaddr,
-    unsigned short port, std::string pw)
-    : ioc(ioc)
-    , pktfty(std::move(pw))
-{
-	auto addr = asio::ip::address::from_string(bindaddr);
-	auto ep = asio::ip::tcp::endpoint(addr, port);
-	acceptor = std::make_unique<asio::ip::tcp::acceptor>(ioc, ep, true);
-	StartAccept();
-}
-
-std::string tcp_server::LocalhostSelf()
-{
-	auto addr = acceptor->local_endpoint().address();
-	if (addr.is_unspecified()) {
-		if (addr.is_v4()) {
-			return asio::ip::address_v4::loopback().to_string();
-		}
-		if (addr.is_v6()) {
-			return asio::ip::address_v6::loopback().to_string();
-		}
-		ABORT();
-	}
-	return addr.to_string();
-}
-
-tcp_server::scc tcp_server::MakeConnection()
-{
-	return std::make_shared<client_connection>(ioc);
-}
-
-plr_t tcp_server::NextFree()
-{
-	for (plr_t i = 0; i < MAX_PLRS; ++i)
-		if (!connections[i])
-			return i;
-	return PLR_BROADCAST;
-}
-
-bool tcp_server::Empty()
-{
-	for (plr_t i = 0; i < MAX_PLRS; ++i)
-		if (connections[i])
-			return false;
-	return true;
-}
-
-void tcp_server::StartReceive(const scc &con)
-{
-	con->socket.async_receive(
-	    asio::buffer(con->recv_buffer),
-	    std::bind(&tcp_server::HandleReceive, this, con, std::placeholders::_1, std::placeholders::_2));
-}
-
-void tcp_server::HandleReceive(const scc &con, const asio::error_code &ec,
-    size_t bytesRead)
-{
-	if (ec || bytesRead == 0) {
-		DropConnection(con);
-		return;
-	}
-	con->recv_buffer.resize(bytesRead);
-	con->recv_queue.Write(std::move(con->recv_buffer));
-	con->recv_buffer.resize(frame_queue::max_frame_size);
-	while (con->recv_queue.PacketReady()) {
-		try {
-			auto pkt = pktfty.make_packet(con->recv_queue.ReadPacket());
-			if (con->plr == PLR_BROADCAST) {
-				HandleReceiveNewPlayer(con, *pkt);
-			} else {
-				con->timeout = timeout_active;
-				HandleReceivePacket(*pkt);
-			}
-		} catch (dvlnet_exception &e) {
-			Log("Network error: {}", e.what());
-			DropConnection(con);
-			return;
-		}
-	}
-	StartReceive(con);
-}
-
-void tcp_server::SendConnect(const scc &con)
-{
-	auto pkt = pktfty.make_packet<PT_CONNECT>(PLR_MASTER, PLR_BROADCAST,
-	    con->plr);
-	SendPacket(*pkt);
-}
-
-void tcp_server::HandleReceiveNewPlayer(const scc &con, packet &pkt)
-{
-	auto newplr = NextFree();
-	if (newplr == PLR_BROADCAST)
-		throw server_exception();
-	if (Empty())
-		game_init_info = pkt.Info();
-	auto reply = pktfty.make_packet<PT_JOIN_ACCEPT>(PLR_MASTER, PLR_BROADCAST,
-	    pkt.Cookie(), newplr,
-	    game_init_info);
-	StartSend(con, *reply);
-	con->plr = newplr;
-	connections[newplr] = con;
-	con->timeout = timeout_active;
-	SendConnect(con);
-}
-
-void tcp_server::HandleReceivePacket(packet &pkt)
-{
-	SendPacket(pkt);
-}
-
-void tcp_server::SendPacket(packet &pkt)
-{
-	if (pkt.Destination() == PLR_BROADCAST) {
-		for (auto i = 0; i < MAX_PLRS; ++i)
-			if (i != pkt.Source() && connections[i])
-				StartSend(connections[i], pkt);
-	} else {
-		if (pkt.Destination() >= MAX_PLRS)
-			throw server_exception();
-		if ((pkt.Destination() != pkt.Source()) && connections[pkt.Destination()])
-			StartSend(connections[pkt.Destination()], pkt);
-	}
-}
-
-void tcp_server::StartSend(const scc &con, packet &pkt)
-{
-	auto frame = std::make_unique<buffer_t>(frame_queue::MakeFrame(pkt.Data()));
-	auto buf = asio::buffer(*frame);
-	asio::async_write(con->socket, buf,
-	    [this, con, frame = std::move(frame)](const asio::error_code &ec, size_t bytesSent) {
-		    HandleSend(con, ec, bytesSent);
-	    });
-}
-
-void tcp_server::HandleSend(const scc &con, const asio::error_code &ec,
-    size_t bytesSent)
-{
-	// empty for now
-}
-
-void tcp_server::StartAccept()
-{
-	auto nextcon = MakeConnection();
-	acceptor->async_accept(
-	    nextcon->socket,
-	    std::bind(&tcp_server::HandleAccept, this, nextcon, std::placeholders::_1));
-}
-
-void tcp_server::HandleAccept(const scc &con, const asio::error_code &ec)
-{
-	if (ec)
-		return;
-	if (NextFree() == PLR_BROADCAST) {
-		DropConnection(con);
-	} else {
-		asio::ip::tcp::no_delay option(true);
-		con->socket.set_option(option);
-		con->timeout = timeout_connect;
-		StartReceive(con);
-		StartTimeout(con);
-	}
-	StartAccept();
-}
-
-void tcp_server::StartTimeout(const scc &con)
-{
-	con->timer.expires_after(std::chrono::seconds(1));
-	con->timer.async_wait(
-	    std::bind(&tcp_server::HandleTimeout, this, con, std::placeholders::_1));
-}
-
-void tcp_server::HandleTimeout(const scc &con, const asio::error_code &ec)
-{
-	if (ec) {
-		DropConnection(con);
-		return;
-	}
-	if (con->timeout > 0)
-		con->timeout -= 1;
-	if (con->timeout <= 0) {
-		con->timeout = 0;
-		DropConnection(con);
-		return;
-	}
-	StartTimeout(con);
-}
-
-void tcp_server::DropConnection(const scc &con)
-{
-	if (con->plr != PLR_BROADCAST) {
-		auto pkt = pktfty.make_packet<PT_DISCONNECT>(PLR_MASTER, PLR_BROADCAST,
-		    con->plr, LEAVE_DROP);
-		connections[con->plr] = nullptr;
-		SendPacket(*pkt);
-		// TODO: investigate if it is really ok for the server to
-		//       drop a client directly.
-	}
-	con->timer.cancel();
-	con->socket.close();
-}
-
-void tcp_server::Close()
-{
-	acceptor->close();
-}
-
-tcp_server::~tcp_server()
-    = default;
-
-} // namespace net
-} // namespace devilution
diff --git a/Source/dvlnet/tcp_server.h b/Source/dvlnet/tcp_server.h
deleted file mode 100644
index 6a718fa5d8d..00000000000
--- a/Source/dvlnet/tcp_server.h
+++ /dev/null
@@ -1,79 +0,0 @@
-#pragma once
-
-#include <string>
-#include <memory>
-#include <array>
-#include <asio/ts/buffer.hpp>
-#include <asio/ts/internet.hpp>
-#include <asio/ts/io_context.hpp>
-#include <asio/ts/net.hpp>
-
-#include "dvlnet/packet.h"
-#include "dvlnet/abstract_net.h"
-#include "dvlnet/frame_queue.h"
-
-namespace devilution {
-namespace net {
-
-class server_exception : public dvlnet_exception {
-public:
-	const char *what() const throw() override
-	{
-		return "Invalid player ID";
-	}
-};
-
-class tcp_server {
-public:
-	tcp_server(asio::io_context &ioc, const std::string &bindaddr,
-	    unsigned short port, std::string pw);
-	std::string LocalhostSelf();
-	void Close();
-	virtual ~tcp_server();
-
-private:
-	static constexpr int timeout_connect = 30;
-	static constexpr int timeout_active = 60;
-
-	struct client_connection {
-		frame_queue recv_queue;
-		buffer_t recv_buffer = buffer_t(frame_queue::max_frame_size);
-		plr_t plr = PLR_BROADCAST;
-		asio::ip::tcp::socket socket;
-		asio::steady_timer timer;
-		int timeout;
-		client_connection(asio::io_context &ioc)
-		    : socket(ioc)
-		    , timer(ioc)
-		{
-		}
-	};
-
-	typedef std::shared_ptr<client_connection> scc;
-
-	asio::io_context &ioc;
-	packet_factory pktfty;
-	std::unique_ptr<asio::ip::tcp::acceptor> acceptor;
-	std::array<scc, MAX_PLRS> connections;
-	buffer_t game_init_info;
-
-	scc MakeConnection();
-	plr_t NextFree();
-	bool Empty();
-	void StartAccept();
-	void HandleAccept(const scc &con, const asio::error_code &ec);
-	void StartReceive(const scc &con);
-	void HandleReceive(const scc &con, const asio::error_code &ec, size_t bytesRead);
-	void HandleReceiveNewPlayer(const scc &con, packet &pkt);
-	void HandleReceivePacket(packet &pkt);
-	void SendConnect(const scc &con);
-	void SendPacket(packet &pkt);
-	void StartSend(const scc &con, packet &pkt);
-	void HandleSend(const scc &con, const asio::error_code &ec, size_t bytesSent);
-	void StartTimeout(const scc &con);
-	void HandleTimeout(const scc &con, const asio::error_code &ec);
-	void DropConnection(const scc &con);
-};
-
-} //namespace net
-} //namespace devilution
diff --git a/Source/dvlnet/zerotier_lwip.cpp b/Source/dvlnet/zerotier_lwip.cpp
deleted file mode 100644
index 8c7b3bb7ce8..00000000000
--- a/Source/dvlnet/zerotier_lwip.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-#include "dvlnet/zerotier_lwip.h"
-
-#include <lwip/igmp.h>
-#include <lwip/mld6.h>
-#include <lwip/sockets.h>
-#include <lwip/tcpip.h>
-
-#include <SDL.h>
-
-#include "utils/log.hpp"
-
-#include "dvlnet/zerotier_native.h"
-
-namespace devilution {
-namespace net {
-
-void print_ip6_addr(void *x)
-{
-	char ipstr[INET6_ADDRSTRLEN];
-	auto *in = static_cast<sockaddr_in6 *>(x);
-	lwip_inet_ntop(AF_INET6, &(in->sin6_addr), ipstr, INET6_ADDRSTRLEN);
-	Log("ZeroTier: ZTS_EVENT_ADDR_NEW_IP6, addr={}", ipstr);
-}
-
-void zt_ip6setup()
-{
-	ip6_addr_t mcaddr;
-	memcpy(mcaddr.addr, dvl_multicast_addr, 16);
-	mcaddr.zone = 0;
-	LOCK_TCPIP_CORE();
-	mld6_joingroup(IP6_ADDR_ANY6, &mcaddr);
-	UNLOCK_TCPIP_CORE();
-}
-
-} // namespace net
-} // namespace devilution
diff --git a/Source/dvlnet/zerotier_lwip.h b/Source/dvlnet/zerotier_lwip.h
deleted file mode 100644
index d5361164be1..00000000000
--- a/Source/dvlnet/zerotier_lwip.h
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace devilution {
-namespace net {
-
-void print_ip6_addr(void *x);
-void zt_ip6setup();
-
-} // namespace net
-} // namespace devilution
diff --git a/Source/dvlnet/zerotier_native.cpp b/Source/dvlnet/zerotier_native.cpp
deleted file mode 100644
index 9cc9356ff7e..00000000000
--- a/Source/dvlnet/zerotier_native.cpp
+++ /dev/null
@@ -1,67 +0,0 @@
-#include "dvlnet/zerotier_native.h"
-
-#include <SDL.h>
-#include <atomic>
-
-#include <ZeroTierSockets.h>
-#include <cstdlib>
-
-#include "utils/log.hpp"
-#include "utils/paths.h"
-
-#include "dvlnet/zerotier_lwip.h"
-
-namespace devilution {
-namespace net {
-
-//static constexpr uint64_t zt_earth = 0x8056c2e21c000001;
-static constexpr uint64_t ZtNetwork = 0xaf78bf943649eb12;
-
-static std::atomic_bool zt_network_ready(false);
-static std::atomic_bool zt_node_online(false);
-static std::atomic_bool zt_started(false);
-static std::atomic_bool zt_joined(false);
-
-static void Callback(struct zts_callback_msg *msg)
-{
-	//printf("callback %i\n", msg->eventCode);
-	if (msg->eventCode == ZTS_EVENT_NODE_ONLINE) {
-		Log("ZeroTier: ZTS_EVENT_NODE_ONLINE, nodeId={:x}", (unsigned long long)msg->node->address);
-		zt_node_online = true;
-		if (!zt_joined) {
-			zts_join(ZtNetwork);
-			zt_joined = true;
-		}
-	} else if (msg->eventCode == ZTS_EVENT_NODE_OFFLINE) {
-		Log("ZeroTier: ZTS_EVENT_NODE_OFFLINE");
-		zt_node_online = false;
-	} else if (msg->eventCode == ZTS_EVENT_NETWORK_READY_IP6) {
-		Log("ZeroTier: ZTS_EVENT_NETWORK_READY_IP6, networkId={:x}", (unsigned long long)msg->network->nwid);
-		zt_ip6setup();
-		zt_network_ready = true;
-	} else if (msg->eventCode == ZTS_EVENT_ADDR_ADDED_IP6) {
-		print_ip6_addr(&(msg->addr->addr));
-	}
-}
-
-bool zerotier_network_ready()
-{
-	return zt_network_ready && zt_node_online;
-}
-
-void zerotier_network_stop()
-{
-	zts_stop();
-}
-
-void zerotier_network_start()
-{
-	if (zt_started)
-		return;
-	std::string ztpath = paths::PrefPath() + "zerotier";
-	zts_start(ztpath.c_str(), (void (*)(void *))Callback, 0);
-	std::atexit(zerotier_network_stop);
-}
-
-} // namespace net
-} // namespace devilution
diff --git a/Source/dvlnet/zerotier_native.h b/Source/dvlnet/zerotier_native.h
deleted file mode 100644
index 9800e899da4..00000000000
--- a/Source/dvlnet/zerotier_native.h
+++ /dev/null
@@ -1,18 +0,0 @@
-#pragma once
-
-namespace devilution {
-namespace net {
-
-bool zerotier_network_ready();
-void zerotier_network_start();
-void zerotier_network_stop();
-
-// NOTE: We have patched our libzt to have the corresponding multicast
-// MAC hardcoded, since libzt is still missing the proper handling.
-const unsigned char dvl_multicast_addr[16] = {
-	0xff, 0x0e, 0xa8, 0xa9, 0xb6, 0x11, 0x58, 0xce,
-	0x04, 0x12, 0xfd, 0x73, 0x37, 0x86, 0x6f, 0xb7
-};
-
-} // namespace net
-} // namespace devilution
diff --git a/Source/storm/storm_net.cpp b/Source/storm/storm_net.cpp
index 5e59732096c..5b3e1a3a3d9 100644
--- a/Source/storm/storm_net.cpp
+++ b/Source/storm/storm_net.cpp
@@ -1,10 +1,4 @@
 #include <memory>
-#ifndef NONET
-#include "utils/sdl_mutex.h"
-#include <mutex>
-#include <thread>
-#include <utility>
-#endif
 
 #include "dvlnet/abstract_net.h"
 #include "menu.h"
@@ -18,15 +12,8 @@ static std::unique_ptr<net::abstract_net> dvlnet_inst;
 static char gpszGameName[128] = {};
 static char gpszGamePassword[128] = {};
 
-#ifndef NONET
-static SdlMutex storm_net_mutex;
-#endif
-
 bool SNetReceiveMessage(int *senderplayerid, void **data, uint32_t *databytes)
 {
-#ifndef NONET
-	std::lock_guard<SdlMutex> lg(storm_net_mutex);
-#endif
 	if (!dvlnet_inst->SNetReceiveMessage(senderplayerid, data, databytes)) {
 		SErrSetLastError(STORM_ERROR_NO_MESSAGES_WAITING);
 		return false;
@@ -36,17 +23,11 @@ bool SNetReceiveMessage(int *senderplayerid, void **data, uint32_t *databytes)
 
 bool SNetSendMessage(int playerID, void *data, unsigned int databytes)
 {
-#ifndef NONET
-	std::lock_guard<SdlMutex> lg(storm_net_mutex);
-#endif
 	return dvlnet_inst->SNetSendMessage(playerID, data, databytes);
 }
 
 bool SNetReceiveTurns(int arraysize, char **arraydata, size_t *arraydatabytes, uint32_t *arrayplayerstatus)
 {
-#ifndef NONET
-	std::lock_guard<SdlMutex> lg(storm_net_mutex);
-#endif
 	if (arraysize != MAX_PLRS)
 		UNIMPLEMENTED();
 	if (!dvlnet_inst->SNetReceiveTurns(arraydata, arraydatabytes, arrayplayerstatus)) {
@@ -58,57 +39,36 @@ bool SNetReceiveTurns(int arraysize, char **arraydata, size_t *arraydatabytes, u
 
 bool SNetSendTurn(char *data, unsigned int databytes)
 {
-#ifndef NONET
-	std::lock_guard<SdlMutex> lg(storm_net_mutex);
-#endif
 	return dvlnet_inst->SNetSendTurn(data, databytes);
 }
 
 void SNetGetProviderCaps(struct _SNETCAPS *caps)
 {
-#ifndef NONET
-	std::lock_guard<SdlMutex> lg(storm_net_mutex);
-#endif
 	dvlnet_inst->SNetGetProviderCaps(caps);
 }
 
 bool SNetUnregisterEventHandler(event_type evtype)
 {
-#ifndef NONET
-	std::lock_guard<SdlMutex> lg(storm_net_mutex);
-#endif
 	return dvlnet_inst->SNetUnregisterEventHandler(evtype);
 }
 
 bool SNetRegisterEventHandler(event_type evtype, SEVTHANDLER func)
 {
-#ifndef NONET
-	std::lock_guard<SdlMutex> lg(storm_net_mutex);
-#endif
 	return dvlnet_inst->SNetRegisterEventHandler(evtype, func);
 }
 
 bool SNetDestroy()
 {
-#ifndef NONET
-	std::lock_guard<SdlMutex> lg(storm_net_mutex);
-#endif
 	return true;
 }
 
 bool SNetDropPlayer(int playerid, uint32_t flags)
 {
-#ifndef NONET
-	std::lock_guard<SdlMutex> lg(storm_net_mutex);
-#endif
 	return dvlnet_inst->SNetDropPlayer(playerid, flags);
 }
 
 bool SNetGetGameInfo(game_info type, void *dst, unsigned int length)
 {
-#ifndef NONET
-	std::lock_guard<SdlMutex> lg(storm_net_mutex);
-#endif
 	switch (type) {
 	case GAMEINFO_NAME:
 		strncpy((char *)dst, gpszGameName, length);
@@ -123,9 +83,6 @@ bool SNetGetGameInfo(game_info type, void *dst, unsigned int length)
 
 bool SNetLeaveGame(int type)
 {
-#ifndef NONET
-	std::lock_guard<SdlMutex> lg(storm_net_mutex);
-#endif
 	if (dvlnet_inst == nullptr)
 		return true;
 	return dvlnet_inst->SNetLeaveGame(type);
@@ -137,9 +94,6 @@ bool SNetLeaveGame(int type)
  */
 bool SNetInitializeProvider(uint32_t provider, struct GameData *gameData)
 {
-#ifndef NONET
-	std::lock_guard<SdlMutex> lg(storm_net_mutex);
-#endif
 	dvlnet_inst = net::abstract_net::MakeNet(provider);
 	return mainmenu_select_hero_dialog(gameData);
 }
@@ -149,9 +103,6 @@ bool SNetInitializeProvider(uint32_t provider, struct GameData *gameData)
  */
 bool SNetCreateGame(const char *pszGameName, const char *pszGamePassword, char *gameTemplateData, int gameTemplateSize, int *playerID)
 {
-#ifndef NONET
-	std::lock_guard<SdlMutex> lg(storm_net_mutex);
-#endif
 	if (gameTemplateSize != sizeof(GameData))
 		ABORT();
 	net::buffer_t gameInitInfo(gameTemplateData, gameTemplateData + gameTemplateSize);
@@ -172,9 +123,6 @@ bool SNetCreateGame(const char *pszGameName, const char *pszGamePassword, char *
 
 bool SNetJoinGame(char *pszGameName, char *pszGamePassword, int *playerID)
 {
-#ifndef NONET
-	std::lock_guard<SdlMutex> lg(storm_net_mutex);
-#endif
 	if (pszGameName != nullptr)
 		strncpy(gpszGameName, pszGameName, sizeof(gpszGameName) - 1);
 	if (pszGamePassword != nullptr)
@@ -188,17 +136,11 @@ bool SNetJoinGame(char *pszGameName, char *pszGamePassword, int *playerID)
  */
 bool SNetGetOwnerTurnsWaiting(uint32_t *turns)
 {
-#ifndef NONET
-	std::lock_guard<SdlMutex> lg(storm_net_mutex);
-#endif
 	return dvlnet_inst->SNetGetOwnerTurnsWaiting(turns);
 }
 
 bool SNetGetTurnsInTransit(uint32_t *turns)
 {
-#ifndef NONET
-	std::lock_guard<SdlMutex> lg(storm_net_mutex);
-#endif
 	return dvlnet_inst->SNetGetTurnsInTransit(turns);
 }
 
@@ -207,9 +149,6 @@ bool SNetGetTurnsInTransit(uint32_t *turns)
  */
 bool SNetSetBasePlayer(int /*unused*/)
 {
-#ifndef NONET
-	std::lock_guard<SdlMutex> lg(storm_net_mutex);
-#endif
 	return true;
 }
 
diff --git a/docs/building.md b/docs/building.md
index 21dd912131e..d3e16174a52 100644
--- a/docs/building.md
+++ b/docs/building.md
@@ -42,7 +42,6 @@ Make sure to install the `C++ CMake tools for Windows` component for Visual Stud
 
 ### General
 - `-DCMAKE_BUILD_TYPE=Release` changed build type to release and optimize for distribution.
-- `-DNONET=ON` disable network support, this also removes the need for the ASIO and Sodium.
 - `-DCMAKE_TOOLCHAIN_FILE=../CMake/32bit.cmake` generate 32bit builds on 64bit platforms.
 
 ### Debug builds