From 262ed6b7f34440ec292c139e7977c0af18366404 Mon Sep 17 00:00:00 2001
From: LJ Sonic <lamr@free.fr>
Date: Thu, 29 Dec 2022 10:23:12 +0100
Subject: [PATCH] Split packet handling switch into functions

---
 src/d_clisrv.c | 1111 ++++++++++++++++++++++++------------------------
 src/d_netfil.c |   49 ++-
 src/d_netfil.h |    8 +-
 3 files changed, 605 insertions(+), 563 deletions(-)

diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index be10e4f9b..d3f6854c7 100755
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -3677,8 +3677,531 @@ static void HandleServerInfo(SINT8 node)
 }
 #endif
 
-static void PT_WillResendGamestate(void)
+// Helper function for packets that should only be sent by the server
+// If it is NOT from the server, bail out and close the connection!
+static boolean ServerOnly(SINT8 node)
 {
+	if (node == servernode)
+		return false;
+
+	Net_CloseConnection(node);
+	return true;
+}
+
+static void PT_AskInfoViaMS(SINT8 node)
+{
+	Net_CloseConnection(node);
+}
+
+static void PT_TellFilesNeeded(SINT8 node)
+{
+	if (server && serverrunning)
+	{
+		UINT8 *p;
+		INT32 firstfile = netbuffer->u.filesneedednum;
+
+		netbuffer->packettype = PT_MOREFILESNEEDED;
+		netbuffer->u.filesneededcfg.first = firstfile;
+		netbuffer->u.filesneededcfg.more = 0;
+
+		p = PutFileNeeded(firstfile);
+
+		HSendPacket(node, false, 0, p - ((UINT8 *)&netbuffer->u));
+	}
+	else // Shouldn't get this if you aren't the server...?
+		Net_CloseConnection(node);
+}
+
+static void PT_MoreFilesNeeded(SINT8 node)
+{
+	if (server && serverrunning)
+	{ // But wait I thought I'm the server?
+		Net_CloseConnection(node);
+		return;
+	}
+	if (ServerOnly(node))
+		return;
+	if (cl_mode == CL_ASKFULLFILELIST && netbuffer->u.filesneededcfg.first == fileneedednum)
+	{
+		D_ParseFileneeded(netbuffer->u.filesneededcfg.num, netbuffer->u.filesneededcfg.files, netbuffer->u.filesneededcfg.first);
+		if (!netbuffer->u.filesneededcfg.more)
+			cl_lastcheckedfilecount = UINT16_MAX; // Got the whole file list
+	}
+}
+
+static void PT_AskInfo(SINT8 node)
+{
+	if (server && serverrunning)
+	{
+		SV_SendServerInfo(node, (tic_t)LONG(netbuffer->u.askinfo.time));
+		SV_SendPlayerInfo(node); // Send extra info
+	}
+	Net_CloseConnection(node);
+}
+
+// Negative response of client join request
+static void PT_ServerRefuse(SINT8 node)
+{
+	if (server && serverrunning)
+	{ // But wait I thought I'm the server?
+		Net_CloseConnection(node);
+		return;
+	}
+	if (ServerOnly(node))
+		return;
+	if (cl_mode == CL_WAITJOINRESPONSE)
+	{
+		// Save the reason so it can be displayed after quitting the netgame
+		char *reason = strdup(netbuffer->u.serverrefuse.reason);
+		if (!reason)
+			I_Error("Out of memory!\n");
+
+		if (strstr(reason, "Maximum players reached"))
+		{
+			serverisfull = true;
+			//Special timeout for when refusing due to player cap. The client will wait 3 seconds between join requests when waiting for a slot, so we need this to be much longer
+			//We set it back to the value of cv_nettimeout.value in CL_Reset
+			connectiontimeout = NEWTICRATE*7;
+			cl_mode = CL_ASKJOIN;
+			free(reason);
+			return;
+		}
+
+		M_StartMessage(va(M_GetText("Server refuses connection\n\nReason:\n%s"),
+			reason), NULL, MM_NOTHING);
+
+		D_QuitNetGame();
+		CL_Reset();
+		D_StartTitle();
+
+		free(reason);
+
+		// Will be reset by caller. Signals refusal.
+		cl_mode = CL_ABORTED;
+	}
+}
+
+// Positive response of client join request
+static void PT_ServerCFG(SINT8 node)
+{
+	if (server && serverrunning && node != servernode)
+	{ // but wait I thought I'm the server?
+		Net_CloseConnection(node);
+		return;
+	}
+	if (ServerOnly(node))
+		return;
+	/// \note how would this happen? and is it doing the right thing if it does?
+	if (cl_mode != CL_WAITJOINRESPONSE)
+		return;
+
+	if (client)
+	{
+		maketic = gametic = neededtic = (tic_t)LONG(netbuffer->u.servercfg.gametic);
+		G_SetGametype(netbuffer->u.servercfg.gametype);
+		modifiedgame = netbuffer->u.servercfg.modifiedgame;
+		memcpy(server_context, netbuffer->u.servercfg.server_context, 8);
+	}
+
+	nodeingame[(UINT8)servernode] = true;
+	serverplayer = netbuffer->u.servercfg.serverplayer;
+	doomcom->numslots = SHORT(netbuffer->u.servercfg.totalslotnum);
+	mynode = netbuffer->u.servercfg.clientnode;
+	if (serverplayer >= 0)
+		playernode[(UINT8)serverplayer] = servernode;
+
+	if (netgame)
+#ifndef NONET
+		CONS_Printf(M_GetText("Join accepted, waiting for complete game state...\n"));
+#else
+		CONS_Printf(M_GetText("Join accepted, waiting for next level change...\n"));
+#endif
+	DEBFILE(va("Server accept join gametic=%u mynode=%d\n", gametic, mynode));
+
+#ifndef NONET
+	/// \note Wait. What if a Lua script uses some global custom variables synched with the NetVars hook?
+	///       Shouldn't them be downloaded even at intermission time?
+	///       Also, according to HandleConnect, the server will send the savegame even during intermission...
+	if (netbuffer->u.servercfg.gamestate == GS_LEVEL/* ||
+		netbuffer->u.servercfg.gamestate == GS_INTERMISSION*/)
+		cl_mode = CL_DOWNLOADSAVEGAME;
+	else
+#endif
+		cl_mode = CL_CONNECTED;
+}
+
+static void PT_ClientCmd(SINT8 node, INT32 netconsole)
+{
+	tic_t realend, realstart;
+
+	if (client)
+		return;
+
+	// To save bytes, only the low byte of tic numbers are sent
+	// Use ExpandTics to figure out what the rest of the bytes are
+	realstart = ExpandTics(netbuffer->u.clientpak.client_tic, node);
+	realend = ExpandTics(netbuffer->u.clientpak.resendfrom, node);
+
+	if (netbuffer->packettype == PT_CLIENTMIS || netbuffer->packettype == PT_CLIENT2MIS
+		|| netbuffer->packettype == PT_NODEKEEPALIVEMIS
+		|| supposedtics[node] < realend)
+	{
+		supposedtics[node] = realend;
+	}
+	// Discard out of order packet
+	if (nettics[node] > realend)
+	{
+		DEBFILE(va("out of order ticcmd discarded nettics = %u\n", nettics[node]));
+		return;
+	}
+
+	// Update the nettics
+	nettics[node] = realend;
+
+	// Don't do anything for packets of type NODEKEEPALIVE?
+	if (netconsole == -1 || netbuffer->packettype == PT_NODEKEEPALIVE
+		|| netbuffer->packettype == PT_NODEKEEPALIVEMIS)
+		return;
+
+	// As long as clients send valid ticcmds, the server can keep running, so reset the timeout
+	/// \todo Use a separate cvar for that kind of timeout?
+	freezetimeout[node] = I_GetTime() + connectiontimeout;
+
+	// Copy ticcmd
+	G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][netconsole], &netbuffer->u.clientpak.cmd, 1);
+
+	// Check ticcmd for "speed hacks"
+	if (netcmds[maketic%BACKUPTICS][netconsole].forwardmove > MAXPLMOVE || netcmds[maketic%BACKUPTICS][netconsole].forwardmove < -MAXPLMOVE
+		|| netcmds[maketic%BACKUPTICS][netconsole].sidemove > MAXPLMOVE || netcmds[maketic%BACKUPTICS][netconsole].sidemove < -MAXPLMOVE)
+	{
+		CONS_Alert(CONS_WARNING, M_GetText("Illegal movement value received from node %d\n"), netconsole);
+		//D_Clearticcmd(k);
+
+		SendKick(netconsole, KICK_MSG_CON_FAIL);
+		return;
+	}
+
+	// Splitscreen cmd
+	if ((netbuffer->packettype == PT_CLIENT2CMD || netbuffer->packettype == PT_CLIENT2MIS)
+		&& nodetoplayer2[node] >= 0)
+		G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][(UINT8)nodetoplayer2[node]],
+			&netbuffer->u.client2pak.cmd2, 1);
+
+	// Check player consistancy during the level
+	if (realstart <= gametic && realstart + BACKUPTICS - 1 > gametic && gamestate == GS_LEVEL
+		&& consistancy[realstart%BACKUPTICS] != SHORT(netbuffer->u.clientpak.consistancy)
+#ifndef NONET
+		&& !SV_ResendingSavegameToAnyone()
+#endif
+		&& !resendingsavegame[node] && savegameresendcooldown[node] <= I_GetTime())
+	{
+		if (cv_resynchattempts.value)
+		{
+			// Tell the client we are about to resend them the gamestate
+			netbuffer->packettype = PT_WILLRESENDGAMESTATE;
+			HSendPacket(node, true, 0, 0);
+
+			resendingsavegame[node] = true;
+
+			if (cv_blamecfail.value)
+				CONS_Printf(M_GetText("Synch failure for player %d (%s); expected %hd, got %hd\n"),
+					netconsole+1, player_names[netconsole],
+					consistancy[realstart%BACKUPTICS],
+					SHORT(netbuffer->u.clientpak.consistancy));
+			DEBFILE(va("Restoring player %d (synch failure) [%update] %d!=%d\n",
+				netconsole, realstart, consistancy[realstart%BACKUPTICS],
+				SHORT(netbuffer->u.clientpak.consistancy)));
+			return;
+		}
+		else
+		{
+			SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
+			DEBFILE(va("player %d kicked (synch failure) [%u] %d!=%d\n",
+				netconsole, realstart, consistancy[realstart%BACKUPTICS],
+				SHORT(netbuffer->u.clientpak.consistancy)));
+			return;
+		}
+	}
+}
+
+static void PT_TextCmd(SINT8 node, INT32 netconsole)
+{
+	if (client)
+		return;
+
+	// splitscreen special
+	if (netbuffer->packettype == PT_TEXTCMD2)
+		netconsole = nodetoplayer2[node];
+
+	if (netconsole < 0 || netconsole >= MAXPLAYERS)
+		Net_UnAcknowledgePacket(node);
+	else
+	{
+		size_t j;
+		tic_t tic = maketic;
+		UINT8 *textcmd;
+
+		// ignore if the textcmd has a reported size of zero
+		// this shouldn't be sent at all
+		if (!netbuffer->u.textcmd[0])
+		{
+			DEBFILE(va("GetPacket: Textcmd with size 0 detected! (node %u, player %d)\n",
+				node, netconsole));
+			Net_UnAcknowledgePacket(node);
+			return;
+		}
+
+		// ignore if the textcmd size var is actually larger than it should be
+		// BASEPACKETSIZE + 1 (for size) + textcmd[0] should == datalength
+		if (netbuffer->u.textcmd[0] > (size_t)doomcom->datalength-BASEPACKETSIZE-1)
+		{
+			DEBFILE(va("GetPacket: Bad Textcmd packet size! (expected %d, actual %s, node %u, player %d)\n",
+			netbuffer->u.textcmd[0], sizeu1((size_t)doomcom->datalength-BASEPACKETSIZE-1),
+				node, netconsole));
+			Net_UnAcknowledgePacket(node);
+			return;
+		}
+
+		// check if tic that we are making isn't too large else we cannot send it :(
+		// doomcom->numslots+1 "+1" since doomcom->numslots can change within this time and sent time
+		j = software_MAXPACKETLENGTH
+			- (netbuffer->u.textcmd[0]+2+BASESERVERTICSSIZE
+			+ (doomcom->numslots+1)*sizeof(ticcmd_t));
+
+		// search a tic that have enougth space in the ticcmd
+		while ((textcmd = D_GetExistingTextcmd(tic, netconsole)),
+			(TotalTextCmdPerTic(tic) > j || netbuffer->u.textcmd[0] + (textcmd ? textcmd[0] : 0) > MAXTEXTCMD)
+			&& tic < firstticstosend + BACKUPTICS)
+			tic++;
+
+		if (tic >= firstticstosend + BACKUPTICS)
+		{
+			DEBFILE(va("GetPacket: Textcmd too long (max %s, used %s, mak %d, "
+				"tosend %u, node %u, player %d)\n", sizeu1(j), sizeu2(TotalTextCmdPerTic(maketic)),
+				maketic, firstticstosend, node, netconsole));
+			Net_UnAcknowledgePacket(node);
+			return;
+		}
+
+		// Make sure we have a buffer
+		if (!textcmd) textcmd = D_GetTextcmd(tic, netconsole);
+
+		DEBFILE(va("textcmd put in tic %u at position %d (player %d) ftts %u mk %u\n",
+			tic, textcmd[0]+1, netconsole, firstticstosend, maketic));
+
+		M_Memcpy(&textcmd[textcmd[0]+1], netbuffer->u.textcmd+1, netbuffer->u.textcmd[0]);
+		textcmd[0] += (UINT8)netbuffer->u.textcmd[0];
+	}
+}
+
+static void PT_Login(SINT8 node, INT32 netconsole)
+{
+	(void)node;
+
+	if (client)
+		return;
+
+#ifndef NOMD5
+	UINT8 finalmd5[16];/* Well, it's the cool thing to do? */
+
+	if (doomcom->datalength < 16)/* ignore partial sends */
+		return;
+
+	if (!adminpasswordset)
+	{
+		CONS_Printf(M_GetText("Password from %s failed (no password set).\n"), player_names[netconsole]);
+		return;
+	}
+
+	// Do the final pass to compare with the sent md5
+	D_MD5PasswordPass(adminpassmd5, 16, va("PNUM%02d", netconsole), &finalmd5);
+
+	if (!memcmp(netbuffer->u.md5sum, finalmd5, 16))
+	{
+		CONS_Printf(M_GetText("%s passed authentication.\n"), player_names[netconsole]);
+		COM_BufInsertText(va("promote %d\n", netconsole)); // do this immediately
+	}
+	else
+		CONS_Printf(M_GetText("Password from %s failed.\n"), player_names[netconsole]);
+#else
+	(void)netconsole;
+#endif
+}
+
+static void PT_ClientQuit(SINT8 node, INT32 netconsole)
+{
+	if (client)
+		return;
+
+	if (!nodeingame[node])
+	{
+		Net_CloseConnection(node);
+		return;
+	}
+
+	// nodeingame will be put false in the execution of kick command
+	// this allow to send some packets to the quitting client to have their ack back
+	nodewaiting[node] = 0;
+	if (netconsole != -1 && playeringame[netconsole])
+	{
+		UINT8 kickmsg;
+
+		if (netbuffer->packettype == PT_NODETIMEOUT)
+			kickmsg = KICK_MSG_TIMEOUT;
+		else
+			kickmsg = KICK_MSG_PLAYER_QUIT;
+		kickmsg |= KICK_MSG_KEEP_BODY;
+
+		SendKick(netconsole, kickmsg);
+		nodetoplayer[node] = -1;
+
+		if (nodetoplayer2[node] != -1 && nodetoplayer2[node] >= 0
+			&& playeringame[(UINT8)nodetoplayer2[node]])
+		{
+			SendKick(nodetoplayer2[node], kickmsg);
+			nodetoplayer2[node] = -1;
+		}
+	}
+	Net_CloseConnection(node);
+	nodeingame[node] = false;
+}
+
+static void PT_CanReceiveGamestate(SINT8 node)
+{
+#ifndef NONET
+	if (client || sendingsavegame[node])
+		return;
+
+	CONS_Printf(M_GetText("Resending game state to %s...\n"), player_names[nodetoplayer[node]]);
+
+	SV_SendSaveGame(node, true); // Resend a complete game state
+	resendingsavegame[node] = true;
+#else
+	(void)node;
+#endif
+}
+
+static void PT_AskLuaFile(SINT8 node)
+{
+	if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_ASKED)
+		AddLuaFileToSendQueue(node, luafiletransfers->realfilename);
+}
+
+static void PT_HasLuaFile(SINT8 node)
+{
+	if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_SENDING)
+		SV_HandleLuaFileSent(node);
+}
+
+static void PT_ReceivedGamestate(SINT8 node)
+{
+	sendingsavegame[node] = false;
+	resendingsavegame[node] = false;
+	savegameresendcooldown[node] = I_GetTime() + 5 * TICRATE;
+}
+
+static void PT_ServerTics(SINT8 node, INT32 netconsole)
+{
+	UINT8 *pak, *txtpak, numtxtpak;
+	tic_t realend, realstart;
+
+	if (!nodeingame[node])
+	{
+		// Do not remove my own server (we have just get a out of order packet)
+		if (node != servernode)
+		{
+			DEBFILE(va("unknown packet received (%d) from unknown host\n",netbuffer->packettype));
+			Net_CloseConnection(node);
+		}
+		return;
+	}
+
+	// Only accept PT_SERVERTICS from the server.
+	if (node != servernode)
+	{
+		CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_SERVERTICS", node);
+		if (server)
+			SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
+		return;
+	}
+
+	realstart = netbuffer->u.serverpak.starttic;
+	realend = realstart + netbuffer->u.serverpak.numtics;
+
+	txtpak = (UINT8 *)&netbuffer->u.serverpak.cmds[netbuffer->u.serverpak.numslots
+		* netbuffer->u.serverpak.numtics];
+
+	if (realend > gametic + CLIENTBACKUPTICS)
+		realend = gametic + CLIENTBACKUPTICS;
+	cl_packetmissed = realstart > neededtic;
+
+	if (realstart <= neededtic && realend > neededtic)
+	{
+		tic_t i, j;
+		pak = (UINT8 *)&netbuffer->u.serverpak.cmds;
+
+		for (i = realstart; i < realend; i++)
+		{
+			// clear first
+			D_Clearticcmd(i);
+
+			// copy the tics
+			pak = G_ScpyTiccmd(netcmds[i%BACKUPTICS], pak,
+				netbuffer->u.serverpak.numslots*sizeof (ticcmd_t));
+
+			// copy the textcmds
+			numtxtpak = *txtpak++;
+			for (j = 0; j < numtxtpak; j++)
+			{
+				INT32 k = *txtpak++; // playernum
+				const size_t txtsize = txtpak[0]+1;
+
+				if (i >= gametic) // Don't copy old net commands
+					M_Memcpy(D_GetTextcmd(i, k), txtpak, txtsize);
+				txtpak += txtsize;
+			}
+		}
+
+		neededtic = realend;
+	}
+	else
+	{
+		DEBFILE(va("frame not in bound: %u\n", neededtic));
+		/*if (realend < neededtic - 2 * TICRATE || neededtic + 2 * TICRATE < realstart)
+			I_Error("Received an out of order PT_SERVERTICS packet!\n"
+					"Got tics %d-%d, needed tic %d\n\n"
+					"Please report this crash on the Master Board,\n"
+					"IRC or Discord so it can be fixed.\n", (INT32)realstart, (INT32)realend, (INT32)neededtic);*/
+	}
+}
+
+static void PT_Ping(SINT8 node, INT32 netconsole)
+{
+	// Only accept PT_PING from the server.
+	if (node != servernode)
+	{
+		CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_PING", node);
+		if (server)
+			SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
+		return;
+	}
+
+	//Update client ping table from the server.
+	if (client)
+	{
+		UINT8 i;
+		for (i = 0; i < MAXPLAYERS; i++)
+			if (playeringame[i])
+				playerpingtable[i] = (tic_t)netbuffer->u.pingtable[i];
+
+		servermaxping = (tic_t)netbuffer->u.pingtable[MAXPLAYERS];
+	}
+}
+
+static void PT_WillResendGamestate(SINT8 node)
+{
+	(void)node;
+
 #ifndef NONET
 	char tmpsave[256];
 
@@ -3705,19 +4228,12 @@ static void PT_WillResendGamestate(void)
 #endif
 }
 
