diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c
index 02cab5f64..7e0fbc5ab 100644
--- a/engine/client/cl_parse.c
+++ b/engine/client/cl_parse.c
@@ -1024,30 +1024,30 @@ int CL_LoadModels(int stage, qboolean dontactuallyload)
 	{
 		char *s;
 		qboolean anycsqc;
+		char *endptr;
+		unsigned int chksum;
 #ifdef _DEBUG
 		anycsqc = true;
 #else
 		anycsqc = atoi(Info_ValueForKey(cl.serverinfo, "anycsqc"));
 #endif
+		if (cls.demoplayback)
+			anycsqc = true;
 		s = Info_ValueForKey(cl.serverinfo, "*csprogs");
-		if (anycsqc || *s || cls.demoplayback)	//only allow csqc if the server says so, and the 'checksum' matches.
+		chksum = strtoul(s, &endptr, 0);
+		if (*endptr)
 		{
-			char *endptr;
-			unsigned int chksum = strtoul(s, &endptr, 0);
-			if (*endptr)
-			{
-				Con_Printf("corrupt *csprogs key in serverinfo\n");
-				anycsqc = true;
-				chksum = 0;
-			}
-			SCR_SetLoadingFile("csprogs");
-			if (!CSQC_Init(anycsqc, chksum))
-			{
-				Sbar_Start();	//try and start this before we're actually on the server,
-								//this'll stop the mod from sending so much stuffed data at us, whilst we're frozen while trying to load.
-								//hopefully this'll make it more robust.
-								//csqc is expected to use it's own huds, or to run on decent servers. :p
-			}
+			Con_Printf("corrupt *csprogs key in serverinfo\n");
+			anycsqc = true;
+			chksum = 0;
+		}
+		SCR_SetLoadingFile("csprogs");
+		if (!CSQC_Init(anycsqc, *s?true:false, chksum))
+		{
+			Sbar_Start();	//try and start this before we're actually on the server,
+							//this'll stop the mod from sending so much stuffed data at us, whilst we're frozen while trying to load.
+							//hopefully this'll make it more robust.
+							//csqc is expected to use it's own huds, or to run on decent servers. :p
 		}
 		endstage();
 	}
@@ -1285,7 +1285,6 @@ Sound_NextDownload
 */
 void Sound_CheckDownloads (void)
 {
-	char	*s;
 	int		i;
 
 
@@ -1294,6 +1293,7 @@ void Sound_CheckDownloads (void)
 #ifdef CSQC_DAT
 //	if (cls.fteprotocolextensions & PEXT_CSQC)
 	{
+		char	*s;
 		s = Info_ValueForKey(cl.serverinfo, "*csprogs");
 		if (*s)	//only allow csqc if the server says so, and the 'checksum' matches.
 		{
@@ -2843,8 +2843,7 @@ void CLNQ_ParseServerData(void)		//Doesn't change gamedir - use with caution.
 
 #ifdef PEXT_CSQC
 	CSQC_Shutdown();
-	if (cls.demoplayback)
-		CSQC_Init(true, 0);
+	CSQC_Init(cls.demoplayback, false, 0);
 #endif
 }
 void CLNQ_SignonReply (void)
@@ -2881,10 +2880,7 @@ Con_DPrintf ("CL_SignonReply: %i\n", cls.signon);
 			{
 				char *s;
 				s = Info_ValueForKey(cl.serverinfo, "*csprogs");
-				if (*s)
-					CSQC_Init(false, atoi(s));
-				else
-					CSQC_Shutdown();
+				CSQC_Init(false, *s?true:false, atoi(s));
 			}
 #endif
 		}
@@ -4786,18 +4782,15 @@ void CL_PrintChat(player_info_t *plr, char *rawmsg, char *msg, int plrflags)
 
 		if (plrflags & (TPM_TEAM|TPM_OBSERVEDTEAM)) // for team chat don't highlight the name, just the brackets
 		{
-			// color is reset every printf so we're safe here
-			Q_strncatz(fullchatmessage, va("\1(%s^%c", name_coloured?"":"^m", c), sizeof(fullchatmessage));
-			Q_strncatz(fullchatmessage, va("%s%s^d",  name_coloured?"^m":"", name), sizeof(fullchatmessage));
-			Q_strncatz(fullchatmessage, va("%s^%c)", name_coloured?"^m":"", c), sizeof(fullchatmessage));
+			Q_strncatz(fullchatmessage, va("\1(^[%s%s^d\\player\\%i^])", name_coloured?"^m":"", name, plr-cl.players), sizeof(fullchatmessage));
 		}
 		else if (cl_standardchat.ival)
 		{
-			Q_strncatz(fullchatmessage, va("\1%s", name), sizeof(fullchatmessage));
+			Q_strncatz(fullchatmessage, va("\1^[%s%s^d\\player\\%i^]", name_coloured?"^m":"", name, plr-cl.players), sizeof(fullchatmessage));
 		}
 		else
 		{
-			Q_strncatz(fullchatmessage, va("\1%s^%c%s^d", name_coloured?"":"^m", c, name), sizeof(fullchatmessage));
+			Q_strncatz(fullchatmessage, va("\1^[%s^%c%s^d\\player\\%i^]", name_coloured?"^m":"", c, name, plr-cl.players), sizeof(fullchatmessage));
 		}
 
 		if (!memessage)
@@ -4935,7 +4928,7 @@ void CL_PrintStandardMessage(char *msg, int printlevel)
 				c = '0' + CL_PlayerColor(p, &coloured);
 
 			// print name
-			Q_strncatz(fullmessage, va("%s^%c%s^7%s", coloured?"^m":"", c, name, coloured?"^m":""), sizeof(fullmessage));
+			Q_strncatz(fullmessage, va("^[%s^%c%s^d\\player\\%i^]", coloured?"^m":"", c, name, p - cl.players), sizeof(fullmessage));
 			break;
 		}
 	}
diff --git a/engine/client/client.h b/engine/client/client.h
index e15793116..c6e7e3954 100644
--- a/engine/client/client.h
+++ b/engine/client/client.h
@@ -1075,7 +1075,8 @@ char *CG_GetConfigString(int num);
 #ifdef CSQC_DAT
 qboolean CSQC_Inited(void);
 void CSQC_RendererRestarted(void);
-qboolean CSQC_Init (qboolean anycsqc, unsigned int checksum);
+qboolean CSQC_Init (qboolean anycsqc, qboolean csdatenabled, unsigned int checksum);
+qboolean CSQC_ConsoleLink(char *text, char *info);
 void CSQC_RegisterCvarsAndThings(void);
 qboolean CSQC_DrawView(void);
 void CSQC_Shutdown(void);
diff --git a/engine/client/console.c b/engine/client/console.c
index f6b36f6bb..6191edcfe 100644
--- a/engine/client/console.c
+++ b/engine/client/console.c
@@ -848,7 +848,7 @@ int Con_DrawInput (int left, int right, int y)
 	text[key_linepos] = 0;
 	cursor = COM_ParseFunString(CON_WHITEMASK, text, maskedtext, sizeof(maskedtext) - sizeof(maskedtext[0]), true);
 	text[key_linepos] = i;
-	endmtext = COM_ParseFunString(CON_WHITEMASK, text, maskedtext, sizeof(maskedtext) - sizeof(maskedtext[0]), true);
+	endmtext = COM_ParseFunString(CON_WHITEMASK, text+key_linepos, cursor, ((char*)maskedtext)+sizeof(maskedtext) - (char*)(cursor+1), true);
 
 	endmtext[1] = 0;
 
@@ -890,11 +890,11 @@ int Con_DrawInput (int left, int right, int y)
 #endif
 		cursorframe = ((int)(realtime*con_cursorspeed)&1);
 
-	for (lhs = 0, i = key_linepos-1; i >= 0; i--)
+	for (lhs = 0, i = cursor - maskedtext-1; i >= 0; i--)
 	{
 		lhs += Font_CharWidth(maskedtext[i]);
 	}
