mirror of
https://github.com/FWGS/xash3d-fwgs
synced 2024-11-25 03:11:06 +01:00
Alibek Omarov
5e4fc64430
The goal is to share filesystem code between engine and utilities and provide C++ VFileSystem interface in the future
1187 lines
22 KiB
C
1187 lines
22 KiB
C
/*
|
|
common.c - misc functions used by dlls'
|
|
Copyright (C) 2008 Uncle Mike
|
|
|
|
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 3 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.
|
|
*/
|
|
|
|
#if defined( ALLOCA_H )
|
|
#include ALLOCA_H
|
|
#endif
|
|
#include "common.h"
|
|
#include "studio.h"
|
|
#include "xash3d_mathlib.h"
|
|
#include "const.h"
|
|
#include "client.h"
|
|
#include "library.h"
|
|
#include "sequence.h"
|
|
|
|
static const char *file_exts[] =
|
|
{
|
|
"cfg",
|
|
"lst",
|
|
"exe",
|
|
"vbs",
|
|
"com",
|
|
"bat",
|
|
"dll",
|
|
"ini",
|
|
"log",
|
|
"sys",
|
|
};
|
|
|
|
#ifdef _DEBUG
|
|
void DBG_AssertFunction( qboolean fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage )
|
|
{
|
|
if( fExpr ) return;
|
|
|
|
if( szMessage != NULL )
|
|
Con_DPrintf( S_ERROR "ASSERT FAILED:\n %s \n(%s@%d)\n%s\n", szExpr, szFile, szLine, szMessage );
|
|
else Con_DPrintf( S_ERROR "ASSERT FAILED:\n %s \n(%s@%d)\n", szExpr, szFile, szLine );
|
|
}
|
|
#endif // DEBUG
|
|
|
|
static int idum = 0;
|
|
|
|
#define MAX_RANDOM_RANGE 0x7FFFFFFFUL
|
|
#define IA 16807
|
|
#define IM 2147483647
|
|
#define IQ 127773
|
|
#define IR 2836
|
|
#define NTAB 32
|
|
#define EPS 1.2e-7
|
|
#define NDIV (1 + (IM - 1) / NTAB)
|
|
#define AM (1.0 / IM)
|
|
#define RNMX (1.0 - EPS)
|
|
|
|
static int lran1( void )
|
|
{
|
|
static int iy = 0;
|
|
static int iv[NTAB];
|
|
int j;
|
|
int k;
|
|
|
|
if( idum <= 0 || !iy )
|
|
{
|
|
if( -(idum) < 1 ) idum = 1;
|
|
else idum = -(idum);
|
|
|
|
for( j = NTAB + 7; j >= 0; j-- )
|
|
{
|
|
k = (idum) / IQ;
|
|
idum = IA * (idum - k * IQ) - IR * k;
|
|
if( idum < 0 ) idum += IM;
|
|
if( j < NTAB ) iv[j] = idum;
|
|
}
|
|
|
|
iy = iv[0];
|
|
}
|
|
|
|
k = (idum) / IQ;
|
|
idum = IA * (idum - k * IQ) - IR * k;
|
|
if( idum < 0 ) idum += IM;
|
|
j = iy / NDIV;
|
|
iy = iv[j];
|
|
iv[j] = idum;
|
|
|
|
return iy;
|
|
}
|
|
|
|
// fran1 -- return a random floating-point number on the interval [0,1]
|
|
static float fran1( void )
|
|
{
|
|
float temp = (float)AM * lran1();
|
|
if( temp > RNMX )
|
|
return (float)RNMX;
|
|
return temp;
|
|
}
|
|
|
|
void COM_SetRandomSeed( int lSeed )
|
|
{
|
|
if( lSeed ) idum = lSeed;
|
|
else idum = -time( NULL );
|
|
|
|
if( 1000 < idum )
|
|
idum = -idum;
|
|
else if( -1000 < idum )
|
|
idum -= 22261048;
|
|
}
|
|
|
|
float GAME_EXPORT COM_RandomFloat( float flLow, float flHigh )
|
|
{
|
|
float fl;
|
|
|
|
if( idum == 0 ) COM_SetRandomSeed( 0 );
|
|
|
|
fl = fran1(); // float in [0,1]
|
|
return (fl * (flHigh - flLow)) + flLow; // float in [low, high)
|
|
}
|
|
|
|
int GAME_EXPORT COM_RandomLong( int lLow, int lHigh )
|
|
{
|
|
dword maxAcceptable;
|
|
dword n, x = lHigh - lLow + 1;
|
|
|
|
if( idum == 0 ) COM_SetRandomSeed( 0 );
|
|
|
|
if( x <= 0 || MAX_RANDOM_RANGE < x - 1 )
|
|
return lLow;
|
|
|
|
// The following maps a uniform distribution on the interval [0, MAX_RANDOM_RANGE]
|
|
// to a smaller, client-specified range of [0,x-1] in a way that doesn't bias
|
|
// the uniform distribution unfavorably. Even for a worst case x, the loop is
|
|
// guaranteed to be taken no more than half the time, so for that worst case x,
|
|
// the average number of times through the loop is 2. For cases where x is
|
|
// much smaller than MAX_RANDOM_RANGE, the average number of times through the
|
|
// loop is very close to 1.
|
|
maxAcceptable = MAX_RANDOM_RANGE - ((MAX_RANDOM_RANGE + 1) % x );
|
|
do
|
|
{
|
|
n = lran1();
|
|
} while( n > maxAcceptable );
|
|
|
|
return lLow + (n % x);
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
LZSS Compression
|
|
|
|
===============================================================================
|
|
*/
|
|
#define LZSS_ID (('S'<<24)|('S'<<16)|('Z'<<8)|('L'))
|
|
#define LZSS_LOOKSHIFT 4
|
|
#define LZSS_WINDOW_SIZE 4096
|
|
#define LZSS_LOOKAHEAD BIT( LZSS_LOOKSHIFT )
|
|
|
|
|
|
typedef struct
|
|
{
|
|
unsigned int id;
|
|
unsigned int size;
|
|
} lzss_header_t;
|
|
|
|
// expected to be sixteen bytes
|
|
typedef struct lzss_node_s
|
|
{
|
|
const byte *data;
|
|
struct lzss_node_s *prev;
|
|
struct lzss_node_s *next;
|
|
char pad[4];
|
|
} lzss_node_t;
|
|
|
|
typedef struct
|
|
{
|
|
lzss_node_t *start;
|
|
lzss_node_t *end;
|
|
} lzss_list_t;
|
|
|
|
typedef struct
|
|
{
|
|
lzss_list_t *hash_table;
|
|
lzss_node_t *hash_node;
|
|
int window_size;
|
|
} lzss_state_t;
|
|
|
|
qboolean LZSS_IsCompressed( const byte *source )
|
|
{
|
|
lzss_header_t *phdr = (lzss_header_t *)source;
|
|
|
|
if( phdr && phdr->id == LZSS_ID )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
uint LZSS_GetActualSize( const byte *source )
|
|
{
|
|
lzss_header_t *phdr = (lzss_header_t *)source;
|
|
|
|
if( phdr && phdr->id == LZSS_ID )
|
|
return phdr->size;
|
|
return 0;
|
|
}
|
|
|
|
static void LZSS_BuildHash( lzss_state_t *state, const byte *source )
|
|
{
|
|
lzss_list_t *list;
|
|
lzss_node_t *node;
|
|
unsigned int targetindex = (uint)source & ( state->window_size - 1 );
|
|
|
|
node = &state->hash_node[targetindex];
|
|
|
|
if( node->data )
|
|
{
|
|
list = &state->hash_table[*node->data];
|
|
if( node->prev )
|
|
{
|
|
list->end = node->prev;
|
|
node->prev->next = NULL;
|
|
}
|
|
else
|
|
{
|
|
list->start = NULL;
|
|
list->end = NULL;
|
|
}
|
|
}
|
|
|
|
list = &state->hash_table[*source];
|
|
node->data = source;
|
|
node->prev = NULL;
|
|
node->next = list->start;
|
|
if( list->start )
|
|
list->start->prev = node;
|
|
else list->end = node;
|
|
list->start = node;
|
|
}
|
|
|
|
byte *LZSS_CompressNoAlloc( lzss_state_t *state, byte *pInput, int input_length, byte *pOutputBuf, uint *pOutputSize )
|
|
{
|
|
byte *pStart = pOutputBuf; // allocate the output buffer, compressed buffer is expected to be less, caller will free
|
|
byte *pEnd = pStart + input_length - sizeof( lzss_header_t ) - 8; // prevent compression failure
|
|
lzss_header_t *header = (lzss_header_t *)pStart;
|
|
byte *pOutput = pStart + sizeof( lzss_header_t );
|
|
const byte *pEncodedPosition = NULL;
|
|
byte *pLookAhead = pInput;
|
|
byte *pWindow = pInput;
|
|
int i, putCmdByte = 0;
|
|
byte *pCmdByte = NULL;
|
|
|
|
if( input_length <= sizeof( lzss_header_t ) + 8 )
|
|
return NULL;
|
|
|
|
// set LZSS header
|
|
header->id = LZSS_ID;
|
|
header->size = input_length;
|
|
|
|
// create the compression work buffers, small enough (~64K) for stack
|
|
state->hash_table = (lzss_list_t *)alloca( 256 * sizeof( lzss_list_t ));
|
|
memset( state->hash_table, 0, 256 * sizeof( lzss_list_t ));
|
|
state->hash_node = (lzss_node_t *)alloca( state->window_size * sizeof( lzss_node_t ));
|
|
memset( state->hash_node, 0, state->window_size * sizeof( lzss_node_t ));
|
|
|
|
while( input_length > 0 )
|
|
{
|
|
int lookAheadLength = input_length < LZSS_LOOKAHEAD ? input_length : LZSS_LOOKAHEAD;
|
|
lzss_node_t *hash = state->hash_table[pLookAhead[0]].start;
|
|
int encoded_length = 0;
|
|
|
|
pWindow = pLookAhead - state->window_size;
|
|
|
|
if( pWindow < pInput )
|
|
pWindow = pInput;
|
|
|
|
if( !putCmdByte )
|
|
{
|
|
pCmdByte = pOutput++;
|
|
*pCmdByte = 0;
|
|
}
|
|
|
|
putCmdByte = ( putCmdByte + 1 ) & 0x07;
|
|
|
|
while( hash != NULL )
|
|
{
|
|
int length = lookAheadLength;
|
|
int match_length = 0;
|
|
|
|
while( length-- && hash->data[match_length] == pLookAhead[match_length] )
|
|
match_length++;
|
|
|
|
if( match_length > encoded_length )
|
|
{
|
|
encoded_length = match_length;
|
|
pEncodedPosition = hash->data;
|
|
}
|
|
|
|
if( match_length == lookAheadLength )
|
|
break;
|
|
|
|
hash = hash->next;
|
|
}
|
|
|
|
if ( encoded_length >= 3 )
|
|
{
|
|
*pCmdByte = (*pCmdByte >> 1) | 0x80;
|
|
*pOutput++ = (( pLookAhead - pEncodedPosition - 1 ) >> LZSS_LOOKSHIFT );
|
|
*pOutput++ = (( pLookAhead - pEncodedPosition - 1 ) << LZSS_LOOKSHIFT ) | ( encoded_length - 1 );
|
|
}
|
|
else
|
|
{
|
|
*pCmdByte = ( *pCmdByte >> 1 );
|
|
*pOutput++ = *pLookAhead;
|
|
encoded_length = 1;
|
|
}
|
|
|
|
for( i = 0; i < encoded_length; i++ )
|
|
{
|
|
LZSS_BuildHash( state, pLookAhead++ );
|
|
}
|
|
|
|
input_length -= encoded_length;
|
|
|
|
if( pOutput >= pEnd )
|
|
{
|
|
// compression is worse, abandon
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if( input_length != 0 )
|
|
{
|
|
// unexpected failure
|
|
Assert( 0 );
|
|
return NULL;
|
|
}
|
|
|
|
if( !putCmdByte )
|
|
{
|
|
pCmdByte = pOutput++;
|
|
*pCmdByte = 0x01;
|
|
}
|
|
else
|
|
{
|
|
*pCmdByte = (( *pCmdByte >> 1 ) | 0x80 ) >> ( 7 - putCmdByte );
|
|
}
|
|
|
|
// put two ints at end of buffer
|
|
*pOutput++ = 0;
|
|
*pOutput++ = 0;
|
|
|
|
if( pOutputSize )
|
|
*pOutputSize = pOutput - pStart;
|
|
|
|
return pStart;
|
|
}
|
|
|
|
byte *LZSS_Compress( byte *pInput, int inputLength, uint *pOutputSize )
|
|
{
|
|
byte *pStart = (byte *)malloc( inputLength );
|
|
byte *pFinal = NULL;
|
|
lzss_state_t state;
|
|
|
|
memset( &state, 0, sizeof( state ));
|
|
state.window_size = LZSS_WINDOW_SIZE;
|
|
|
|
pFinal = LZSS_CompressNoAlloc( &state, pInput, inputLength, pStart, pOutputSize );
|
|
|
|
if( !pFinal )
|
|
{
|
|
free( pStart );
|
|
return NULL;
|
|
}
|
|
|
|
return pStart;
|
|
}
|
|
|
|
uint LZSS_Decompress( const byte *pInput, byte *pOutput )
|
|
{
|
|
uint totalBytes = 0;
|
|
int getCmdByte = 0;
|
|
int cmdByte = 0;
|
|
uint actualSize = LZSS_GetActualSize( pInput );
|
|
|
|
if( !actualSize )
|
|
return 0;
|
|
|
|
pInput += sizeof( lzss_header_t );
|
|
|
|
while( 1 )
|
|
{
|
|
if( !getCmdByte )
|
|
cmdByte = *pInput++;
|
|
getCmdByte = ( getCmdByte + 1 ) & 0x07;
|
|
|
|
if( cmdByte & 0x01 )
|
|
{
|
|
int position = *pInput++ << LZSS_LOOKSHIFT;
|
|
int i, count;
|
|
byte *pSource;
|
|
|
|
position |= ( *pInput >> LZSS_LOOKSHIFT );
|
|
count = ( *pInput++ & 0x0F ) + 1;
|
|
|
|
if( count == 1 )
|
|
break;
|
|
|
|
pSource = pOutput - position - 1;
|
|
for( i = 0; i < count; i++ )
|
|
*pOutput++ = *pSource++;
|
|
totalBytes += count;
|
|
}
|
|
else
|
|
{
|
|
*pOutput++ = *pInput++;
|
|
totalBytes++;
|
|
}
|
|
cmdByte = cmdByte >> 1;
|
|
}
|
|
|
|
if( totalBytes != actualSize )
|
|
{
|
|
Assert( 0 );
|
|
return 0;
|
|
}
|
|
return totalBytes;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
COM_IsWhiteSpace
|
|
|
|
interpret symbol as whitespace
|
|
==============
|
|
*/
|
|
|
|
static int COM_IsWhiteSpace( char space )
|
|
{
|
|
if( space == ' ' || space == '\t' || space == '\r' || space == '\n' )
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
================
|
|
COM_ParseVector
|
|
|
|
================
|
|
*/
|
|
qboolean COM_ParseVector( char **pfile, float *v, size_t size )
|
|
{
|
|
string token;
|
|
qboolean bracket = false;
|
|
char *saved;
|
|
uint i;
|
|
|
|
if( v == NULL || size == 0 )
|
|
return false;
|
|
|
|
memset( v, 0, sizeof( *v ) * size );
|
|
|
|
if( size == 1 )
|
|
{
|
|
*pfile = COM_ParseFile( *pfile, token, sizeof( token ));
|
|
v[0] = Q_atof( token );
|
|
return true;
|
|
}
|
|
|
|
saved = *pfile;
|
|
|
|
if(( *pfile = COM_ParseFile( *pfile, token, sizeof( token ))) == NULL )
|
|
return false;
|
|
|
|
if( token[0] == '(' )
|
|
bracket = true;
|
|
else *pfile = saved; // restore token to right get it again
|
|
|
|
for( i = 0; i < size; i++ )
|
|
{
|
|
*pfile = COM_ParseFile( *pfile, token, sizeof( token ));
|
|
v[i] = Q_atof( token );
|
|
}
|
|
|
|
if( !bracket ) return true; // done
|
|
|
|
if(( *pfile = COM_ParseFile( *pfile, token, sizeof( token ))) == NULL )
|
|
return false;
|
|
|
|
if( token[0] == ')' )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
COM_FileSize
|
|
|
|
=============
|
|
*/
|
|
int GAME_EXPORT COM_FileSize( const char *filename )
|
|
{
|
|
return FS_FileSize( filename, false );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
COM_AddAppDirectoryToSearchPath
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT COM_AddAppDirectoryToSearchPath( const char *pszBaseDir, const char *appName )
|
|
{
|
|
FS_AddGameHierarchy( pszBaseDir, FS_NOWRITE_PATH );
|
|
}
|
|
|
|
/*
|
|
===========
|
|
COM_ExpandFilename
|
|
|
|
Finds the file in the search path, copies over the name with the full path name.
|
|
This doesn't search in the pak file.
|
|
===========
|
|
*/
|
|
int GAME_EXPORT COM_ExpandFilename( const char *fileName, char *nameOutBuffer, int nameOutBufferSize )
|
|
{
|
|
const char *path;
|
|
char result[MAX_SYSPATH];
|
|
|
|
if( !COM_CheckString( fileName ) || !nameOutBuffer || nameOutBufferSize <= 0 )
|
|
return 0;
|
|
|
|
// filename examples:
|
|
// media\sierra.avi - D:\Xash3D\valve\media\sierra.avi
|
|
// models\barney.mdl - D:\Xash3D\bshift\models\barney.mdl
|
|
if(( path = FS_GetDiskPath( fileName, false )) != NULL )
|
|
{
|
|
Q_sprintf( result, "%s/%s", host.rootdir, path );
|
|
|
|
// check for enough room
|
|
if( Q_strlen( result ) > nameOutBufferSize )
|
|
return 0;
|
|
|
|
Q_strncpy( nameOutBuffer, result, nameOutBufferSize );
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
COM_TrimSpace
|
|
|
|
trims all whitespace from the front
|
|
and end of a string
|
|
=============
|
|
*/
|
|
void COM_TrimSpace( const char *source, char *dest )
|
|
{
|
|
int start, end, length;
|
|
|
|
start = 0;
|
|
end = Q_strlen( source );
|
|
|
|
while( source[start] && COM_IsWhiteSpace( source[start] ))
|
|
start++;
|
|
end--;
|
|
|
|
while( end > 0 && COM_IsWhiteSpace( source[end] ))
|
|
end--;
|
|
end++;
|
|
|
|
length = end - start;
|
|
|
|
if( length > 0 )
|
|
memcpy( dest, source + start, length );
|
|
else length = 0;
|
|
|
|
// terminate the dest string
|
|
dest[length] = 0;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
COM_Nibble
|
|
|
|
Returns the 4 bit nibble for a hex character
|
|
==================
|
|
*/
|
|
byte COM_Nibble( char c )
|
|
{
|
|
if(( c >= '0' ) && ( c <= '9' ))
|
|
{
|
|
return (byte)(c - '0');
|
|
}
|
|
|
|
if(( c >= 'A' ) && ( c <= 'F' ))
|
|
{
|
|
return (byte)(c - 'A' + 0x0a);
|
|
}
|
|
|
|
if(( c >= 'a' ) && ( c <= 'f' ))
|
|
{
|
|
return (byte)(c - 'a' + 0x0a);
|
|
}
|
|
|
|
return '0';
|
|
}
|
|
|
|
/*
|
|
==================
|
|
COM_HexConvert
|
|
|
|
Converts pszInput Hex string to nInputLength/2 binary
|
|
==================
|
|
*/
|
|
void COM_HexConvert( const char *pszInput, int nInputLength, byte *pOutput )
|
|
{
|
|
const char *pIn;
|
|
byte *p = pOutput;
|
|
int i;
|
|
|
|
|
|
for( i = 0; i < nInputLength; i += 2 )
|
|
{
|
|
pIn = &pszInput[i];
|
|
*p = COM_Nibble( pIn[0] ) << 4 | COM_Nibble( pIn[1] );
|
|
p++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
COM_MemFgets
|
|
|
|
=============
|
|
*/
|
|
char *COM_MemFgets( byte *pMemFile, int fileSize, int *filePos, char *pBuffer, int bufferSize )
|
|
{
|
|
int i, last, stop;
|
|
|
|
if( !pMemFile || !pBuffer || !filePos )
|
|
return NULL;
|
|
|
|
if( *filePos >= fileSize )
|
|
return NULL;
|
|
|
|
i = *filePos;
|
|
last = fileSize;
|
|
|
|
// fgets always NULL terminates, so only read bufferSize-1 characters
|
|
if( last - *filePos > ( bufferSize - 1 ))
|
|
last = *filePos + ( bufferSize - 1);
|
|
|
|
stop = 0;
|
|
|
|
// stop at the next newline (inclusive) or end of buffer
|
|
while( i < last && !stop )
|
|
{
|
|
if( pMemFile[i] == '\n' )
|
|
stop = 1;
|
|
i++;
|
|
}
|
|
|
|
// if we actually advanced the pointer, copy it over
|
|
if( i != *filePos )
|
|
{
|
|
// we read in size bytes
|
|
int size = i - *filePos;
|
|
|
|
// copy it out
|
|
memcpy( pBuffer, pMemFile + *filePos, size );
|
|
|
|
// If the buffer isn't full, terminate (this is always true)
|
|
if( size < bufferSize ) pBuffer[size] = 0;
|
|
|
|
// update file pointer
|
|
*filePos = i;
|
|
return pBuffer;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
Cache_Check
|
|
|
|
consistency check
|
|
====================
|
|
*/
|
|
void *Cache_Check( poolhandle_t mempool, cache_user_t *c )
|
|
{
|
|
if( !c->data )
|
|
return NULL;
|
|
|
|
if( !Mem_IsAllocatedExt( mempool, c->data ))
|
|
return NULL;
|
|
|
|
return c->data;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
COM_LoadFileForMe
|
|
|
|
=============
|
|
*/
|
|
byte* GAME_EXPORT COM_LoadFileForMe( const char *filename, int *pLength )
|
|
{
|
|
string name;
|
|
byte *file, *pfile;
|
|
fs_offset_t iLength;
|
|
|
|
if( !COM_CheckString( filename ))
|
|
{
|
|
if( pLength )
|
|
*pLength = 0;
|
|
return NULL;
|
|
}
|
|
|
|
Q_strncpy( name, filename, sizeof( name ));
|
|
COM_FixSlashes( name );
|
|
|
|
pfile = FS_LoadFile( name, &iLength, false );
|
|
if( pLength ) *pLength = (int)iLength;
|
|
|
|
if( pfile )
|
|
{
|
|
file = malloc( iLength + 1 );
|
|
if( file != NULL )
|
|
{
|
|
memcpy( file, pfile, iLength );
|
|
file[iLength] = '\0';
|
|
}
|
|
Mem_Free( pfile );
|
|
pfile = file;
|
|
}
|
|
|
|
return pfile;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
COM_LoadFile
|
|
|
|
=============
|
|
*/
|
|
byte *COM_LoadFile( const char *filename, int usehunk, int *pLength )
|
|
{
|
|
return COM_LoadFileForMe( filename, pLength );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
COM_SaveFile
|
|
|
|
=============
|
|
*/
|
|
int GAME_EXPORT COM_SaveFile( const char *filename, const void *data, int len )
|
|
{
|
|
// check for empty filename
|
|
if( !COM_CheckString( filename ))
|
|
return false;
|
|
|
|
// check for null data
|
|
if( !data || len <= 0 )
|
|
return false;
|
|
|
|
return FS_WriteFile( filename, data, len );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
COM_FreeFile
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT COM_FreeFile( void *buffer )
|
|
{
|
|
free( buffer );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
COM_NormalizeAngles
|
|
|
|
=============
|
|
*/
|
|
void COM_NormalizeAngles( vec3_t angles )
|
|
{
|
|
int i;
|
|
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
if( angles[i] > 180.0f )
|
|
angles[i] -= 360.0f;
|
|
else if( angles[i] < -180.0f )
|
|
angles[i] += 360.0f;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnGetModelType
|
|
|
|
=============
|
|
*/
|
|
int GAME_EXPORT pfnGetModelType( model_t *mod )
|
|
{
|
|
if( !mod ) return mod_bad;
|
|
return mod->type;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnGetModelBounds
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT pfnGetModelBounds( model_t *mod, float *mins, float *maxs )
|
|
{
|
|
if( mod )
|
|
{
|
|
if( mins ) VectorCopy( mod->mins, mins );
|
|
if( maxs ) VectorCopy( mod->maxs, maxs );
|
|
}
|
|
else
|
|
{
|
|
if( mins ) VectorClear( mins );
|
|
if( maxs ) VectorClear( maxs );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnCvar_RegisterServerVariable
|
|
|
|
standard path to register game variable
|
|
=============
|
|
*/
|
|
void GAME_EXPORT pfnCvar_RegisterServerVariable( cvar_t *variable )
|
|
{
|
|
if( variable != NULL )
|
|
SetBits( variable->flags, FCVAR_EXTDLL );
|
|
Cvar_RegisterVariable( (convar_t *)variable );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnCvar_RegisterEngineVariable
|
|
|
|
use with precaution: this cvar will NOT unlinked
|
|
after game.dll is unloaded
|
|
=============
|
|
*/
|
|
void GAME_EXPORT pfnCvar_RegisterEngineVariable( cvar_t *variable )
|
|
{
|
|
Cvar_RegisterVariable( (convar_t *)variable );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnCvar_RegisterVariable
|
|
|
|
=============
|
|
*/
|
|
cvar_t *pfnCvar_RegisterClientVariable( const char *szName, const char *szValue, int flags )
|
|
{
|
|
// a1ba: try to mitigate outdated client.dll vulnerabilities
|
|
if( !Q_stricmp( szName, "motdfile" ))
|
|
flags |= FCVAR_PRIVILEGED;
|
|
|
|
if( FBitSet( flags, FCVAR_GLCONFIG ))
|
|
return (cvar_t *)Cvar_Get( szName, szValue, flags, va( CVAR_GLCONFIG_DESCRIPTION, szName ));
|
|
return (cvar_t *)Cvar_Get( szName, szValue, flags|FCVAR_CLIENTDLL, Cvar_BuildAutoDescription( flags|FCVAR_CLIENTDLL ));
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnCvar_RegisterVariable
|
|
|
|
=============
|
|
*/
|
|
cvar_t *pfnCvar_RegisterGameUIVariable( const char *szName, const char *szValue, int flags )
|
|
{
|
|
if( FBitSet( flags, FCVAR_GLCONFIG ))
|
|
return (cvar_t *)Cvar_Get( szName, szValue, flags, va( CVAR_GLCONFIG_DESCRIPTION, szName ));
|
|
return (cvar_t *)Cvar_Get( szName, szValue, flags|FCVAR_GAMEUIDLL, Cvar_BuildAutoDescription( flags|FCVAR_GAMEUIDLL ));
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnCVarGetPointer
|
|
|
|
can return NULL
|
|
=============
|
|
*/
|
|
cvar_t *pfnCVarGetPointer( const char *szVarName )
|
|
{
|
|
return (cvar_t *)Cvar_FindVar( szVarName );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnCVarDirectSet
|
|
|
|
allow to set cvar directly
|
|
=============
|
|
*/
|
|
void GAME_EXPORT pfnCVarDirectSet( cvar_t *var, const char *szValue )
|
|
{
|
|
Cvar_DirectSet( (convar_t *)var, szValue );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
COM_CompareFileTime
|
|
|
|
=============
|
|
*/
|
|
int GAME_EXPORT COM_CompareFileTime( const char *filename1, const char *filename2, int *iCompare )
|
|
{
|
|
int bRet = 0;
|
|
|
|
*iCompare = 0;
|
|
|
|
if( filename1 && filename2 )
|
|
{
|
|
int ft1 = FS_FileTime( filename1, false );
|
|
int ft2 = FS_FileTime( filename2, false );
|
|
|
|
// one of files is missing
|
|
if( ft1 == -1 || ft2 == -1 )
|
|
return bRet;
|
|
|
|
*iCompare = Host_CompareFileTime( ft1, ft2 );
|
|
bRet = 1;
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
COM_CheckParm
|
|
|
|
=============
|
|
*/
|
|
int GAME_EXPORT COM_CheckParm( char *parm, char **ppnext )
|
|
{
|
|
int i = Sys_CheckParm( parm );
|
|
|
|
if( ppnext )
|
|
{
|
|
if( i != 0 && i < host.argc - 1 )
|
|
*ppnext = (char *)host.argv[i + 1];
|
|
else *ppnext = NULL;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnTime
|
|
|
|
=============
|
|
*/
|
|
float GAME_EXPORT pfnTime( void )
|
|
{
|
|
return (float)Sys_DoubleTime();
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnGetGameDir
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT pfnGetGameDir( char *szGetGameDir )
|
|
{
|
|
if( !szGetGameDir ) return;
|
|
Q_strcpy( szGetGameDir, GI->gamefolder );
|
|
}
|
|
|
|
qboolean COM_IsSafeFileToDownload( const char *filename )
|
|
{
|
|
char lwrfilename[4096];
|
|
const char *first, *last;
|
|
const char *ext;
|
|
int i;
|
|
|
|
if( !COM_CheckString( filename ))
|
|
return false;
|
|
|
|
if( !Q_strncmp( filename, "!MD5", 4 ))
|
|
return true;
|
|
|
|
Q_strnlwr( filename, lwrfilename, sizeof( lwrfilename ));
|
|
|
|
if( Q_strpbrk( lwrfilename, "\\:~" ) || Q_strstr( lwrfilename, ".." ) )
|
|
return false;
|
|
|
|
if( lwrfilename[0] == '/' )
|
|
return false;
|
|
|
|
first = Q_strchr( lwrfilename, '.' );
|
|
last = Q_strrchr( lwrfilename, '.' );
|
|
|
|
if( first == NULL || last == NULL )
|
|
return false;
|
|
|
|
if( first != last )
|
|
return false;
|
|
|
|
if( Q_strlen( first ) != 4 )
|
|
return false;
|
|
|
|
ext = COM_FileExtension( lwrfilename );
|
|
|
|
for( i = 0; i < ARRAYSIZE( file_exts ); i++ )
|
|
{
|
|
if( !Q_stricmp( ext, file_exts[i] ))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
char *_copystring( poolhandle_t mempool, const char *s, const char *filename, int fileline )
|
|
{
|
|
char *b;
|
|
|
|
if( !s ) return NULL;
|
|
if( !mempool ) mempool = host.mempool;
|
|
|
|
b = _Mem_Alloc( mempool, Q_strlen( s ) + 1, false, filename, fileline );
|
|
Q_strcpy( b, s );
|
|
|
|
return b;
|
|
}
|
|
|
|
/*
|
|
======================
|
|
|
|
COMMON EXPORT STUBS
|
|
|
|
======================
|
|
*/
|
|
|
|
|
|
/*
|
|
=============
|
|
pfnSequenceGet
|
|
|
|
used by CS:CZ
|
|
=============
|
|
*/
|
|
void *GAME_EXPORT pfnSequenceGet( const char *fileName, const char *entryName )
|
|
{
|
|
Msg( "Sequence_Get: file %s, entry %s\n", fileName, entryName );
|
|
|
|
|
|
return Sequence_Get( fileName, entryName );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnSequencePickSentence
|
|
|
|
used by CS:CZ
|
|
=============
|
|
*/
|
|
void *GAME_EXPORT pfnSequencePickSentence( const char *groupName, int pickMethod, int *picked )
|
|
{
|
|
Msg( "Sequence_PickSentence: group %s, pickMethod %i\n", groupName, pickMethod );
|
|
|
|
return Sequence_PickSentence( groupName, pickMethod, picked );
|
|
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnIsCareerMatch
|
|
|
|
used by CS:CZ (client stub)
|
|
=============
|
|
*/
|
|
int GAME_EXPORT GAME_EXPORT pfnIsCareerMatch( void )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnRegisterTutorMessageShown
|
|
|
|
only exists in PlayStation version
|
|
=============
|
|
*/
|
|
void GAME_EXPORT pfnRegisterTutorMessageShown( int mid )
|
|
{
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnGetTimesTutorMessageShown
|
|
|
|
only exists in PlayStation version
|
|
=============
|
|
*/
|
|
int GAME_EXPORT pfnGetTimesTutorMessageShown( int mid )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnProcessTutorMessageDecayBuffer
|
|
|
|
only exists in PlayStation version
|
|
=============
|
|
*/
|
|
void GAME_EXPORT pfnProcessTutorMessageDecayBuffer( int *buffer, int bufferLength )
|
|
{
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnConstructTutorMessageDecayBuffer
|
|
|
|
only exists in PlayStation version
|
|
=============
|
|
*/
|
|
void GAME_EXPORT pfnConstructTutorMessageDecayBuffer( int *buffer, int bufferLength )
|
|
{
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnResetTutorMessageDecayData
|
|
|
|
only exists in PlayStation version
|
|
=============
|
|
*/
|
|
void GAME_EXPORT pfnResetTutorMessageDecayData( void )
|
|
{
|
|
}
|
|
|
|
#if XASH_ENGINE_TESTS
|
|
|
|
#include "tests.h"
|
|
|
|
void Test_RunCommon( void )
|
|
{
|
|
char *file = (char *)"q asdf \"qwerty\" \"f \\\"f\" meowmeow\n// comment \"stuff ignored\"\nbark";
|
|
int len;
|
|
char buf[5];
|
|
|
|
Msg( "Checking COM_ParseFile...\n" );
|
|
|
|
file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL );
|
|
TASSERT( !Q_strcmp( buf, "q" ) && len == 1);
|
|
|
|
file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL );
|
|
TASSERT( !Q_strcmp( buf, "asdf" ) && len == 4);
|
|
|
|
file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL );
|
|
TASSERT( !Q_strcmp( buf, "qwer" ) && len == -1);
|
|
|
|
file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL );
|
|
TASSERT( !Q_strcmp( buf, "f \"f" ) && len == 4);
|
|
|
|
file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL );
|
|
TASSERT( !Q_strcmp( buf, "meow" ) && len == -1);
|
|
|
|
file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL );
|
|
TASSERT( !Q_strcmp( buf, "bark" ) && len == 4);
|
|
}
|
|
#endif
|