-static void PT_CanReceiveGamestate(SINT8 node)
+static void PT_SendingLuaFile(SINT8 node)
 {
-#ifndef NONET
-	if (client || sendingsavegame[node])
-		return;
-
-	CONS_Printf(M_GetText("Resending game state to %s...\n"), player_names[nodetoplayer[node]]);
-
-	SV_SendSaveGame(node, true); // Resend a complete game state
-	resendingsavegame[node] = true;
-#else
 	(void)node;
-#endif
+
+	if (client)
+		CL_PrepareDownloadLuaFile();
 }
 
 /** Handles a packet received from a node that isn't in game
@@ -3733,202 +4249,27 @@ static void HandlePacketFromAwayNode(SINT8 node)
 	if (node != servernode)
 		DEBFILE(va("Received packet from unknown host %d\n", node));
 
-// macro for packets that should only be sent by the server
-// if it is NOT from the server, bail out and close the connection!
-#define SERVERONLY \
-			if (node != servernode) \
-			{ \
-				Net_CloseConnection(node); \
-				break; \
-			}
 	switch (netbuffer->packettype)
 	{
-		case PT_ASKINFOVIAMS:
-			Net_CloseConnection(node);
-			break;
-
-		case PT_TELLFILESNEEDED:
-			if (server && serverrunning)
-			{
-				UINT8 *p;
-				INT32 firstfile = netbuffer->u.filesneedednum;
-
-				netbuffer->packettype = PT_MOREFILESNEEDED;
-				netbuffer->u.filesneededcfg.first = firstfile;
-				netbuffer->u.filesneededcfg.more = 0;
-
-				p = PutFileNeeded(firstfile);
-
-				HSendPacket(node, false, 0, p - ((UINT8 *)&netbuffer->u));
-			}
-			else // Shouldn't get this if you aren't the server...?
-				Net_CloseConnection(node);
-			break;
-
-		case PT_MOREFILESNEEDED:
-			if (server && serverrunning)
-			{ // But wait I thought I'm the server?
-				Net_CloseConnection(node);
-				break;
-			}
-			SERVERONLY
-			if (cl_mode == CL_ASKFULLFILELIST && netbuffer->u.filesneededcfg.first == fileneedednum)
-			{
-				D_ParseFileneeded(netbuffer->u.filesneededcfg.num, netbuffer->u.filesneededcfg.files, netbuffer->u.filesneededcfg.first);
-				if (!netbuffer->u.filesneededcfg.more)
-					cl_lastcheckedfilecount = UINT16_MAX; // Got the whole file list
-			}
-			break;
-
-		case PT_ASKINFO:
-			if (server && serverrunning)
-			{
-				SV_SendServerInfo(node, (tic_t)LONG(netbuffer->u.askinfo.time));
-				SV_SendPlayerInfo(node); // Send extra info
-			}
-			Net_CloseConnection(node);
-			break;
-
-		case PT_SERVERREFUSE: // Negative response of client join request
-			if (server && serverrunning)
-			{ // But wait I thought I'm the server?
-				Net_CloseConnection(node);
-				break;
-			}
-			SERVERONLY
-			if (cl_mode == CL_WAITJOINRESPONSE)
-			{
-				// Save the reason so it can be displayed after quitting the netgame
-				char *reason = strdup(netbuffer->u.serverrefuse.reason);
-				if (!reason)
-					I_Error("Out of memory!\n");
-
-				if (strstr(reason, "Maximum players reached"))
-				{
-					serverisfull = true;
-					//Special timeout for when refusing due to player cap. The client will wait 3 seconds between join requests when waiting for a slot, so we need this to be much longer
-					//We set it back to the value of cv_nettimeout.value in CL_Reset
-					connectiontimeout = NEWTICRATE*7;
-					cl_mode = CL_ASKJOIN;
-					free(reason);
-					break;
-				}
-
-				M_StartMessage(va(M_GetText("Server refuses connection\n\nReason:\n%s"),
-					reason), NULL, MM_NOTHING);
-
-				D_QuitNetGame();
-				CL_Reset();
-				D_StartTitle();
-
-				free(reason);
-
-				// Will be reset by caller. Signals refusal.
-				cl_mode = CL_ABORTED;
-			}
-			break;
-
-		case PT_SERVERCFG: // Positive response of client join request
-		{
-			if (server && serverrunning && node != servernode)
-			{ // but wait I thought I'm the server?
-				Net_CloseConnection(node);
-				break;
-			}
-			SERVERONLY
-			/// \note how would this happen? and is it doing the right thing if it does?
-			if (cl_mode != CL_WAITJOINRESPONSE)
-				break;
-
-			if (client)
-			{
-				maketic = gametic = neededtic = (tic_t)LONG(netbuffer->u.servercfg.gametic);
-				G_SetGametype(netbuffer->u.servercfg.gametype);
-				modifiedgame = netbuffer->u.servercfg.modifiedgame;
-				memcpy(server_context, netbuffer->u.servercfg.server_context, 8);
-			}
-
-			nodeingame[(UINT8)servernode] = true;
-			serverplayer = netbuffer->u.servercfg.serverplayer;
-			doomcom->numslots = SHORT(netbuffer->u.servercfg.totalslotnum);
-			mynode = netbuffer->u.servercfg.clientnode;
-			if (serverplayer >= 0)
-				playernode[(UINT8)serverplayer] = servernode;
-
-			if (netgame)
-#ifndef NONET
-				CONS_Printf(M_GetText("Join accepted, waiting for complete game state...\n"));
-#else
-				CONS_Printf(M_GetText("Join accepted, waiting for next level change...\n"));
-#endif
-			DEBFILE(va("Server accept join gametic=%u mynode=%d\n", gametic, mynode));
-
-#ifndef NONET
-			/// \note Wait. What if a Lua script uses some global custom variables synched with the NetVars hook?
-			///       Shouldn't them be downloaded even at intermission time?
-			///       Also, according to HandleConnect, the server will send the savegame even during intermission...
-			if (netbuffer->u.servercfg.gamestate == GS_LEVEL/* ||
-				netbuffer->u.servercfg.gamestate == GS_INTERMISSION*/)
-				cl_mode = CL_DOWNLOADSAVEGAME;
-			else
-#endif
-				cl_mode = CL_CONNECTED;
-			break;
-		}
-
-		// Handled in d_netfil.c
-		case PT_FILEFRAGMENT:
-			if (server)
-			{ // But wait I thought I'm the server?
-				Net_CloseConnection(node);
-				break;
-			}
-			SERVERONLY
-			PT_FileFragment();
-			break;
-
-		case PT_FILEACK:
-			if (server)
-				PT_FileAck();
-			break;
-
-		case PT_FILERECEIVED:
-			if (server)
-				PT_FileReceived();
-			break;
-
-		case PT_REQUESTFILE:
-			if (server)
-			{
-				if (!cv_downloading.value || !PT_RequestFile(node))
-					Net_CloseConnection(node); // close connection if one of the requested files could not be sent, or you disabled downloading anyway
-			}
-			else
-				Net_CloseConnection(node); // nope
-			break;
-
-		case PT_NODETIMEOUT:
-		case PT_CLIENTQUIT:
-			if (server)
-				Net_CloseConnection(node);
-			break;
-
-		case PT_CLIENTCMD:
-			break; // This is not an "unknown packet"
-
-		case PT_SERVERTICS:
-			// Do not remove my own server (we have just get a out of order packet)
-			if (node == servernode)
-				break;
-			/* FALLTHRU */
+		case PT_ASKINFOVIAMS   : PT_AskInfoViaMS   (node    ); break;
+		case PT_TELLFILESNEEDED: PT_TellFilesNeeded(node    ); break;
+		case PT_MOREFILESNEEDED: PT_MoreFilesNeeded(node    ); break;
+		case PT_ASKINFO        : PT_AskInfo        (node    ); break;
+		case PT_SERVERREFUSE   : PT_ServerRefuse   (node    ); break;
+		case PT_SERVERCFG      : PT_ServerCFG      (node    ); break;
+		case PT_FILEFRAGMENT   : PT_FileFragment   (node, -1); break;
+		case PT_FILEACK        : PT_FileAck        (node    ); break;
+		case PT_FILERECEIVED   : PT_FileReceived   (node    ); break;
+		case PT_REQUESTFILE    : PT_RequestFile    (node    ); break;
+		case PT_NODETIMEOUT    : PT_ClientQuit     (node, -1); break;
+		case PT_CLIENTQUIT     : PT_ClientQuit     (node, -1); break;
+		case PT_SERVERTICS     : PT_ServerTics     (node, -1); break;
+		case PT_CLIENTCMD      :                               break; // This is not an "unknown packet"
 
 		default:
 			DEBFILE(va("unknown packet received (%d) from unknown host\n",netbuffer->packettype));
 			Net_CloseConnection(node);