-	for (rhs = 0, i = key_linepos+1; maskedtext[i]; i++)
+	for (rhs = 0, i = cursor - maskedtext; maskedtext[i]; i++)
 	{
 		rhs += Font_CharWidth(maskedtext[i]);
 	}
@@ -1575,11 +1575,12 @@ void Con_DrawConsole (int lines, qboolean noback)
 
 
 
-char *Con_CopyConsole(void)
+char *Con_CopyConsole(qboolean nomarkup)
 {
 	conchar_t *cur;
 	conline_t *l;
 	conchar_t *lend;
+	conchar_t col = CON_WHITEMASK;
 	char *result;
 	int outlen, maxlen;
 	int finalendoffset;
@@ -1590,6 +1591,9 @@ char *Con_CopyConsole(void)
 	maxlen = 1024*1024;
 	result = Z_Malloc(maxlen+1);
 
+//	for (cur = (conchar_t*)(selstartline+1), finalendoffset = 0; cur < (conchar_t*)(selstartline+1) + selstartline->length; cur++, finalendoffset++)
+//		result[finalendoffset] = *cur & 0xffff;
+
 	l = selstartline;
 	cur = (conchar_t*)(l+1) + selstartoffset;
 	finalendoffset = selendoffset;
@@ -1603,21 +1607,48 @@ char *Con_CopyConsole(void)
 			while (cur > (conchar_t*)(l+1))
 			{
 				cur--;
-				if ((*cur & 0xff) == ' ')
+				if ((*cur & CON_CHARMASK) == ' ')
 				{
 					cur++;
 					break;
 				}
+				if (*cur == CON_LINKSTART)
+					break;
 			}
 			while (finalendoffset < selendline->length)
 			{
-				if ((((conchar_t*)(l+1))[finalendoffset] & 0xff) != ' ')
+				if ((((conchar_t*)(l+1))[finalendoffset] & CON_CHARMASK) != ' ')
 					finalendoffset++;
 				else
 					break;
 			}
 		}
 	}
+	
+	//scan backwards to find any link enclosure
+	for(lend = cur; lend >= (conchar_t*)(l+1); lend--)
+	{
+		if (*lend == CON_LINKSTART)
+		{
+			//found one
+			cur = lend;
+			break;
+		}
+		if (*lend == CON_LINKEND)
+		{
+			//some other link ended here. don't use its start.
+			break;
+		}
+	}
+	//scan forwards to find the end of the selected link
+	for(lend = (conchar_t*)(selendline+1) + finalendoffset; lend < (conchar_t*)(selendline+1) + selendline->length; lend++)
+	{
+		if (*lend == CON_LINKEND)
+		{
+			finalendoffset = lend+1 - (conchar_t*)(selendline+1);
+			break;
+		}
+	}
 
 	outlen = 0;
 	for (;;)
@@ -1626,12 +1657,8 @@ char *Con_CopyConsole(void)
 			lend = (conchar_t*)(l+1) + finalendoffset;
 		else
 			lend = (conchar_t*)(l+1) + l->length;
-		while (cur < lend)
-		{
-			if (outlen == maxlen)
-				break;
-			result[outlen++] = *cur++;
-		}
+
+		outlen = COM_DeFunString(cur, lend, result + outlen, maxlen - outlen, nomarkup) - result;
 
 		if (l == selendline)
 			break;
@@ -1642,6 +1669,9 @@ char *Con_CopyConsole(void)
 			Con_Printf("Error: Bad console buffer\n");
 			break;
 		}
+
+		if (outlen+3 > maxlen)
+			break;
 #ifdef _WIN32
 		result[outlen++] = '\r';
 #endif
diff --git a/engine/client/keys.c b/engine/client/keys.c
index 1e84cbe44..54dc447b0 100644
--- a/engine/client/keys.c
+++ b/engine/client/keys.c
@@ -409,6 +409,12 @@ qboolean Key_GetConsoleSelectionBox(int *sx, int *sy, int *ex, int *ey)
 			con_mousedown[1] -= 8;
 			con_current->display = con_current->display->newer;
 		}
+
+		*sx = mousecursor_x;
+		*sy = mousecursor_y;
+		*ex = mousecursor_x;
+		*ey = mousecursor_y;
+		return true;
 	}
 	else if (con_mousedown[2] == 2)
 	{
@@ -421,16 +427,253 @@ qboolean Key_GetConsoleSelectionBox(int *sx, int *sy, int *ex, int *ey)
 	return false;
 }
 
