mirror of
https://github.com/nzp-team/fteqw.git
synced 2024-11-23 12:22:42 +00:00
c0680334c7
Multiple consoles can now be printed/cleared via extra con commands. Fixed the tab-completion alignment, by adding support for \t characters. Changing the download mechanisms. Don't try downloading an ftp:// file. It'll probably crash you for now. Trying to fix load time issues on q3bsps with a lot of curves. Fixed sprites. Added warning prints/spam where the new backend is bypassed, thus marking things that still need to be fixed. QTV proxy fixed to not sit on qw servers unless someone is actually watching. Will ping for status requests still. QTV proxy now supports ipv6. QTV proxy now attempts to use the fte browser plugin. Reworked the browser plugin code, now uses threads instead of ugly hacks. This should make cooperation with other such plugins work. Fixes unresponsiveness of opera, and gives an API that can be used from any other bit of software you want, tbh (read: internet explorer/activex plugins). git-svn-id: https://svn.code.sf.net/p/fteqw/code/branches/wip@3516 fc73d0e0-1445-4013-8a0c-d673dee63da5
2553 lines
56 KiB
C
2553 lines
56 KiB
C
#include "quakedef.h"
|
|
#include "netinc.h"
|
|
|
|
//#define com_gamedir com__gamedir
|
|
|
|
#include <ctype.h>
|
|
#include <limits.h>
|
|
#include <errno.h>
|
|
|
|
#include "fs.h"
|
|
|
|
#if defined(MINGW) && defined(_SDL)
|
|
#include "./mingw-libs/SDL_syswm.h" // mingw sdl cross binary complains off sys_parentwindow
|
|
#endif
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma comment(lib, MSVCLIBSPATH "zlib.lib")
|
|
#endif
|
|
|
|
hashtable_t filesystemhash;
|
|
qboolean com_fschanged = true;
|
|
extern cvar_t com_fs_cache;
|
|
int active_fs_cachetype;
|
|
|
|
struct
|
|
{
|
|
const char *extension;
|
|
searchpathfuncs_t *funcs;
|
|
} searchpathformats[64];
|
|
|
|
int FS_RegisterFileSystemType(const char *extension, searchpathfuncs_t *funcs)
|
|
{
|
|
unsigned int i;
|
|
for (i = 0; i < sizeof(searchpathformats)/sizeof(searchpathformats[0]); i++)
|
|
{
|
|
if (searchpathformats[i].extension && !strcmp(searchpathformats[i].extension, extension))
|
|
break; //extension match always replaces
|
|
if (!searchpathformats[i].extension && !searchpathformats[i].funcs)
|
|
break;
|
|
}
|
|
if (i == sizeof(searchpathformats)/sizeof(searchpathformats[0]))
|
|
return 0;
|
|
|
|
searchpathformats[i].extension = extension;
|
|
searchpathformats[i].funcs = funcs;
|
|
com_fschanged = true;
|
|
|
|
return i+1;
|
|
}
|
|
|
|
void FS_UnRegisterFileSystemType(int idx)
|
|
{
|
|
if ((unsigned int)(idx-1) >= sizeof(searchpathformats)/sizeof(searchpathformats[0]))
|
|
return;
|
|
|
|
searchpathformats[idx-1].funcs = NULL;
|
|
com_fschanged = true;
|
|
|
|
//FS_Restart will be needed
|
|
}
|
|
|
|
|
|
vfsfile_t *FS_OpenVFSLoc(flocation_t *loc, char *mode);
|
|
|
|
|
|
|
|
char *VFS_GETS(vfsfile_t *vf, char *buffer, int buflen)
|
|
{
|
|
char in;
|
|
char *out = buffer;
|
|
int len;
|
|
len = buflen-1;
|
|
if (len == 0)
|
|
return NULL;
|
|
while (len > 0)
|
|
{
|
|
if (!VFS_READ(vf, &in, 1))
|
|
{
|
|
if (len == buflen-1)
|
|
return NULL;
|
|
*out = '\0';
|
|
return buffer;
|
|
}
|
|
if (in == '\n')
|
|
break;
|
|
*out++ = in;
|
|
len--;
|
|
}
|
|
*out = '\0';
|
|
|
|
return buffer;
|
|
}
|
|
|
|
void VARGS VFS_PRINTF(vfsfile_t *vf, 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);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
char gamedirfile[MAX_OSPATH];
|
|
|
|
|
|
|
|
|
|
//the various COM_LoadFiles set these on return
|
|
int com_filesize;
|
|
qboolean com_file_copyprotected;
|
|
|
|
|
|
//char *com_basedir; //obsolete
|
|
|
|
char com_quakedir[MAX_OSPATH];
|
|
char com_homedir[MAX_OSPATH];
|
|
|
|
char com_configdir[MAX_OSPATH]; //homedir/fte/configs
|
|
|
|
int fs_hash_dups;
|
|
int fs_hash_files;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int COM_FileOpenRead (char *path, FILE **hndl);
|
|
static const char *FS_GetCleanPath(const char *pattern, char *outbuf, int outlen);
|
|
void FS_RegisterDefaultFileSystems(void);
|
|
|
|
#define ENFORCEFOPENMODE(mode) {if (strcmp(mode, "r") && strcmp(mode, "w")/* && strcmp(mode, "rw")*/)Sys_Error("fs mode %s is not permitted here\n");}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//======================================================================================================
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct searchpath_s
|
|
{
|
|
const searchpathfuncs_t *funcs;
|
|
qboolean copyprotected; //don't allow downloads from here.
|
|
qboolean istemporary;
|
|
qboolean isexplicit; //explicitly loaded (ie: id1|qw|$gamedir|fte)
|
|
void *handle;
|
|
|
|
char purepath[256]; //server tracks the path used to load them so it can tell the client
|
|
int crc_check; //client sorts packs according to this checksum
|
|
int crc_reply; //client sends a different crc back to the server, for the paks it's actually loaded.
|
|
|
|
struct searchpath_s *next;
|
|
struct searchpath_s *nextpure;
|
|
} searchpath_t;
|
|
|
|
searchpath_t *com_searchpaths;
|
|
searchpath_t *com_purepaths;
|
|
searchpath_t *com_base_searchpaths; // without gamedirs
|
|
|
|
/*
|
|
================
|
|
COM_filelength
|
|
================
|
|
*/
|
|
int COM_filelength (FILE *f)
|
|
{
|
|
int pos;
|
|
int end;
|
|
|
|
pos = ftell (f);
|
|
fseek (f, 0, SEEK_END);
|
|
end = ftell (f);
|
|
fseek (f, pos, SEEK_SET);
|
|
|
|
return end;
|
|
}
|
|
/*
|
|
static int COM_FileOpenRead (char *path, FILE **hndl)
|
|
{
|
|
FILE *f;
|
|
|
|
f = fopen(path, "rb");
|
|
if (!f)
|
|
{
|
|
*hndl = NULL;
|
|
return -1;
|
|
}
|
|
*hndl = f;
|
|
|
|
return COM_filelength(f);
|
|
}
|
|
*/
|
|
|
|
int COM_FileSize(const char *path)
|
|
{
|
|
int len;
|
|
flocation_t loc;
|
|
len = FS_FLocateFile(path, FSLFRT_LENGTH, &loc);
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
============
|
|
COM_Path_f
|
|
|
|
============
|
|
*/
|
|
void COM_Path_f (void)
|
|
{
|
|
searchpath_t *s;
|
|
|
|
Con_TPrintf (TL_CURRENTSEARCHPATH);
|
|
|
|
if (com_purepaths)
|
|
{
|
|
Con_Printf ("Pure paths:\n");
|
|
for (s=com_purepaths ; s ; s=s->nextpure)
|
|
{
|
|
s->funcs->PrintPath(s->handle);
|
|
}
|
|
Con_Printf ("----------\n");
|
|
Con_Printf ("Impure paths:\n");
|
|
}
|
|
|
|
|
|
for (s=com_searchpaths ; s ; s=s->next)
|
|
{
|
|
if (s == com_base_searchpaths)
|
|
Con_Printf ("----------\n");
|
|
|
|
s->funcs->PrintPath(s->handle);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
COM_Dir_f
|
|
|
|
============
|
|
*/
|
|
static int COM_Dir_List(const char *name, int size, void *parm)
|
|
{
|
|
Con_Printf("%s (%i)\n", name, size);
|
|
return 1;
|
|
}
|
|
|
|
void COM_Dir_f (void)
|
|
{
|
|
char match[MAX_QPATH];
|
|
|
|
Q_strncpyz(match, Cmd_Argv(1), sizeof(match));
|
|
if (Cmd_Argc()>2)
|
|
{
|
|
strncat(match, "/*.", sizeof(match)-1);
|
|
match[sizeof(match)-1] = '\0';
|
|
strncat(match, Cmd_Argv(2), sizeof(match)-1);
|
|
match[sizeof(match)-1] = '\0';
|
|
}
|
|
else
|
|
strncat(match, "/*", sizeof(match)-1);
|
|
|
|
COM_EnumerateFiles(match, COM_Dir_List, NULL);
|
|
}
|
|
|
|
/*
|
|
============
|
|
COM_Locate_f
|
|
|
|
============
|
|
*/
|
|
void COM_Locate_f (void)
|
|
{
|
|
flocation_t loc;
|
|
if (FS_FLocateFile(Cmd_Argv(1), FSLFRT_LENGTH, &loc)>=0)
|
|
{
|
|
if (!*loc.rawname)
|
|
{
|
|
Con_Printf("File is compressed inside ");
|
|
loc.search->funcs->PrintPath(loc.search->handle);
|
|
}
|
|
else
|
|
{
|
|
Con_Printf("Inside %s\n", loc.rawname);
|
|
loc.search->funcs->PrintPath(loc.search->handle);
|
|
}
|
|
}
|
|
else
|
|
Con_Printf("Not found\n");
|
|
}
|
|
|
|
/*
|
|
============
|
|
COM_WriteFile
|
|
|
|
The filename will be prefixed by the current game directory
|
|
============
|
|
*/
|
|
void COM_WriteFile (const char *filename, const void *data, int len)
|
|
{
|
|
vfsfile_t *vfs;
|
|
|
|
Sys_Printf ("COM_WriteFile: %s\n", filename);
|
|
|
|
FS_CreatePath(filename, FS_GAMEONLY);
|
|
vfs = FS_OpenVFS(filename, "wb", FS_GAMEONLY);
|
|
if (vfs)
|
|
{
|
|
VFS_WRITE(vfs, data, len);
|
|
VFS_CLOSE(vfs);
|
|
}
|
|
|
|
com_fschanged=true;
|
|
}
|
|
|
|
FILE *COM_WriteFileOpen (char *filename) //like fopen, but based around quake's paths.
|
|
{
|
|
FILE *f;
|
|
char name[MAX_OSPATH];
|
|
|
|
if (!FS_NativePath(filename, FS_GAMEONLY, name, sizeof(name)))
|
|
return NULL;
|
|
|
|
COM_CreatePath(name);
|
|
|
|
f = fopen (name, "wb");
|
|
|
|
return f;
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
COM_CreatePath
|
|
|
|
Only used for CopyFile and download
|
|
============
|
|
*/
|
|
void COM_CreatePath (char *path)
|
|
{
|
|
char *ofs;
|
|
|
|
for (ofs = path+1 ; *ofs ; ofs++)
|
|
{
|
|
if (*ofs == '/')
|
|
{ // create the directory
|
|
*ofs = 0;
|
|
Sys_mkdir (path);
|
|
*ofs = '/';
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===========
|
|
COM_CopyFile
|
|
|
|
Copies a file over from the net to the local cache, creating any directories
|
|
needed. This is for the convenience of developers using ISDN from home.
|
|
===========
|
|
*/
|
|
/*
|
|
static void COM_CopyFile (char *netpath, char *cachepath)
|
|
{
|
|
FILE *in, *out;
|
|
int remaining, count;
|
|
char buf[4096];
|
|
|
|
remaining = COM_FileOpenRead (netpath, &in);
|
|
COM_CreatePath (cachepath); // create directories up to the cache file
|
|
out = fopen(cachepath, "wb");
|
|
if (!out)
|
|
Sys_Error ("Error opening %s", cachepath);
|
|
|
|
while (remaining)
|
|
{
|
|
if (remaining < sizeof(buf))
|
|
count = remaining;
|
|
else
|
|
count = sizeof(buf);
|
|
fread (buf, 1, count, in);
|
|
fwrite (buf, 1, count, out);
|
|
remaining -= count;
|
|
}
|
|
|
|
fclose (in);
|
|
fclose (out);
|
|
}
|
|
//*/
|
|
|
|
int fs_hash_dups;
|
|
int fs_hash_files;
|
|
|
|
void FS_FlushFSHash(void)
|
|
{
|
|
if (filesystemhash.numbuckets)
|
|
{
|
|
int i;
|
|
bucket_t *bucket, *next;
|
|
|
|
for (i = 0; i < filesystemhash.numbuckets; i++)
|
|
{
|
|
bucket = filesystemhash.bucket[i];
|
|
filesystemhash.bucket[i] = NULL;
|
|
while(bucket)
|
|
{
|
|
next = bucket->next;
|
|
if (bucket->key.string == (char*)(bucket+1))
|
|
Z_Free(bucket);
|
|
bucket = next;
|
|
}
|
|
}
|
|
}
|
|
|
|
com_fschanged = true;
|
|
}
|
|
|
|
void FS_RebuildFSHash(void)
|
|
{
|
|
searchpath_t *search;
|
|
if (!filesystemhash.numbuckets)
|
|
{
|
|
filesystemhash.numbuckets = 1024;
|
|
filesystemhash.bucket = (bucket_t**)Z_Malloc(Hash_BytesForBuckets(filesystemhash.numbuckets));
|
|
}
|
|
else
|
|
{
|
|
FS_FlushFSHash();
|
|
}
|
|
Hash_InitTable(&filesystemhash, filesystemhash.numbuckets, filesystemhash.bucket);
|
|
|
|
fs_hash_dups = 0;
|
|
fs_hash_files = 0;
|
|
|
|
if (com_purepaths)
|
|
{ //go for the pure paths first.
|
|
for (search = com_purepaths; search; search = search->nextpure)
|
|
{
|
|
search->funcs->BuildHash(search->handle);
|
|
}
|
|
}
|
|
for (search = com_searchpaths ; search ; search = search->next)
|
|
{
|
|
search->funcs->BuildHash(search->handle);
|
|
}
|
|
|
|
com_fschanged = false;
|
|
|
|
Con_Printf("%i unique files, %i duplicates\n", fs_hash_files, fs_hash_dups);
|
|
}
|
|
|
|
/*
|
|
===========
|
|
COM_FindFile
|
|
|
|
Finds the file in the search path.
|
|
Sets com_filesize and one of handle or file
|
|
===========
|
|
*/
|
|
//if loc is valid, loc->search is always filled in, the others are filled on success.
|
|
//returns -1 if couldn't find.
|
|
int FS_FLocateFile(const char *filename, FSLF_ReturnType_e returntype, flocation_t *loc)
|
|
{
|
|
int depth=0, len;
|
|
searchpath_t *search;
|
|
char cleanpath[MAX_QPATH];
|
|
|
|
void *pf;
|
|
//Con_Printf("Finding %s: ", filename);
|
|
|
|
filename = FS_GetCleanPath(filename, cleanpath, sizeof(cleanpath));
|
|
if (!filename)
|
|
{
|
|
pf = NULL;
|
|
goto fail;
|
|
}
|
|
|
|
if (com_fs_cache.value)
|
|
{
|
|
if (com_fschanged)
|
|
FS_RebuildFSHash();
|
|
pf = Hash_GetInsensative(&filesystemhash, filename);
|
|
if (!pf)
|
|
goto fail;
|
|
}
|
|
else
|
|
pf = NULL;
|
|
|
|
if (com_purepaths)
|
|
{
|
|
for (search = com_purepaths ; search ; search = search->nextpure)
|
|
{
|
|
if (search->funcs->FindFile(search->handle, loc, filename, pf))
|
|
{
|
|
if (loc)
|
|
{
|
|
loc->search = search;
|
|
len = loc->len;
|
|
}
|
|
else
|
|
len = 0;
|
|
com_file_copyprotected = search->copyprotected;
|
|
goto out;
|
|
}
|
|
depth += (search->isexplicit || returntype == FSLFRT_DEPTH_ANYPATH);
|
|
}
|
|
}
|
|
|
|
//
|
|
// search through the path, one element at a time
|
|
//
|
|
for (search = com_searchpaths ; search ; search = search->next)
|
|
{
|
|
if (search->funcs->FindFile(search->handle, loc, filename, pf))
|
|
{
|
|
if (loc)
|
|
{
|
|
loc->search = search;
|
|
len = loc->len;
|
|
}
|
|
else
|
|
len = 1;
|
|
com_file_copyprotected = search->copyprotected;
|
|
goto out;
|
|
}
|
|
depth += (search->isexplicit || returntype == FSLFRT_DEPTH_ANYPATH);
|
|
}
|
|
fail:
|
|
if (loc)
|
|
loc->search = NULL;
|
|
depth = 0x7fffffff;
|
|
len = -1;
|
|
out:
|
|
|
|
/* if (len>=0)
|
|
{
|
|
if (loc)
|
|
Con_Printf("Found %s:%i\n", loc->rawname, loc->len);
|
|
else
|
|
Con_Printf("Found %s\n", filename);
|
|
}
|
|
else
|
|
Con_Printf("Failed\n");
|
|
*/
|
|
if (returntype == FSLFRT_IFFOUND)
|
|
return len != -1;
|
|
else if (returntype == FSLFRT_LENGTH)
|
|
return len;
|
|
else
|
|
return depth;
|
|
}
|
|
|
|
char *FS_WhichPackForLocation(flocation_t *loc)
|
|
{
|
|
char *ret;
|
|
if (!loc->search)
|
|
return NULL; //huh? not a valid location.
|
|
|
|
ret = strchr(loc->search->purepath, '/');
|
|
if (!ret)
|
|
return NULL;
|
|
ret++;
|
|
if (strchr(ret, '/'))
|
|
return NULL;
|
|
return ret;
|
|
}
|
|
|
|
|
|
char *FS_GetPackHashes(char *buffer, int buffersize, qboolean referencedonly)
|
|
{
|
|
searchpath_t *search;
|
|
buffersize--;
|
|
*buffer = 0;
|
|
|
|
if (com_purepaths)
|
|
{
|
|
for (search = com_purepaths ; search ; search = search->nextpure)
|
|
{
|
|
Q_strncatz(buffer, va("%i ", search->crc_check), buffersize);
|
|
}
|
|
return buffer;
|
|
}
|
|
else
|
|
{
|
|
for (search = com_searchpaths ; search ; search = search->next)
|
|
{
|
|
if (!search->crc_check && search->funcs->GeneratePureCRC)
|
|
search->crc_check = search->funcs->GeneratePureCRC(search->handle, 0, 0);
|
|
if (search->crc_check)
|
|
{
|
|
Q_strncatz(buffer, va("%i ", search->crc_check), buffersize);
|
|
}
|
|
}
|
|
return buffer;
|
|
}
|
|
}
|
|
char *FS_GetPackNames(char *buffer, int buffersize, qboolean referencedonly)
|
|
{
|
|
searchpath_t *search;
|
|
buffersize--;
|
|
*buffer = 0;
|
|
|
|
if (com_purepaths)
|
|
{
|
|
for (search = com_purepaths ; search ; search = search->nextpure)
|
|
{
|
|
Q_strncatz(buffer, va("%s ", search->purepath), buffersize);
|
|
}
|
|
return buffer;
|
|
}
|
|
else
|
|
{
|
|
for (search = com_searchpaths ; search ; search = search->next)
|
|
{
|
|
if (!search->crc_check && search->funcs->GeneratePureCRC)
|
|
search->crc_check = search->funcs->GeneratePureCRC(search->handle, 0, 0);
|
|
if (search->crc_check)
|
|
{
|
|
Q_strncatz(buffer, va("%s ", search->purepath), buffersize);
|
|
}
|
|
}
|
|
return buffer;
|
|
}
|
|
}
|
|
|
|
|
|
#if 0
|
|
int COM_FOpenLocationFILE(flocation_t *loc, FILE **file)
|
|
{
|
|
if (!*loc->rawname)
|
|
{
|
|
if (!loc->len)
|
|
{
|
|
*file = NULL;
|
|
return -1;
|
|
}
|
|
|
|
if (loc->search->funcs->ReadFile)
|
|
{//create a new, temp file, bung the contents of the compressed file into it, then continue.
|
|
char *buf;
|
|
FILE *f = tmpfile();
|
|
buf = BZ_Malloc(loc->len);
|
|
loc->search->funcs->ReadFile(loc->search->handle, loc, buf);
|
|
fwrite(buf, 1, loc->len, f);
|
|
BZ_Free(buf);
|
|
fseek(f, 0, SEEK_SET);
|
|
|
|
*file = f;
|
|
com_pathforfile = loc->search;
|
|
return loc->len;
|
|
}
|
|
return -1;
|
|
}
|
|
// Con_Printf("Opening %s\n", loc->rawname);
|
|
*file = fopen(loc->rawname, "rb");
|
|
if (!*file)
|
|
return -1;
|
|
fseek(*file, loc->offset, SEEK_SET);
|
|
com_pathforfile = loc->search;
|
|
return loc->len;
|
|
}
|
|
|
|
int COM_FOpenFile(char *filename, FILE **file)
|
|
{
|
|
flocation_t loc;
|
|
Con_Printf(CON_ERROR "COM_FOpenFile is obsolete\n");
|
|
FS_FLocateFile(filename, FSLFRT_LENGTH, &loc);
|
|
|
|
com_filesize = -1;
|
|
if (loc.search)
|
|
{
|
|
com_file_copyprotected = loc.search->copyprotected;
|
|
com_filesize = COM_FOpenLocationFILE(&loc, file);
|
|
}
|
|
else
|
|
*file = NULL;
|
|
return com_filesize;
|
|
}
|
|
/*
|
|
int COM_FOpenWriteFile(char *filename, FILE **file)
|
|
{
|
|
COM_CreatePath(filename);
|
|
*file = fopen(filename, "wb");
|
|
return !!*file;
|
|
}
|
|
*/
|
|
#endif
|
|
//int COM_FOpenFile (char *filename, FILE **file) {file_from_pak=0;return COM_FOpenFile2 (filename, file, false);} //FIXME: TEMPORARY
|
|
|
|
//outbuf might not be written into
|
|
static const char *FS_GetCleanPath(const char *pattern, char *outbuf, int outlen)
|
|
{
|
|
char *s;
|
|
|
|
if (strchr(pattern, '\\'))
|
|
{
|
|
Q_strncpyz(outbuf, pattern, outlen);
|
|
pattern = outbuf;
|
|
|
|
Con_Printf("Warning: \\ characters in filename %s\n", pattern);
|
|
while((s = strchr(pattern, '\\')))
|
|
{
|
|
*s = '/';
|
|
|
|
}
|
|
}
|
|
|
|
if (strstr(pattern, "//"))
|
|
{
|
|
//amiga uses // as equivelent to /../
|
|
|
|
Q_strncpyz(outbuf, pattern, outlen);
|
|
pattern = outbuf;
|
|
|
|
Con_Printf("Warning: // characters in filename %s\n", pattern);
|
|
while ((s=strstr(pattern, "//")))
|
|
{
|
|
s++;
|
|
while (*s)
|
|
{
|
|
*s = *(s+1);
|
|
s++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (strstr(pattern, ".."))
|
|
Con_Printf("Error: '..' characters in filename %s\n", pattern);
|
|
else if (pattern[0] == '/')
|
|
Con_Printf("Error: absolute path in filename %s\n", pattern);
|
|
else if (strstr(pattern, ":")) //win32 drive seperator (or mac path seperator, but / works there and they're used to it) (or amiga device separator)
|
|
Con_Printf("Error: absolute path in filename %s\n", pattern);
|
|
else
|
|
{
|
|
return pattern;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef AVAIL_ZLIB
|
|
typedef struct {
|
|
unsigned char ident1;
|
|
unsigned char ident2;
|
|
unsigned char cm;
|
|
unsigned char flags;
|
|
unsigned int mtime;
|
|
unsigned char xflags;
|
|
unsigned char os;
|
|
} gzheader_t;
|
|
#define sizeofgzheader_t 10
|
|
|
|
#define GZ_FTEXT 1
|
|
#define GZ_FHCRC 2
|
|
#define GZ_FEXTRA 4
|
|
#define GZ_FNAME 8
|
|
#define GZ_FCOMMENT 16
|
|
#define GZ_RESERVED (32|64|128)
|
|
|
|
#include <zlib.h>
|
|
|
|
vfsfile_t *FS_DecompressGZip(vfsfile_t *infile, gzheader_t *header)
|
|
{
|
|
char inchar;
|
|
unsigned short inshort;
|
|
vfsfile_t *temp;
|
|
|
|
if (header->flags & GZ_RESERVED)
|
|
{ //reserved bits should be 0
|
|
//this is probably static, so it's not a gz. doh.
|
|
VFS_SEEK(infile, 0);
|
|
return infile;
|
|
}
|
|
|
|
if (header->flags & GZ_FEXTRA)
|
|
{
|
|
VFS_READ(infile, &inshort, sizeof(inshort));
|
|
inshort = LittleShort(inshort);
|
|
VFS_SEEK(infile, VFS_TELL(infile) + inshort);
|
|
}
|
|
|
|
if (header->flags & GZ_FNAME)
|
|
{
|
|
Con_Printf("gzipped file name: ");
|
|
do {
|
|
if (VFS_READ(infile, &inchar, sizeof(inchar)) != 1)
|
|
break;
|
|
Con_Printf("%c", inchar);
|
|
} while(inchar);
|
|
Con_Printf("\n");
|
|
}
|
|
|
|
if (header->flags & GZ_FCOMMENT)
|
|
{
|
|
Con_Printf("gzipped file comment: ");
|
|
do {
|
|
if (VFS_READ(infile, &inchar, sizeof(inchar)) != 1)
|
|
break;
|
|
Con_Printf("%c", inchar);
|
|
} while(inchar);
|
|
Con_Printf("\n");
|
|
}
|
|
|
|
if (header->flags & GZ_FHCRC)
|
|
{
|
|
VFS_READ(infile, &inshort, sizeof(inshort));
|
|
}
|
|
|
|
|
|
|
|
temp = FS_OpenTemp();
|
|
if (!temp)
|
|
{
|
|
VFS_SEEK(infile, 0); //doh
|
|
return infile;
|
|
}
|
|
|
|
|
|
{
|
|
char inbuffer[16384];
|
|
char outbuffer[16384];
|
|
int ret;
|
|
|
|
z_stream strm = {
|
|
inbuffer,
|
|
0,
|
|
0,
|
|
|
|
outbuffer,
|
|
sizeof(outbuffer),
|
|
0,
|
|
|
|
NULL,
|
|
NULL,
|
|
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
|
|
Z_UNKNOWN,
|
|
0,
|
|
0
|
|
};
|
|
|
|
strm.avail_in = VFS_READ(infile, inbuffer, sizeof(inbuffer));
|
|
strm.next_in = inbuffer;
|
|
|
|
inflateInit2(&strm, -MAX_WBITS);
|
|
|
|
while ((ret=inflate(&strm, Z_SYNC_FLUSH)) != Z_STREAM_END)
|
|
{
|
|
if (strm.avail_in == 0 || strm.avail_out == 0)
|
|
{
|
|
if (strm.avail_in == 0)
|
|
{
|
|
strm.avail_in = VFS_READ(infile, inbuffer, sizeof(inbuffer));
|
|
strm.next_in = inbuffer;
|
|
}
|
|
|
|
if (strm.avail_out == 0)
|
|
{
|
|
strm.next_out = outbuffer;
|
|
VFS_WRITE(temp, outbuffer, strm.total_out);
|
|
strm.total_out = 0;
|
|
strm.avail_out = sizeof(outbuffer);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
//doh, it terminated for no reason
|
|
inflateEnd(&strm);
|
|
if (ret != Z_STREAM_END)
|
|
{
|
|
Con_Printf("Couldn't decompress gz file\n");
|
|
VFS_CLOSE(temp);
|
|
VFS_CLOSE(infile);
|
|
return NULL;
|
|
}
|
|
}
|
|
//we got to the end
|
|
VFS_WRITE(temp, outbuffer, strm.total_out);
|
|
|
|
inflateEnd(&strm);
|
|
|
|
VFS_SEEK(temp, 0);
|
|
}
|
|
VFS_CLOSE(infile);
|
|
|
|
return temp;
|
|
}
|
|
#endif
|
|
|
|
vfsfile_t *VFS_Filter(const char *filename, vfsfile_t *handle)
|
|
{
|
|
// char *ext;
|
|
|
|
if (!handle || handle->WriteBytes || handle->seekingisabadplan) //only on readonly files
|
|
return handle;
|
|
// ext = COM_FileExtension (filename);
|
|
#ifdef AVAIL_ZLIB
|
|
// if (!stricmp(ext, ".gz"))
|
|
{
|
|
gzheader_t gzh;
|
|
if (VFS_READ(handle, &gzh, sizeofgzheader_t) == sizeofgzheader_t)
|
|
{
|
|
if (gzh.ident1 == 0x1f && gzh.ident2 == 0x8b && gzh.cm == 8)
|
|
{ //it'll do
|
|
return FS_DecompressGZip(handle, &gzh);
|
|
}
|
|
}
|
|
VFS_SEEK(handle, 0);
|
|
}
|
|
#endif
|
|
return handle;
|
|
}
|
|
|
|
qboolean FS_NativePath(const char *fname, enum fs_relative relativeto, char *out, int outlen)
|
|
{
|
|
char cleanname[MAX_QPATH];
|
|
fname = FS_GetCleanPath(fname, cleanname, sizeof(cleanname));
|
|
if (!fname)
|
|
return false;
|
|
|
|
switch (relativeto)
|
|
{
|
|
case FS_GAMEONLY:
|
|
case FS_GAME:
|
|
if (*com_homedir)
|
|
snprintf(out, outlen, "%s%s/%s", com_homedir, gamedirfile, fname);
|
|
else
|
|
snprintf(out, outlen, "%s%s/%s", com_quakedir, gamedirfile, fname);
|
|
break;
|
|
case FS_SKINS:
|
|
if (*com_homedir)
|
|
snprintf(out, outlen, "%sqw/skins/%s", com_homedir, fname);
|
|
else
|
|
snprintf(out, outlen, "%sqw/skins/%s", com_quakedir, fname);
|
|
break;
|
|
case FS_ROOT:
|
|
if (*com_homedir)
|
|
snprintf(out, outlen, "%s%s", com_homedir, fname);
|
|
else
|
|
snprintf(out, outlen, "%s%s", com_quakedir, fname);
|
|
break;
|
|
case FS_CONFIGONLY:
|
|
if (*com_homedir)
|
|
snprintf(out, outlen, "%sfte/%s", com_homedir, fname);
|
|
else
|
|
snprintf(out, outlen, "%sfte/%s", com_quakedir, fname);
|
|
break;
|
|
default:
|
|
Sys_Error("FS_Rename case not handled\n");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
vfsfile_t *FS_OpenVFS(const char *filename, const char *mode, enum fs_relative relativeto)
|
|
{
|
|
char cleanname[MAX_QPATH];
|
|
char fullname[MAX_OSPATH];
|
|
flocation_t loc;
|
|
vfsfile_t *vfs;
|
|
|
|
//eventually, this function will be the *ONLY* way to get at files
|
|
|
|
//blanket-bans
|
|
|
|
|
|
filename = FS_GetCleanPath(filename, cleanname, sizeof(cleanname));
|
|
if (!filename)
|
|
return NULL;
|
|
|
|
|
|
if (strcmp(mode, "rb"))
|
|
if (strcmp(mode, "wb"))
|
|
if (strcmp(mode, "ab"))
|
|
return NULL; //urm, unable to write/append
|
|
|
|
//if there can only be one file (eg: write access) find out where it is.
|
|
switch (relativeto)
|
|
{
|
|
case FS_GAMEONLY: //OS access only, no paks
|
|
if (*com_homedir)
|
|
{
|
|
snprintf(fullname, sizeof(fullname), "%s%s/%s", com_homedir, gamedirfile, filename);
|
|
vfs = VFSOS_Open(fullname, mode);
|
|
if (vfs)
|
|
return vfs;
|
|
}
|
|
snprintf(fullname, sizeof(fullname), "%s%s/%s", com_quakedir, gamedirfile, filename);
|
|
return VFSOS_Open(fullname, mode);
|
|
case FS_GAME:
|
|
if (*com_homedir)
|
|
snprintf(fullname, sizeof(fullname), "%s%s/%s", com_homedir, gamedirfile, filename);
|
|
else
|
|
snprintf(fullname, sizeof(fullname), "%s%s/%s", com_quakedir, gamedirfile, filename);
|
|
break;
|
|
case FS_SKINS:
|
|
if (*com_homedir)
|
|
snprintf(fullname, sizeof(fullname), "%sqw/skins/%s", com_homedir, filename);
|
|
else
|
|
snprintf(fullname, sizeof(fullname), "%sqw/skins/%s", com_quakedir, filename);
|
|
break;
|
|
case FS_ROOT:
|
|
if (*com_homedir)
|
|
{
|
|
snprintf(fullname, sizeof(fullname), "%s%s", com_homedir, filename);
|
|
vfs = VFSOS_Open(fullname, mode);
|
|
if (vfs)
|
|
return vfs;
|
|
}
|
|
snprintf(fullname, sizeof(fullname), "%s%s", com_quakedir, filename);
|
|
return VFSOS_Open(fullname, mode);
|
|
case FS_CONFIGONLY:
|
|
if (*com_homedir)
|
|
{
|
|
snprintf(fullname, sizeof(fullname), "%sfte/%s", com_homedir, filename);
|
|
vfs = VFSOS_Open(fullname, mode);
|
|
if (vfs)
|
|
return vfs;
|
|
}
|
|
snprintf(fullname, sizeof(fullname), "%sfte/%s", com_quakedir, filename);
|
|
return VFSOS_Open(fullname, mode);
|
|
default:
|
|
Sys_Error("FS_OpenVFS: Bad relative path (%i)", relativeto);
|
|
break;
|
|
}
|
|
|
|
FS_FLocateFile(filename, FSLFRT_IFFOUND, &loc);
|
|
|
|
if (loc.search)
|
|
{
|
|
com_file_copyprotected = loc.search->copyprotected;
|
|
return VFS_Filter(filename, loc.search->funcs->OpenVFS(loc.search->handle, &loc, mode));
|
|
}
|
|
|
|
//if we're meant to be writing, best write to it.
|
|
if (strchr(mode , 'w') || strchr(mode , 'a'))
|
|
return VFSOS_Open(fullname, mode);
|
|
return NULL;
|
|
}
|
|
|
|
vfsfile_t *FS_OpenReadLocation(flocation_t *location)
|
|
{
|
|
if (location->search)
|
|
{
|
|
com_file_copyprotected = location->search->copyprotected;
|
|
return VFS_Filter(NULL, location->search->funcs->OpenVFS(location->search->handle, location, "rb"));
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int FS_Rename2(const char *oldf, const char *newf, enum fs_relative oldrelativeto, enum fs_relative newrelativeto)
|
|
{
|
|
char oldfullname[MAX_OSPATH];
|
|
char newfullname[MAX_OSPATH];
|
|
|
|
if (!FS_NativePath(oldf, oldrelativeto, oldfullname, sizeof(oldfullname)))
|
|
return EPERM;
|
|
if (!FS_NativePath(newf, newrelativeto, newfullname, sizeof(newfullname)))
|
|
return EPERM;
|
|
|
|
FS_CreatePath(newf, newrelativeto);
|
|
return rename(oldfullname, newfullname);
|
|
}
|
|
int FS_Rename(const char *oldf, const char *newf, enum fs_relative relativeto)
|
|
{
|
|
return FS_Rename2(oldf, newf, relativeto, relativeto);
|
|
}
|
|
int FS_Remove(const char *fname, enum fs_relative relativeto)
|
|
{
|
|
char fullname[MAX_OSPATH];
|
|
|
|
if (!FS_NativePath(fname, relativeto, fullname, sizeof(fullname)))
|
|
return EPERM;
|
|
|
|
return Sys_remove (fullname);
|
|
}
|
|
//create a path for the given filename (dir-only must have trailing slash)
|
|
void FS_CreatePath(const char *pname, enum fs_relative relativeto)
|
|
{
|
|
char fullname[MAX_OSPATH];
|
|
if (!FS_NativePath(pname, relativeto, fullname, sizeof(fullname)))
|
|
return;
|
|
|
|
COM_CreatePath(fullname);
|
|
}
|
|
|
|
qboolean FS_WriteFile (const char *filename, const void *data, int len, enum fs_relative relativeto)
|
|
{
|
|
vfsfile_t *f;
|
|
FS_CreatePath(filename, relativeto);
|
|
f = FS_OpenVFS(filename, "wb", relativeto);
|
|
if (!f)
|
|
return false;
|
|
VFS_WRITE(f, data, len);
|
|
VFS_CLOSE(f);
|
|
|
|
return true;
|
|
}
|
|
|
|
qboolean FS_Copy(const char *source, const char *dest, enum fs_relative relativesource, enum fs_relative relativedest)
|
|
{
|
|
vfsfile_t *d, *s;
|
|
char buffer[8192*8];
|
|
int read;
|
|
qboolean result = false;
|
|
FS_CreatePath(dest, relativedest);
|
|
s = FS_OpenVFS(source, "rb", relativesource);
|
|
if (s)
|
|
{
|
|
d = FS_OpenVFS(dest, "wb", relativedest);
|
|
if (d)
|
|
{
|
|
result = true;
|
|
|
|
for (;;)
|
|
{
|
|
read = VFS_READ(s, buffer, sizeof(buffer));
|
|
if (read <= 0)
|
|
break;
|
|
if (VFS_WRITE(d, buffer, read) != read)
|
|
{
|
|
result = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
VFS_CLOSE(d);
|
|
|
|
if (!result)
|
|
FS_Remove(dest, relativedest);
|
|
}
|
|
VFS_CLOSE(s);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
static cache_user_t *loadcache;
|
|
static qbyte *loadbuf;
|
|
static int loadsize;
|
|
|
|
/*
|
|
============
|
|
COM_LoadFile
|
|
|
|
Filename are reletive to the quake directory.
|
|
Always appends a 0 qbyte to the loaded data.
|
|
============
|
|
*/
|
|
qbyte *COM_LoadFile (const char *path, int usehunk)
|
|
{
|
|
vfsfile_t *f;
|
|
qbyte *buf;
|
|
int len;
|
|
char base[MAX_OSPATH];
|
|
flocation_t loc;
|
|
FS_FLocateFile(path, FSLFRT_LENGTH, &loc);
|
|
|
|
if (!loc.search)
|
|
return NULL; //wasn't found
|
|
|
|
|
|
f = loc.search->funcs->OpenVFS(loc.search->handle, &loc, "rb");
|
|
if (!f)
|
|
return NULL;
|
|
|
|
com_filesize = len = VFS_GETLEN(f);
|
|
// extract the filename base name for hunk tag
|
|
COM_FileBase (path, base, sizeof(base));
|
|
|
|
if (usehunk == 0)
|
|
buf = (qbyte*)Z_Malloc (len+1);
|
|
else if (usehunk == 1)
|
|
buf = (qbyte*)Hunk_AllocName (len+1, base);
|
|
else if (usehunk == 2)
|
|
buf = (qbyte*)Hunk_TempAlloc (len+1);
|
|
else if (usehunk == 3)
|
|
buf = (qbyte*)Cache_Alloc (loadcache, len+1, base);
|
|
else if (usehunk == 4)
|
|
{
|
|
if (len+1 > loadsize)
|
|
buf = (qbyte*)Hunk_TempAlloc (len+1);
|
|
else
|
|
buf = loadbuf;
|
|
}
|
|
else if (usehunk == 5)
|
|
buf = (qbyte*)BZ_Malloc(len+1);
|
|
else if (usehunk == 6)
|
|
buf = (qbyte*)Hunk_TempAllocMore (len+1);
|
|
else
|
|
{
|
|
Sys_Error ("COM_LoadFile: bad usehunk");
|
|
buf = NULL;
|
|
}
|
|
|
|
if (!buf)
|
|
Sys_Error ("COM_LoadFile: not enough space for %s", path);
|
|
|
|
((qbyte *)buf)[len] = 0;
|
|
#ifndef SERVERONLY
|
|
if (qrenderer != QR_NONE)
|
|
if (Draw_BeginDisc)
|
|
Draw_BeginDisc ();
|
|
#endif
|
|
|
|
VFS_READ(f, buf, len);
|
|
VFS_CLOSE(f);
|
|
|
|
#ifndef SERVERONLY
|
|
if (qrenderer != QR_NONE)
|
|
if (Draw_EndDisc)
|
|
Draw_EndDisc ();
|
|
#endif
|
|
|
|
return buf;
|
|
}
|
|
|
|
qbyte *FS_LoadMallocFile (const char *path)
|
|
{
|
|
return COM_LoadFile (path, 5);
|
|
}
|
|
|
|
qbyte *COM_LoadHunkFile (const char *path)
|
|
{
|
|
return COM_LoadFile (path, 1);
|
|
}
|
|
|
|
qbyte *COM_LoadTempFile (const char *path)
|
|
{
|
|
return COM_LoadFile (path, 2);
|
|
}
|
|
qbyte *COM_LoadTempFile2 (const char *path)
|
|
{
|
|
return COM_LoadFile (path, 6);
|
|
}
|
|
|
|
void COM_LoadCacheFile (const char *path, struct cache_user_s *cu)
|
|
{
|
|
loadcache = cu;
|
|
COM_LoadFile (path, 3);
|
|
}
|
|
|
|
// uses temp hunk if larger than bufsize
|
|
qbyte *COM_LoadStackFile (const char *path, void *buffer, int bufsize)
|
|
{
|
|
qbyte *buf;
|
|
|
|
loadbuf = (qbyte *)buffer;
|
|
loadsize = bufsize;
|
|
buf = COM_LoadFile (path, 4);
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
/*warning: at some point I'll change this function to return only read-only buffers*/
|
|
int FS_LoadFile(char *name, void **file)
|
|
{
|
|
*file = FS_LoadMallocFile(name);
|
|
if (!*file)
|
|
return -1;
|
|
return com_filesize;
|
|
}
|
|
void FS_FreeFile(void *file)
|
|
{
|
|
BZ_Free(file);
|
|
}
|
|
|
|
|
|
|
|
void COM_EnumerateFiles (const char *match, int (*func)(const char *, int, void *), void *parm)
|
|
{
|
|
searchpath_t *search;
|
|
for (search = com_searchpaths; search ; search = search->next)
|
|
{
|
|
// is the element a pak file?
|
|
if (!search->funcs->EnumerateFiles(search->handle, match, func, parm))
|
|
break;
|
|
}
|
|
}
|
|
|
|
void COM_FlushTempoaryPacks(void)
|
|
{
|
|
searchpath_t *next;
|
|
while (com_searchpaths && com_searchpaths->istemporary)
|
|
{
|
|
com_searchpaths->funcs->ClosePath(com_searchpaths->handle);
|
|
next = com_searchpaths->next;
|
|
Z_Free (com_searchpaths);
|
|
com_searchpaths = next;
|
|
|
|
com_fschanged = true;
|
|
}
|
|
}
|
|
|
|
qboolean COM_LoadMapPackFile (const char *filename, int ofs)
|
|
{
|
|
return false;
|
|
/*
|
|
dpackheader_t header;
|
|
int i;
|
|
packfile_t *newfiles;
|
|
int numpackfiles;
|
|
pack_t *pack;
|
|
FILE *packhandle;
|
|
dpackfile_t info;
|
|
int fstart;
|
|
char *ballsup;
|
|
|
|
flocation_t loc;
|
|
|
|
FS_FLocateFile(filename, FSLFRT_LENGTH, &loc);
|
|
|
|
if (!loc.search)
|
|
{
|
|
Con_Printf("Couldn't refind file\n");
|
|
return false;
|
|
}
|
|
|
|
if (!*loc.rawname)
|
|
{
|
|
Con_Printf("File %s is compressed\n");
|
|
return false;
|
|
}
|
|
packhandle = fopen(loc.rawname, "rb");
|
|
if (!packhandle)
|
|
{
|
|
Con_Printf("Couldn't reopen file\n");
|
|
return false;
|
|
}
|
|
fseek(packhandle, loc.offset, SEEK_SET);
|
|
|
|
fstart = loc.offset;
|
|
fseek(packhandle, ofs+fstart, SEEK_SET);
|
|
|
|
fread (&header, 1, sizeof(header), packhandle);
|
|
if (header.id[0] != 'P' || header.id[1] != 'A'
|
|
|| header.id[2] != 'C' || header.id[3] != 'K')
|
|
{
|
|
return false;
|
|
}
|
|
header.dirofs = LittleLong (header.dirofs);
|
|
header.dirlen = LittleLong (header.dirlen);
|
|
|
|
numpackfiles = header.dirlen / sizeof(dpackfile_t);
|
|
|
|
newfiles = (packfile_t*)Z_Malloc (numpackfiles * sizeof(packfile_t));
|
|
|
|
fseek (packhandle, header.dirofs+fstart, SEEK_SET);
|
|
|
|
pack = (pack_t*)Z_Malloc (sizeof (pack_t));
|
|
|
|
// parse the directory
|
|
for (i=0 ; i<numpackfiles ; i++)
|
|
{
|
|
fread (&info, 1, sizeof(info), packhandle);
|
|
|
|
strcpy (newfiles[i].name, info.name);
|
|
Q_strlwr(newfiles[i].name);
|
|
while ((ballsup = strchr(newfiles[i].name, '\\')))
|
|
*ballsup = '/';
|
|
newfiles[i].filepos = LittleLong(info.filepos)+fstart;
|
|
newfiles[i].filelen = LittleLong(info.filelen);
|
|
}
|
|
|
|
strcpy (pack->filename, loc.rawname);
|
|
pack->handle = packhandle;
|
|
pack->numfiles = numpackfiles;
|
|
pack->files = newfiles;
|
|
|
|
Con_TPrintf (TL_ADDEDPACKFILE, filename, numpackfiles);
|
|
|
|
COM_AddPathHandle(&packfilefuncs, pack, true, true);
|
|
return true;
|
|
*/
|
|
}
|
|
|
|
static searchpath_t *FS_AddPathHandle(const char *purepath, const char *probablepath, const searchpathfuncs_t *funcs, void *handle, qboolean copyprotect, qboolean istemporary, qboolean isexplicit, unsigned int loadstuff);
|
|
|
|
typedef struct {
|
|
const searchpathfuncs_t *funcs;
|
|
searchpath_t *parentpath;
|
|
const char *parentdesc;
|
|
const char *puredesc;
|
|
} wildpaks_t;
|
|
|
|
static int FS_AddWildDataFiles (const char *descriptor, int size, void *vparam)
|
|
{
|
|
wildpaks_t *param = vparam;
|
|
vfsfile_t *vfs;
|
|
const searchpathfuncs_t *funcs = param->funcs;
|
|
searchpath_t *search;
|
|
void *pak;
|
|
char pakfile[MAX_OSPATH];
|
|
char purefile[MAX_OSPATH];
|
|
flocation_t loc;
|
|
|
|
sprintf (pakfile, "%s%s", param->parentdesc, descriptor);
|
|
|
|
for (search = com_searchpaths; search; search = search->next)
|
|
{
|
|
if (search->funcs != funcs)
|
|
continue;
|
|
if (!stricmp((char*)search->handle, pakfile)) //assumption: first member of structure is a char array
|
|
return true; //already loaded (base paths?)
|
|
}
|
|
|
|
search = param->parentpath;
|
|
|
|
if (!search->funcs->FindFile(search->handle, &loc, descriptor, NULL))
|
|
return true; //not found..
|
|
vfs = search->funcs->OpenVFS(search->handle, &loc, "rb");
|
|
pak = funcs->OpenNew (vfs, pakfile);
|
|
if (!pak)
|
|
return true;
|
|
|
|
sprintf (pakfile, "%s%s/", param->parentdesc, descriptor);
|
|
if (*param->puredesc)
|
|
snprintf (purefile, sizeof(purefile), "%s/%s", param->puredesc, descriptor);
|
|
else
|
|
Q_strncpyz(purefile, descriptor, sizeof(purefile));
|
|
FS_AddPathHandle(purefile, pakfile, funcs, pak, true, false, false, (unsigned int)-1);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static void FS_AddDataFiles(const char *purepath, const char *pathto, searchpath_t *search, const char *extension, searchpathfuncs_t *funcs)
|
|
{
|
|
//search is the parent
|
|
int i;
|
|
void *handle;
|
|
char pakfile[MAX_OSPATH];
|
|
char purefile[MAX_OSPATH];
|
|
vfsfile_t *vfs;
|
|
flocation_t loc;
|
|
wildpaks_t wp;
|
|
|
|
//first load all the numbered pak files
|
|
for (i=0 ; ; i++)
|
|
{
|
|
snprintf (pakfile, sizeof(pakfile), "pak%i.%s", i, extension);
|
|
if (!search->funcs->FindFile(search->handle, &loc, pakfile, NULL))
|
|
break; //not found..
|
|
snprintf (pakfile, sizeof(pakfile), "%spak%i.%s", pathto, i, extension);
|
|
vfs = search->funcs->OpenVFS(search->handle, &loc, "r");
|
|
if (!vfs)
|
|
break;
|
|
Con_Printf("Opened %s\n", pakfile);
|
|
handle = funcs->OpenNew (vfs, pakfile);
|
|
if (!handle)
|
|
break;
|
|
snprintf (pakfile, sizeof(pakfile), "%spak%i.%s/", pathto, i, extension);
|
|
snprintf (purefile, sizeof(pakfile), "%s/pak%i.%s", purepath, i, extension);
|
|
FS_AddPathHandle(purefile, pakfile, funcs, handle, true, false, false, (unsigned int)-1);
|
|
}
|
|
|
|
//now load the random ones
|
|
sprintf (pakfile, "*.%s", extension);
|
|
wp.funcs = funcs;
|
|
wp.parentdesc = pathto;
|
|
wp.parentpath = search;
|
|
wp.puredesc = purepath;
|
|
search->funcs->EnumerateFiles(search->handle, pakfile, FS_AddWildDataFiles, &wp);
|
|
}
|
|
|
|
static searchpath_t *FS_AddPathHandle(const char *purepath, const char *probablepath, const searchpathfuncs_t *funcs, void *handle, qboolean copyprotect, qboolean istemporary, qboolean isexplicit, unsigned int loadstuff)
|
|
{
|
|
unsigned int i;
|
|
|
|
searchpath_t *search;
|
|
|
|
if (!funcs)
|
|
{
|
|
Con_Printf("COM_AddPathHandle: %s format not supported in this build\n", probablepath);
|
|
return NULL;
|
|
}
|
|
|
|
search = (searchpath_t*)Z_Malloc (sizeof(searchpath_t));
|
|
search->copyprotected = copyprotect;
|
|
search->istemporary = istemporary;
|
|
search->isexplicit = isexplicit;
|
|
search->handle = handle;
|
|
search->funcs = funcs;
|
|
Q_strncpyz(search->purepath, purepath, sizeof(search->purepath));
|
|
|
|
search->next = com_searchpaths;
|
|
com_searchpaths = search;
|
|
|
|
com_fschanged = true;
|
|
|
|
|
|
for (i = 0; i < sizeof(searchpathformats)/sizeof(searchpathformats[0]); i++)
|
|
{
|
|
if (!searchpathformats[i].extension || !searchpathformats[i].funcs || !searchpathformats[i].funcs->OpenNew)
|
|
continue;
|
|
if (loadstuff & (1<<i))
|
|
{
|
|
FS_AddDataFiles(purepath, probablepath, search, searchpathformats[i].extension, searchpathformats[i].funcs);
|
|
}
|
|
}
|
|
|
|
return search;
|
|
}
|
|
|
|
void COM_RefreshFSCache_f(void)
|
|
{
|
|
com_fschanged=true;
|
|
}
|
|
|
|
void COM_FlushFSCache(void)
|
|
{
|
|
if (com_fs_cache.value != 2)
|
|
com_fschanged=true;
|
|
}
|
|
/*
|
|
================
|
|
FS_AddGameDirectory
|
|
|
|
Sets com_gamedir, adds the directory to the head of the path,
|
|
then loads and adds pak1.pak pak2.pak ...
|
|
================
|
|
*/
|
|
void FS_AddGameDirectory (const char *puredir, const char *dir, unsigned int loadstuff)
|
|
{
|
|
searchpath_t *search;
|
|
|
|
char *p;
|
|
|
|
if ((p = strrchr(dir, '/')) != NULL)
|
|
strcpy(gamedirfile, ++p);
|
|
else
|
|
strcpy(gamedirfile, dir);
|
|
|
|
for (search = com_searchpaths; search; search = search->next)
|
|
{
|
|
if (search->funcs != &osfilefuncs)
|
|
continue;
|
|
if (!stricmp(search->handle, dir))
|
|
return; //already loaded (base paths?)
|
|
}
|
|
|
|
//
|
|
// add the directory to the search path
|
|
//
|
|
|
|
p = Z_Malloc(strlen(dir)+1);
|
|
strcpy(p, dir);
|
|
FS_AddPathHandle((*dir?puredir:""), va("%s/", dir), &osfilefuncs, p, false, false, true, loadstuff);
|
|
}
|
|
|
|
char *COM_NextPath (char *prevpath)
|
|
{
|
|
searchpath_t *s;
|
|
char *prev;
|
|
|
|
prev = NULL;
|
|
for (s=com_searchpaths ; s ; s=s->next)
|
|
{
|
|
if (s->funcs != &osfilefuncs)
|
|
continue;
|
|
|
|
if (prevpath == prev)
|
|
return s->handle;
|
|
prev = s->handle;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#ifndef CLIENTONLY
|
|
char *COM_GetPathInfo (int i, int *crc)
|
|
{
|
|
//#ifdef WEBSERVER
|
|
// extern cvar_t httpserver;
|
|
//#endif
|
|
|
|
searchpath_t *s;
|
|
static char name[MAX_OSPATH];
|
|
// char adr[MAX_ADR_SIZE];
|
|
char *protocol;
|
|
|
|
for (s=com_searchpaths ; s ; s=s->next)
|
|
{
|
|
i--;
|
|
if (!i)
|
|
break;
|
|
}
|
|
if (i) //too high.
|
|
return NULL;
|
|
|
|
/*
|
|
#ifdef WEBSERVER
|
|
if (httpserver.value)
|
|
protocol = va("http://%s/", NET_AdrToString(adr, sizeof(adr), net_local_sv_ipadr));
|
|
else
|
|
#endif
|
|
*/
|
|
protocol = "qw://";
|
|
|
|
*crc = 0;//s->crc;
|
|
strcpy(name, "FIXME");
|
|
// Q_strncpyz(name, va("%s%s", protocol, COM_SkipPath(s->filename)), sizeof(name));
|
|
return name;
|
|
}
|
|
#endif
|
|
|
|
|
|
char *FS_GetGamedir(void)
|
|
{
|
|
return gamedirfile;
|
|
}
|
|
/*
|
|
================
|
|
COM_Gamedir
|
|
|
|
Sets the gamedir and path to a different directory.
|
|
================
|
|
*/
|
|
void COM_Gamedir (const char *dir)
|
|
{
|
|
searchpath_t *next;
|
|
int plen, dlen;
|
|
char *p;
|
|
qboolean isbase;
|
|
|
|
if (!*dir || !strcmp(dir, ".") || strstr(dir, "..") || strstr(dir, "/")
|
|
|| strstr(dir, "\\") || strstr(dir, ":") )
|
|
{
|
|
Con_TPrintf (TL_GAMEDIRAINTPATH);
|
|
return;
|
|
}
|
|
|
|
isbase = false;
|
|
dlen = strlen(dir);
|
|
for (next = com_searchpaths; next; next = next->next)
|
|
{
|
|
if (next == com_base_searchpaths)
|
|
isbase = true;
|
|
if (next->funcs == &osfilefuncs)
|
|
{
|
|
p = next->handle;
|
|
plen = strlen(p);
|
|
if (plen == dlen)
|
|
{
|
|
//no basedir, maybe
|
|
if (!strcmp(p, dir))
|
|
{
|
|
if (isbase && com_searchpaths == com_base_searchpaths)
|
|
{
|
|
Q_strncpyz (gamedirfile, dir, sizeof(gamedirfile));
|
|
return;
|
|
}
|
|
if (!isbase)
|
|
return;
|
|
break;
|
|
}
|
|
}
|
|
else if (plen > dlen)
|
|
{
|
|
if (*(p+plen-dlen-1) == '/')
|
|
{
|
|
if (!strcmp(p+plen-dlen, dir))
|
|
{
|
|
if (isbase && com_searchpaths == com_base_searchpaths)
|
|
{
|
|
Q_strncpyz (gamedirfile, dir, sizeof(gamedirfile));
|
|
return;
|
|
}
|
|
if (!isbase)
|
|
return;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
FS_ForceToPure(NULL, NULL, 0);
|
|
|
|
#ifndef SERVERONLY
|
|
Host_WriteConfiguration(); //before we change anything.
|
|
#endif
|
|
|
|
Q_strncpyz (gamedirfile, dir, sizeof(gamedirfile));
|
|
|
|
#ifndef CLIENTONLY
|
|
sv.gamedirchanged = true;
|
|
#endif
|
|
#ifndef SERVERONLY
|
|
cl.gamedirchanged = true;
|
|
#endif
|
|
|
|
FS_FlushFSHash();
|
|
|
|
//
|
|
// free up any current game dir info
|
|
//
|
|
while (com_searchpaths != com_base_searchpaths)
|
|
{
|
|
com_searchpaths->funcs->ClosePath(com_searchpaths->handle);
|
|
next = com_searchpaths->next;
|
|
Z_Free (com_searchpaths);
|
|
com_searchpaths = next;
|
|
}
|
|
|
|
com_fschanged = true;
|
|
|
|
//
|
|
// flush all data, so it will be forced to reload
|
|
//
|
|
Cache_Flush ();
|
|
|
|
if (strchr(dir, ';'))
|
|
{
|
|
//separate case because parsestringset splits by whitespace too
|
|
while (dir = COM_ParseStringSet(dir))
|
|
{
|
|
if (!strcmp(dir, ";"))
|
|
continue;
|
|
if (!*dir)
|
|
continue;
|
|
|
|
FS_AddGameDirectory(dir, va("%s%s", com_quakedir, com_token), ~0);
|
|
if (*com_homedir)
|
|
FS_AddGameDirectory(dir, va("%s%s", com_homedir, com_token), ~0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FS_AddGameDirectory(dir, va("%s%s", com_quakedir, dir), ~0);
|
|
if (*com_homedir)
|
|
FS_AddGameDirectory(dir, va("%s%s", com_homedir, dir), ~0);
|
|
}
|
|
|
|
|
|
#ifndef SERVERONLY
|
|
if (!isDedicated)
|
|
{
|
|
// if (qrenderer != QR_NONE) //only do this if we have already started the renderer
|
|
// Cbuf_InsertText("vid_restart\n", RESTRICT_LOCAL);
|
|
|
|
|
|
if (COM_FDepthFile("config.cfg", true) <= (*com_homedir?1:0))
|
|
{
|
|
Cbuf_InsertText("cl_warncmd 0\n"
|
|
"exec config.cfg\n"
|
|
"exec fte.cfg\n"
|
|
"exec frontend.cfg\n"
|
|
"cl_warncmd 1\n", RESTRICT_LOCAL, false);
|
|
}
|
|
}
|
|
|
|
{
|
|
extern void Shader_Init(void);
|
|
Shader_Init(); //FIXME!
|
|
}
|
|
|
|
COM_Effectinfo_Clear();
|
|
|
|
Validation_FlushFileList(); //prevent previous hacks from making a difference.
|
|
|
|
//FIXME: load new palette, if different cause a vid_restart.
|
|
|
|
#endif
|
|
}
|
|
|
|
#define NEXCFG "set sv_maxairspeed \"400\"\nset sv_mintic \"0.01\"\ncl_nolerp 0\n"
|
|
#define DMFCFG "set com_parseutf8 1\npm_airstep 1\n"
|
|
|
|
typedef struct {
|
|
const char *protocolname; //sent to the master server when this is the current gamemode.
|
|
const char *exename; //used if the exe name contains this
|
|
const char *argname; //used if this was used as a parameter.
|
|
const char *auniquefile; //used if this file is relative from the gamedir
|
|
|
|
const char *customexec;
|
|
|
|
const char *dir[4];
|
|
const char *poshname; //Full name for the game.
|
|
} gamemode_info_t;
|
|
const gamemode_info_t gamemode_info[] = {
|
|
//note that there is no basic 'fte' gamemode, this is because we aim for network compatability. Darkplaces-Quake is the closest we get.
|
|
//this is to avoid having too many gamemodes anyway.
|
|
|
|
//rogue/hipnotic have no special files - the detection conflicts and stops us from running regular quake
|
|
//protocol name(dpmaster) exename cmdline switch identifying file exec dir1 dir2 dir3 dir(fte) full name
|
|
{"Darkplaces-Quake", "darkplaces", "-quake", "id1/pak0.pak", NULL, {"id1", "qw", "fte"}, "Quake"},
|
|
{"Darkplaces-Hipnotic", "hipnotic", "-hipnotic", NULL, NULL, {"id1", "qw", "hipnotic", "fte"}, "Quake: Scourge of Armagon"},
|
|
{"Darkplaces-Rogue", "rogue", "-rogue", NULL, NULL, {"id1", "qw", "rogue", "fte"}, "Quake: Dissolution of Eternity"},
|
|
{"Nexuiz", "nexuiz", "-nexuiz", "nexuiz.exe", NEXCFG, {"data", "ftedata"}, "Nexuiz"},
|
|
{"DMF", "dmf", "-dmf", "base/src/progs.src",DMFCFG,{"base", }, "DMF"},
|
|
|
|
//supported commercial mods (some are currently only partially supported)
|
|
{"FTE-Hexen2", "hexen", "-hexen2", "data1/pak0.pak", NULL, {"data1", "fteh2"}, "Hexen II"},
|
|
{"FTE-Quake2", "q2", "-q2", "baseq2/pak0.pak", NULL, {"baseq2", "fteq2"}, "Quake II"},
|
|
{"FTE-Quake3", "q3", "-q3", "baseq3/pak0.pk3", NULL, {"baseq3", "fteq3"}, "Quake III Arena"},
|
|
|
|
{"FTE-JK2", "jk2", "-jk2", "base/assets0.pk3", NULL, {"base", "fte"}, "Jedi Knight II: Jedi Outcast"},
|
|
|
|
{"FTE-HalfLife", "hl", "-halflife", "valve/liblist.gam",NULL, {"valve", "ftehl"}, "Half-Life"},
|
|
|
|
{NULL}
|
|
};
|
|
|
|
//space-seperate pk3 names followed by space-seperated crcs
|
|
//note that we'll need to reorder and filter out files that don't match the crc.
|
|
void FS_ForceToPure(const char *str, const char *crcs, int seed)
|
|
{
|
|
//pure files are more important than non-pure.
|
|
|
|
searchpath_t *sp;
|
|
searchpath_t *lastpure = NULL;
|
|
int crc;
|
|
|
|
if (!str)
|
|
{ //pure isn't in use.
|
|
if (com_purepaths)
|
|
Con_Printf("Pure FS deactivated\n");
|
|
com_purepaths = NULL;
|
|
FS_FlushFSHash();
|
|
return;
|
|
}
|
|
if (!com_purepaths)
|
|
Con_Printf("Pure FS activated\n");
|
|
|
|
for (sp = com_searchpaths; sp; sp = sp->next)
|
|
{
|
|
if (sp->funcs->GeneratePureCRC)
|
|
{
|
|
sp->nextpure = (void*)0x1;
|
|
sp->crc_check = sp->funcs->GeneratePureCRC(sp->handle, seed, 0);
|
|
sp->crc_reply = sp->funcs->GeneratePureCRC(sp->handle, seed, 1);
|
|
}
|
|
else
|
|
{
|
|
sp->crc_check = 0;
|
|
sp->crc_reply = 0;
|
|
}
|
|
}
|
|
|
|
while(crcs)
|
|
{
|
|
crcs = COM_Parse(crcs);
|
|
crc = atoi(com_token);
|
|
|
|
if (!crc)
|
|
continue;
|
|
|
|
for (sp = com_searchpaths; sp; sp = sp->next)
|
|
{
|
|
if (sp->nextpure == (void*)0x1) //don't add twice.
|
|
if (sp->crc_check == crc)
|
|
{
|
|
if (lastpure)
|
|
lastpure->nextpure = sp;
|
|
else
|
|
com_purepaths = sp;
|
|
sp->nextpure = NULL;
|
|
lastpure = sp;
|
|
break;
|
|
}
|
|
}
|
|
if (!sp)
|
|
Con_Printf("Pure crc %i wasn't found\n", crc);
|
|
}
|
|
|
|
/* don't add any extras.
|
|
for (sp = com_searchpaths; sp; sp = sp->next)
|
|
{
|
|
if (sp->nextpure == (void*)0x1)
|
|
{
|
|
if (lastpure)
|
|
lastpure->nextpure = sp;
|
|
sp->nextpure = NULL;
|
|
lastpure = sp;
|
|
}
|
|
}
|
|
*/
|
|
|
|
FS_FlushFSHash();
|
|
}
|
|
|
|
char *FSQ3_GenerateClientPacksList(char *buffer, int maxlen, int basechecksum)
|
|
{ //this is for q3 compatibility.
|
|
|
|
flocation_t loc;
|
|
int numpaks = 0;
|
|
searchpath_t *sp;
|
|
|
|
FS_FLocateFile("vm/cgame.qvm", FSLFRT_LENGTH, &loc);
|
|
Q_strncatz(buffer, va("%i ", loc.search->crc_reply), maxlen);
|
|
basechecksum ^= loc.search->crc_reply;
|
|
|
|
FS_FLocateFile("vm/ui.qvm", FSLFRT_LENGTH, &loc);
|
|
Q_strncatz(buffer, va("%i ", loc.search->crc_reply), maxlen);
|
|
basechecksum ^= loc.search->crc_reply;
|
|
|
|
Q_strncatz(buffer, "@ ", maxlen);
|
|
|
|
for (sp = com_purepaths; sp; sp = sp->nextpure)
|
|
{
|
|
if (sp->crc_reply)
|
|
{
|
|
Q_strncatz(buffer, va("%i ", sp->crc_reply), maxlen);
|
|
basechecksum ^= sp->crc_reply;
|
|
numpaks++;
|
|
}
|
|
}
|
|
|
|
basechecksum ^= numpaks;
|
|
Q_strncatz(buffer, va("%i ", basechecksum), maxlen);
|
|
|
|
return buffer;
|
|
}
|
|
|
|
/*
|
|
================
|
|
FS_ReloadPackFiles
|
|
================
|
|
|
|
Called when the client has downloaded a new pak/pk3 file
|
|
*/
|
|
void FS_ReloadPackFilesFlags(unsigned int reloadflags)
|
|
{
|
|
searchpath_t *oldpaths;
|
|
searchpath_t *oldbase;
|
|
searchpath_t *next;
|
|
|
|
|
|
//a lame way to fix pure paks
|
|
#ifndef SERVERONLY
|
|
if (cls.state && com_purepaths)
|
|
{
|
|
CL_Disconnect_f();
|
|
CL_Reconnect_f();
|
|
}
|
|
#endif
|
|
|
|
FS_FlushFSHash();
|
|
|
|
oldpaths = com_searchpaths;
|
|
com_searchpaths = NULL;
|
|
com_purepaths = NULL;
|
|
oldbase = com_base_searchpaths;
|
|
com_base_searchpaths = NULL;
|
|
|
|
//invert the order
|
|
next = NULL;
|
|
while(oldpaths)
|
|
{
|
|
oldpaths->nextpure = next;
|
|
next = oldpaths;
|
|
oldpaths = oldpaths->next;
|
|
}
|
|
oldpaths = next;
|
|
|
|
com_base_searchpaths = NULL;
|
|
|
|
while(oldpaths)
|
|
{
|
|
next = oldpaths->nextpure;
|
|
|
|
if (oldbase == oldpaths)
|
|
com_base_searchpaths = com_searchpaths;
|
|
|
|
if (oldpaths->funcs == &osfilefuncs)
|
|
FS_AddGameDirectory(oldpaths->purepath, oldpaths->handle, reloadflags);
|
|
|
|
oldpaths->funcs->ClosePath(oldpaths->handle);
|
|
Z_Free(oldpaths);
|
|
oldpaths = next;
|
|
}
|
|
|
|
if (!com_base_searchpaths)
|
|
com_base_searchpaths = com_searchpaths;
|
|
}
|
|
|
|
void FS_UnloadPackFiles(void)
|
|
{
|
|
FS_ReloadPackFilesFlags(1);
|
|
}
|
|
|
|
void FS_ReloadPackFiles(void)
|
|
{
|
|
FS_ReloadPackFilesFlags(~0);
|
|
}
|
|
|
|
void FS_ReloadPackFiles_f(void)
|
|
{
|
|
if (atoi(Cmd_Argv(1)))
|
|
FS_ReloadPackFilesFlags(atoi(Cmd_Argv(1)));
|
|
else
|
|
FS_ReloadPackFilesFlags(~0);
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#ifdef MINGW
|
|
#define byte BYTE //some versions of mingw headers are broken slightly. this lets it compile.
|
|
#endif
|
|
#include <shlobj.h>
|
|
qboolean Sys_FindGameData(const char *poshname, const char *gamename, char *basepath, int basepathlen)
|
|
{
|
|
DWORD resultlen;
|
|
HKEY key = NULL;
|
|
|
|
#ifndef INVALID_FILE_ATTRIBUTES
|
|
#define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
|
|
#endif
|
|
|
|
//first, try and find it in our game paths location
|
|
if (!FAILED(RegOpenKeyEx(HKEY_CURRENT_USER, "SOFTWARE\\" FULLENGINENAME "\\GamePaths", 0, STANDARD_RIGHTS_READ|KEY_QUERY_VALUE, &key)))
|
|
{
|
|
resultlen = basepathlen;
|
|
if (!RegQueryValueEx(key, gamename, NULL, NULL, basepath, &resultlen))
|
|
{
|
|
if (GetFileAttributes(basepath) != INVALID_FILE_ATTRIBUTES)
|
|
{
|
|
RegCloseKey(key);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
RegCloseKey(key);
|
|
}
|
|
|
|
|
|
if (!strcmp(gamename, "q1"))
|
|
{
|
|
//try and find it via steam
|
|
//reads HKEY_LOCAL_MACHINE\SOFTWARE\Valve\Steam\InstallPath
|
|
//append SteamApps\common\quake
|
|
//use it if we find winquake.exe there
|
|
FILE *f;
|
|
if (!FAILED(RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Valve\\Steam", 0, STANDARD_RIGHTS_READ|KEY_QUERY_VALUE, &key)))
|
|
{
|
|
resultlen = basepathlen;
|
|
RegQueryValueEx(key, "InstallPath", NULL, NULL, basepath, &resultlen);
|
|
RegCloseKey(key);
|
|
Q_strncatz(basepath, "/SteamApps/common/quake", basepathlen);
|
|
if (f = fopen(va("%s/Winquake.exe", basepath), "rb"))
|
|
{
|
|
fclose(f);
|
|
return true;
|
|
}
|
|
}
|
|
//well, okay, so they don't have quake installed from steam.
|
|
|
|
//quite a lot of people have it in c:\quake, as that's the default install location from the quake cd.
|
|
if (f = fopen("c:/quake/quake.exe", "rb"))
|
|
{
|
|
//HAHAHA! Found it!
|
|
fclose(f);
|
|
Q_strncpyz(basepath, "c:/quake", basepathlen);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!strcmp(gamename, "q2"))
|
|
{
|
|
//try and find it via steam
|
|
//reads HKEY_LOCAL_MACHINE\SOFTWARE\Valve\Steam\InstallPath
|
|
//append SteamApps\common\quake 2
|
|
//use it if we find quake2.exe there
|
|
FILE *f;
|
|
DWORD resultlen;
|
|
HKEY key = NULL;
|
|
if (!FAILED(RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Valve\\Steam", 0, STANDARD_RIGHTS_READ|KEY_QUERY_VALUE, &key)))
|
|
{
|
|
resultlen = basepathlen;
|
|
RegQueryValueEx(key, "InstallPath", NULL, NULL, basepath, &resultlen);
|
|
RegCloseKey(key);
|
|
Q_strncatz(basepath, "/SteamApps/common/quake 2", basepathlen);
|
|
if (f = fopen(va("%s/quake2.exe", basepath), "rb"))
|
|
{
|
|
fclose(f);
|
|
return true;
|
|
}
|
|
}
|
|
//well, okay, so they don't have quake2 installed from steam.
|
|
|
|
//look for HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\Quake2_exe\Path
|
|
if (!FAILED(RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Quake2_exe", 0, STANDARD_RIGHTS_READ|KEY_QUERY_VALUE, &key)))
|
|
{
|
|
resultlen = basepathlen;
|
|
RegQueryValueEx(key, "Path", NULL, NULL, basepath, &resultlen);
|
|
RegCloseKey(key);
|
|
if (f = fopen(va("%s/quake2.exe", basepath), "rb"))
|
|
{
|
|
fclose(f);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!strcmp(gamename, "q3"))
|
|
{
|
|
DWORD resultlen;
|
|
HKEY key = NULL;
|
|
//reads HKEY_LOCAL_MACHINE\SOFTWARE\id\Quake III Arena\InstallPath
|
|
if (!FAILED(RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\id\\Quake III Arena", 0, STANDARD_RIGHTS_READ|KEY_QUERY_VALUE, &key)))
|
|
{
|
|
resultlen = basepathlen;
|
|
RegQueryValueEx(key, "InstallPath", NULL, NULL, basepath, &resultlen);
|
|
RegCloseKey(key);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!strcmp(gamename, "wop"))
|
|
{
|
|
DWORD resultlen;
|
|
HKEY key = NULL;
|
|
//reads HKEY_LOCAL_MACHINE\SOFTWARE\World Of Padman\Path
|
|
if (!FAILED(RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\World Of Padman", 0, STANDARD_RIGHTS_READ|KEY_QUERY_VALUE, &key)))
|
|
{
|
|
resultlen = basepathlen;
|
|
RegQueryValueEx(key, "Path", NULL, NULL, basepath, &resultlen);
|
|
RegCloseKey(key);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
if (!strcmp(gamename, "d3"))
|
|
{
|
|
DWORD resultlen;
|
|
HKEY key = NULL;
|
|
//reads HKEY_LOCAL_MACHINE\SOFTWARE\id\Doom 3\InstallPath
|
|
if (!FAILED(RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\id\\Doom 3", 0, STANDARD_RIGHTS_READ|KEY_QUERY_VALUE, &key)))
|
|
{
|
|
resultlen = basepathlen;
|
|
RegQueryValueEx(key, "InstallPath", NULL, NULL, basepath, &resultlen);
|
|
RegCloseKey(key);
|
|
return true;
|
|
}
|
|
}
|
|
*/
|
|
|
|
if (!strcmp(gamename, "h2"))
|
|
{
|
|
//try and find it via steam
|
|
//reads HKEY_LOCAL_MACHINE\SOFTWARE\Valve\Steam\InstallPath
|
|
//append SteamApps\common\hexen 2
|
|
}
|
|
|
|
#if !defined(NPQTV) && !defined(SERVERONLY) //this is *really* unfortunate, but doing this crashes the browser
|
|
//I assume its because the client
|
|
|
|
if (poshname)
|
|
{
|
|
char resultpath[MAX_PATH];
|
|
BROWSEINFO bi;
|
|
LPITEMIDLIST il;
|
|
memset(&bi, 0, sizeof(bi));
|
|
|
|
#if defined(_SDL) && defined (WIN32) && defined (MINGW) // mingw32 sdl cross compiled binary, code completely untested, doesn't crash so good sign ~moodles
|
|
SDL_SysWMinfo wmInfo;
|
|
SDL_GetWMInfo(&wmInfo);
|
|
HWND sys_parentwindow = wmInfo.window;
|
|
#endif
|
|
|
|
if (sys_parentwindow)
|
|
bi.hwndOwner = sys_parentwindow; //note that this is usually still null
|
|
else
|
|
bi.hwndOwner = mainwindow; //note that this is usually still null
|
|
bi.pidlRoot = NULL;
|
|
bi.pszDisplayName = resultpath;
|
|
bi.lpszTitle = va("Please locate your existing %s installation", poshname);
|
|
bi.ulFlags = BIF_RETURNONLYFSDIRS;
|
|
bi.lpfn = NULL;
|
|
bi.lParam = 0;
|
|
bi.iImage = 0;
|
|
|
|
il = SHBrowseForFolder(&bi);
|
|
if (il)
|
|
{
|
|
SHGetPathFromIDList(il, resultpath);
|
|
CoTaskMemFree(il);
|
|
Q_strncpyz(basepath, resultpath, basepathlen-1);
|
|
|
|
//and save it into the windows registry
|
|
if (!FAILED(RegCreateKeyEx(HKEY_CURRENT_USER, "SOFTWARE\\" FULLENGINENAME "\\GamePaths",
|
|
0, NULL,
|
|
REG_OPTION_NON_VOLATILE,
|
|
KEY_WRITE,
|
|
NULL,
|
|
&key,
|
|
NULL)))
|
|
{
|
|
RegSetValueEx(key, gamename, 0, REG_SZ, basepath, strlen(basepath));
|
|
|
|
RegCloseKey(key);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
#else
|
|
qboolean Sys_FindGameData(const char *poshname, const char *gamename, char *basepath, int basepathlen)
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
void FS_Shutdown(void)
|
|
{
|
|
searchpath_t *next;
|
|
FS_FlushFSHash();
|
|
|
|
//
|
|
// free up any current game dir info
|
|
//
|
|
while (com_searchpaths)
|
|
{
|
|
com_searchpaths->funcs->ClosePath(com_searchpaths->handle);
|
|
next = com_searchpaths->next;
|
|
Z_Free (com_searchpaths);
|
|
com_searchpaths = next;
|
|
}
|
|
|
|
com_fschanged = true;
|
|
|
|
}
|
|
|
|
/*
|
|
================
|
|
COM_InitFilesystem
|
|
================
|
|
*/
|
|
void COM_InitFilesystem (void)
|
|
{
|
|
FILE *f;
|
|
int i;
|
|
|
|
char *ev;
|
|
qboolean usehome;
|
|
|
|
int gamenum=-1;
|
|
|
|
FS_RegisterDefaultFileSystems();
|
|
|
|
Cmd_AddCommand("fs_restart", FS_ReloadPackFiles_f);
|
|
|
|
//
|
|
// -basedir <path>
|
|
// Overrides the system supplied base directory (under id1)
|
|
//
|
|
i = COM_CheckParm ("-basedir");
|
|
if (i && i < com_argc-1)
|
|
strcpy (com_quakedir, com_argv[i+1]);
|
|
else
|
|
strcpy (com_quakedir, host_parms.basedir);
|
|
|
|
if (*com_quakedir)
|
|
{
|
|
if (com_quakedir[strlen(com_quakedir)-1] == '\\')
|
|
com_quakedir[strlen(com_quakedir)-1] = '/';
|
|
else if (com_quakedir[strlen(com_quakedir)-1] != '/')
|
|
{
|
|
com_quakedir[strlen(com_quakedir)+1] = '\0';
|
|
com_quakedir[strlen(com_quakedir)] = '/';
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Cvar_Register(&com_gamename, "evil hacks");
|
|
Cvar_Register(&com_modname, "evil hacks");
|
|
//identify the game from a telling file
|
|
for (i = 0; gamemode_info[i].argname; i++)
|
|
{
|
|
if (!gamemode_info[i].auniquefile)
|
|
continue; //no more
|
|
f = fopen(va("%s%s", com_quakedir, gamemode_info[i].auniquefile), "rb");
|
|
if (f)
|
|
{
|
|
fclose(f);
|
|
gamenum = i;
|
|
break;
|
|
}
|
|
}
|
|
//use the game based on an exe name over the filesystem one (could easily have multiple fs path matches).
|
|
for (i = 0; gamemode_info[i].argname; i++)
|
|
{
|
|
if (strstr(com_argv[0], gamemode_info[i].exename))
|
|
gamenum = i;
|
|
}
|
|
//use the game based on an parameter over all else.
|
|
for (i = 0; gamemode_info[i].argname; i++)
|
|
{
|
|
if (COM_CheckParm(gamemode_info[i].argname))
|
|
{
|
|
gamenum = i;
|
|
|
|
if (gamemode_info[gamenum].auniquefile)
|
|
{
|
|
f = fopen(va("%s%s", com_quakedir, gamemode_info[i].auniquefile), "rb");
|
|
if (f)
|
|
{
|
|
//we found it, its all okay
|
|
fclose(f);
|
|
break;
|
|
}
|
|
#ifdef _WIN32
|
|
if (Sys_FindGameData(gamemode_info[i].poshname, gamemode_info[i].exename, com_quakedir, sizeof(com_quakedir)))
|
|
{
|
|
if (com_quakedir[strlen(com_quakedir)-1] == '\\')
|
|
com_quakedir[strlen(com_quakedir)-1] = '/';
|
|
else if (com_quakedir[strlen(com_quakedir)-1] != '/')
|
|
{
|
|
com_quakedir[strlen(com_quakedir)+1] = '\0';
|
|
com_quakedir[strlen(com_quakedir)] = '/';
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
Con_Printf("Couldn't find the gamedata for this game mode!\n");
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//still failed? find quake and use that one by default
|
|
if (gamenum<0)
|
|
{
|
|
for (i = 0; gamemode_info[i].argname; i++)
|
|
{
|
|
if (!strcmp(gamemode_info[i].argname, "-quake"))
|
|
gamenum = i;
|
|
}
|
|
}
|
|
|
|
Cvar_Set(&com_gamename, gamemode_info[gamenum].protocolname);
|
|
|
|
if (gamemode_info[gamenum].customexec)
|
|
Cbuf_AddText(gamemode_info[gamenum].customexec, RESTRICT_LOCAL);
|
|
|
|
usehome = false;
|
|
|
|
#ifdef _WIN32
|
|
{ //win32 sucks.
|
|
HMODULE shfolder = LoadLibrary("shfolder.dll");
|
|
DWORD winver = (DWORD)LOBYTE(LOWORD(GetVersion()));
|
|
|
|
if (shfolder)
|
|
{
|
|
HRESULT (WINAPI *dSHGetFolderPath) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath);
|
|
dSHGetFolderPath = (void *)GetProcAddress(shfolder, "SHGetFolderPathA");
|
|
if (dSHGetFolderPath)
|
|
{
|
|
char folder[MAX_PATH];
|
|
// 0x5 == CSIDL_PERSONAL
|
|
if (dSHGetFolderPath(NULL, 0x5, NULL, 0, folder) == S_OK)
|
|
Q_snprintfz(com_homedir, sizeof(com_homedir), "%s/My Games/%s/", folder, FULLENGINENAME);
|
|
}
|
|
FreeLibrary(shfolder);
|
|
}
|
|
|
|
if (!*com_homedir)
|
|
{
|
|
ev = getenv("USERPROFILE");
|
|
if (ev)
|
|
Q_snprintfz(com_homedir, sizeof(com_homedir), "%s/My Documents/My Games/%s/", ev, FULLENGINENAME);
|
|
}
|
|
|
|
#ifdef NPQTV
|
|
if (!*com_homedir)
|
|
Q_snprintfz(com_homedir, sizeof(com_homedir), "/%s/", FULLENGINENAME);
|
|
//as a browser plugin, always use their home directory
|
|
usehome = true;
|
|
#else
|
|
if (winver >= 0x6) // Windows Vista and above
|
|
usehome = true; // always use home directory by default, as Vista+ mimics this behavior anyway
|
|
else if (winver >= 0x5) // Windows 2000/XP/2003
|
|
{
|
|
HMODULE advapi32;
|
|
advapi32 = LoadLibrary("advapi32.dll");
|
|
|
|
if (advapi32)
|
|
{
|
|
BOOL (WINAPI *dCheckTokenMembership) (HANDLE TokenHandle, PSID SidToCheck, PBOOL IsMember);
|
|
dCheckTokenMembership = (void *)GetProcAddress(advapi32, "CheckTokenMembership");
|
|
|
|
if (dCheckTokenMembership)
|
|
{
|
|
// on XP systems, only use a home directory by default if we're a limited user or if we're on a network
|
|
BOOL isadmin, isonnetwork;
|
|
SID_IDENTIFIER_AUTHORITY ntauth = SECURITY_NT_AUTHORITY;
|
|
PSID adminSID, networkSID;
|
|
|
|
isadmin = AllocateAndInitializeSid(&ntauth,
|
|
2,
|
|
SECURITY_BUILTIN_DOMAIN_RID,
|
|
DOMAIN_ALIAS_RID_ADMINS,
|
|
0, 0, 0, 0, 0, 0,
|
|
&adminSID);
|
|
|
|
// just checking the network rid should be close enough to matching domain logins
|
|
isonnetwork = AllocateAndInitializeSid(&ntauth,
|
|
1,
|
|
SECURITY_NETWORK_RID,
|
|
0, 0, 0, 0, 0, 0, 0,
|
|
&networkSID);
|
|
|
|
if (isadmin && !dCheckTokenMembership(0, adminSID, &isadmin))
|
|
isadmin = 0;
|
|
|
|
if (isonnetwork && !dCheckTokenMembership(0, networkSID, &isonnetwork))
|
|
isonnetwork = 0;
|
|
|
|
usehome = isonnetwork || !isadmin;
|
|
|
|
FreeSid(networkSID);
|
|
FreeSid(adminSID);
|
|
}
|
|
|
|
FreeLibrary(advapi32);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
#else
|
|
//yay for unix!.
|
|
ev = getenv("HOME");
|
|
if (ev && *ev)
|
|
{
|
|
if (ev[strlen(ev)-1] == '/')
|
|
Q_snprintfz(com_homedir, sizeof(com_homedir), "%s.fte/", ev);
|
|
else
|
|
Q_snprintfz(com_homedir, sizeof(com_homedir), "%s/.fte/", ev);
|
|
usehome = true; // always use home on unix unless told not to
|
|
}
|
|
else
|
|
*com_homedir = '\0';
|
|
#endif
|
|
|
|
if (!usehome && !COM_CheckParm("-usehome"))
|
|
*com_homedir = '\0';
|
|
|
|
if (COM_CheckParm("-nohome"))
|
|
*com_homedir = '\0';
|
|
|
|
if (*com_homedir)
|
|
Con_Printf("Using home directory \"%s\"\n", com_homedir);
|
|
|
|
//
|
|
// start up with id1 by default
|
|
//
|
|
i = COM_CheckParm ("-basegame");
|
|
if (i && i < com_argc-1)
|
|
{
|
|
do //use multiple -basegames
|
|
{
|
|
FS_AddGameDirectory (com_argv[i+1], va("%s%s", com_quakedir, com_argv[i+1]), ~0);
|
|
if (*com_homedir)
|
|
FS_AddGameDirectory (com_argv[i+1], va("%s%s", com_homedir, com_argv[i+1]), ~0);
|
|
|
|
i = COM_CheckNextParm ("-basegame", i);
|
|
}
|
|
while (i && i < com_argc-1);
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < sizeof(gamemode_info[gamenum].dir)/sizeof(gamemode_info[gamenum].dir[0]); i++)
|
|
{
|
|
if (gamemode_info[gamenum].dir[i])
|
|
{
|
|
FS_AddGameDirectory (gamemode_info[gamenum].dir[i], va("%s%s", com_quakedir, gamemode_info[gamenum].dir[i]), ~0);
|
|
if (*com_homedir)
|
|
FS_AddGameDirectory (gamemode_info[gamenum].dir[i], va("%s%s", com_homedir, gamemode_info[gamenum].dir[i]), ~0);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
i = COM_CheckParm ("-addbasegame");
|
|
while (i && i < com_argc-1) //use multiple -addbasegames (this is so the basic dirs don't die)
|
|
{
|
|
FS_AddGameDirectory (com_argv[i+1], va("%s%s", com_quakedir, com_argv[i+1]), ~0);
|
|
if (*com_homedir)
|
|
FS_AddGameDirectory (com_argv[i+1], va("%s%s", com_homedir, com_argv[i+1]), ~0);
|
|
|
|
i = COM_CheckNextParm ("-addbasegame", i);
|
|
}
|
|
|
|
|
|
// any set gamedirs will be freed up to here
|
|
com_base_searchpaths = com_searchpaths;
|
|
|
|
//-game specifies the mod gamedir to use in NQ
|
|
i = COM_CheckParm ("-game"); //effectivly replace with +gamedir x (But overridable)
|
|
if (i && i < com_argc-1)
|
|
{
|
|
COM_Gamedir(com_argv[i+1]);
|
|
}
|
|
|
|
//+gamedir specifies the mod gamedir to use in QW
|
|
//hack - we parse the commandline after the config so commandline always overrides
|
|
//but this means ktpro/server.cfg (for example) is not found
|
|
//so if they specify a gamedir on the commandline, let the default configs be loaded from that gamedir
|
|
//note that -game +gamedir will result in both being loaded. but hey, who cares
|
|
i = COM_CheckParm ("+gamedir"); //effectivly replace with +gamedir x (But overridable)
|
|
if (i && i < com_argc-1)
|
|
{
|
|
COM_Gamedir(com_argv[i+1]);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//this is at the bottom of the file to ensure these globals are not used elsewhere
|
|
extern searchpathfuncs_t packfilefuncs;
|
|
extern searchpathfuncs_t zipfilefuncs;
|
|
extern searchpathfuncs_t doomwadfilefuncs;
|
|
void FS_RegisterDefaultFileSystems(void)
|
|
{
|
|
FS_RegisterFileSystemType("pak", &packfilefuncs);
|
|
#ifdef AVAIL_ZLIB
|
|
FS_RegisterFileSystemType("pk3", &zipfilefuncs);
|
|
FS_RegisterFileSystemType("pk4", &zipfilefuncs);
|
|
#endif
|
|
#ifdef DOOMWADS
|
|
FS_RegisterFileSystemType("wad", &doomwadfilefuncs);
|
|
#endif
|
|
}
|