/*
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_FixSlashes

Changes all '/' characters into '\' characters, in place.
============
*/
void COM_FixSlashes( char *pname )
{
	while( *pname )
	{
		if( *pname == '\\' )
			*pname = '/';
		pname++;
	}
}

/*
==================
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 );
	TASSERT( !Q_strcmp( buf, "q" ) && len == 1);

	file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len );
	TASSERT( !Q_strcmp( buf, "asdf" ) && len == 4);

	file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len );
	TASSERT( !Q_strcmp( buf, "qwer" ) && len == -1);

	file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len );
	TASSERT( !Q_strcmp( buf, "f \"f" ) && len == 4);

	file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len );
	TASSERT( !Q_strcmp( buf, "meow" ) && len == -1);

	file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len );
	TASSERT( !Q_strcmp( buf, "bark" ) && len == 4);
}
#endif