+/*insert the given text at the console input line at the current cursor pos*/
+void Key_ConsoleInsert(char *instext)
+{
+	int i;
+	int len;
+	len = strlen(instext);
+	if (len + strlen(key_lines[edit_line]) > MAXCMDLINE - 1)
+		len = MAXCMDLINE - 1 - strlen(key_lines[edit_line]);
+	if (len > 0)
+	{	// insert the string
+		memmove (key_lines[edit_line] + key_linepos + len,
+			key_lines[edit_line] + key_linepos, strlen(key_lines[edit_line]) - key_linepos + 1);
+		memcpy (key_lines[edit_line] + key_linepos, instext, len);
+		for (i = 0; i < len; i++)
+		{
+			if (key_lines[edit_line][key_linepos+i] == '\r')
+				key_lines[edit_line][key_linepos+i] = ' ';
+			else if (key_lines[edit_line][key_linepos+i] == '\n')
+				key_lines[edit_line][key_linepos+i] = ';';
+		}
+		key_linepos += len;
+	}
+}
+
 void Key_ConsoleRelease(int key, int unicode)
 {
+	extern int mousecursor_x, mousecursor_y;
+	char *buffer;
 	if (key == K_MOUSE1)
+	{
 		con_mousedown[2] = 0;
+		if (abs(con_mousedown[0] - mousecursor_x) < 5 && abs(con_mousedown[1] - mousecursor_y) < 5)
+		{
+			buffer = Con_CopyConsole(false);
+			if (!buffer)
+				return;
+			if (keydown[K_SHIFT])
+			{
+				int len;
+				len = strlen(buffer);
+				//strip any trailing dots/elipsis
+				while (len > 1 && !strcmp(buffer+len-1, "."))
+				{
+					len-=1;
+					buffer[len] = 0;
+				}
+				//strip any enclosing quotes
+				while (*buffer == '\"' && len > 2 && !strcmp(buffer+len-1, "\""))
+				{
+					len-=2;
+					memmove(buffer, buffer+1, len);
+					buffer[len] = 0;
+				}
+				Key_ConsoleInsert(buffer);
+			}
+			else
+			{
+				if (buffer[0] == '^' && buffer[1] == '[')
+				{
+					//looks like it might be a link!
+					char *end = NULL;
+					char *info;
+					for (info = buffer + 2; *info; )
+					{
+						if (info[0] == '^' && info[1] == ']')
+						{
+							break;
+						}
+						if (*info == '\\')
+							break;
+						else if (info[0] == '^' && info[1] == '^')
+							info+=2;
+						else
+							info++;
+					}
+					for(end = info; *end; )
+					{
+						if (end[0] == '^' && end[1] == ']')
+						{
+							char *c;
+							//okay, its a valid link that they clicked
+							*end = 0;
+#ifdef CSQC_DAT
+							if (!CSQC_ConsoleLink(buffer+2, info))
+#endif
+							{
+								/*the engine supports specific default links*/
+								/*we don't support everything. a: there's no point. b: unbindall links are evil.*/
+								c = Info_ValueForKey(info, "player");
+								if (*c)
+								{
+									unsigned int player = atoi(c);
+									int i;
+									if (player >= MAX_CLIENTS)
+										break;
+
+									c = Info_ValueForKey(info, "action");
+									if (*c)
+									{
+										if (!strcmp(c, "mute"))
+										{
+											if (!cl.players[player].vignored)
+											{
+												cl.players[player].vignored = true;
+												Con_Printf("^[%s\\player\\%i^] muted\n", cl.players[player].name, player);
+											}
+											else
+											{
+												cl.players[player].vignored = false;
+												Con_Printf("^[%s\\player\\%i^] unmuted\n", cl.players[player].name, player);
+											}
+										}
+										else if (!strcmp(c, "ignore"))
+										{
+											if (!cl.players[player].ignored)
+											{
+												cl.players[player].ignored = true;
+												cl.players[player].vignored = true;
+												Con_Printf("^[%s\\player\\%i^] ignored\n", cl.players[player].name, player);
+											}
+											else
+											{
+												cl.players[player].ignored = false;
+												cl.players[player].vignored = false;
+												Con_Printf("^[%s\\player\\%i^] unignored\n", cl.players[player].name, player);
+											}
+										}
+										else if (!strcmp(c, "kick"))
+										{
+#ifndef CLIENTONLY
+											if (sv.active)
+											{
+												//use the q3 command, because we can.
+												Cbuf_AddText(va("\nclientkick %i\n", player), RESTRICT_LOCAL);
+											}
+											else
+#endif
+												Cbuf_AddText(va("\nrcon kick %s\n", cl.players[player].name), RESTRICT_LOCAL);
+										}
+										else if (!strcmp(c, "ban"))
+										{
+#ifndef CLIENTONLY
+											if (sv.active)
+											{
+												//use the q3 command, because we can.
+												Cbuf_AddText(va("\nbanname %s QuickBan\n", cl.players[player].name), RESTRICT_LOCAL);
+											}
+											else
+#endif
+												Cbuf_AddText(va("\nrcon banname %s QuickBan\n", cl.players[player].name), RESTRICT_LOCAL);
+										}
+										break;
+									}
+
+									Con_Printf("^m#^m ^[%s\\player\\%i^]: %if %ims", cl.players[player].name, player, cl.players[player].frags, cl.players[player].ping);
+
+									for (i = 0; i < cl.splitclients; i++)
+									{
+										if (cl.playernum[i] == player)
+											break;
+									}
+									if (i == cl.splitclients)
+									{
+										extern cvar_t rcon_password;
+										if (cls.protocol == CP_QUAKEWORLD && strcmp(cl.players[cl.playernum[0]].team, cl.players[player].team))
+											Con_Printf(" ^[[Join Team %s]\\cmd\\setinfo team %s^]", cl.players[player].team, cl.players[player].team);
+										Con_Printf(" ^[%sgnore\\player\\%i\\action\\ignore^]", cl.players[player].ignored?"Uni":"I", player);
+//										if (cl_voip_play.ival)
+											Con_Printf(" ^[%sute\\player\\%i\\action\\mute^]", cl.players[player].vignored?"Unm":"M",  player);
+
+										if (*rcon_password.string
+#ifndef CLIENTONLY
+											|| (sv.state && svs.clients[player].netchan.remote_address.type != NA_LOOPBACK)
+#endif
+											)
+										{
+											Con_Printf(" ^[Kick\\player\\%i\\action\\kick^]", player);
+											Con_Printf(" ^[Ban\\player\\%i\\action\\ban^]", player);
+										}
+									}
+									else
+									{
+										char cmdprefix[6];
+										snprintf(cmdprefix, sizeof(cmdprefix), "%i ", i);
+
+										//hey look! its you!
+										Con_Printf(" ^[Suicide\\cmd\\kill^]");
+#ifndef CLIENTONLY
+										if (!sv.state)
+											Con_Printf(" ^[Disconnect\\cmd\\disconnect^]");
+										if (cls.allow_cheats || (sv.state && sv.allocated_client_slots == 1))
+#else
+										Con_Printf(" ^[Disconnect\\cmd\\disconnect^]");
+										if (cls.allow_cheats)
+#endif
+										{
+											Con_Printf(" ^[Noclip\\cmd\\noclip^]");
+											Con_Printf(" ^[Fly\\cmd\\fly^]");
+											Con_Printf(" ^[God\\cmd\\god^]");
+											Con_Printf(" ^[Give\\impulse\\9^]");
+										}
+									}
+									Con_Printf("\r");
+									break;
+								}
+								c = Info_ValueForKey(info, "connect");
+								if (*c && !strchr(c, ';') && !strchr(c, '\n'))
+								{
+									Cbuf_AddText(va("\nconnect %s\n", c), RESTRICT_LOCAL);
+									break;
+								}
+								c = Info_ValueForKey(info, "qtv");
+								if (*c && !strchr(c, ';') && !strchr(c, '\n'))
+								{
+									Cbuf_AddText(va("\nqtvplay %s\n", c), RESTRICT_LOCAL);
+									break;
+								}
+								c = Info_ValueForKey(info, "cmd");
+								if (*c && !strchr(c, ';') && !strchr(c, '\n'))
+								{
+									Cbuf_AddText(va("\ncmd %s\n", c), RESTRICT_LOCAL);
+									break;
+								}
+								c = Info_ValueForKey(info, "impulse");
+								if (*c && !strchr(c, ';') && !strchr(c, '\n'))
+								{
+									Cbuf_AddText(va("\nimpulse %s\n", c), RESTRICT_LOCAL);
+									break;
+								}
+							}
+
+							break;
+						}
+						if (end[0] == '^' && end[1] == '^')
+							end+=2;
+						else
+							end++;
+					}
+				}
+			}
+			Z_Free(buffer);
+		}
+	}
 	if (key == K_MOUSE2 && con_mousedown[2] == 2)
 	{
-		extern int mousecursor_x, mousecursor_y;
-		char *buffer;
 		con_mousedown[2] = 0;
-		buffer = Con_CopyConsole();
+		buffer = Con_CopyConsole(true);	//don't keep markup if we're copying to the clipboard
 		if (!buffer)
 			return;
 		Sys_SaveClipboard(buffer);
@@ -439,6 +682,91 @@ void Key_ConsoleRelease(int key, int unicode)
 	}
 }
 
