#include "quakedef.h" #if defined(WEBCLIENT) && !defined(NOBUILTINMENUS) #define DOWNLOADMENU #endif #ifdef DOWNLOADMENU //whole load of extra args for the downloads menu (for the downloads menu to handle engine updates). #ifdef VKQUAKE #define PHPVK "&vk=1" #else #define PHPVK #endif #ifdef GLQUAKE #define PHPGL "&gl=1" #else #define PHPGL #endif #ifdef D3DQUAKE #define PHPD3D "&d3d=1" #else #define PHPD3D #endif #ifdef MINIMAL #define PHPMIN "&min=1" #else #define PHPMIN #endif #ifdef NOLEGACY #define PHPLEG "&leg=0" #else #define PHPLEG "&leg=1" #endif #if defined(_DEBUG) || defined(DEBUG) #define PHPDBG "&dbg=1" #else #define PHPDBG #endif #ifndef SVNREVISION #define SVNREVISION - #endif #define DOWNLOADABLESARGS "?ver=" STRINGIFY(SVNREVISION) PHPVK PHPGL PHPD3D PHPMIN PHPLEG PHPDBG extern cvar_t fs_downloads_url; #define INSTALLEDFILES "installed.lst" //the file that resides in the quakedir (saying what's installed). #define DPF_HAVEAVERSION 1 //any old version #define DPF_WANTTOINSTALL 2 //user selected it #define DPF_DISPLAYVERSION 4 //some sort of conflict, the package is listed twice, so show versions so the user knows what's old. #define DPF_FORGETONUNINSTALL 8 //for previously installed packages, remove them from the list if there's no current version any more (should really be automatic if there's no known mirrors) #define DPF_UNKNOWNVERSION 16 //we have a file with this name already, with no idea where it came from. #define DPF_HIDDEN 32 //wrong arch, file conflicts, etc. still listed if actually installed. void CL_StartCinematicOrMenu(void); //note: these are allocated for the life of the exe static char *downloadablelist[32]; static char *downloadablelistnameprefix[countof(downloadablelist)]; static char downloadablelistreceived[countof(downloadablelist)]; //well static int numdownloadablelists = 0; #define THISARCH PLATFORM "_" ARCH_CPU_POSTFIX typedef struct package_s { char fullname[256]; char *name; struct package_s *override; //the package that obscures this one (later version, or whatever) unsigned int trymirrors; char *mirror[8]; char gamedir[16]; enum fs_relative fsroot; char version[16]; char *arch; enum { EXTRACT_COPY, //just copy the download over EXTRACT_XZ, //give the download code a write filter so that it automatically decompresses on the fly EXTRACT_GZ, //give the download code a write filter so that it automatically decompresses on the fly EXTRACT_ZIP //extract stuff once it completes. kinda sucky. } extract; struct packagedep_s { struct packagedep_s *next; enum { DEP_CONFLICT, DEP_FILECONFLICT, //don't install if this file already exists. DEP_REQUIRE, DEP_RECOMMEND, //like depend, but uninstalling will not bubble. DEP_FILE } dtype; char name[1]; } *deps; struct dl_download *download; int flags; struct package_s *next; } package_t; typedef struct { menucustom_t *list; char intermediatefilename[MAX_QPATH]; char pathprefix[MAX_QPATH]; int parsedsourcenum; qboolean populated; } dlmenu_t; static package_t *availablepackages; static int numpackages; #ifdef HAVEAUTOUPDATE static int autoupdatesetting = -1; #endif static qboolean MD_CheckFile(const char *filename, enum fs_relative base) { vfsfile_t *f = FS_OpenVFS(filename, "rb", base); if (f) { VFS_CLOSE(f); return true; } return false; } void MD_AddDep(package_t *p, int deptype, const char *depname) { struct packagedep_s *nd, **link; //no dupes. for (link = &p->deps; (nd=*link) ; link = &nd->next) { if (nd->dtype == deptype && !strcmp(nd->name, depname)) return; } //add it on the end, preserving order. nd = Z_Malloc(sizeof(*nd) + strlen(depname)); nd->dtype = deptype; strcpy(nd->name, depname); nd->next = *link; *link = nd; } static void M_DL_AddSubList(const char *url, const char *prefix) { int i; for (i = 0; i < numdownloadablelists; i++) { if (!strcmp(downloadablelist[i], url)) break; } if (i == numdownloadablelists && i < countof(downloadablelist)) { downloadablelist[i] = BZ_Malloc(strlen(url)+1); strcpy(downloadablelist[i], url); downloadablelistnameprefix[i] = BZ_Malloc(strlen(prefix)+1); strcpy(downloadablelistnameprefix[i], prefix); numdownloadablelists++; } } static package_t *BuildPackageList(vfsfile_t *f, int flags, const char *url, const char *prefix) { char line[1024]; package_t *p, *o; package_t *first = NULL; struct packagedep_s *dep; char *sl; vfsfile_t *pf; int version; char defaultgamedir[64]; char mirror[countof(p->mirror)][MAX_OSPATH]; int nummirrors = 0; int argc; if (!f) return NULL; Q_strncpyz(defaultgamedir, FS_GetGamedir(false), sizeof(defaultgamedir)); if (url) { Q_strncpyz(mirror[nummirrors], url, sizeof(mirror[nummirrors])); sl = COM_SkipPath(mirror[nummirrors]); *sl = 0; nummirrors++; } do { if (!VFS_GETS(f, line, sizeof(line)-1)) break; while((sl=strchr(line, '\n'))) *sl = '\0'; while((sl=strchr(line, '\r'))) *sl = '\0'; Cmd_TokenizeString (line, false, false); } while (!Cmd_Argc()); if (strcmp(Cmd_Argv(0), "version")) return NULL; //it's not the right format. version = atoi(Cmd_Argv(1)); if (version != 0 && version != 1 && version != 2) { Con_Printf("Packagelist is of a future or incompatible version\n"); return NULL; //it's not the right version. } while(1) { if (!VFS_GETS(f, line, sizeof(line)-1)) break; while((sl=strchr(line, '\n'))) *sl = '\0'; while((sl=strchr(line, '\r'))) *sl = '\0'; Cmd_TokenizeString (line, false, false); argc = Cmd_Argc(); if (argc) { if (!strcmp(Cmd_Argv(0), "sublist")) { char *subprefix; if (*prefix) subprefix = va("%s/%s", prefix, Cmd_Argv(2)); else subprefix = Cmd_Argv(2); M_DL_AddSubList(Cmd_Argv(1), subprefix); continue; } if (!strcmp(Cmd_Argv(0), "set")) { if (!strcmp(Cmd_Argv(1), "gamedir")) { if (argc == 2) Q_strncpyz(defaultgamedir, FS_GetGamedir(false), sizeof(defaultgamedir)); else Q_strncpyz(defaultgamedir, Cmd_Argv(2), sizeof(defaultgamedir)); } else if (!strcmp(Cmd_Argv(1), "mirrors")) { nummirrors = 0; while (nummirrors < countof(mirror) && 2+nummirrors < argc) { Q_strncpyz(mirror[nummirrors], Cmd_Argv(2+nummirrors), sizeof(mirror[nummirrors])); if (!*mirror[nummirrors]) break; nummirrors++; } } else { //erk } continue; } if (version == 2) { char *fullname = Cmd_Argv(0); char *file = NULL; char *url = NULL; char *gamedir = NULL; char *ver = NULL; int extract = EXTRACT_COPY; int i; p = Z_Malloc(sizeof(*p)); for (i = 1; i < argc; i++) { char *arg = Cmd_Argv(i); if (!strncmp(arg, "url=", 4)) url = arg+4; else if (!strncmp(arg, "gamedir=", 8)) gamedir = arg+8; else if (!strncmp(arg, "ver=", 4)) ver = arg+4; else if (!strncmp(arg, "v=", 2)) ver = arg+2; else if (!strncmp(arg, "arch=", 5)) /*arch = arg+5*/; else if (!strncmp(arg, "file=", 5)) { if (!file) file = arg+5; MD_AddDep(p, DEP_FILE, arg+5); } else if (!strncmp(arg, "extract=", 8)) { if (!strcmp(arg+8, "xz")) extract = EXTRACT_XZ; else if (!strcmp(arg+8, "gz")) extract = EXTRACT_GZ; else if (!strcmp(arg+8, "zip")) extract = EXTRACT_ZIP; else Con_Printf("Unknown decompression method: %s\n", arg+8); } else if (!strncmp(arg, "depend=", 7)) MD_AddDep(p, DEP_REQUIRE, arg+7); else if (!strncmp(arg, "conflict=", 9)) MD_AddDep(p, DEP_CONFLICT, arg+9); else if (!strncmp(arg, "fileconflict=", 13)) MD_AddDep(p, DEP_FILECONFLICT, arg+13); else if (!strncmp(arg, "recommend=", 10)) MD_AddDep(p, DEP_RECOMMEND, arg+10); else { Con_DPrintf("Unknown package property\n"); } } if (*prefix) Q_snprintfz(p->fullname, sizeof(p->fullname), "%s/%s", prefix, fullname); else Q_snprintfz(p->fullname, sizeof(p->fullname), "%s", fullname); p->name = COM_SkipPath(p->fullname); if (!gamedir) gamedir = defaultgamedir; Q_strncpyz(p->version, ver?ver:"", sizeof(p->version)); Q_snprintfz(p->gamedir, sizeof(p->gamedir), "%s", gamedir); p->fsroot = FS_ROOT; p->extract = extract; if (url && (!strncmp(url, "http://", 7) || !strncmp(url, "https://", 8))) p->mirror[0] = Z_StrDup(url); else { int m; char *ext = ""; if (!url) { if (extract == EXTRACT_XZ) ext = ".xz"; else if (extract == EXTRACT_GZ) ext = ".gz"; else if (extract == EXTRACT_ZIP) ext = ".zip"; url = file; } for (m = 0; m < nummirrors; m++) p->mirror[m] = Z_StrDup(va("%s%s%s", mirror[m], url, ext)); } } else { if (argc > 5 || argc < 3) { Con_Printf("Package list is bad - %s\n", line); continue; //but try the next line away } p = Z_Malloc(sizeof(*p)); if (*prefix) Q_strncpyz(p->fullname, va("%s/%s", prefix, Cmd_Argv(0)), sizeof(p->fullname)); else Q_strncpyz(p->fullname, Cmd_Argv(0), sizeof(p->fullname)); p->name = p->fullname; while((sl = strchr(p->name, '/'))) p->name = sl+1; p->mirror[0] = Z_StrDup(Cmd_Argv(1)); MD_AddDep(p, DEP_FILE, Cmd_Argv(2)); Q_strncpyz(p->version, Cmd_Argv(3), sizeof(p->version)); Q_strncpyz(p->gamedir, Cmd_Argv(4), sizeof(p->gamedir)); if (!strcmp(p->gamedir, "../")) { p->fsroot = FS_ROOT; *p->gamedir = 0; } else { if (!*p->gamedir) { strcpy(p->gamedir, FS_GetGamedir(false)); // p->fsroot = FS_GAMEONLY; } p->fsroot = FS_ROOT; } } p->flags = flags; if (p->arch && Q_strcasecmp(p->arch, THISARCH)) p->flags |= DPF_HIDDEN; for (dep = p->deps; dep; dep = dep->next) { if (dep->dtype == DEP_FILECONFLICT) { const char *n; if (*p->gamedir) n = va("%s/%s", p->gamedir, dep->name); else n = dep->name; if (MD_CheckFile(n, p->fsroot)) p->flags |= DPF_HIDDEN; } } if (flags & DPF_HAVEAVERSION) { for (dep = p->deps; dep; dep = dep->next) { char *n; if (dep->dtype != DEP_FILE) continue; if (*p->gamedir) n = va("%s/%s", p->gamedir, dep->name); else n = dep->name; pf = FS_OpenVFS(n, "rb", p->fsroot); if (pf) VFS_CLOSE(pf); else Con_Printf("WARNING: %s (%s) no longer exists\n", p->fullname, n); } } else { for (dep = p->deps; dep; dep = dep->next) { char *n; struct packagedep_s *odep; if (dep->dtype != DEP_FILE) continue; if (*p->gamedir) n = va("%s/%s", p->gamedir, dep->name); else n = dep->name; pf = FS_OpenVFS(n, "rb", p->fsroot); if (pf) { VFS_CLOSE(pf); for (o = availablepackages; o; o = o->next) { if (o->flags & DPF_HAVEAVERSION) { if (!strcmp(p->gamedir, o->gamedir) && p->fsroot == o->fsroot) if (strcmp(p->fullname, o->fullname) || strcmp(p->version, o->version)) { for (odep = o->deps; odep; odep = odep->next) { if (!strcmp(dep->name, odep->name)) break; } if (odep) break; } } } if (!o) { p->flags |= DPF_UNKNOWNVERSION; break; } } } } p->next = first; first = p; } } return first; } static void COM_QuotedConcat(const char *cat, char *buf, size_t bufsize) { const unsigned char *gah; for (gah = (const unsigned char*)cat; *gah; gah++) { if (*gah <= ' ' || *gah == '$' || *gah == '\"') break; } if (*gah || *cat == '\\' || strstr(cat, "//") || strstr(cat, "/*")) { //contains some dodgy stuff. size_t curlen = strlen(buf); buf += curlen; bufsize -= curlen; COM_QuotedString(cat, buf, bufsize, false); } else { //okay, no need for quotes. Q_strncatz(buf, cat, bufsize); } } static void WriteInstalledPackages(void) { char *s; package_t *p; struct packagedep_s *dep; vfsfile_t *f = FS_OpenVFS(INSTALLEDFILES, "wb", FS_ROOT); if (!f) { Con_Printf("menu_download: Can't update installed list\n"); return; } s = "version 2\n"; VFS_WRITE(f, s, strlen(s)); for (p = availablepackages; p ; p=p->next) { if (p->flags & DPF_HAVEAVERSION) { char buf[8192]; buf[0] = 0; COM_QuotedString(p->fullname, buf, sizeof(buf), false); if (*p->version) { Q_strncatz(buf, " ", sizeof(buf)); COM_QuotedConcat(va("ver=%s", p->version), buf, sizeof(buf)); } //if (*p->gamedir) { Q_strncatz(buf, " ", sizeof(buf)); COM_QuotedConcat(va("gamedir=%s", p->gamedir), buf, sizeof(buf)); } if (p->arch) { Q_strncatz(buf, " ", sizeof(buf)); COM_QuotedConcat(va("arch=%s", p->arch), buf, sizeof(buf)); } for (dep = p->deps; dep; dep = dep->next) { if (dep->dtype == DEP_FILE) { Q_strncatz(buf, " ", sizeof(buf)); COM_QuotedConcat(va("file=%s", dep->name), buf, sizeof(buf)); } else if (dep->dtype == DEP_REQUIRE) { Q_strncatz(buf, " ", sizeof(buf)); COM_QuotedConcat(va("depend=%s", dep->name), buf, sizeof(buf)); } else if (dep->dtype == DEP_CONFLICT) { Q_strncatz(buf, " ", sizeof(buf)); COM_QuotedConcat(va("conflict=%s", dep->name), buf, sizeof(buf)); } else if (dep->dtype == DEP_FILECONFLICT) { Q_strncatz(buf, " ", sizeof(buf)); COM_QuotedConcat(va("fileconflict=%s", dep->name), buf, sizeof(buf)); } else if (dep->dtype == DEP_RECOMMEND) { Q_strncatz(buf, " ", sizeof(buf)); COM_QuotedConcat(va("recommend=%s", dep->name), buf, sizeof(buf)); } } Q_strncatz(buf, "\n", sizeof(buf)); VFS_WRITE(f, buf, strlen(buf)); } } VFS_CLOSE(f); } static qboolean ComparePackages(package_t **l, package_t *p) { int v = strcmp((*l)->fullname, p->fullname); if (v > 0) { p->next = (*l); (*l) = p; numpackages++; return true; } else if (v == 0) { if (!strcmp(p->version, (*l)->version)) if (!strcmp(p->gamedir, (*l)->gamedir)) // if (!strcmp((*l)->fullname, p->fullname)) { /*package matches, free the new one, don't add*/ p->override = *l; return false; } p->flags |= DPF_DISPLAYVERSION; (*l)->flags |= DPF_DISPLAYVERSION; } return false; } static void InsertPackage(package_t **l, package_t *p) { package_t *lp; if (!*l) //there IS no list. { *l = p; p->next = NULL; numpackages++; return; } if (ComparePackages(l, p)) return; for (lp = *l; lp->next; lp=lp->next) { if (ComparePackages(&lp->next, p)) return; } lp->next = p; p->next = NULL; numpackages++; } static void ConcatPackageLists(package_t *l2) { package_t *n; while(l2) { n = l2->next; l2->next = NULL; InsertPackage(&availablepackages, l2); l2 = n; } } static void M_DL_Notification(struct dl_download *dl) { int i; vfsfile_t *f; f = dl->file; dl->file = NULL; i = dl->user_num; if (f) { downloadablelistreceived[i] = 1; ConcatPackageLists(BuildPackageList(f, 0, dl->url, downloadablelistnameprefix[i])); VFS_CLOSE(f); } else downloadablelistreceived[i] = -1; } static void MD_Draw (int x, int y, struct menucustom_s *c, struct menu_s *m) { package_t *p; int fl; char *n; p = c->dptr; if (p) { fl = p->flags & (DPF_HAVEAVERSION | DPF_WANTTOINSTALL); if (p->flags & DPF_HIDDEN) { Draw_FunString (x+4, y, "---"); return; } else if (p->download) Draw_FunString (x+4, y, va("%i", (int)p->download->qdownload.percent)); else if (p->trymirrors) Draw_FunString (x+4, y, "PND"); else { switch(fl) { case 0: if (p->flags & DPF_UNKNOWNVERSION) Draw_FunString (x, y, "???"); else { Draw_FunString (x+4, y, "^Ue080^Ue082"); Draw_FunString (x+8, y, "^Ue081"); } break; case DPF_HAVEAVERSION: Draw_FunString (x, y, "DEL"); break; case DPF_WANTTOINSTALL: Draw_FunString (x, y, "GET"); break; case DPF_HAVEAVERSION | DPF_WANTTOINSTALL: Draw_FunString (x+4, y, "^Ue080^Ue082"); Draw_FunString (x+8, y, "^Ue083"); break; } } n = p->name; if (p->flags & DPF_DISPLAYVERSION) n = va("%s (%s)", n, *p->version?p->version:"unversioned"); if (&m->selecteditem->common == &c->common) Draw_AltFunString (x+48, y, n); else Draw_FunString(x+48, y, n); } } //finds the newest version static package_t *MD_FindPackage(char *packagename) { package_t *p, *r = NULL; for (p = availablepackages; p; p = p->next) { if (!strcmp(p->name, packagename)) { if (!r || strcmp(r->version, p->version)>0) r = p; } } return r; } //returns the installed version of a package, if any. static package_t *MD_HavePackage(char *packagename) { package_t *p; for (p = availablepackages; p; p = p->next) { if (p->flags & DPF_WANTTOINSTALL) if (!strcmp(p->name, packagename)) return p; } return NULL; } //just flags, doesn't delete static void MD_RemovePackage(package_t *package) { package_t *o; struct packagedep_s *dep; if (!(package->flags & DPF_WANTTOINSTALL)) return; //looks like its already deselected. package->flags &= ~DPF_WANTTOINSTALL; //Is this safe? package->trymirrors = 0; //if its enqueued, cancel that quickly... if (package->download) { //if its currently downloading, cancel it. DL_Close(package->download); package->download = NULL; } //remove stuff that depends on us for (o = availablepackages; o; o = o->next) { for (dep = package->deps; dep; dep = dep->next) if (dep->dtype == DEP_REQUIRE) if (!strcmp(dep->name, package->name)) MD_RemovePackage(o); } } //just flags, doesn't install static void MD_AddPackage(package_t *package) { package_t *o; struct packagedep_s *dep, *dep2; qboolean replacing = false; if (package->flags & DPF_WANTTOINSTALL) return; //looks like its already picked. //any file-conflicts prevent the package from being installable. //this is mostly for pak1.pak for (dep = package->deps; dep; dep = dep->next) { if (dep->dtype == DEP_FILECONFLICT) { const char *n; if (*package->gamedir) n = va("%s/%s", package->gamedir, dep->name); else n = dep->name; if (MD_CheckFile(n, package->fsroot)) return; } } package->flags |= DPF_WANTTOINSTALL; //first check to see if we're replacing a different version of the same package for (o = availablepackages; o; o = o->next) { if (o == package) continue; if (o->flags & DPF_WANTTOINSTALL) { if (!strcmp(o->fullname, package->fullname)) { //replaces this package o->flags &= ~DPF_WANTTOINSTALL; replacing = true; } else { //two packages with the same filename are always mutually incompatible, but with totally separate dependancies etc. qboolean remove = false; for (dep = package->deps; dep; dep = dep->next) { if (dep->dtype == DEP_FILE) for (dep2 = o->deps; dep2; dep2 = dep2->next) { if (dep2->dtype == DEP_FILE) if (!strcmp(dep->name, dep2->name)) { MD_RemovePackage(o); remove = true; break; } } if (remove) break; } //fixme: zip content conflicts } } } //if we are replacing an existing one, then dependancies are already settled (only because we don't do version deps) if (replacing) return; //satisfy our dependancies. for (dep = package->deps; dep; dep = dep->next) { if (dep->dtype == DEP_REQUIRE || dep->dtype == DEP_RECOMMEND) { package_t *d = MD_FindPackage(dep->name); if (d) MD_AddPackage(d); } if (dep->dtype == DEP_CONFLICT) { for (;;) { package_t *d = MD_HavePackage(dep->name); if (!d) break; MD_RemovePackage(d); } } } //remove any packages that conflict with us. for (o = availablepackages; o; o = o->next) { for (dep = package->deps; dep; dep = dep->next) if (dep->dtype == DEP_CONFLICT) if (!strcmp(dep->name, package->name)) MD_RemovePackage(o); } } static qboolean MD_Key (struct menucustom_s *c, struct menu_s *m, int key, unsigned int unicode) { package_t *p, *p2; struct packagedep_s *dep, *dep2; p = c->dptr; if (p->flags & DPF_HIDDEN) return false; if (key == K_ENTER || key == K_KP_ENTER || key == K_MOUSE1) { if (p->flags & DPF_WANTTOINSTALL) MD_RemovePackage(p); // else if (p->flags & DPF_UNKNOWNVERSION) // p->flags &= ~DPF_UNKNOWNVERSION; else MD_AddPackage(p); if (p->flags&DPF_WANTTOINSTALL) { //any other packages that conflict should be flagged for uninstall now that this one will replace it. for (p2 = availablepackages; p2; p2 = p2->next) { if (p == p2) continue; for (dep = p->deps; dep; dep = dep->next) { if (dep->dtype != DEP_FILE) continue; for (dep2 = p2->deps; dep2; dep2 = dep2->next) { if (dep2->dtype != DEP_FILE) continue; if (!strcmp(dep->name, dep2->name)) { p2->flags &= ~DPF_WANTTOINSTALL; break; } } } } } else p->trymirrors = 0; return true; } return false; } #ifdef HAVEAUTOUPDATE static void MD_EngineUpdate_Draw (int x, int y, struct menucustom_s *c, struct menu_s *m) { char *settings[] = { "Unsupported", "Revert", "Off", "Stable Updates", "Unsable Updates" }; int setting = autoupdatesetting; char *text; if (setting == -1) { setting = Sys_GetAutoUpdateSetting(); text = va("Auto Update: %s", settings[setting+1]); } else text = va("Auto Update: %s (unsaved)", settings[setting+1]); if (&m->selecteditem->common == &c->common) Draw_AltFunString (x+4, y, text); else Draw_FunString (x+4, y, text); } static qboolean MD_EngineUpdate_Key (struct menucustom_s *c, struct menu_s *m, int key, unsigned int unicode) { if (key == K_ENTER || key == K_KP_ENTER || key == K_MOUSE1) { if (autoupdatesetting == -1) autoupdatesetting = Sys_GetAutoUpdateSetting(); if (autoupdatesetting != -1) { autoupdatesetting+=1; if (autoupdatesetting >= 4) autoupdatesetting = 0; } } return false; } #endif qboolean MD_PopMenu (union menuoption_s *mo,struct menu_s *m,int key) { if (key == K_ENTER || key == K_KP_ENTER || key == K_MOUSE1) { M_RemoveMenu(m); return true; } return false; } vfsfile_t *FS_XZ_DecompressWriteFilter(vfsfile_t *infile); vfsfile_t *FS_GZ_DecompressWriteFilter(vfsfile_t *outfile, qboolean autoclosefile); static char *MD_GetTempName(package_t *p) { struct packagedep_s *dep; char *destname, *t, *ts; //always favour a file so that we can rename safely without needing a copy. for (dep = p->deps; dep; dep = dep->next) { if (dep->dtype != DEP_FILE) continue; if (*p->gamedir) destname = va("%s/%s.tmp", p->gamedir, dep->name); else destname = va("%s.tmp", dep->name); return Z_StrDup(destname); } ts = Z_StrDup(p->name); for (t = ts; *t; t++) { switch(*t) { case '/': case '?': case '<': case '>': case '\\': case ':': case '*': case '|': case '\"': case '.': *t = '_'; break; default: break; } } if (*ts) { if (*p->gamedir) destname = va("%s/%s.tmp", p->gamedir, ts); else destname = va("%s.tmp", ts); } else destname = va("%x.tmp", (unsigned int)(quintptr_t)p); Z_Free(ts); return Z_StrDup(destname); } static void Menu_Download_Got(struct dl_download *dl); static void MD_StartADownload(void) { vfsfile_t *tmpfile; char *temp; // char native[MAX_OSPATH]; package_t *p; int simultaneous = 1; int i; for (p = availablepackages; p ; p=p->next) { if (p->download) simultaneous--; } for (p = availablepackages; p && simultaneous > 0; p=p->next) { if (p->trymirrors) { //flagged for a (re?)download char *mirror = NULL; for (i = 0; i < countof(p->mirror); i++) { if (p->mirror[i] && (p->trymirrors & (1u<mirror[i]; p->trymirrors &= ~(1u<trymirrors = 0; continue; } temp = MD_GetTempName(p); //FIXME: we should lock in the temp path, in case the user foolishly tries to change gamedirs. FS_CreatePath(temp, p->fsroot); switch (p->extract) { case EXTRACT_ZIP: case EXTRACT_COPY: tmpfile = FS_OpenVFS(temp, "wb", p->fsroot); break; #ifdef AVAIL_XZDEC case EXTRACT_XZ: { vfsfile_t *raw; raw = FS_OpenVFS(temp, "wb", p->fsroot); tmpfile = FS_XZ_DecompressWriteFilter(raw); if (!tmpfile) VFS_CLOSE(raw); } break; #endif #ifdef AVAIL_GZDEC case EXTRACT_GZ: { vfsfile_t *raw; raw = FS_OpenVFS(temp, "wb", p->fsroot); tmpfile = FS_GZ_DecompressWriteFilter(raw, true); if (!tmpfile) VFS_CLOSE(raw); } break; #endif default: Con_Printf("decompression method not supported\n"); continue; } if (tmpfile) p->download = HTTP_CL_Get(mirror, NULL, Menu_Download_Got); if (p->download) { Con_Printf("Downloading %s\n", p->fullname); p->download->file = tmpfile; p->download->user_ctx = temp; #ifdef MULTITHREAD DL_CreateThread(p->download, NULL, NULL); #endif } else { Con_Printf("Unable to download %s\n", p->fullname); p->flags &= ~DPF_WANTTOINSTALL; //can't do it. if (tmpfile) VFS_CLOSE(tmpfile); FS_Remove(temp, p->fsroot); } simultaneous--; } } } static qboolean MD_ApplyDownloads (union menuoption_s *mo,struct menu_s *m,int key) { if (key == K_ENTER || key == K_KP_ENTER || key == K_MOUSE1) { package_t *p, *o, **link; #ifdef HAVEAUTOUPDATE if (autoupdatesetting != -1) { Sys_SetAutoUpdateSetting(autoupdatesetting); autoupdatesetting = -1; } #endif //delete any that don't exist for (link = &availablepackages; *link ; ) { p = *link; if (!(p->flags&DPF_WANTTOINSTALL) && (p->flags&DPF_HAVEAVERSION)) { //if we don't want it but we have it anyway: qboolean reloadpacks = false; struct packagedep_s *dep; for (dep = p->deps; dep; dep = dep->next) { if (dep->dtype == DEP_FILE) { if (!reloadpacks) { char ext[8]; COM_FileExtension(dep->name, ext, sizeof(ext)); if (!stricmp(ext, "pak") || !stricmp(ext, "pk3")) { reloadpacks = true; FS_UnloadPackFiles(); } } if (*p->gamedir) FS_Remove(va("%s/%s", p->gamedir, dep->name), p->fsroot); else FS_Remove(dep->name, p->fsroot); } } if (reloadpacks) FS_ReloadPackFiles(); p->flags &= ~DPF_UNKNOWNVERSION; p->flags &= ~DPF_HAVEAVERSION; //make sure it actually got wiped. if there's still a file there then something went screwy. //we don't reliably know if the remove actually succeeded or failed. for (dep = p->deps; dep; dep = dep->next) { if (dep->dtype == DEP_FILE) { const char *n; if (*p->gamedir) n = va("%s/%s", p->gamedir, dep->name); else n = dep->name; if (MD_CheckFile(n, p->fsroot)) { p->flags |= DPF_UNKNOWNVERSION; break; } } } WriteInstalledPackages(); if (p->flags & DPF_FORGETONUNINSTALL) { *link = p->next; for (o = availablepackages; o; o = o->next) { if (o->override == p) o->override = NULL; } p->flags |= DPF_HIDDEN; // BZ_Free(p); continue; } } link = &(*link)->next; } //and flag any new/updated ones for a download for (p = availablepackages; p ; p=p->next) { if ((p->flags&DPF_WANTTOINSTALL) && !(p->flags&DPF_HAVEAVERSION) && !p->download) p->trymirrors = ~0u; } MD_StartADownload(); //and try to do those downloads. return true; } return false; } void M_AddItemsToDownloadMenu(menu_t *m) { char path[MAX_QPATH]; int y; package_t *p; menucustom_t *c; char *slash; menuoption_t *mo; dlmenu_t *info = m->data; int prefixlen; p = availablepackages; prefixlen = strlen(info->pathprefix); y = 48; MC_AddCommand(m, 0, 170, y, "Apply", MD_ApplyDownloads); y+=8; MC_AddCommand(m, 0, 170, y, "Back", MD_PopMenu); y+=8; #ifdef HAVEAUTOUPDATE if (!prefixlen) { c = MC_AddCustom(m, 0, y, p, 0); c->draw = MD_EngineUpdate_Draw; c->key = MD_EngineUpdate_Key; c->common.width = 320; c->common.height = 8; y += 8; } #endif y+=4; //small gap for (p = availablepackages; p; p = p->next) { if (strncmp(p->fullname, info->pathprefix, prefixlen)) continue; if ((p->flags & DPF_HIDDEN) && !(p->flags & DPF_HAVEAVERSION)) continue; if (p->override) continue; slash = strchr(p->fullname+prefixlen, '/'); if (slash) { Q_strncpyz(path, p->fullname, MAX_QPATH); slash = strchr(path+prefixlen, '/'); if (slash) *slash = '\0'; for (mo = m->options; mo; mo = mo->common.next) if (mo->common.type == mt_button) if (!strcmp(mo->button.text, path + prefixlen)) break; if (!mo) { menubutton_t *b = MC_AddConsoleCommand(m, 6*8, 170, y, path+prefixlen, va("menu_download \"%s/\"", path)); y += 8; if (!m->selecteditem) m->selecteditem = (menuoption_t*)b; } } else { c = MC_AddCustom(m, 0, y, p, 0); c->draw = MD_Draw; c->key = MD_Key; c->common.width = 320; c->common.height = 8; y += 8; if (!m->selecteditem) m->selecteditem = (menuoption_t*)c; } } } void M_Download_UpdateStatus(struct menu_s *m) { struct dl_download *dl; dlmenu_t *info = m->data; int i; while (!cls.download && (info->parsedsourcenum==-1 || info->parsedsourcenum < numdownloadablelists)) { //done downloading info->parsedsourcenum++; if (info->parsedsourcenum < numdownloadablelists) { if (!downloadablelistreceived[info->parsedsourcenum]) { dl = HTTP_CL_Get(va("%s"DOWNLOADABLESARGS, downloadablelist[info->parsedsourcenum]), NULL, M_DL_Notification); if (dl) { dl->user_num = info->parsedsourcenum; dl->file = VFSPIPE_Open(); dl->isquery = true; } else Con_Printf("Could not contact server\n"); return; } } } for (i = 0; i < numdownloadablelists; i++) { if (!downloadablelistreceived[i]) { // Draw_String(x+8, y+8, "Waiting for package list"); return; } } if (!info->populated) { info->populated = true; M_AddItemsToDownloadMenu(m); } } #include "fs.h" static int QDECL MD_ExtractFiles(const char *fname, qofs_t fsize, time_t mtime, void *parm, searchpathfuncs_t *spath) { //this is gonna suck. threading would help, but gah. package_t *p = parm; flocation_t loc; if (spath->FindFile(spath, &loc, fname, NULL) && loc.len < 0x80000000u) { char *f = malloc(loc.len); const char *n; if (f) { spath->ReadFile(spath, &loc, f); if (*p->gamedir) n = va("%s/%s", p->gamedir, fname); else n = fname; FS_WriteFile(n, f, loc.len, p->fsroot); free(f); //keep track of the installed files, so we can delete them properly after. MD_AddDep(p, DEP_FILE, fname); } } return 1; } static void Menu_Download_Got(struct dl_download *dl) { qboolean successful = dl->status == DL_FINISHED; package_t *p; char *tempname = dl->user_ctx; for (p = availablepackages; p ; p=p->next) { if (p->download == dl) break; } if (dl->file) { VFS_CLOSE(dl->file); dl->file = NULL; } if (p) { char ext[8]; char *destname; struct packagedep_s *dep; p->download = NULL; if (!successful) { Con_Printf("Couldn't download %s (from %s)\n", p->name, dl->url); FS_Remove (tempname, p->fsroot); Z_Free(tempname); MD_StartADownload(); return; } if (p->extract == EXTRACT_ZIP) { vfsfile_t *f = FS_OpenVFS(tempname, "rb", p->fsroot); if (f) { searchpathfuncs_t *archive = FSZIP_LoadArchive(f, tempname, NULL); if (archive) { archive->EnumerateFiles(archive, "*", MD_ExtractFiles, p); archive->ClosePath(archive); p->flags |= DPF_HAVEAVERSION; WriteInstalledPackages(); if (!stricmp(ext, "pak") || !stricmp(ext, "pk3")) FS_ReloadPackFiles(); } else VFS_CLOSE(f); } FS_Remove (tempname, FS_GAMEONLY); Z_Free(tempname); MD_StartADownload(); return; } else { for (dep = p->deps; dep; dep = dep->next) { if (dep->dtype != DEP_FILE) continue; COM_FileExtension(dep->name, ext, sizeof(ext)); if (!stricmp(ext, "pak") || !stricmp(ext, "pk3")) FS_UnloadPackFiles(); //we reload them after if (*p->gamedir) destname = va("%s/%s", p->gamedir, dep->name); else destname = dep->name; if (FS_Remove(destname, p->fsroot)) ; if (!FS_Rename2(tempname, destname, p->fsroot, p->fsroot)) { Con_Printf("Couldn't rename %s to %s. Removed instead.\n", tempname, destname); FS_Remove (tempname, p->fsroot); Z_Free(tempname); MD_StartADownload(); return; } Z_Free(tempname); Con_Printf("Downloaded %s (to %s)\n", p->name, destname); p->flags |= DPF_HAVEAVERSION; WriteInstalledPackages(); if (!stricmp(ext, "pak") || !stricmp(ext, "pk3")) FS_ReloadPackFiles(); MD_StartADownload(); return; } } Con_Printf("menu_download: %s has no filename info\n", p->name); } else Con_Printf("menu_download: Can't figure out where %s came from (url: %s)\n", dl->localname, dl->url); FS_Remove (tempname, FS_GAMEONLY); Z_Free(tempname); MD_StartADownload(); } void Menu_DownloadStuff_f (void) { static qboolean loadedinstalled; int i; menu_t *menu; dlmenu_t *info; Key_Dest_Add(kdm_emenu); m_state = m_complex; menu = M_CreateMenu(sizeof(dlmenu_t)); info = menu->data; menu->predraw = M_Download_UpdateStatus; /* menu->selecteditem = (menuoption_t *)(info->list = MC_AddCustom(menu, 0, 32, NULL)); info->list->draw = M_Download_Draw; info->list->key = M_Download_Key; */ info->parsedsourcenum = -1; if (*fs_downloads_url.string) M_DL_AddSubList(fs_downloads_url.string, ""); Q_strncpyz(info->pathprefix, Cmd_Argv(1), sizeof(info->pathprefix)); if (!*info->pathprefix) { for (i = 0; i < numdownloadablelists; i++) downloadablelistreceived[i] = 0; } MC_AddWhiteText(menu, 24, 170, 8, "Downloads", false); MC_AddWhiteText(menu, 16, 170, 24, "^Ue01d^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01e^Ue01f", false); if (!loadedinstalled) { vfsfile_t *f = FS_OpenVFS(INSTALLEDFILES, "rb", FS_ROOT); loadedinstalled = true; if (f) { ConcatPackageLists(BuildPackageList(f, DPF_FORGETONUNINSTALL|DPF_HAVEAVERSION|DPF_WANTTOINSTALL, NULL, "")); VFS_CLOSE(f); } } } #else void Menu_DownloadStuff_f (void) { Con_Printf("Download menu not implemented in this build\n"); } #endif