From a4db77b22fdaaeb0c9ffd6a8cdd47b764cd9e9ec Mon Sep 17 00:00:00 2001 From: Spoike Date: Thu, 12 Jun 2014 23:08:42 +0000 Subject: [PATCH] rewrite download code detect rates and get the best speeds practical. client can cope with files over 4gb. added download resumption. hopefully. fix some q3 bugs. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@4685 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- engine/client/cl_cg.c | 12 +- engine/client/cl_input.c | 2 +- engine/client/cl_main.c | 60 +-- engine/client/cl_parse.c | 1036 +++++++++++++++++++++++------------- engine/client/cl_pred.c | 4 +- engine/client/cl_screen.c | 12 +- engine/client/client.h | 84 ++- engine/client/clq3_parse.c | 222 ++++---- engine/client/clq3defs.h | 2 - engine/client/console.c | 11 +- engine/client/m_download.c | 2 +- engine/client/pr_csqc.c | 2 +- engine/common/common.h | 2 +- engine/common/fs.c | 68 ++- engine/common/fs_pak.c | 2 +- engine/common/fs_zip.c | 266 ++++++--- engine/common/gl_q2bsp.c | 12 +- engine/gl/gl_alias.c | 2 + engine/gl/gl_vidnt.c | 15 +- engine/http/httpclient.c | 54 +- engine/http/iweb.h | 2 + engine/qclib/qcc_pr_lex.c | 4 +- engine/server/pr_cmds.c | 2 +- engine/server/sv_ents.c | 10 +- engine/server/sv_user.c | 4 +- engine/server/svq3_game.c | 41 +- 26 files changed, 1245 insertions(+), 688 deletions(-) diff --git a/engine/client/cl_cg.c b/engine/client/cl_cg.c index 826dde6d..d9fd7ecc 100644 --- a/engine/client/cl_cg.c +++ b/engine/client/cl_cg.c @@ -320,16 +320,16 @@ typedef struct { qbyte weapon; // weapon signed char forwardmove, rightmove, upmove; } q3usercmd_t; -#define CMD_MASK Q3UPDATE_MASK +#define CMD_BACKUP UPDATE_BACKUP +#define CMD_MASK UPDATE_MASK qboolean CGQ3_GetUserCmd(int cmdNumber, q3usercmd_t *ucmd) { usercmd_t *cmd; - cmdNumber--; if (cmdNumber > cl.movesequence) Host_EndGame("CL_GetUserCmd: cmdNumber > ccs.currentUserCmdNumber"); - if (cl.movesequence - cmdNumber > CMD_MASK) + if (cl.movesequence - cmdNumber >= CMD_BACKUP) return false; // too old cmd = &cl.outframes[(cmdNumber) & CMD_MASK].cmd[0]; @@ -956,7 +956,7 @@ static qintptr_t CG_SystemCalls(void *offset, quintptr_t mask, qintptr_t fn, con VALIDATEPOINTER(arg[0], sizeof(int)); VALIDATEPOINTER(arg[1], sizeof(int)); *(int *)VM_POINTER(arg[0]) = ccs.snap.serverMessageNum; - *(int *)VM_POINTER(arg[1]) = ccs.snap.serverTime;// + Sys_DoubleTime()*1000-ccs.snap.localTime; + *(int *)VM_POINTER(arg[1]) = ccs.snap.serverTime; break; case CG_GETSNAPSHOT: @@ -965,7 +965,7 @@ static qintptr_t CG_SystemCalls(void *offset, quintptr_t mask, qintptr_t fn, con break; case CG_GETCURRENTCMDNUMBER: - VM_LONG(ret) = cl.movesequence; + VM_LONG(ret) = cl.movesequence-1; break; case CG_GETUSERCMD: VALIDATEPOINTER(arg[1], sizeof(q3usercmd_t)); @@ -1143,7 +1143,7 @@ int CG_Refresh(void) if (!cgvm) return false; - time = ccs.serverTime; + time = cl.time*1000; VM_Call(cgvm, CG_DRAW_ACTIVE_FRAME, time, 0, false); R2D_ImageColours(1, 1, 1, 1); diff --git a/engine/client/cl_input.c b/engine/client/cl_input.c index 7fde8f88..b0566133 100644 --- a/engine/client/cl_input.c +++ b/engine/client/cl_input.c @@ -1663,7 +1663,7 @@ void CL_SendCmd (double frametime, qboolean mainloop) if (!runningindepphys) { // while we're not playing send a slow keepalive fullsend to stop mvdsv from screwing up - if (cls.state < ca_active && !cls.downloadmethod) + if (cls.state < ca_active && !cls.download) { #ifdef IRCCONNECT //don't spam irc. if (cls.netchan.remote_address.type == NA_IRC) diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index b2c3c26d..f6757b69 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -554,6 +554,7 @@ void CL_SendConnectPacket (netadr_t *to, int mtu, #ifdef Q3CLIENT if (connectinfo.protocol == CP_QUAKE3) { //q3 requires some very strange things. + cls.challenge = connectinfo.challenge; CLQ3_SendConnectPacket(to); return; } @@ -1483,7 +1484,7 @@ void CL_Disconnect (void) CL_ClearState(); - FS_PureMode(0, NULL, NULL, 0); + FS_PureMode(0, NULL, NULL, NULL, NULL, 0); Alias_WipeStuffedAliases(); @@ -1660,6 +1661,7 @@ void CL_PakDownloads(int mode) mode=0 no downloads (forced to 1 for pure) mode=1 archived names so local stuff is not poluted mode=2 downloaded packages will always be present. Use With Caution. + mode&4 download even packages that are not referenced. */ char local[256]; char *pname; @@ -1678,10 +1680,10 @@ void CL_PakDownloads(int mode) //'*' prefix means 'referenced'. so if the server isn't using any files from it, don't bother downloading it. if (*pname == '*') pname++; - else + else if (!(mode & 4)) continue; - if (mode != 2) + if ((mode&3) != 2) { /*if we already have such a file, this is a no-op*/ if (CL_CheckDLFile(va("package/%s", pname))) @@ -1709,7 +1711,7 @@ void CL_CheckServerPacks(void) if (pure != oldpure || cl.serverpakschanged) { CL_PakDownloads((pure && !cl_download_packages.ival)?1:cl_download_packages.ival); - FS_PureMode(pure, cl.serverpaknames, cl.serverpakcrcs, cls.challenge); + FS_PureMode(pure, cl.serverpaknames, cl.serverpakcrcs, NULL, NULL, cls.challenge); if (pure) { @@ -2290,7 +2292,7 @@ drop to full console void CL_Changing_f (void) { char *mapname = Cmd_Argv(1); - if (cls.downloadqw) // don't change when downloading + if (cls.download) // don't change when downloading return; if (*mapname) @@ -2323,7 +2325,7 @@ The server is changing levels */ void CL_Reconnect_f (void) { - if (cls.downloadqw) // don't change when downloading + if (cls.download) // don't change when downloading return; #ifdef NQPROT if (cls.protocol == CP_NETQUAKE && Cmd_FromGamecode()) @@ -2430,6 +2432,7 @@ void CL_ConnectionlessPacket (void) Con_Printf ("junk on the end of the packet\n"); CL_Disconnect_f(); } + cls.netchan.last_received = realtime; //in case there's some virus scanner running on the server making it stall... for instance... } return; } @@ -3171,6 +3174,7 @@ void CL_Download_f (void) if (Cmd_IsInsecure()) //mark server specified downloads. { + //don't let gamecode order us to download random junk if (!CL_AllowArbitaryDownload(url)) return; @@ -3198,14 +3202,14 @@ void CL_DownloadSize_f(void) if (!strcmp(size, "e")) { Con_Printf("Download of \"%s\" failed. Not found.\n", rname); - CL_DownloadFailed(rname, false); + CL_DownloadFailed(rname, NULL); } else if (!strcmp(size, "p")) { - if (stricmp(cls.downloadremotename, rname)) + if (cls.download && stricmp(cls.download->remotename, rname)) { Con_Printf("Download of \"%s\" failed. Not allowed.\n", rname); - CL_DownloadFailed(rname, false); + CL_DownloadFailed(rname, NULL); } } else if (!strcmp(size, "r")) @@ -3215,7 +3219,7 @@ void CL_DownloadSize_f(void) if (!CL_AllowArbitaryDownload(redirection)) return; - dl = CL_DownloadFailed(rname, false); + dl = CL_DownloadFailed(rname, NULL); Con_DPrintf("Download of \"%s\" redirected to \"%s\".\n", rname, redirection); CL_CheckOrEnqueDownloadFile(redirection, NULL, dl->flags); } @@ -3234,42 +3238,26 @@ void CL_DownloadSize_f(void) } void CL_FinishDownload(char *filename, char *tempname); -void CL_ForceStopDownload (qboolean finish) +void CL_ForceStopDownload (qdownload_t *dl, qboolean finish) { if (Cmd_IsInsecure()) { Con_Printf(CON_WARNING "Execution from server rejected for %s\n", Cmd_Argv(0)); return; } + if (!dl) + return; - if (!cls.downloadqw) + if (!dl->file) { Con_Printf("No files downloading by QW protocol\n"); return; } - VFS_CLOSE (cls.downloadqw); - cls.downloadqw = NULL; - if (finish) - CL_DownloadFinished(); + DL_Abort(dl, QDL_COMPLETED); else - { - char *tempname; - - if (*cls.downloadtempname) - tempname = cls.downloadtempname; - else - tempname = cls.downloadlocalname; - - if (strncmp(tempname,"skins/",6)) - FS_Remove(tempname, FS_GAME); - else - FS_Remove(tempname, FS_PUBBASEGAMEONLY); - } - *cls.downloadlocalname = '\0'; - *cls.downloadremotename = '\0'; - cls.downloadpercent = 0; + DL_Abort(dl, QDL_FAILED); // get another file if needed CL_RequestNextDownload (); @@ -3277,12 +3265,12 @@ void CL_ForceStopDownload (qboolean finish) void CL_SkipDownload_f (void) { - CL_ForceStopDownload(false); + CL_ForceStopDownload(cls.download, false); } void CL_FinishDownload_f (void) { - CL_ForceStopDownload(true); + CL_ForceStopDownload(cls.download, true); } #if defined(_WIN32) && !defined(WINRT) @@ -4333,6 +4321,10 @@ double Host_Frame (double time) cl.paused; // TODO: check if minimized or unfocused + //read packets early and always, so we don't have stuff waiting for reception quite so often. + //should smooth out a few things, and increase download speeds. + CL_ReadPackets (); + if (idle && cl_idlefps.value > 0) { double idlesec = 1.0 / cl_idlefps.value; diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index c58545c0..82d9e13d 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -276,17 +276,11 @@ int cl_spikeindex, cl_playerindex, cl_h_playerindex, cl_flagindex, cl_rocketind //called after disconnect, purges all memory that was allocated etc void CL_Parse_Disconnected(void) { - if (cls.downloadmethod <= DL_QWPENDING) - cls.downloadmethod = DL_NONE; - if (cls.downloadqw) + if (cls.download) { - VFS_CLOSE(cls.downloadqw); - cls.downloadqw = NULL; - } - if (!cls.downloadmethod) - { - *cls.downloadlocalname = '\0'; - *cls.downloadremotename = '\0'; + //note: not all downloads abort when the server disconnects, as they're fully out of bounds (ie: http) + if (cls.download->method <= DL_QWPENDING) + DL_Abort(cls.download, QDL_DISCONNECT); } { @@ -421,8 +415,9 @@ int CL_IsDownloading(const char *localname) return 2; //queued } - if (!strcmp(cls.downloadlocalname, localname)) - return 1; //downloading + if (cls.download) + if (!strcmp(cls.download->localname, localname)) + return 1; //downloading return 0; } @@ -524,9 +519,10 @@ qboolean CL_EnqueDownload(const char *filename, const char *localname, unsigned #pragma warningmsg("fix this") #endif int downloadsize; -void CL_GetDownloadSizes(unsigned int *filecount, unsigned int *totalsize, qboolean *somesizesunknown) +void CL_GetDownloadSizes(unsigned int *filecount, qofs_t *totalsize, qboolean *somesizesunknown) { downloadlist_t *dl; + qdownload_t *d; *filecount = 0; *totalsize = 0; *somesizesunknown = false; @@ -539,13 +535,13 @@ void CL_GetDownloadSizes(unsigned int *filecount, unsigned int *totalsize, qbool *totalsize += dl->size; } - if (cls.downloadmethod == DL_QW) + d = cls.download; + if (d) { - *totalsize += downloadsize; - *somesizesunknown = true; + if (d->sizeunknown) + *somesizesunknown = true; + *totalsize += d->size; } - if (cls.downloadmethod == DL_QWCHUNKS) - *totalsize += downloadsize; } void CL_DisenqueDownload(char *filename) @@ -579,114 +575,92 @@ void CL_DisenqueDownload(char *filename) void CL_WebDownloadFinished(struct dl_download *dl) { if (dl->status == DL_FAILED) - CL_DownloadFailed(dl->url, false); + CL_DownloadFailed(dl->url, &dl->qdownload); else if (dl->status == DL_FINISHED) { if (dl->file) VFS_CLOSE(dl->file); dl->file = NULL; - CL_DownloadFinished(); + CL_DownloadFinished(&dl->qdownload); } } #endif void CL_SendDownloadStartRequest(char *filename, char *localname, unsigned int flags) { - strcpy (cls.downloadremotename, filename); - strcpy (cls.downloadlocalname, localname); + static int dlsequence; + qdownload_t *dl; + +#ifdef WEBCLIENT + if (!strncmp(filename, "http://", 7)) + { + if (!HTTP_CL_Get(filename, localname, CL_WebDownloadFinished)) + CL_DownloadFailed(filename, NULL); + return; + } +#endif + if (cls.download) + return; //no! + + dl = Z_Malloc(sizeof(*dl)); + dl->filesequence = ++dlsequence; + + Q_strncpyz(dl->remotename, filename, sizeof(dl->remotename)); + Q_strncpyz(dl->localname, localname, sizeof(dl->localname)); if (!(flags & DLLF_TEMPORARY)) - Con_TPrintf ("Downloading %s...\n", cls.downloadlocalname); + Con_TPrintf ("Downloading %s...\n", 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 (localname, cls.downloadtempname, sizeof(cls.downloadtempname)-5); - strcat (cls.downloadtempname, ".tmp"); + COM_StripExtension (localname, dl->tempname, sizeof(dl->tempname)-5); + Q_strncatz (dl->tempname, ".tmp", sizeof(dl->tempname)); -#ifdef WEBCLIENT - if (!strncmp(cls.downloadremotename, "http://", 7)) - { - cls.downloadmethod = DL_HTTP; - cls.downloadpercent = 0; - if (!HTTP_CL_Get(cls.downloadremotename, cls.downloadtempname, CL_WebDownloadFinished)) - CL_DownloadFailed(cls.downloadremotename, false); - } - else -#endif - { - CL_SendClientCommand(true, "download %s", filename); + CL_SendClientCommand(true, "download %s", filename); + + dl->method = DL_QWPENDING; + dl->percent = 0; + dl->sizeunknown = true; + dl->flags = flags&DLLF_OVERWRITE; - //prevent ftp/http from changing stuff - cls.downloadmethod = DL_QWPENDING; - cls.downloadpercent = 0; - } CL_DisenqueDownload(filename); + + cls.download = dl; } //Do any reloading for the file that just reloaded. -void CL_DownloadFinished(void) +void CL_DownloadFinished(qdownload_t *dl) { int i; char *ext; - char *filename = cls.downloadlocalname; - char *tempname = cls.downloadtempname; + char filename[MAX_QPATH]; + char tempname[MAX_QPATH]; + + Q_strncpyz(filename, dl->localname, sizeof(filename)); + Q_strncpyz(tempname, dl->tempname, sizeof(tempname)); + + DL_Abort(dl, QDL_COMPLETED); COM_RefreshFSCache_f(); - cls.downloadmethod = DL_NONE; - // rename the temp file to it's final name if (tempname) { #ifdef TERRAIN if (!strncmp(tempname, "temp/", 5) && Terr_DownloadedSection(tempname)) { - FS_Remove(tempname, FS_GAME); + FS_Remove(tempname, dl->fsroot); return; } - else #endif - if (strcmp(tempname, filename)) - { - if (!strncmp(tempname,"package/",8)) - { - if (!FS_Rename(tempname+8, filename+8, FS_ROOT)) - { - char nativetmp[MAX_OSPATH], nativefinal[MAX_OSPATH]; - FS_NativePath(tempname+8, FS_ROOT, nativetmp, sizeof(nativetmp)); - FS_NativePath(filename+8, FS_ROOT, nativefinal, sizeof(nativefinal)); - Con_Printf("Couldn't rename %s to %s\n", nativetmp, nativefinal); - } - } - else if (!strncmp(tempname,"skins/",6)) - { - if (!FS_Rename(tempname, filename, FS_PUBBASEGAMEONLY)) - { - char nativetmp[MAX_OSPATH], nativefinal[MAX_OSPATH]; - FS_NativePath(tempname, FS_PUBBASEGAMEONLY, nativetmp, sizeof(nativetmp)); - FS_NativePath(filename, FS_PUBBASEGAMEONLY, nativefinal, sizeof(nativefinal)); - Con_Printf("Couldn't rename %s to %s\n", nativetmp, nativefinal); - } - } - else - { - if (!FS_Rename(tempname, filename, FS_GAME)) - { - char nativetmp[MAX_OSPATH], nativefinal[MAX_OSPATH]; - FS_NativePath(tempname, FS_GAME, nativetmp, sizeof(nativetmp)); - FS_NativePath(filename, FS_GAME, nativefinal, sizeof(nativefinal)); - Con_Printf("Couldn't rename %s to %s\n", nativetmp, nativefinal); - } - } - } } ext = COM_FileExtension(filename); - - if (!strncmp(ext, "pk4", 3) || !strncmp(ext, "pk3", 3) || !strncmp(ext, "pak", 3)) + //should probably ask the filesytem code if its a package format instead. + if (!strncmp(filename, "package/", 8) || !strncmp(ext, "pk4", 3) || !strncmp(ext, "pk3", 3) || !strncmp(ext, "pak", 3)) { FS_ReloadPackFiles(); CL_CheckServerInfo(); @@ -1426,7 +1400,7 @@ void CL_RequestNextDownload (void) int stage; /*already downloading*/ - if (cls.downloadmethod && !cls.demoplayback) + if (cls.download && !cls.demoplayback) return; /*request downloads only if we're at the point where we've received a complete list of them*/ @@ -1550,47 +1524,16 @@ void CL_SendDownloadReq(sizebuf_t *msg) if (cls.demoplayback == DPB_EZTV) return; //tcp connection, so no need to constantly ask - if (cl.downloadlist && !cls.downloadmethod) + if (!cls.download) { - CL_RequestNextDownload(); + if (cl.downloadlist) + CL_RequestNextDownload(); return; } #ifdef PEXT_CHUNKEDDOWNLOADS - if (cls.downloadmethod == DL_QWCHUNKS) - { - extern int download_file_number; - extern cvar_t drate; - static float lasttime; - int p; - if (!drate.ival) - p = 64; - else - { - lasttime = fabs(realtime - lasttime); - if (lasttime > 1) - lasttime = 1; - p = lasttime * drate.ival / 1000; - if (p > 90) - p = 90; - } - lasttime = realtime; - if (!p) - p = 1; - while (p-->0) - { - int i = CL_RequestADownloadChunk(); - if (i >= 0) - { - char *cmd = va("nextdl %i - %i\n", i, download_file_number); - CL_RemoveClientCommands(cmd); - CL_SendClientCommand(false, "%s", cmd); - } - else - break;//we can stop downloading now. - } - return; - } + if (cls.download->method == DL_QWCHUNKS) + DLC_Poll(cls.download); #endif } @@ -1640,7 +1583,7 @@ char *ZLibDownloadDecode(int *messagesize, char *input, int finalsize) } #endif -downloadlist_t *CL_DownloadFailed(const char *name, qboolean cancel) +downloadlist_t *CL_DownloadFailed(const char *name, qdownload_t *qdl) { //add this to our failed list. (so we don't try downloading it again...) downloadlist_t *failed, **link, *dl; @@ -1651,24 +1594,9 @@ downloadlist_t *CL_DownloadFailed(const char *name, qboolean cancel) //if this is what we're currently downloading, close it up now. //don't do this if we're just marking the file as unavailable for download. - if (cancel && (!stricmp(cls.downloadremotename, name) || !*name)) + if (qdl && (!stricmp(qdl->remotename, name) || !*name)) { - cls.downloadmethod = DL_NONE; - - if (cls.downloadqw) - { - VFS_CLOSE(cls.downloadqw); - cls.downloadqw = NULL; - if (!strncmp(cls.downloadtempname, "package/", 8)) - FS_Remove(cls.downloadtempname, FS_ROOT); - else if (!strncmp(cls.downloadtempname,"skins/",6)) - FS_Remove(cls.downloadtempname, FS_PUBBASEGAMEONLY); - else - FS_Remove(cls.downloadtempname, FS_GAME); - CL_SendClientCommand(true, "stopdownload"); - } - *cls.downloadlocalname = 0; - *cls.downloadremotename = 0; + DL_Abort(qdl, QDL_FAILED); } link = &cl.downloadlist; @@ -1689,36 +1617,271 @@ downloadlist_t *CL_DownloadFailed(const char *name, qboolean cancel) } #ifdef PEXT_CHUNKEDDOWNLOADS -#define MAXBLOCKS 512 //must be power of 2 #define DLBLOCKSIZE 1024 -int downloadsize; -struct -{ - int chunkno; -} dlblock[MAXBLOCKS]; -int firstblock; -int blockcycle; -int download_file_number; int CL_DownloadRate(void) { - return cls.downloadedbytes/(Sys_DoubleTime() - cls.downloadstarttime); + qdownload_t *dl = cls.download; + if (dl) + { + double curtime = Sys_DoubleTime(); + if (!dl->ratetime) + { + dl->ratetime = curtime; + return dl->completedbytes/(Sys_DoubleTime() - dl->starttime); + } + if (curtime - dl->ratetime > 1) + { + dl->rate = dl->ratebytes / (curtime - dl->ratetime); + dl->ratetime = curtime; + dl->ratebytes = 0; + } + return dl->rate; + } + return 0; +} + +#ifdef _MSC_VER +#define strtoull _strtoui64 +#endif + +//called when the server acks the download. opens the local file and stuff. returns false on failure +qboolean DL_Begun(qdownload_t *dl) +{ + //figure out where the file is meant to be going. + dl->prefixbytes = 0; + if (!strncmp(dl->tempname, "package/", 8)) + { + dl->prefixbytes = 8; //ignore the package/ part + dl->fsroot = FS_ROOT; //and put it in the root dir (-basedir), and hope the name includes a gamedir part + } + else if (!strncmp(dl->tempname,"skins/",6)) + dl->fsroot = FS_PUBBASEGAMEONLY; //shared between gamedirs, so only use the basegame. + else + dl->fsroot = FS_GAMEONLY; //other files are relative to the active gamedir. + + Q_snprintfz(dl->dclname, sizeof(dl->dclname), "%s.dcl", dl->tempname); + + if (dl->method == DL_QWCHUNKS) + { + qboolean error = false; + char partline[256]; + char partterm[128]; + char *p, t; + qofs_t lastend = 0; + qofs_t start, end; + struct dlblock_s **link = &dl->dlblocks; + vfsfile_t *parts = FS_OpenVFS(dl->dclname+dl->prefixbytes, "rb", dl->fsroot); + if (!parts) + error = true; + while(!error && VFS_GETS(parts, partline, sizeof(partline))) + { + p = COM_ParseOut(partline, partterm, sizeof(partterm)); + t = *partterm; + p = COM_ParseOut(p, partterm, sizeof(partterm)); + start = strtoull(partterm, NULL, 0); + p = COM_ParseOut(p, partterm, sizeof(partterm)); + end = strtoull(partterm, NULL, 0); + + (*link) = Z_Malloc(sizeof(**link)); + (*link)->start = start; + (*link)->end = end; + (*link)->state = (t == 'c')?DLB_RECEIVED:DLB_MISSING; + link = &(*link)->next; + + if (t == 'c') + dl->completedbytes += end - start; + + if (start != lastend) + error = true; + lastend = end; + } + if (lastend != dl->size) + error = true; + if (parts) + VFS_CLOSE(parts); + if (!error) + dl->file = FS_OpenVFS(dl->tempname+dl->prefixbytes, "w+b", dl->fsroot); + } + if (!dl->file) + { + struct dlblock_s *b; + //make sure we don't get confused if someone end-tasks us before the download is complete. + FS_Remove(dl->dclname+dl->prefixbytes, dl->fsroot); + dl->completedbytes = 0; + while (dl->dlblocks) + { + b = dl->dlblocks; + dl->dlblocks = b->next; + Z_Free(b); + } + FS_CreatePath(dl->tempname+dl->prefixbytes, dl->fsroot); + dl->file = FS_OpenVFS(dl->tempname+dl->prefixbytes, "wb", dl->fsroot); + } + if (!dl->file) + { + char native[MAX_OSPATH]; + FS_NativePath(dl->tempname+dl->prefixbytes, dl->fsroot, native, sizeof(native)); + Con_TPrintf("Unable to open \"%s\"\n", native); + return false; + } + + if (dl->method == DL_QWPENDING) + Con_TPrintf("method is still 'pending'\n"); + + if (dl->method == DL_QWCHUNKS && !dl->dlblocks) + { + dl->dlblocks = Z_Malloc(sizeof(*dl->dlblocks)); + dl->dlblocks->start = 0; + dl->dlblocks->end = dl->size; + dl->dlblocks->state = DLB_MISSING; + } + dl->flags |= DLLF_BEGUN; + + dl->starttime = Sys_DoubleTime(); + return true; +} + +void DL_Completed(qdownload_t *dl, qofs_t start, qofs_t end) +{ + struct dlblock_s *prev = NULL, *b, *n, *e; + if (end <= start) + return; //ignore invalid ranges. + for (b = dl->dlblocks; b; ) + { + if (b->state == DLB_RECEIVED) + { + //nothing to be done. dupe. somehow. or simply a different range. + } + else if (b->start >= start && b->end <= end) + { + //whole block +// Con_Printf("Whole block\n"); + b->state = DLB_RECEIVED; + dl->completedbytes += b->end - b->start; + dl->ratebytes += b->end - b->start; + } + else if (start > b->start && end < b->end) + { +// Con_Printf("chop out middle\n"); + //in the middle, no need to merge + n = Z_Malloc(sizeof(*n)); + e = Z_Malloc(sizeof(*e)); + e->next = b->next; + n->next = e; + b->next = n; + + e->state = b->state; + e->sequence = b->sequence; + n->state = DLB_RECEIVED; + + e->end = b->end; + b->end = start; + n->start = start; + n->end = end; + e->start = end; + + dl->completedbytes += n->end - n->start; + dl->ratebytes += n->end - n->start; + } + //data overlaps the start (data end must be smaller than block end) + else if (start <= b->start && end > b->start) + { +// Con_Printf("complete start\n"); + + //split it. new(non-complete) block is second. + n = Z_Malloc(sizeof(*n)); + n->next = b->next; + b->next = n; + //second block keeps original block's state. first block gets completed + n->state = b->state; + n->sequence = b->sequence; + b->state = DLB_RECEIVED; + + n->start = end; + n->end = b->end; + b->end = end; + + dl->completedbytes += b->end - b->start; + dl->ratebytes += b->end - b->start; + } + //new data overlaps the end + else if (start > b->start && start < b->end) + { +// Con_Printf("complete end\n"); + //split it. new(completed) block is second. + n = Z_Malloc(sizeof(*n)); + n->next = b->next; + b->next = n; + //second block keeps original block's state. first block gets completed + n->state = DLB_RECEIVED; + n->sequence = 0; + + n->start = end; + n->end = b->end; + b->end = end; + + dl->completedbytes += n->end - n->start; + dl->ratebytes += n->end - n->start; + + prev = b; + b = n; + } + else + {//don't bother merging, as nothing changed + prev = b; + b = b->next; + continue; + } + + //merge with next block + if (b->next && b->next->state == DLB_RECEIVED) + { + n = b->next; + b->next = n->next; + b->end = n->end; + Z_Free(n); + } + //merge with previous block if possible + if (prev && prev->state == DLB_RECEIVED) + { + n = b; + prev->end = b->end; + prev->next = b->next; + Z_Free(b); + b = prev->next; + continue;//careful here + } + prev = b; + b = b->next; + } } qboolean CL_AllowArbitaryDownload(char *localfile); -void CL_ParseChunkedDownload(void) +static float chunkrate; + +void CL_ParseChunkedDownload(qdownload_t *dl) { qbyte *svname; - //qbyte osname[MAX_OSPATH]; //unreferenced - int totalsize; - qofs_t chunknum, ridx; + int flag; + qofs_t filesize; + qofs_t chunknum; char data[DLBLOCKSIZE]; chunknum = MSG_ReadLong(); if (chunknum == -1) { - totalsize = MSG_ReadLong(); + flag = MSG_ReadLong(); + if (flag == 0x80000000) + { //really big files need special handling here. + flag = MSG_ReadLong(); + filesize = qofs_Make(flag, MSG_ReadLong()); + flag = 0; + } + else + filesize = flag; + svname = MSG_ReadString(); if (cls.demoplayback) return; @@ -1735,48 +1898,56 @@ void CL_ParseChunkedDownload(void) } } - if (totalsize < 0) + if (flag < 0) { - if (totalsize == -4) + if (flag == -4) { if (CL_AllowArbitaryDownload(svname)) { - Con_Printf("Download of \"%s\" redirected to \"%s\"\n", cls.downloadremotename, svname); + Con_Printf("Download of \"%s\" redirected to \"%s\"\n", dl->remotename, svname); if (CL_CheckOrEnqueDownloadFile(svname, NULL, 0)) Con_Printf("However, \"%s\" already exists. You may need to delete it.\n", svname); } - svname = cls.downloadremotename; + svname = dl->remotename; } - else if (totalsize == -3) + else if (flag == -3) Con_Printf("Server reported an error when downloading file \"%s\"\n", svname); - else if (totalsize == -2) + else if (flag == -2) Con_Printf("Server permissions deny downloading file \"%s\"\n", svname); else Con_Printf("Couldn't find file \"%s\" on the server\n", svname); - cls.downloadmethod = DL_NONE; - CL_DownloadFailed(svname, true); + if (dl) + { + CL_DownloadFailed(svname, dl); - CL_RequestNextDownload(); + CL_RequestNextDownload(); + } return; } - if (cls.downloadmethod == DL_QWCHUNKS) + if (!dl) + { + Con_Printf("ignoring download start. we're not meant to be downloading\n"); + return; + } + + if (dl->method == DL_QWCHUNKS) Host_EndGame("Received second download - \"%s\"\n", svname); - if (stricmp(cls.downloadremotename, svname)) + if (stricmp(dl->remotename, svname)) { //fixme: we should allow extension changes, in the case of ogg/mp3/wav, or tga/png/jpg/pcx, or the addition of .gz or whatever - Host_EndGame("Server sent the wrong download - \"%s\" instead of \"%s\"\n", svname, cls.downloadremotename); + Host_EndGame("Server sent the wrong download - \"%s\" instead of \"%s\"\n", svname, dl->remotename); } //start the new download - cls.downloadmethod = DL_QWCHUNKS; - cls.downloadpercent = 0; - downloadsize = totalsize; + dl->method = DL_QWCHUNKS; + dl->percent = 0; + dl->size = filesize; - cls.downloadstarttime = Sys_DoubleTime(); + dl->starttime = Sys_DoubleTime(); /* strcpy(cls.downloadname, svname); @@ -1784,34 +1955,12 @@ void CL_ParseChunkedDownload(void) COM_DefaultExtension(cls.downloadtempname, ".tmp"); */ - if (!strncmp(cls.downloadtempname, "package/", 8)) + if (!DL_Begun(dl)) { - FS_CreatePath(cls.downloadtempname+8, FS_ROOT); - cls.downloadqw = FS_OpenVFS(cls.downloadtempname + 8, "wb", FS_ROOT); - } - else if (!strncmp(cls.downloadtempname,"skins/",6)) - { - FS_CreatePath (cls.downloadtempname, FS_PUBBASEGAMEONLY); - cls.downloadqw = FS_OpenVFS (cls.downloadtempname, "wb", FS_PUBBASEGAMEONLY); - } - else - { - FS_CreatePath (cls.downloadtempname, FS_GAME); - cls.downloadqw = FS_OpenVFS (cls.downloadtempname, "wb", FS_GAME); - } - - if (!cls.downloadqw) - { - CL_DownloadFailed(svname, true); + CL_DownloadFailed(svname, dl); return; } - download_file_number++; - firstblock = 0; - cls.downloadedbytes = 0; - blockcycle = -1; //so it requests 0 first. :) - for (chunknum = 0; chunknum < MAXBLOCKS; chunknum++) - dlblock[chunknum].chunkno = ~0u; return; } @@ -1819,10 +1968,16 @@ void CL_ParseChunkedDownload(void) MSG_ReadData(data, DLBLOCKSIZE); - if (chunknum*DLBLOCKSIZE > downloadsize+DLBLOCKSIZE) + if (!dl) + { + Con_Printf("ignoring download data packet\n"); + return; + } + + if (chunknum*DLBLOCKSIZE > dl->size+DLBLOCKSIZE) return; - if (!cls.downloadqw) + if (!dl->file) return; if (cls.demoplayback) @@ -1830,28 +1985,17 @@ void CL_ParseChunkedDownload(void) return; } - for (ridx = 0; ridx < MAXBLOCKS; ridx++) - { - if (dlblock[ridx].chunkno == chunknum) - break; - } - if (ridx == MAXBLOCKS) - { - Con_DPrintf("dupe/invalid chunk received\n"); - return; - } - dlblock[ridx].chunkno = ~0; - -// Con_Printf("usable\n", chunknum); - cls.downloadedbytes+=DLBLOCKSIZE; - - VFS_SEEK(cls.downloadqw, chunknum*DLBLOCKSIZE); - if (downloadsize - chunknum*DLBLOCKSIZE < DLBLOCKSIZE) //final block is actually meant to be smaller than we recieve. - VFS_WRITE(cls.downloadqw, data, downloadsize - chunknum*DLBLOCKSIZE); + 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); else - VFS_WRITE(cls.downloadqw, data, DLBLOCKSIZE); + VFS_WRITE(dl->file, data, DLBLOCKSIZE); - cls.downloadpercent = cls.downloadedbytes/(float)downloadsize*100; + DL_Completed(dl, chunknum*DLBLOCKSIZE, (chunknum+1)*DLBLOCKSIZE); + + dl->percent = dl->completedbytes/(float)dl->size*100; + + chunkrate += 1; } int CL_CountQueuedDownloads(void) @@ -1864,49 +2008,250 @@ int CL_CountQueuedDownloads(void) return count; } -int CL_RequestADownloadChunk(void) +static void DLC_RequestDownloadChunks(qdownload_t *dl, float frametime) { - int i; - int ridx; + char *cmd; + qofs_t chunk; + struct dlblock_s *b, *n; + qboolean stillpending = false; + qboolean haveloss = false; + int chunks, chunksremaining; + static float slop; //try to keep things as integers +// int cmds = 20; + if (frametime < 0) + frametime = 0; + if (frametime > 0.1) + frametime = 0.1; //erg? - if (cls.downloadmethod != DL_QWCHUNKS) + if (chunkrate < 0) + chunkrate = 0; + slop += chunkrate*frametime; + chunksremaining = slop; + slop -= chunksremaining; + if (chunksremaining < 1) { - Con_Printf("download not initiated\n"); - return -1; + if (chunkrate < 30) + chunksremaining = 1; + else + return; +/* if (!chunkrate) + chunkrate = 72; + else + chunkrate+=frametime; + return; +*/ } + if (chunksremaining > 100) + { //we're going to need some sanity limit, for cpu resources. + chunkrate -= (chunksremaining-100); + chunksremaining = 100; } - - for (i = 0; i < MAXBLOCKS; i++) +//Con_DPrintf("%i\n", chunksremaining); + for (b = dl->dlblocks; b; b = b->next) { - blockcycle++; - ridx = blockcycle&(MAXBLOCKS-1); - if (dlblock[ridx].chunkno == ~0u) + //packetloss reverts blocks to missing. + if (b->state == DLB_PENDING) { - if (firstblock >= (downloadsize+DLBLOCKSIZE-1)/DLBLOCKSIZE) - continue; - dlblock[ridx].chunkno = firstblock++; + if (b->sequence < cls.netchan.incoming_sequence-10) + { + haveloss = true; + b->state = DLB_MISSING; + for (;;) //merge it with the next if they're all invalid + { + n = b->next; + if (!n) + break; + if (n->state == DLB_MISSING || (n->state == DLB_PENDING && n->sequence < cls.netchan.incoming_sequence-10)) + { + b->next = n->next; + b->end = n->end; + Z_Free(n); + continue; + } + break; + } + } + else + stillpending = true; + } + if (b->state == DLB_MISSING && chunksremaining) + { + chunk = b->start / DLBLOCKSIZE; + chunks = 1;//((b->end+DLBLOCKSIZE-1)/DLBLOCKSIZE) - (b->start / DLBLOCKSIZE); + if (chunks > chunksremaining) + chunks = chunksremaining; + + //if this block is bigger than a chunk, split the two blocks. + if (b->end - b->start > DLBLOCKSIZE*chunks) + { + n = Z_Malloc(sizeof(*n)); + n->next = b->next; + n->start = (chunk+chunks)*DLBLOCKSIZE; + n->end = b->end; + b->end = n->start; + n->state = DLB_MISSING; + b->next = n; + } + b->state = DLB_PENDING; + b->sequence = cls.netchan.outgoing_sequence; + stillpending = true; + + if (chunks > 1) + cmd = va("nextdl %u %3g %i %i\n", (unsigned int)chunk, dl->percent, dl->filesequence, chunks); + else + cmd = va("nextdl %u %3g %i\n", (unsigned int)chunk, dl->percent, dl->filesequence); + CL_RemoveClientCommands(cmd); + CL_SendClientCommand(false, "%s", cmd); + chunksremaining -= chunks; + if (chunksremaining <= 0) + break; + /*if (--cmds <= 0) + { + chunkrate -= chunksremaining; +// haveloss = true; + break; + }*/ } - return dlblock[ridx].chunkno; } + if (haveloss) + { + chunkrate *= 0.98; + } + if (!stillpending) + { //when there's nothing still pending, the download is complete. + Con_DPrintf("Download took %i seconds (%i more)\n", (int)(Sys_DoubleTime() - dl->starttime), CL_CountQueuedDownloads()); + CL_DownloadFinished(dl); + } +} -// Con_Printf("^1 EOF?\n"); - - VFS_CLOSE(cls.downloadqw); - cls.downloadqw = NULL; - - CL_SendClientCommand(true, "stopdownload"); - CL_DownloadFinished(); - - Con_DPrintf("Download took %i seconds (%i more)\n", (int)(Sys_DoubleTime() - cls.downloadstarttime), CL_CountQueuedDownloads()); - - *cls.downloadlocalname = '\0'; - *cls.downloadremotename = '\0'; - cls.downloadpercent = 0; - - return -1; +void DLC_Poll(qdownload_t *dl) +{ + extern cvar_t drate; + static float lasttime; + DLC_RequestDownloadChunks(dl, realtime - lasttime); + lasttime = realtime; } #endif +void DL_Abort(qdownload_t *dl, enum qdlabort aborttype) +{ + struct dlblock_s *b, *n; + + if (dl->file) + { + VFS_CLOSE(dl->file); + dl->file = NULL; + } + + if (dl->flags & DLLF_BEGUN) + { + if (aborttype == QDL_COMPLETED) + { + //this file isn't needed now the download has finished. + FS_Remove(dl->dclname+dl->prefixbytes, dl->fsroot); + + if (dl->flags & DLLF_TEMPORARY) + { + if (!Terr_DownloadedSection(dl->tempname+dl->prefixbytes)) + Con_Printf("Downloaded unusable temporary file\n"); + FS_Remove(dl->tempname+dl->prefixbytes, dl->fsroot); + } + else if (Q_strcasecmp(dl->tempname, dl->localname)) + { + if (dl->flags & DLLF_OVERWRITE) + FS_Remove(dl->localname+dl->prefixbytes, dl->fsroot); + if (!FS_Rename(dl->tempname+dl->prefixbytes, dl->localname+dl->prefixbytes, dl->fsroot)) + { + char nativetmp[MAX_OSPATH], nativefinal[MAX_OSPATH]; + FS_NativePath(dl->tempname+dl->prefixbytes, dl->fsroot, nativetmp, sizeof(nativetmp)); + FS_NativePath(dl->localname+dl->prefixbytes, dl->fsroot, nativefinal, sizeof(nativefinal)); + Con_Printf("Couldn't rename %s to %s\n", nativetmp, nativefinal); + } + } + } + else + { + //file was aborted half way through... + if (dl->dlblocks) + { + //save the list of valid chunks so we don't have to redownload those. + vfsfile_t *parts; + parts = FS_OpenVFS(dl->dclname+dl->prefixbytes, "wb", dl->fsroot); + if (parts) + { + for (b = dl->dlblocks; b; b = n) + { + if (b->state == DLB_RECEIVED) + VFS_PRINTF(parts, "c %#llx %#llx\n", (long long)b->start, (long long)b->end); + else + { + for(;;) + { + n = b->next; + if (n && n->state != DLB_RECEIVED) + { + b->end = n->end; + b->next = n->next; + Z_Free(n); + continue; + } + break; + } + VFS_PRINTF(parts, "m %#llx %#llx\n", (long long)b->start, (long long)b->end); + } + + n = b->next; + Z_Free(b); + } + dl->dlblocks = NULL; + VFS_CLOSE(parts); + } + else + FS_Remove(dl->tempname + dl->prefixbytes, dl->fsroot); + } + else + { + //download looks like it was non-resumable. just delete it. + FS_Remove(dl->tempname + dl->prefixbytes, dl->fsroot); + } + } + + if (aborttype != QDL_DISCONNECT) + { + switch(dl->method) + { + default: + break; +#ifdef Q3CLIENT + case DL_Q3: + CLQ3_SendClientCommand("stopdl"); + break; +#endif + case DL_DARKPLACES: + case DL_QW: + case DL_QWCHUNKS: + { + char *serverversion = Info_ValueForKey(cl.serverinfo, "*version"); + if (strncmp(serverversion , "MVDSV ", 6)) //don't tell mvdsv to stop, because it has retarded annoying clientprints that are spammy as fuck, and we don't want that. + CL_SendClientCommand(true, "stopdownload"); + } + break; + } + } + } + + for (b = dl->dlblocks; b; b = n) + { + n = b->next; + Z_Free(b); + } + dl->dlblocks = NULL; + + Z_Free(dl); + if (cls.download == dl) + cls.download = NULL; +} + /* ===================== CL_ParseDownload @@ -1919,13 +2264,14 @@ void CL_ParseDownload (void) extern cvar_t cl_dlemptyterminate; int size, percent; qbyte name[1024]; + qdownload_t *dl = cls.download; #ifdef PEXT_CHUNKEDDOWNLOADS if (cls.fteprotocolextensions & PEXT_CHUNKEDDOWNLOADS) { if (cls.demoplayback == DPB_EZTV) Host_EndGame("CL_ParseDownload: chunked download on qtv proxy."); - CL_ParseChunkedDownload(); + CL_ParseChunkedDownload(dl); return; } #endif @@ -1947,13 +2293,8 @@ void CL_ParseDownload (void) requestedname = MSG_ReadString(); Con_DPrintf("Download for %s redirected to %s\n", requestedname, name); /*quakeforge http download redirection*/ - if (cls.downloadqw) - { - Con_Printf ("cls.download shouldn't have been set\n"); - VFS_CLOSE (cls.downloadqw); - cls.downloadqw = NULL; - } - CL_DownloadFailed(cls.downloadremotename, true); + if (dl) + CL_DownloadFailed(dl->remotename, dl); //FIXME: find some safe way to do this and actually test it. we should already know the local name, but we might have gained a .gz or something (this is quakeforge after all). // CL_CheckOrEnqueDownloadFile(name, localname, DLLF_IGNOREFAILED); return; @@ -1961,62 +2302,42 @@ void CL_ParseDownload (void) if (cls.demoplayback && cls.demoplayback != DPB_EZTV) { + if (size > 0) + msg_readcount += size; + return; // not in demo playback, we don't know the name of the file. + } + if (!dl) + { + //download packet without file requested. if (size > 0) msg_readcount += size; return; // not in demo playback } - if (!*cls.downloadlocalname) //huh... that's not right... - { - Con_Printf(CON_WARNING "Warning: Server sending unknown file.\n"); - strcpy(cls.downloadlocalname, "unknown.txt"); - strcpy(cls.downloadtempname, "unknown.tmp"); - } - if (!*cls.downloadremotename) - strcpy(cls.downloadremotename, "unknown.txt"); - if (size < 0) { Con_TPrintf ("File not found.\n"); - if (cls.downloadqw) - { - Con_Printf ("cls.download shouldn't have been set\n"); - VFS_CLOSE (cls.downloadqw); - cls.downloadqw = NULL; - } - CL_DownloadFailed(cls.downloadremotename, true); + if (dl) + CL_DownloadFailed(dl->remotename, dl); return; } // open the file if not opened yet - if (!cls.downloadqw) + if (dl->method == DL_QWPENDING) { - if (!strncmp(cls.downloadtempname,"skins/",6)) - { - Q_snprintfz(name, sizeof(name), "%s", cls.downloadtempname); - FS_CreatePath (name, FS_PUBBASEGAMEONLY); - cls.downloadqw = FS_OpenVFS (name, "wb", FS_PUBBASEGAMEONLY); - } - else - { - Q_snprintfz(name, sizeof(name), "%s", cls.downloadtempname); - FS_CreatePath (name, FS_GAMEONLY); - cls.downloadqw = FS_OpenVFS (name, "wb", FS_GAMEONLY); - } - if (!cls.downloadqw) + dl->method = DL_QW; + if (!DL_Begun(dl)) { msg_readcount += size; - Con_TPrintf ("Failed to open %s\n", cls.downloadtempname); - CL_DownloadFailed(cls.downloadremotename, true); + Con_TPrintf ("Failed to open %s\n", dl->tempname); + CL_DownloadFailed(dl->remotename, dl); CL_RequestNextDownload (); return; } - - cls.downloadstarttime = Sys_DoubleTime(); - cls.downloadedbytes = 0; SCR_EndLoadingPlaque(); } + #ifdef PEXT_ZLIBDL if (percent >= 101 && percent <= 201)// && cls.fteprotocolextensions & PEXT_ZLIBDL) { @@ -2031,42 +2352,33 @@ void CL_ParseDownload (void) else #endif { - VFS_WRITE (cls.downloadqw, net_message.data + msg_readcount, size); + VFS_WRITE (dl->file, net_message.data + msg_readcount, size); msg_readcount += size; } - cls.downloadedbytes += size; - if (cls.downloadpercent != percent) //try and guess the size (its most acurate when the percent value changes) - downloadsize = ((float)cls.downloadedbytes*100)/percent; - - if (cls.downloadmethod == DL_QWPENDING) - cls.downloadmethod = DL_QW; + dl->completedbytes += size; + dl->ratebytes += size; + if (dl->percent != percent) //try and guess the size (its most acurate when the percent value changes) + dl->size = ((float)dl->completedbytes*100)/percent; if (percent != 100 && size == 0 && cl_dlemptyterminate.ival) { - Con_Printf(CON_WARNING "WARNING: Client received empty svc_download, assuming EOF\n"); + Con_Printf(CON_WARNING "WARNING: Client received empty svc_download, assuming EOF.\n"); percent = 100; } if (percent != 100) { -// change display routines by zoid // request next block - cls.downloadpercent = percent; + dl->percent = percent; CL_SendClientCommand(true, "nextdl"); } else { - VFS_CLOSE (cls.downloadqw); + Con_DPrintf("Download took %i seconds\n", (int)(Sys_DoubleTime() - dl->starttime)); - CL_DownloadFinished(); - *cls.downloadlocalname = '\0'; - *cls.downloadremotename = '\0'; - cls.downloadqw = NULL; - cls.downloadpercent = 0; - - Con_DPrintf("Download took %i seconds\n", (int)(Sys_DoubleTime() - cls.downloadstarttime)); + CL_DownloadFinished(dl); // get another file if needed @@ -2076,7 +2388,11 @@ void CL_ParseDownload (void) qboolean CL_ParseOOBDownload(void) { - if (MSG_ReadLong() != download_file_number) + qdownload_t *dl = cls.download; + if (!dl) + return false; + + if (MSG_ReadLong() != dl->filesequence) return false; if (MSG_ReadChar() != svc_download) @@ -2088,6 +2404,7 @@ qboolean CL_ParseOOBDownload(void) void CLDP_ParseDownloadData(void) { + qdownload_t *dl = cls.download; unsigned char buffer[1<<16]; qofs_t start; int size; @@ -2096,12 +2413,15 @@ void CLDP_ParseDownloadData(void) MSG_ReadData(buffer, size); - if (cls.downloadqw) - { - VFS_SEEK(cls.downloadqw, start); - VFS_WRITE(cls.downloadqw, buffer, size); + if (!dl) + return; - cls.downloadpercent = start / (float)VFS_GETLEN(cls.downloadqw) * 100; + if (dl->file) + { + VFS_SEEK(dl->file, start); + VFS_WRITE(dl->file, buffer, size); + + dl->percent = (start+size) / (float)VFS_GETLEN(dl->file) * 100; } //this is only reliable because I'm lazy @@ -2112,6 +2432,7 @@ void CLDP_ParseDownloadData(void) void CLDP_ParseDownloadBegin(char *s) { + qdownload_t *dl = cls.download; char buffer[8192]; unsigned int size, pos, chunk; char *fname; @@ -2119,80 +2440,72 @@ void CLDP_ParseDownloadBegin(char *s) size = (unsigned int)atoi(Cmd_Argv(1)); fname = Cmd_Argv(2); - if (strcmp(fname, cls.downloadlocalname)) + if (!dl || strcmp(fname, dl->remotename)) { Con_Printf("Warning: server started sending a file we did not request. Ignoring.\n"); return; } - COM_StripExtension (fname, cls.downloadtempname, sizeof(cls.downloadtempname)-5); - strcat (cls.downloadtempname, ".tmp"); + if (dl->method != DL_DARKPLACES) + { + Con_Printf("Warning: download method isn't right.\n"); + return; + } + + dl->sizeunknown = false; + dl->size = size; + if (!DL_Begun(dl)) + { + CL_DownloadFailed(dl->remotename, dl); + return; + } CL_SendClientCommand(true, "sv_startdownload"); - if (cls.downloadqw) + //fill the file with 0 bytes, for some reason + memset(buffer, 0, sizeof(buffer)); + for (pos = 0, chunk = 1; chunk; pos += chunk) { - Con_Printf("Warning: cl_begindownload while already downloading\n"); - VFS_CLOSE(cls.downloadqw); + chunk = size - pos; + if (chunk > sizeof(buffer)) + chunk = sizeof(buffer); + VFS_WRITE(dl->file, buffer, chunk); } - - FS_CreatePath (cls.downloadtempname, FS_GAMEONLY); - cls.downloadqw = FS_OpenVFS (cls.downloadtempname, "wb", FS_GAMEONLY); - cls.downloadmethod = DL_DARKPLACES; - - Q_strncpyz(cls.downloadlocalname, fname, sizeof(cls.downloadlocalname)); - cls.downloadstarttime = Sys_DoubleTime(); - cls.downloadedbytes = 0; - - if (cls.downloadqw) - { - //fill the file with 0 bytes - memset(buffer, 0, sizeof(buffer)); - for (pos = 0, chunk = 1; chunk; pos += chunk) - { - chunk = size - pos; - if (chunk > sizeof(buffer)) - chunk = sizeof(buffer); - VFS_WRITE(cls.downloadqw, buffer, chunk); - } - } - else - CL_DownloadFailed(cls.downloadremotename, true); - - cls.downloadstarttime = Sys_DoubleTime(); } void CLDP_ParseDownloadFinished(char *s) { + qdownload_t *dl = cls.download; unsigned short runningcrc = 0; char buffer[8192]; int size, pos, chunk; - if (!cls.downloadqw) + if (!dl || !dl->file) return; Cmd_TokenizeString(s+1, false, false); - VFS_CLOSE (cls.downloadqw); + VFS_CLOSE (dl->file); - cls.downloadqw = FS_OpenVFS (cls.downloadtempname, "rb", FS_GAME); - if (cls.downloadqw) + dl->file = FS_OpenVFS (dl->tempname+dl->prefixbytes, "rb", dl->fsroot); + if (dl->file) { - size = VFS_GETLEN(cls.downloadqw); + size = dl->size; QCRC_Init(&runningcrc); for (pos = 0, chunk = 1; chunk; pos += chunk) { chunk = size - pos; if (chunk > sizeof(buffer)) chunk = sizeof(buffer); - VFS_READ(cls.downloadqw, buffer, chunk); + VFS_READ(dl->file, buffer, chunk); QCRC_AddBlock(&runningcrc, buffer, chunk); } - VFS_CLOSE (cls.downloadqw); + VFS_CLOSE (dl->file); + dl->file = NULL; } else { Con_Printf("Download failed: unable to check CRC of download\n"); - CL_DownloadFailed(cls.downloadremotename, true); + CL_DownloadFailed(dl->remotename, dl); return; } @@ -2200,23 +2513,19 @@ void CLDP_ParseDownloadFinished(char *s) if (size != atoi(Cmd_Argv(1))) { Con_Printf("Download failed: wrong file size\n"); - CL_DownloadFailed(cls.downloadremotename, true); + CL_DownloadFailed(dl->remotename, dl); return; } if (runningcrc != atoi(Cmd_Argv(2))) { Con_Printf("Download failed: wrong crc\n"); - CL_DownloadFailed(cls.downloadremotename, true); + CL_DownloadFailed(dl->remotename, dl); return; } - CL_DownloadFinished(); - *cls.downloadlocalname = '\0'; - *cls.downloadremotename = '\0'; - cls.downloadqw = NULL; - cls.downloadpercent = 0; + Con_DPrintf("Download took %i seconds\n", (int)(Sys_DoubleTime() - dl->starttime)); - Con_DPrintf("Download took %i seconds\n", (int)(Sys_DoubleTime() - cls.downloadstarttime)); + CL_DownloadFinished(dl); // get another file if needed @@ -2390,11 +2699,11 @@ void CLQW_ParseServerData (void) float maxspeed, entgrav; - if (cls.downloadmethod == DL_QWPENDING) + if (cls.download && cls.download->method == DL_QWPENDING) { //if we didn't actually start downloading it yet, cancel the current download. //this is to avoid qizmo not responding to the download command, resulting in hanging downloads that cause the client to then be unable to connect anywhere simply because someone's skin was set. - CL_DownloadFailed(cls.downloadremotename, true); + CL_DownloadFailed(cls.download->remotename, cls.download); } Con_DPrintf ("Serverdata packet received.\n"); @@ -6389,7 +6698,10 @@ void CLNQ_ParseServerMessage (void) else if (!strncmp(s, "\ncl_downloadfinished ", 17)) CLDP_ParseDownloadFinished(s); else if (!strcmp(s, "\nstopdownload\n")) - CL_DownloadFailed(cls.downloadremotename, true); + { + if (cls.download) + CL_DownloadFailed(cls.download->remotename, cls.download); + } else if (!strncmp(s, "csqc_progname ", 14)) COM_ParseOut(s+14, cl_dp_csqc_progsname, sizeof(cl_dp_csqc_progsname)); else if (!strncmp(s, "csqc_progsize ", 14)) diff --git a/engine/client/cl_pred.c b/engine/client/cl_pred.c index b6f561b3..42a7c27b 100644 --- a/engine/client/cl_pred.c +++ b/engine/client/cl_pred.c @@ -537,7 +537,7 @@ void CL_CalcClientTime(void) extern float demtime; if (!cls.state) cl.servertime += host_frametime; - else if (cls.protocol != CP_QUAKE3) + else// if (cls.protocol != CP_QUAKE3) { float oldst = realtime; @@ -549,7 +549,7 @@ void CL_CalcClientTime(void) f = bound(0, f, 1); cl.servertime = cl.gametime*f + cl.oldgametime*(1-f); } - else if (!cl_predict_smooth.ival || (cl_predict_smooth.ival == 2 && !cls.demoplayback)) + else if (cls.protocol != CP_QUAKE3 && (!cl_predict_smooth.ival || (cl_predict_smooth.ival == 2 && !cls.demoplayback))) { float f; f = cl.gametime - cl.oldgametime; diff --git a/engine/client/cl_screen.c b/engine/client/cl_screen.c index ec4957db..6ed660b9 100644 --- a/engine/client/cl_screen.c +++ b/engine/client/cl_screen.c @@ -1571,20 +1571,20 @@ void SCR_DrawLoading (qboolean opaque) } R2D_ImageColours(1, 1, 1, 1); - if (cl.downloadlist || cls.downloadmethod) + if (cl.downloadlist || cls.download) { unsigned int fcount; - unsigned int tsize; + qofs_t tsize; qboolean sizeextra; x = vid.width/2 - 160; CL_GetDownloadSizes(&fcount, &tsize, &sizeextra); //downloading files? - if (cls.downloadmethod) + if (cls.download) Draw_FunString(x+8, y+4, va("Downloading %s... %i%%", - cls.downloadlocalname, - (int)cls.downloadpercent)); + cls.download->localname, + (int)cls.download->percent)); if (tsize > 1024*1024*16) { @@ -2017,7 +2017,7 @@ void SCR_DrawCharToSnap (int num, qbyte *dest, int width) if (!draw_chars) { - draw_chars = W_GetLumpName("conchars"); + draw_chars = W_SafeGetLumpName("conchars"); if (!draw_chars) return; } diff --git a/engine/client/client.h b/engine/client/client.h index 631e05e3..479ab806 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -324,6 +324,58 @@ typedef enum { dl_singlestuffed } dltype_t; // download type +typedef struct qdownload_s +{ + enum {DL_QW, DL_QWCHUNKS, DL_Q3, DL_DARKPLACES, DL_QWPENDING, DL_HTTP, DL_FTP} method; + vfsfile_t *file; // file transfer from server + char dclname[MAX_OSPATH]; //file to read/write the chunklist from, for download resumption. + char tempname[MAX_OSPATH]; //file its currently writing to. + char localname[MAX_OSPATH]; //file its going to be renamed to. + int prefixbytes; //number of bytes that prefix the above names (ie: package/ or nothing). + char remotename[MAX_OSPATH]; //file its coming from. + float percent; //for progress indicator. + float starttime; //for speed info + qofs_t completedbytes; //number of bytes downloaded, for progress/speed info + qofs_t size; //total size (may be a guess) + qboolean sizeunknown; //says that size is a guess + unsigned int filesequence; //unique file id. + enum fs_relative fsroot; //where the local+temp file is meant to be relative to. + + double ratetime; + int rate; + int ratebytes; + unsigned int flags; + + //chunked downloads uses this + struct dlblock_s + { + qofs_t start; + qofs_t end; + enum + { + DLB_MISSING, + DLB_PENDING, + DLB_RECEIVED + } state:16; + unsigned int sequence; //sequence is only valid on pending blocks. + + struct dlblock_s *next; + } *dlblocks; +} qdownload_t; +enum qdlabort +{ + QDL_FAILED, //delete file, tell server. + QDL_DISCONNECT, //delete file, don't tell server. + QDL_COMPLETED, //rename file, tell server. +}; +qboolean DL_Begun(qdownload_t *dl); +void DL_Completed(qdownload_t *dl, qofs_t start, qofs_t end); //notifies the download logic that a chunk of the file is no longer needed. +void DL_Abort(qdownload_t *dl, enum qdlabort aborttype); //just frees the download's resources. does not delete the temp file. + +//chunked downloads +void DLC_Poll(qdownload_t *dl); + + // // the client_static_t structure is persistant through an arbitrary number // of server connections @@ -384,15 +436,7 @@ typedef struct struct ftenet_connections_s *sockets; - enum {DL_NONE, DL_QW, DL_QWCHUNKS, DL_Q3, DL_DARKPLACES, DL_QWPENDING, DL_HTTP, DL_FTP} downloadmethod; - vfsfile_t *downloadqw; // file transfer from server - char downloadtempname[MAX_OSPATH]; //file its currently writing to. - char downloadlocalname[MAX_OSPATH]; //file its going to be renamed to. - char downloadremotename[MAX_OSPATH]; //file its coming from. - float downloadpercent; //for progress indicator. - int downloadchunknum; //for QW downloads only. - float downloadstarttime; //for speed info - unsigned int downloadedbytes; //number of bytes downloaded, for progress/speed info + qdownload_t *download; // demo loop control int demonum; // -1 = don't play demos @@ -452,13 +496,15 @@ typedef struct downloadlist_s { char localname[128]; unsigned int size; unsigned int flags; -#define DLLF_VERBOSE 1 //tell the user that its downloading -#define DLLF_REQUIRED 2 //means that it won't load models etc until its downloaded (ie: requiredownloads 0 makes no difference) -#define DLLF_OVERWRITE 4 //overwrite it even if it already exists -#define DLLF_SIZEUNKNOWN 8 //download's size isn't known -#define DLLF_IGNOREFAILED 16 // -#define DLLF_NONGAME 32 //means the requested download filename+localname is gamedir explicit (so id1/foo.txt is distinct from qw/foo.txt) -#define DLLF_TEMPORARY 64 //download it, but don't actually save it (DLLF_OVERWRITE doesn't actually overwrite, but does ignore any local files) +#define DLLF_VERBOSE (1u<<0) //tell the user that its downloading +#define DLLF_REQUIRED (1u<<1) //means that it won't load models etc until its downloaded (ie: requiredownloads 0 makes no difference) +#define DLLF_OVERWRITE (1u<<2) //overwrite it even if it already exists +#define DLLF_SIZEUNKNOWN (1u<<3) //download's size isn't known +#define DLLF_IGNOREFAILED (1u<<4) // +#define DLLF_NONGAME (1u<<5) //means the requested download filename+localname is gamedir explicit (so id1/foo.txt is distinct from qw/foo.txt) +#define DLLF_TEMPORARY (1u<<6) //download it, but don't actually save it (DLLF_OVERWRITE doesn't actually overwrite, but does ignore any local files) + +#define DLLF_BEGUN (1u<<7) //server has confirmed that the file exists, is readable, and we've opened a file. should not be set on new requests. struct downloadlist_s *next; } downloadlist_t; @@ -1033,11 +1079,11 @@ void CL_NewTranslation (int slot); int CL_IsDownloading(const char *localname); qboolean CL_CheckOrEnqueDownloadFile (const char *filename, const char *localname, unsigned int flags); qboolean CL_EnqueDownload(const char *filename, const char *localname, unsigned int flags); -downloadlist_t *CL_DownloadFailed(const char *name, qboolean cancel); +downloadlist_t *CL_DownloadFailed(const char *name, qdownload_t *qdl); int CL_DownloadRate(void); -void CL_GetDownloadSizes(unsigned int *filecount, unsigned int *totalsize, qboolean *somesizesunknown); +void CL_GetDownloadSizes(unsigned int *filecount, qofs_t *totalsize, qboolean *somesizesunknown); qboolean CL_ParseOOBDownload(void); -void CL_DownloadFinished(void); +void CL_DownloadFinished(qdownload_t *dl); void CL_RequestNextDownload (void); void CL_SendDownloadReq(sizebuf_t *msg); void Sound_CheckDownload(const char *s); /*checkorenqueue a sound file*/ diff --git a/engine/client/clq3_parse.c b/engine/client/clq3_parse.c index 7cf73c18..4efb8318 100644 --- a/engine/client/clq3_parse.c +++ b/engine/client/clq3_parse.c @@ -8,7 +8,8 @@ #include "clq3defs.h" -#define CMD_MASK Q3UPDATE_MASK +#define CMD_BACKUP UPDATE_BACKUP +#define CMD_MASK UPDATE_MASK #define SHOWSTRING(s) if(cl_shownet.value==2)Con_Printf ("%s\n", s); #define SHOWNET(x) if(cl_shownet.value==2)Con_Printf ("%3i:%s\n", msg_readcount-1, x); @@ -263,7 +264,12 @@ void CLQ3_ParseSnapshot(void) snap.serverMessageNum = ccs.serverMessageNum; snap.serverCommandNum = ccs.lastServerCommandNum; snap.serverTime = MSG_ReadLong(); - snap.localTime = Sys_Milliseconds(); + + //so we can delta to it properly. + cl.oldgametime = cl.gametime; + cl.oldgametimemark = cl.gametimemark; + cl.gametime = snap.serverTime / 1000.0f; + cl.gametimemark = Sys_DoubleTime(); // If the frame is delta compressed from data that we // no longer have available, we must suck up the rest of @@ -327,9 +333,9 @@ void CLQ3_ParseSnapshot(void) // Find last usercmd server has processed and calculate snap.ping snap.ping = 3; - for (i=cls.netchan.outgoing_sequence-1 ; i>cls.netchan.outgoing_sequence-Q3UPDATE_BACKUP ; i--) + for (i=cls.netchan.outgoing_sequence-1 ; i>cls.netchan.outgoing_sequence-CMD_BACKUP ; i--) { - frame = &cl.outframes[i & Q3UPDATE_MASK]; + frame = &cl.outframes[i & CMD_MASK]; if (frame->server_message_num == snap.deltaFrame) { snap.ping = Sys_Milliseconds() - frame->client_time; @@ -343,37 +349,34 @@ void CLQ3_ParseSnapshot(void) SHOWSTRING(va("snapshot:%i delta:%i ping:%i", snap.serverMessageNum, snap.deltaFrame, snap.ping)); } -#define MAXCHUNKSIZE 2048 +#define MAXCHUNKSIZE 65536 void CLQ3_ParseDownload(void) { + qdownload_t *dl = cls.download; unsigned int chunknum; - static unsigned int downloadsize; unsigned int chunksize; unsigned char chunkdata[MAXCHUNKSIZE]; int i; char *s; chunknum = (unsigned short) MSG_ReadShort(); - - if (downloadsize >= MAXCHUNKSIZE*0xffff) - { - chunknum |= ccs.downloadchunknum&0x10000; //add the chunk number, truncated by the network protocol. - } + chunknum |= ccs.downloadchunknum&~0xffff; //add the chunk number, truncated by the network protocol. if (!chunknum) { - downloadsize = MSG_ReadLong(); - Cvar_SetValue( Cvar_Get("cl_downloadSize", "0", 0, "Download stuff"), downloadsize ); + dl->size = (unsigned int)MSG_ReadLong(); + Cvar_SetValue( Cvar_Get("cl_downloadSize", "0", 0, "Download stuff"), dl->size ); } - if (downloadsize == (unsigned int)-1) + if (dl->size == (unsigned int)-1) { s = MSG_ReadString(); Con_Printf("\nDownload refused:\n %s\n", s); + CL_DownloadFailed(dl->remotename, dl); return; } - chunksize = MSG_ReadShort(); + chunksize = (unsigned short)MSG_ReadShort(); if (chunksize > MAXCHUNKSIZE) Host_EndGame("Server sent a download chunk of size %i (it's too damn big!)\n", chunksize); @@ -387,53 +390,102 @@ void CLQ3_ParseDownload(void) } ccs.downloadchunknum++; - if (!cls.downloadqw) + if (!dl || dl->method != DL_Q3) { - if (!*cls.downloadtempname) - { - Con_Printf("Server sending download, but no download was requested\n"); - CLQ3_SendClientCommand("stopdl"); - cls.downloadmethod = DL_NONE; - return; - } + Con_Printf("Server sending download, but no download was requested\n"); + CLQ3_SendClientCommand("stopdl"); + return; + } - FS_CreatePath(cls.downloadtempname, FS_ROOT); - cls.downloadqw = FS_OpenVFS(cls.downloadtempname, "wb", FS_ROOT); - if (!cls.downloadqw) + if (!dl->file) + { + if (!DL_Begun(dl)) { - Con_Printf("Couldn't write to temporary file %s - stopping download\n", cls.downloadtempname); - CLQ3_SendClientCommand("stopdl"); - cls.downloadmethod = DL_NONE; + CL_DownloadFailed(dl->remotename, dl); return; } } - Con_Printf("dl: chnk %i, size %i, csize %i\n", chunknum, downloadsize, chunksize); + Con_DPrintf("dl: chnk %u, size %u, csize %u\n", (unsigned int)chunknum, (unsigned int)dl->size, (unsigned int)chunksize); if (!chunksize) { - VFS_CLOSE(cls.downloadqw); - cls.downloadqw = NULL; - FS_Rename(cls.downloadtempname, cls.downloadlocalname, FS_ROOT); // -> - *cls.downloadtempname = *cls.downloadlocalname = *cls.downloadremotename = 0; - cls.downloadmethod = DL_NONE; + CL_DownloadFinished(dl); FS_ReloadPackFiles(); cl.servercount = -1; //make sure the server resends us that vital gamestate. ccs.downloadchunknum = -1; + return; } else { - VFS_WRITE(cls.downloadqw, chunkdata, chunksize); - chunksize=VFS_TELL(cls.downloadqw); + VFS_WRITE(dl->file, chunkdata, chunksize); + dl->ratebytes += chunksize; + chunksize=VFS_TELL(dl->file); // Con_Printf("Recieved %i\n", chunksize); - cls.downloadpercent = (100.0 * chunksize) / downloadsize; + dl->percent = (100.0 * chunksize) / dl->size; } - CLQ3_SendClientCommand("nextdl %i", chunknum); + CLQ3_SendClientCommand("nextdl %u", chunknum); +} + +static qboolean CLQ3_SendDownloads(char *rc, char *rn) +{ + while(rn) + { + qdownload_t *dl; + char localname[MAX_QPATH]; + char tempname[MAX_QPATH]; + char crc[64]; + vfsfile_t *f; + rc = COM_ParseOut(rc, crc, sizeof(crc)); + rn = COM_Parse(rn); + if (!*com_token) + break; + + if (!strchr(com_token, '/')) //don't let some muppet tell us to download quake3.exe + break; + + //as much as I'd like to use COM_FCheckExists, this stuf is relative to root, not the gamedir. + f = FS_OpenVFS(va("%s.pk3", com_token), "rb", FS_ROOT); + if (f) + { + VFS_CLOSE(f); + continue; + } + if (!FS_GenCachedPakName(va("%s.pk3", com_token), crc, localname, sizeof(localname))) + continue; + f = FS_OpenVFS(localname, "rb", FS_ROOT); + if (f) + { + VFS_CLOSE(f); + continue; + } + + if (!FS_GenCachedPakName(va("%s.tmp", com_token), crc, tempname, sizeof(tempname))) + continue; + + //fixme: request to download it + Con_Printf("Sending request to download %s\n", com_token); + CLQ3_SendClientCommand("download %s.pk3", com_token); + ccs.downloadchunknum = 0; + dl = Z_Malloc(sizeof(*dl)); + //q3's downloads are relative to root, but they do at least force a pk3 extension. + Q_snprintfz(dl->localname, sizeof(dl->localname), "package/%s", localname); + Q_snprintfz(dl->tempname, sizeof(dl->tempname), "package/%s", tempname); + dl->prefixbytes = 8; + dl->fsroot = FS_ROOT; + + Q_snprintfz(dl->remotename, sizeof(dl->remotename), "%s.pk3", com_token); + dl->method = DL_Q3; + dl->percent = 0; + cls.download = dl; + return true; + } + return false; } qboolean CLQ3_SystemInfoChanged(char *str) @@ -466,63 +518,14 @@ qboolean CLQ3_SystemInfoChanged(char *str) COM_FlushFSCache(); } - if (usingpure) - { - rc = Info_ValueForKey(str, "sv_referencedPaks"); //the ones that we should download. - rn = Info_ValueForKey(str, "sv_referencedPakNames"); + rc = Info_ValueForKey(str, "sv_referencedPaks"); //the ones that we should download. + rn = Info_ValueForKey(str, "sv_referencedPakNames"); + if (CLQ3_SendDownloads(rc, rn)) + return false; - - - while(rn) - { - char crc[64]; - vfsfile_t *f; - rc = COM_ParseOut(rc, crc, sizeof(crc)); - rn = COM_Parse(rn); - if (!*com_token) - break; - - if (!strchr(com_token, '/')) //don't let some muppet tell us to download quake3.exe - break; - - //as much as I'd like to use COM_FCheckExists, this stuf is relative to root, not the gamedir. - f = FS_OpenVFS(va("%s.pk3", com_token), "rb", FS_ROOT); - if (f) - { - VFS_CLOSE(f); - continue; - } - if (!FS_GenCachedPakName(va("%s.pk3", com_token), crc, cls.downloadlocalname, sizeof(cls.downloadlocalname))) - continue; - f = FS_OpenVFS(cls.downloadlocalname, "rb", FS_ROOT); - if (f) - { - VFS_CLOSE(f); - continue; - } - - if (!FS_GenCachedPakName(va("%s.tmp", com_token), crc, cls.downloadtempname, sizeof(cls.downloadtempname))) - continue; - - //fixme: request to download it - Con_Printf("Sending request to download %s\n", com_token); - CLQ3_SendClientCommand("download %s.pk3", com_token); - ccs.downloadchunknum = 0; - //q3's downloads are relative to root, but they do at least force a pk3 extension. - snprintf(cls.downloadremotename, sizeof(cls.downloadremotename), "%s.pk3", com_token); - cls.downloadmethod = DL_Q3; - cls.downloadpercent = 0; - return false; - } - - pc = Info_ValueForKey(str, "sv_paks"); //the ones that we are allowed to use (in order!) - pn = Info_ValueForKey(str, "sv_pakNames"); - FS_PureMode(2, pn, pc, ccs.fs_key); - } - else - { - FS_PureMode(0, NULL, NULL, ccs.fs_key); - } + pc = Info_ValueForKey(str, "sv_paks"); //the ones that we are allowed to use (in order!) + pn = Info_ValueForKey(str, "sv_pakNames"); + FS_PureMode(usingpure?2:0, pn, pc, rn, rc, ccs.fs_key); return true; //yay, we're in } @@ -597,7 +600,10 @@ void CLQ3_ParseGameState(void) ccs.fs_key = MSG_ReadLong(); if (!CLQ3_SystemInfoChanged(CG_GetConfigString(CFGSTR_SYSINFO))) + { + UI_Restart_f(); return; + } CG_Restart_f(); UI_Restart_f(); @@ -900,13 +906,8 @@ void CLQ3_SendCmd(usercmd_t *cmd) CLQ3_SendClientCommand("userinfo \"%s\"", cls.userinfo[0]); } - ccs.serverTime = ccs.snap.serverTime + (Sys_Milliseconds()-ccs.snap.localTime); - cl.servertime = ccs.serverTime / 1000.0f; - - cl.gametime = cl.oldgametime = cl.servertime; - //reuse the q1 array - cmd->servertime = ccs.serverTime; + cmd->servertime = cl.servertime*1000; cmd->weapon = ccs.selected_weapon; cmd->forwardmove *= 127/400.0f; @@ -933,18 +934,15 @@ void CLQ3_SendCmd(usercmd_t *cmd) if (Key_Dest_Has(~kdm_game) || (keycatcher&3)) cmd->buttons |= 2; //add in the 'at console' button - cl.outframes[cl.movesequence&Q3UPDATE_MASK].cmd[0] = *cmd; - cl.movesequence++; + cl.outframes[cl.movesequence&CMD_MASK].cmd[0] = *cmd; - - frame = &cl.outframes[cls.netchan.outgoing_sequence & Q3UPDATE_MASK]; - frame->cmd_sequence = cl.movesequence; + frame = &cl.outframes[cls.netchan.outgoing_sequence & CMD_MASK]; + frame->cmd_sequence = cl.movesequence++; frame->server_message_num = ccs.serverMessageNum; frame->server_time = cl.gametime; frame->client_time = Sys_DoubleTime()*1000; - memset(&msg, 0, sizeof(msg)); msg.maxsize = sizeof(data); msg.data = data; @@ -965,11 +963,11 @@ void CLQ3_SendCmd(usercmd_t *cmd) MSG_WriteBits(&msg, 0, 8); } - i = (cls.netchan.outgoing_sequence - 1); - oldframe = &cl.outframes[i & Q3UPDATE_MASK]; + i = (cls.netchan.outgoing_sequence-1); + oldframe = &cl.outframes[i & CMD_MASK]; cmdcount = cl.movesequence - oldframe->cmd_sequence; - if (cmdcount > Q3UPDATE_MASK) - cmdcount = Q3UPDATE_MASK; + if (cmdcount > CMD_MASK) + cmdcount = CMD_MASK; // begin a client move command, if any if( cmdcount ) { @@ -990,7 +988,7 @@ void CLQ3_SendCmd(usercmd_t *cmd) // send this and the previous cmds in the message, so // if the last packet was dropped, it can be recovered from = &nullcmd; - for (i = oldframe->cmd_sequence; i < cl.movesequence; i++) + for (i = cl.movesequence-cmdcount; i < cl.movesequence; i++) { to = &cl.outframes[i&CMD_MASK].cmd[0]; MSG_Q3_WriteDeltaUsercmd( &msg, key, from, to ); diff --git a/engine/client/clq3defs.h b/engine/client/clq3defs.h index 14c71934..777f733e 100644 --- a/engine/client/clq3defs.h +++ b/engine/client/clq3defs.h @@ -186,7 +186,6 @@ typedef struct clientSnap_s { int serverMessageNum; int serverCommandNum; int serverTime; // server time the message is valid for (in msec) - int localTime; int deltaFrame; qbyte areabits[MAX_MAP_AREA_BYTES]; // portalarea visibility bits q3playerState_t playerstate; @@ -224,7 +223,6 @@ typedef struct { int numClientCommands; int serverMessageNum; - int serverTime; int downloadchunknum; diff --git a/engine/client/console.c b/engine/client/console.c index 4998cc82..b31c5465 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -1385,12 +1385,13 @@ static int Con_DrawProgress(int left, int right, int y) // draw the download bar // figure out width - if (cls.downloadmethod) + if (cls.download) { - unsigned int count, total; + unsigned int count; + qofs_t total; qboolean extra; - progresstext = cls.downloadlocalname; - progresspercent = cls.downloadpercent; + progresstext = cls.download->localname; + progresspercent = cls.download->percent; CL_GetDownloadSizes(&count, &total, &extra); @@ -1398,7 +1399,7 @@ static int Con_DrawProgress(int left, int right, int y) sprintf(progresspercenttext, " %5.1f%% (%ukbps)", progresspercent, CL_DownloadRate()/1000); else { - sprintf(progresspercenttext, " %5.1f%% (%u%skb)", progresspercent, total/1024, extra?"+":""); + sprintf(progresspercenttext, " %5.1f%% (%u%skb)", progresspercent, (int)(total/1024), extra?"+":""); } } #ifdef RUNTIMELIGHTING diff --git a/engine/client/m_download.c b/engine/client/m_download.c index bbdfbec0..d425c8e2 100644 --- a/engine/client/m_download.c +++ b/engine/client/m_download.c @@ -500,7 +500,7 @@ void M_Download_UpdateStatus(struct menu_s *m) dlmenu_t *info = m->data; int i; - while (!cls.downloadmethod && (info->parsedsourcenum==-1 || info->parsedsourcenum < numdownloadablelists)) + while (!cls.download && (info->parsedsourcenum==-1 || info->parsedsourcenum < numdownloadablelists)) { //done downloading char basename[64]; diff --git a/engine/client/pr_csqc.c b/engine/client/pr_csqc.c index b527c701..a107cbc4 100644 --- a/engine/client/pr_csqc.c +++ b/engine/client/pr_csqc.c @@ -3471,7 +3471,7 @@ static void QCBUILTIN PF_cs_OpenPortal (pubprogfuncs_t *prinst, struct globalvar int state = G_FLOAT(OFS_PARM1)!=0; edict_t *portal = G_EDICT(prinst, OFS_PARM0); int area1 = portal->pvsinfo.areanum, area2 = portal->pvsinfo.areanum2; - if (area1 == area2 || !area1 || !area2) + if (area1 == area2 || area1<0 || area2<0) return; CMQ3_SetAreaPortalState(portal->pvsinfo.areanum, portal->pvsinfo.areanum2, state); } diff --git a/engine/common/common.h b/engine/common/common.h index 7a6ad8ad..9658874a 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -451,7 +451,7 @@ vfsfile_t *FS_OpenTCP(const char *name, int defaultport); void FS_UnloadPackFiles(void); void FS_ReloadPackFiles(void); char *FSQ3_GenerateClientPacksList(char *buffer, int maxlen, int basechecksum); -void FS_PureMode(int mode, char *packagelist, char *crclist, int seed); //implies an fs_restart +void FS_PureMode(int mode, char *purenamelist, char *purecrclist, char *refnamelist, char *refcrclist, int seed); //implies an fs_restart. ref package names are optional, for q3 where pure names don't contain usable paths //recursively tries to open files until it can get a zip. vfsfile_t *CL_OpenFileInPackage(searchpathfuncs_t *search, char *name); diff --git a/engine/common/fs.c b/engine/common/fs.c index 5827ce43..096d00a8 100644 --- a/engine/common/fs.c +++ b/engine/common/fs.c @@ -505,6 +505,8 @@ static searchpath_t *com_purepaths; static searchpath_t *com_base_searchpaths; // without gamedirs static int fs_puremode; //0=deprioritise pure, 1=prioritise pure, 2=pure only. +static char *fs_refnames; //list of allowed packages +static char *fs_refcrcs; //list of crcs for those packages. one token per package. static char *fs_purenames; //list of allowed packages static char *fs_purecrcs; //list of crcs for those packages. one token per package. static unsigned int fs_pureseed; //used as a key so the server knows we're obeying. completely unreliable/redundant in an open source project, but needed for q3 network compat. @@ -1673,6 +1675,7 @@ void COM_EnumerateFiles (const char *match, int (QDECL *func)(const char *, qofs void COM_FlushTempoaryPacks(void) { +#if 0 searchpath_t *sp, **link; link = &com_searchpaths; while (*link) @@ -1691,6 +1694,7 @@ void COM_FlushTempoaryPacks(void) link = &sp->next; } com_purepaths = NULL; +#endif } qboolean COM_LoadMapPackFile (const char *filename, qofs_t ofs) @@ -2535,26 +2539,31 @@ vfsfile_t *CL_OpenFileInPackage(searchpathfuncs_t *search, char *name) return NULL; } - -void FS_PureMode(int puremode, char *packagenames, char *packagecrcs, int pureseed) +void FS_PureMode(int puremode, char *purenamelist, char *purecrclist, char *refnamelist, char *refcrclist, int pureseed) { qboolean pureflush; if (puremode == fs_puremode && fs_pureseed == pureseed) { - if ((!packagenames && !fs_purenames) || !strcmp(fs_purenames?fs_purenames:"", packagenames?packagenames:"")) - if ((!packagecrcs && !fs_purecrcs) || !strcmp(fs_purecrcs?fs_purecrcs:"", packagecrcs?packagecrcs:"")) - return; + if ((!purenamelist && !fs_purenames) || !strcmp(fs_purenames?fs_purenames:"", purenamelist?purenamelist:"")) + if ((!purecrclist && !fs_purecrcs) || !strcmp(fs_purecrcs?fs_purecrcs:"", purecrclist?purecrclist:"")) + if ((!refnamelist && !fs_refnames) || !strcmp(fs_refnames?fs_refnames:"", refnamelist?refnamelist:"")) + if ((!refcrclist && !fs_refcrcs) || !strcmp(fs_refcrcs?fs_refcrcs:"", refcrclist?refcrclist:"")) + return; } Z_Free(fs_purenames); Z_Free(fs_purecrcs); + Z_Free(fs_refnames); + Z_Free(fs_refcrcs); pureflush = (fs_puremode != 2 && puremode == 2); fs_puremode = puremode; - fs_purenames = packagenames?Z_StrDup(packagenames):NULL; - fs_purecrcs = packagecrcs?Z_StrDup(packagecrcs):NULL; + fs_purenames = purenamelist?Z_StrDup(purenamelist):NULL; + fs_purecrcs = purecrclist?Z_StrDup(purecrclist):NULL; fs_pureseed = pureseed; + fs_refnames = refnamelist?Z_StrDup(refnamelist):NULL; + fs_refcrcs = refcrclist?Z_StrDup(refcrclist):NULL; FS_ChangeGame(fs_manifest, false); @@ -2717,6 +2726,7 @@ void FS_ReloadPackFilesFlags(unsigned int reloadflags) { char crctok[64]; char nametok[MAX_QPATH]; + char nametok2[MAX_QPATH]; searchpath_t *sp, *lastpure = NULL; char *names = fs_purenames, *pname; char *crcs = fs_purecrcs; @@ -2748,6 +2758,27 @@ void FS_ReloadPackFilesFlags(unsigned int reloadflags) continue; pname = nametok; + + if (fs_refnames && fs_refcrcs) + { //q3 is annoying as hell + int r; + int crc2; + char *rc = fs_refcrcs; + char *rn = fs_refnames; + pname = ""; + for (; rc && rn; ) + { + rc = COM_ParseOut(rc, crctok, sizeof(crctok)); + rn = COM_ParseOut(rn, nametok2, sizeof(nametok2)); + crc2 = strtoul(crctok, NULL, 0); + if (crc2 == crc) + { + COM_DefaultExtension(nametok2, ".pk3", sizeof(nametok2)); + pname = nametok2; + break; + } + } + } if (*pname == '*') // * means that its 'referenced' (read: actually useful) thus should be downloaded, which is not relevent here. pname++; @@ -2778,7 +2809,7 @@ void FS_ReloadPackFilesFlags(unsigned int reloadflags) } } //if its not already loaded (via wildcards), load it from the download cache, if we can - if (!sp) + if (!sp && *pname) { char local[MAX_OSPATH]; vfsfile_t *vfs; @@ -2787,7 +2818,28 @@ void FS_ReloadPackFilesFlags(unsigned int reloadflags) int i; if (FS_GenCachedPakName(pname, va("%i", crc), local, sizeof(local))) + { + unsigned int keptflags; + handle = FS_GetOldPath(&oldpaths, local, &keptflags); + if (handle) + { + sp = FS_AddPathHandle(&oldpaths, pname, local, handle, SPF_COPYPROTECTED|SPF_TEMPORARY|keptflags, (unsigned int)-1); + if (sp->crc_check == crc) + { + if (fs_puremode) + { + if (lastpure) + lastpure->nextpure = sp; + else + com_purepaths = sp; + sp->nextpure = NULL; + lastpure = sp; + } + } + continue; + } vfs = FS_OpenVFS(local, "rb", FS_ROOT); + } else vfs = NULL; if (vfs) diff --git a/engine/common/fs_pak.c b/engine/common/fs_pak.c index e33cbcf2..aa80e3a9 100644 --- a/engine/common/fs_pak.c +++ b/engine/common/fs_pak.c @@ -364,7 +364,7 @@ searchpathfuncs_t *QDECL FSPAK_LoadArchive (vfsfile_t *file, const char *desc) pack->references++; - Con_TPrintf ("Added packfile %s (%i files)\n", desc, numpackfiles); +// Con_TPrintf ("Added packfile %s (%i files)\n", desc, numpackfiles); pack->pub.fsver = FSVER; pack->pub.GetPathDetails = FSPAK_GetPathDetails; diff --git a/engine/common/fs_zip.c b/engine/common/fs_zip.c index 79656973..0bc42f6e 100644 --- a/engine/common/fs_zip.c +++ b/engine/common/fs_zip.c @@ -245,7 +245,8 @@ typedef struct { fsbucket_t bucket; char name[MAX_QPATH]; - qofs_t localpos, filelen; + qofs_t localpos; //location of local header + qofs_t filelen; //uncompressed size unsigned int crc; unsigned int flags; } zpackfile_t; @@ -1094,9 +1095,8 @@ static qboolean FSZIP_EnumerateCentralDirectory(zipfile_t *zip, struct zipinfo * return success; } -static qboolean FSZIP_FindEndCentralDirectory(zipfile_t *zip) +static qboolean FSZIP_FindEndCentralDirectory(zipfile_t *zip, struct zipinfo *info) { - struct zipinfo info; qboolean result = false; //zip comment is capped to 65k or so, so we can use a single buffer for this qbyte traildata[0x10000 + SIZE_ENDOFCENTRALDIRECTORY+SIZE_ZIP64ENDOFCENTRALDIRECTORYLOCATOR]; @@ -1108,7 +1108,7 @@ static qboolean FSZIP_FindEndCentralDirectory(zipfile_t *zip) VFS_SEEK(zip->raw, zip->rawsize - trailsize); VFS_READ(zip->raw, traildata, trailsize); - memset(&info, 0, sizeof(info)); + memset(info, 0, sizeof(*info)); for (magic = traildata+trailsize-SIZE_ENDOFCENTRALDIRECTORY; magic >= traildata; magic--) { @@ -1117,15 +1117,15 @@ static qboolean FSZIP_FindEndCentralDirectory(zipfile_t *zip) magic[2] == 5 && magic[3] == 6) { - info.centraldir_end = (zip->rawsize-trailsize)+(magic-traildata); + info->centraldir_end = (zip->rawsize-trailsize)+(magic-traildata); - info.thisdisk = LittleU2FromPtr(magic+4); - info.centraldir_startdisk = LittleU2FromPtr(magic+6); - info.centraldir_numfiles_disk = LittleU2FromPtr(magic+8); - info.centraldir_numfiles_all = LittleU2FromPtr(magic+10); - info.centraldir_size = LittleU4FromPtr(magic+12); - info.centraldir_offset = LittleU4FromPtr(magic+16); - info.commentlength = LittleU2FromPtr(magic+20); + info->thisdisk = LittleU2FromPtr(magic+4); + info->centraldir_startdisk = LittleU2FromPtr(magic+6); + info->centraldir_numfiles_disk = LittleU2FromPtr(magic+8); + info->centraldir_numfiles_all = LittleU2FromPtr(magic+10); + info->centraldir_size = LittleU4FromPtr(magic+12); + info->centraldir_offset = LittleU4FromPtr(magic+16); + info->commentlength = LittleU2FromPtr(magic+20); result = true; break; @@ -1148,17 +1148,17 @@ static qboolean FSZIP_FindEndCentralDirectory(zipfile_t *zip) { qbyte z64eocd[SIZE_ZIP64ENDOFCENTRALDIRECTORY]; - info.zip64_centraldirend_disk = LittleU4FromPtr(magic+4); - info.zip64_centraldirend_offset = LittleU8FromPtr(magic+8); - info.zip64_diskcount = LittleU4FromPtr(magic+16); + info->zip64_centraldirend_disk = LittleU4FromPtr(magic+4); + info->zip64_centraldirend_offset = LittleU8FromPtr(magic+8); + info->zip64_diskcount = LittleU4FromPtr(magic+16); - if (info.zip64_diskcount != 1 || info.zip64_centraldirend_disk != 0) + if (info->zip64_diskcount != 1 || info->zip64_centraldirend_disk != 0) { Con_Printf("zip: archive is spanned\n"); return false; } - VFS_SEEK(zip->raw, info.zip64_centraldirend_offset); + VFS_SEEK(zip->raw, info->zip64_centraldirend_offset); VFS_READ(zip->raw, z64eocd, sizeof(z64eocd)); if (z64eocd[0] == 'P' && @@ -1166,27 +1166,27 @@ static qboolean FSZIP_FindEndCentralDirectory(zipfile_t *zip) z64eocd[2] == 6 && z64eocd[3] == 6) { - info.zip64_eocdsize = LittleU8FromPtr(z64eocd+4) + 12; - info.zip64_version_madeby = LittleU2FromPtr(z64eocd+12); - info.zip64_version_needed = LittleU2FromPtr(z64eocd+14); - info.thisdisk = LittleU4FromPtr(z64eocd+16); - info.centraldir_startdisk = LittleU4FromPtr(z64eocd+20); - info.centraldir_numfiles_disk = LittleU8FromPtr(z64eocd+24); - info.centraldir_numfiles_all = LittleU8FromPtr(z64eocd+32); - info.centraldir_size = LittleU8FromPtr(z64eocd+40); - info.centraldir_offset = LittleU8FromPtr(z64eocd+48); + info->zip64_eocdsize = LittleU8FromPtr(z64eocd+4) + 12; + info->zip64_version_madeby = LittleU2FromPtr(z64eocd+12); + info->zip64_version_needed = LittleU2FromPtr(z64eocd+14); + info->thisdisk = LittleU4FromPtr(z64eocd+16); + info->centraldir_startdisk = LittleU4FromPtr(z64eocd+20); + info->centraldir_numfiles_disk = LittleU8FromPtr(z64eocd+24); + info->centraldir_numfiles_all = LittleU8FromPtr(z64eocd+32); + info->centraldir_size = LittleU8FromPtr(z64eocd+40); + info->centraldir_offset = LittleU8FromPtr(z64eocd+48); - if (info.zip64_eocdsize >= 84) + if (info->zip64_eocdsize >= 84) { - info.centraldir_compressionmethod = LittleU2FromPtr(z64eocd+56); -// info.zip64_2_centraldir_csize = LittleU8FromPtr(z64eocd+58); -// info.zip64_2_centraldir_usize = LittleU8FromPtr(z64eocd+66); - info.centraldir_algid = LittleU2FromPtr(z64eocd+74); + info->centraldir_compressionmethod = LittleU2FromPtr(z64eocd+56); +// info->zip64_2_centraldir_csize = LittleU8FromPtr(z64eocd+58); +// info->zip64_2_centraldir_usize = LittleU8FromPtr(z64eocd+66); + info->centraldir_algid = LittleU2FromPtr(z64eocd+74); // info.zip64_2_bitlen = LittleU2FromPtr(z64eocd+76); -// info.zip64_2_flags = LittleU2FromPtr(z64eocd+78); -// info.zip64_2_hashid = LittleU2FromPtr(z64eocd+80); -// info.zip64_2_hashlength = LittleU2FromPtr(z64eocd+82); - //info.zip64_2_hashdata = LittleUXFromPtr(z64eocd+84, info.zip64_2_hashlength); +// info->zip64_2_flags = LittleU2FromPtr(z64eocd+78); +// info->zip64_2_hashid = LittleU2FromPtr(z64eocd+80); +// info->zip64_2_hashlength = LittleU2FromPtr(z64eocd+82); + //info->zip64_2_hashdata = LittleUXFromPtr(z64eocd+84, info->zip64_2_hashlength); } } else @@ -1199,28 +1199,17 @@ static qboolean FSZIP_FindEndCentralDirectory(zipfile_t *zip) } } - if (info.thisdisk || info.centraldir_startdisk || info.centraldir_numfiles_disk != info.centraldir_numfiles_all) + if (info->thisdisk || info->centraldir_startdisk || info->centraldir_numfiles_disk != info->centraldir_numfiles_all) { Con_Printf("zip: archive is spanned\n"); result = false; } - if (info.centraldir_compressionmethod || info.centraldir_algid) + if (info->centraldir_compressionmethod || info->centraldir_algid) { Con_Printf("zip: encrypted centraldir\n"); result = false; } - if (result) - { - result = FSZIP_EnumerateCentralDirectory(zip, &info); - if (!result && !info.zip64_diskcount) - { - //uh oh... the central directory wasn't where it was meant to be! - //assuming that the endofcentraldir is packed at the true end of the centraldir (and that we're not zip64 and thus don't have an extra block), then we can guess based upon the offset difference - info.zipoffset = info.centraldir_end - (info.centraldir_offset+info.centraldir_size); - result = FSZIP_EnumerateCentralDirectory(zip, &info); - } - } return result; } @@ -1237,42 +1226,46 @@ of the list so they override previous pack files. searchpathfuncs_t *QDECL FSZIP_LoadArchive (vfsfile_t *packhandle, const char *desc) { zipfile_t *zip; + struct zipinfo info; zip = Z_Malloc(sizeof(zipfile_t)); Q_strncpyz(zip->filename, desc, sizeof(zip->filename)); zip->raw = packhandle; zip->rawsize = VFS_GETLEN(zip->raw); - if (!FSZIP_FindEndCentralDirectory(zip)) + //find the footer + if (!FSZIP_FindEndCentralDirectory(zip, &info)) { Z_Free(zip); Con_TPrintf ("Failed opening zipfile \"%s\" corrupt?\n", desc); return NULL; } -/* - for (i = 0; i < zip->numfiles; i++) + //find the central directory. + if (!FSZIP_FindEndCentralDirectory(zip, &info)) { - if (unzGetCurrentFileInfo (zip->handle, &file_info, newfiles[i].name, sizeof(newfiles[i].name), NULL, 0, NULL, 0) != UNZ_OK) - Con_Printf("Zip Error\n"); - Q_strlwr(newfiles[i].name); - if (!*newfiles[i].name || newfiles[i].name[strlen(newfiles[i].name)-1] == '/') - newfiles[i].filelen = -1; - else - newfiles[i].filelen = file_info.uncompressed_size; - newfiles[i].filepos = file_info.c_offset; - - nextfileziphandle = unzGoToNextFile (zip->handle); - if (nextfileziphandle == UNZ_END_OF_LIST_OF_FILE) - break; - else if (nextfileziphandle != UNZ_OK) - Con_Printf("Zip Error\n"); + Z_Free(zip); + Con_TPrintf ("Failed opening zipfile \"%s\" corrupt?\n", desc); + return NULL; + } + + //now read it. + if (!FSZIP_EnumerateCentralDirectory(zip, &info) && !info.zip64_diskcount) + { + //uh oh... the central directory wasn't where it was meant to be! + //assuming that the endofcentraldir is packed at the true end of the centraldir (and that we're not zip64 and thus don't have an extra block), then we can guess based upon the offset difference + info.zipoffset = info.centraldir_end - (info.centraldir_offset+info.centraldir_size); + if (FSZIP_EnumerateCentralDirectory(zip, &info)) + { + Z_Free(zip); + Con_TPrintf ("zipfile \"%s\" appears to be missing its central directory\n", desc); + return NULL; + } } - */ zip->references = 1; - Con_TPrintf ("Added zipfile %s (%i files)\n", desc, zip->numfiles); +// Con_TPrintf ("Added zipfile %s (%i files)\n", desc, zip->numfiles); zip->pub.fsver = FSVER; zip->pub.GetPathDetails = FSZIP_GetPathDetails; @@ -1286,5 +1279,144 @@ searchpathfuncs_t *QDECL FSZIP_LoadArchive (vfsfile_t *packhandle, const char *d return &zip->pub; } + + + + +#if 0 + + +typedef struct +{ + struct archivedeltafuncs_s + { + //called when the downloader needs to know a new target region. each call should return a new region, eventually returning the entire file as data is injected from elsewhere + //return false on error. + qboolean (*GetNextRange) (struct archivedeltafuncs_s *archive, qofs_t *start, qofs_t *end); + + //called when the download completes/aborts. frees memory. + void (*Finish) (struct archivedeltafuncs_s); + } pub; + + qdownload_t *dl; + + enum + { + step_eocd, + step_cd, + step_file + } step; + + struct + { + qofs_t start; + qofs_t end; + } eocd; + + zipfile_t *old; + + zipfile_t zip; + struct zipinfo info; +} ziparchivedelta_t; + +void FSZIPDL_Finish(struct archivedeltafuncs_s *archive) +{ + ziparchivedelta_t *za = (ziparchivedelta_t*)archive; + + za->old->pub.ClosePath(&za->old->pub); + + if (za->zip.files) + Z_Free(za->zip.files); + + Z_Free(za); +} +qboolean FSZIPDL_GetNextRange(struct archivedeltafuncs_s *archive, qofs_t *start, qofs_t *end) +{ + flocation_t loc; + int i; + ziparchivedelta_t *za = (ziparchivedelta_t*)archive; + switch(za->step) + { + case step_eocd: + //find the header (actually, the trailer) + *start = za->eocd.start; + *end = za->eocd.end; + za->step++; + break; + case step_cd: + if (!FSZIP_FindEndCentralDirectory(&za->zip, &za->info)) + return false; //corrupt/unusable. + + //central directory should be located at this position + *start = za->info.centraldir_offset+za->info.zipoffset; + *end = za->info.centraldir_end+za->info.zipoffset; + za->step++; + break; + case step_file: + if (FSZIP_EnumerateCentralDirectory(&za->zip, &za->info)) + { + return false; //couldn't find the central directory properly (fixme: this can happen with zip32 self-extractors) + } + + //walk through the central directory looking for matching files in the existing versions of the zip + for (i = 0; i < za->zip.numfiles; i++) + if (FSZIP_FLocate(&za->old->pub, &loc, za->zip.files[i].name, NULL) != FF_NOTFOUND) + { + if (za->old->files[loc.index].crc == za->zip.files[i].crc) + { + qofs_t datastart, datasize; + if (FSZIP_ValidateLocalHeader(za->old, &za->old->files[loc.index], &datastart, &datasize)) + { + size_t chunk; + datastart = za->old->files[loc.index].localpos; + + //tell the download context that we already know this data region + DL_Completed(za->dl, datastart, datastart + datasize); + //and inject the data into the downloaded file. + VFS_SEEK(za->old->raw, datastart); + VFS_SEEK(za->dl->file, za->zip.files[i].localpos); + for(;datasize;) + { + char copybuffer[0x10000]; + chunk = datasize; + if (chunk > sizeof(copybuffer)) + chunk = sizeof(copybuffer); + + VFS_READ(za->old->raw, copybuffer, chunk); + datasize -= chunk; + } + //FIXME: graphics/progress updates. + } + } + } + + //anything we didn't receive yet needs to be completed. the download code is meant to track the holes. + *start = 0; + *end = za->eocd.end; + za->step++; + break; + default: + return false; + } + return true; +} +struct archivedeltafuncs_s *FSZIP_OpenDeltaZip(qdownload_t *dl) +{ + ziparchivedelta_t *za; + unsigned int trailsize = 0x10000 + SIZE_ENDOFCENTRALDIRECTORY+SIZE_ZIP64ENDOFCENTRALDIRECTORYLOCATOR; + za->eocd.end = dl->size; + if (dl->size < trailsize) + za->eocd.start = 0; + else + za->eocd.start = dl->size - trailsize; + + za->dl = dl; + za->zip.rawsize = dl->size; + za->zip.raw = dl->file; + za->pub.GetNextRange = FSZIPDL_GetNextRange; + return &za->pub; +} +#endif + #endif diff --git a/engine/common/gl_q2bsp.c b/engine/common/gl_q2bsp.c index 9101c003..001af5f2 100644 --- a/engine/common/gl_q2bsp.c +++ b/engine/common/gl_q2bsp.c @@ -3218,7 +3218,7 @@ qboolean CModQ3_LoadLeafs (lump_t *l) out->minmaxs[3+j] = LittleLong(in->maxs[j]); } out->cluster = LittleLong(in->cluster); - out->area = LittleLong(in->area) + 1; + out->area = LittleLong(in->area); // out->firstleafface = LittleLong(in->firstleafsurface); // out->numleaffaces = LittleLong(in->num_leafsurfaces); out->contents = 0; @@ -6103,7 +6103,7 @@ static void FloodArea_r (q2carea_t *area, int floodnum) area->floodvalid = floodvalid; if (mapisq3) { - for (i=1 ; i0) FloodArea_r (&map_q2areas[i], floodnum); @@ -6138,7 +6138,7 @@ static void FloodAreaConnections (void) floodnum = 0; // area 0 is not used - for (i=1 ; ifloodvalid == floodvalid) @@ -6170,7 +6170,7 @@ void CMQ3_SetAreaPortalState (unsigned int area1, unsigned int area2, qboolean o return; // Host_Error ("CMQ3_SetAreaPortalState on non-q3 map"); - if (area1 > numareas || area2 > numareas) + if (area1 >= numareas || area2 >= numareas) Host_Error ("CMQ3_SetAreaPortalState: area > numareas"); if (open) @@ -6192,6 +6192,8 @@ qboolean VARGS CM_AreasConnected (model_t *mod, unsigned int area1, unsigned int if (map_noareas.value) return true; + if (area1 == ~0 || area2 == ~0) + return area1 == area2; if (area1 > numareas || area2 > numareas) Host_Error ("area > numareas"); @@ -6228,7 +6230,7 @@ int CM_WriteAreaBits (model_t *mod, qbyte *buffer, int area) memset (buffer, 0, bytes); floodnum = map_q2areas[area].floodnum; - for (i=1 ; i>3] |= 1<<(i&7); diff --git a/engine/gl/gl_alias.c b/engine/gl/gl_alias.c index 063a22a4..b2f946bd 100644 --- a/engine/gl/gl_alias.c +++ b/engine/gl/gl_alias.c @@ -2301,6 +2301,8 @@ void BE_GenPolyBatches(batch_t **batches) return; shader = cl_stris[i].shader; + if (!shader) + continue; b->buildmeshes = R_DB_Poly; b->ent = &r_worldentity; diff --git a/engine/gl/gl_vidnt.c b/engine/gl/gl_vidnt.c index 929f3b8d..6491a9e5 100644 --- a/engine/gl/gl_vidnt.c +++ b/engine/gl/gl_vidnt.c @@ -519,10 +519,13 @@ qboolean GLInitialise (char *renderer) RECT centerrect(unsigned int parentleft, unsigned int parenttop, unsigned int parentwidth, unsigned int parentheight, unsigned int cwidth, unsigned int cheight) { RECT r; - if (!vid_width.ival) - cwidth = parentwidth; - if (!vid_height.ival) - cheight = parentwidth; + if (modestate!=MS_WINDOWED) + { + if (!vid_width.ival) + cwidth = parentwidth; + if (!vid_height.ival) + cheight = parentwidth; + } if (parentwidth < cwidth) { @@ -557,6 +560,8 @@ qboolean VID_SetWindowedMode (rendererstate_t *info) int wwidth, wheight, pleft, ptop, pwidth, pheight; RECT rect; + modestate = MS_WINDOWED; + hdc = GetDC(NULL); if (GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) { @@ -671,8 +676,6 @@ qboolean VID_SetWindowedMode (rendererstate_t *info) // ShowWindow (dibwindow, SW_SHOWDEFAULT); // UpdateWindow (dibwindow); - modestate = MS_WINDOWED; - // because we have set the background brush for the window to NULL // (to avoid flickering when re-sizing the window on the desktop), // we clear the window to black when created, otherwise it will be diff --git a/engine/http/httpclient.c b/engine/http/httpclient.c index 8ae3266e..5bdffbde 100644 --- a/engine/http/httpclient.c +++ b/engine/http/httpclient.c @@ -894,21 +894,13 @@ struct dl_download *DL_Create(const char *url) return newdl; } -static struct dl_download *showndownload; /*destroys an entire download context*/ void DL_Close(struct dl_download *dl) { #ifndef NPFTE - if (showndownload == dl) - { - if (cls.downloadmethod == DL_HTTP) - { - cls.downloadmethod = DL_NONE; - *cls.downloadlocalname = *cls.downloadremotename = 0; - } - showndownload = NULL; - } + if (cls.download == &dl->qdownload) + cls.download = NULL; #endif #ifdef MULTITHREAD @@ -930,7 +922,6 @@ void DL_Close(struct dl_download *dl) #ifndef NPFTE static struct dl_download *activedownloads; -unsigned int shownbytestart; /*create a download context and add it to the list, for lazy people. not threaded*/ struct dl_download *HTTP_CL_Get(const char *url, const char *localfile, void (*NotifyFunction)(struct dl_download *dl)) { @@ -995,34 +986,27 @@ void HTTP_CL_Think(void) } link = &dl->next; - if (!cls.downloadmethod) + if (!cls.download) { - cls.downloadmethod = DL_HTTP; - showndownload = dl; + cls.download = &dl->qdownload; + dl->qdownload.method = DL_HTTP; if (*dl->localname) - strcpy(cls.downloadlocalname, dl->localname); + strcpy(dl->qdownload.localname, dl->localname); else - strcpy(cls.downloadlocalname, dl->url); - strcpy(cls.downloadremotename, dl->url); - cls.downloadstarttime = Sys_DoubleTime(); - cls.downloadedbytes = 0; - shownbytestart = dl->completed; - } - if (cls.downloadmethod == DL_HTTP) - { - if (showndownload == dl) - { - if (dl->status == DL_FINISHED) - cls.downloadpercent = 100; - else if (dl->status != DL_ACTIVE) - cls.downloadpercent = 0; - else if (dl->totalsize <= 0) - cls.downloadpercent = 50; - else - cls.downloadpercent = dl->completed*100.0f/dl->totalsize; - cls.downloadedbytes = dl->completed; - } + strcpy(dl->qdownload.localname, dl->url); + strcpy(dl->qdownload.remotename, dl->url); + dl->qdownload.starttime = Sys_DoubleTime(); } + + if (dl->status == DL_FINISHED) + dl->qdownload.percent = 100; + else if (dl->status != DL_ACTIVE) + dl->qdownload.percent = 0; + else if (dl->totalsize <= 0) + dl->qdownload.percent = 50; + else + dl->qdownload.percent = dl->completed*100.0f/dl->totalsize; + dl->qdownload.completedbytes = dl->completed; } } #endif diff --git a/engine/http/iweb.h b/engine/http/iweb.h index f3480dd6..2e7c3b8f 100644 --- a/engine/http/iweb.h +++ b/engine/http/iweb.h @@ -103,6 +103,8 @@ struct dl_download unsigned int user_num; void *user_ctx; + qdownload_t qdownload; + /*stream config*/ char url[MAX_OSPATH]; /*original url*/ char redir[MAX_OSPATH]; /*current redirected url*/ diff --git a/engine/qclib/qcc_pr_lex.c b/engine/qclib/qcc_pr_lex.c index 9ca797b3..e83c41a2 100644 --- a/engine/qclib/qcc_pr_lex.c +++ b/engine/qclib/qcc_pr_lex.c @@ -3105,9 +3105,9 @@ void QCC_PR_Lex (void) // if the first character is a valid identifier, parse until a non-id // character is reached - if ((c == '~' || c == '%') && pr_file_p[1] >= '0' && pr_file_p[1] <= '9') //let's see which one we make into an operator first... possibly both... + if ((c == '%') && pr_file_p[1] >= '0' && pr_file_p[1] <= '9') //let's see which one we make into an operator first... possibly both... { - QCC_PR_ParseWarning(0, "~ or %% prefixes to denote integers are deprecated. Please use a postfix of 'i'"); + QCC_PR_ParseWarning(0, "%% prefixes to denote integers are deprecated. Please use a postfix of 'i'"); pr_file_p++; pr_token_type = tt_immediate; pr_immediate_type = type_integer; diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c index d62b9724..81bdf09f 100644 --- a/engine/server/pr_cmds.c +++ b/engine/server/pr_cmds.c @@ -6269,7 +6269,7 @@ static void QCBUILTIN PF_OpenPortal (pubprogfuncs_t *prinst, struct globalvars_s client_t *client; edict_t *portal = G_EDICT(prinst, OFS_PARM0); int area1 = portal->pvsinfo.areanum, area2 = portal->pvsinfo.areanum2; - if (area1 == area2 || !area1 || !area2) + if (area1 == area2 || area1<0 || area2<0) return; for (client = svs.clients, i = 0; i < sv.allocated_client_slots; i++, client++) { diff --git a/engine/server/sv_ents.c b/engine/server/sv_ents.c index 6344fbab..48d0942f 100644 --- a/engine/server/sv_ents.c +++ b/engine/server/sv_ents.c @@ -2746,10 +2746,16 @@ unsigned int Q2BSP_FatPVS(model_t *mod, vec3_t org, qbyte *buffer, unsigned int qboolean Q2BSP_EdictInFatPVS(model_t *mod, pvscache_t *ent, qbyte *pvs) { int i,l; - if (!CM_AreasConnected (mod, clientarea, ent->areanum)) + int nullarea = (mod->fromgame == fg_quake2)?0:-1; + if (clientarea == ent->areanum) + { + if (clientarea == nullarea) + return false; + } + else if (!CM_AreasConnected (mod, clientarea, ent->areanum)) { // doors can legally straddle two areas, so // we may need to check another one - if (!ent->areanum2 + if (ent->areanum2 == nullarea || !CM_AreasConnected (mod, clientarea, ent->areanum2)) return false; // blocked by a door } diff --git a/engine/server/sv_user.c b/engine/server/sv_user.c index 6c6cbda2..c76d998e 100644 --- a/engine/server/sv_user.c +++ b/engine/server/sv_user.c @@ -604,7 +604,7 @@ void SVNQ_New_f (void) // set view MSG_WriteByte (&host_client->netchan.message, svc_setview); - MSG_WriteEntity (&host_client->netchan.message, host_client - svs.clients);//NUM_FOR_EDICT(svprogfuncs, host_client->edict)); + MSG_WriteEntity (&host_client->netchan.message, (host_client - svs.clients)+1);//NUM_FOR_EDICT(svprogfuncs, host_client->edict)); MSG_WriteByte (&host_client->netchan.message, svc_signonnum); MSG_WriteByte (&host_client->netchan.message, 1); @@ -3063,7 +3063,7 @@ void SV_StopDownload_f(void) host_client->download = NULL; } else - SV_ClientPrintf(host_client, PRINT_HIGH, "But you're not downloading anything\n"); + SV_ClientPrintf(host_client, PRINT_HIGH, "Can't stop download - not downloading anything\n"); host_client->downloadstarted = false; } diff --git a/engine/server/svq3_game.c b/engine/server/svq3_game.c index 978be4dd..39f7cb13 100644 --- a/engine/server/svq3_game.c +++ b/engine/server/svq3_game.c @@ -283,7 +283,7 @@ static void Q3G_LinkEntity(q3sharedEntity_t *ent) { clusters[i] = CM_LeafCluster(sv.world.worldmodel, leafs[i]); area = CM_LeafArea(sv.world.worldmodel, leafs[i]); - if(area > 0) //FIXME: this should be >= + if(area >= 0) { // doors may legally straggle two areas, // but nothing should ever need more than that @@ -2982,11 +2982,38 @@ void SVQ3_UpdateUserinfo_f(client_t *cl) VM_Call(q3gamevm, GAME_CLIENT_USERINFO_CHANGED, (int)(cl-svs.clients)); } -void SVQ3_Drop_f(client_t *cl) +static void SVQ3_Drop_f(client_t *cl) { SV_DropClient(cl); } +//part of the sv_pure mechanism. verifies the client's pack list and kicks if they're wrong. +//safe to ignore, if you're okay with potential cheats. +static void SVQ3_ClientPacks_f(client_t *cl) +{ +} + +static void SVQ3_Download_f(client_t *cl) +{ + //clients might end up waiting for the download which will never come. + //kick them so that doesn't happen. downloads are not supported at this time. not even reporting failure! :s + SV_DropClient(cl); +// short 0 +// long -1 +} +static void SVQ3_NextDL_f(client_t *cl) +{ + //send next chunk +} +static void SVQ3_StopDL_f(client_t *cl) +{ + //abort/close current download, if any +} +static void SVQ3_DoneDL_f(client_t *cl) +{ + //send new gamestate +} + typedef struct ucmd_s { char *name; @@ -2998,11 +3025,11 @@ static const ucmd_t ucmds[] = {"userinfo", SVQ3_UpdateUserinfo_f}, {"disconnect", SVQ3_Drop_f}, - {"cp", NULL}, - {"download", NULL}, - {"nextdl", NULL}, - {"stopdl", NULL}, - {"donedl", NULL}, + {"cp", SVQ3_ClientPacks_f}, + {"download", SVQ3_Download_f}, + {"nextdl", SVQ3_NextDL_f}, + {"stopdl", SVQ3_StopDL_f}, + {"donedl", SVQ3_DoneDL_f}, {NULL, NULL} };