+//move the cursor one char to the left. cursor must be within the 'start' string.
+unsigned char *utf_left(unsigned char *start, unsigned char *cursor)
+{
+	extern cvar_t com_parseutf8;
+	if (cursor == start)
+		return cursor;
+	if (com_parseutf8.ival>0)
+	{
+		cursor--;
+		while ((*cursor & 0xc0) == 0x80 && cursor > start)
+			cursor--;
+	}
+	else
+		cursor--;
+
+	if (*cursor == ']' && cursor > start && cursor[-1] == '^')
+	{
+		//just stepped onto a link
+		char *linkstart;
+		linkstart = cursor-1;
+		while(linkstart >= start)
+		{
+			if (linkstart[0] == '^' && linkstart[1] == '[')
+				return linkstart;
+			linkstart--;
+		}
+	}
+
+	return cursor;
+}
+
+//move the cursor one char to the right.
+unsigned char *utf_right(unsigned char *cursor)
+{
+	extern cvar_t com_parseutf8;
+
+	if (*cursor == '^' && cursor[1] == '[')
+	{
+		//just stepped over a link
+		char *linkend;
+		linkend = cursor+2;
+		while(*linkend)
+		{
+			if (linkend[0] == '^' && linkend[1] == '^')
+				linkend += 2;
+			else if (linkend[0] == '^' && linkend[1] == ']')
+				return linkend+2;
+			else
+				linkend++;
+		}
+		return linkend;
+	}
+
+	if (com_parseutf8.ival>0)
+	{
+		int skip = 1;
+		//figure out the length of the char
+		if ((*cursor & 0xc0) == 0x80)
+			skip = 1;	//error
+		else if ((*cursor & 0xe0) == 0xc0)
+			skip = 2;
+		else if ((*cursor & 0xf0) == 0xe0)
+			skip = 3;
+		else if ((*cursor & 0xf1) == 0xf0)
+			skip = 4;
+		else if ((*cursor & 0xf3) == 0xf1)
+			skip = 5;
+		else if ((*cursor & 0xf7) == 0xf3)
+			skip = 6;
+		else if ((*cursor & 0xff) == 0xf7)
+			skip = 7;
+		else skip = 1;
+
+		while (*cursor && skip)
+		{
+			cursor++;
+			skip--;
+		}
+	}
+	else if (*cursor)
+		cursor++;
+
+	return cursor;
+}
+
 /*
 ====================
 Key_Console
@@ -544,15 +872,14 @@ void Key_Console (unsigned int unicode, int key)
 	
 	if (key == K_LEFTARROW)
 	{
-		if (key_linepos > 1)
-			key_linepos--;
+		key_linepos = utf_left(key_lines[edit_line]+1, key_lines[edit_line] + key_linepos) - key_lines[edit_line];
 		return;
 	}
 	if (key == K_RIGHTARROW)
 	{
 		if (key_lines[edit_line][key_linepos])
 		{
-			key_linepos++;
+			key_linepos = utf_right(key_lines[edit_line] + key_linepos) - key_lines[edit_line];
 			return;
 		}
 		else
@@ -563,15 +890,8 @@ void Key_Console (unsigned int unicode, int key)
 	{
 		if (key_lines[edit_line][key_linepos])
 		{
-			int charlen = 1;
-			if (com_parseutf8.ival>0 &&
-				(key_lines[edit_line][key_linepos] & 0xc0) != 0x80)
-			{
-				while((key_lines[edit_line][key_linepos+charlen] & 0xc0) == 0x80)
-					charlen++;
-			}
-
-			memmove(key_lines[edit_line]+key_linepos, key_lines[edit_line]+key_linepos+charlen, strlen(key_lines[edit_line]+key_linepos+charlen)+charlen);
+			int charlen = utf_right(key_lines[edit_line] + key_linepos) - (key_lines[edit_line] + key_linepos);
+			memmove(key_lines[edit_line]+key_linepos, key_lines[edit_line]+key_linepos+charlen, strlen(key_lines[edit_line]+key_linepos+charlen)+1);
 			return;
 		}
 		else
@@ -582,13 +902,8 @@ void Key_Console (unsigned int unicode, int key)
 	{
 		if (key_linepos > 1)
 		{
-			int charlen = 1;
-			if (com_parseutf8.ival>0)
-			{
-				while (key_linepos > charlen && (key_lines[edit_line][key_linepos-charlen] & 0xc0) == 0x80)
-					charlen++;
-			}
-			memmove(key_lines[edit_line]+key_linepos-charlen, key_lines[edit_line]+key_linepos, strlen(key_lines[edit_line]+key_linepos)+charlen);
+			int charlen = (key_lines[edit_line]+key_linepos) - utf_left(key_lines[edit_line]+1, key_lines[edit_line] + key_linepos);
+			memmove(key_lines[edit_line]+key_linepos-charlen, key_lines[edit_line]+key_linepos, strlen(key_lines[edit_line]+key_linepos)+1);
 			key_linepos -= charlen;
 		}
 		return;
@@ -692,36 +1007,19 @@ void Key_Console (unsigned int unicode, int key)
 		return;
 	}
 
-	if (((unicode=='C' || unicode=='c') && keydown[K_CTRL]) || (keydown[K_CTRL] && key == K_INS))
+	//beware that windows translates ctrl+c and ctrl+v to a control char
+	if (((unicode=='C' || unicode=='c' || unicode==3) && keydown[K_CTRL]) || (keydown[K_CTRL] && key == K_INS))
 	{
 		Sys_SaveClipboard(key_lines[edit_line]+1);
 		return;
 	}
 
-	if (((unicode=='V' || unicode=='v') && keydown[K_CTRL]) || (keydown[K_SHIFT] && key == K_INS))
+	if (((unicode=='V' || unicode=='v' || unicode==22) && keydown[K_CTRL]) || (keydown[K_SHIFT] && key == K_INS))
 	{
 		clipText = Sys_GetClipboard();
 		if (clipText)
 		{
-			int i;
-			int len;
-			len = strlen(clipText);
-			if (len + strlen(key_lines[edit_line]) > MAXCMDLINE - 1)
-				len = MAXCMDLINE - 1 - strlen(key_lines[edit_line]);
-			if (len > 0)
-			{	// insert the string
-				memmove (key_lines[edit_line] + key_linepos + len,
-					key_lines[edit_line] + key_linepos, strlen(key_lines[edit_line]) - key_linepos + 1);
-				memcpy (key_lines[edit_line] + key_linepos, clipText, len);
-				for (i = 0; i < len; i++)
-				{
-					if (key_lines[edit_line][key_linepos+i] == '\r')
-						key_lines[edit_line][key_linepos+i] = ' ';
-					else if (key_lines[edit_line][key_linepos+i] == '\n')
-						key_lines[edit_line][key_linepos+i] = ';';
-				}
-				key_linepos += len;
-			}
+			Key_ConsoleInsert(clipText);
 			Sys_CloseClipboard(clipText);
 		}
 		return;
@@ -1466,7 +1764,7 @@ void Key_Event (int devid, int key, unsigned int unicode, qboolean down)
 
 	//yes, csqc is allowed to steal the escape key.
 	if (key != '`' && key != '~')
-	if (key_dest == key_game)
+	if (key_dest == key_game && !Media_PlayingFullScreen())
 	{
 #ifdef CSQC_DAT
 		if (CSQC_KeyPress(key, unicode, down, devid))	//give csqc a chance to handle it.
diff --git a/engine/client/m_mp3.c b/engine/client/m_mp3.c
index 44720d569..0fe7bb489 100644
--- a/engine/client/m_mp3.c
+++ b/engine/client/m_mp3.c
@@ -2199,6 +2199,8 @@ qboolean Media_PlayFilm(char *name)
 			cin->ended = false;
 			if (cin->rewind)
 				cin->rewind(cin);
+			if (cin->changestream)
+				cin->changestream(cin, "cmd:focus");
 		}
 		else
 		{
diff --git a/engine/client/pr_csqc.c b/engine/client/pr_csqc.c
index 48aab5844..95239219f 100644
--- a/engine/client/pr_csqc.c
+++ b/engine/client/pr_csqc.c
@@ -107,6 +107,7 @@ extern sfx_t			*cl_sfx_r_exp3;
 	globalfunction(input_event,			"CSQC_InputEvent");	\
 	globalfunction(input_frame,			"CSQC_Input_Frame");/*EXT_CSQC_1*/	\
 	globalfunction(console_command,		"CSQC_ConsoleCommand");	\
+	globalfunction(console_link,		"CSQC_ConsoleLink");	\
 	globalfunction(gamecommand,			"GameCommand");	/*DP extension*/\
 	\
 	globalfunction(ent_update,			"CSQC_Ent_Update");	\
@@ -1251,6 +1252,15 @@ static void QCBUILTIN PF_R_GetViewFlag(progfuncs_t *prinst, struct globalvars_s
 		*r = r_refdef.useperspective;
 		break;
 
+	case VF_SCREENVSIZE:
+		r[0] = vid.width;
+		r[1] = vid.height;
+		break;
+	case VF_SCREENPSIZE:
+		r[0] = vid.rotpixelwidth;
+		r[1] = vid.rotpixelheight;
+		break;
+
 	default:
 		Con_DPrintf("GetViewFlag: %i not recognised\n", parametertype);
 		break;
@@ -4947,7 +4957,7 @@ qboolean CSQC_Inited(void)
 }
 
 double  csqctime;
-qboolean CSQC_Init (qboolean anycsqc, unsigned int checksum)
+qboolean CSQC_Init (qboolean anycsqc, qboolean csdatenabled, unsigned int checksum)
 {
 	int i;
 	string_t *str;
@@ -5054,23 +5064,27 @@ qboolean CSQC_Init (qboolean anycsqc, unsigned int checksum)
 		}
 
 		csqc_isdarkplaces = false;
