diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 335dde96f554bd8da39d88a452fa4a280320d5f5..ca33f1f7945eab8d6342ce3f958c599644ebd34b 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -68,14 +68,6 @@ static boolean serverrunning = false;
 INT32 serverplayer = 0;
 char motd[254], server_context[8]; // Message of the Day, Unique Context (even without Mumble support)
 
-// server specific vars
-UINT8 playernode[MAXPLAYERS];
-UINT8 net_nodecount, net_playercount;
-
-SINT8 nodetoplayer[MAXNETNODES];
-SINT8 nodetoplayer2[MAXNETNODES]; // say the numplayer for this node if any (splitscreen)
-UINT8 playerpernode[MAXNETNODES]; // used specialy for scplitscreen
-boolean nodeingame[MAXNETNODES]; // set false as nodes leave game
 static tic_t nettics[MAXNETNODES]; // what tic the client have received
 static tic_t supposedtics[MAXNETNODES]; // nettics prevision for smaller packet
 static UINT8 nodewaiting[MAXNETNODES];
@@ -192,23 +184,6 @@ void RegisterNetXCmd(netxcmd_t id, void (*cmd_f)(UINT8 **p, INT32 playernum))
 	listnetxcmd[id] = cmd_f;
 }
 
-void SendNetXCmd(netxcmd_t id, const void *param, size_t nparam)
-{
-	// NET TODO
-}
-
-// splitscreen player
-void SendNetXCmd2(netxcmd_t id, const void *param, size_t nparam)
-{
-	// NET TODO
-}
-
-UINT8 GetFreeXCmdSize(void)
-{
-	// NET TODO
-	return -1;
-}
-
 // Frees all textcmd memory for the specified tic
 static void D_FreeTextcmd(void)
 {
@@ -1727,15 +1702,6 @@ void D_QuitNetGame(void)
 	// abort send/receive of files
 	CloseNetFile();
 
-	if (server)
-	{
-		// NET TODO: Send server shutdown packets to everyone.
-	}
-	else if (servernode > 0 && servernode < MAXNETNODES && nodeingame[(UINT8)servernode]!=0)
-	{
-		// NET TODO: Send client quit packet to server.
-	}
-
 	D_CloseConnection();
 	adminplayer = -1;
 
diff --git a/src/d_enet.c b/src/d_enet.c
index 8bfdf22eb988ed0bd1fefaaca0b0ca8c7e7214f0..8e1fc79e9a0201eb3d0ffa554c4c102bcc1e6630 100644
--- a/src/d_enet.c
+++ b/src/d_enet.c
@@ -1,6 +1,30 @@
+#include <enet/enet.h>
+
 #include "doomdef.h"
 #include "doomstat.h"
 #include "d_enet.h"
+#include "z_zone.h"
+#include "m_menu.h"
+
+UINT8 net_nodecount, net_playercount;
+UINT8 playernode[MAXPLAYERS];
+SINT8 nodetoplayer[MAXNETNODES];
+SINT8 nodetoplayer2[MAXNETNODES]; // say the numplayer for this node if any (splitscreen)
+UINT8 playerpernode[MAXNETNODES]; // used specialy for scplitscreen
+boolean nodeingame[MAXNETNODES]; // set false as nodes leave game
+
+#define NETCHANNELS 4
+
+#define DISCONNECT_SHUTDOWN 1
+
+static ENetHost *ServerHost = NULL,
+	*ClientHost = NULL;
+static ENetPeer *nodetopeer[MAXNETNODES];
+static UINT8 nodeleaving[MAXNETNODES];
+
+typedef struct PeerData_s {
+	UINT8 node;
+} PeerData_t;
 
 boolean Net_GetNetStat(void)
 {
@@ -10,6 +34,70 @@ boolean Net_GetNetStat(void)
 
 void Net_AckTicker(void)
 {
+	ENetEvent e;
+	UINT8 i;
+	PeerData_t *pdata;
+
+	while (enet_host_service(ClientHost, &e, 0) > 0)
+		switch (e.type)
+		{
+		case ENET_EVENT_TYPE_CONNECT:
+			break;
+		case ENET_EVENT_TYPE_DISCONNECT:
+			if (!server)
+			{
+				CL_Reset();
+				D_StartTitle();
+				if (e.data == DISCONNECT_SHUTDOWN)
+					M_StartMessage(M_GetText("Server shut down.\n\nPress ESC\n"), NULL, MM_NOTHING);
+				else
+					M_StartMessage(M_GetText("Disconnected from server.\n\nPress ESC\n"), NULL, MM_NOTHING);
+			}
+			break;
+		case ENET_EVENT_TYPE_RECEIVE:
+			enet_packet_destroy(e.packet);
+			break;
+		default:
+			break;
+		}
+	while (enet_host_service(ServerHost, &e, 0) > 0)
+		switch (e.type)
+		{
+		case ENET_EVENT_TYPE_CONNECT:
+			for (i = 0; i < MAXNETNODES && nodetopeer[i]; i++)
+				;
+			I_Assert(i < MAXNETNODES); // ENet should not be able to send connect events when nodes are full.
+			nodetopeer[i] = e.peer;
+			pdata = ZZ_Alloc(sizeof(*pdata));
+			pdata->node = i;
+			e.peer->data = pdata;
+			break;
+		case ENET_EVENT_TYPE_DISCONNECT:
+			pdata = (PeerData_t *)e.peer->data;
+			if (!nodeleaving[pdata->node])
+			{
+				XBOXSTATIC UINT8 buf[2];
+				buf[0] = nodetoplayer[pdata->node];
+				buf[1] = KICK_MSG_PLAYER_QUIT;
+				SendNetXCmd(XD_KICK, &buf, 2);
+				if (playerpernode[pdata->node] == 2)
+				{
+					buf[0] = nodetoplayer2[pdata->node];
+					SendNetXCmd(XD_KICK, &buf, 2);
+				}
+			}
+			net_nodecount--;
+			nodeleaving[pdata->node] = false;
+			nodetopeer[pdata->node] = NULL;
+			Z_Free(pdata);
+			e.peer->data = NULL;
+			break;
+		case ENET_EVENT_TYPE_RECEIVE:
+			enet_packet_destroy(e.packet);
+			break;
+		default:
+			break;
+		}
 }
 
 boolean Net_AllAckReceived(void)
@@ -19,21 +107,89 @@ boolean Net_AllAckReceived(void)
 
 void D_SetDoomcom(void)
 {
-	//net_nodecount = net_playercount = 0;
+	net_nodecount = 0;
+	net_playercount = 0;
 }
 
+// Initialize network.
+// Returns true if the server is booting up right into a level according to startup args and whatnot.
+// netgame is set to true before this is called if -server was passed.
 boolean D_CheckNetGame(void)
 {
-	multiplayer = false;
-	server = true;
+	if (enet_initialize())
+		I_Error("Failed to initialize ENet.\n");
+	if (netgame)
+	{
+		if (server)
+		{
+			ENetAddress address = { ENET_HOST_ANY, 5029 };
+			ServerHost = enet_host_create(&address, MAXNETNODES, NETCHANNELS, 0, 0);
+			if (!ServerHost)
+				I_Error("ENet failed to open server host. (Check if the port is in use?)");
+		}
+		if (!dedicated)
+		{
+			ClientHost = enet_host_create(NULL, 1, NETCHANNELS, 0, 0);
+			if (!ClientHost)
+				I_Error("ENet failed to initialize client host.");
+		}
+	} else
+		server = true;
+	multiplayer = netgame;
 	D_ClientServerInit();
-	return false;
+	return netgame;
 }
 
 void D_CloseConnection(void)
 {
+	ENetEvent e;
+	if (ServerHost)
+	{
+		UINT8 i;
+		// tell everyone to go away
+		for (i = 0; i < MAXNETNODES; i++)
+			if (nodeingame[i])
+				enet_peer_disconnect(nodetopeer[i], DISCONNECT_SHUTDOWN);
+		// wait for messages to go through.
+		while (enet_host_service(ServerHost, &e, 3000) > 0)
+			switch (e.type)
+			{
+			// i don't care, shut up.
+			case ENET_EVENT_TYPE_RECEIVE:
+				enet_packet_destroy(e.packet);
+				break;
+			// good, go away.
+			case ENET_EVENT_TYPE_DISCONNECT:
+				break;
+			// no, we're shutting down.
+			case ENET_EVENT_TYPE_CONNECT:
+				enet_peer_reset(e.peer);
+				break;
+			}
+		// alright, we're finished.
+		enet_host_destroy(ServerHost);
+	}
+	if (ClientHost)
+	{
+		enet_peer_disconnect(nodetopeer[servernode], 0);
+		while (enet_host_service(ServerHost, &e, 3000) > 0)
+			switch (e.type)
+			{
+			case ENET_EVENT_TYPE_RECEIVE:
+				enet_packet_destroy(e.packet);
+				break;
+			case ENET_EVENT_TYPE_DISCONNECT:
+				break;
+			case ENET_EVENT_TYPE_CONNECT:
+				// how the what ???
+				enet_peer_reset(e.peer);
+				break;
+			}
+		enet_host_destroy(ClientHost);
+	}
 	netgame = false;
 	addedtogame = false;
+	servernode = 0;
 }
 
 void Net_UnAcknowledgPacket(INT32 node)
@@ -42,6 +198,10 @@ void Net_UnAcknowledgPacket(INT32 node)
 
 void Net_CloseConnection(INT32 node)
 {
+	if (nodeleaving[node] || nodetopeer[node] == NULL)
+		return;
+	nodeleaving[node] = true;
+	enet_peer_disconnect(nodetopeer[node], 0);
 }
 
 void Net_AbortPacketType(UINT8 packettype)
@@ -55,3 +215,17 @@ void Net_SendAcks(INT32 node)
 void Net_WaitAllAckReceived(UINT32 timeout)
 {
 }
+
+void SendNetXCmd(netxcmd_t id, const void *param, size_t nparam)
+{
+}
+
+// splitscreen player
+void SendNetXCmd2(netxcmd_t id, const void *param, size_t nparam)
+{
+}
+
+UINT8 GetFreeXCmdSize(void)
+{
+	return -1;
+}
diff --git a/src/d_enet.h b/src/d_enet.h
index dd340ec7e4dd2d7f2d4969f55f696c430b80b984..6cc954a6da29f572434652ca5cbee63d0673f88f 100644
--- a/src/d_enet.h
+++ b/src/d_enet.h
@@ -1,6 +1,13 @@
 // Make sure we allocate a network node for every player, "server full" denials, the Master server heartbeat, and potential RCON connections.
 #define MAXNETNODES MAXPLAYERS+2
 
+extern UINT8 net_nodecount, net_playercount;
+extern UINT8 playernode[MAXPLAYERS];
+extern SINT8 nodetoplayer[MAXNETNODES];
+extern SINT8 nodetoplayer2[MAXNETNODES]; // say the numplayer for this node if any (splitscreen)
+extern UINT8 playerpernode[MAXNETNODES]; // used specialy for scplitscreen
+extern boolean nodeingame[MAXNETNODES]; // set false as nodes leave game
+
 boolean Net_GetNetStat(void);
 void Net_AckTicker(void);
 boolean Net_AllAckReceived(void);
diff --git a/src/d_main.c b/src/d_main.c
index 160249ac7aed2fe956421cde3609cc8b9e4db925..dc981ca32a7d7a8f22bdbfa1b0cbea043c88bd1d 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -592,11 +592,6 @@ void D_SRB2Loop(void)
 		HW3S_BeginFrameUpdate();
 #endif
 
-		// don't skip more than 10 frames at a time
-		// (fadein / fadeout cause massive frame skip!)
-		if (realtics > 8)
-			realtics = 1;
-
 		// process tics (but maybe not if realtic == 0)
 		TryRunTics(realtics);