diff -Naur src/engine/command.cpp src_mod/engine/command.cpp --- src/engine/command.cpp 2007-04-10 02:17:52.000000000 -0400 +++ src_mod/engine/command.cpp 2007-07-15 20:44:12.000000000 -0400 @@ -522,7 +522,7 @@ void writecfg() { - FILE *f = fopen("config.cfg", "w"); + FILE *f = openfile("config.cfg", "w"); if(!f) return; fprintf(f, "// automatically written on exit, DO NOT MODIFY\n// delete this file to have defaults.cfg overwrite these settings\n// modify settings in game, or put settings in autoexec.cfg to override anything\n\n"); cc->writeclientinfo(f); diff -Naur src/engine/cubeloader.cpp src_mod/engine/cubeloader.cpp --- src/engine/cubeloader.cpp 2007-04-05 02:39:59.000000000 -0400 +++ src_mod/engine/cubeloader.cpp 2007-07-15 21:06:45.000000000 -0400 @@ -236,7 +236,7 @@ string pakname, cgzname; s_sprintf(pakname)("cube/%s", mname); s_sprintf(cgzname)("packages/%s.cgz", pakname); - gzFile f = gzopen(path(cgzname), "rb9"); + gzFile f = gzopen(pathforfile(path(cgzname), "r"), "rb9"); if(!f) { conoutf("could not read cube map %s", cgzname); return; } emptymap(12, true); freeocta(worldroot); diff -Naur src/engine/engine.h src_mod/engine/engine.h --- src/engine/engine.h 2007-04-13 02:46:40.000000000 -0400 +++ src_mod/engine/engine.h 2007-07-17 00:01:25.000000000 -0400 @@ -8,6 +8,7 @@ #include "spheretree.h" #include "texture.h" #include "model.h" +#include "vfs.h" // GL_ARB_multitexture extern PFNGLACTIVETEXTUREARBPROC glActiveTexture_; diff -Naur src/engine/main.cpp src_mod/engine/main.cpp --- src/engine/main.cpp 2007-04-08 01:26:36.000000000 -0400 +++ src_mod/engine/main.cpp 2007-07-17 01:57:01.000000000 -0400 @@ -288,6 +288,7 @@ { if(argv[i][0]=='-') switch(argv[i][1]) { + case 'i': addmod(&argv[i][2]); break; case 'd': dedicated = true; break; case 'w': scr_w = atoi(&argv[i][2]); scr_h = scr_w*3/4; break; case 'h': scr_h = atoi(&argv[i][2]); break; diff -Naur src/engine/md2.h src_mod/engine/md2.h --- src/engine/md2.h 2007-03-28 20:45:00.000000000 -0400 +++ src_mod/engine/md2.h 2007-07-15 20:44:13.000000000 -0400 @@ -125,7 +125,7 @@ bool load(char *filename) { if(loaded) return true; - FILE *file = fopen(filename, "rb"); + FILE *file = openfile(filename, "rb"); if(!file) return false; show_out_of_renderloop_progress(0, filename); diff -Naur src/engine/md3.h src_mod/engine/md3.h --- src/engine/md3.h 2007-03-28 20:45:00.000000000 -0400 +++ src_mod/engine/md3.h 2007-07-15 20:44:13.000000000 -0400 @@ -52,7 +52,7 @@ bool load(char *path) { if(loaded) return true; - FILE *f = fopen(path, "rb"); + FILE *f = openfile(path, "rb"); if(!f) return false; md3header header; fread(&header, sizeof(md3header), 1, f); diff -Naur src/engine/octaedit.cpp src_mod/engine/octaedit.cpp --- src/engine/octaedit.cpp 2007-04-11 22:33:27.000000000 -0400 +++ src_mod/engine/octaedit.cpp 2007-07-15 20:44:12.000000000 -0400 @@ -918,7 +918,7 @@ void savebrush(const char *name) { - FILE *f = fopen("mybrushes.cfg", "a"); + FILE *f = openfile("mybrushes.cfg", "a"); if(!f) return; fprintf(f, "newbrush \"%s\" %d %d [\n", name, brushx, brushy); int skipped = 0; diff -Naur src/engine/renderextras.cpp src_mod/engine/renderextras.cpp --- src/engine/renderextras.cpp 2006-12-02 08:23:18.000000000 -0500 +++ src_mod/engine/renderextras.cpp 2007-07-15 20:56:38.000000000 -0400 @@ -16,9 +16,9 @@ void flipnormalmapy(char *destfile, char *normalfile) // RGB (jpg/png) -> BGR (tga) { - SDL_Surface *ns = IMG_Load(normalfile); + SDL_Surface *ns = IMG_Load(pathforfile(normalfile, "r")); if(!ns) return; - FILE *f = fopen(destfile, "wb"); + FILE *f = openfile(destfile, "wb"); if(f) { writetgaheader(f, ns, 24); @@ -36,12 +36,12 @@ void mergenormalmaps(char *heightfile, char *normalfile) // BGR (tga) -> BGR (tga) (SDL loads TGA as BGR!) { - SDL_Surface *hs = IMG_Load(heightfile); - SDL_Surface *ns = IMG_Load(normalfile); + SDL_Surface *hs = IMG_Load(pathforfile(heightfile, "r")); + SDL_Surface *ns = IMG_Load(pathforfile(normalfile, "r")); if(hs && ns) { uchar def_n[] = { 255, 128, 128 }; - FILE *f = fopen(normalfile, "wb"); + FILE *f = openfile(normalfile, "wb"); if(f) { writetgaheader(f, ns, 24); diff -Naur src/engine/serverbrowser.cpp src_mod/engine/serverbrowser.cpp --- src/engine/serverbrowser.cpp 2007-03-28 20:45:01.000000000 -0400 +++ src_mod/engine/serverbrowser.cpp 2007-07-15 20:44:13.000000000 -0400 @@ -378,7 +378,7 @@ void writeservercfg() { - FILE *f = fopen("servers.cfg", "w"); + FILE *f = openfile("servers.cfg", "w"); if(!f) return; fprintf(f, "// servers connected to are added here automatically\n\n"); loopvrev(servers) fprintf(f, "addserver %s\n", servers[i].name); diff -Naur src/engine/texture.cpp src_mod/engine/texture.cpp --- src/engine/texture.cpp 2007-04-11 22:16:45.000000000 -0400 +++ src_mod/engine/texture.cpp 2007-07-15 20:55:14.000000000 -0400 @@ -187,7 +187,7 @@ show_out_of_renderloop_progress(0, file); - SDL_Surface *s = IMG_Load(file); + SDL_Surface *s = IMG_Load(pathforfile(file, "r")); if(!s) { if(msg) conoutf("could not load texture %s", tname); return NULL; } int bpp = s->format->BitsPerPixel; if(!texformat(bpp)) { SDL_FreeSurface(s); conoutf("texture must be 8, 24, or 32 bpp: %s", tname); return NULL; } @@ -208,7 +208,7 @@ void loadalphamask(Texture *t) { if(t->alphamask || t->bpp!=32) return; - SDL_Surface *s = IMG_Load(t->name); + SDL_Surface *s = IMG_Load(pathforfile(t->name, "r")); if(!s || !s->format->Amask) { if(s) SDL_FreeSurface(s); return; } uint alpha = s->format->Amask; t->alphamask = new uchar[s->h * ((s->w+7)/8)]; @@ -779,9 +779,9 @@ void flipnormalmapy(char *destfile, char *normalfile) // RGB (jpg/png) -> BGR (tga) { - SDL_Surface *ns = IMG_Load(normalfile); + SDL_Surface *ns = IMG_Load(pathforfile(normalfile,"r")); if(!ns) return; - FILE *f = fopen(destfile, "wb"); + FILE *f = openfile(destfile, "wb"); if(f) { writetgaheader(f, ns, 24); @@ -799,12 +799,12 @@ void mergenormalmaps(char *heightfile, char *normalfile) // BGR (tga) -> BGR (tga) (SDL loads TGA as BGR!) { - SDL_Surface *hs = IMG_Load(heightfile); - SDL_Surface *ns = IMG_Load(normalfile); + SDL_Surface *hs = IMG_Load(pathforfile(heightfile, "r")); + SDL_Surface *ns = IMG_Load(pathforfile(normalfile, "r")); if(hs && ns) { uchar def_n[] = { 255, 128, 128 }; - FILE *f = fopen(normalfile, "wb"); + FILE *f = openfile(normalfile, "wb"); if(f) { writetgaheader(f, ns, 24); diff -Naur src/engine/vfs.cpp src_mod/engine/vfs.cpp --- src/engine/vfs.cpp 1969-12-31 19:00:00.000000000 -0500 +++ src_mod/engine/vfs.cpp 2007-07-17 02:22:16.000000000 -0400 @@ -0,0 +1,54 @@ +// vfs commands +#include "pch.h" +#include "engine.h" + +static int findmod(char* mod) +{ + for(int i = 0; i < mod_directories.length(); i++) + { + if(strcmp(mod, mod_directories[i].name) == 0) + return i; + } + return -1; +} + +void addmod(char* mod) +{ + if(findmod(mod) > -1) + return; + //this is pretty interesting, we actually can have arbitrary nesting, since we use pathforfile() to locate the mod. + char* mod_location = pathforfile(mod, "w"); + if(!mod_location) + { + conoutf("failed to add mod %s", mod); + return; + } + struct mod m; + m.name = newstring(mod); + int l = strlen(mod_location); + m.path = newstring(mod_location, l + 1); + m.path[l] = PATHDIV; + mod_directories.add(m); + + //run [modname].cfg + char cfg[strlen(mod) + 5]; + sprintf(cfg, "%s.cfg", mod); + execfile(cfg); +} + +COMMAND(addmod, "s"); + +void removemod(char* mod) +{ + int i = findmod(mod); + if(i == -1) + { + conoutf("%s is not currently loaded", mod); + return; + } + delete mod_directories[i].name; + delete mod_directories[i].path; + mod_directories.remove(i); +} + +COMMAND(removemod, "s"); diff -Naur src/engine/vfs.h src_mod/engine/vfs.h --- src/engine/vfs.h 1969-12-31 19:00:00.000000000 -0500 +++ src_mod/engine/vfs.h 2007-07-17 01:44:47.000000000 -0400 @@ -0,0 +1,4 @@ +//vfs commands + +extern void addmod(char* mod); +extern void removemod(char* mod); diff -Naur src/engine/worldio.cpp src_mod/engine/worldio.cpp --- src/engine/worldio.cpp 2007-04-05 02:40:00.000000000 -0400 +++ src_mod/engine/worldio.cpp 2007-07-15 21:04:02.000000000 -0400 @@ -236,7 +236,7 @@ if(!*mname) mname = cl->getclientmap(); setnames(mname); if(savebak) backup(cgzname, bakname); - gzFile f = gzopen(cgzname, "wb9"); + gzFile f = gzopen(pathforfile(cgzname, "w"), "wb9"); if(!f) { conoutf("could not write map to %s", cgzname); return; } hdr.version = MAPVERSION; hdr.numents = 0; @@ -310,7 +310,7 @@ { int loadingstart = SDL_GetTicks(); setnames(mname, cname); - gzFile f = gzopen(cgzname, "rb9"); + gzFile f = gzopen(pathforfile(cgzname, "r"), "rb9"); if(!f) { conoutf("could not read map %s", cgzname); return; } header newhdr; gzread(f, &newhdr, sizeof(header)); @@ -512,7 +512,7 @@ hasVBO = false; allchanged(); s_sprintfd(fname)("%s.obj", name); - FILE *f = fopen(fname, "w"); + FILE *f = openfile(fname, "w"); if(!f) return; fprintf(f, "# obj file of sauerbraten level\n"); extern vector valist; diff -Naur src/fpsgame/client.h src_mod/fpsgame/client.h --- src/fpsgame/client.h 2007-04-12 23:22:27.000000000 -0400 +++ src_mod/fpsgame/client.h 2007-07-15 20:44:38.000000000 -0400 @@ -831,7 +831,7 @@ s_strcpy(oldname, cl.getclientmap()); s_sprintfd(mname)("getmap_%d", cl.lastmillis); s_sprintfd(fname)("packages/base/%s.ogz", mname); - FILE *map = fopen(fname, "wb"); + FILE *map = openfile(fname, "wb"); if(!map) return; conoutf("received map"); fwrite(data, 1, len, map); @@ -854,7 +854,7 @@ s_sprintfd(mname)("sendmap_%d", cl.lastmillis); save_world(mname, true); s_sprintfd(fname)("packages/base/%s.ogz", mname); - FILE *map = fopen(fname, "rb"); + FILE *map = openfile(fname, "rb"); if(map) { fseek(map, 0, SEEK_END); diff -Naur src/Makefile src_mod/Makefile --- src/Makefile 2007-07-15 19:41:38.000000000 -0400 +++ src_mod/Makefile 2007-07-17 02:27:52.000000000 -0400 @@ -35,6 +35,7 @@ engine/spheretree.o \ engine/sound.o \ engine/texture.o \ + engine/vfs.o \ engine/water.o \ engine/world.o \ engine/worldio.o \ diff -Naur src/shared/pch.h src_mod/shared/pch.h --- src/shared/pch.h 2006-06-03 05:05:48.000000000 -0400 +++ src_mod/shared/pch.h 2007-07-15 22:03:38.000000000 -0400 @@ -25,6 +25,10 @@ #ifdef WIN32 #define WIN32_LEAN_AND_MEAN #include "windows.h" +#else +#include +#include +#include #endif #include diff -Naur src/shared/tools.cpp src_mod/shared/tools.cpp --- src/shared/tools.cpp 2007-03-28 20:41:01.000000000 -0400 +++ src_mod/shared/tools.cpp 2007-07-17 02:11:50.000000000 -0400 @@ -2,9 +2,107 @@ #include "pch.h" #include "tools.h" +#include "vfs.h" ///////////////////////// misc tools /////////////////////// +//get platform specific base directory for per user configuration +char* get_config_dir() +{ + static char* config_dir = NULL; + const static char dirname[] = ".sauerbraten"; + if(config_dir == NULL) + { + char* env_provided_str; +#ifdef WIN32 + //this may not be optimal + env_provided_str = getenv("USERPROFILE"); +#else + //not sure that this is entirely appropriate for mac, but it isn't wrong either... + env_provided_str = getenv("HOME"); +#endif + size_t env_len = strlen(env_provided_str); + size_t dir_len = strlen(dirname); + char* ret = new char[env_len + dir_len + 3]; + strncpy(ret, env_provided_str, env_len); + ret[env_len] = PATHDIV; + ret[env_len + 1] = '\0'; + strncat(ret, dirname, dir_len); + ret[env_len + dir_len + 1] = PATHDIV; + ret[env_len + dir_len + 2] = '\0'; + if(!fileexists(ret, "w")) + { + //any initial setup should be done here + const static char* pkgdir = "packages"; + const static char* basedir = "base"; + char tmp[env_len + dir_len + strlen(pkgdir) + strlen(basedir) + 4]; + createdir(ret); + strncpy(tmp, ret, strlen(ret)); + strncat(tmp, pkgdir, strlen(pkgdir)); + createdir(tmp); + tmp[env_len + dir_len + strlen(pkgdir) + 2] = PATHDIV; + tmp[env_len + dir_len + strlen(pkgdir) + 3] = '\0'; + strncat(tmp, basedir, strlen(basedir)); + createdir(tmp); + } + config_dir = ret; + } + return config_dir; +} + +vector mod_directories; + +static void concat(const char* path, const char* file, char** buffer, size_t* buffer_len) +{ + char* buf = *buffer; + size_t buf_len = *buffer_len; + size_t pathlen = strlen(path); + size_t filelen = strlen(file); + size_t len = pathlen + filelen + 1; + if(!buf) + { + buf = (char*)malloc(len * sizeof(char)); + buf_len = len; + } + if(buf_len < len) + { + char* tmp = (char*)realloc(buf, len *sizeof(char)); + buf = tmp; + buf_len = len; + } + char* subpath = buf + pathlen; + strncpy(buf, path, pathlen); + strncpy(subpath, file, filelen); + buf[len - 1] = '\0'; + *buffer = buf; + *buffer_len = buf_len; +} + +char* pathforfile(const char* filename, const char* mode) +{ + static char nofile[] = "";//we return a pointer to an empty string bc SDL funcs vomit when you give them a NULL filename. + char* dir = get_config_dir(); + static char *path = NULL; + static size_t path_len = 0; + //if we're opening RO, look in all directories. otherwise, only look in user directory. + bool ro = true; + if(strpbrk(mode, "w")) ro = false; + + loopv(mod_directories) + { + concat(mod_directories[i].path, filename, &path, &path_len); + if(fileexists(path, ro)) return path; + } + + concat(dir, filename, &path, &path_len); + if(fileexists(path, ro)) return path; + if(!ro) return nofile;//we never write to the install directory. + + strcpy(path, filename); + if(fileexists(path, ro)) return path; + return nofile; +} + char *path(char *s) { for(char *t = s; (t = strpbrk(t, "/\\")); *t++ = PATHDIV); @@ -14,6 +112,7 @@ char *parentdir(char *directory) { char *p = strrchr(directory, '/'); + if(!p) p = strrchr(directory, '\\'); if(!p) p = directory; size_t len = p-directory+1; char *parent = new char[len]; @@ -21,9 +120,57 @@ return parent; } +bool fileexists(char* path, bool ro) +{ + char* tmp_path = path; + bool ret = true; + //if we're creating a file, we don't care if it exists, only if it's parent directory exists. + if(!ro) + tmp_path = parentdir(path); +#ifdef WIN32 + if(GetFileAttributes(path) == INVALID_FILE_ATTRIBUTES) + ret = false; +#else + int mode = R_OK; + if(!ro) mode |= W_OK; + if(access(path, mode) == -1) + ret = false; +#endif + if(tmp_path != path) + delete tmp_path; + return ret; +} + +bool createdir(char* path) +{ + size_t pathlen = strlen(path); + bool trailingslash = false; + bool ret = true; + if(path[pathlen - 1] == PATHDIV) + { + path[pathlen - 1] = '\0'; + trailingslash = true; + } +#ifdef WIN32 + if(!CreateDirectory(path, NULL)) + ret = false; +#else + if(mkdir(path, 0777) == -1) + ret = false; +#endif + if(trailingslash) + path[pathlen - 1] = PATHDIV; + return ret; +} + +FILE* openfile(char* name, char* mode) +{ + return fopen(pathforfile(name, mode), mode); +} + char *loadfile(char *fn, int *size) { - FILE *f = fopen(fn, "rb"); + FILE *f = openfile(fn, "rb"); if(!f) return NULL; fseek(f, 0, SEEK_END); int len = ftell(f); diff -Naur src/shared/tools.h src_mod/shared/tools.h --- src/shared/tools.h 2007-03-28 20:41:01.000000000 -0400 +++ src_mod/shared/tools.h 2007-07-17 01:46:24.000000000 -0400 @@ -100,10 +100,11 @@ template void _swap(T &a, T &b) { T t = a; a = b; b = t; } - - extern char *path(char *s); +extern bool fileexists(char* path, bool ro); +extern bool createdir(char* path); extern char *parentdir(char *directory); +extern FILE* openfile(char* name, char* mode); extern char *loadfile(char *fn, int *size); extern void endianswap(void *, int, int); extern void seedMT(uint seed); @@ -535,6 +536,18 @@ inline char *newstring(const char *s) { return newstring(s, strlen(s)); } inline char *newstringbuf(const char *s) { return newstring(s, _MAXDEFSTR-1); } +//vfs stuff +struct mod +{ + char* name; + char* path; +}; + +extern vector mod_directories; + +extern char* pathforfile(const char* filename, const char* mode); +extern char* get_config_dir(); + #ifndef __GNUC__ #ifdef _DEBUG //#define _CRTDBG_MAP_ALLOC