-		if (PR_LoadProgs(csqcprogs, "csprogs.dat", 22390, NULL, 0) >= 0)
-			loaded = true;
-		else
+		if (csdatenabled || csqc_singlecheats || anycsqc)
 		{
-			if (PR_LoadProgs(csqcprogs, "csprogs.dat", 52195, NULL, 0) >= 0)
-				loaded = true;
-			else if (PR_LoadProgs(csqcprogs, "csprogs.dat", 0, NULL, 0) >= 0) 
+			if (PR_LoadProgs(csqcprogs, "csprogs.dat", 22390, NULL, 0) >= 0)
 				loaded = true;
 			else
-				loaded = false;
+			{
+				if (PR_LoadProgs(csqcprogs, "csprogs.dat", 52195, NULL, 0) >= 0)
+					loaded = true;
+				else if (PR_LoadProgs(csqcprogs, "csprogs.dat", 0, NULL, 0) >= 0) 
+					loaded = true;
+				else
+					loaded = false;
 
-			if (loaded)
-				Con_Printf(CON_WARNING "Running outdated or unknown csprogs.dat version\n");
+				if (loaded)
+					Con_Printf(CON_WARNING "Running outdated or unknown csprogs.dat version\n");
+			}
 		}
 
-		if (csqc_singlecheats)
+		if (csqc_singlecheats || anycsqc)
 		{
+			Con_DPrintf("loading csaddon.dat...\n");
 			if (PR_LoadProgs(csqcprogs, "csaddon.dat", 0, NULL, 0) >= 0)
 				loaded = true;
 		}
@@ -5518,6 +5532,21 @@ qboolean CSQC_Accelerometer(float x, float y, float z)
 	return G_FLOAT(OFS_RETURN);
 }
 
