2
0
mirror of https://github.com/FWGS/xash3d-fwgs synced 2024-11-22 18:07:09 +01:00
xash3d-fwgs/engine/common/common.c

1058 lines
20 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"
static const char *const file_exts[] =
{
// ban text files that don't make sense as resource
"cfg", "lst", "ini", "log",
// ban Windows code
"exe", "vbs", "com", "bat",
"dll", "sys", "ps1",
// ban common unix code
// NOTE: in unix anything can be executed as long it has access flag
"so", "sh", "dylib",
// ban mobile archives
"apk", "ipa",
};
#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 GAME_EXPORT 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);
}
/*
============
va
does a varargs printf into a temp buffer,
so I don't need to have varargs versions
of all text functions.
============
*/
char *va( const char *format, ... )
{
va_list argptr;
static char string[16][MAX_VA_STRING], *s;
static int stringindex = 0;
s = string[stringindex];
stringindex = (stringindex + 1) & 15;
va_start( argptr, format );
Q_vsnprintf( s, sizeof( string[0] ), format, argptr );
va_end( argptr );
return s;
}
/*
===============================================================================
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;
}
static 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;
if( !pStart )
return NULL;
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_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
==================
*/
static 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 *GAME_EXPORT 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 *GAME_EXPORT 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 *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 = g_fsapi.LoadFileMalloc( name, &iLength, false );
if( pLength ) *pLength = (int)iLength;
return pfile;
}
/*
=============
COM_LoadFile
=============
*/
byte *GAME_EXPORT 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 );
}
/*
=============
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 );
}
}
/*
=============
pfnCVarGetPointer
can return NULL
=============
*/
cvar_t *GAME_EXPORT pfnCVarGetPointer( const char *szVarName )
{
return (cvar_t *)Cvar_FindVar( szVarName );
}
/*
=============
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 = ft1 < ft2 ? -1 : ( ft1 > ft2 ? 1 : 0 );
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_strncpy( szGetGameDir, GI->gamefolder, sizeof( GI->gamefolder ));
}
qboolean COM_IsSafeFileToDownload( const char *filename )
{
char lwrfilename[4096];
const char *first, *last;
const char *ext;
size_t len;
int i;
if( !COM_CheckString( filename ))
return false;
ext = COM_FileExtension( filename );
len = Q_strlen( filename );
// only allow extensionless files that start with !MD5
if( !Q_strncmp( filename, "!MD5", 4 ))
{
if( COM_CheckStringEmpty( ext ))
return false;
len = Q_strlen( filename );
if( len != 36 )
return false;
for( i = 4; i < len; i++ )
{
if(( filename[i] >= '0' && filename[i] <= '9' ) ||
( filename[i] >= 'A' && filename[i] <= 'F' ))
continue;
return false;
}
return true;
}
Q_strnlwr( filename, lwrfilename, sizeof( lwrfilename ));
ext = COM_FileExtension( 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;
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 )
{
size_t size;
char *b;
if( !s ) return NULL;
if( !mempool ) mempool = host.mempool;
size = Q_strlen( s ) + 1;
b = _Mem_Alloc( mempool, size, false, filename, fileline );
Q_strncpy( b, s, size );
return b;
}
/*
======================
COMMON EXPORT STUBS
======================
*/
/*
=============
pfnSequenceGet
used by CS:CZ
=============
*/
void *GAME_EXPORT pfnSequenceGet( const char *fileName, const char *entryName )
{
Msg( "%s: file %s, entry %s\n", __func__, fileName, entryName );
return NULL;
}
/*
=============
pfnSequencePickSentence
used by CS:CZ
=============
*/
void *GAME_EXPORT pfnSequencePickSentence( const char *groupName, int pickMethod, int *picked )
{
Msg( "%s: group %s, pickMethod %i\n", __func__, groupName, pickMethod );
return NULL;
}
/*
=============
pfnIsCareerMatch
used by CS:CZ (client stub)
=============
*/
int GAME_EXPORT pfnIsCareerMatch( void )
{
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 )
{
Msg( "Checking COM_IsSafeFileToDownload...\n" );
TASSERT_EQi( COM_IsSafeFileToDownload( "!MD5AAB5E8B307672DA86FBD10AC302BC732" ), true );
TASSERT_EQi( COM_IsSafeFileToDownload( "!MD56f1ffd8c96bd64c9c27955309f6ecfe6" ), false );
TASSERT_EQi( COM_IsSafeFileToDownload( "!MD5AAB5E8B307672DA86FBD10AC302B.exe" ), false );
TASSERT_EQi( COM_IsSafeFileToDownload( "!MD5/../../valve/resource/GameMenu.res" ), false );
TASSERT_EQi( COM_IsSafeFileToDownload( "not-a-virus-trust-me.bat" ), false );
TASSERT_EQi( COM_IsSafeFileToDownload( "a-texture.png" ), true );
}
#endif