diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c
index 42d837f8e..8ea1e25c3 100644
--- a/engine/client/cl_parse.c
+++ b/engine/client/cl_parse.c
@@ -2240,6 +2240,7 @@ static void DL_Completed(qdownload_t *dl, qofs_t start, qofs_t end)
 
 static float chunkrate;
 
+static int CL_CountQueuedDownloads(void);
 static void CL_ParseChunkedDownload(qdownload_t *dl)
 {
 	qbyte	*svname;
@@ -2263,7 +2264,41 @@ static void CL_ParseChunkedDownload(qdownload_t *dl)
 
 		svname = MSG_ReadString();
 		if (cls.demoplayback)
-			return;
+		{	//downloading in demos is allowed ONLY for csprogs.dat
+			extern cvar_t cl_downloads, cl_download_csprogs;
+			if (!cls.download && !dl &&
+					!strcmp(svname, "csprogs.dat") && filesize && filesize == strtoul(InfoBuf_ValueForKey(&cl.serverinfo, "*csprogssize"), NULL, 0) &&
+					cl_downloads.ival && cl_download_csprogs.ival)
+			{
+				//FIXME: should probably save this to memory instead of bloating it on disk.
+				dl = Z_Malloc(sizeof(*dl));
+
+				Q_strncpyz(dl->remotename, svname, sizeof(dl->remotename));
+				Q_strncpyz(dl->localname, va("csprogsvers/%x.dat", (unsigned int)strtoul(InfoBuf_ValueForKey(&cl.serverinfo, "*csprogs"), NULL, 0)), sizeof(dl->localname));
+
+				// download to a temp name, and only rename
+				// to the real name when done, so if interrupted
+				// a runt file wont be left
+				COM_StripExtension (dl->localname, dl->tempname, sizeof(dl->tempname)-5);
+				Q_strncatz (dl->tempname, ".tmp", sizeof(dl->tempname));
+
+				dl->method = DL_QWPENDING;
+				dl->percent = 0;
+				dl->sizeunknown = true;
+				dl->flags = DLLF_OVERWRITE;
+
+				if (COM_FCheckExists(dl->localname))
+				{
+					Con_DPrintf("Demo embeds redundant %s\n", dl->localname);
+					Z_Free(dl);
+					return;
+				}
+				cls.download = dl;
+				Con_Printf("Saving recorded file %s (%lu bytes)\n", dl->localname, (unsigned long)filesize);
+			}
+			else
+				return;
+		}
 
 		if (!*svname)
 		{
@@ -2367,6 +2402,7 @@ static void CL_ParseChunkedDownload(qdownload_t *dl)
 		dl->method = DL_QWCHUNKS;
 		dl->percent = 0;
 		dl->size = filesize;
+		dl->sizeunknown = false;
 
 		dl->starttime = Sys_DoubleTime();
 
@@ -2391,7 +2427,8 @@ static void CL_ParseChunkedDownload(qdownload_t *dl)
 
 	if (!dl)
 	{
-		Con_Printf("ignoring download data packet\n");
+		if (!cls.demoplayback)	//mute it in demos.
+			Con_Printf("ignoring download data packet\n");
 		return;
 	}
 
@@ -2401,11 +2438,6 @@ static void CL_ParseChunkedDownload(qdownload_t *dl)
 	if (!dl->file)
 		return;
 
-	if (cls.demoplayback)
-	{	//err, yeah, when playing demos we don't actually pay any attention to this.
-		return;
-	}
-
 	VFS_SEEK(dl->file, chunknum*DLBLOCKSIZE);
 	if (dl->size - chunknum*DLBLOCKSIZE < DLBLOCKSIZE)	//final block is actually meant to be smaller than we recieve.
 		VFS_WRITE(dl->file, data, dl->size - chunknum*DLBLOCKSIZE);
@@ -2417,6 +2449,9 @@ static void CL_ParseChunkedDownload(qdownload_t *dl)
 	dl->percent = dl->completedbytes/(float)dl->size*100;
 
 	chunkrate += 1;
+
+	if (dl->completedbytes == dl->size)
+		CL_DownloadFinished(dl);
 }
 
 static int CL_CountQueuedDownloads(void)
diff --git a/engine/client/cl_pred.c b/engine/client/cl_pred.c
index 233d133fa..05a029e0f 100644
--- a/engine/client/cl_pred.c
+++ b/engine/client/cl_pred.c
@@ -959,6 +959,8 @@ float CL_GetPredictionRealtime(playerview_t *pv)
 
 	return simtime;
 }
