diff --git a/src/d_main.c b/src/d_main.c
index cf68ba3d2faa336ff28a4b66f1c920d69b3b50b2..a0672bb4e56cd3f14f78f20a3b5c28c979653e3b 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -880,6 +880,40 @@ static inline void D_CleanFile(void)
 	}
 }
 
+///\brief Checks if a netgame URL is being handled, and changes working directory to the EXE's if so.
+///       Done because browsers (at least, Firefox on Windows) launch the game from the browser's directory, which causes problems.
+static void ChangeDirForUrlHandler(void)
+{
+	// URL handlers are opened by web browsers (at least Firefox) from the browser's working directory, not the game's stored directory,
+	// so chdir to that directory unless overridden.
+	if (M_GetUrlProtocolArg() != NULL && !M_CheckParm("-nochdir"))
+	{
+		size_t i;
+
+		CONS_Printf("%s connect links load game files from the SRB2 application's stored directory. Switching to ", SERVER_URL_PROTOCOL);
+		strlcpy(srb2path, myargv[0], sizeof(srb2path));
+
+		// Get just the directory, minus the EXE name
+		for (i = strlen(srb2path)-1; i > 0; i--)
+		{
+			if (srb2path[i] == '/' || srb2path[i] == '\\')
+			{
+				srb2path[i] = '\0';
+				break;
+			}
+		}
+
+		CONS_Printf("%s\n", srb2path);
+
+#if defined (_WIN32)
+		SetCurrentDirectoryA(srb2path);
+#else
+		if (chdir(srb2path) == -1)
+			I_OutputMsg("Couldn't change working directory\n");
+#endif
+	}
+}
+
 // ==========================================================================
 // Identify the SRB2 version, and IWAD file to use.
 // ==========================================================================
@@ -1068,6 +1102,9 @@ void D_SRB2Main(void)
 	// Test Dehacked lists
 	DEH_Check();
 
+	// Netgame URL special case: change working dir to EXE folder.
+	ChangeDirForUrlHandler();
+
 	// identify the main IWAD file to use
 	IdentifyVersion();
 
@@ -1158,7 +1195,7 @@ void D_SRB2Main(void)
 
 	// add any files specified on the command line with -file wadfile
 	// to the wad list
-	if (!(M_CheckParm("-connect") && !M_CheckParm("-server")))
+	if (!((M_GetUrlProtocolArg() || M_CheckParm("-connect")) && !M_CheckParm("-server")))
 	{
 		if (M_CheckParm("-file"))
 		{
@@ -1190,7 +1227,7 @@ void D_SRB2Main(void)
 	M_InitMenuPresTables();
 
 	// init title screen display params
-	if (M_CheckParm("-connect"))
+	if (M_GetUrlProtocolArg() || M_CheckParm("-connect"))
 		F_InitMenuPresValues();
 
 	//---------------------------------------------------- READY TIME
diff --git a/src/doomdef.h b/src/doomdef.h
index 71c8850192d9a94dc00b4dc640b85bf595946d64..5a5c933e373f4c72a0fc1714bc9bda27e86a6132 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -150,6 +150,9 @@ extern char logfilename[1024];
 // Otherwise we can't force updates!
 #endif
 
+/* A custom URL protocol for server links. */
+#define SERVER_URL_PROTOCOL "srb2://"
+
 // Does this version require an added patch file?
 // Comment or uncomment this as necessary.
 #define USE_PATCH_DTA
diff --git a/src/i_tcp.c b/src/i_tcp.c
index 34cad1765df8225cb86fb93d1b781191cd506f2f..373ea1bd03836514cd219030fd72fb1431791126 100644
--- a/src/i_tcp.c
+++ b/src/i_tcp.c
@@ -1423,6 +1423,7 @@ static void SOCK_ClearBans(void)
 boolean I_InitTcpNetwork(void)
 {
 	char serverhostname[255];
+	const char *urlparam = NULL;
 	boolean ret = false;
 	// initilize the OS's TCP/IP stack
 	if (!I_InitTcpDriver())
@@ -1476,10 +1477,12 @@ boolean I_InitTcpNetwork(void)
 
 		ret = true;
 	}
-	else if (M_CheckParm("-connect"))
+	else if ((urlparam = M_GetUrlProtocolArg()) != NULL || M_CheckParm("-connect"))
 	{
-		if (M_IsNextParm())
-			strcpy(serverhostname, M_GetNextParm());
+		if (urlparam != NULL)
+			strlcpy(serverhostname, urlparam, sizeof(serverhostname));
+		else if (M_IsNextParm())
+			strlcpy(serverhostname, M_GetNextParm(), sizeof(serverhostname));
 		else
 			serverhostname[0] = 0; // assuming server in the LAN, use broadcast to detect it
 
diff --git a/src/m_argv.c b/src/m_argv.c
index acb74cff415d2d84058ce5cc81e74a548c0ee936..7d43d96bc62f0c20c2b1f1485809defab2bdda0a 100644
--- a/src/m_argv.c
+++ b/src/m_argv.c
@@ -34,6 +34,25 @@ boolean myargmalloc = false;
 */
 static INT32 found;
 
+/**	\brief Parses a server URL (such as srb2://127.0.0.1) as may be passed to the game via a web browser, etc.
+
+	\return the contents of the URL after the protocol (a server to join), or NULL if not found
+*/
+const char *M_GetUrlProtocolArg(void)
+{
+	INT32 i;
+	const size_t len = strlen(SERVER_URL_PROTOCOL);
+
+	for (i = 1; i < myargc; i++)
+	{
+		if (strlen(myargv[i]) > len && !strnicmp(myargv[i], SERVER_URL_PROTOCOL, len))
+		{
+			return &myargv[i][len];
+		}
+	}
+
+	return NULL;
+}
 
 /**	\brief	The M_CheckParm function
 
diff --git a/src/m_argv.h b/src/m_argv.h
index ca97d9b126106e1e2a6a2d70271acc7ee40ab380..92770f4e9e9b0c58b3a32ad8b752e62cca65b236 100644
--- a/src/m_argv.h
+++ b/src/m_argv.h
@@ -21,6 +21,9 @@ extern INT32 myargc;
 extern char **myargv;
 extern boolean myargmalloc;
 
+// Looks for an srb2:// (or similar) URL passed in as an argument and returns the IP to connect to if found.
+const char *M_GetUrlProtocolArg(void);
+
 // Returns the position of the given parameter in the arg list (0 if not found).
 INT32 M_CheckParm(const char *check);