mirror of
https://github.com/yquake2/yquake2remaster.git
synced 2025-02-28 14:41:30 +00:00
There were two ways to implement this. One was to go with the stuff already included in minizip and one to implement our own wrapper around fopen(). This is the second options since @DanielGibson convinced me that it would be safer.
1654 lines
32 KiB
C
1654 lines
32 KiB
C
/*
|
|
* Copyright (C) 1997-2001 Id Software, Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or (at
|
|
* your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
*
|
|
* See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
|
* 02111-1307, USA.
|
|
*
|
|
* =======================================================================
|
|
*
|
|
* The Quake II file system, implements generic file system operations
|
|
* as well as the .pak file format and support for .pk3 files.
|
|
*
|
|
* =======================================================================
|
|
*/
|
|
|
|
#include "header/common.h"
|
|
#include "../common/header/glob.h"
|
|
|
|
#ifdef ZIP
|
|
#include "unzip/unzip.h"
|
|
#endif
|
|
|
|
#define MAX_HANDLES 512
|
|
#define MAX_PAKS 100
|
|
|
|
#ifdef SYSTEMWIDE
|
|
#ifndef SYSTEMDIR
|
|
#define SYSTEMDIR "/usr/share/games/quake2"
|
|
#endif
|
|
#endif
|
|
|
|
typedef struct
|
|
{
|
|
char name[MAX_QPATH];
|
|
fsMode_t mode;
|
|
FILE *file; /* Only one will be used. */
|
|
#ifdef ZIP
|
|
unzFile *zip; /* (file or zip) */
|
|
#endif
|
|
} fsHandle_t;
|
|
|
|
typedef struct fsLink_s
|
|
{
|
|
char *from;
|
|
char *to;
|
|
int length;
|
|
struct fsLink_s *next;
|
|
} fsLink_t;
|
|
|
|
typedef struct
|
|
{
|
|
char name[MAX_QPATH];
|
|
int size;
|
|
int offset; /* Ignored in PK3 files. */
|
|
} fsPackFile_t;
|
|
|
|
typedef struct
|
|
{
|
|
char name[MAX_OSPATH];
|
|
int numFiles;
|
|
FILE *pak;
|
|
#ifdef ZIP
|
|
unzFile *pk3;
|
|
#endif
|
|
fsPackFile_t *files;
|
|
} fsPack_t;
|
|
|
|
typedef struct fsSearchPath_s
|
|
{
|
|
char path[MAX_OSPATH]; /* Only one used. */
|
|
fsPack_t *pack; /* (path or pack) */
|
|
struct fsSearchPath_s *next;
|
|
} fsSearchPath_t;
|
|
|
|
typedef enum
|
|
{
|
|
PAK,
|
|
#ifdef ZIP
|
|
PK3
|
|
#endif
|
|
} fsPackFormat_t;
|
|
|
|
typedef struct
|
|
{
|
|
char *suffix;
|
|
fsPackFormat_t format;
|
|
} fsPackTypes_t;
|
|
|
|
fsHandle_t fs_handles[MAX_HANDLES];
|
|
fsLink_t *fs_links;
|
|
fsSearchPath_t *fs_searchPaths;
|
|
fsSearchPath_t *fs_baseSearchPaths;
|
|
|
|
/* Pack formats / suffixes. */
|
|
fsPackTypes_t fs_packtypes[] = {
|
|
{"pak", PAK},
|
|
#ifdef ZIP
|
|
{"pk2", PK3},
|
|
{"pk3", PK3},
|
|
{"zip", PK3}
|
|
#endif
|
|
};
|
|
|
|
char datadir[MAX_OSPATH];
|
|
char fs_gamedir[MAX_OSPATH];
|
|
qboolean file_from_pak;
|
|
|
|
cvar_t *fs_basedir;
|
|
cvar_t *fs_cddir;
|
|
cvar_t *fs_gamedirvar;
|
|
cvar_t *fs_debug;
|
|
|
|
fsHandle_t *FS_GetFileByHandle(fileHandle_t f);
|
|
|
|
// --------
|
|
|
|
// Raw search path, the actual search
|
|
// bath is build from this one.
|
|
typedef struct fsRawPath_s {
|
|
char path[MAX_OSPATH];
|
|
qboolean create;
|
|
struct fsRawPath_s *next;
|
|
} fsRawPath_t;
|
|
|
|
fsRawPath_t *fs_rawPath;
|
|
|
|
// --------
|
|
|
|
#ifdef ZIP
|
|
#if _WIN32
|
|
/*
|
|
* We need some trickery to make minizip Unicode compatible...
|
|
*/
|
|
|
|
#include <windows.h>
|
|
#include "unzip/ioapi.h"
|
|
|
|
zlib_filefunc_def zlib_file_api;
|
|
|
|
static voidpf ZCALLBACK fopen_file_func_utf(voidpf opaque, const char *filename, int mode)
|
|
{
|
|
FILE* file = NULL;
|
|
WCHAR *mode_fopen = NULL;
|
|
WCHAR wfilename[MAX_OSPATH];
|
|
|
|
if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ)
|
|
{
|
|
mode_fopen = L"rb";
|
|
}
|
|
else if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
|
|
{
|
|
mode_fopen = L"r+b";
|
|
}
|
|
else if (mode & ZLIB_FILEFUNC_MODE_CREATE)
|
|
{
|
|
mode_fopen = L"wb";
|
|
}
|
|
|
|
if (!((filename == NULL) || (mode_fopen == NULL)))
|
|
{
|
|
MultiByteToWideChar(CP_UTF8, 0, filename, -1, wfilename, sizeof(wfilename));
|
|
file = _wfopen((const wchar_t *) wfilename, mode_fopen);
|
|
}
|
|
|
|
return file;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
// --------
|
|
|
|
/*
|
|
* All of Quake's data access is through a hierchal file system, but the
|
|
* contents of the file system can be transparently merged from several
|
|
* sources.
|
|
*
|
|
* The "base directory" is the path to the directory holding the quake.exe and
|
|
* all game directories. The sys_* files pass this to host_init in
|
|
* quakeparms_t->basedir. This can be overridden with the "-basedir" command
|
|
* line parm to allow code debugging in a different directory. The base
|
|
* directory is only used during filesystem initialization.
|
|
*
|
|
* The "game directory" is the first tree on the search path and directory that
|
|
* all generated files (savegames, screenshots, demos, config files) will be
|
|
* saved to. This can be overridden with the "-game" command line parameter.
|
|
* The game directory can never be changed while quake is executing. This is
|
|
* a precacution against having a malicious server instruct clients to write
|
|
* files over areas they shouldn't.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* Returns the path up to, but not including the last '/'.
|
|
*/
|
|
void
|
|
Com_FilePath(const char *path, char *dst, int dstSize)
|
|
{
|
|
char *pos; /* Position of the last '/'. */
|
|
|
|
if ((pos = strrchr(path, '/')) != NULL)
|
|
{
|
|
pos--;
|
|
|
|
if ((pos - path) < dstSize)
|
|
{
|
|
memcpy(dst, path, pos - path);
|
|
dst[pos - path] = '\0';
|
|
}
|
|
else
|
|
{
|
|
Com_Printf("Com_FilePath: not enough space.\n");
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Q_strlcpy(dst, path, dstSize);
|
|
}
|
|
}
|
|
|
|
int
|
|
FS_FileLength(FILE *f)
|
|
{
|
|
int pos; /* Current position. */
|
|
int end; /* End of file. */
|
|
|
|
pos = ftell(f);
|
|
fseek(f, 0, SEEK_END);
|
|
end = ftell(f);
|
|
fseek(f, pos, SEEK_SET);
|
|
|
|
return end;
|
|
}
|
|
|
|
/*
|
|
* Creates any directories needed to store the given filename.
|
|
*/
|
|
void
|
|
FS_CreatePath(char *path)
|
|
{
|
|
char *cur; /* Current '/'. */
|
|
char *old; /* Old '/'. */
|
|
|
|
FS_DPrintf("FS_CreatePath(%s)\n", path);
|
|
|
|
if (strstr(path, "..") != NULL)
|
|
{
|
|
Com_Printf("WARNING: refusing to create relative path '%s'.\n", path);
|
|
return;
|
|
}
|
|
|
|
cur = old = path;
|
|
|
|
while (cur != NULL)
|
|
{
|
|
if ((cur - old) > 1)
|
|
{
|
|
*cur = '\0';
|
|
Sys_Mkdir(path);
|
|
*cur = '/';
|
|
}
|
|
|
|
old = cur;
|
|
cur = strchr(old + 1, '/');
|
|
}
|
|
}
|
|
|
|
void
|
|
FS_DPrintf(const char *format, ...)
|
|
{
|
|
char msg[1024];
|
|
va_list argPtr;
|
|
|
|
if (fs_debug->value != 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
va_start(argPtr, format);
|
|
vsnprintf(msg, sizeof(msg), format, argPtr);
|
|
va_end(argPtr);
|
|
|
|
Com_Printf("%s", msg);
|
|
}
|
|
|
|
char *
|
|
FS_Gamedir(void)
|
|
{
|
|
return fs_gamedir;
|
|
}
|
|
|
|
/*
|
|
* Finds a free fileHandle_t.
|
|
*/
|
|
fsHandle_t *
|
|
FS_HandleForFile(const char *path, fileHandle_t *f)
|
|
{
|
|
int i;
|
|
fsHandle_t *handle;
|
|
|
|
handle = fs_handles;
|
|
|
|
for (i = 0; i < MAX_HANDLES; i++, handle++)
|
|
{
|
|
if ((handle->file == NULL)
|
|
#ifdef ZIP
|
|
&& (handle->zip == NULL)
|
|
#endif
|
|
)
|
|
{
|
|
Q_strlcpy(handle->name, path, sizeof(handle->name));
|
|
*f = i + 1;
|
|
return handle;
|
|
}
|
|
}
|
|
|
|
/* Failed. */
|
|
Com_Error(ERR_DROP, "FS_HandleForFile: none free");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Returns a fsHandle_t * for the given fileHandle_t.
|
|
*/
|
|
fsHandle_t *
|
|
FS_GetFileByHandle(fileHandle_t f)
|
|
{
|
|
if ((f < 0) || (f > MAX_HANDLES))
|
|
{
|
|
Com_Error(ERR_DROP, "FS_GetFileByHandle: out of range");
|
|
}
|
|
|
|
if (f == 0)
|
|
{
|
|
f++;
|
|
}
|
|
|
|
return &fs_handles[f - 1];
|
|
}
|
|
|
|
/*
|
|
* Other dll's can't just call fclose() on files returned by FS_FOpenFile.
|
|
*/
|
|
void
|
|
FS_FCloseFile(fileHandle_t f)
|
|
{
|
|
fsHandle_t *handle;
|
|
|
|
handle = FS_GetFileByHandle(f);
|
|
|
|
if (handle->file)
|
|
{
|
|
fclose(handle->file);
|
|
}
|
|
#ifdef ZIP
|
|
else if (handle->zip)
|
|
{
|
|
unzCloseCurrentFile(handle->zip);
|
|
unzClose(handle->zip);
|
|
}
|
|
#endif
|
|
|
|
memset(handle, 0, sizeof(*handle));
|
|
}
|
|
|
|
/*
|
|
* Finds the file in the search path. Returns filesize and an open FILE *. Used
|
|
* for streaming data out of either a pak file or a seperate file.
|
|
*/
|
|
int
|
|
FS_FOpenFile(const char *name, fileHandle_t *f, qboolean gamedir_only)
|
|
{
|
|
char path[MAX_OSPATH];
|
|
fsHandle_t *handle;
|
|
fsPack_t *pack;
|
|
fsSearchPath_t *search;
|
|
int i;
|
|
|
|
file_from_pak = false;
|
|
handle = FS_HandleForFile(name, f);
|
|
Q_strlcpy(handle->name, name, sizeof(handle->name));
|
|
handle->mode = FS_READ;
|
|
|
|
/* Search through the path, one element at a time. */
|
|
for (search = fs_searchPaths; search; search = search->next)
|
|
{
|
|
if (gamedir_only)
|
|
{
|
|
if (strstr(search->path, FS_Gamedir()) == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Evil hack for maps.lst and players/
|
|
// TODO: A flag to ignore paks would be better
|
|
if ((strcmp(fs_gamedirvar->string, "") == 0) && search->pack) {
|
|
if ((strcmp(name, "maps.lst") == 0)|| (strncmp(name, "players/", 8) == 0)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Search inside a pack file. */
|
|
if (search->pack)
|
|
{
|
|
pack = search->pack;
|
|
|
|
for (i = 0; i < pack->numFiles; i++)
|
|
{
|
|
if (Q_stricmp(pack->files[i].name, handle->name) == 0)
|
|
{
|
|
/* Found it! */
|
|
if (fs_debug->value)
|
|
{
|
|
Com_Printf("FS_FOpenFile: '%s' (found in '%s').\n",
|
|
handle->name, pack->name);
|
|
}
|
|
|
|
if (pack->pak)
|
|
{
|
|
/* PAK */
|
|
file_from_pak = true;
|
|
handle->file = Q_fopen(pack->name, "rb");
|
|
|
|
if (handle->file)
|
|
{
|
|
fseek(handle->file, pack->files[i].offset, SEEK_SET);
|
|
return pack->files[i].size;
|
|
}
|
|
}
|
|
#ifdef ZIP
|
|
else if (pack->pk3)
|
|
{
|
|
/* PK3 */
|
|
file_from_pak = true;
|
|
|
|
#ifdef _WIN32
|
|
handle->zip = unzOpen2(pack->name, &zlib_file_api);
|
|
#else
|
|
handle->zip = unzOpen(pack->name);
|
|
#endif
|
|
|
|
if (handle->zip)
|
|
{
|
|
if (unzLocateFile(handle->zip, handle->name, 2) == UNZ_OK)
|
|
{
|
|
if (unzOpenCurrentFile(handle->zip) == UNZ_OK)
|
|
{
|
|
return pack->files[i].size;
|
|
}
|
|
}
|
|
|
|
unzClose(handle->zip);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
Com_Error(ERR_FATAL, "Couldn't reopen '%s'", pack->name);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Search in a directory tree. */
|
|
Com_sprintf(path, sizeof(path), "%s/%s", search->path, handle->name);
|
|
|
|
handle->file = Q_fopen(path, "rb");
|
|
|
|
if (!handle->file)
|
|
{
|
|
Q_strlwr(path);
|
|
handle->file = Q_fopen(path, "rb");
|
|
}
|
|
|
|
if (!handle->file)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (handle->file)
|
|
{
|
|
if (fs_debug->value)
|
|
{
|
|
Com_Printf("FS_FOpenFile: '%s' (found in '%s').\n",
|
|
handle->name, search->path);
|
|
}
|
|
|
|
return FS_FileLength(handle->file);
|
|
}
|
|
}
|
|
}
|
|
if (fs_debug->value)
|
|
{
|
|
Com_Printf("FS_FOpenFile: couldn't find '%s'.\n", handle->name);
|
|
}
|
|
|
|
/* Couldn't open, so free the handle. */
|
|
memset(handle, 0, sizeof(*handle));
|
|
*f = 0;
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Properly handles partial reads.
|
|
*/
|
|
int
|
|
FS_Read(void *buffer, int size, fileHandle_t f)
|
|
{
|
|
qboolean tried = false; /* Tried to read from a CD. */
|
|
byte *buf; /* Buffer. */
|
|
int r; /* Number of bytes read. */
|
|
int remaining; /* Remaining bytes. */
|
|
fsHandle_t *handle; /* File handle. */
|
|
|
|
handle = FS_GetFileByHandle(f);
|
|
|
|
/* Read. */
|
|
remaining = size;
|
|
buf = (byte *)buffer;
|
|
|
|
while (remaining)
|
|
{
|
|
if (handle->file)
|
|
{
|
|
r = fread(buf, 1, remaining, handle->file);
|
|
}
|
|
#ifdef ZIP
|
|
else if (handle->zip)
|
|
{
|
|
r = unzReadCurrentFile(handle->zip, buf, remaining);
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (r == 0)
|
|
{
|
|
if (!tried)
|
|
{
|
|
/* Might tried to read from a CD. */
|
|
tried = true;
|
|
}
|
|
else
|
|
{
|
|
/* Already tried once. */
|
|
Com_Error(ERR_FATAL,
|
|
va("FS_Read: 0 bytes read from '%s'", handle->name));
|
|
return size - remaining;
|
|
}
|
|
}
|
|
else if (r == -1)
|
|
{
|
|
Com_Error(ERR_FATAL,
|
|
"FS_Read: -1 bytes read from '%s'",
|
|
handle->name);
|
|
}
|
|
|
|
remaining -= r;
|
|
buf += r;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
/*
|
|
* Properly handles partial reads of size up to count times. No error if it
|
|
* can't read.
|
|
*/
|
|
int
|
|
FS_FRead(void *buffer, int size, int count, fileHandle_t f)
|
|
{
|
|
qboolean tried = false; /* Tried to read from a CD. */
|
|
byte *buf; /* Buffer. */
|
|
int loops; /* Loop indicator. */
|
|
int r; /* Number of bytes read. */
|
|
int remaining; /* Remaining bytes. */
|
|
fsHandle_t *handle; /* File handle. */
|
|
|
|
handle = FS_GetFileByHandle(f);
|
|
|
|
/* Read. */
|
|
loops = count;
|
|
buf = (byte *)buffer;
|
|
|
|
while (loops)
|
|
{
|
|
/* Read in chunks. */
|
|
remaining = size;
|
|
|
|
while (remaining)
|
|
{
|
|
if (handle->file)
|
|
{
|
|
r = fread(buf, 1, remaining, handle->file);
|
|
}
|
|
#ifdef ZIP
|
|
else if (handle->zip)
|
|
{
|
|
r = unzReadCurrentFile(handle->zip, buf, remaining);
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (r == 0)
|
|
{
|
|
if (!tried)
|
|
{
|
|
/* Might tried to read from a CD. */
|
|
tried = true;
|
|
}
|
|
else
|
|
{
|
|
/* Already tried once. */
|
|
return size - remaining;
|
|
}
|
|
}
|
|
else if (r == -1)
|
|
{
|
|
Com_Error(ERR_FATAL,
|
|
"FS_FRead: -1 bytes read from '%s'",
|
|
handle->name);
|
|
}
|
|
|
|
remaining -= r;
|
|
buf += r;
|
|
}
|
|
|
|
loops--;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
/*
|
|
* Filename are reletive to the quake search path. A null buffer will just
|
|
* return the file length without loading.
|
|
*/
|
|
int
|
|
FS_LoadFile(char *path, void **buffer)
|
|
{
|
|
byte *buf; /* Buffer. */
|
|
int size; /* File size. */
|
|
fileHandle_t f; /* File handle. */
|
|
|
|
buf = NULL;
|
|
size = FS_FOpenFile(path, &f, false);
|
|
|
|
if (size <= 0)
|
|
{
|
|
if (buffer)
|
|
{
|
|
*buffer = NULL;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
if (buffer == NULL)
|
|
{
|
|
FS_FCloseFile(f);
|
|
return size;
|
|
}
|
|
|
|
buf = Z_Malloc(size);
|
|
*buffer = buf;
|
|
|
|
FS_Read(buf, size, f);
|
|
FS_FCloseFile(f);
|
|
|
|
return size;
|
|
}
|
|
|
|
void
|
|
FS_FreeFile(void *buffer)
|
|
{
|
|
if (buffer == NULL)
|
|
{
|
|
FS_DPrintf("FS_FreeFile: NULL buffer.\n");
|
|
return;
|
|
}
|
|
|
|
Z_Free(buffer);
|
|
}
|
|
|
|
/*
|
|
* Takes an explicit (not game tree related) path to a pak file.
|
|
*
|
|
* Loads the header and directory, adding the files at the beginning of the
|
|
* list so they override previous pack files.
|
|
*/
|
|
fsPack_t *
|
|
FS_LoadPAK(const char *packPath)
|
|
{
|
|
int i; /* Loop counter. */
|
|
int numFiles; /* Number of files in PAK. */
|
|
FILE *handle; /* File handle. */
|
|
fsPackFile_t *files; /* List of files in PAK. */
|
|
fsPack_t *pack; /* PAK file. */
|
|
dpackheader_t header; /* PAK file header. */
|
|
dpackfile_t info[MAX_FILES_IN_PACK]; /* PAK info. */
|
|
|
|
handle = Q_fopen(packPath, "rb");
|
|
|
|
if (handle == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
fread(&header, 1, sizeof(dpackheader_t), handle);
|
|
|
|
if (LittleLong(header.ident) != IDPAKHEADER)
|
|
{
|
|
fclose(handle);
|
|
Com_Error(ERR_FATAL, "FS_LoadPAK: '%s' is not a pack file", packPath);
|
|
}
|
|
|
|
header.dirofs = LittleLong(header.dirofs);
|
|
header.dirlen = LittleLong(header.dirlen);
|
|
|
|
numFiles = header.dirlen / sizeof(dpackfile_t);
|
|
|
|
if ((numFiles > MAX_FILES_IN_PACK) || (numFiles == 0))
|
|
{
|
|
fclose(handle);
|
|
Com_Error(ERR_FATAL, "FS_LoadPAK: '%s' has %i files",
|
|
packPath, numFiles);
|
|
}
|
|
|
|
files = Z_Malloc(numFiles * sizeof(fsPackFile_t));
|
|
|
|
fseek(handle, header.dirofs, SEEK_SET);
|
|
fread(info, 1, header.dirlen, handle);
|
|
|
|
/* Parse the directory. */
|
|
for (i = 0; i < numFiles; i++)
|
|
{
|
|
Q_strlcpy(files[i].name, info[i].name, sizeof(files[i].name));
|
|
files[i].offset = LittleLong(info[i].filepos);
|
|
files[i].size = LittleLong(info[i].filelen);
|
|
}
|
|
|
|
pack = Z_Malloc(sizeof(fsPack_t));
|
|
Q_strlcpy(pack->name, packPath, sizeof(pack->name));
|
|
pack->pak = handle;
|
|
#ifdef ZIP
|
|
pack->pk3 = NULL;
|
|
#endif
|
|
pack->numFiles = numFiles;
|
|
pack->files = files;
|
|
|
|
Com_Printf("Added packfile '%s' (%i files).\n", pack->name, numFiles);
|
|
|
|
return pack;
|
|
}
|
|
|
|
#ifdef ZIP
|
|
/*
|
|
* Takes an explicit (not game tree related) path to a pack file.
|
|
*
|
|
* Loads the header and directory, adding the files at the beginning of the list
|
|
* so they override previous pack files.
|
|
*/
|
|
fsPack_t *
|
|
FS_LoadPK3(const char *packPath)
|
|
{
|
|
char fileName[MAX_QPATH]; /* File name. */
|
|
int i = 0; /* Loop counter. */
|
|
int numFiles; /* Number of files in PK3. */
|
|
int status; /* Error indicator. */
|
|
fsPackFile_t *files; /* List of files in PK3. */
|
|
fsPack_t *pack; /* PK3 file. */
|
|
unzFile *handle; /* Zip file handle. */
|
|
unz_file_info info; /* Zip file info. */
|
|
unz_global_info global; /* Zip file global info. */
|
|
|
|
#ifdef _WIN32
|
|
handle = unzOpen2(packPath, &zlib_file_api);
|
|
#else
|
|
handle = unzOpen(packPath);
|
|
#endif
|
|
|
|
if (handle == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if (unzGetGlobalInfo(handle, &global) != UNZ_OK)
|
|
{
|
|
unzClose(handle);
|
|
Com_Error(ERR_FATAL, "FS_LoadPK3: '%s' is not a pack file", packPath);
|
|
}
|
|
|
|
numFiles = global.number_entry;
|
|
|
|
if ((numFiles > MAX_FILES_IN_PACK) || (numFiles == 0))
|
|
{
|
|
unzClose(handle);
|
|
Com_Error(ERR_FATAL, "FS_LoadPK3: '%s' has %i files",
|
|
packPath, numFiles);
|
|
}
|
|
|
|
files = Z_Malloc(numFiles * sizeof(fsPackFile_t));
|
|
|
|
/* Parse the directory. */
|
|
status = unzGoToFirstFile(handle);
|
|
|
|
while (status == UNZ_OK)
|
|
{
|
|
fileName[0] = '\0';
|
|
unzGetCurrentFileInfo(handle, &info, fileName, MAX_QPATH,
|
|
NULL, 0, NULL, 0);
|
|
Q_strlcpy(files[i].name, fileName, sizeof(files[i].name));
|
|
files[i].offset = -1; /* Not used in ZIP files */
|
|
files[i].size = info.uncompressed_size;
|
|
i++;
|
|
status = unzGoToNextFile(handle);
|
|
}
|
|
|
|
pack = Z_Malloc(sizeof(fsPack_t));
|
|
Q_strlcpy(pack->name, packPath, sizeof(pack->name));
|
|
pack->pak = NULL;
|
|
pack->pk3 = handle;
|
|
pack->numFiles = numFiles;
|
|
pack->files = files;
|
|
|
|
Com_Printf("Added packfile '%s' (%i files).\n", pack->name, numFiles);
|
|
|
|
return pack;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Allows enumerating all of the directories in the search path.
|
|
*/
|
|
char *
|
|
FS_NextPath(char *prevPath)
|
|
{
|
|
char *prev;
|
|
fsSearchPath_t *search;
|
|
|
|
if (prevPath == NULL)
|
|
{
|
|
return fs_gamedir;
|
|
}
|
|
|
|
prev = fs_gamedir;
|
|
|
|
for (search = fs_searchPaths; search; search = search->next)
|
|
{
|
|
if (search->pack != NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (prevPath == prev)
|
|
{
|
|
return search->path;
|
|
}
|
|
|
|
prev = search->path;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
FS_Path_f(void)
|
|
{
|
|
int i;
|
|
int totalFiles = 0;
|
|
fsSearchPath_t *search;
|
|
fsHandle_t *handle;
|
|
fsLink_t *link;
|
|
|
|
Com_Printf("Current search path:\n");
|
|
|
|
for (search = fs_searchPaths; search; search = search->next)
|
|
{
|
|
if (search->pack != NULL)
|
|
{
|
|
Com_Printf("%s (%i files)\n", search->pack->name,
|
|
search->pack->numFiles);
|
|
totalFiles += search->pack->numFiles;
|
|
}
|
|
else
|
|
{
|
|
Com_Printf("%s\n", search->path);
|
|
}
|
|
}
|
|
|
|
Com_Printf("\n");
|
|
|
|
for (i = 0, handle = fs_handles; i < MAX_HANDLES; i++, handle++)
|
|
{
|
|
if ((handle->file != NULL)
|
|
#ifdef ZIP
|
|
|| (handle->zip != NULL)
|
|
#endif
|
|
)
|
|
{
|
|
Com_Printf("Handle %i: '%s'.\n", i + 1, handle->name);
|
|
}
|
|
}
|
|
|
|
for (i = 0, link = fs_links; link; i++, link = link->next)
|
|
{
|
|
Com_Printf("Link %i: '%s' -> '%s'.\n", i, link->from, link->to);
|
|
}
|
|
|
|
Com_Printf("----------------------\n");
|
|
#ifdef ZIP
|
|
Com_Printf("%i files in PAK/PK2/PK3/ZIP files.\n", totalFiles);
|
|
#else
|
|
Com_Printf("%i files in PAK/PK2 files.\n", totalFiles);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Creates a filelink_t.
|
|
*/
|
|
void
|
|
FS_Link_f(void)
|
|
{
|
|
fsLink_t *l, **prev;
|
|
|
|
if (Cmd_Argc() != 3)
|
|
{
|
|
Com_Printf("USAGE: link <from> <to>\n");
|
|
return;
|
|
}
|
|
|
|
/* See if the link already exists. */
|
|
prev = &fs_links;
|
|
|
|
for (l = fs_links; l != NULL; l = l->next)
|
|
{
|
|
if (strcmp(l->from, Cmd_Argv(1)) == 0)
|
|
{
|
|
Z_Free(l->to);
|
|
|
|
if (strlen(Cmd_Argv(2)) == 0)
|
|
{
|
|
/* Delete it. */
|
|
*prev = l->next;
|
|
Z_Free(l->from);
|
|
Z_Free(l);
|
|
return;
|
|
}
|
|
|
|
l->to = CopyString(Cmd_Argv(2));
|
|
return;
|
|
}
|
|
|
|
prev = &l->next;
|
|
}
|
|
|
|
/* Create a new link. */
|
|
l = Z_Malloc(sizeof(*l));
|
|
l->next = fs_links;
|
|
fs_links = l;
|
|
l->from = CopyString(Cmd_Argv(1));
|
|
l->length = strlen(l->from);
|
|
l->to = CopyString(Cmd_Argv(2));
|
|
}
|
|
|
|
/*
|
|
* Create a list of files that match a criteria.
|
|
*/
|
|
char **
|
|
FS_ListFiles(char *findname, int *numfiles,
|
|
unsigned musthave, unsigned canthave)
|
|
{
|
|
char **list; /* List of files. */
|
|
char *s; /* Next file in list. */
|
|
int nfiles; /* Number of files in list. */
|
|
|
|
/* Initialize variables. */
|
|
list = NULL;
|
|
nfiles = 0;
|
|
|
|
/* Count the number of matches. */
|
|
s = Sys_FindFirst(findname, musthave, canthave);
|
|
|
|
while (s != NULL)
|
|
{
|
|
if (s[strlen(s) - 1] != '.')
|
|
{
|
|
nfiles++;
|
|
}
|
|
|
|
s = Sys_FindNext(musthave, canthave);
|
|
}
|
|
|
|
Sys_FindClose();
|
|
|
|
/* Check if there are matches. */
|
|
if (nfiles == 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
nfiles++; /* Add space for a guard. */
|
|
*numfiles = nfiles;
|
|
|
|
/* Allocate the list. */
|
|
list = calloc(nfiles, sizeof(char *));
|
|
|
|
/* Fill the list. */
|
|
s = Sys_FindFirst(findname, musthave, canthave);
|
|
nfiles = 0;
|
|
|
|
while (s)
|
|
{
|
|
if (s[strlen(s) - 1] != '.')
|
|
{
|
|
list[nfiles] = strdup(s);
|
|
nfiles++;
|
|
}
|
|
|
|
s = Sys_FindNext(musthave, canthave);
|
|
}
|
|
|
|
Sys_FindClose();
|
|
|
|
return list;
|
|
}
|
|
|
|
/*
|
|
* Compare file attributes (musthave and canthave) in packed files. If
|
|
* "output" is not NULL, "size" is greater than zero and the file matches the
|
|
* attributes then a copy of the matching string will be placed there (with
|
|
* SFF_SUBDIR it changes).
|
|
*/
|
|
qboolean
|
|
ComparePackFiles(const char *findname, const char *name, unsigned musthave,
|
|
unsigned canthave, char *output, int size)
|
|
{
|
|
qboolean retval;
|
|
char *ptr;
|
|
char buffer[MAX_OSPATH];
|
|
|
|
Q_strlcpy(buffer, name, sizeof(buffer));
|
|
|
|
if ((canthave & SFF_SUBDIR) && (name[strlen(name) - 1] == '/'))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (musthave & SFF_SUBDIR)
|
|
{
|
|
if ((ptr = strrchr(buffer, '/')) != NULL)
|
|
{
|
|
*ptr = '\0';
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ((musthave & SFF_HIDDEN) || (canthave & SFF_HIDDEN))
|
|
{
|
|
if ((ptr = strrchr(buffer, '/')) == NULL)
|
|
{
|
|
ptr = buffer;
|
|
}
|
|
|
|
if (((musthave & SFF_HIDDEN) && (ptr[1] != '.')) ||
|
|
((canthave & SFF_HIDDEN) && (ptr[1] == '.')))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (canthave & SFF_RDONLY)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
retval = glob_match((char *)findname, buffer);
|
|
|
|
if (retval && (output != NULL))
|
|
{
|
|
Q_strlcpy(output, buffer, size);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* Create a list of files that match a criteria.
|
|
* Searchs are relative to the game directory and use all the search paths
|
|
* including .pak and .pk3 files.
|
|
*/
|
|
char **
|
|
FS_ListFiles2(char *findname, int *numfiles,
|
|
unsigned musthave, unsigned canthave)
|
|
{
|
|
fsSearchPath_t *search; /* Search path. */
|
|
int i, j; /* Loop counters. */
|
|
int nfiles; /* Number of files found. */
|
|
int tmpnfiles; /* Temp number of files. */
|
|
char **tmplist; /* Temporary list of files. */
|
|
char **list; /* List of files found. */
|
|
char path[MAX_OSPATH]; /* Temporary path. */
|
|
|
|
nfiles = 0;
|
|
list = malloc(sizeof(char *));
|
|
|
|
for (search = fs_searchPaths; search != NULL; search = search->next)
|
|
{
|
|
if (search->pack != NULL)
|
|
{
|
|
if (canthave & SFF_INPACK)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (i = 0, j = 0; i < search->pack->numFiles; i++)
|
|
{
|
|
if (ComparePackFiles(findname, search->pack->files[i].name,
|
|
musthave, canthave, NULL, 0))
|
|
{
|
|
j++;
|
|
}
|
|
}
|
|
|
|
if (j == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
nfiles += j;
|
|
list = realloc(list, nfiles * sizeof(char *));
|
|
|
|
for (i = 0, j = nfiles - j; i < search->pack->numFiles; i++)
|
|
{
|
|
if (ComparePackFiles(findname, search->pack->files[i].name,
|
|
musthave, canthave, path, sizeof(path)))
|
|
{
|
|
list[j++] = strdup(path);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (musthave & SFF_INPACK)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Com_sprintf(path, sizeof(path), "%s/%s", search->path, findname);
|
|
tmplist = FS_ListFiles(path, &tmpnfiles, musthave, canthave);
|
|
|
|
if (tmplist != NULL)
|
|
{
|
|
tmpnfiles--;
|
|
nfiles += tmpnfiles;
|
|
list = realloc(list, nfiles * sizeof(char *));
|
|
|
|
for (i = 0, j = nfiles - tmpnfiles; i < tmpnfiles; i++, j++)
|
|
{
|
|
list[j] = strdup(tmplist[i] + strlen(search->path) + 1);
|
|
}
|
|
|
|
FS_FreeList(tmplist, tmpnfiles + 1);
|
|
}
|
|
}
|
|
|
|
/* Delete duplicates. */
|
|
tmpnfiles = 0;
|
|
|
|
for (i = 0; i < nfiles; i++)
|
|
{
|
|
if (list[i] == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (j = i + 1; j < nfiles; j++)
|
|
{
|
|
if ((list[j] != NULL) &&
|
|
(strcmp(list[i], list[j]) == 0))
|
|
{
|
|
free(list[j]);
|
|
list[j] = NULL;
|
|
tmpnfiles++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tmpnfiles > 0)
|
|
{
|
|
nfiles -= tmpnfiles;
|
|
tmplist = malloc(nfiles * sizeof(char *));
|
|
|
|
for (i = 0, j = 0; i < nfiles + tmpnfiles; i++)
|
|
{
|
|
if (list[i] != NULL)
|
|
{
|
|
tmplist[j++] = list[i];
|
|
}
|
|
}
|
|
|
|
free(list);
|
|
list = tmplist;
|
|
}
|
|
|
|
/* Add a guard. */
|
|
if (nfiles > 0)
|
|
{
|
|
nfiles++;
|
|
list = realloc(list, nfiles * sizeof(char *));
|
|
list[nfiles - 1] = NULL;
|
|
}
|
|
|
|
else
|
|
{
|
|
free(list);
|
|
list = NULL;
|
|
}
|
|
|
|
*numfiles = nfiles;
|
|
|
|
return list;
|
|
}
|
|
|
|
/*
|
|
* Free list of files created by FS_ListFiles().
|
|
*/
|
|
void
|
|
FS_FreeList(char **list, int nfiles)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < nfiles - 1; i++)
|
|
{
|
|
free(list[i]);
|
|
}
|
|
|
|
free(list);
|
|
}
|
|
|
|
/*
|
|
* Directory listing.
|
|
*/
|
|
void
|
|
FS_Dir_f(void)
|
|
{
|
|
char **dirnames; /* File list. */
|
|
char findname[1024]; /* File search path and pattern. */
|
|
char *path = NULL; /* Search path. */
|
|
char wildcard[1024] = "*.*"; /* File pattern. */
|
|
int i; /* Loop counter. */
|
|
int ndirs; /* Number of files in list. */
|
|
|
|
/* Check for pattern in arguments. */
|
|
if (Cmd_Argc() != 1)
|
|
{
|
|
Q_strlcpy(wildcard, Cmd_Argv(1), sizeof(wildcard));
|
|
}
|
|
|
|
/* Scan search paths and list files. */
|
|
while ((path = FS_NextPath(path)) != NULL)
|
|
{
|
|
Com_sprintf(findname, sizeof(findname), "%s/%s", path, wildcard);
|
|
Com_Printf("Directory of '%s'.\n", findname);
|
|
Com_Printf("----\n");
|
|
|
|
if ((dirnames = FS_ListFiles(findname, &ndirs, 0, 0)) != 0)
|
|
{
|
|
for (i = 0; i < ndirs - 1; i++)
|
|
{
|
|
if (strrchr(dirnames[i], '/'))
|
|
{
|
|
Com_Printf("%s\n", strrchr(dirnames[i], '/') + 1);
|
|
}
|
|
else
|
|
{
|
|
Com_Printf("%s\n", dirnames[i]);
|
|
}
|
|
}
|
|
|
|
FS_FreeList(dirnames, ndirs);
|
|
}
|
|
|
|
Com_Printf("\n");
|
|
}
|
|
}
|
|
|
|
// --------
|
|
|
|
void
|
|
FS_AddDirToSearchPath(char *dir, qboolean create) {
|
|
char **list;
|
|
char path[MAX_OSPATH];
|
|
int i, j;
|
|
int nfiles;
|
|
fsPack_t *pack = NULL;
|
|
fsSearchPath_t *search;
|
|
size_t len = strlen(dir);
|
|
|
|
// The directory must not end with an /. It would
|
|
// f*ck up the logic in other parts of the game...
|
|
if (dir[len - 1] == '/')
|
|
{
|
|
dir[len - 1] = '\0';
|
|
}
|
|
else if (dir[len - 1] == '\\') {
|
|
dir[len - 1] = '\0';
|
|
}
|
|
|
|
// Set the current directory as game directory. This
|
|
// is somewhat fragile since the game directory MUST
|
|
// be the last directory added to the search path.
|
|
Q_strlcpy(fs_gamedir, dir, sizeof(fs_gamedir));
|
|
|
|
if (create) {
|
|
FS_CreatePath(fs_gamedir);
|
|
}
|
|
|
|
// Add the directory itself.
|
|
search = Z_Malloc(sizeof(fsSearchPath_t));
|
|
Q_strlcpy(search->path, dir, sizeof(search->path));
|
|
search->next = fs_searchPaths;
|
|
fs_searchPaths = search;
|
|
|
|
// We need to add numbered paks in te directory in
|
|
// sequence and all other paks after them. Otherwise
|
|
// the gamedata may break.
|
|
for (i = 0; i < sizeof(fs_packtypes) / sizeof(fs_packtypes[0]); i++) {
|
|
for (j = 0; j < MAX_PAKS; j++) {
|
|
Com_sprintf(path, sizeof(path), "%s/pak%d.%s", dir, j, fs_packtypes[i].suffix);
|
|
|
|
switch (fs_packtypes[i].format)
|
|
{
|
|
case PAK:
|
|
pack = FS_LoadPAK(path);
|
|
break;
|
|
#ifdef ZIP
|
|
case PK3:
|
|
pack = FS_LoadPK3(path);
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
if (pack == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
search = Z_Malloc(sizeof(fsSearchPath_t));
|
|
search->pack = pack;
|
|
search->next = fs_searchPaths;
|
|
fs_searchPaths = search;
|
|
}
|
|
}
|
|
|
|
// And as said above all other pak files.
|
|
for (i = 0; i < sizeof(fs_packtypes) / sizeof(fs_packtypes[0]); i++) {
|
|
Com_sprintf(path, sizeof(path), "%s/*.%s", dir, fs_packtypes[i].suffix);
|
|
|
|
// Nothing here, next pak type please.
|
|
if ((list = FS_ListFiles(path, &nfiles, 0, 0)) == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Com_sprintf(path, sizeof(path), "%s/pak*.%s", dir, fs_packtypes[i].suffix);
|
|
|
|
for (j = 0; j < nfiles - 1; j++)
|
|
{
|
|
// If the pak starts with the string 'pak' it's ignored.
|
|
// This is somewhat stupid, it would be better to ignore
|
|
// just pak%d...
|
|
if (glob_match(path, list[j]))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
switch (fs_packtypes[i].format)
|
|
{
|
|
case PAK:
|
|
pack = FS_LoadPAK(list[j]);
|
|
break;
|
|
#ifdef ZIP
|
|
case PK3:
|
|
pack = FS_LoadPK3(list[j]);
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
if (pack == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
search = Z_Malloc(sizeof(fsSearchPath_t));
|
|
search->pack = pack;
|
|
search->next = fs_searchPaths;
|
|
fs_searchPaths = search;
|
|
}
|
|
|
|
FS_FreeList(list, nfiles);
|
|
}
|
|
}
|
|
|
|
void FS_BuildGenericSearchPath(void) {
|
|
// We may not use the va() function from shared.c
|
|
// since it's buffersize is 1024 while most OS have
|
|
// a maximum path size of 4096...
|
|
char path[MAX_OSPATH];
|
|
|
|
fsRawPath_t *search = fs_rawPath;
|
|
|
|
while (search != NULL) {
|
|
Com_sprintf(path, sizeof(path), "%s/%s", search->path, BASEDIRNAME);
|
|
FS_AddDirToSearchPath(path, search->create);
|
|
|
|
search = search->next;
|
|
}
|
|
|
|
// Until here we've added the generic directories to the
|
|
// search path. Save the current head node so we can
|
|
// distinguish generic and specialized directories.
|
|
fs_baseSearchPaths = fs_searchPaths;
|
|
|
|
// We need to create the game directory.
|
|
Sys_Mkdir(fs_gamedir);
|
|
|
|
// We need to create the screenshot directory since the
|
|
// render dll doesn't link the filesystem stuff.
|
|
Com_sprintf(path, sizeof(path), "%s/scrnshot", fs_gamedir);
|
|
Sys_Mkdir(path);
|
|
}
|
|
|
|
void
|
|
FS_BuildGameSpecificSearchPath(char *dir)
|
|
{
|
|
// We may not use the va() function from shared.c
|
|
// since it's buffersize is 1024 while most OS have
|
|
// a maximum path size of 4096...
|
|
char path[MAX_OSPATH];
|
|
int i;
|
|
fsRawPath_t *search;
|
|
fsSearchPath_t *next;
|
|
|
|
// This is against PEBCAK. The user may give us paths like
|
|
// xatrix/ or even /home/stupid/quake2/xatrix.
|
|
if (!*dir || !strcmp(dir, ".") || strstr(dir, "..") || strstr(dir, "/") || strstr(dir, "\\"))
|
|
{
|
|
Com_Printf("Gamedir should be a single filename, not a path.\n");
|
|
return;
|
|
}
|
|
|
|
// BASEDIR is already added as a generic directory. Adding it
|
|
// again as a specialised directory breaks the logic in other
|
|
// parts of the code. This may happen if the user does something
|
|
// like ./quake2 +set game baseq2
|
|
if(!Q_stricmp(dir, BASEDIRNAME))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// We may already have specialised directories in our search
|
|
// path. This can happen if the server changes the mod. Let's
|
|
// remove them.
|
|
while (fs_searchPaths != fs_baseSearchPaths)
|
|
{
|
|
if (fs_searchPaths->pack)
|
|
{
|
|
if (fs_searchPaths->pack->pak)
|
|
{
|
|
fclose(fs_searchPaths->pack->pak);
|
|
}
|
|
|
|
#ifdef ZIP
|
|
if (fs_searchPaths->pack->pk3)
|
|
{
|
|
unzClose(fs_searchPaths->pack->pk3);
|
|
}
|
|
#endif
|
|
|
|
Z_Free(fs_searchPaths->pack->files);
|
|
Z_Free(fs_searchPaths->pack);
|
|
}
|
|
|
|
next = fs_searchPaths->next;
|
|
Z_Free(fs_searchPaths);
|
|
fs_searchPaths = next;
|
|
}
|
|
|
|
/* Close open files for game dir. */
|
|
for (i = 0; i < MAX_HANDLES; i++)
|
|
{
|
|
if (strstr(fs_handles[i].name, dir) &&
|
|
((fs_handles[i].file != NULL)
|
|
#ifdef ZIP
|
|
|| (fs_handles[i].zip != NULL)
|
|
#endif
|
|
))
|
|
{
|
|
FS_FCloseFile(i);
|
|
}
|
|
}
|
|
|
|
// Enforce a renderer and sound backend restart to
|
|
// purge all internal caches. This is rather hacky
|
|
// but Quake II doesn't have a better mechanism...
|
|
if ((dedicated != NULL) && (dedicated->value != 1))
|
|
{
|
|
Cbuf_AddText("vid_restart\nsnd_restart\n");
|
|
}
|
|
|
|
// The game was reset to baseq2. Nothing to do here.
|
|
if ((Q_stricmp(dir, BASEDIRNAME) == 0) || (*dir == 0)) {
|
|
Cvar_FullSet("gamedir", "", CVAR_SERVERINFO | CVAR_NOSET);
|
|
Cvar_FullSet("game", "", CVAR_LATCH | CVAR_SERVERINFO);
|
|
|
|
// fs_gamedir must be reset to the last
|
|
// dir of the generic search path.
|
|
Q_strlcpy(fs_gamedir, fs_baseSearchPaths->path, sizeof(fs_gamedir));
|
|
} else {
|
|
Cvar_FullSet("gamedir", dir, CVAR_SERVERINFO | CVAR_NOSET);
|
|
search = fs_rawPath;
|
|
|
|
while (search != NULL) {
|
|
Com_sprintf(path, sizeof(path), "%s/%s", search->path, dir);
|
|
FS_AddDirToSearchPath(path, search->create);
|
|
|
|
search = search->next;
|
|
}
|
|
}
|
|
|
|
// Create the game directory.
|
|
Sys_Mkdir(fs_gamedir);
|
|
|
|
// We need to create the screenshot directory since the
|
|
// render dll doesn't link the filesystem stuff.
|
|
Com_sprintf(path, sizeof(path), "%s/scrnshot", fs_gamedir);
|
|
Sys_Mkdir(path);
|
|
}
|
|
|
|
// --------
|
|
|
|
void FS_AddDirToRawPath (const char *dir, qboolean create) {
|
|
fsRawPath_t *search;
|
|
|
|
// Add the directory
|
|
search = Z_Malloc(sizeof(fsRawPath_t));
|
|
Q_strlcpy(search->path, dir, sizeof(search->path));
|
|
search->create = create;
|
|
search->next = fs_rawPath;
|
|
fs_rawPath = search;
|
|
}
|
|
|
|
|
|
void FS_BuildRawPath(void) {
|
|
// Add $HOME/.yq2 (MUST be the last dir!)
|
|
if (!is_portable) {
|
|
const char *homedir = Sys_GetHomeDir();
|
|
|
|
if (homedir != NULL) {
|
|
FS_AddDirToRawPath(homedir, true);
|
|
}
|
|
}
|
|
|
|
// Add $binarydir
|
|
const char *binarydir = Sys_GetBinaryDir();
|
|
|
|
if(binarydir[0] != '\0')
|
|
{
|
|
FS_AddDirToRawPath(binarydir, false);
|
|
}
|
|
|
|
// Add $basedir/
|
|
FS_AddDirToRawPath(datadir, false);
|
|
|
|
// Add SYSTEMDIR
|
|
#ifdef SYSTEMWIDE
|
|
FS_AddDirToRawPath(SYSTEMDIR, false);
|
|
#endif
|
|
|
|
// The CD must be the last directory of the path,
|
|
// otherwise we cannot be sure that the game won't
|
|
// stream the videos from the CD.
|
|
if (fs_cddir->string[0] != '\0') {
|
|
FS_AddDirToRawPath(fs_cddir->string, false);
|
|
}
|
|
}
|
|
|
|
// --------
|
|
|
|
void
|
|
FS_InitFilesystem(void)
|
|
{
|
|
// Register FS commands.
|
|
Cmd_AddCommand("path", FS_Path_f);
|
|
Cmd_AddCommand("link", FS_Link_f);
|
|
Cmd_AddCommand("dir", FS_Dir_f);
|
|
|
|
// Register cvars
|
|
fs_basedir = Cvar_Get("basedir", ".", CVAR_NOSET);
|
|
fs_cddir = Cvar_Get("cddir", "", CVAR_NOSET);
|
|
fs_gamedirvar = Cvar_Get("game", "", CVAR_LATCH | CVAR_SERVERINFO);
|
|
fs_debug = Cvar_Get("fs_debug", "0", 0);
|
|
|
|
// Deprecation warning, can be removed at a later time.
|
|
if (strcmp(fs_basedir->string, ".") != 0)
|
|
{
|
|
Com_Printf("+set basedir is deprecated, use -datadir instead\n");
|
|
strcpy(datadir, fs_basedir->string);
|
|
}
|
|
else if (strlen(datadir) == 0)
|
|
{
|
|
strcpy(datadir, ".");
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
// setup minizip for Unicode compatibility
|
|
fill_fopen_filefunc(&zlib_file_api);
|
|
zlib_file_api.zopen_file = fopen_file_func_utf;
|
|
#endif
|
|
|
|
// Build search path
|
|
FS_BuildRawPath();
|
|
FS_BuildGenericSearchPath();
|
|
|
|
if (fs_gamedirvar->string[0] != '\0')
|
|
{
|
|
FS_BuildGameSpecificSearchPath(fs_gamedirvar->string);
|
|
}
|
|
|
|
// Debug output
|
|
Com_Printf("Using '%s' for writing.\n", fs_gamedir);
|
|
}
|
|
|