+
+qboolean CSQC_GetSSQCEntityOrigin(unsigned int ssqcent, float *out);
 /*
 ==============
 CL_PredictMove
@@ -1060,7 +1062,8 @@ void CL_PredictMovePNum (int seat)
 	if (pv->cam_state == CAM_PENDING && pv->cam_spec_track >= 0 && pv->cam_spec_track < cl.allocated_client_slots && pv->viewentity != pv->cam_spec_track+1)
 	{
 		if ((cl.inframes[cl.validsequence & UPDATE_MASK].playerstate[pv->cam_spec_track].messagenum == cl.validsequence) ||
-			(pv->cam_spec_track+1 < cl.maxlerpents && cl.lerpents[pv->cam_spec_track+1].sequence == cl.lerpentssequence))
+			(pv->cam_spec_track+1 < cl.maxlerpents && cl.lerpents[pv->cam_spec_track+1].sequence == cl.lerpentssequence) ||
+			CSQC_GetSSQCEntityOrigin(pv->cam_spec_track+1, NULL))
 		{
 			pv->cam_state = CAM_EYECAM;
 			pv->viewentity = pv->cam_spec_track+1;
diff --git a/engine/client/cl_tent.c b/engine/client/cl_tent.c
index 08b9c2e63..5681f6748 100644
--- a/engine/client/cl_tent.c
+++ b/engine/client/cl_tent.c
@@ -2870,7 +2870,7 @@ entity_t *CL_NewTempEntity (void)
 	return ent;
 }
 
-void CSQC_GetEntityOrigin(unsigned int csqcent, float *out);
+qboolean CSQC_GetEntityOrigin(unsigned int csqcent, float *out);
 
 /*
 =================
diff --git a/engine/client/pr_csqc.c b/engine/client/pr_csqc.c
index 70a079cc4..4b6120a68 100644
--- a/engine/client/pr_csqc.c
+++ b/engine/client/pr_csqc.c
@@ -8310,7 +8310,11 @@ qboolean CSQC_Init (qboolean anycsqc, const char *csprogsname, unsigned int chec
 	}
 
 	if (cl_nocsqc.value)
+	{
+		if (checksum || progssize)
+			Con_Printf(CON_WARNING"Server is using csqc, but its disabled via %s\n", cl_nocsqc.name);
 		return false;
+	}
 
 	if (cls.state == ca_disconnected)
 	{
@@ -8430,6 +8434,8 @@ qboolean CSQC_Init (qboolean anycsqc, const char *csprogsname, unsigned int chec
 			csprogsnum = PR_LoadProgs(csqcprogs, csprogs_checkname);
 			if (csprogsnum >= 0)
 				Con_DPrintf("Loaded csprogs.dat\n");
+			else if (csprogs_checksum || csprogs_checksize)
+				Con_Printf(CON_WARNING"Unable to load \"csprogsvers/%x.dat\"\n", csprogs_checksum);
 		}
 		
 		if (csqc_singlecheats || anycsqc)
@@ -9725,13 +9731,29 @@ int CSQC_StartSound(int entnum, int channel, char *soundname, vec3_t pos, float
 	return false;
 }
 
-void CSQC_GetEntityOrigin(unsigned int csqcent, float *out)
+qboolean CSQC_GetEntityOrigin(unsigned int csqcent, float *out)
 {
 	wedict_t *ent;
 	if (!csqcprogs)
-		return;
+		return false;
 	ent = WEDICT_NUM_UB(csqcprogs, csqcent);
 	VectorCopy(ent->v->origin, out);
+	return true;
+}
+qboolean CSQC_GetSSQCEntityOrigin(unsigned int ssqcent, float *out)
+{
+	csqcedict_t *ent;
+	if (csqcprogs && ssqcent < maxcsqcentities)
+	{
+		ent = csqcent[ssqcent];
+		if (ent)
+		{
+			if (out)
+				VectorCopy(ent->v->origin, out);
+			return true;
+		}
+	}
+	return false;
 }
 
 void CSQC_ParseEntities(qboolean sized)
@@ -9744,7 +9766,23 @@ void CSQC_ParseEntities(qboolean sized)
 	qboolean removeflag;
 
 	if (!csqcprogs)
-		Host_EndGame("CSQC needs to be initialized for this server.\n");
+	{
+		const char *fname = va("csprogsvers/%x.dat", (unsigned int)strtoul(InfoBuf_ValueForKey(&cl.serverinfo, "*csprogs"), NULL, 0));
+		const char *msg;
+		if (cl_nocsqc.value)
+			msg = "blocked by cl_nocsqc.\n";
+		else if (!COM_FCheckExists(fname))
+		{
+			extern cvar_t cl_downloads, cl_download_csprogs;
+			if (!cl_downloads.ival || !cl_download_csprogs.ival)
+				msg = "downloading blocked by cl_downloads or cl_download_csprogs.\n";
+			else
+				msg = "unable to download.\n";
+		}
+		else
+			msg = "not initialised.\n";
+		Host_EndGame("%s required, but %s", fname, msg);
+	}
 
 	if (!csqcg.CSQC_Ent_Update || !csqcg.self)
 		Host_EndGame("CSQC has no CSQC_Ent_Update function\n");
@@ -9858,6 +9896,13 @@ void CSQC_ParseEntities(qboolean sized)
 				if (cl_csqcdebug.ival)
 					Con_Printf("Update %i\n", entnum);
 			}
+#ifdef QUAKESTATS
+			if (entnum-1 < cl.allocated_client_slots && cls.findtrack && cl.players[entnum-1].stats[STAT_HEALTH] > 0)
+			{	//FIXME: is this still needed with the autotrack stuff?
+				Cam_Lock(&cl.playerview[0], entnum-1);
+				cls.findtrack = false;
+			}
+#endif
 
 			*csqcg.self = EDICT_TO_PROG(csqcprogs, (void*)ent);
 			csqc_mayread = true;
diff --git a/engine/server/sv_mvd.c b/engine/server/sv_mvd.c
index 0e5519442..76f02355f 100644
--- a/engine/server/sv_mvd.c
+++ b/engine/server/sv_mvd.c
@@ -1633,7 +1633,7 @@ qboolean SV_MVD_Record (mvddest_t *dest)
 		else if (sv_demoExtensions.ival)
 		{	/*everything*/
 			extern cvar_t pext_replacementdeltas;
