Quake-III-Arena

Quake III Arena GPL Source Release
Log | Files | Refs

l_qfiles.c (17763B)


      1 /*
      2 ===========================================================================
      3 Copyright (C) 1999-2005 Id Software, Inc.
      4 
      5 This file is part of Quake III Arena source code.
      6 
      7 Quake III Arena source code is free software; you can redistribute it
      8 and/or modify it under the terms of the GNU General Public License as
      9 published by the Free Software Foundation; either version 2 of the License,
     10 or (at your option) any later version.
     11 
     12 Quake III Arena source code is distributed in the hope that it will be
     13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
     14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15 GNU General Public License for more details.
     16 
     17 You should have received a copy of the GNU General Public License
     18 along with Foobar; if not, write to the Free Software
     19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     20 ===========================================================================
     21 */
     22 
     23 #if defined(WIN32)|defined(_WIN32)
     24 #include <windows.h>
     25 #include <sys/types.h>
     26 #include <sys/stat.h>
     27 #else
     28 #include <glob.h>
     29 #include <sys/stat.h>
     30 #include <unistd.h>
     31 #endif
     32 
     33 #include "qbsp.h"
     34 
     35 //file extensions with their type
     36 typedef struct qfile_exttype_s
     37 {
     38 	char *extension;
     39 	int type;
     40 } qfile_exttyp_t;
     41 
     42 qfile_exttyp_t quakefiletypes[] =
     43 {
     44 	{QFILEEXT_UNKNOWN, QFILETYPE_UNKNOWN},
     45 	{QFILEEXT_PAK, QFILETYPE_PAK},
     46 	{QFILEEXT_PK3, QFILETYPE_PK3},
     47 	{QFILEEXT_SIN, QFILETYPE_PAK},
     48 	{QFILEEXT_BSP, QFILETYPE_BSP},
     49 	{QFILEEXT_MAP, QFILETYPE_MAP},
     50 	{QFILEEXT_MDL, QFILETYPE_MDL},
     51 	{QFILEEXT_MD2, QFILETYPE_MD2},
     52 	{QFILEEXT_MD3, QFILETYPE_MD3},
     53 	{QFILEEXT_WAL, QFILETYPE_WAL},
     54 	{QFILEEXT_WAV, QFILETYPE_WAV},
     55 	{QFILEEXT_AAS, QFILETYPE_AAS},
     56 	{NULL, 0}
     57 };
     58 
     59 //===========================================================================
     60 //
     61 // Parameter:			-
     62 // Returns:				-
     63 // Changes Globals:		-
     64 //===========================================================================
     65 int QuakeFileExtensionType(char *extension)
     66 {
     67 	int i;
     68 
     69 	for (i = 0; quakefiletypes[i].extension; i++)
     70 	{
     71 		if (!stricmp(extension, quakefiletypes[i].extension))
     72 		{
     73 			return quakefiletypes[i].type;
     74 		} //end if
     75 	} //end for
     76 	return QFILETYPE_UNKNOWN;
     77 } //end of the function QuakeFileExtensionType
     78 //===========================================================================
     79 //
     80 // Parameter:			-
     81 // Returns:				-
     82 // Changes Globals:		-
     83 //===========================================================================
     84 char *QuakeFileTypeExtension(int type)
     85 {
     86 	int i;
     87 
     88 	for (i = 0; quakefiletypes[i].extension; i++)
     89 	{
     90 		if (quakefiletypes[i].type == type)
     91 		{
     92 			return quakefiletypes[i].extension;
     93 		} //end if
     94 	} //end for
     95 	return QFILEEXT_UNKNOWN;
     96 } //end of the function QuakeFileExtension
     97 //===========================================================================
     98 //
     99 // Parameter:			-
    100 // Returns:				-
    101 // Changes Globals:		-
    102 //===========================================================================
    103 int QuakeFileType(char *filename)
    104 {
    105 	char ext[_MAX_PATH] = ".";
    106 
    107 	ExtractFileExtension(filename, ext+1);
    108 	return QuakeFileExtensionType(ext);
    109 } //end of the function QuakeFileTypeFromFileName
    110 //===========================================================================
    111 //
    112 // Parameter:				-
    113 // Returns:					-
    114 // Changes Globals:		-
    115 //===========================================================================
    116 char *StringContains(char *str1, char *str2, int casesensitive)
    117 {
    118 	int len, i, j;
    119 
    120 	len = strlen(str1) - strlen(str2);
    121 	for (i = 0; i <= len; i++, str1++)
    122 	{
    123 		for (j = 0; str2[j]; j++)
    124 		{
    125 			if (casesensitive)
    126 			{
    127 				if (str1[j] != str2[j]) break;
    128 			} //end if
    129 			else
    130 			{
    131 				if (toupper(str1[j]) != toupper(str2[j])) break;
    132 			} //end else
    133 		} //end for
    134 		if (!str2[j]) return str1;
    135 	} //end for
    136 	return NULL;
    137 } //end of the function StringContains
    138 //===========================================================================
    139 //
    140 // Parameter:			-
    141 // Returns:				-
    142 // Changes Globals:		-
    143 //===========================================================================
    144 int FileFilter(char *filter, char *filename, int casesensitive)
    145 {
    146 	char buf[1024];
    147 	char *ptr;
    148 	int i, found;
    149 
    150 	while(*filter)
    151 	{
    152 		if (*filter == '*')
    153 		{
    154 			filter++;
    155 			for (i = 0; *filter; i++)
    156 			{
    157 				if (*filter == '*' || *filter == '?') break;
    158 				buf[i] = *filter;
    159 				filter++;
    160 			} //end for
    161 			buf[i] = '\0';
    162 			if (strlen(buf))
    163 			{
    164 				ptr = StringContains(filename, buf, casesensitive);
    165 				if (!ptr) return false;
    166 				filename = ptr + strlen(buf);
    167 			} //end if
    168 		} //end if
    169 		else if (*filter == '?')
    170 		{
    171 			filter++;
    172 			filename++;
    173 		} //end else if
    174 		else if (*filter == '[' && *(filter+1) == '[')
    175 		{
    176 			filter++;
    177 		} //end if
    178 		else if (*filter == '[')
    179 		{
    180 			filter++;
    181 			found = false;
    182 			while(*filter && !found)
    183 			{
    184 				if (*filter == ']' && *(filter+1) != ']') break;
    185 				if (*(filter+1) == '-' && *(filter+2) && (*(filter+2) != ']' || *(filter+3) == ']'))
    186 				{
    187 					if (casesensitive)
    188 					{
    189 						if (*filename >= *filter && *filename <= *(filter+2)) found = true;
    190 					} //end if
    191 					else
    192 					{
    193 						if (toupper(*filename) >= toupper(*filter) &&
    194 							toupper(*filename) <= toupper(*(filter+2))) found = true;
    195 					} //end else
    196 					filter += 3;
    197 				} //end if
    198 				else
    199 				{
    200 					if (casesensitive)
    201 					{
    202 						if (*filter == *filename) found = true;
    203 					} //end if
    204 					else
    205 					{
    206 						if (toupper(*filter) == toupper(*filename)) found = true;
    207 					} //end else
    208 					filter++;
    209 				} //end else
    210 			} //end while
    211 			if (!found) return false;
    212 			while(*filter)
    213 			{
    214 				if (*filter == ']' && *(filter+1) != ']') break;
    215 				filter++;
    216 			} //end while
    217 			filter++;
    218 			filename++;
    219 		} //end else if
    220 		else
    221 		{
    222 			if (casesensitive)
    223 			{
    224 				if (*filter != *filename) return false;
    225 			} //end if
    226 			else
    227 			{
    228 				if (toupper(*filter) != toupper(*filename)) return false;
    229 			} //end else
    230 			filter++;
    231 			filename++;
    232 		} //end else
    233 	} //end while
    234 	return true;
    235 } //end of the function FileFilter
    236 //===========================================================================
    237 //
    238 // Parameter:			-
    239 // Returns:				-
    240 // Changes Globals:		-
    241 //===========================================================================
    242 quakefile_t *FindQuakeFilesInZip(char *zipfile, char *filter)
    243 {
    244 	unzFile			uf;
    245 	int				err;
    246 	unz_global_info gi;
    247 	char			filename_inzip[MAX_PATH];
    248 	unz_file_info	file_info;
    249 	int				i;
    250 	quakefile_t		*qfiles, *lastqf, *qf;
    251 
    252 	uf = unzOpen(zipfile);
    253 	err = unzGetGlobalInfo(uf, &gi);
    254 
    255 	if (err != UNZ_OK) return NULL;
    256 
    257 	unzGoToFirstFile(uf);
    258 
    259 	qfiles = NULL;
    260 	lastqf = NULL;
    261 	for (i = 0; i < gi.number_entry; i++)
    262 	{
    263 		err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL,0,NULL,0);
    264 		if (err != UNZ_OK) break;
    265 
    266 		ConvertPath(filename_inzip);
    267 		if (FileFilter(filter, filename_inzip, false))
    268 		{
    269 			qf = malloc(sizeof(quakefile_t));
    270 			if (!qf) Error("out of memory");
    271 			memset(qf, 0, sizeof(quakefile_t));
    272 			strcpy(qf->pakfile, zipfile);
    273 			strcpy(qf->filename, zipfile);
    274 			strcpy(qf->origname, filename_inzip);
    275 			qf->zipfile = true;
    276 			//memcpy( &buildBuffer[i].zipfileinfo, (unz_s*)uf, sizeof(unz_s));
    277 			memcpy(&qf->zipinfo, (unz_s*)uf, sizeof(unz_s));
    278 			qf->offset = 0;
    279 			qf->length = file_info.uncompressed_size;
    280 			qf->type = QuakeFileType(filename_inzip);
    281 			//add the file ot the list
    282 			qf->next = NULL;
    283 			if (lastqf) lastqf->next = qf;
    284 			else qfiles = qf;
    285 			lastqf = qf;
    286 		} //end if
    287 		unzGoToNextFile(uf);
    288 	} //end for
    289 
    290 	unzClose(uf);
    291 
    292 	return qfiles;
    293 } //end of the function FindQuakeFilesInZip
    294 //===========================================================================
    295 //
    296 // Parameter:			-
    297 // Returns:				-
    298 // Changes Globals:		-
    299 //===========================================================================
    300 quakefile_t *FindQuakeFilesInPak(char *pakfile, char *filter)
    301 {
    302 	FILE *fp;
    303 	dpackheader_t packheader;
    304 	dsinpackfile_t *packfiles;
    305 	dpackfile_t *idpackfiles;
    306 	quakefile_t *qfiles, *lastqf, *qf;
    307 	int numpackdirs, i;
    308 
    309 	qfiles = NULL;
    310 	lastqf = NULL;
    311 	//open the pak file
    312 	fp = fopen(pakfile, "rb");
    313 	if (!fp)
    314 	{
    315 		Warning("can't open pak file %s", pakfile);
    316 		return NULL;
    317 	} //end if
    318 	//read pak header, check for valid pak id and seek to the dir entries
    319 	if ((fread(&packheader, 1, sizeof(dpackheader_t), fp) != sizeof(dpackheader_t))
    320 		|| (packheader.ident != IDPAKHEADER && packheader.ident != SINPAKHEADER)
    321 		||	(fseek(fp, LittleLong(packheader.dirofs), SEEK_SET))
    322 		)
    323 	{
    324 		fclose(fp);
    325 		Warning("invalid pak file %s", pakfile);
    326 		return NULL;
    327 	} //end if
    328 	//if it is a pak file from id software
    329 	if (packheader.ident == IDPAKHEADER)
    330 	{
    331 		//number of dir entries in the pak file
    332 		numpackdirs = LittleLong(packheader.dirlen) / sizeof(dpackfile_t);
    333 		idpackfiles = (dpackfile_t *) malloc(numpackdirs * sizeof(dpackfile_t));
    334 		if (!idpackfiles) Error("out of memory");
    335 		//read the dir entry
    336 		if (fread(idpackfiles, sizeof(dpackfile_t), numpackdirs, fp) != numpackdirs)
    337 		{
    338 			fclose(fp);
    339 			free(idpackfiles);
    340 			Warning("can't read the Quake pak file dir entries from %s", pakfile);
    341 			return NULL;
    342 		} //end if
    343 		fclose(fp);
    344 		//convert to sin pack files
    345 		packfiles = (dsinpackfile_t *) malloc(numpackdirs * sizeof(dsinpackfile_t));
    346 		if (!packfiles) Error("out of memory");
    347 		for (i = 0; i < numpackdirs; i++)
    348 		{
    349 			strcpy(packfiles[i].name, idpackfiles[i].name);
    350 			packfiles[i].filepos = LittleLong(idpackfiles[i].filepos);
    351 			packfiles[i].filelen = LittleLong(idpackfiles[i].filelen);
    352 		} //end for
    353 		free(idpackfiles);
    354 	} //end if
    355 	else //its a Sin pack file
    356 	{
    357 		//number of dir entries in the pak file
    358 		numpackdirs = LittleLong(packheader.dirlen) / sizeof(dsinpackfile_t);
    359 		packfiles = (dsinpackfile_t *) malloc(numpackdirs * sizeof(dsinpackfile_t));
    360 		if (!packfiles) Error("out of memory");
    361 		//read the dir entry
    362 		if (fread(packfiles, sizeof(dsinpackfile_t), numpackdirs, fp) != numpackdirs)
    363 		{
    364 			fclose(fp);
    365 			free(packfiles);
    366 			Warning("can't read the Sin pak file dir entries from %s", pakfile);
    367 			return NULL;
    368 		} //end if
    369 		fclose(fp);
    370 		for (i = 0; i < numpackdirs; i++)
    371 		{
    372 			packfiles[i].filepos = LittleLong(packfiles[i].filepos);
    373 			packfiles[i].filelen = LittleLong(packfiles[i].filelen);
    374 		} //end for
    375 	} //end else
    376 	//
    377 	for (i = 0; i < numpackdirs; i++)
    378 	{
    379 		ConvertPath(packfiles[i].name);
    380 		if (FileFilter(filter, packfiles[i].name, false))
    381 		{
    382 			qf = malloc(sizeof(quakefile_t));
    383 			if (!qf) Error("out of memory");
    384 			memset(qf, 0, sizeof(quakefile_t));
    385 			strcpy(qf->pakfile, pakfile);
    386 			strcpy(qf->filename, pakfile);
    387 			strcpy(qf->origname, packfiles[i].name);
    388 			qf->zipfile = false;
    389 			qf->offset = packfiles[i].filepos;
    390 			qf->length = packfiles[i].filelen;
    391 			qf->type = QuakeFileType(packfiles[i].name);
    392 			//add the file ot the list
    393 			qf->next = NULL;
    394 			if (lastqf) lastqf->next = qf;
    395 			else qfiles = qf;
    396 			lastqf = qf;
    397 		} //end if
    398 	} //end for
    399 	free(packfiles);
    400 	return qfiles;
    401 } //end of the function FindQuakeFilesInPak
    402 //===========================================================================
    403 //
    404 // Parameter:			-
    405 // Returns:				-
    406 // Changes Globals:		-
    407 //===========================================================================
    408 quakefile_t *FindQuakeFilesWithPakFilter(char *pakfilter, char *filter)
    409 {
    410 #if defined(WIN32)|defined(_WIN32)
    411 	WIN32_FIND_DATA filedata;
    412 	HWND handle;
    413 	struct _stat statbuf;
    414 #else
    415 	glob_t globbuf;
    416 	struct stat statbuf;
    417 	int j;
    418 #endif
    419 	quakefile_t *qfiles, *lastqf, *qf;
    420 	char pakfile[_MAX_PATH], filename[_MAX_PATH], *str;
    421 	int done;
    422 
    423 	qfiles = NULL;
    424 	lastqf = NULL;
    425 	if (pakfilter && strlen(pakfilter))
    426 	{
    427 #if defined(WIN32)|defined(_WIN32)
    428 		handle = FindFirstFile(pakfilter, &filedata);
    429 		done = (handle == INVALID_HANDLE_VALUE);
    430 		while(!done)
    431 		{
    432 			_splitpath(pakfilter, pakfile, NULL, NULL, NULL);
    433 			_splitpath(pakfilter, NULL, &pakfile[strlen(pakfile)], NULL, NULL);
    434 			AppendPathSeperator(pakfile, _MAX_PATH);
    435 			strcat(pakfile, filedata.cFileName);
    436 			_stat(pakfile, &statbuf);
    437 #else
    438 		glob(pakfilter, 0, NULL, &globbuf);
    439 		for (j = 0; j < globbuf.gl_pathc; j++)
    440 		{
    441 			strcpy(pakfile, globbuf.gl_pathv[j]);
    442 			stat(pakfile, &statbuf);
    443 #endif
    444 			//if the file with .pak or .pk3 is a folder
    445 			if (statbuf.st_mode & S_IFDIR)
    446 			{
    447 				strcpy(filename, pakfilter);
    448 				AppendPathSeperator(filename, _MAX_PATH);
    449 				strcat(filename, filter);
    450 				qf = FindQuakeFilesWithPakFilter(NULL, filename);
    451 				if (lastqf) lastqf->next = qf;
    452 				else qfiles = qf;
    453 				lastqf = qf;
    454 				while(lastqf->next) lastqf = lastqf->next;
    455 			} //end if
    456 			else
    457 			{
    458 #if defined(WIN32)|defined(_WIN32)
    459 				str = StringContains(pakfile, ".pk3", false);
    460 #else
    461 				str = StringContains(pakfile, ".pk3", true);
    462 #endif
    463 				if (str && str == pakfile + strlen(pakfile) - strlen(".pk3"))
    464 				{
    465 					qf = FindQuakeFilesInZip(pakfile, filter);
    466 				} //end if
    467 				else
    468 				{
    469 					qf = FindQuakeFilesInPak(pakfile, filter);
    470 				} //end else
    471 				//
    472 				if (qf)
    473 				{
    474 					if (lastqf) lastqf->next = qf;
    475 					else qfiles = qf;
    476 					lastqf = qf;
    477 					while(lastqf->next) lastqf = lastqf->next;
    478 				} //end if
    479 			} //end else
    480 			//
    481 #if defined(WIN32)|defined(_WIN32)
    482 			//find the next file
    483 			done = !FindNextFile(handle, &filedata);
    484 		} //end while
    485 #else
    486 		} //end for
    487 		globfree(&globbuf);
    488 #endif
    489 	} //end if
    490 	else
    491 	{
    492 #if defined(WIN32)|defined(_WIN32)
    493 		handle = FindFirstFile(filter, &filedata);
    494 		done = (handle == INVALID_HANDLE_VALUE);
    495 		while(!done)
    496 		{
    497 			_splitpath(filter, filename, NULL, NULL, NULL);
    498 			_splitpath(filter, NULL, &filename[strlen(filename)], NULL, NULL);
    499 			AppendPathSeperator(filename, _MAX_PATH);
    500 			strcat(filename, filedata.cFileName);
    501 #else
    502 		glob(filter, 0, NULL, &globbuf);
    503 		for (j = 0; j < globbuf.gl_pathc; j++)
    504 		{
    505 			strcpy(filename, globbuf.gl_pathv[j]);
    506 #endif
    507 			//
    508 			qf = malloc(sizeof(quakefile_t));
    509 			if (!qf) Error("out of memory");
    510 			memset(qf, 0, sizeof(quakefile_t));
    511 			strcpy(qf->pakfile, "");
    512 			strcpy(qf->filename, filename);
    513 			strcpy(qf->origname, filename);
    514 			qf->offset = 0;
    515 			qf->length = 0;
    516 			qf->type = QuakeFileType(filename);
    517 			//add the file ot the list
    518 			qf->next = NULL;
    519 			if (lastqf) lastqf->next = qf;
    520 			else qfiles = qf;
    521 			lastqf = qf;
    522 #if defined(WIN32)|defined(_WIN32)
    523 			//find the next file
    524 			done = !FindNextFile(handle, &filedata);
    525 		} //end while
    526 #else
    527 		} //end for
    528 		globfree(&globbuf);
    529 #endif
    530 	} //end else
    531 	return qfiles;
    532 } //end of the function FindQuakeFilesWithPakFilter
    533 //===========================================================================
    534 //
    535 // Parameter:			-
    536 // Returns:				-
    537 // Changes Globals:		-
    538 //===========================================================================
    539 quakefile_t *FindQuakeFiles(char *filter)
    540 {
    541 	char *str;
    542 	char newfilter[_MAX_PATH];
    543 	char pakfilter[_MAX_PATH];
    544 	char filefilter[_MAX_PATH];
    545 
    546 	strcpy(newfilter, filter);
    547 	ConvertPath(newfilter);
    548 	strcpy(pakfilter, newfilter);
    549 
    550 	str = StringContains(pakfilter, ".pak", false);
    551 	if (!str) str = StringContains(pakfilter, ".pk3", false);
    552 
    553 	if (str)
    554 	{
    555 		str += strlen(".pak");
    556 		if (*str)
    557 		{
    558 			*str++ = '\0';
    559 			while(*str == '\\' || *str == '/') str++;
    560 			strcpy(filefilter, str);
    561 			return FindQuakeFilesWithPakFilter(pakfilter, filefilter);
    562 		} //end if
    563 	} //end else
    564 	return FindQuakeFilesWithPakFilter(NULL, newfilter);
    565 } //end of the function FindQuakeFiles
    566 //===========================================================================
    567 //
    568 // Parameter:			-
    569 // Returns:				-
    570 // Changes Globals:		-
    571 //===========================================================================
    572 int LoadQuakeFile(quakefile_t *qf, void **bufferptr)
    573 {
    574 	FILE *fp;
    575 	void *buffer;
    576 	int length;
    577 	unzFile zf;
    578 
    579 	if (qf->zipfile)
    580 	{
    581 		//open the zip file
    582 		zf = unzOpen(qf->pakfile);
    583 		//set the file pointer
    584 		qf->zipinfo.file = ((unz_s *) zf)->file;
    585 		//open the Quake file in the zip file
    586 		unzOpenCurrentFile(&qf->zipinfo);
    587 		//allocate memory for the buffer
    588 		length = qf->length;
    589 		buffer = GetMemory(length+1);
    590 		//read the Quake file from the zip file
    591 		length = unzReadCurrentFile(&qf->zipinfo, buffer, length);
    592 		//close the Quake file in the zip file
    593 		unzCloseCurrentFile(&qf->zipinfo);
    594 		//close the zip file
    595 		unzClose(zf);
    596 
    597 		*bufferptr = buffer;
    598 		return length;
    599 	} //end if
    600 	else
    601 	{
    602 		fp = SafeOpenRead(qf->filename);
    603 		if (qf->offset) fseek(fp, qf->offset, SEEK_SET);
    604 		length = qf->length;
    605 		if (!length) length = Q_filelength(fp);
    606 		buffer = GetMemory(length+1);
    607 		((char *)buffer)[length] = 0;
    608 		SafeRead(fp, buffer, length);
    609 		fclose(fp);
    610 
    611 		*bufferptr = buffer;
    612 		return length;
    613 	} //end else
    614 } //end of the function LoadQuakeFile
    615 //===========================================================================
    616 //
    617 // Parameter:			-
    618 // Returns:				-
    619 // Changes Globals:		-
    620 //===========================================================================
    621 int ReadQuakeFile(quakefile_t *qf, void *buffer, int offset, int length)
    622 {
    623 	FILE *fp;
    624 	int read;
    625 	unzFile zf;
    626 	char tmpbuf[1024];
    627 
    628 	if (qf->zipfile)
    629 	{
    630 		//open the zip file
    631 		zf = unzOpen(qf->pakfile);
    632 		//set the file pointer
    633 		qf->zipinfo.file = ((unz_s *) zf)->file;
    634 		//open the Quake file in the zip file
    635 		unzOpenCurrentFile(&qf->zipinfo);
    636 		//
    637 		while(offset > 0)
    638 		{
    639 			read = offset;
    640 			if (read > sizeof(tmpbuf)) read = sizeof(tmpbuf);
    641 			unzReadCurrentFile(&qf->zipinfo, tmpbuf, read);
    642 			offset -= read;
    643 		} //end while
    644 		//read the Quake file from the zip file
    645 		length = unzReadCurrentFile(&qf->zipinfo, buffer, length);
    646 		//close the Quake file in the zip file
    647 		unzCloseCurrentFile(&qf->zipinfo);
    648 		//close the zip file
    649 		unzClose(zf);
    650 
    651 		return length;
    652 	} //end if
    653 	else
    654 	{
    655 		fp = SafeOpenRead(qf->filename);
    656 		if (qf->offset) fseek(fp, qf->offset, SEEK_SET);
    657 		if (offset) fseek(fp, offset, SEEK_CUR);
    658 		SafeRead(fp, buffer, length);
    659 		fclose(fp);
    660 
    661 		return length;
    662 	} //end else
    663 } //end of the function ReadQuakeFile