-			break; // Ignore it
-
 	}
-#undef SERVERONLY
 }
 
 /** Handles a packet received from a node that is in game
@@ -3942,13 +4283,6 @@ static void HandlePacketFromAwayNode(SINT8 node)
 static void HandlePacketFromPlayer(SINT8 node)
 {
 	INT32 netconsole;
-	tic_t realend, realstart;
-	UINT8 *pak, *txtpak, numtxtpak;
-#ifndef NOMD5
-	UINT8 finalmd5[16];/* Well, it's the cool thing to do? */
-#endif
-
-	txtpak = NULL;
 
 	if (dedicated && node == 0)
 		netconsole = 0;
@@ -3961,358 +4295,39 @@ static void HandlePacketFromPlayer(SINT8 node)
 
 	switch (netbuffer->packettype)
 	{
-// -------------------------------------------- SERVER RECEIVE ----------
+		// SERVER RECEIVE
 		case PT_CLIENTCMD:
 		case PT_CLIENT2CMD:
 		case PT_CLIENTMIS:
 		case PT_CLIENT2MIS:
 		case PT_NODEKEEPALIVE:
 		case PT_NODEKEEPALIVEMIS:
-			if (client)
-				break;
-
-			// To save bytes, only the low byte of tic numbers are sent
-			// Use ExpandTics to figure out what the rest of the bytes are
-			realstart = ExpandTics(netbuffer->u.clientpak.client_tic, node);
-			realend = ExpandTics(netbuffer->u.clientpak.resendfrom, node);
-
-			if (netbuffer->packettype == PT_CLIENTMIS || netbuffer->packettype == PT_CLIENT2MIS
-				|| netbuffer->packettype == PT_NODEKEEPALIVEMIS
-				|| supposedtics[node] < realend)
-			{
-				supposedtics[node] = realend;
-			}
-			// Discard out of order packet
-			if (nettics[node] > realend)
-			{
-				DEBFILE(va("out of order ticcmd discarded nettics = %u\n", nettics[node]));
-				break;
-			}
-
-			// Update the nettics
-			nettics[node] = realend;
-
-			// Don't do anything for packets of type NODEKEEPALIVE?
-			if (netconsole == -1 || netbuffer->packettype == PT_NODEKEEPALIVE
-				|| netbuffer->packettype == PT_NODEKEEPALIVEMIS)
-				break;
-
-			// As long as clients send valid ticcmds, the server can keep running, so reset the timeout
-			/// \todo Use a separate cvar for that kind of timeout?
-			freezetimeout[node] = I_GetTime() + connectiontimeout;
-
-			// Copy ticcmd
-			G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][netconsole], &netbuffer->u.clientpak.cmd, 1);
-
-			// Check ticcmd for "speed hacks"
-			if (netcmds[maketic%BACKUPTICS][netconsole].forwardmove > MAXPLMOVE || netcmds[maketic%BACKUPTICS][netconsole].forwardmove < -MAXPLMOVE
-				|| netcmds[maketic%BACKUPTICS][netconsole].sidemove > MAXPLMOVE || netcmds[maketic%BACKUPTICS][netconsole].sidemove < -MAXPLMOVE)
-			{
-				CONS_Alert(CONS_WARNING, M_GetText("Illegal movement value received from node %d\n"), netconsole);
-				//D_Clearticcmd(k);
-
-				SendKick(netconsole, KICK_MSG_CON_FAIL);
-				break;
-			}
-
-			// Splitscreen cmd
-			if ((netbuffer->packettype == PT_CLIENT2CMD || netbuffer->packettype == PT_CLIENT2MIS)
-				&& nodetoplayer2[node] >= 0)
-				G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][(UINT8)nodetoplayer2[node]],
-					&netbuffer->u.client2pak.cmd2, 1);
-
-			// Check player consistancy during the level
-			if (realstart <= gametic && realstart + BACKUPTICS - 1 > gametic && gamestate == GS_LEVEL
-				&& consistancy[realstart%BACKUPTICS] != SHORT(netbuffer->u.clientpak.consistancy)
-#ifndef NONET
-				&& !SV_ResendingSavegameToAnyone()
-#endif
-				&& !resendingsavegame[node] && savegameresendcooldown[node] <= I_GetTime())
-			{
-				if (cv_resynchattempts.value)
-				{
-					// Tell the client we are about to resend them the gamestate
-					netbuffer->packettype = PT_WILLRESENDGAMESTATE;
-					HSendPacket(node, true, 0, 0);
-
-					resendingsavegame[node] = true;
-
-					if (cv_blamecfail.value)
-						CONS_Printf(M_GetText("Synch failure for player %d (%s); expected %hd, got %hd\n"),
-							netconsole+1, player_names[netconsole],
-							consistancy[realstart%BACKUPTICS],
-							SHORT(netbuffer->u.clientpak.consistancy));
-					DEBFILE(va("Restoring player %d (synch failure) [%update] %d!=%d\n",
-						netconsole, realstart, consistancy[realstart%BACKUPTICS],
-						SHORT(netbuffer->u.clientpak.consistancy)));
-					break;
-				}
-				else
-				{
-					SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
-					DEBFILE(va("player %d kicked (synch failure) [%u] %d!=%d\n",
-						netconsole, realstart, consistancy[realstart%BACKUPTICS],
-						SHORT(netbuffer->u.clientpak.consistancy)));
-					break;
-				}
-			}
+			PT_ClientCmd(node, netconsole);
 			break;
