diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index ec2ce776cd821eebc233b0dc84a9f4c60c73ac97..90f8cfa18a38b607b45ad1fdc484e0cdce17d2f2 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -2673,15 +2673,25 @@ static void Command_ShowBan(void) //Print out ban list
 		CONS_Printf(M_GetText("(empty)\n"));
 }
 
+static boolean bansLoaded = false;
+
 void D_SaveBan(void)
 {
 	FILE *f;
 	size_t i;
-	const char *address, *mask, *reason;
+	const char *address, *mask;
+	const char *username, *reason;
 	const time_t curTime = time(NULL);
 	time_t unbanTime = NO_BAN_TIME;
 	const char *path = va("%s"PATHSEP"%s", srb2home, "ban.txt");
 
+	if (bansLoaded != true)
+	{
+		// You didn't even get to ATTEMPT to load bans.txt.
+		// Don't immediately save nothing over it.
+		return;
+	}
+
 	f = fopen(path, "w");
 
 	if (!f)
@@ -2696,24 +2706,37 @@ void D_SaveBan(void)
 		{
 			unbanTime = I_GetUnbanTime(i);
 		}
+		else
+		{
+			unbanTime = NO_BAN_TIME;
+		}
 
 		if (unbanTime != NO_BAN_TIME && curTime >= unbanTime)
 		{
-			// Don't need to save this one anymore.
+			// This one has served their sentence.
+			// We don't need to save them in the file anymore.
 			continue;
 		}
 
+		mask = NULL;
 		if (!I_GetBanMask || (mask = I_GetBanMask(i)) == NULL)
-			fprintf(f, "%s 0", address);
+			fprintf(f, "%s/0", address);
 		else
-			fprintf(f, "%s %s", address, mask);
+			fprintf(f, "%s/%s", address, mask);
 
 		fprintf(f, " %ld", (long)unbanTime);
 
+		username = NULL;
+		if (I_GetBanUsername && (username = I_GetBanUsername(i)) != NULL)
+			fprintf(f, " \"%s\"", username);
+		else
+			fprintf(f, " \"%s\"", "Direct IP ban");
+
+		reason = NULL;
 		if (I_GetBanReason && (reason = I_GetBanReason(i)) != NULL)
-			fprintf(f, " %s\n", reason);
+			fprintf(f, " \"%s\"\n", reason);
 		else
-			fprintf(f, " %s\n", "No reason given");
+			fprintf(f, " \"%s\"\n", "No reason given");
 	}
 
 	fclose(f);
@@ -2728,14 +2751,21 @@ static void Command_ClearBans(void)
 	D_SaveBan();
 }
 
