+void CL_BeginDownloads(qboolean aborteddownload)
+{
+ // quakeworld works differently
+ if (cls.protocol == PROTOCOL_QUAKEWORLD)
+ return;
+
+ // TODO: this would be a good place to do curl downloads
+
+ if (cl.downloadcsqc)
+ {
+ size_t progsize;
+ cl.downloadcsqc = false;
+ if (cls.netcon
+ && !sv.active
+ && csqc_progname.string
+ && csqc_progname.string[0]
+ && csqc_progcrc.integer >= 0
+ && cl_serverextension_download.integer
+ && (FS_CRCFile(csqc_progname.string, &progsize) != csqc_progcrc.integer || ((int)progsize != csqc_progsize.integer && csqc_progsize.integer != -1))
+ && !FS_FileExists(va("dlcache/%s.%i.%i", csqc_progname.string, csqc_progsize.integer, csqc_progcrc.integer)))
+ Cmd_ForwardStringToServer(va("download %s", csqc_progname.string));
+ }
+
+ if (cl.loadmodel_current < cl.loadmodel_total)
+ {
+ // loading models
+
+ for (;cl.loadmodel_current < cl.loadmodel_total;cl.loadmodel_current++)
+ {
+ if (cl.model_precache[cl.loadmodel_current] && cl.model_precache[cl.loadmodel_current]->Draw)
+ continue;
+ if (cls.signon < SIGNONS)
+ CL_KeepaliveMessage(true);
+ cl.model_precache[cl.loadmodel_current] = Mod_ForName(cl.model_name[cl.loadmodel_current], false, false, cl.loadmodel_current == 1);
+ if (cl.model_precache[cl.loadmodel_current] && cl.model_precache[cl.loadmodel_current]->Draw && cl.loadmodel_current == 1)
+ {
+ // we now have the worldmodel so we can set up the game world
+ cl.entities[0].render.model = cl.worldmodel = cl.model_precache[1];
+ CL_UpdateRenderEntity(&cl.entities[0].render);
+ R_Modules_NewMap();
+ // check memory integrity
+ Mem_CheckSentinelsGlobal();
+ if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer)
+ {
+ cl.loadfinished = true;
+ // now issue the spawn to move on to signon 3 like normal
+ if (cls.netcon)
+ Cmd_ForwardStringToServer("spawn");
+ }
+ }
+ }
+
+ // finished loading models
+ }
+
+ if (cl.loadsound_current < cl.loadsound_total)
+ {
+ // loading sounds
+
+ for (;cl.loadsound_current < cl.loadsound_total;cl.loadsound_current++)
+ {
+ if (cl.sound_precache[cl.loadsound_current] && S_IsSoundPrecached(cl.sound_precache[cl.loadsound_current]))
+ continue;
+ if (cls.signon < SIGNONS)
+ CL_KeepaliveMessage(true);
+ // Don't lock the sfx here, S_ServerSounds already did that
+ cl.sound_precache[cl.loadsound_current] = S_PrecacheSound(cl.sound_name[cl.loadsound_current], false, false);
+ }
+
+ // finished loading sounds
+ }
+
+ // note: the reason these loops skip already-loaded things is that it
+ // enables this command to be issued during the game if desired
+
+ if (cl.downloadmodel_current < cl.loadmodel_total)
+ {
+ // loading models
+
+ for (;cl.downloadmodel_current < cl.loadmodel_total;cl.downloadmodel_current++)
+ {
+ if (aborteddownload)
+ {
+ if (cl.downloadmodel_current == 1)
+ {
+ // the worldmodel failed, but we need to set up anyway
+ cl.entities[0].render.model = cl.worldmodel = cl.model_precache[1];
+ CL_UpdateRenderEntity(&cl.entities[0].render);
+ R_Modules_NewMap();
+ // check memory integrity
+ Mem_CheckSentinelsGlobal();
+ if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer)
+ {
+ cl.loadfinished = true;
+ // now issue the spawn to move on to signon 3 like normal
+ if (cls.netcon)
+ Cmd_ForwardStringToServer("spawn");
+ }
+ }
+ aborteddownload = false;
+ continue;
+ }
+ if (cl.model_precache[cl.downloadmodel_current] && cl.model_precache[cl.downloadmodel_current]->Draw)
+ continue;
+ if (cls.signon < SIGNONS)
+ CL_KeepaliveMessage(true);
+ if (!FS_FileExists(cl.model_name[cl.downloadmodel_current]))
+ {
+ if (cl.downloadmodel_current == 1)
+ Con_Printf("Map %s not found\n", cl.model_name[cl.downloadmodel_current]);
+ else
+ Con_Printf("Model %s not found\n", cl.model_name[cl.downloadmodel_current]);
+ // regarding the * check: don't try to download submodels
+ if (cl_serverextension_download.integer && cls.netcon && cl.model_name[cl.downloadmodel_current][0] != '*' && !sv.active)
+ {
+ Cmd_ForwardStringToServer(va("download %s", cl.model_name[cl.downloadmodel_current]));
+ // we'll try loading again when the download finishes
+ return;
+ }
+ }
+ cl.model_precache[cl.downloadmodel_current] = Mod_ForName(cl.model_name[cl.downloadmodel_current], false, false, cl.downloadmodel_current == 1);
+ if (cl.downloadmodel_current == 1)
+ {
+ // we now have the worldmodel so we can set up the game world
+ cl.entities[0].render.model = cl.worldmodel = cl.model_precache[1];
+ CL_UpdateRenderEntity(&cl.entities[0].render);
+ R_Modules_NewMap();
+ // check memory integrity
+ Mem_CheckSentinelsGlobal();
+ if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer)
+ {
+ cl.loadfinished = true;
+ // now issue the spawn to move on to signon 3 like normal
+ if (cls.netcon)
+ Cmd_ForwardStringToServer("spawn");
+ }
+ }
+ }
+
+ // finished loading models
+ }
+
+ if (cl.downloadsound_current < cl.loadsound_total)
+ {
+ // loading sounds
+
+ for (;cl.downloadsound_current < cl.loadsound_total;cl.downloadsound_current++)
+ {
+ char soundname[MAX_QPATH];
+ if (aborteddownload)
+ {
+ aborteddownload = false;
+ continue;
+ }
+ if (cl.sound_precache[cl.downloadsound_current] && S_IsSoundPrecached(cl.sound_precache[cl.downloadsound_current]))
+ continue;
+ if (cls.signon < SIGNONS)
+ CL_KeepaliveMessage(true);
+ dpsnprintf(soundname, sizeof(soundname), "sound/%s", cl.sound_name[cl.downloadsound_current]);
+ if (!FS_FileExists(soundname) && !FS_FileExists(cl.sound_name[cl.downloadsound_current]))
+ {
+ Con_Printf("Sound %s not found\n", soundname);
+ if (cl_serverextension_download.integer && cls.netcon && !sv.active)
+ {
+ Cmd_ForwardStringToServer(va("download %s", soundname));
+ // we'll try loading again when the download finishes
+ return;
+ }
+ }
+ // Don't lock the sfx here, S_ServerSounds already did that
+ cl.sound_precache[cl.downloadsound_current] = S_PrecacheSound(cl.sound_name[cl.downloadsound_current], false, false);
+ }
+
+ // finished loading sounds
+ }
+
+ if (!cl.loadfinished)
+ {
+ cl.loadfinished = true;
+
+ // check memory integrity
+ Mem_CheckSentinelsGlobal();
+
+ // now issue the spawn to move on to signon 3 like normal
+ if (cls.netcon)
+ Cmd_ForwardStringToServer("spawn");
+ }
+}
+
+void CL_BeginDownloads_f(void)
+{
+ CL_BeginDownloads(false);
+}
+
+void CL_StopDownload(int size, int crc)
+{
+ if (cls.qw_downloadmemory && cls.qw_downloadmemorycursize == size && CRC_Block(cls.qw_downloadmemory, size) == crc)
+ {
+ int existingcrc;
+ size_t existingsize;
+ const char *extension;
+
+ // finished file
+ // save to disk only if we don't already have it
+ // (this is mainly for playing back demos)
+ existingcrc = FS_CRCFile(cls.qw_downloadname, &existingsize);
+ if (existingsize)
+ {
+ if ((int)existingsize != size || existingcrc != crc)
+ {
+ // we have a mismatching file, pick another name for it
+ char name[MAX_QPATH*2];
+ dpsnprintf(name, sizeof(name), "dlcache/%s.%i.%i", cls.qw_downloadname, size, crc);
+ if (!FS_FileExists(name))
+ {
+ Con_Printf("Downloaded \"%s\" (%i bytes, %i CRC)\n", name, size, crc);
+ FS_WriteFile(name, cls.qw_downloadmemory, cls.qw_downloadmemorycursize);
+ }
+ }
+ }
+ else
+ {
+ // we either don't have it or have a mismatching file...
+ // so it's time to accept the file
+ // but if we already have a mismatching file we need to rename
+ // this new one, and if we already have this file in renamed form,
+ // we do nothing
+ Con_Printf("Downloaded \"%s\" (%i bytes, %i CRC)\n", cls.qw_downloadname, size, crc);
+ FS_WriteFile(cls.qw_downloadname, cls.qw_downloadmemory, cls.qw_downloadmemorycursize);
+ extension = FS_FileExtension(cls.qw_downloadname);
+ if (!strcasecmp(extension, "pak") || !strcasecmp(extension, "pk3"))
+ FS_Rescan();
+ }
+ }
+
+ if (cls.qw_downloadmemory)
+ Mem_Free(cls.qw_downloadmemory);
+ cls.qw_downloadmemory = NULL;
+ cls.qw_downloadname[0] = 0;
+ cls.qw_downloadmemorymaxsize = 0;
+ cls.qw_downloadmemorycursize = 0;
+ cls.qw_downloadpercent = 0;
+}
+
+void CL_ParseDownload(void)
+{
+ int i, start, size;
+ unsigned char data[65536];
+ start = MSG_ReadLong();
+ size = (unsigned short)MSG_ReadShort();
+
+ // record the start/size information to ack in the next input packet
+ for (i = 0;i < CL_MAX_DOWNLOADACKS;i++)
+ {
+ if (!cls.dp_downloadack[i].start && !cls.dp_downloadack[i].size)
+ {
+ cls.dp_downloadack[i].start = start;
+ cls.dp_downloadack[i].size = size;
+ break;
+ }
+ }
+
+ MSG_ReadBytes(size, data);
+
+ if (!cls.qw_downloadname[0])
+ {
+ if (size > 0)
+ Con_Printf("CL_ParseDownload: received %i bytes with no download active\n", size);
+ return;
+ }
+
+ if (start + size > cls.qw_downloadmemorymaxsize)
+ Host_Error("corrupt download message\n");
+
+ // only advance cursize if the data is at the expected position
+ // (gaps are unacceptable)
+ memcpy(cls.qw_downloadmemory + start, data, size);
+ cls.qw_downloadmemorycursize = start + size;
+ cls.qw_downloadpercent = (int)floor((start+size) * 100.0 / cls.qw_downloadmemorymaxsize);
+ cls.qw_downloadpercent = bound(0, cls.qw_downloadpercent, 100);
+ cls.qw_downloadspeedcount += size;
+}
+
+void CL_DownloadBegin_f(void)
+{
+ int size = atoi(Cmd_Argv(1));
+
+ if (size < 0 || size > 1<<30 || FS_CheckNastyPath(Cmd_Argv(2), false))
+ {
+ Con_Printf("cl_downloadbegin: received bogus information\n");
+ CL_StopDownload(0, 0);
+ return;
+ }
+
+ if (cls.qw_downloadname[0])
+ Con_Printf("Download of %s aborted\n", cls.qw_downloadname);
+
+ CL_StopDownload(0, 0);
+
+ // we're really beginning a download now, so initialize stuff
+ strlcpy(cls.qw_downloadname, Cmd_Argv(2), sizeof(cls.qw_downloadname));
+ cls.qw_downloadmemorymaxsize = size;
+ cls.qw_downloadmemory = Mem_Alloc(cls.permanentmempool, cls.qw_downloadmemorymaxsize);
+ cls.qw_downloadnumber++;
+
+ Cmd_ForwardStringToServer("sv_startdownload");
+}
+
+void CL_StopDownload_f(void)
+{
+ if (cls.qw_downloadname[0])
+ {
+ Con_Printf("Download of %s aborted\n", cls.qw_downloadname);
+ CL_StopDownload(0, 0);
+ }
+ CL_BeginDownloads(true);
+}
+
+void CL_DownloadFinished_f(void)
+{
+ if (Cmd_Argc() < 3)
+ {
+ Con_Printf("Malformed cl_downloadfinished command\n");
+ return;
+ }
+ CL_StopDownload(atoi(Cmd_Argv(1)), atoi(Cmd_Argv(2)));
+ CL_BeginDownloads(false);
+}
+