-		case PT_TEXTCMD2: // splitscreen special
-			netconsole = nodetoplayer2[node];
-			/* FALLTHRU */
-		case PT_TEXTCMD:
-			if (client)
-				break;
+		case PT_TEXTCMD            : PT_TextCmd            (node, netconsole); break;
+		case PT_TEXTCMD2           : PT_TextCmd            (node, netconsole); break;
+		case PT_LOGIN              : PT_Login              (node, netconsole); break;
+		case PT_NODETIMEOUT        : PT_ClientQuit         (node, netconsole); break;
+		case PT_CLIENTQUIT         : PT_ClientQuit         (node, netconsole); break;
+		case PT_CANRECEIVEGAMESTATE: PT_CanReceiveGamestate(node            ); break;
+		case PT_ASKLUAFILE         : PT_AskLuaFile         (node            ); break;
+		case PT_HASLUAFILE         : PT_HasLuaFile         (node            ); break;
+		case PT_RECEIVEDGAMESTATE  : PT_ReceivedGamestate  (node            ); break;
 
-			if (netconsole < 0 || netconsole >= MAXPLAYERS)
-				Net_UnAcknowledgePacket(node);
-			else
-			{
-				size_t j;
-				tic_t tic = maketic;
-				UINT8 *textcmd;
+		// CLIENT RECEIVE
+		case PT_SERVERTICS         : PT_ServerTics         (node, netconsole); break;
+		case PT_PING               : PT_Ping               (node, netconsole); break;
+		case PT_FILEFRAGMENT       : PT_FileFragment       (node, netconsole); break;
+		case PT_FILEACK            : PT_FileAck            (node            ); break;
+		case PT_FILERECEIVED       : PT_FileReceived       (node            ); break;
+		case PT_WILLRESENDGAMESTATE: PT_WillResendGamestate(node            ); break;
+		case PT_SENDINGLUAFILE     : PT_SendingLuaFile     (node            ); break;
+		case PT_SERVERCFG          :                                           break;
 
-				// ignore if the textcmd has a reported size of zero
-				// this shouldn't be sent at all
-				if (!netbuffer->u.textcmd[0])
-				{
-					DEBFILE(va("GetPacket: Textcmd with size 0 detected! (node %u, player %d)\n",
-						node, netconsole));
-					Net_UnAcknowledgePacket(node);
-					break;
-				}
-
-				// ignore if the textcmd size var is actually larger than it should be
-				// BASEPACKETSIZE + 1 (for size) + textcmd[0] should == datalength
-				if (netbuffer->u.textcmd[0] > (size_t)doomcom->datalength-BASEPACKETSIZE-1)
-				{
-					DEBFILE(va("GetPacket: Bad Textcmd packet size! (expected %d, actual %s, node %u, player %d)\n",
-					netbuffer->u.textcmd[0], sizeu1((size_t)doomcom->datalength-BASEPACKETSIZE-1),
-						node, netconsole));
-					Net_UnAcknowledgePacket(node);
-					break;
-				}
-
-				// check if tic that we are making isn't too large else we cannot send it :(
-				// doomcom->numslots+1 "+1" since doomcom->numslots can change within this time and sent time
-				j = software_MAXPACKETLENGTH
-					- (netbuffer->u.textcmd[0]+2+BASESERVERTICSSIZE
-					+ (doomcom->numslots+1)*sizeof(ticcmd_t));
-
-				// search a tic that have enougth space in the ticcmd
-				while ((textcmd = D_GetExistingTextcmd(tic, netconsole)),
-					(TotalTextCmdPerTic(tic) > j || netbuffer->u.textcmd[0] + (textcmd ? textcmd[0] : 0) > MAXTEXTCMD)
-					&& tic < firstticstosend + BACKUPTICS)
-					tic++;
-
-				if (tic >= firstticstosend + BACKUPTICS)
-				{
-					DEBFILE(va("GetPacket: Textcmd too long (max %s, used %s, mak %d, "
-						"tosend %u, node %u, player %d)\n", sizeu1(j), sizeu2(TotalTextCmdPerTic(maketic)),
-						maketic, firstticstosend, node, netconsole));
-					Net_UnAcknowledgePacket(node);
-					break;
-				}
-
-				// Make sure we have a buffer
-				if (!textcmd) textcmd = D_GetTextcmd(tic, netconsole);
-
-				DEBFILE(va("textcmd put in tic %u at position %d (player %d) ftts %u mk %u\n",
-					tic, textcmd[0]+1, netconsole, firstticstosend, maketic));
-
-				M_Memcpy(&textcmd[textcmd[0]+1], netbuffer->u.textcmd+1, netbuffer->u.textcmd[0]);
-				textcmd[0] += (UINT8)netbuffer->u.textcmd[0];
-			}
-			break;
-		case PT_LOGIN:
-			if (client)
-				break;
-
-#ifndef NOMD5
-			if (doomcom->datalength < 16)/* ignore partial sends */
-				break;
-
-			if (!adminpasswordset)
-			{
-				CONS_Printf(M_GetText("Password from %s failed (no password set).\n"), player_names[netconsole]);
-				break;
-			}
-
-			// Do the final pass to compare with the sent md5
-			D_MD5PasswordPass(adminpassmd5, 16, va("PNUM%02d", netconsole), &finalmd5);
-
-			if (!memcmp(netbuffer->u.md5sum, finalmd5, 16))
-			{
-				CONS_Printf(M_GetText("%s passed authentication.\n"), player_names[netconsole]);
-				COM_BufInsertText(va("promote %d\n", netconsole)); // do this immediately
-			}
-			else
-				CONS_Printf(M_GetText("Password from %s failed.\n"), player_names[netconsole]);
-#endif
-			break;
-		case PT_NODETIMEOUT:
-		case PT_CLIENTQUIT:
-			if (client)
-				break;
-
-			// nodeingame will be put false in the execution of kick command
-			// this allow to send some packets to the quitting client to have their ack back
-			nodewaiting[node] = 0;
-			if (netconsole != -1 && playeringame[netconsole])
-			{
-				UINT8 kickmsg;
-
-				if (netbuffer->packettype == PT_NODETIMEOUT)
-					kickmsg = KICK_MSG_TIMEOUT;
-				else
-					kickmsg = KICK_MSG_PLAYER_QUIT;
-				kickmsg |= KICK_MSG_KEEP_BODY;
-
-				SendKick(netconsole, kickmsg);
-				nodetoplayer[node] = -1;
-
-				if (nodetoplayer2[node] != -1 && nodetoplayer2[node] >= 0
-					&& playeringame[(UINT8)nodetoplayer2[node]])
-				{
-					SendKick(nodetoplayer2[node], kickmsg);
-					nodetoplayer2[node] = -1;
-				}
-			}
-			Net_CloseConnection(node);
-			nodeingame[node] = false;
-			break;
-		case PT_CANRECEIVEGAMESTATE:
-			PT_CanReceiveGamestate(node);
-			break;
-		case PT_ASKLUAFILE:
-			if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_ASKED)
-				AddLuaFileToSendQueue(node, luafiletransfers->realfilename);
-			break;
-		case PT_HASLUAFILE:
-			if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_SENDING)
-				SV_HandleLuaFileSent(node);
-			break;
-		case PT_RECEIVEDGAMESTATE:
-			sendingsavegame[node] = false;
-			resendingsavegame[node] = false;
-			savegameresendcooldown[node] = I_GetTime() + 5 * TICRATE;
-			break;
-// -------------------------------------------- CLIENT RECEIVE ----------
-		case PT_SERVERTICS:
-			// Only accept PT_SERVERTICS from the server.
-			if (node != servernode)
-			{
-				CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_SERVERTICS", node);
-				if (server)
-					SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
-				break;
-			}
-
-			realstart = netbuffer->u.serverpak.starttic;
-			realend = realstart + netbuffer->u.serverpak.numtics;
-
-			if (!txtpak)
-				txtpak = (UINT8 *)&netbuffer->u.serverpak.cmds[netbuffer->u.serverpak.numslots
-					* netbuffer->u.serverpak.numtics];
-
-			if (realend > gametic + CLIENTBACKUPTICS)
-				realend = gametic + CLIENTBACKUPTICS;
-			cl_packetmissed = realstart > neededtic;
-
-			if (realstart <= neededtic && realend > neededtic)
-			{
-				tic_t i, j;
-				pak = (UINT8 *)&netbuffer->u.serverpak.cmds;
-
-				for (i = realstart; i < realend; i++)
-				{
-					// clear first
-					D_Clearticcmd(i);
-
-					// copy the tics
-					pak = G_ScpyTiccmd(netcmds[i%BACKUPTICS], pak,
-						netbuffer->u.serverpak.numslots*sizeof (ticcmd_t));
-
-					// copy the textcmds
-					numtxtpak = *txtpak++;
-					for (j = 0; j < numtxtpak; j++)
-					{
-						INT32 k = *txtpak++; // playernum
-						const size_t txtsize = txtpak[0]+1;
-
-						if (i >= gametic) // Don't copy old net commands
-							M_Memcpy(D_GetTextcmd(i, k), txtpak, txtsize);
-						txtpak += txtsize;
-					}
-				}
-
-				neededtic = realend;
-			}
-			else
-			{
-				DEBFILE(va("frame not in bound: %u\n", neededtic));
-				/*if (realend < neededtic - 2 * TICRATE || neededtic + 2 * TICRATE < realstart)
-					I_Error("Received an out of order PT_SERVERTICS packet!\n"
-							"Got tics %d-%d, needed tic %d\n\n"
-							"Please report this crash on the Master Board,\n"
-							"IRC or Discord so it can be fixed.\n", (INT32)realstart, (INT32)realend, (INT32)neededtic);*/
-			}
-			break;
-		case PT_PING:
-			// Only accept PT_PING from the server.
-			if (node != servernode)
-			{
-				CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_PING", node);
-				if (server)
-					SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
-				break;
-			}
-
-			//Update client ping table from the server.
-			if (client)
-			{
-				UINT8 i;
-				for (i = 0; i < MAXPLAYERS; i++)
-					if (playeringame[i])
-						playerpingtable[i] = (tic_t)netbuffer->u.pingtable[i];
-
-				servermaxping = (tic_t)netbuffer->u.pingtable[MAXPLAYERS];
-			}
-
-			break;
-		case PT_SERVERCFG:
-			break;
-		case PT_FILEFRAGMENT:
-			// Only accept PT_FILEFRAGMENT from the server.
-			if (node != servernode)
-			{
-				CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_FILEFRAGMENT", node);
-				if (server)
-					SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
-				break;
-			}
-			if (client)
-				PT_FileFragment();
-			break;
-		case PT_FILEACK:
-			if (server)
-				PT_FileAck();
-			break;
-		case PT_FILERECEIVED:
-			if (server)
-				PT_FileReceived();
-			break;
-		case PT_WILLRESENDGAMESTATE:
-			PT_WillResendGamestate();
-			break;
-		case PT_SENDINGLUAFILE:
-			if (client)
-				CL_PrepareDownloadLuaFile();
-			break;
 		default:
 			DEBFILE(va("UNKNOWN PACKET TYPE RECEIVED %d from host %d\n",
 				netbuffer->packettype, node));
