diff --git a/source/src/audiomanager.cpp b/source/src/audiomanager.cpp index d0c70af6..cefb2800 100644 --- a/source/src/audiomanager.cpp +++ b/source/src/audiomanager.cpp @@ -923,6 +923,7 @@ void getmapsoundorigin(char *fname) const char *res = s; switch(findfilelocation) { + case FFL_ZIP: res = "zip"; break; case FFL_WORKDIR: res = fileexists(s, "r") ? "official" : ""; break; case FFL_HOME: res = "custom"; break; default: formatstring(s)("package dir #%d", findfilelocation); break; diff --git a/source/src/client.cpp b/source/src/client.cpp index d60c459a..6f6c00c6 100644 --- a/source/src/client.cpp +++ b/source/src/client.cpp @@ -1033,7 +1033,7 @@ int processdownload(package *pck) case PCK_MAP: case PCK_MAPMODEL: { - addzip("tmp", pck->name, NULL, true, pck->type); +// addzip("tmp", pck->name, NULL, true, pck->type); break; } @@ -1041,7 +1041,7 @@ int processdownload(package *pck) { char *fname = newstring(pck->name), *ls = strrchr(fname, '/'); if(ls) *ls = '\0'; - addzip("tmp", fname, NULL, true, pck->type); +// addzip("tmp", fname, NULL, true, pck->type); rereadtexturelists = 1; break; } diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index 97426c3e..6fdd518a 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -1468,7 +1468,7 @@ void receivefile(uchar *data, int len) break; } path(fname); - stream *demo = openrawfile(fname, "wb"); + stream *demo = openfile(fname, "wb"); if(!demo) { conoutf("failed writing to \"%s\"", fname); diff --git a/source/src/command.cpp b/source/src/command.cpp index c869ccaf..84e8753d 100644 --- a/source/src/command.cpp +++ b/source/src/command.cpp @@ -1616,7 +1616,7 @@ void deletecfg() loopi(sizeof(configs)/sizeof(configs[0])) { const char *file = findfile(path(configs[i], true), "r"); - if(!file) continue; + if(!file || findfilelocation == FFL_ZIP) continue; delfile(file); } } diff --git a/source/src/main.cpp b/source/src/main.cpp index b1dea776..696e0432 100644 --- a/source/src/main.cpp +++ b/source/src/main.cpp @@ -146,6 +146,8 @@ void writeinitcfg() f->printf("audio %d\n", audio > 0 ? 1 : 0); f->printf("soundchannels %d\n", soundchannels); if(lang && *lang) f->printf("lang %s\n", lang); + extern void writezipmodconfig(stream *f); + writezipmodconfig(f); delete f; } @@ -941,6 +943,7 @@ void autostartscripts(const char *prefix) void createconfigtemplates(const char *templatezip) // create customisable config files in homedir - only if missing { +#if 0 if(addzip(templatezip)) { vector files; @@ -971,6 +974,7 @@ void createconfigtemplates(const char *templatezip) // create customisable conf removezip(templatezip); } findfile(AUTOSTARTPATH "dummy", "w"); // create empty autostart directory, if it doesn't exist yet +#endif } extern void connectserv(char *, int *, char *); diff --git a/source/src/stream.cpp b/source/src/stream.cpp index c87b66f6..9ffd3819 100644 --- a/source/src/stream.cpp +++ b/source/src/stream.cpp @@ -241,6 +241,11 @@ const char *findfile(const char *filename, const char *mode) } return s; } + findfilelocation = FFL_ZIP; +#ifndef STANDALONE + formatstring(s)("zip://%s", filename); + if(findzipfile(filename)) return s; +#endif loopv(packagedirs) { findfilelocation++; @@ -290,25 +295,23 @@ bool listdir(const char *dir, const char *ext, vector &files) else return false; } -int listfiles(const char *dir, const char *ext, vector &files) +void listfiles(const char *dir, const char *ext, vector &files) { - int dirs = 0; - if(listdir(dir, ext, files)) dirs++; + listdir(dir, ext, files); string s; if(homedir[0]) { formatstring(s)("%s%s", homedir, dir); - if(listdir(s, ext, files)) dirs++; + listdir(s, ext, files); } loopv(packagedirs) { formatstring(s)("%s%s", packagedirs[i], dir); - if(listdir(s, ext, files)) dirs++; + listdir(s, ext, files); } #ifndef STANDALONE - dirs += listzipfiles(dir, ext, files); + listzipfiles(dir, ext, files); #endif - return dirs; } #ifndef STANDALONE @@ -332,7 +335,7 @@ void listfilesrecursive(const char *dir, vector &files, int level) bool delfile(const char *path) { - return !remove(path); + return strncmp(path, "zip://", 6) ? !remove(path) : false; } void backup(char *name, char *backupname) @@ -885,13 +888,12 @@ stream *openmemfile(const uchar *buf, int size, int *refcnt) stream *openrawfile(const char *filename, const char *mode) { - const char *found = findfile(filename, mode); #ifndef STANDALONE - if(mode && (mode[0]=='w' || mode[0]=='a')) conoutf("writing to file: %s", found); + if(mode && (mode[0]=='w' || mode[0]=='a')) conoutf("writing to file: %s", filename); #endif - if(!found) return NULL; + if(!strncmp(filename, "zip://", 6)) return NULL; filestream *file = new filestream; - if(!file->open(found, mode)) + if(!file->open(filename, mode)) { #ifndef STANDALONE // conoutf("file failure! %s",filename); @@ -903,11 +905,11 @@ stream *openrawfile(const char *filename, const char *mode) stream *openfile(const char *filename, const char *mode) { + const char *found = findfile(filename, mode); #ifndef STANDALONE - stream *s = openzipfile(filename, mode); - if(s) return s; + if(!strncmp(found, "zip://", 6)) return openzipfile(found + 6, mode); #endif - return openrawfile(filename, mode); + return openrawfile(found, mode); } int getfilesize(const char *filename) diff --git a/source/src/texture.cpp b/source/src/texture.cpp index 50dd39a1..dc80a363 100644 --- a/source/src/texture.cpp +++ b/source/src/texture.cpp @@ -366,18 +366,22 @@ GLuint loadsurface(const char *texname, int &xs, int &ys, int &bpp, int clamp = } SDL_Surface *s = NULL; - stream *z = openzipfile(file, "rb"); - if(z) + const char *ffile = findfile(file, "rb"); + if(findfilelocation == FFL_ZIP) { - SDL_RWops *rw = z->rwops(); - if(rw) + stream *z = openzipfile(file, "rb"); + if(z) { - s = IMG_Load_RW(rw, 0); - SDL_FreeRW(rw); + SDL_RWops *rw = z->rwops(); + if(rw) + { + s = IMG_Load_RW(rw, 0); + SDL_FreeRW(rw); + } + delete z; } - delete z; } - if(!s) s = IMG_Load(findfile(file, "rb")); + else if(!s) s = IMG_Load(ffile); if(!s) { if(trydl) @@ -1080,6 +1084,7 @@ void gettextureorigin(char *fname) const char *res = s; switch(findfilelocation) { + case FFL_ZIP: res = "zip"; break; case FFL_WORKDIR: res = fileexists(s, "r") ? "official" : ""; break; case FFL_HOME: res = "custom"; break; default: formatstring(s)("package dir #%d", findfilelocation); break; diff --git a/source/src/tools.h b/source/src/tools.h index e5eaa73c..489eec3d 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -944,12 +944,12 @@ extern size_t fixpackagedir(char *dir); extern void sethomedir(const char *dir); extern void addpackagedir(const char *dir); extern int findfilelocation; -enum { FFL_WORKDIR = -1, FFL_HOME = 0 }; +enum { FFL_WORKDIR = -2, FFL_HOME = -1, FFL_ZIP = 0 }; extern const char *findfile(const char *filename, const char *mode); extern int getfilesize(const char *filename); extern stream *openvecfile(vector *s = NULL); extern stream *openmemfile(const uchar *buf, int size, int *refcnt); -extern stream *openrawfile(const char *filename, const char *mode); +extern bool findzipfile(const char *name); extern stream *openzipfile(const char *filename, const char *mode); extern stream *openfile(const char *filename, const char *mode); extern stream *opentempfile(const char *filename, const char *mode); @@ -957,13 +957,11 @@ extern stream *opengzfile(const char *filename, const char *mode, stream *file = extern char *loadfile(const char *fn, int *size, const char *mode = NULL); extern void filerotate(const char *basename, const char *ext, int keepold, const char *oldformat = NULL); extern bool listdir(const char *dir, const char *ext, vector &files); -extern int listfiles(const char *dir, const char *ext, vector &files); +extern void listfiles(const char *dir, const char *ext, vector &files); extern void listfilesrecursive(const char *dir, vector &files, int level = 0); -extern int listzipfiles(const char *dir, const char *ext, vector &files); +extern void listzipfiles(const char *dir, const char *ext, vector &files); extern bool delfile(const char *path); extern void backup(char *name, char *backupname); -extern bool addzip(const char *name, const char *mount = NULL, const char *strip = NULL, bool extract = false, int type = -1); -extern bool removezip(const char *name); extern struct mapstats *loadmapstats(const char *filename, bool getlayout); extern bool cmpb(void *b, int n, enet_uint32 c); extern bool cmpf(char *fn, enet_uint32 c); diff --git a/source/src/zip.cpp b/source/src/zip.cpp index fbec80e6..5a288a38 100644 --- a/source/src/zip.cpp +++ b/source/src/zip.cpp @@ -44,35 +44,24 @@ struct ziparchive; struct zipfile // file in a zip archive { - char *name; + char *name, *fullname; // name from zip, full path name uint header, offset, size, compressedsize; + ziparchive *location; - zipfile() : name(NULL), header(0), offset(~0U), size(0), compressedsize(0) - { - } - ~zipfile() - { - DELETEA(name); - } + zipfile() : name(NULL), fullname(NULL), header(0), offset(~0U), size(0), compressedsize(0), location(NULL) { } + ~zipfile() { DELSTRING(name); DELSTRING(fullname); } }; struct ziparchive // zip archive file { char *name; - hashtable files; stream *data; + vector files; int openfiles; - int type; zipstream *owner; - ziparchive() : name(NULL), files(512), data(NULL), openfiles(0), type(-1), owner(NULL) - { - } - ~ziparchive() - { - DELETEA(name); - DELETEP(data); - } + ziparchive() : name(NULL), data(NULL), openfiles(0), owner(NULL) { } + ~ziparchive() { DELSTRING(name); DELETEP(data); } }; static bool findzipdirectory(stream *f, zipdirectoryheader &hdr) @@ -195,8 +184,7 @@ static bool readlocalfileheader(stream *f, ziplocalfileheader &h, uint offset) return true; } -static vector archives; - +#if 0 ziparchive *findzip(const char *name) { loopv(archives) if(!strcmp(name, archives[i]->name)) return archives[i]; @@ -355,6 +343,9 @@ bool removezip(const char *name) delete exists; return true; } +#endif + +// stream interface struct zipstream : stream { @@ -554,68 +545,178 @@ struct zipstream : stream } }; -bool extractzipfile(ziparchive *a, zipfile *f, const char *name) +// manage zips as part of the AC virtual filesystem + +static vector archives; // list of mounted zip files, highest priority first +static hashtable zipfiles; // table of all files in all mounted zips + +static void mountzip(ziparchive &arch, const char *mountdir, const char *stripdir, bool allowconfig) { - zipstream *s = new zipstream; - FILE *target = NULL; - defformatstring(fname)("%s", findfile(name, "wb")); - bool error = false; - if(s->open(a, f) && (target = fopen(fname, "wb"))) + string mdir = "", fname; + if(mountdir) { - size_t len; - uchar copybuf[1024]; - while((len = s->read(copybuf, 1024))) fwrite(copybuf, 1, len, target); + copystring(mdir, mountdir); + if(fixpackagedir(mdir) <= 1) mdir[0] = '\0'; } - else error = true; - if(error) conoutf("failed to extract zip file %s from archive %s", fname, a->name); - if(target) fclose(target); - delete s; - return !error; + int striplen = stripdir ? (int)strlen(stripdir) : 0; + + loopv(arch.files) + { // build full path names - and check them + zipfile *zf = &arch.files[i]; + formatstring(fname)("%s%s", mdir, striplen && !strncmp(zf->name, stripdir, striplen) ? zf->name + striplen : zf->name); + path(fname); + unixpath(fname); + zf->fullname = newstring(fname); + zf->location = &arch; + filtertext(fname, fname, FTXT__MEDIAFILEPATH); + if(strcmp(fname, zf->fullname) || (strncmp("packages/", fname, 9) && (!allowconfig || strncmp("config/", fname, 7)))) + { // path is not allowed (illegal characters or illegal path) +#if defined(_DEBUG) && !defined(STANDALONE) + clientlogf("mountzip: ignored file %s from zip %s", fname, arch.name); +#endif + arch.files.remove(i--).~zipfile(); + } + else path(zf->fullname); + } +} + +static void rebuildzipfilehashtable() +{ + zipfiles.clear(false); + loopv(archives) + { + ziparchive *arch = archives[i]; + loopvj(arch->files) + { + zipfile *zf = &arch->files[j]; + zipfiles.access(zf->fullname, zf); + } + } +} + +// manage zipped mod packages in subdirectory "mods/" including description and preview images +// zip packages are restricted to contain files below "packages/" only - unless the zip filename starts with "###", which additionally allows files below "config/" + +ziparchive *findzip(const char *name) +{ + loopv(archives) if(!strcmp(name, archives[i]->name)) return archives[i]; + return NULL; +} + +#ifndef STANDALONE + +const char *fixzipname(char *name) // check, if valid path + filename, based on mods/, return NULL if invalid +{ + string tmp; + unixpath(name); // changes "name"! + filtertext(tmp, name, FTXT__MEDIAFILEPATH); + const char *bp = behindpath(tmp), *pd = parentdir(tmp); + static defformatstring(pname)("%s%s%s%s", pd, bp != tmp ? "/" : "", !strncmp("###", behindpath(name), 3) ? "###" : "", bp); + if(strlen(name) > 40 || strchr(name, '.') || strcmp(pname, name) || *name == '/') return NULL; // illegal filename + formatstring(pname)("mods/%s.zip", name); + return path(pname); +} + +VAR(zipcachemaxsize, 0, 512, 1024); // load zip files smaller than 512K straight into RAM + +void addzipmod(char *name) +{ + const char *pname = fixzipname(name); + if(!pname) + { + conoutf("addzipmod: illegal filename %s", name); + return; + } + ziparchive *exists = findzip(name); + if(exists) + { // already added zip + archives.removeobj(exists); + archives.add(exists); // sort to the end of the list + rebuildzipfilehashtable(); + return; + } + ziparchive *arch = new ziparchive; + arch->name = newstring(name); + if(!(arch->data = openfile(pname, "rb"))) + { + conoutf("could not open file %s", pname); + delete arch; + return; + } + int zipsize = arch->data->size(); + if(zipsize > 0 && zipsize < zipcachemaxsize * 1024) + { + uchar *buf = new uchar[zipsize]; + if(arch->data->read(buf, zipsize) != zipsize) + { + conoutf("could not cache file %s", pname); + delete[] buf; + delete arch; + return; + } + delete arch->data; + arch->data = openmemfile(buf, zipsize, NULL); + } + zipdirectoryheader h; + if(!findzipdirectory(arch->data, h) || !readzipdirectory(arch->data, h.entries, h.offset, h.size, arch->files)) + { + conoutf("could not read directory in zip %s", pname); + delete arch; + return; + } + mountzip(*arch, NULL, NULL, !strncmp(behindpath(name), "###", 3)); + archives.add(arch); + rebuildzipfilehashtable(); + clientlogf("added zipmod %s, %d bytes, %d files", pname, zipsize, arch->files.length()); +} +COMMAND(addzipmod, "s"); + +void writezipmodconfig(stream *f) +{ + f->printf("zipcachemaxsize %d\n", zipcachemaxsize); + loopv(archives) f->printf("addzipmod %s\n", archives[i]->name); +} + +#endif + +// AC virtual filesystem + +bool findzipfile(const char *name) +{ + return zipfiles.access(name) != NULL; } stream *openzipfile(const char *name, const char *mode) { for(; *mode; mode++) if(*mode=='w' || *mode=='a') return NULL; - loopvrev(archives) +// if(!strncmp(name, "zip://", 6)) name += 6; + zipfile **zf = zipfiles.access(name); + if(zf && *zf) { - ziparchive *arch = archives[i]; - zipfile *f = arch->files.access(name); - if(!f) continue; zipstream *s = new zipstream; - if(s->open(arch, f)) return s; + if(s->open((*zf)->location, *zf)) return s; delete s; } return NULL; } -int listzipfiles(const char *dir, const char *ext, vector &files) +void listzipfiles(const char *dir, const char *ext, vector &files) // (does not list memfiles) { - int extsize = ext ? (int)strlen(ext)+1 : 0, dirsize = (int)strlen(dir), dirs = 0; - loopvrev(archives) + int extsize = ext ? (int)strlen(ext)+1 : 0, dirsize = (int)strlen(dir); + enumerate(zipfiles, zipfile *, zf, { - ziparchive *arch = archives[i]; - int oldsize = files.length(); - enumerate(arch->files, zipfile, f, + if(strncmp(zf->fullname, dir, dirsize)) continue; + const char *name = zf->fullname + dirsize; + if(name[0] == PATHDIV) name++; + if(strchr(name, PATHDIV)) continue; + if(!ext) files.add(newstring(name)); + else { - if(strncmp(f.name, dir, dirsize)) continue; - const char *name = f.name + dirsize; - if(name[0] == PATHDIV) name++; - if(strchr(name, PATHDIV)) continue; - if(!ext) files.add(newstring(name)); - else - { - int namelength = (int)strlen(name) - extsize; - if(namelength > 0 && name[namelength] == '.' && strncmp(name+namelength+1, ext, extsize-1)==0) - files.add(newstring(name, namelength)); - } - }); - if(files.length() > oldsize) dirs++; - } - return dirs; + int namelength = (int)strlen(name) - extsize; + if(namelength > 0 && name[namelength] == '.' && strncmp(name+namelength+1, ext, extsize-1)==0) + files.add(newstring(name, namelength)); + } + }); } -#ifndef STANDALONE -COMMANDF(addzip, "sss", (const char *name, const char *mount, const char *strip) { addzip(name, mount[0] ? mount : NULL, strip[0] ? strip : NULL); }); -COMMANDF(removezip, "s", (const char *name) { removezip(name); }); -#endif