-			demo.recorder.fteprotocolextensions = PEXT_CSQC | PEXT_COLOURMOD | PEXT_DPFLAGS | PEXT_CUSTOMTEMPEFFECTS | PEXT_ENTITYDBL | PEXT_ENTITYDBL2 | PEXT_FATNESS | PEXT_HEXEN2 | PEXT_HULLSIZE | PEXT_LIGHTSTYLECOL | PEXT_MODELDBL | PEXT_SCALE | PEXT_SETATTACHMENT | PEXT_SETVIEW | PEXT_SOUNDDBL | PEXT_SPAWNSTATIC2 | PEXT_TRANS;
+			demo.recorder.fteprotocolextensions = PEXT_CHUNKEDDOWNLOADS | PEXT_CSQC | PEXT_COLOURMOD | PEXT_DPFLAGS | PEXT_CUSTOMTEMPEFFECTS | PEXT_ENTITYDBL | PEXT_ENTITYDBL2 | PEXT_FATNESS | PEXT_HEXEN2 | PEXT_HULLSIZE | PEXT_LIGHTSTYLECOL | PEXT_MODELDBL | PEXT_SCALE | PEXT_SETATTACHMENT | PEXT_SETVIEW | PEXT_SOUNDDBL | PEXT_SPAWNSTATIC2 | PEXT_TRANS;
 #ifdef PEXT_VIEW2
 			demo.recorder.fteprotocolextensions |= PEXT_VIEW2;
 #endif