Enable path() to collapse multiple levels of "../" (sauer backport). Clean up multiple consecutive path delimiters. Remove feature to concat several paths with '&' delimiters (unused in AC). Filter paths in findfile() to disallow access to files below the AC root directory. This commit fixes the problem which prevented downloaded map models from using stock textures.
839 lines
22 KiB
C++
839 lines
22 KiB
C++
#include "cube.h"
|
|
|
|
///////////////////////// file system ///////////////////////
|
|
|
|
#ifndef WIN32
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <dirent.h>
|
|
#endif
|
|
|
|
string homedir = "";
|
|
vector<char *> packagedirs;
|
|
|
|
char *makerelpath(const char *dir, const char *file, const char *prefix, const char *cmd)
|
|
{
|
|
static string tmp;
|
|
if(prefix) copystring(tmp, prefix);
|
|
else tmp[0] = '\0';
|
|
if(file[0]=='<')
|
|
{
|
|
const char *end = strrchr(file, '>');
|
|
if(end)
|
|
{
|
|
size_t len = strlen(tmp);
|
|
copystring(&tmp[len], file, min(sizeof(tmp)-len, size_t(end+2-file)));
|
|
file = end+1;
|
|
}
|
|
}
|
|
if(cmd) concatstring(tmp, cmd);
|
|
defformatstring(pname)("%s/%s", dir, file);
|
|
concatstring(tmp, pname);
|
|
return tmp;
|
|
}
|
|
|
|
char *path(char *s)
|
|
{
|
|
char *c = s;
|
|
// skip "<decal>"
|
|
if(c[0] == '<')
|
|
{
|
|
char *enddecal = strrchr(c, '>');
|
|
if(!enddecal) return s;
|
|
c = enddecal + 1;
|
|
}
|
|
// substitute with single, proper path delimiters
|
|
for(char *t = c; (t = strpbrk(t, "/\\")); )
|
|
{
|
|
*t++ = PATHDIV;
|
|
size_t d = strspn(t, "/\\");
|
|
if(d) memmove(t, t + d, strlen(t + d) + 1); // remove multiple path delimiters
|
|
}
|
|
// collapse ".."-parts
|
|
for(char *prevdir = NULL, *curdir = s;;)
|
|
{
|
|
prevdir = curdir[0] == PATHDIV ? curdir + 1 : curdir;
|
|
curdir = strchr(prevdir, PATHDIV);
|
|
if(!curdir) break;
|
|
if(prevdir + 1 == curdir && prevdir[0]=='.')
|
|
{ // simply remove "./"
|
|
memmove(prevdir, curdir + 1, strlen(curdir));
|
|
curdir = prevdir;
|
|
}
|
|
else if(curdir[1] == '.' && curdir[2] == '.' && curdir[3] == PATHDIV)
|
|
{ // collapse "/foo/../" to "/"
|
|
if(prevdir + 2 == curdir && prevdir[0] == '.' && prevdir[1] == '.') continue; // foo is also ".." -> skip
|
|
memmove(prevdir, curdir + 4, strlen(curdir + 3));
|
|
if(prevdir >= c + 2 && prevdir[-1] == PATHDIV)
|
|
{
|
|
prevdir -= 2;
|
|
while(prevdir > c && prevdir[-1] != PATHDIV) --prevdir;
|
|
}
|
|
curdir = prevdir;
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
char *unixpath(char *s)
|
|
{
|
|
for(char *t = s; (t = strchr(t, '\\')); *t++ = '/');
|
|
return s;
|
|
}
|
|
|
|
char *path(const char *s, bool copy)
|
|
{
|
|
static string tmp;
|
|
copystring(tmp, s);
|
|
path(tmp);
|
|
return tmp;
|
|
}
|
|
|
|
const char *parentdir(const char *directory)
|
|
{
|
|
const char *p = directory + strlen(directory);
|
|
while(p > directory && *p != '/' && *p != '\\') p--;
|
|
static string parent;
|
|
size_t len = p-directory+1;
|
|
copystring(parent, directory, len);
|
|
return parent;
|
|
}
|
|
|
|
const char *behindpath(const char *s)
|
|
{
|
|
const char *t = s;
|
|
for( ; (s = strpbrk(s, "/\\")); t = ++s);
|
|
return t;
|
|
}
|
|
|
|
bool fileexists(const char *path, const char *mode)
|
|
{
|
|
bool exists = true;
|
|
if(mode[0]=='w' || mode[0]=='a') path = parentdir(path);
|
|
#ifdef WIN32
|
|
if(GetFileAttributes(path) == INVALID_FILE_ATTRIBUTES) exists = false;
|
|
#else
|
|
if(access(path, R_OK | (mode[0]=='w' || mode[0]=='a' ? W_OK : 0)) == -1) exists = false;
|
|
#endif
|
|
return exists;
|
|
}
|
|
|
|
bool createdir(const char *path)
|
|
{
|
|
size_t len = strlen(path);
|
|
if(path[len-1]==PATHDIV)
|
|
{
|
|
static string strip;
|
|
path = copystring(strip, path, len);
|
|
}
|
|
#ifdef WIN32
|
|
return CreateDirectory(path, NULL)!=0;
|
|
#else
|
|
return mkdir(path, 0777)==0;
|
|
#endif
|
|
}
|
|
|
|
size_t fixpackagedir(char *dir)
|
|
{
|
|
path(dir);
|
|
size_t len = strlen(dir);
|
|
if(len > 0 && dir[len-1] != PATHDIV)
|
|
{
|
|
dir[len] = PATHDIV;
|
|
dir[len+1] = '\0';
|
|
}
|
|
return len;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
char *getregszvalue(HKEY root, const char *keystr, const char *query)
|
|
{
|
|
HKEY key;
|
|
if(RegOpenKeyEx(HKEY_CURRENT_USER, keystr, 0, KEY_READ, &key)==ERROR_SUCCESS)
|
|
{
|
|
DWORD type = 0, len = 0;
|
|
if(RegQueryValueEx(key, query, 0, &type, 0, &len)==ERROR_SUCCESS && type==REG_SZ)
|
|
{
|
|
char *val = new char[len];
|
|
long result = RegQueryValueEx(key, query, 0, &type, (uchar *)val, &len);
|
|
if(result==ERROR_SUCCESS)
|
|
{
|
|
RegCloseKey(key);
|
|
val[len-1] = '\0';
|
|
return val;
|
|
}
|
|
delete[] val;
|
|
}
|
|
RegCloseKey(key);
|
|
}
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
void sethomedir(const char *dir)
|
|
{
|
|
string tmpdir;
|
|
copystring(tmpdir, dir);
|
|
|
|
#ifdef WIN32
|
|
const char substitute[] = "?MYDOCUMENTS?";
|
|
if(!strncmp(dir, substitute, strlen(substitute)))
|
|
{
|
|
const char *regpath = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders";
|
|
char *mydocuments = getregszvalue(HKEY_CURRENT_USER, regpath, "Personal");
|
|
if(mydocuments)
|
|
{
|
|
formatstring(tmpdir)("%s%s", mydocuments, dir+strlen(substitute));
|
|
delete[] mydocuments;
|
|
}
|
|
else
|
|
{
|
|
printf("failed to retrieve 'Personal' path from '%s'\n", regpath);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if(fixpackagedir(tmpdir) > 0)
|
|
{
|
|
#ifndef STANDALONE
|
|
clientlogf("Using home directory: %s", tmpdir);
|
|
#endif
|
|
copystring(homedir, tmpdir);
|
|
createdir(homedir);
|
|
}
|
|
}
|
|
|
|
void addpackagedir(const char *dir)
|
|
{
|
|
string pdir;
|
|
copystring(pdir, dir);
|
|
if(fixpackagedir(pdir) > 0)
|
|
{
|
|
#ifndef STANDALONE
|
|
clientlogf("Adding package directory: %s", pdir);
|
|
#endif
|
|
packagedirs.add(newstring(pdir));
|
|
}
|
|
}
|
|
|
|
const char *findfile(const char *filename, const char *mode)
|
|
{
|
|
while(filename[0] == PATHDIV) filename++; // skip leading pathdiv
|
|
while(!strncmp(".." PATHDIVS, filename, 3)) filename += 3; // skip leading "../" (don't allow access to files below "AC root dir")
|
|
static string s;
|
|
formatstring(s)("%s%s", homedir, filename); // homedir may be ""
|
|
if(homedir[0] && fileexists(s, mode)) return s;
|
|
if(mode[0]=='w' || mode[0]=='a')
|
|
{ // create missing directories, if necessary
|
|
string dirs;
|
|
copystring(dirs, s);
|
|
char *dir = strchr(dirs[0]==PATHDIV ? dirs+1 : dirs, PATHDIV);
|
|
while(dir)
|
|
{
|
|
*dir = '\0';
|
|
if(!fileexists(dirs, "r") && !createdir(dirs)) return s;
|
|
*dir = PATHDIV;
|
|
dir = strchr(dir+1, PATHDIV);
|
|
}
|
|
return s;
|
|
}
|
|
loopv(packagedirs)
|
|
{
|
|
formatstring(s)("%s%s", packagedirs[i], filename);
|
|
if(fileexists(s, mode)) return s;
|
|
}
|
|
return filename;
|
|
}
|
|
|
|
bool listdir(const char *dir, const char *ext, vector<char *> &files)
|
|
{
|
|
int extsize = ext ? (int)strlen(ext)+1 : 0;
|
|
#if defined(WIN32)
|
|
defformatstring(pathname)("%s\\*.%s", dir, ext ? ext : "*");
|
|
WIN32_FIND_DATA FindFileData;
|
|
HANDLE Find = FindFirstFile(path(pathname), &FindFileData);
|
|
if(Find != INVALID_HANDLE_VALUE)
|
|
{
|
|
do {
|
|
files.add(newstring(FindFileData.cFileName, (int)strlen(FindFileData.cFileName) - extsize));
|
|
} while(FindNextFile(Find, &FindFileData));
|
|
FindClose(Find);
|
|
return true;
|
|
}
|
|
#else
|
|
string pathname;
|
|
copystring(pathname, dir);
|
|
DIR *d = opendir(path(pathname));
|
|
if(d)
|
|
{
|
|
struct dirent *de;
|
|
while((de = readdir(d)) != NULL)
|
|
{
|
|
if(!ext) files.add(newstring(de->d_name));
|
|
else
|
|
{
|
|
int namelength = (int)strlen(de->d_name) - extsize;
|
|
if(namelength > 0 && de->d_name[namelength] == '.' && strncmp(de->d_name+namelength+1, ext, extsize-1)==0)
|
|
files.add(newstring(de->d_name, namelength));
|
|
}
|
|
}
|
|
closedir(d);
|
|
return true;
|
|
}
|
|
#endif
|
|
else return false;
|
|
}
|
|
|
|
int listfiles(const char *dir, const char *ext, vector<char *> &files)
|
|
{
|
|
int dirs = 0;
|
|
if(listdir(dir, ext, files)) dirs++;
|
|
string s;
|
|
if(homedir[0])
|
|
{
|
|
formatstring(s)("%s%s", homedir, dir);
|
|
if(listdir(s, ext, files)) dirs++;
|
|
}
|
|
loopv(packagedirs)
|
|
{
|
|
formatstring(s)("%s%s", packagedirs[i], dir);
|
|
if(listdir(s, ext, files)) dirs++;
|
|
}
|
|
#ifndef STANDALONE
|
|
dirs += listzipfiles(dir, ext, files);
|
|
#endif
|
|
return dirs;
|
|
}
|
|
|
|
#ifndef STANDALONE
|
|
void listfilesrecursive(const char *dir, vector<char *> &files, int level)
|
|
{
|
|
if(level > 8) return; // 8 levels is insane enough...
|
|
vector<char *> thisdir;
|
|
listfiles(dir, NULL, thisdir);
|
|
loopv(thisdir)
|
|
{
|
|
if(thisdir[i][0] != '.') // ignore "." and ".." (and also other directories starting with '.', like it is unix-convention - and doesn't hurt on windows)
|
|
{
|
|
defformatstring(name)("%s/%s", dir, thisdir[i]);
|
|
files.add(newstring(name));
|
|
listfilesrecursive(name, files, level + 1);
|
|
}
|
|
delstring(thisdir[i]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
bool delfile(const char *path)
|
|
{
|
|
return !remove(path);
|
|
}
|
|
|
|
void backup(char *name, char *backupname)
|
|
{
|
|
string backupfile;
|
|
copystring(backupfile, findfile(backupname, "wb"));
|
|
remove(backupfile);
|
|
rename(findfile(name, "wb"), backupfile);
|
|
}
|
|
|
|
#ifndef STANDALONE
|
|
static int rwopsseek(SDL_RWops *rw, int offset, int whence)
|
|
{
|
|
stream *f = (stream *)rw->hidden.unknown.data1;
|
|
if((!offset && whence==SEEK_CUR) || f->seek(offset, whence)) return f->tell();
|
|
return -1;
|
|
}
|
|
|
|
static int rwopsread(SDL_RWops *rw, void *buf, int size, int nmemb)
|
|
{
|
|
stream *f = (stream *)rw->hidden.unknown.data1;
|
|
return f->read(buf, size*nmemb)/size;
|
|
}
|
|
|
|
static int rwopswrite(SDL_RWops *rw, const void *buf, int size, int nmemb)
|
|
{
|
|
stream *f = (stream *)rw->hidden.unknown.data1;
|
|
return f->write(buf, size*nmemb)/size;
|
|
}
|
|
|
|
static int rwopsclose(SDL_RWops *rw)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
SDL_RWops *stream::rwops()
|
|
{
|
|
SDL_RWops *rw = SDL_AllocRW();
|
|
if(!rw) return NULL;
|
|
rw->hidden.unknown.data1 = this;
|
|
rw->seek = rwopsseek;
|
|
rw->read = rwopsread;
|
|
rw->write = rwopswrite;
|
|
rw->close = rwopsclose;
|
|
return rw;
|
|
}
|
|
#endif
|
|
|
|
long stream::size()
|
|
{
|
|
long pos = tell(), endpos;
|
|
if(pos < 0 || !seek(0, SEEK_END)) return -1;
|
|
endpos = tell();
|
|
return pos == endpos || seek(pos, SEEK_SET) ? endpos : -1;
|
|
}
|
|
|
|
bool stream::getline(char *str, int len)
|
|
{
|
|
loopi(len-1)
|
|
{
|
|
if(read(&str[i], 1) != 1) { str[i] = '\0'; return i > 0; }
|
|
else if(str[i] == '\n') { str[i+1] = '\0'; return true; }
|
|
}
|
|
if(len > 0) str[len-1] = '\0';
|
|
return true;
|
|
}
|
|
|
|
#ifndef WIN32
|
|
#include <sys/statvfs.h>
|
|
const int64_t MINFSSIZE = 50000000; // 50MB
|
|
#endif
|
|
|
|
struct filestream : stream
|
|
{
|
|
FILE *file;
|
|
|
|
filestream() : file(NULL) {}
|
|
~filestream() { close(); }
|
|
|
|
bool open(const char *name, const char *mode)
|
|
{
|
|
if(file) return false;
|
|
file = fopen(name, mode);
|
|
#ifndef WIN32
|
|
struct statvfs buf;
|
|
if(file && strchr(mode,'w'))
|
|
{
|
|
int fail = fstatvfs(fileno(file), &buf);
|
|
if (fail || (int64_t)buf.f_frsize * (int64_t)buf.f_bavail < MINFSSIZE)
|
|
{
|
|
close();
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
return file!=NULL;
|
|
}
|
|
|
|
bool opentemp(const char *name, const char *mode)
|
|
{
|
|
if(file) return false;
|
|
#ifdef WIN32
|
|
file = fopen(name, mode);
|
|
#else
|
|
file = tmpfile();
|
|
#endif
|
|
return file!=NULL;
|
|
}
|
|
|
|
void close()
|
|
{
|
|
if(file) { fclose(file); file = NULL; }
|
|
}
|
|
|
|
bool end() { return feof(file)!=0; }
|
|
long tell() { return ftell(file); }
|
|
bool seek(long offset, int whence) { return fseek(file, offset, whence) >= 0; }
|
|
void fflush() { if(file) ::fflush(file); }
|
|
int read(void *buf, int len) { return (int)fread(buf, 1, len, file); }
|
|
int write(const void *buf, int len) { return (int)fwrite(buf, 1, len, file); }
|
|
int getchar() { return fgetc(file); }
|
|
bool putchar(int c) { return fputc(c, file)!=EOF; }
|
|
bool getline(char *str, int len) { return fgets(str, len, file)!=NULL; }
|
|
bool putstring(const char *str) { return fputs(str, file)!=EOF; }
|
|
|
|
int printf(const char *fmt, ...)
|
|
{
|
|
va_list v;
|
|
va_start(v, fmt);
|
|
int result = vfprintf(file, fmt, v);
|
|
va_end(v);
|
|
return result;
|
|
}
|
|
};
|
|
|
|
#ifndef STANDALONE
|
|
//VAR(dbggz, 0, 0, 1);
|
|
const int dbggz = 0;
|
|
#endif
|
|
|
|
struct gzstream : stream
|
|
{
|
|
enum
|
|
{
|
|
MAGIC1 = 0x1F,
|
|
MAGIC2 = 0x8B,
|
|
BUFSIZE = 16384,
|
|
OS_UNIX = 0x03
|
|
};
|
|
|
|
enum
|
|
{
|
|
F_ASCII = 0x01,
|
|
F_CRC = 0x02,
|
|
F_EXTRA = 0x04,
|
|
F_NAME = 0x08,
|
|
F_COMMENT = 0x10,
|
|
F_RESERVED = 0xE0
|
|
};
|
|
|
|
stream *file;
|
|
z_stream zfile;
|
|
uchar *buf;
|
|
bool reading, writing, autoclose;
|
|
uint crc;
|
|
int headersize;
|
|
|
|
gzstream() : file(NULL), buf(NULL), reading(false), writing(false), autoclose(false), crc(0), headersize(0)
|
|
{
|
|
zfile.zalloc = NULL;
|
|
zfile.zfree = NULL;
|
|
zfile.opaque = NULL;
|
|
zfile.next_in = zfile.next_out = NULL;
|
|
zfile.avail_in = zfile.avail_out = 0;
|
|
}
|
|
|
|
~gzstream()
|
|
{
|
|
close();
|
|
}
|
|
|
|
void writeheader()
|
|
{
|
|
uchar header[] = { MAGIC1, MAGIC2, Z_DEFLATED, 0, 0, 0, 0, 0, 0, OS_UNIX };
|
|
file->write(header, sizeof(header));
|
|
}
|
|
|
|
void readbuf(int size = BUFSIZE)
|
|
{
|
|
if(!zfile.avail_in) zfile.next_in = (Bytef *)buf;
|
|
size = min(size, int(&buf[BUFSIZE] - &zfile.next_in[zfile.avail_in]));
|
|
int n = file->read(zfile.next_in + zfile.avail_in, size);
|
|
if(n > 0) zfile.avail_in += n;
|
|
}
|
|
|
|
int readbyte(int size = BUFSIZE)
|
|
{
|
|
if(!zfile.avail_in) readbuf(size);
|
|
if(!zfile.avail_in) return 0;
|
|
zfile.avail_in--;
|
|
return *(uchar *)zfile.next_in++;
|
|
}
|
|
|
|
void skipbytes(int n)
|
|
{
|
|
while(n > 0 && zfile.avail_in > 0)
|
|
{
|
|
int skipped = min(n, (int)zfile.avail_in);
|
|
zfile.avail_in -= skipped;
|
|
zfile.next_in += skipped;
|
|
n -= skipped;
|
|
}
|
|
if(n <= 0) return;
|
|
file->seek(n, SEEK_CUR);
|
|
}
|
|
|
|
bool checkheader()
|
|
{
|
|
readbuf(10);
|
|
if(readbyte() != MAGIC1 || readbyte() != MAGIC2 || readbyte() != Z_DEFLATED) return false;
|
|
int flags = readbyte();
|
|
if(flags & F_RESERVED) return false;
|
|
skipbytes(6);
|
|
if(flags & F_EXTRA)
|
|
{
|
|
int len = readbyte(512);
|
|
len |= readbyte(512)<<8;
|
|
skipbytes(len);
|
|
}
|
|
if(flags & F_NAME) while(readbyte(512));
|
|
if(flags & F_COMMENT) while(readbyte(512));
|
|
if(flags & F_CRC) skipbytes(2);
|
|
headersize = file->tell() - zfile.avail_in;
|
|
return zfile.avail_in > 0 || !file->end();
|
|
}
|
|
|
|
bool open(stream *f, const char *mode, bool needclose, int level)
|
|
{
|
|
if(file) return false;
|
|
for(; *mode; mode++)
|
|
{
|
|
if(*mode=='r') { reading = true; break; }
|
|
else if(*mode=='w') { writing = true; break; }
|
|
}
|
|
if(reading)
|
|
{
|
|
if(inflateInit2(&zfile, -MAX_WBITS) != Z_OK) reading = false;
|
|
}
|
|
else if(writing && deflateInit2(&zfile, level, Z_DEFLATED, -MAX_WBITS, min(MAX_MEM_LEVEL, 8), Z_DEFAULT_STRATEGY) != Z_OK) writing = false;
|
|
if(!reading && !writing) return false;
|
|
|
|
autoclose = needclose;
|
|
file = f;
|
|
crc = crc32(0, NULL, 0);
|
|
buf = new uchar[BUFSIZE];
|
|
|
|
if(reading)
|
|
{
|
|
if(!checkheader()) { stopreading(); return false; }
|
|
}
|
|
else if(writing) writeheader();
|
|
return true;
|
|
}
|
|
|
|
uint getcrc() { return crc; }
|
|
|
|
void finishreading()
|
|
{
|
|
if(!reading) return;
|
|
#ifndef STANDALONE
|
|
if(dbggz)
|
|
{
|
|
uint checkcrc = 0, checksize = 0;
|
|
loopi(4) checkcrc |= uint(readbyte()) << (i*8);
|
|
loopi(4) checksize |= uint(readbyte()) << (i*8);
|
|
if(checkcrc != crc)
|
|
conoutf("gzip crc check failed: read %X, calculated %X", checkcrc, crc);
|
|
if(checksize != zfile.total_out)
|
|
conoutf("gzip size check failed: read %d, calculated %d", checksize, zfile.total_out);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void stopreading()
|
|
{
|
|
if(!reading) return;
|
|
inflateEnd(&zfile);
|
|
reading = false;
|
|
}
|
|
|
|
void finishwriting()
|
|
{
|
|
if(!writing) return;
|
|
for(;;)
|
|
{
|
|
int err = zfile.avail_out > 0 ? deflate(&zfile, Z_FINISH) : Z_OK;
|
|
if(err != Z_OK && err != Z_STREAM_END) break;
|
|
flush();
|
|
if(err == Z_STREAM_END) break;
|
|
}
|
|
uchar trailer[8] =
|
|
{
|
|
uchar(crc&0xFF), uchar((crc>>8)&0xFF), uchar((crc>>16)&0xFF), uchar((crc>>24)&0xFF),
|
|
uchar(zfile.total_in&0xFF), uchar((zfile.total_in>>8)&0xFF), uchar((zfile.total_in>>16)&0xFF), uchar((zfile.total_in>>24)&0xFF)
|
|
};
|
|
file->write(trailer, sizeof(trailer));
|
|
}
|
|
|
|
void stopwriting()
|
|
{
|
|
if(!writing) return;
|
|
deflateEnd(&zfile);
|
|
writing = false;
|
|
}
|
|
|
|
void close()
|
|
{
|
|
if(reading) finishreading();
|
|
stopreading();
|
|
if(writing) finishwriting();
|
|
stopwriting();
|
|
DELETEA(buf);
|
|
if(autoclose) DELETEP(file);
|
|
}
|
|
|
|
bool end() { return !reading && !writing; }
|
|
long tell() { return reading ? zfile.total_out : (writing ? zfile.total_in : -1); }
|
|
|
|
bool seek(long offset, int whence)
|
|
{
|
|
if(writing || !reading) return false;
|
|
|
|
if(whence == SEEK_END)
|
|
{
|
|
uchar skip[512];
|
|
while(read(skip, sizeof(skip)) == sizeof(skip));
|
|
return !offset;
|
|
}
|
|
else if(whence == SEEK_CUR) offset += zfile.total_out;
|
|
|
|
if(offset >= (int)zfile.total_out) offset -= zfile.total_out;
|
|
else if(offset < 0 || !file->seek(headersize, SEEK_SET)) return false;
|
|
else
|
|
{
|
|
if(zfile.next_in && zfile.total_in <= uint(zfile.next_in - buf))
|
|
{
|
|
zfile.avail_in += zfile.total_in;
|
|
zfile.next_in -= zfile.total_in;
|
|
}
|
|
else
|
|
{
|
|
zfile.avail_in = 0;
|
|
zfile.next_in = NULL;
|
|
}
|
|
inflateReset(&zfile);
|
|
crc = crc32(0, NULL, 0);
|
|
}
|
|
|
|
uchar skip[512];
|
|
while(offset > 0)
|
|
{
|
|
int skipped = min(offset, (long)sizeof(skip));
|
|
if(read(skip, skipped) != skipped) { stopreading(); return false; }
|
|
offset -= skipped;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int read(void *buf, int len)
|
|
{
|
|
if(!reading || !buf || !len) return 0;
|
|
zfile.next_out = (Bytef *)buf;
|
|
zfile.avail_out = len;
|
|
while(zfile.avail_out > 0)
|
|
{
|
|
if(!zfile.avail_in)
|
|
{
|
|
readbuf(BUFSIZE);
|
|
if(!zfile.avail_in) { stopreading(); break; }
|
|
}
|
|
int err = inflate(&zfile, Z_NO_FLUSH);
|
|
if(err == Z_STREAM_END) { crc = crc32(crc, (Bytef *)buf, len - zfile.avail_out); finishreading(); stopreading(); return len - zfile.avail_out; }
|
|
else if(err != Z_OK) { stopreading(); break; }
|
|
}
|
|
crc = crc32(crc, (Bytef *)buf, len - zfile.avail_out);
|
|
return len - zfile.avail_out;
|
|
}
|
|
|
|
bool flush()
|
|
{
|
|
if(zfile.next_out && zfile.avail_out < BUFSIZE)
|
|
{
|
|
if(file->write(buf, BUFSIZE - zfile.avail_out) != int(BUFSIZE - zfile.avail_out))
|
|
return false;
|
|
}
|
|
zfile.next_out = buf;
|
|
zfile.avail_out = BUFSIZE;
|
|
return true;
|
|
}
|
|
|
|
int write(const void *buf, int len)
|
|
{
|
|
if(!writing || !buf || !len) return 0;
|
|
zfile.next_in = (Bytef *)buf;
|
|
zfile.avail_in = len;
|
|
while(zfile.avail_in > 0)
|
|
{
|
|
if(!zfile.avail_out && !flush()) { stopwriting(); break; }
|
|
int err = deflate(&zfile, Z_NO_FLUSH);
|
|
if(err != Z_OK) { stopwriting(); break; }
|
|
}
|
|
crc = crc32(crc, (Bytef *)buf, len - zfile.avail_in);
|
|
return len - zfile.avail_in;
|
|
}
|
|
};
|
|
|
|
|
|
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);
|
|
#endif
|
|
if(!found) return NULL;
|
|
filestream *file = new filestream;
|
|
if(!file->open(found, mode))
|
|
{
|
|
#ifndef STANDALONE
|
|
// conoutf("file failure! %s",filename);
|
|
#endif
|
|
delete file; return NULL;
|
|
}
|
|
return file;
|
|
}
|
|
|
|
stream *openfile(const char *filename, const char *mode)
|
|
{
|
|
#ifndef STANDALONE
|
|
stream *s = openzipfile(filename, mode);
|
|
if(s) return s;
|
|
#endif
|
|
return openrawfile(filename, mode);
|
|
}
|
|
|
|
int getfilesize(const char *filename)
|
|
{
|
|
stream *f = openfile(filename, "rb");
|
|
if(!f) return -1;
|
|
int len = f->size();
|
|
delete f;
|
|
return len;
|
|
}
|
|
|
|
stream *opentempfile(const char *name, const char *mode)
|
|
{
|
|
const char *found = findfile(name, mode);
|
|
filestream *file = new filestream;
|
|
if(!file->opentemp(found ? found : name, mode)) { delete file; return NULL; }
|
|
return file;
|
|
}
|
|
|
|
stream *opengzfile(const char *filename, const char *mode, stream *file, int level)
|
|
{
|
|
stream *source = file ? file : openfile(filename, mode);
|
|
if(!source) return NULL;
|
|
gzstream *gz = new gzstream;
|
|
if(!gz->open(source, mode, !file, level)) { if(!file) delete source; return NULL; }
|
|
return gz;
|
|
}
|
|
|
|
char *loadfile(const char *fn, int *size, const char *mode)
|
|
{
|
|
stream *f = openfile(fn, mode ? mode : "rb");
|
|
if(!f) return NULL;
|
|
int len = f->size();
|
|
if(len<=0) { delete f; return NULL; }
|
|
char *buf = new char[len+1];
|
|
if(!buf) { delete f; return NULL; }
|
|
buf[len] = 0;
|
|
int rlen = f->read(buf, len);
|
|
delete f;
|
|
if(len!=rlen && (!mode || strchr(mode, 'b')))
|
|
{
|
|
delete[] buf;
|
|
return NULL;
|
|
}
|
|
if(size!=NULL) *size = len;
|
|
return buf;
|
|
}
|
|
|
|
#ifndef STANDALONE
|
|
void filerotate(const char *basename, const char *ext, int keepold, const char *oldformat) // rotate old logfiles
|
|
{
|
|
char fname1[MAXSTRLEN * 2], fname2[MAXSTRLEN] = "";
|
|
copystring(fname1, findfile(basename, "w"));
|
|
int mid = strlen(fname1);
|
|
if(!oldformat) oldformat = "-old-%d";
|
|
for(; keepold >= 0; keepold--)
|
|
{
|
|
formatstring(fname1 + mid)(keepold ? oldformat : "", keepold);
|
|
concatformatstring(fname1 + mid, ".%s", ext);
|
|
if(fname2[0]) rename(fname1, fname2);
|
|
else remove(fname1);
|
|
copystring(fname2, fname1);
|
|
}
|
|
}
|
|
#endif
|