-static void Ban_Load_File(boolean warning)
+void D_LoadBan(boolean warning)
 {
 	FILE *f;
 	size_t i;
-	const char *address, *mask, *reason;
+	const char *address, *mask;
+	const char *username, *reason;
 	time_t unbanTime = NO_BAN_TIME;
 	char buffer[MAX_WADPATH];
 
+	if (!I_ClearBans)
+		return;
+
+	// We at least attempted loading bans.txt
+	bansLoaded = true;
+
 	f = fopen(va("%s"PATHSEP"%s", srb2home, "ban.txt"), "r");
 
 	if (!f)
@@ -2747,16 +2777,26 @@ static void Ban_Load_File(boolean warning)
 
 	I_ClearBans();
 
-	for (i=0; fgets(buffer, (int)sizeof(buffer), f); i++)
+	for (i = 0; fgets(buffer, (int)sizeof(buffer), f); i++)
 	{
-		address = strtok(buffer, " \t\r\n");
+		address = strtok(buffer, "/\t\r\n");
 		mask = strtok(NULL, " \t\r\n");
-		unbanTime = atoi(strtok(NULL, " \t\r\n"));
-		reason = strtok(NULL, "\r\n");
+
+		unbanTime = atoi(strtok(NULL, " \"\t\r\n"));
+
+		username = strtok(NULL, "\"\t\r\n"); // go until next "
+
+		strtok(NULL, "\"\t\r\n"); // remove first "
+		reason = strtok(NULL, "\"\r\n"); // go until next "
 
 		I_SetBanAddress(address, mask);
+
 		if (I_SetUnbanTime)
 			I_SetUnbanTime(unbanTime);
+
+		if (I_SetBanUsername)
+			I_SetBanUsername(username);
+
 		if (I_SetBanReason)
 			I_SetBanReason(reason);
 	}
@@ -2766,7 +2806,7 @@ static void Ban_Load_File(boolean warning)
 
 static void Command_ReloadBan(void)  //recheck ban.txt
 {
-	Ban_Load_File(true);
+	D_LoadBan(true);
 }
 
 static void Command_connect(void)
@@ -3152,31 +3192,60 @@ static void Command_Ban(void)
 
 static void Command_BanIP(void)
 {
-	if (COM_Argc() < 2)
+	size_t ac = COM_Argc();
+
+	if (ac < 2)
 	{
-		CONS_Printf(M_GetText("banip <ip> <reason>: ban an ip address\n"));
+		CONS_Printf(M_GetText("banip <ip> [<reason>]: ban an ip address\n"));
 		return;
 	}
 
 	if (server) // Only the server can use this, otherwise does nothing.
 	{
-		const char *address = (COM_Argv(1));
-		const char *reason;
+		char *addressInput = Z_StrDup(COM_Argv(1));
 
-		if (COM_Argc() == 2)
-			reason = NULL;
-		else
+		const char *address = NULL;
+		const char *mask = NULL;
+
+		const char *reason = NULL;
+
+		address = strtok(addressInput, "/");
+		mask = strtok(NULL, "");
+
+		if (ac > 2)
+		{
 			reason = COM_Argv(2);
+		}
 
-		if (I_SetBanAddress && I_SetBanAddress(address, NULL))
+		if (I_SetBanAddress && I_SetBanAddress(address, mask))
 		{
 			if (reason)
-				CONS_Printf("Banned IP address %s for: %s\n", address, reason);
+			{
+				CONS_Printf(
+					"Banned IP address %s%s for: %s\n",
+					address,
+					(mask && (strlen(mask) > 0)) ? va("/%s", mask) : "",
+					reason
+				);
+			}
 			else
-				CONS_Printf("Banned IP address %s\n", address);
+			{
+				CONS_Printf(
+					"Banned IP address %s%s\n",
+					address,
+					(mask && (strlen(mask) > 0)) ? va("/%s", mask) : ""
+				);
+			}
+
+			if (I_SetUnbanTime)
+				I_SetUnbanTime(NO_BAN_TIME);
+
+			if (I_SetBanUsername)
+				I_SetBanUsername(NULL);
 
 			if (I_SetBanReason)
 				I_SetBanReason(reason);
+
 			D_SaveBan();
 		}
 		else
@@ -3337,6 +3406,9 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 			}
 			else
 			{
+				if (I_SetBanUsername)
+					I_SetBanUsername(player_names[pnum]);
+
 				if (I_SetBanReason)
 					I_SetBanReason(reason);
 
@@ -3645,7 +3717,7 @@ void D_ClientServerInit(void)
 #ifdef DUMPCONSISTENCY
 	CV_RegisterVar(&cv_dumpconsistency);
 #endif
-	Ban_Load_File(false);
+	D_LoadBan(false);
 #endif
 
 	gametic = 0;
diff --git a/src/d_net.c b/src/d_net.c
index 70086ef2b25e839b44921afb02a1741f1d9d6446..264e7c8bd2a7823238c981faad1a3a4c21bd4252 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -79,9 +79,11 @@ void (*I_ClearBans)(void) = NULL;
 const char *(*I_GetNodeAddress) (INT32 node) = NULL;
 const char *(*I_GetBanAddress) (size_t ban) = NULL;
 const char *(*I_GetBanMask) (size_t ban) = NULL;
+const char *(*I_GetBanUsername) (size_t ban) = NULL;
 const char *(*I_GetBanReason) (size_t ban) = NULL;
 time_t (*I_GetUnbanTime) (size_t ban) = NULL;
 boolean (*I_SetBanAddress) (const char *address, const char *mask) = NULL;
+boolean (*I_SetBanUsername) (const char *username) = NULL;
 boolean (*I_SetBanReason) (const char *reason) = NULL;
 boolean (*I_SetUnbanTime) (time_t timestamp) = NULL;
 boolean *bannednode = NULL;
diff --git a/src/d_net.h b/src/d_net.h
index 607f3e9019f14ce4fb7304187e28203636699798..e267f52683669a9787cb2f0326a1420226e15f92 100644
--- a/src/d_net.h
+++ b/src/d_net.h
@@ -55,6 +55,7 @@ boolean HGetPacket(void);
 void D_SetDoomcom(void);
 #ifndef NONET
 void D_SaveBan(void);
+void D_LoadBan(boolean warning);
 #endif
 boolean D_CheckNetGame(void);
 void D_CloseConnection(void);
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index a192feda7c63ff0f505c3fbb9c2da39368d675df..aadd48aa1445764d8f2e00567e61fbeba2d2a8e9 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1035,7 +1035,16 @@ void D_RegisterClientCommands(void)
   * \sa CleanupPlayerName, SetPlayerName, Got_NameAndColor
   * \author Graue <graue@oceanbase.org>
   */
-static boolean IsNameGood(char *name, INT32 playernum)
+
+static boolean AllowedPlayerNameChar(char ch)
+{
+	if (!isprint(ch) || ch == ';' || ch == '"' || (UINT8)(ch) >= 0x80)
+		return false;
+
+	return true;
+}
+
+boolean EnsurePlayerNameIsGood(char *name, INT32 playernum)
 {
 	INT32 ix;
 
@@ -1056,7 +1065,7 @@ static boolean IsNameGood(char *name, INT32 playernum)
 	// Also, anything over 0x80 is disallowed too, since compilers love to
 	// differ on whether they're printable characters or not.
 	for (ix = 0; name[ix] != '\0'; ix++)
-		if (!isprint(name[ix]) || name[ix] == ';' || (UINT8)(name[ix]) >= 0x80)
+		if (!AllowedPlayerNameChar(name[ix]))
 			return false;
 
 	// Check if a player is currently using the name, case-insensitively.
@@ -1142,6 +1151,16 @@ static void CleanupPlayerName(INT32 playernum, const char *newname)
 
 		tmpname = p;
 
+		do
+		{
+			if (!AllowedPlayerNameChar(*p))
+				break;
+		}
+		while (*++p) ;
+
+		if (*p)/* bad char found */
+			break;
+
 		// Remove trailing spaces.
 		p = &tmpname[strlen(tmpname)-1]; // last character
 		while (*p == ' ' && p >= tmpname)
diff --git a/src/i_net.h b/src/i_net.h
index 9a2e11f4d7fe0fb2c3d0c7f530cb7996e92d5072..155935f5b7df7d23f9d35c37cb4a51ecf23f5871 100644
--- a/src/i_net.h
+++ b/src/i_net.h
@@ -147,9 +147,11 @@ extern void (*I_ClearBans)(void);
 extern const char *(*I_GetNodeAddress) (INT32 node);
 extern const char *(*I_GetBanAddress) (size_t ban);
 extern const char *(*I_GetBanMask) (size_t ban);
+extern const char *(*I_GetBanUsername) (size_t ban);
 extern const char *(*I_GetBanReason) (size_t ban);
 extern time_t (*I_GetUnbanTime) (size_t ban);
 extern boolean (*I_SetBanAddress) (const char *address,const char *mask);
+extern boolean (*I_SetBanUsername) (const char *username);
 extern boolean (*I_SetBanReason) (const char *reason);
 extern boolean (*I_SetUnbanTime) (time_t timestamp);
 extern boolean *bannednode;
diff --git a/src/i_tcp.c b/src/i_tcp.c
index f425bbce69583f337d56dd02ed20f1fb66228e7d..25720b8e1efe024fe88681fbcbb39f5d7f7edf1c 100644
--- a/src/i_tcp.c
+++ b/src/i_tcp.c
@@ -235,6 +235,7 @@ typedef struct
 {
 	mysockaddr_t address;
 	UINT8 mask;
+	char *username;
 	char *reason;
 	time_t timestamp;
 } banned_t;
@@ -500,6 +501,18 @@ static const char *SOCK_GetBanMask(size_t ban)
 	return NULL;
 }
 
+static const char *SOCK_GetBanUsername(size_t ban)
+{
+#ifdef NONET
+	(void)ban;
+	return NULL;
+#else
+	if (ban >= numbans)
+		return NULL;
+	return banned[ban].username;
+#endif
+}
+
 static const char *SOCK_GetBanReason(size_t ban)
 {
 #ifdef NONET
@@ -1564,6 +1577,11 @@ static boolean SOCK_SetBanAddress(const char *address, const char *mask)
 			banned[ban].mask = 128;
 #endif
 
+		// Set defaults, in case anything funny happens.
+		SOCK_SetBanUsername(NULL);
+		SOCK_SetBanReason(NULL);
+		SOCK_SetUnbanTime(NO_BAN_TIME);
+
 		runp = runp->ai_next;
 	}
 
@@ -1573,17 +1591,45 @@ static boolean SOCK_SetBanAddress(const char *address, const char *mask)
 #endif
 }
 
+static boolean SOCK_SetBanUsername(const char *username)
+{
+#ifdef NONET
+	(void)username;
+	return false;
+#else
+	if (username == NULL || strlen(username) == 0)
+	{
+		username = "Direct IP ban";
+	}
+
+	if (banned[numbans - 1].username)
+	{
+		Z_Free(banned[numbans - 1].username);
+		banned[numbans - 1].username = NULL;
+	}
+
+	banned[numbans - 1].username = Z_StrDup(username);
+	return true;
+#endif
+}
+
 static boolean SOCK_SetBanReason(const char *reason)
 {
 #ifdef NONET
 	(void)reason;
 	return false;
 #else
-	if (!reason)
+	if (reason == NULL || strlen(reason) == 0)
 	{
 		reason = "No reason given";
 	}
 
+	if (banned[numbans - 1].reason)
+	{
+		Z_Free(banned[numbans - 1].reason);
+		banned[numbans - 1].reason = NULL;
+	}
+
 	banned[numbans - 1].reason = Z_StrDup(reason);
 	return true;
 #endif
@@ -1696,9 +1742,11 @@ boolean I_InitTcpNetwork(void)
 	I_GetNodeAddress = SOCK_GetNodeAddress;
 	I_GetBanAddress = SOCK_GetBanAddress;
 	I_GetBanMask = SOCK_GetBanMask;
+	I_GetBanUsername = SOCK_GetBanUsername;
 	I_GetBanReason = SOCK_GetBanReason;
 	I_GetUnbanTime = SOCK_GetUnbanTime;
 	I_SetBanAddress = SOCK_SetBanAddress;
+	I_SetBanUsername = SOCK_SetBanUsername;
 	I_SetBanReason = SOCK_SetBanReason;
 	I_SetUnbanTime = SOCK_SetUnbanTime;
 	bannednode = SOCK_bannednode;