+qboolean CSQC_ConsoleLink(char *text, char *info)
+{
+	void *pr_globals;
+	if (!csqcprogs || !csqcg.console_link)
+		return false;
+
+	pr_globals = PR_globals(csqcprogs, PR_CURRENT);
+	(((string_t *)pr_globals)[OFS_PARM1] = PR_TempString(csqcprogs, info));
+	*info = 0;
+	(((string_t *)pr_globals)[OFS_PARM0] = PR_TempString(csqcprogs, text));
+	*info = '\\';
+	PR_ExecuteProgram (csqcprogs, csqcg.console_link);
+	return G_FLOAT(OFS_RETURN);
+}
+
 qboolean CSQC_ConsoleCommand(char *cmd)
 {
 	void *pr_globals;
diff --git a/engine/common/common.c b/engine/common/common.c
index a452e9a09..7f4aeb185 100644
--- a/engine/common/common.c
+++ b/engine/common/common.c
@@ -1863,31 +1863,74 @@ conchar_t q3codemasks[MAXQ3COLOURS] = {
 	0x07000000  // 9, "half-intensity" (BX_COLOREDTEXT)
 };
 
-
-//Strips out the flags
-void COM_DeFunString(conchar_t *str, char *out, int outsize, qboolean ignoreflags)
+//Converts a conchar_t string into a char string. returns the null terminator. pass NULL for stop to calc it
+char *COM_DeFunString(conchar_t *str, conchar_t *stop, char *out, int outsize, qboolean ignoreflags)
 {
-	if (ignoreflags)
+	if (!stop)
 	{
-		while(*str)
+		for (stop = str; *stop; stop++)
+			;
+	}
+#ifdef _DEBUG
+	if (!outsize)
+		Sys_Error("COM_DeFunString given outsize=0");
+#endif
+
+	/*if (ignoreflags)
+	{
+		while(str < stop)
 		{
 			if (!--outsize)
 				break;
 			*out++ = (unsigned char)(*str++&255);
 		}
-		*out++ = 0;
+		*out = 0;
 	}
-	else
+	else*/
 	{
 		int fl, d;
+		unsigned int c;
+		int prelinkflags = CON_WHITEMASK;	//if used, its already an error.
 		//FIXME: TEST!
 
 		fl = CON_WHITEMASK;
-		while(*str)
+		while(str < stop)
 		{
-			if (!--outsize)
-				break;
-			if ((*str & CON_FLAGSMASK) != fl)
+			if ((*str & CON_HIDDEN) && ignoreflags)
+			{
+				str++;
+				continue;
+			}
+			if (*str == CON_LINKSTART)
+			{
+				if (!ignoreflags)
+				{
+					if (outsize<=2)
+						break;
+					outsize -= 2;
+					*out++ = '^';
+					*out++ = '[';
+				}
+				prelinkflags = fl;
+				fl = COLOR_RED << CON_FGSHIFT;
+				str++;
+				continue;
+			}
+			else if (*str == CON_LINKEND)
+			{
+				if (!ignoreflags)
+				{
+					if (outsize<=2)
+						break;
+					outsize -= 2;
+					*out++ = '^';
+					*out++ = ']';
+				}
+				fl = prelinkflags;
+				str++;
+				continue;
+			}
+			else if ((*str & CON_FLAGSMASK) != fl && !ignoreflags)
 			{
 				d = fl^(*str & CON_FLAGSMASK);
 //				if (fl & CON_NONCLEARBG)	//not represented.
@@ -1918,31 +1961,130 @@ void COM_DeFunString(conchar_t *str, char *out, int outsize, qboolean ignoreflag
 
 				if (d & (CON_FGMASK | CON_BGMASK | CON_NONCLEARBG))
 				{
-					if (outsize<=4)
-						break;
-					outsize -= 4;
-
-					d = (*str & CON_FLAGSMASK);
-					*out++ = '^';
-					*out++ = '&';
-					if ((d & CON_FGMASK) == CON_WHITEMASK)
-						*out++ = '-';
-					else
-						sprintf(out, "%X", d>>24);
-					if (d & CON_NONCLEARBG)
+					static char q3[16] = {	'0', 0,   0,   0,
+											0,   0,   0,   0,
+											0,	 '4', '2', '5',
+											'1', '6', '3', '7'};
+					if (!(d & (CON_BGMASK | CON_NONCLEARBG)) && q3[(*str & CON_FGMASK) >> CON_FGSHIFT] && !((d|fl) & CON_HALFALPHA))
 					{
-						sprintf(out, "%X", d>>28);
+						if (outsize<=2)
+							break;
+						outsize -= 2;
+
+						d = (*str & CON_FLAGSMASK);
+						*out++ = '^';
+						*out++ = q3[(*str & CON_FGMASK) >> CON_FGSHIFT];
 					}
 					else
-						*out++ = '-';
+					{
+						if (outsize<=4)
+							break;
+						outsize -= 4;
+
+						d = (*str & CON_FLAGSMASK);
+						*out++ = '^';
+						*out++ = '&';
+						if ((d & CON_FGMASK) == CON_WHITEMASK)
+							*out = '-';
+						else
+							sprintf(out, "%X", d>>24);
+						out++;
+						if (d & CON_NONCLEARBG)
+							sprintf(out, "%X", d>>28);
+						else
+							*out = '-';
+						out++;
+					}
 				}
 
 				fl = (*str & CON_FLAGSMASK);
 			}
-			*out++ = (unsigned char)(*str++&255);
+
+			//don't magically show hidden text
+			if (ignoreflags && (*str & CON_HIDDEN))
+				continue;
+
+			c = *str++ & 0xffff;
+			if (com_parseutf8.ival > 0)
+			{
+				//utf-8
+				if (c > 0x7ff)
+				{
+					if (outsize<=3)
+						break;
+					outsize -= 3;
+
+					*out++ = (unsigned char)((c>>12)&0x0f) | 0xe0;
+					*out++ = (unsigned char)((c>>6)&0x3f) | 0x80;
+					*out++ = (unsigned char)(c&0x3f) | 0x80;
+				}
+				else if (c > 0x7f)
+				{
+					if (outsize<=2)
+						break;
+					outsize -= 2;
+
+					*out++ = (unsigned char)((c>>6)&0x1f) | 0xc0;
+					*out++ = (unsigned char)(c&0x3f) | 0x80;
+				}
+				else
+				{
+					if (outsize<=1)
+						break;
+					outsize -= 1;
+					*out++ = (unsigned char)(c&0x7f);
+				}
+			}
+			else if (com_parseutf8.ival)
+			{
+				//iso8859-1
+				if ((c >= 0 && c < 255) || (c >= 0xe000+32 && c < 0xe000+127))	//quake chars between 32 and 127 are identical to iso8859-1
+				{
+					if (!--outsize)
+						break;
+					*out++ = (unsigned char)(c&255);
+				}
+				else	//any other quake char is not iso8859-1
+				{
+					const char hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+					if (outsize<=6)
+						break;
+					outsize -= 6;
+					*out++ = '^';
+					*out++ = 'U';
+					*out++ = hex[(c>>12)&15];
+					*out++ = hex[(c>>8)&15];
+					*out++ = hex[(c>>4)&15];
+					*out++ = hex[(c>>0)&15];
+				}
+			}
+			else
+			{
+				//quake chars
+				if (c == '\n' || c == '\r' || c == '\t' || (c >= 32 && c < 127) || (c >= 0xe000 && c < 0xe100))	//quake chars between 32 and 127 are identical to iso8859-1
+				{
+					if (!--outsize)
+						break;
+					*out++ = (unsigned char)(c&255);
+				}
+				else	//any other char is not quake
+				{
+					const char hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+					if (outsize<=6)
+						break;
+					outsize -= 6;
+					*out++ = '^';
+					*out++ = 'U';
+					*out++ = hex[(c>>12)&15];
+					*out++ = hex[(c>>8)&15];
+					*out++ = hex[(c>>4)&15];
+					*out++ = hex[(c>>0)&15];
+				}
+			}
 		}
-		*out++ = 0;
+		*out = 0;
 	}
+	return out;
 }
 
 static int dehex(int i)
@@ -1963,6 +2105,9 @@ conchar_t *COM_ParseFunString(conchar_t defaultflags, const char *str, conchar_t
 	int extstackdepth = 0;
 	unsigned int uc, l;
 	int utf8 = com_parseutf8.ival;
+	conchar_t linkinitflags = CON_WHITEMASK;/*doesn't need the init, but msvc is stupid*/
+	qboolean linkkeep = keepmarkup;
+	conchar_t *linkstart = NULL;
 
 	conchar_t ext;
 
@@ -2120,6 +2265,40 @@ conchar_t *COM_ParseFunString(conchar_t defaultflags, const char *str, conchar_t
 				// else invalid code
 				goto messedup;
 			}
+			else if (str[1] == '[' && !linkstart)
+			{
+				//preserved flags and reset to white. links must contain their own colours.
+				linkinitflags = ext;
+				ext = COLOR_RED << CON_FGSHIFT;
+				linkstart = out;
+				*out++ = '[';
+
+				//never keep the markup
+				linkkeep = keepmarkup;
+				keepmarkup = false;
+				str+=2;
+				continue;
+			}
+			else if (str[1] == ']' && linkstart)
+			{
+				*out++ = ']';
+
+				//its a valid link, so we can hide it all now
+				*linkstart++ |= CON_HIDDEN;	//leading [ is hidden
+				while(linkstart < out-1 && (*linkstart&CON_CHARMASK) != '\\')	//link text is NOT hidden
+					linkstart++;
+				while(linkstart < out)	//but the infostring behind it is, as well as the terminator
+					*linkstart++ |= CON_HIDDEN;
+
+				//reset colours to how they used to be
+				ext = linkinitflags;
+				linkstart = NULL;
+				keepmarkup = linkkeep;
+
+				//never keep the markup
+				str+=2;
+				continue;
+			}
 			else if (str[1] == 'a')
 			{
 				ext ^= CON_2NDCHARSETTEXT;
@@ -2130,7 +2309,10 @@ conchar_t *COM_ParseFunString(conchar_t defaultflags, const char *str, conchar_t
 			}
 			else if (str[1] == 'd')
 			{
-				ext = defaultflags;
+				if (linkstart)
+					ext = COLOR_RED << CON_FGSHIFT;
+				else
+					ext = defaultflags;
 			}
 			else if (str[1] == 'm')
 			{
diff --git a/engine/common/common.h b/engine/common/common.h
index a59fb9722..cf76ac64e 100644
--- a/engine/common/common.h
+++ b/engine/common/common.h
@@ -275,7 +275,7 @@ void COM_InitArgv (int argc, const char **argv);
 void COM_ParsePlusSets (void);
 
 typedef unsigned int conchar_t;
-void COM_DeFunString(conchar_t *str, char *out, int outsize, qboolean ignoreflags);
+char *COM_DeFunString(conchar_t *str, conchar_t *stop, char *out, int outsize, qboolean ignoreflags);
 conchar_t *COM_ParseFunString(conchar_t defaultflags, const char *str, conchar_t *out, int outsize, qboolean keepmarkup);	//ext is usually CON_WHITEMASK, returns its null terminator
 int COM_FunStringLength(unsigned char *str);
 
diff --git a/engine/common/console.h b/engine/common/console.h
index 63a93d6df..ff5e00650 100644
--- a/engine/common/console.h
+++ b/engine/common/console.h
@@ -38,7 +38,7 @@ extern conchar_t q3codemasks[MAXQ3COLOURS];
 #define CON_HIGHCHARSMASK	0x00000080 // Quake's alternative mask
 
 #define CON_FLAGSMASK		0xFFF00000
-#define CON_UNUSEDMASK		0x000F0000
+#define CON_HIDDEN			0x000F0000
 #define CON_CHARMASK		0x0000FFFF
 
 #define CON_FGMASK			0x0F000000
@@ -51,6 +51,9 @@ extern conchar_t q3codemasks[MAXQ3COLOURS];
 
 #define CON_DEFAULTCHAR		(CON_WHITEMASK | 32)
 
+#define CON_LINKSTART		(CON_HIDDEN | '[')
+#define CON_LINKEND			(CON_HIDDEN | ']')
+
 // RGBI standard colors
 #define COLOR_BLACK			0
 #define COLOR_DARKBLUE		1
@@ -142,7 +145,7 @@ void Con_ForceActiveNow(void);
 void Con_Init (void);
 void Con_Shutdown (void);
 void Con_DrawConsole (int lines, qboolean noback);
-char *Con_CopyConsole(void);
+char *Con_CopyConsole(qboolean nomarkup);
 void Con_Print (char *txt);
 void VARGS Con_Printf (const char *fmt, ...) LIKEPRINTF(1);
 void VARGS Con_TPrintf (translation_t text, ...);
diff --git a/engine/common/gl_q2bsp.c b/engine/common/gl_q2bsp.c
index ee6308e83..e1e7b98f2 100644
--- a/engine/common/gl_q2bsp.c
+++ b/engine/common/gl_q2bsp.c
@@ -5846,7 +5846,7 @@ int CM_WriteAreaBits (model_t *mod, qbyte *buffer, int area)
 	return bytes;
 }
 
-
+#ifndef CLIENTONLY
 void	CM_InitPortalState(void)
 {
 	int i;
@@ -5857,6 +5857,7 @@ void	CM_InitPortalState(void)
 			map_q2areas[i].floodnum = 0;
 	}
 }
+#endif
 
 /*
 ===================
diff --git a/engine/common/pr_bgcmd.c b/engine/common/pr_bgcmd.c
index 23894d075..ed784680d 100644
--- a/engine/common/pr_bgcmd.c
+++ b/engine/common/pr_bgcmd.c
@@ -2290,7 +2290,7 @@ void QCBUILTIN PF_strdecolorize (progfuncs_t *prinst, struct globalvars_s *pr_gl
 	char result[8192];
 	unsigned int flagged[8192];
 	COM_ParseFunString(CON_WHITEMASK, in, flagged, sizeof(flagged), false);
-	COM_DeFunString(flagged, result, sizeof(result), true);
+	COM_DeFunString(flagged, NULL, result, sizeof(result), true);
 
 	RETURN_TSTRING(result);
 }
diff --git a/engine/common/pr_common.h b/engine/common/pr_common.h
index 9c7df50b9..fc4075abf 100644
--- a/engine/common/pr_common.h
+++ b/engine/common/pr_common.h
@@ -435,6 +435,8 @@ typedef enum
 	//201 used by DP... WTF? CLEARSCREEN
 	VF_LPLAYER = 202,
 	VF_AFOV = 203,	//aproximate fov (match what the engine would normally use for the fov cvar). p0=fov, p1=zoom
+	VF_SCREENVSIZE = 204,
+	VF_SCREENPSIZE = 205,
 } viewflags;
 
 /*FIXME: this should be changed*/
diff --git a/engine/d3d/d3d11_shader.c b/engine/d3d/d3d11_shader.c
index 16135eeda..684e9dd05 100644
--- a/engine/d3d/d3d11_shader.c
+++ b/engine/d3d/d3d11_shader.c
@@ -338,4 +338,5 @@ int D3D11Shader_FindUniform(union programhandle_u *h, int type, char *name)
 #endif
 	return -1;
 }
-#endif
\ No newline at end of file
+#endif
+
diff --git a/engine/d3d/vid_d3d11.c b/engine/d3d/vid_d3d11.c
index 2cf38da12..f658a7a73 100644
--- a/engine/d3d/vid_d3d11.c
+++ b/engine/d3d/vid_d3d11.c
@@ -9,11 +9,6 @@
 #define COBJMACROS
 #include <d3d11.h>
 
-//#include    <d3d9.h>
-
-//#pragma comment(lib, "../libs/dxsdk9/lib/d3d9.lib")
-
-
 /*Fixup outdated windows headers*/
 #ifndef WM_XBUTTONDOWN
    #define WM_XBUTTONDOWN      0x020B
diff --git a/engine/dotnet2005/npfte.vcproj b/engine/dotnet2005/npfte.vcproj
index 3c177f0a4..ca2d9d3a9 100644
--- a/engine/dotnet2005/npfte.vcproj
+++ b/engine/dotnet2005/npfte.vcproj
@@ -22538,6 +22538,18 @@
 						/>
 					</FileConfiguration>
 				</File>
+				<File
+					RelativePath="..\common\sha1.c"
+					>
+					<FileConfiguration
+						Name="GLDebug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="0"
+						/>
+					</FileConfiguration>
+				</File>
 				<File
 					RelativePath="..\common\translate.c"
 					>
diff --git a/engine/gl/gl_backend.c b/engine/gl/gl_backend.c
index a12626123..508ef5b61 100644
--- a/engine/gl/gl_backend.c
+++ b/engine/gl/gl_backend.c
@@ -3213,11 +3213,11 @@ void GLBE_SelectDLight(dlight_t *dl, vec3_t colour)
 	shaderstate.lastuniform = 0;
 
 	lmode = 0;
-	if (dl->fov && shaderstate.shader_light[lmode|LSHADER_SPOT]->prog)
+	if (dl->fov && shaderstate.shader_light[lmode|LSHADER_SPOT] && shaderstate.shader_light[lmode|LSHADER_SPOT]->prog)
 		lmode |= LSHADER_SPOT;
-	if ((dl->flags & LFLAG_SHADOWMAP) && shaderstate.shader_light[lmode|LSHADER_SMAP]->prog)
+	if ((dl->flags & LFLAG_SHADOWMAP) && shaderstate.shader_light[lmode|LSHADER_SMAP] && shaderstate.shader_light[lmode|LSHADER_SMAP]->prog)
 		lmode |= LSHADER_SMAP;
-	if (TEXVALID(shaderstate.lightcubemap) && shaderstate.shader_light[lmode|LSHADER_CUBE]->prog)
+	if (TEXVALID(shaderstate.lightcubemap) && shaderstate.shader_light[lmode|LSHADER_CUBE] && shaderstate.shader_light[lmode|LSHADER_CUBE]->prog)
 		lmode |= LSHADER_CUBE;
 	shaderstate.lightmode = lmode;
 }
diff --git a/engine/gl/gl_font.c b/engine/gl/gl_font.c
index b25b6f322..24d6b2a05 100644
--- a/engine/gl/gl_font.c
+++ b/engine/gl/gl_font.c
@@ -1190,6 +1190,8 @@ int Font_CharEndCoord(struct font_s *font, int x, unsigned int charcode)
 {
 	struct charcache_s *c;
 #define TABWIDTH (8*20)
+	if (charcode&CON_HIDDEN)
+		return x;
 	if ((charcode&CON_CHARMASK) == '\t')
 		return x + ((TABWIDTH - (x % TABWIDTH)) % TABWIDTH);
 
@@ -1212,6 +1214,8 @@ int Font_CharWidth(unsigned int charcode)
 {
 	struct charcache_s *c;
 	struct font_s *font = curfont;
+	if (charcode&CON_HIDDEN)
+		return 0;
 	if ((charcode & CON_2NDCHARSETTEXT) && font->alt)
 		font = font->alt;
 
@@ -1333,6 +1337,9 @@ int Font_DrawChar(int px, int py, unsigned int charcode)
 	int col;
 	int v;
 	struct font_s *font = curfont;
+	if (charcode & CON_HIDDEN)
+		return px;
+
 	if ((charcode & CON_2NDCHARSETTEXT) && font->alt)
 		font = font->alt;
 
diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c
index 3d8200ded..194e66f7c 100644
--- a/engine/server/pr_cmds.c
+++ b/engine/server/pr_cmds.c
@@ -9946,6 +9946,8 @@ void PR_DumpPlatform_f(void)
 		{"VF_PERSPECTIVE",		"const float", CS, VF_PERSPECTIVE},
 		{"VF_LPLAYER",			"const float", CS, VF_LPLAYER},
 		{"VF_AFOV",				"const float", CS, VF_AFOV},
+		{"VF_SCREENVSIZE",		"const float", CS, VF_SCREENVSIZE},
+		{"VF_SCREENPSIZE",		"const float", CS, VF_SCREENPSIZE},
 
 		{"RF_VIEWMODEL",		"const float", CS, CSQCRF_VIEWMODEL},
 		{"RF_EXTERNALMODEL",	"const float", CS, CSQCRF_EXTERNALMODEL},
diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c
index 0bbfb0f55..93cd76f15 100644
--- a/engine/server/sv_main.c
+++ b/engine/server/sv_main.c
@@ -4523,6 +4523,7 @@ void SV_FixupName(char *in, char *out, unsigned int outlen)
 {
 	char *s, *p;
 	unsigned int len;
+	conchar_t testbuf[1024], *t, *e;
 
 	if (outlen == 0)
 		return;
@@ -4544,7 +4545,20 @@ void SV_FixupName(char *in, char *out, unsigned int outlen)
 	}
 	*s = '\0';
 
-	if (!*out)
+	/*note: clients are not guarenteed to parse things the same as the server. utf-8 surrogates may be awkward here*/
+	e = COM_ParseFunString(CON_WHITEMASK, out, testbuf, sizeof(testbuf), false);
+	for (t = testbuf; t < e; t++)
+	{
+		/*reject anything hidden in names*/
+		if (*t & CON_HIDDEN)
+			break;
+		/*reject pictograms*/
+		if ((*t & CON_CHARMASK) >= 0xe100 && (*t & CON_CHARMASK) < 0xe200)
+			break;
+		/*FIXME: should we try to ensure that the chars are in most fonts? that might annoy speakers of more exotic languages I suppose. cvar it?*/
+	}
+
+	if (!*out || (t < e) || e == testbuf)
 	{	//reached end and it was all whitespace
 		//white space only
 		strncpy(out, "unnamed", outlen);
diff --git a/plugins/berkelium/plugapi.cpp b/plugins/berkelium/plugapi.cpp
index 805cfef92..2d7de18d5 100644
--- a/plugins/berkelium/plugapi.cpp
+++ b/plugins/berkelium/plugapi.cpp
@@ -23,40 +23,105 @@ class MyDelegate : public Berkelium::WindowDelegate
 private:
 	decctx *ctx;
 
+	virtual void onCrashedWorker(Berkelium::Window *win)
+	{
+		int i;
+		Con_Printf("Berkelium worker crashed\n");
+
+		/*black it out*/
+		for (i = 0; i < ctx->width*ctx->height; i++)
+		{
+			ctx->buffer[i] = 0xff000000;
+		}
+		ctx->repainted = true;
+	}
+
+	virtual void onCrashed(Berkelium::Window *win)
+	{
+		int i;
+		Con_Printf("Berkelium window crashed\n");
+
+		/*black it out*/
+		for (i = 0; i < ctx->width*ctx->height; i++)
+		{
+			ctx->buffer[i] = 0xff000000;
+		}
+		ctx->repainted = true;
+	}
+	virtual void onUnresponsive(Berkelium::Window *win)
+	{
+		Con_Printf("Berkelium window unresponsive\n");
+	}
+	virtual void onResponsive(Berkelium::Window *win)
+	{
+		Con_Printf("Berkelium window responsive again, yay\n");
+	}
+
 	virtual void onPaint(Berkelium::Window *wini, const unsigned char *bitmap_in, const Berkelium::Rect &bitmap_rect, size_t num_copy_rects, const Berkelium::Rect *copy_rects, int dx, int dy, const Berkelium::Rect& scroll_rect)
 	{
 		int i;
 		// handle paint events...
 		if (dx || dy)
 		{
-			int y;
-			int t = scroll_rect.top();
-			int l = scroll_rect.left();
+			int y, m;
+			int dt = scroll_rect.top();
+			int dl = scroll_rect.left();
 			int w = scroll_rect.width();
 			int h = scroll_rect.height();
-			if (dy > 0)
-			{
-				//if we're moving downwards, we need to write the bottom before the top (so we don't overwrite the data before its copied)
-				for (y = t+h-1; y >= t; y--)
-				{
-					if (y < 0 || y >= ctx->height)
-						continue;
-					if (y+dy < 0 || y+dy >= ctx->height)
-						continue;
-					memmove(ctx->buffer + ((l+dx) + (y+dy)*ctx->width), ctx->buffer + (l + y*ctx->width), w*4);
-				}
-			}
-			else
-			{
-				//moving upwards requires we write the top row first
-				for (y = t; y < t+h; y++)
-				{
-					if (y < 0 || y >= ctx->height)
-						continue;
-					if (y+dy < 0 || y+dy >= ctx->height)
-						continue;
+			int st = dt - dy;
+			int sl = dl - dx;
 
-					memmove(ctx->buffer + ((l+dx) + (y+dy)*ctx->width), ctx->buffer + (l + y*ctx->width), w*4);
+			/*bound the output rect*/
+			if (dt < 0)
+			{
+				st -= dt;
+				h += dt;
+				dt = 0;
+			}
+			if (dl < 0)
+			{
+				sl -= dl;
+				w += dl;
+				dl = 0;
+			}
+			/*bound the source rect*/
+			if (st < 0)
+			{
+				dt -= st;
+				h += st;
+				st = 0;
+			}
+			if (sl < 0)
+			{
+				dl -= sl;
+				w += sl;
+				sl = 0;
+			}
+			/*bound the width*/
+			m = (dl>sl)?dl:sl;
+			if (m + w > ctx->width)
+				w = ctx->width - m;
+			m = (dt>st)?dt:st;
+			if (m + h > ctx->height)
+				h = ctx->height - m;
+
+			if (w > 0 && h > 0)
+			{
+				if (dy > 0)
+				{
+					//if we're moving downwards, we need to write the bottom before the top (so we don't overwrite the data before its copied)
+					for (y = h; y >= 0; y--)
+					{
+						memmove(ctx->buffer + (dl + (dt+y)*ctx->width), ctx->buffer + (sl + (st+y)*ctx->width), w*4);
+					}
+				}
+				else
+				{
+					//moving upwards requires we write the top row first
+					for (y = 0; y < h; y++)
+					{
+						memmove(ctx->buffer + (dl + (dt+y)*ctx->width), ctx->buffer + (sl + (st+y)*ctx->width), w*4);
+					}
 				}
 			}
 		}
@@ -65,22 +130,37 @@ private:
 			unsigned int *out = ctx->buffer;
 			const unsigned int *in = (const unsigned int*)bitmap_in;
 			int x, y;
-			int t = copy_rects[i].top() - bitmap_rect.top();
-			int l = copy_rects[i].left() - bitmap_rect.left();
+			int t = copy_rects[i].top();
+			int l = copy_rects[i].left();
 			int r = copy_rects[i].width() + l;
 			int b = copy_rects[i].height() + t;
-			unsigned int instride = bitmap_rect.width() - (r - l);
-			unsigned int outstride = ctx->width - (r - l);
+			int w, h;
 
-			out += copy_rects[i].left();
-			out += copy_rects[i].top() * ctx->width;
+			//Clip the rect to the display. This should generally happen anyway, but resizes can be lagged a bit with the whole multi-process/thread thing.
+			//don't need to clip to the bitmap rect, that should be correct.
+			if (l < 0)
+				l = 0;
+			if (t < 0)
+				t = 0;
+			if (r > ctx->width)
+				r = ctx->width;
+			if (b > ctx->height)
+				b = ctx->height;
+			w = r - l;
+			h = b - t;
 
-			in += l;
-			in += t * bitmap_rect.width();
+			unsigned int instride = bitmap_rect.width() - (w);
+			unsigned int outstride = ctx->width - (w);
 
-			for (y = t; y < b; y++)
+			out += l;
+			out += t * ctx->width;
+
+			in += (l-bitmap_rect.left());
+			in += (t-bitmap_rect.top()) * bitmap_rect.width();
+
+			for (y = 0; y < h; y++)
 			{
-				for (x = l; x < r; x++)
+				for (x = 0; x < w; x++)
 				{
 					*out++ = *in++;
 				}
@@ -182,16 +262,47 @@ static void Dec_Key (void *vctx, int code, int unicode, int isup)
 	wchar_t wchr = unicode;
 
 	if (code >= 178 && code < 178+6)
-		ctx->wnd->mouseButton(code - 178, !isup);
+	{
+		code = code - 178;
+		//swap mouse2+3
+		if (code == 1)
+			code = 2;
+		else if (code == 2)
+			code = 1;
+		ctx->wnd->mouseButton(code, !isup);
+	}
 	else if (code == 188 || code == 189)
-		ctx->wnd->mouseWheel(0, (code==189)?-30:30);
+	{
+		if (!isup)
+			ctx->wnd->mouseWheel(0, (code==189)?-30:30);
+	}
 	else
 	{
 		if (code)
 		{
 			int mods = 0;
 			if (code == 127)
+				code = 0x08;
+			else if (code == 140)	//del
 				code = 0x2e;
+			else if (code == 143)	//home
+				code = 0x24;
+			else if (code == 144)	//end
+				code = 0x23;
+			else if (code == 141)	//pgdn
+				code = 0x22;
+			else if (code == 142)	//pgup
+				code = 0x21;
+			else if (code == 139)	//ins
+				code = 0x2d;
+			else if (code == 132)	//up
+				code = 0x26;
+			else if (code == 133)	//down
+				code = 0x28;
+			else if (code == 134)	//left
+				code = 0x25;
+			else if (code == 135)	//right
+				code = 0x27;
 			ctx->wnd->keyEvent(!isup, mods, code, 0);
 		}
 		if (unicode && !isup)
@@ -214,6 +325,10 @@ static void Dec_ChangeStream(void *vctx, char *newstream)
 			ctx->wnd->refresh();
 		else if (!strcmp(newstream+4, "transparent"))
 			ctx->wnd->setTransparent(true);
+		else if (!strcmp(newstream+4, "focus"))
+			ctx->wnd->focus();
+		else if (!strcmp(newstream+4, "unfocus"))
+			ctx->wnd->unfocus();
 		else if (!strcmp(newstream+4, "opaque"))
 			ctx->wnd->setTransparent(true);
 		else if (!strcmp(newstream+4, "stop"))