Tweak QI plugin to translate quaddicted's map database to fte's meta format. Make 'map package:map' download+run a map with the specified package enabled.

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5976 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
Spoike 2021-07-17 15:11:35 +00:00
parent 9e54944bde
commit 0907e66cff
11 changed files with 750 additions and 170 deletions

View File

@ -5378,7 +5378,6 @@ extern int waitingformanifest;
void Host_DoRunFile(hrf_t *f);
void CL_PlayDemoStream(vfsfile_t *file, char *filename, qboolean issyspath, int demotype, float bufferdelay);
void CL_ParseQTVDescriptor(vfsfile_t *f, const char *name);
qboolean FS_PathURLCache(char *url, char *path, size_t pathsize);
//guesses the file type based upon its file extension. mdl/md3/iqm distinctions are not important, so we can usually get away with this in the context of quake.
unsigned int Host_GuessFileType(const char *mimetype, const char *filename)

View File

@ -78,6 +78,7 @@ cvar_t pkg_autoupdate = CVARFD("pkg_autoupdate", "-1", CVAR_NOTFROMSERVER|CVAR_N
#define DPF_SIGNATUREREJECTED (1u<<17) //signature is bad
#define DPF_SIGNATUREACCEPTED (1u<<18) //signature is good (required for dll/so/exe files)
#define DPF_SIGNATUREUNKNOWN (1u<<19) //signature is unknown
#define DPF_HIDEUNLESSPRESENT (1u<<20) //hidden in the ui, unless present.
#define DPF_MARKED (DPF_USERMARKED|DPF_AUTOMARKED) //flags that will enable it
#define DPF_ALLMARKED (DPF_USERMARKED|DPF_AUTOMARKED|DPF_MANIMARKED) //flags that will download it without necessarily enabling it.
@ -133,6 +134,7 @@ typedef struct package_s {
char version[16];
char *arch;
char *qhash;
char *packprefix; //extra weirdness to skip embedded gamedirs or force extra maps/ nesting
quint64_t filesize; //in bytes, as part of verifying the hash.
char *filesha1;
@ -168,10 +170,12 @@ typedef struct package_s {
DEP_NEEDFEATURE, //requires a specific feature to be available (typically controlled via a cvar)
// DEP_MIRROR,
// DEP_FAILEDMIRROR,
DEP_MAP, //This package contains this map. woo.
DEP_SOURCE, //which source url we found this package from
DEP_EXTRACTNAME, //a file that will be installed
DEP_FILE //a file that will be installed
DEP_FILE, //a file that will be installed. will be loaded as a package, where appropriate.
DEP_CACHEFILE //an installed file that's relative to the downloads/ subdir
} dtype;
char name[1];
} *deps;
@ -194,6 +198,12 @@ static char *manifestpackages; //metapackage named by the manicfest.
static char *declinedpackages; //metapackage named by the manicfest.
static int domanifestinstall; //SECURITY_MANIFEST_*
static struct
{
char *package; //package to load. don't forget its dependancies too.
char *map; //the map to load.
} pm_onload;
#ifndef SERVERONLY
//static qboolean pluginpromptshown; //so we only show prompts for new externally-installed plugins once, instead of every time the file is reloaded.
#endif
@ -209,7 +219,7 @@ static qboolean pm_packagesinstalled;
//FIXME: these are allocated for the life of the exe. changing basedir should purge the list.
static size_t pm_numsources = 0;
static struct
static struct pm_source_s
{
char *url; //url to query. unique.
char *prefix; //category prefix for packages from this source.
@ -229,13 +239,17 @@ static struct
#define SRCFL_NESTED (1u<<1) //discovered from a different source. always disabled.
#define SRCFL_MANIFEST (1u<<2) //not saved. often default to enabled.
#define SRCFL_USER (1u<<3) //user explicitly added it. included into installed.lst. enabled (if trusted).
#define SRCFLMASK_FROM (SRCFL_HISTORIC|SRCFL_NESTED|SRCFL_MANIFEST|SRCFL_USER) //mask of flags, forming priority for replacements.
#define SRCFL_DISABLED (1u<<4) //source was explicitly disabled.
#define SRCFL_ENABLED (1u<<5) //source was explicitly enabled.
#define SRCFL_PROMPTED (1u<<6) //source was explicitly enabled.
#define SRCFL_ONCE (1u<<7) //autoupdates are disabled, but the user is viewing packages anyway. enabled via a prompt.
#define SRCFL_PLUGIN (1u<<4) //user explicitly added it. included into installed.lst. enabled (if trusted).
#define SRCFLMASK_FROM (SRCFL_HISTORIC|SRCFL_NESTED|SRCFL_MANIFEST|SRCFL_USER|SRCFL_PLUGIN) //mask of flags, forming priority for replacements.
#define SRCFL_DISABLED (1u<<5) //source was explicitly disabled.
#define SRCFL_ENABLED (1u<<6) //source was explicitly enabled.
#define SRCFL_PROMPTED (1u<<7) //source was explicitly enabled.
#define SRCFL_ONCE (1u<<8) //autoupdates are disabled, but the user is viewing packages anyway. enabled via a prompt.
unsigned int flags;
struct dl_download *curdl; //the download context
void *module; //plugins
plugupdatesourcefuncs_t *funcs;
} *pm_source/*[pm_maxsources]*/;
static int downloadablessequence; //bumped any time any package is purged
@ -478,33 +492,47 @@ static void PM_ValidatePackage(package_t *p)
struct packagedep_s *dep;
vfsfile_t *pf;
char temp[MAX_OSPATH];
p->flags &=~ (DPF_NATIVE|DPF_CACHED|DPF_CORRUPT);
p->flags &=~ (DPF_NATIVE|DPF_CACHED|DPF_CORRUPT|DPF_HIDEUNLESSPRESENT);
if (p->flags & DPF_ENABLED)
{
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)
if (dep->dtype == DEP_CACHEFILE)
{
VFS_CLOSE(pf);
p->flags |= DPF_NATIVE;
}
else if (PM_TryGenCachedName(n, p, temp, sizeof(temp)))
{
pf = FS_OpenVFS(temp, "rb", p->fsroot);
// p->flags |= DPF_HIDEUNLESSPRESENT;
n = va("downloads/%s", dep->name);
pf = FS_OpenVFS(n, "rb", p->fsroot);
if (pf)
{
VFS_CLOSE(pf);
p->flags |= DPF_CACHED;
}
}
else if (dep->dtype == DEP_FILE)
{
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);
p->flags |= DPF_NATIVE;
}
else if (PM_TryGenCachedName(n, p, temp, sizeof(temp)))
{
pf = FS_OpenVFS(temp, "rb", p->fsroot);
if (pf)
{
VFS_CLOSE(pf);
p->flags |= DPF_CACHED;
}
}
}
else
continue;
if (!(p->flags & (DPF_NATIVE|DPF_CACHED)))
Con_Printf("WARNING: %s (%s) no longer exists\n", p->name, n);
}
@ -516,19 +544,29 @@ static void PM_ValidatePackage(package_t *p)
char *n;
struct packagedep_s *odep;
unsigned int fl = DPF_NATIVE;
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 && PM_TryGenCachedName(n, p, temp, sizeof(temp)))
if (dep->dtype == DEP_FILE)
{
pf = FS_OpenVFS(temp, "rb", p->fsroot);
fl = DPF_CACHED;
//fixme: skip any archive checks
if (*p->gamedir)
n = va("%s/%s", p->gamedir, dep->name);
else
n = dep->name;
pf = FS_OpenVFS(n, "rb", p->fsroot);
if (!pf && PM_TryGenCachedName(n, p, temp, sizeof(temp)))
{
pf = FS_OpenVFS(temp, "rb", p->fsroot);
fl = DPF_CACHED;
//fixme: skip any archive checks
}
}
else if (dep->dtype == DEP_CACHEFILE)
{
// p->flags |= DPF_HIDEUNLESSPRESENT;
fl = DPF_CACHED;
n = va("downloads/%s", dep->name);
pf = FS_OpenVFS(n, "rb", p->fsroot);
}
else
continue;
if (pf)
{
@ -714,10 +752,56 @@ static qboolean PM_MergePackage(package_t *oldp, package_t *newp)
return false;
}
static int QDECL PM_PackageSortOrdering(const void *l, const void *r)
{ //for qsort.
const package_t *a=*(package_t*const*)l, *b=*(package_t*const*)r;
const char *ac, *bc;
int order;
//sort by categories
ac = a->category?a->category:"";
bc = b->category?b->category:"";
order = Q_strcasecmp(ac,bc);
if (order)
return order;
//otherwise sort by title.
ac = a->title?a->title:a->name;
bc = b->title?b->title:b->name;
order = Q_strcasecmp(ac,bc);
return order;
}
static void PM_ResortPackages(void)
{
int i, count;
package_t **sorted;
package_t *p;
for (count = 0, p = availablepackages; p; p=p->next)
count++;
if (!count)
return;
sorted = Z_Malloc(sizeof(*sorted)*count);
for (count = 0, p = availablepackages; p; p=p->next)
sorted[count++] = p;
qsort(sorted, count, sizeof(*sorted), PM_PackageSortOrdering);
availablepackages = NULL;
for (i = count; i --> 0; )
{
sorted[i]->next = availablepackages;
sorted[i]->link = &availablepackages;
if (availablepackages)
availablepackages->link = &sorted[i]->next;
availablepackages = sorted[i];
}
Z_Free(sorted);
}
static package_t *PM_InsertPackage(package_t *p)
{
package_t **link;
int v;
for (link = &availablepackages; *link; link = &(*link)->next)
{
package_t *prev = *link;
@ -755,7 +839,7 @@ static package_t *PM_InsertPackage(package_t *p)
//FIXME: replace prev...
}
}
v = strcmp(prev->name, p->name);
v = PM_PackageSortOrdering(&prev, &p);
if (v > 0)
break; //insert before this one
else if (v == 0)
@ -870,7 +954,7 @@ static qboolean PM_CheckFile(const char *filename, enum fs_relative base)
return false;
}
static void PM_AddSubList(const char *url, const char *prefix, unsigned int flags)
static void PM_AddSubListModule(void *module, plugupdatesourcefuncs_t *funcs, const char *url, const char *prefix, unsigned int flags)
{
size_t i;
if (!prefix)
@ -888,6 +972,11 @@ static void PM_AddSubList(const char *url, const char *prefix, unsigned int flag
{
unsigned int newpri = flags&SRCFLMASK_FROM;
unsigned int oldpri = pm_source[i].flags&SRCFLMASK_FROM;
if (module)
{
pm_source[i].module = module;
pm_source[i].funcs = funcs;
}
if (newpri > oldpri)
{ //replacing an historic package should stomp on most of it, retaining only its enablement status.
pm_source[i].flags &= ~SRCFLMASK_FROM;
@ -903,6 +992,8 @@ static void PM_AddSubList(const char *url, const char *prefix, unsigned int flag
{
Z_ReallocElements((void*)&pm_source, &pm_numsources, i+1, sizeof(*pm_source));
pm_source[i].module = module;
pm_source[i].funcs = funcs;
pm_source[i].status = SRCSTAT_UNTRIED;
pm_source[i].flags = flags;
@ -915,6 +1006,10 @@ static void PM_AddSubList(const char *url, const char *prefix, unsigned int flag
downloadablessequence++;
}
}
static void PM_AddSubList(const char *url, const char *prefix, unsigned int flags)
{
PM_AddSubListModule(NULL, NULL, url, prefix, flags);
}
#ifdef WEBCLIENT
static void PM_RemSubList(const char *url)
{
@ -962,52 +1057,6 @@ static const char *PM_ParsePackage(struct packagesourceinfo_s *source, const cha
qboolean invariation = false;
#if 0
if (version < 2)
{
char pathname[256];
const char *fullname = Cmd_Argv(0);
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_snprintfz(pathname, sizeof(pathname), "%s/%s", prefix, fullname);
else
Q_snprintfz(pathname, sizeof(pathname), "%s", fullname);
p->name = Z_StrDup(COM_SkipPath(pathname));
p->title = Z_StrDup(p->name);
*COM_SkipPath(pathname) = 0;
p->category = Z_StrDup(pathname);
p->mirror[0] = Z_StrDup(p->name);
p->priority = PM_DEFAULTPRIORITY;
p->flags = parseflags;
p->mirror[0] = Z_StrDup(Cmd_Argv(1));
PM_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;
}
}
else
#endif
{
char pathname[256];
char *fullname = (source->version >= 3)?NULL:Z_StrDup(com_token);
@ -1107,6 +1156,8 @@ static const char *PM_ParsePackage(struct packagesourceinfo_s *source, const cha
p->priority = atoi(val);
else if (!strcmp(key, "qhash"))
Z_StrDupPtr(&p->qhash, val);
else if (!strcmp(key, "packprefix"))
Z_StrDupPtr(&p->packprefix, val);
else if (!strcmp(key, "desc") || !strcmp(key, "description"))
{
if (p->description)
@ -1132,6 +1183,12 @@ static const char *PM_ParsePackage(struct packagesourceinfo_s *source, const cha
Z_StrDupPtr(&file, val);
PM_AddDep(p, DEP_FILE, val);
}
else if (!strcmp(key, "cachefile"))
{ //installed file
if (!file)
Z_StrDupPtr(&file, val);
PM_AddDep(p, DEP_CACHEFILE, val);
}
else if (!strcmp(key, "extract"))
{
if (!strcmp(val, "xz"))
@ -1145,6 +1202,8 @@ static const char *PM_ParsePackage(struct packagesourceinfo_s *source, const cha
else
Con_Printf("Unknown decompression method: %s\n", val);
}
else if (!strcmp(key, "map"))
PM_AddDep(p, DEP_MAP, val);
else if (!strcmp(key, "depend"))
PM_AddDep(p, DEP_REQUIRE, val);
else if (!strcmp(key, "conflict"))
@ -1227,7 +1286,15 @@ static const char *PM_ParsePackage(struct packagesourceinfo_s *source, const cha
p->flags = flags;
if (url && (!strncmp(url, "http://", 7) || !strncmp(url, "https://", 8)))
{
p->mirror[0] = Z_StrDup(url);
if (!file)
{
FS_PathURLCache(url, pathname, sizeof(pathname));
PM_AddDep(p, DEP_CACHEFILE, pathname+10);
}
}
else
{
int m;
@ -1589,6 +1656,42 @@ void PM_EnumeratePlugins(void (*callback)(const char *name))
}
}
#endif
void PM_EnumerateMaps(const char *partial, struct xcommandargcompletioncb_s *ctx)
{
package_t *p;
struct packagedep_s *d;
size_t partiallen = strlen(partial);
char mname[256];
const char *sep = strchr(partial, ':');
size_t pkgpartiallen = sep?sep-partial:partiallen;
PM_PreparePackageList();
for (p = availablepackages; p; p = p->next)
{
if (!Q_strncasecmp(p->name, partial, pkgpartiallen))
{
for (d = p->deps; d; d = d->next)
{
if (d->dtype == DEP_MAP)
{
/*if (!strchr(partial, ':'))
{ //try to expand to only one...
Q_snprintfz(mname, sizeof(mname), "%s:", p->name);
ctx->cb(mname, NULL, NULL, ctx);
break;
}
else*/
{
Q_snprintfz(mname, sizeof(mname), "%s:%s", p->name, d->name);
if (!Q_strncasecmp(mname, partial, partiallen))
ctx->cb(mname, NULL, NULL, ctx);
}
}
}
}
}
}
static qboolean QDECL Host_StubClose (struct vfsfile_s *file)
{
@ -1722,7 +1825,10 @@ static qboolean PM_FileInstalled_Internal(const char *package, const char *categ
p->flags |= DPF_USERMARKED|DPF_ENABLED;
if (PM_InsertPackage(p))
{
PM_ResortPackages();
PM_WriteInstalledPackages();
}
return true;
}
@ -1911,7 +2017,12 @@ void PM_LoadPackages(searchpath_t **oldpaths, const char *parent_pure, const cha
if (d->dtype == DEP_FILE)
{
Q_snprintfz(temp, sizeof(temp), "%s/%s", p->gamedir, d->name);
FS_AddHashedPackage(oldpaths, parent_pure, parent_logical, search, loadstuff, temp, p->qhash, NULL, SPF_COPYPROTECTED|SPF_UNTRUSTED);
FS_AddHashedPackage(oldpaths, parent_pure, parent_logical, search, loadstuff, temp, *p->qhash?p->qhash:NULL, p->packprefix, SPF_COPYPROTECTED|SPF_UNTRUSTED);
}
else if (d->dtype == DEP_CACHEFILE)
{
Q_snprintfz(temp, sizeof(temp), "downloads/%s", d->name);
FS_AddHashedPackage(oldpaths, parent_pure, parent_logical, NULL, loadstuff, temp, *p->qhash?p->qhash:NULL, p->packprefix, SPF_COPYPROTECTED|SPF_UNTRUSTED);
}
}
}
@ -1921,14 +2032,14 @@ void PM_LoadPackages(searchpath_t **oldpaths, const char *parent_pure, const cha
void PM_Shutdown(qboolean soft)
{
size_t i, pm_numoldsources = pm_numsources;
//free everything...
downloadablessequence++;
while(pm_numsources > 0)
pm_numsources = 0;
for (i = 0; i < pm_numoldsources; i++)
{
size_t i = --pm_numsources;
#ifdef WEBCLIENT
if (pm_source[i].curdl)
{
@ -1937,13 +2048,24 @@ void PM_Shutdown(qboolean soft)
}
#endif
pm_source[i].status = SRCSTAT_UNTRIED;
Z_Free(pm_source[i].url);
pm_source[i].url = NULL;
Z_Free(pm_source[i].prefix);
pm_source[i].prefix = NULL;
if (pm_source[i].module && soft)
{ //added via a plugin. reset rather than forget.
pm_source[pm_numsources++] = pm_source[i];
}
else
{ //forget it, oh noes.
Z_Free(pm_source[i].url);
pm_source[i].url = NULL;
Z_Free(pm_source[i].prefix);
pm_source[i].prefix = NULL;
}
}
if (!pm_numsources)
{
Z_Free(pm_source);
pm_source = NULL;
}
Z_Free(pm_source);
pm_source = NULL;
if (!soft)
{
@ -2482,6 +2604,7 @@ static void PM_ListDownloaded(struct dl_download *dl)
{
pm_source[listidx].status = SRCSTAT_OBTAINED;
PM_ParsePackageList(f, 0, dl->url, pm_source[listidx].prefix);
PM_ResortPackages();
}
else if (dl->replycode == HTTP_DNSFAILURE)
pm_source[listidx].status = SRCSTAT_FAILED_DNS;
@ -2560,6 +2683,22 @@ static void PM_ListDownloaded(struct dl_download *dl)
}
}
}
static void PM_Plugin_Source_Finished(void *ctx, vfsfile_t *f)
{
struct pm_source_s *src = ctx;
COM_AssertMainThread("PM_Plugin_Source_Finished");
if (!src->curdl)
{
struct dl_download dl;
dl.file = f;
dl.status = DL_FINISHED;
dl.user_num = src-pm_source;
dl.url = src->url;
src->curdl = &dl;
PM_ListDownloaded(&dl);
}
VFS_CLOSE(f);
}
#endif
#if defined(HAVE_CLIENT) && defined(WEBCLIENT)
static void PM_UpdatePackageList(qboolean autoupdate, int retry);
@ -2633,19 +2772,27 @@ static void PM_UpdatePackageList(qboolean autoupdate, int retry)
if (pm_source[i].status == SRCSTAT_OBTAINED)
return; //already successful once. no need to do it again.
pm_source[i].flags &= ~SRCFL_ONCE;
pm_source[i].curdl = HTTP_CL_Get(pm_source[i].url, NULL, PM_ListDownloaded);
if (pm_source[i].curdl)
{
pm_source[i].curdl->user_num = i;
pm_source[i].curdl->file = VFSPIPE_Open(1, false);
pm_source[i].curdl->isquery = true;
DL_CreateThread(pm_source[i].curdl, NULL, NULL);
if (pm_source[i].funcs)
{
pm_source[i].funcs->Update(pm_source[i].url, VFS_OpenPipeCallback(PM_Plugin_Source_Finished, &pm_source[i]));
}
else
{
Con_Printf("Could not contact updates server - %s\n", pm_source[i].url);
pm_source[i].status = SRCSTAT_FAILED_DNS;
pm_source[i].curdl = HTTP_CL_Get(pm_source[i].url, NULL, PM_ListDownloaded);
if (pm_source[i].curdl)
{
pm_source[i].curdl->user_num = i;
pm_source[i].curdl->file = VFSPIPE_Open(1, false);
pm_source[i].curdl->isquery = true;
DL_CreateThread(pm_source[i].curdl, NULL, NULL);
}
else
{
Con_Printf("Could not contact updates server - %s\n", pm_source[i].url);
pm_source[i].status = SRCSTAT_FAILED_DNS;
}
}
}
@ -2665,7 +2812,24 @@ static void PM_UpdatePackageList(qboolean autoupdate, int retry)
#endif
}
qboolean PM_RegisterUpdateSource(void *module, plugupdatesourcefuncs_t *funcs)
{
size_t i;
if (!funcs)
{
for (i = 0; i < pm_numsources; i++)
{
if (pm_source[i].module == module)
{
pm_source[i].module = NULL;
pm_source[i].funcs = NULL;
}
}
}
else
PM_AddSubListModule(module, funcs, va("plug:%s", funcs->description), NULL, SRCFL_PLUGIN);
return true;
}
static void COM_QuotedConcat(const char *cat, char *buf, size_t bufsize)
{
@ -2798,11 +2962,17 @@ static void PM_WriteInstalledPackages(void)
if (p->fsroot == FS_BINARYPATH)
COM_QuotedKeyVal("root", "bin", buf, sizeof(buf));
if (p->packprefix)
COM_QuotedKeyVal("packprefix", p->packprefix, buf, sizeof(buf));
for (dep = p->deps; dep; dep = dep->next)
{
if (dep->dtype == DEP_FILE)
COM_QuotedKeyVal("file", dep->name, buf, sizeof(buf));
else if (dep->dtype == DEP_CACHEFILE)
COM_QuotedKeyVal("cachefile", dep->name, buf, sizeof(buf));
else if (dep->dtype == DEP_MAP)
COM_QuotedKeyVal("map", dep->name, buf, sizeof(buf));
else if (dep->dtype == DEP_REQUIRE)
COM_QuotedKeyVal("depend", dep->name, buf, sizeof(buf));
else if (dep->dtype == DEP_CONFLICT)
@ -2910,6 +3080,11 @@ static void PM_WriteInstalledPackages(void)
Q_strncatz(buf, " ", sizeof(buf));
COM_QuotedConcat("root=bin", buf, sizeof(buf));
}
if (p->packprefix)
{
Q_strncatz(buf, " ", sizeof(buf));
COM_QuotedConcat(va("packprefix=%s", p->packprefix), buf, sizeof(buf));
}
for (dep = p->deps; dep; dep = dep->next)
{
@ -2918,6 +3093,16 @@ static void PM_WriteInstalledPackages(void)
Q_strncatz(buf, " ", sizeof(buf));
COM_QuotedConcat(va("file=%s", dep->name), buf, sizeof(buf));
}
else if (dep->dtype == DEP_CACHEFILE)
{
Q_strncatz(buf, " ", sizeof(buf));
COM_QuotedConcat(va("cachefile=%s", dep->name), buf, sizeof(buf));
}
else if (dep->dtype == DEP_MAP)
{
Q_strncatz(buf, " ", sizeof(buf));
COM_QuotedConcat(va("map=%s", dep->name), buf, sizeof(buf));
}
else if (dep->dtype == DEP_REQUIRE)
{
Q_strncatz(buf, " ", sizeof(buf));
@ -2984,10 +3169,10 @@ static void PM_PackageEnabled(package_t *p)
FS_FlushFSHashFull();
for (dep = p->deps; dep; dep = dep->next)
{
if (dep->dtype != DEP_FILE)
if (dep->dtype != DEP_FILE && dep->dtype != DEP_CACHEFILE)
continue;
COM_FileExtension(dep->name, ext, sizeof(ext));
if (!stricmp(ext, "pak") || !stricmp(ext, "pk3"))
if (!stricmp(ext, "pak") || !stricmp(ext, "pk3") || !stricmp(ext, "zip"))
{
if (pm_packagesinstalled)
{
@ -3172,33 +3357,47 @@ static void PM_Download_Got(int iarg, void *data)
}
#endif
for (dep = p->deps; dep; dep = dep->next)
{
unsigned int nfl;
if (dep->dtype != DEP_FILE)
if (dep->dtype != DEP_FILE && dep->dtype != DEP_CACHEFILE)
continue;
COM_FileExtension(dep->name, ext, sizeof(ext));
if (!stricmp(ext, "pak") || !stricmp(ext, "pk3"))
if (!stricmp(ext, "pak") || !stricmp(ext, "pk3") || !stricmp(ext, "zip"))
FS_UnloadPackFiles(); //we reload them after
#ifdef PLUGINS
if ((!stricmp(ext, "dll") || !stricmp(ext, "so")) && !Q_strncmp(dep->name, PLUGINPREFIX, strlen(PLUGINPREFIX)))
Cmd_ExecuteString(va("plug_close %s\n", dep->name), RESTRICT_LOCAL); //try to purge plugins so there's no files left open
#endif
nfl = DPF_NATIVE;
if (*p->gamedir)
if (dep->dtype == DEP_CACHEFILE)
{
char temp[MAX_OSPATH];
destname = va("%s/%s", p->gamedir, dep->name);
if (PM_TryGenCachedName(destname, p, temp, sizeof(temp)))
{
nfl = DPF_CACHED;
destname = va("%s", temp);
}
nfl = DPF_CACHED;
destname = va("downloads/%s", dep->name);
}
else
destname = dep->name;
{
nfl = DPF_NATIVE;
if (!*p->gamedir) //basedir
destname = dep->name;
else
{
char temp[MAX_OSPATH];
destname = va("%s/%s", p->gamedir, dep->name);
if (PM_TryGenCachedName(destname, p, temp, sizeof(temp)))
{
nfl = DPF_CACHED;
destname = va("%s", temp);
}
}
}
if (p->flags & DPF_MARKED)
nfl |= DPF_ENABLED;
nfl |= (p->flags & ~(DPF_CACHED|DPF_NATIVE|DPF_CORRUPT));
@ -3319,18 +3518,21 @@ static char *PM_GetTempName(package_t *p)
//always favour the file so that we can rename safely without needing a copy.
for (dep = p->deps, fdep = NULL; dep; dep = dep->next)
{
if (dep->dtype != DEP_FILE)
continue;
if (fdep)
if (dep->dtype == DEP_FILE || dep->dtype == DEP_CACHEFILE)
{
fdep = NULL;
break;
if (fdep)
{
fdep = NULL;
break;
}
fdep = dep;
}
fdep = dep;
}
if (fdep)
{
if (*p->gamedir)
if (fdep->dtype == DEP_CACHEFILE)
destname = va("downloads/%s.tmp", fdep->name);
else if (*p->gamedir)
destname = va("%s/%s.tmp", p->gamedir, fdep->name);
else
destname = va("%s.tmp", fdep->name);
@ -3478,12 +3680,12 @@ static qboolean PM_SignatureOkay(package_t *p)
for (dep = p->deps; dep; dep = dep->next)
{
if (dep->dtype != DEP_FILE)
if (dep->dtype != DEP_FILE && dep->dtype != DEP_CACHEFILE)
continue;
//only allow .pak/.pk3/.zip without a signature, and only when they have a qhash specified (or the .fmf specified it without a qhash...).
COM_FileExtension(dep->name, ext, sizeof(ext));
if ((!stricmp(ext, "pak") || !stricmp(ext, "pk3") || !stricmp(ext, "zip")) && (p->qhash || (p->flags&DPF_MANIFEST)))
if ((!stricmp(ext, "pak") || !stricmp(ext, "pk3") || !stricmp(ext, "zip")) && (p->qhash || dep->dtype == DEP_CACHEFILE || (p->flags&DPF_MANIFEST)))
;
else
return false;
@ -3537,12 +3739,64 @@ int PM_IsApplying(qboolean listsonly)
}
#ifdef WEBCLIENT
static size_t PM_AddFilePackage(const char *packagename, struct gamepacks *gp, size_t numgp)
{
size_t found = 0;
struct packagedep_s *dep;
package_t *p = PM_FindPackage(packagename);
if (!p)
return 0;
if (found < numgp)
{
gp[found].path = NULL;
gp[found].url = p->mirror[0];
for (dep = p->deps; dep; dep = dep->next)
{
if (dep->dtype == DEP_CACHEFILE)
gp[found].path = Z_StrDupf("downloads/%s", dep->name);
}
if (gp[found].path && gp[found].url)
{
gp[found].subpath = p->packprefix;
found++;
}
}
for (dep = p->deps; dep; dep = dep->next)
{
if (dep->dtype == DEP_REQUIRE)
found += PM_AddFilePackage(dep->name, gp+found, numgp);
}
return found;
}
static void PM_DownloadsCompleted(int iarg, void *data)
{ //if something installed, then make sure everything is reconfigured properly.
if (pm_packagesinstalled)
if (pm_packagesinstalled || pm_onload.package || pm_onload.map)
{
pm_packagesinstalled = false;
FS_ChangeGame(fs_manifest, true, false);
if (pm_onload.package)
{
package_t *p = PM_FindPackage(pm_onload.package);
char *map = pm_onload.map;
struct gamepacks packs[64];
size_t usedpacks;
pm_onload.map = NULL;
usedpacks = PM_AddFilePackage(pm_onload.package, packs, countof(packs)-1);
packs[usedpacks].path = NULL;
packs[usedpacks].subpath = NULL;
packs[usedpacks].url = NULL;
Z_Free(pm_onload.package);
pm_onload.package = NULL;
COM_Gamedir(p->gamedir, packs);
Cbuf_InsertText(va("map %s\n", map), RESTRICT_LOCAL, false);
Z_Free(map);
}
else
FS_ChangeGame(fs_manifest, true, false);
}
}
@ -3696,6 +3950,39 @@ static void PM_StartADownload(void)
//clear the updating flag once there's no more activity needed
pkg_updating = downloading;
}
qboolean PM_LoadMapPackage(const char *package)
{
struct packagedep_s *dep;
package_t *p = PM_FindPackage(package);
if (!p)
return false;
if (p->flags & DPF_PRESENT)
return true; //okay, already installed.
if (p->extract == EXTRACT_ZIP)
return false;
//refuse to use weird packages.
for (dep = p->deps; dep; dep = dep->next)
{
if (dep->dtype == DEP_FILE)
return false;
}
//make sure its downloaded. fs code will be asked to explicitly use it, so no need to activate it.
p->trymirrors = ~0u;
return true;
}
void PM_LoadMap(const char *package, const char *map)
{
if (!PM_LoadMapPackage(package))
return;
pm_onload.package = Z_StrDup(package);
pm_onload.map = Z_StrDup(map);
pkg_updating = true;
PM_StartADownload();
}
#endif
//'just' starts doing all the things needed to remove/install selected packages
void PM_ApplyChanges(void)
@ -3726,11 +4013,11 @@ void PM_ApplyChanges(void)
for (dep = p->deps; dep; dep = dep->next)
{
if (dep->dtype == DEP_FILE)
if (dep->dtype == DEP_FILE || dep->dtype == DEP_CACHEFILE)
{
char ext[8];
COM_FileExtension(dep->name, ext, sizeof(ext));
if (!stricmp(ext, "pak") || !stricmp(ext, "pk3"))
if (!stricmp(ext, "pak") || !stricmp(ext, "pk3") || !stricmp(ext, "zip"))
reloadpacks = true;
#ifdef PLUGINS //when disabling/purging plugins, be sure to unload them first (unfortunately there might be some latency before this can actually happen).
@ -3748,7 +4035,13 @@ void PM_ApplyChanges(void)
Con_Printf("Purging package %s\n", p->name);
for (dep = p->deps; dep; dep = dep->next)
{
if (dep->dtype == DEP_FILE)
if (dep->dtype == DEP_CACHEFILE)
{
char *f = va("downloads/%s", dep->name);
if (!FS_Remove(f, p->fsroot))
p->flags |= DPF_CACHED;
}
else if (dep->dtype == DEP_FILE)
{
if (*p->gamedir)
{
@ -3856,7 +4149,7 @@ void PM_ApplyChanges(void)
if (p->mirror[i])
break;
for (dep = p->deps; dep; dep=dep->next)
if (dep->dtype == DEP_FILE)
if (dep->dtype == DEP_FILE||dep->dtype == DEP_CACHEFILE)
break;
if (!dep && i == countof(p->mirror))
{ //this appears to be a meta package with no download
@ -4047,25 +4340,6 @@ qboolean PM_CanInstall(const char *packagename)
return false;
}
static int QDECL sortpackages(const void *l, const void *r)
{
const package_t *a=*(package_t*const*)l, *b=*(package_t*const*)r;
const char *ac, *bc;
int order;
//sort by categories
ac = a->category?a->category:"";
bc = b->category?b->category:"";
order = strcmp(ac,bc);
if (order)
return order;
//otherwise sort by title.
ac = a->title?a->title:a->name;
bc = b->title?b->title:b->name;
order = strcmp(ac,bc);
return order;
}
void PM_Command_f(void)
{
package_t *p;
@ -4155,7 +4429,7 @@ void PM_Command_f(void)
continue;
sorted[count++] = p;
}
qsort(sorted, count, sizeof(*sorted), sortpackages);
qsort(sorted, count, sizeof(*sorted), PM_PackageSortOrdering);
for (i = 0; i < count; i++)
{
char quoted[8192];
@ -4673,6 +4947,7 @@ void PM_AddManifestPackages(ftemanifest_t *man)
continue;
}
PM_ResortPackages();
PM_ApplyChanges();
}
@ -4821,6 +5096,10 @@ static void MD_Draw (int x, int y, struct menucustom_s *c, struct emenu_s *m)
{
package_t *p;
char *n;
if (y + 8 < 0 || y >= vid.height) //small optimisation.
return;
if (c->dint != downloadablessequence)
return; //probably stale
p = c->dptr;
@ -5027,18 +5306,21 @@ static qboolean MD_Key (struct menucustom_s *c, struct emenu_s *m, int key, unsi
{
if (p == p2)
continue;
if (strcmp(p->gamedir, p2->gamedir))
continue; //different gamedirs. don't screw up.
for (dep = p->deps; dep; dep = dep->next)
{
if (dep->dtype != DEP_FILE)
continue;
for (dep2 = p2->deps; dep2; dep2 = dep2->next)
if (dep->dtype == DEP_FILE)
{
if (dep2->dtype != DEP_FILE)
continue;
if (!strcmp(dep->name, dep2->name))
for (dep2 = p2->deps; dep2; dep2 = dep2->next)
{
PM_UnmarkPackage(p2, DPF_MARKED);
break;
if (dep2->dtype != DEP_FILE)
continue;
if (!strcmp(dep->name, dep2->name))
{
PM_UnmarkPackage(p2, DPF_MARKED);
break;
}
}
}
}
@ -5242,6 +5524,8 @@ static int MD_AddItemsToDownloadMenu(emenu_t *m, int y, const char *pathprefix)
continue;
if ((p->flags & DPF_HIDDEN) && (p->arch || !(p->flags & DPF_ENABLED)))
continue;
if ((p->flags & DPF_HIDEUNLESSPRESENT) && !(p->flags & DPF_PRESENT))
continue;
slash = strchr(p->category+prefixlen, '/');
if (!slash)
{
@ -5317,6 +5601,8 @@ static int MD_AddItemsToDownloadMenu(emenu_t *m, int y, const char *pathprefix)
continue;
if ((p->flags & DPF_HIDDEN) && (p->arch || !(p->flags & DPF_ENABLED)))
continue;
if ((p->flags & DPF_HIDEUNLESSPRESENT) && !(p->flags & DPF_PRESENT))
continue;
slash = strchr(p->category+prefixlen, '/');
if (slash)

View File

@ -798,6 +798,7 @@ struct gamepacks
char *subpath; //within the package (for zips)
};
void COM_Gamedir (const char *dir, const struct gamepacks *packagespaths);
qboolean FS_PathURLCache(const char *url, char *path, size_t pathsize); //converts a url to something that can be shoved into a filesystem
qboolean FS_GamedirIsOkay(const char *path);
char *FS_GetGamedir(qboolean publicpathonly);
char *FS_GetManifestArgs(void);

View File

@ -3139,12 +3139,19 @@ void FS_AddHashedPackage(searchpath_t **oldpaths, const char *parentpath, const
continue;
if ((loadstuff & (1<<fmt)) && !Q_strcasecmp(ext, searchpathformats[fmt].extension))
{
//figure out the logical path names
if (!FS_GenCachedPakName(pakpath, qhash, pname, sizeof(pname)))
return; //file name was invalid, panic.
snprintf (lname, sizeof(lname), "%s%s", logicalpaths, pname+ptlen+1);
snprintf (lname2, sizeof(lname), "%s%s", logicalpaths, pakpath+ptlen+1);
if (!search)
{
FS_NativePath(pname, FS_ROOT, lname, sizeof(lname));
FS_NativePath(pakpath, FS_ROOT, lname2, sizeof(lname2));
}
else
{
snprintf (lname, sizeof(lname), "%s%s", logicalpaths, pname+ptlen+1);
snprintf (lname2, sizeof(lname), "%s%s", logicalpaths, pakpath+ptlen+1);
}
//see if we already added it
for (oldp = com_searchpaths; oldp; oldp = oldp->next)

View File

@ -76,6 +76,9 @@ void PM_LoadPackages(searchpath_t **oldpaths, const char *parent_pure, const cha
void *PM_GeneratePackageFromMeta(vfsfile_t *file, char *fname, size_t fnamesize, enum fs_relative *fsroot);
void PM_FileInstalled(const char *filename, enum fs_relative fsroot, void *metainfo, qboolean enable); //we finished installing a file via some other mechanism (drag+drop or from server. insert it into the updates menu.
void PM_EnumeratePlugins(void (*callback)(const char *name));
struct xcommandargcompletioncb_s;
void PM_EnumerateMaps(const char *partial, struct xcommandargcompletioncb_s *ctx);
void PM_LoadMap(const char *package, const char *map);
int PM_IsApplying(qboolean listsonly);
unsigned int PM_MarkUpdates (void); //mark new/updated packages as needing install.
void PM_ApplyChanges(void); //for -install/-doinstall args
@ -85,6 +88,14 @@ qboolean PM_FindUpdatedEngine(char *syspath, size_t syspathsize); //names the en
void PM_AddManifestPackages(ftemanifest_t *man);
void Menu_Download_Update(void);
typedef struct
{
char *description;
void (*Update) (const char *url, vfsfile_t *out);
#define plugupdatesourcefuncs_name "UpdateSource"
} plugupdatesourcefuncs_t;
qboolean PM_RegisterUpdateSource(void *module, plugupdatesourcefuncs_t *funcs);
int FS_EnumerateKnownGames(qboolean (*callback)(void *usr, ftemanifest_t *man), void *usr);
struct modlist_s

View File

@ -406,6 +406,10 @@ static qboolean QDECL PlugBI_ExportInterface(const char *name, void *interfacept
#ifdef HAVE_CLIENT
if (!strcmp(name, plugvrfuncs_name))
return R_RegisterVRDriver(currentplug, interfaceptr);
#endif
#ifdef PACKAGEMANAGER
if (!strcmp(name, plugupdatesourcefuncs_name))
return PM_RegisterUpdateSource(currentplug, interfaceptr);
#endif
return false;
}
@ -1117,7 +1121,6 @@ static void QDECL Plug_Net_Close(qhandle_t handle)
}
#if defined(HAVE_SERVER) && defined(HAVE_CLIENT)
qboolean FS_PathURLCache(const char *url, char *path, size_t pathsize);
static qboolean QDECL Plug_MapLog_Query(const char *packagename, const char *mapname, float *vals)
{
if (!strncmp(packagename, "http://", 7) || !strncmp(packagename, "https://", 8))
@ -1532,6 +1535,9 @@ void Plug_Close(plugin_t *plug)
S_UnregisterSoundInputModule(plug);
#endif
NET_RegisterCrypto(plug, NULL);
#ifdef PACKAGEMANAGER
PM_RegisterUpdateSource(currentplug, NULL);
#endif
FS_UnRegisterFileSystemModule(plug);
Mod_UnRegisterAllModelFormats(plug);
@ -1791,11 +1797,14 @@ static void *QDECL PlugBI_GetEngineInterface(const char *interfacename, size_t s
{
static plugcmdfuncs_t funcs =
{
Plug_Cmd_AddCommand,
COM_QuotedString,
COM_ParseType,
COM_ParseTokenOut,
Plug_Cmd_TokenizeString,
Plug_Cmd_Args,
Plug_Cmd_Argv,
Plug_Cmd_Argc,
Plug_Cmd_AddCommand,
Plug_Cmd_AddText,
};
if (structsize == sizeof(funcs))

View File

@ -1798,6 +1798,9 @@ typedef struct
void *mutex;
int refs;
qboolean terminate; //one end has closed, make the other report failures now that its no longer needed.
void *ctx;
void (*callback) (void *ctx, vfsfile_t *pipe);
} vfspipe_t;
static qboolean QDECL VFSPIPE_Close(vfsfile_t *f)
@ -1814,6 +1817,8 @@ static qboolean QDECL VFSPIPE_Close(vfsfile_t *f)
Sys_DestroyMutex(p->mutex);
free(p);
}
else if (p->callback)
p->callback(p->ctx, f);
return true;
}
static qofs_t QDECL VFSPIPE_GetLen(vfsfile_t *f)
@ -1892,7 +1897,7 @@ static int QDECL VFSPIPE_WriteBytes(vfsfile_t *f, const void *buffer, int len)
return len;
}
vfsfile_t *VFSPIPE_Open(int refs, qboolean seekable)
vfsfile_t *VFS_OpenPipeInternal(int refs, qboolean seekable, void (*callback)(void *ctx, vfsfile_t *file), void *ctx)
{
vfspipe_t *newf;
newf = malloc(sizeof(*newf));
@ -1909,6 +1914,9 @@ vfsfile_t *VFSPIPE_Open(int refs, qboolean seekable)
newf->funcs.ReadBytes = VFSPIPE_ReadBytes;
newf->funcs.WriteBytes = VFSPIPE_WriteBytes;
newf->ctx = ctx;
newf->callback = callback;
if (seekable)
{ //if this is set, then we allow changing the readpos at the expense of buffering the ENTIRE file. no more fifo.
newf->funcs.Seek = VFSPIPE_Seek;
@ -1924,3 +1932,12 @@ vfsfile_t *VFSPIPE_Open(int refs, qboolean seekable)
return &newf->funcs;
}
vfsfile_t *VFS_OpenPipeCallback(void (*callback)(void*ctx, vfsfile_t *file), void *ctx)
{
return VFS_OpenPipeInternal(2, false, callback, ctx);
}
vfsfile_t *VFSPIPE_Open(int refs, qboolean seekable)
{
return VFS_OpenPipeInternal(refs, seekable, NULL, NULL);
}

View File

@ -139,6 +139,7 @@ struct dl_download
};
vfsfile_t *VFSPIPE_Open(int refs, qboolean seekable); //refs should be 1 or 2, to say how many times it must be closed before its actually closed, so both ends can close separately
vfsfile_t *VFS_OpenPipeCallback(void (*callback)(void*ctx, vfsfile_t *file), void *ctx);
void HTTP_CL_Think(const char **fname, float *percent);
void HTTP_CL_Terminate(void); //kills all active downloads
unsigned int HTTP_CL_GetActiveDownloads(void);

View File

@ -19,6 +19,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "quakedef.h"
#include "pr_common.h"
#include "fs.h"
#ifndef CLIENTONLY
@ -491,6 +492,10 @@ static void SV_Map_c(int argn, const char *partial, struct xcommandargcompletion
COM_EnumerateFiles(va("maps/%s*.map.gz", partial), CompleteMapListExt, ctx);
COM_EnumerateFiles(va("maps/%s*.cm", partial), CompleteMapList, ctx);
COM_EnumerateFiles(va("maps/%s*.hmp", partial), CompleteMapList, ctx);
#ifdef PACKAGEMANAGER
PM_EnumerateMaps(partial, ctx);
#endif
}
}
@ -534,6 +539,9 @@ quake2:
quake:
+ is used in certain map names. * cannot be, but $ potentially could be.
fte:
'map package:mapname' should download the specified map package and load up its maps.
mvdsv:
basemap#modifier.ent files
@ -616,6 +624,20 @@ void SV_Map_f (void)
return;
}
#ifdef PACKAGEMANAGER
if (Cmd_Argc() == 2)
{
char *mangled = Cmd_Argv(1);
char *sep = strchr(mangled, ':');
if (sep)
{
*sep++ = 0;
PM_LoadMap(mangled, sep);
return;
}
}
#endif
Q_strncpyz (level, Cmd_Argv(1), sizeof(level));
startspot = ((Cmd_Argc() == 2)?NULL:Cmd_Argv(2));
}

View File

@ -216,15 +216,21 @@ typedef struct //subconsole handling
#define plugsubconsolefuncs_name "SubConsole"
} plugsubconsolefuncs_t;
enum com_tokentype_e;
typedef struct //console command/tokenizing/cbuf functions
{
F(qboolean, AddCommand, (const char *buffer)); //Registers a console command.
F(const char *,QuotedString, (const char *string, char *buf, int buflen, qboolean omitquotes)); //generates a string with c-style markup and relevant quote types.
F(char *, ParseToken, (const char *data, char *token, size_t tokenlen, enum com_tokentype_e *tokentype)); //standard quake-style token parsing.
F(char *, ParsePunctuation, (const char *data, const char *punctuation, char *token, size_t tokenlen, enum com_tokentype_e *tokentype)); //use explicit punctuation.
F(void, TokenizeString, (const char *msg)); //tokenize a string.
F(void, Args, (char *buffer, int bufsize)); //Gets the extra args
F(void, Argv, (int argnum, char *buffer, size_t bufsize)); //Gets a 0-based token
F(int, Argc, (void)); //gets the number of tokens available.
F(qboolean, AddCommand, (const char *cmdname)); //Registers a console command.
F(void, AddText, (const char *text, qboolean insert));
#define plugcmdfuncs_name "Cmd"
} plugcmdfuncs_t;

View File

@ -1,3 +1,5 @@
#include "quakedef.h"
#include "fs.h"
#include "../plugin.h"
static plugsubconsolefuncs_t *confuncs;
static plugfsfuncs_t *filefuncs;
@ -8,6 +10,7 @@ static plugclientfuncs_t *clientfuncs;
#define DATABASEURL "https://www.quaddicted.com/reviews/quaddicted_database.xml"
#define FILEIMAGEURL "https://www.quaddicted.com/reviews/screenshots/%s_injector.jpg"
#define FILEDOWNLOADURL "https://www.quaddicted.com/filebase/%s.zip"
#define FILEREVIEWURL "https://www.quaddicted.com/reviews/%s.html"
#define WINDOWTITLE "Quaddicted Map+Mod Archive"
#define WINDOWNAME "QI"
@ -30,6 +33,7 @@ static plugclientfuncs_t *clientfuncs;
static xmltree_t *thedatabase;
static qhandle_t dlcontext = -1;
static vfsfile_t *packagemanager;
static struct
{
@ -102,7 +106,7 @@ static qboolean QI_SetupWindow(const char *console, qboolean force)
confuncs->SetActive(console);
return true;
}
static void QI_DeHTML(const char *in, char *out, size_t outsize)
static void QI_DeHTML(const char *in, qboolean escapes, char *out, size_t outsize)
{
outsize--;
while(*in && outsize > 0)
@ -175,6 +179,12 @@ static void QI_DeHTML(const char *in, char *out, size_t outsize)
outsize--;
}
}
else if ((*in == '\"') && escapes && outsize >= 2)
{
*out++ = '\\';
*out++ = *in++;
outsize-=2;
}
else
{
*out++ = *in++;
@ -339,7 +349,7 @@ static void QI_RefreshMapList(qboolean forcedisplay)
year += 1900;
Q_snprintf(descbuf, sizeof(descbuf), "^aId:^a %s\n^aAuthor(s):^a %s\n^aDate:^a %04u-%02u-%02u\n^aRating:^a %s\n\n", id, author, year, month, day, ratingtext);
QI_DeHTML(desc, descbuf + strlen(descbuf), sizeof(descbuf) - strlen(descbuf));
QI_DeHTML(desc, false, descbuf + strlen(descbuf), sizeof(descbuf) - strlen(descbuf));
desc = descbuf;
Con_SubPrintf(console, "%s %s ^[^4%s: ^1%s\\tip\\%s\\tipimg\\"FILEIMAGEURL"\\id\\%s^]", type, ratingtext, id, XML_GetChildBody(file, "title", "<NO TITLE>"), desc, id, id);
@ -526,6 +536,187 @@ static void QI_RunMap(xmltree_t *qifile, const char *map)
cmdfuncs->AddText("\n", false);
}
void VARGS VFS_PRINTF(vfsfile_t *vf, const char *format, ...)
{
va_list argptr;
char string[1024];
va_start (argptr, format);
vsnprintf (string,sizeof(string)-1, format,argptr);
va_end (argptr);
VFS_PUTS(vf, string);
}
static void QI_WriteUpdateList(vfsfile_t *pminfo)
{
xmltree_t *file;
char descbuf[1024], *d, *nl;
if (thedatabase)
for (file = thedatabase->child; file; file = file->sibling)
{
const char *id = XML_GetParameter(file, "id", "unnamed");
const char *rating = XML_GetParameter(file, "rating", "");
int ratingnum = atoi(rating);
const char *author = XML_GetChildBody(file, "author", "unknown");
const char *desc = XML_GetChildBody(file, "description", "<NO DESCRIPTION>");
const char *type = XML_GetParameter(file, "type", "");
const char *date = XML_GetChildBody(file, "date", "1.1.1990");
xmltree_t *tech = XML_ChildOfTree(file, "techinfo", 0);
const char *cmdline = XML_GetChildBody(tech, "commandline", "");
const char *zipbasedir = XML_GetChildBody(tech, "zipbasedir", "");
int year, month, day;
int startmapnum, i;
char ratingtext[65];
char gamedir[65];
char prefix[MAX_QPATH];
xmltree_t *startmap;
if (strcmp(file->name, "file"))
continue; //erk?
if (atoi(XML_GetParameter(file, "hide", "")) || atoi(XML_GetParameter(file, "fte_hide", "")))
continue;
switch(atoi(type))
{
case 1:
type = "map"; //'single map file(s)'
break;
case 2:
type = "mod"; //'Partial conversion'
break;
case 4:
type = "spd"; //'speedmapping'
break;
case 5:
type = "otr"; //'misc files'
break;
default:
type = "???"; //no idea
break;
}
if (ratingnum > (sizeof(ratingtext)-5)/6)
ratingnum = (sizeof(ratingtext)-5)/6;
if (ratingnum)
{
Q_snprintf(ratingtext, sizeof(ratingtext), "^a");
for (i = 0; i < ratingnum; i++)
Q_snprintf(ratingtext + i+2, sizeof(ratingtext)-i*2+2, "*");
Q_snprintf(ratingtext + i+2, sizeof(ratingtext)-i*2+2, "^a");
}
else if (*rating)
Q_snprintf(ratingtext, sizeof(ratingtext), "%s", rating);
else
Q_snprintf(ratingtext, sizeof(ratingtext), "%s", "unrated");
day = atoi(date?date:"1");
date = date?strchr(date, '.'):NULL;
month = atoi(date?date+1:"1");
date = date?strchr(date+1, '.'):NULL;
year = atoi(date?date+1:"1990");
if (year < 90)
year += 2000;
else if (year < 1900)
year += 1900;
strcpy(gamedir, "id1");
while (cmdline)
{
cmdline = cmdfuncs->ParseToken(cmdline, descbuf, sizeof(descbuf), NULL);
if (!strcmp(descbuf, "-game"))
cmdline = cmdfuncs->ParseToken(cmdline, gamedir, sizeof(gamedir), NULL);
else if (!strcmp(descbuf, "-hipnotic") || !strcmp(descbuf, "-rogue") || !strcmp(descbuf, "-quoth"))
{
if (!*gamedir)
strcpy(gamedir, descbuf+1);
}
}
if (!*gamedir)
continue; //bad package
*descbuf = 0;
QI_DeHTML(desc, true, descbuf + strlen(descbuf), sizeof(descbuf) - strlen(descbuf));
desc = descbuf;
VFS_PRINTF(pminfo, "{\n"
"\tpackage \"qi_%s\"\n"
"\ttitle \"%s %s\"\n"
"\tcategory \"Quaddicted - %i\"\n"
"\tlicense \"Unknown\"\n"
"\tauthor \"Unknown\"\n"
"\tqhash \"\"\n"
,id, ratingtext, XML_GetChildBody(file, "title", "<NO TITLE>"), year);
VFS_PRINTF(pminfo, "\tgamedir \"%s\"\n", gamedir);
VFS_PRINTF(pminfo, "\tver %04u-%02u-%02u\n", year, month, day);
if (!strchr(author, '\"') && !strchr(author, '\n'))
VFS_PRINTF(pminfo, "\tauthor \"%s\"\n", author);
VFS_PRINTF(pminfo, "\twebsite \""FILEREVIEWURL"\"\n", id); //in lieu of an actual site, lets fill it with quaddicted's reviews.
VFS_PRINTF(pminfo, "\turl \""FILEDOWNLOADURL"\"\n", id);
//skip any dodgy leading slashes
while (*zipbasedir == '/' || *zipbasedir == '\\')
zipbasedir++;
if (!*zipbasedir)
strcpy(prefix, ".."); //err, there wasn't a directory... we still need to 'strip' it though.
else
{
//skip the zip's gamedir
while (*zipbasedir && *zipbasedir != '/' && *zipbasedir != '\\')
zipbasedir++;
//skip any trailing
while (*zipbasedir == '/' || *zipbasedir == '\\')
zipbasedir++;
for (i = 0; *zipbasedir; i++)
{
if (i >= sizeof(prefix)-1)
break;
if (*zipbasedir == '\\') //sigh
prefix[i] = '/';
else if (*zipbasedir == '\"' || *zipbasedir == '\n' || *zipbasedir == '\r')
break; //bad char...
else
prefix[i] = *zipbasedir;
zipbasedir++;
}
while (i > 0 && prefix[i-1] == '/')
i--;
prefix[i] = 0;
}
if (*prefix)
VFS_PRINTF(pminfo, "\tpackprefix \"%s\"\n", prefix);
for(d = descbuf;;)
{
nl = strchr(d, '\n');
if (nl)
*nl++ = 0;
VFS_PRINTF(pminfo, "\tdesc \"%s\"\n", d);
if (!nl)
break;
d = nl;
}
//VFS_PRINTF(pminfo, "\tpreview \""FILEIMAGEURL"\"\n", id);
for (startmapnum = 0; ; startmapnum++)
{
startmap = XML_ChildOfTree(tech, "startmap", startmapnum);
if (!startmap)
break;
VFS_PRINTF(pminfo, "\tmap \"%s\"\n", startmap->body);
}
if (!startmapnum) //if there's no start maps listed, use the package's id instead. these are not intended for anything else.
VFS_PRINTF(pminfo, "\tmap \"%s\"\n", id);
VFS_PRINTF(pminfo, "}\n");
}
}
static unsigned int QI_GetDate(xmltree_t *file)
{
unsigned int day, month, year;
@ -685,8 +876,8 @@ static void QDECL QI_Tick(double realtime, double gametime)
{
if (dlcontext != -1)
{
qofs_t flen;
if (filefuncs->GetLen(dlcontext, &flen))
qofs_t flen=0;
if (dlcontext < 0 || filefuncs->GetLen(dlcontext, &flen))
{
int ofs = 0;
char *file;
@ -729,6 +920,22 @@ static void QDECL QI_Tick(double realtime, double gametime)
// XML_ConPrintTree(thedatabase, "quadicted_xml", 0);
}
}
else if (packagemanager)
{
if (!thedatabase && dlcontext == -1)
{
dlcontext = -2;
//if (filefuncs->Open(DATABASEURL, &dlcontext, 1) >= 0)
return;
}
else
{
VFS_PRINTF(packagemanager, "version 3\n");
QI_WriteUpdateList(packagemanager);
}
VFS_CLOSE(packagemanager);
packagemanager = NULL;
}
}
static int QDECL QI_ConExecuteCommand(qboolean isinsecure)
@ -774,12 +981,26 @@ static qboolean QI_ExecuteCommand(qboolean isinsecure)
return false;
}
void QI_GenPackages(const char *url, vfsfile_t *pipe)
{
if (packagemanager)
VFS_CLOSE(packagemanager);
packagemanager = pipe;
}
static plugupdatesourcefuncs_t sourcefuncs =
{
"Quaddicted Maps",
QI_GenPackages
};
qboolean Plug_Init(void)
{
confuncs = plugfuncs->GetEngineInterface(plugsubconsolefuncs_name, sizeof(*confuncs));
filefuncs = plugfuncs->GetEngineInterface(plugfsfuncs_name, sizeof(*filefuncs));
clientfuncs = plugfuncs->GetEngineInterface(plugclientfuncs_name, sizeof(*clientfuncs));
plugfuncs->ExportInterface(plugupdatesourcefuncs_name, &sourcefuncs, sizeof(sourcefuncs));
if (confuncs && filefuncs && clientfuncs)
{
filters.minrating = filters.maxrating = -1;