-	} // end switch
+	}
 }
 
 /**	Handles all received packets, if any
diff --git a/src/d_netfil.c b/src/d_netfil.c
index edbef30bb..7c9ed54d8 100644
--- a/src/d_netfil.c
+++ b/src/d_netfil.c
@@ -413,12 +413,17 @@ boolean CL_SendFileRequest(void)
 }
 
 // get request filepak and put it on the send queue
-// returns false if a requested file was not found or cannot be sent
-boolean PT_RequestFile(INT32 node)
+void PT_RequestFile(SINT8 node)
 {
 	UINT8 *p = netbuffer->u.textcmd;
 	UINT8 id;
 
+	if (client || !cv_downloading.value)
+	{
+		Net_CloseConnection(node); // close connection if you are not the server or disabled downloading
+		return;
+	}
+
 	while (p < netbuffer->u.textcmd + MAXTEXTCMD-1) // Don't allow hacked client to overflow
 	{
 		id = READUINT8(p);
@@ -428,11 +433,12 @@ boolean PT_RequestFile(INT32 node)
 		if (!AddFileToSendQueue(node, id))
 		{
 			SV_AbortSendFiles(node);
-			return false; // don't read the rest of the files
+			Net_CloseConnection(node); // close connection if one of the requested files could not be sent
+			return; // don't read the rest of the files
 		}
 	}
 
-	return true; // no problems with any files
+	return; // no problems with any files
 }
 
 /** Checks if the files needed aren't already loaded or on the disk
@@ -1137,13 +1143,15 @@ void FileSendTicker(void)
 	}
 }
 
-void PT_FileAck(void)
+void PT_FileAck(SINT8 node)
 {
 	fileack_pak *packet = &netbuffer->u.fileack;
-	INT32 node = doomcom->remotenode;
 	filetran_t *trans = &transfer[node];
 	INT32 i, j;
 
+	if (client)
+		return;
+
 	// Wrong file id? Ignore it, it's probably a late packet
 	if (!(trans->txlist && packet->fileid == trans->txlist->fileid))
 		return;
@@ -1190,12 +1198,12 @@ void PT_FileAck(void)
 	}
 }
 
-void PT_FileReceived(void)
+void PT_FileReceived(SINT8 node)
 {
-	filetx_t *trans = transfer[doomcom->remotenode].txlist;
+	filetx_t *trans = transfer[node].txlist;
 
-	if (trans && netbuffer->u.filereceived == trans->fileid)
-		SV_EndFileSend(doomcom->remotenode);
+	if (server && trans && netbuffer->u.filereceived == trans->fileid)
+		SV_EndFileSend(node);
 }
 
 static void SendAckPacket(fileack_pak *packet, UINT8 fileid)
@@ -1281,8 +1289,27 @@ void FileReceiveTicker(void)
 	}
 }
 
-void PT_FileFragment(void)
+void PT_FileFragment(SINT8 node, INT32 netconsole)
 {
+	if (nodeingame[node])
+	{
+		// Only accept PT_FILEFRAGMENT from the server.
+		if (node != servernode)
+		{
+			CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_FILEFRAGMENT", node);
+			if (server)
+				SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
+			return;
+		}
+		if (server)
+			return;
+	}
+	else if (server || node != servernode)
+	{
+		Net_CloseConnection(node);
+		return;
+	}
+
 	INT32 filenum = netbuffer->u.filetxpak.fileid;
 	fileneeded_t *file = &fileneeded[filenum];
 	UINT32 fragmentpos = LONG(netbuffer->u.filetxpak.position);
diff --git a/src/d_netfil.h b/src/d_netfil.h
index f778a518f..5c7f4ef49 100644
--- a/src/d_netfil.h
+++ b/src/d_netfil.h
@@ -90,16 +90,16 @@ void AddRamToSendQueue(INT32 node, void *data, size_t size, freemethod_t freemet
 	UINT8 fileid);
 
 void FileSendTicker(void);
-void PT_FileAck(void);
-void PT_FileReceived(void);
+void PT_FileAck(SINT8 node);
+void PT_FileReceived(SINT8 node);
 boolean SendingFile(INT32 node);
 
 void FileReceiveTicker(void);
-void PT_FileFragment(void);
+void PT_FileFragment(SINT8 node, INT32 netconsole);
 
 boolean CL_CheckDownloadable(void);
 boolean CL_SendFileRequest(void);
-boolean PT_RequestFile(INT32 node);
+void PT_RequestFile(SINT8 node);
 
 typedef enum
 {