2
0
mirror of https://github.com/FWGS/xash3d-fwgs synced 2024-11-28 13:02:13 +01:00
xash3d-fwgs/engine/common/common.c
Alibek Omarov e23580c1de engine: remove czeror sequence parser
This file initially came from HLND, a Chinese GoldSrc recreation.
It turned out to be suspiciously close to the original version, down
to the comments and code style. We don't work with leaked sources here,
so remove it.

A proper parser should be reimplemented from ground-up, when we will
start working on CZDS support.
2023-07-26 19:40:03 +03:00

1143 lines
21 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 *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 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;
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 )
{
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( g_fsapi.GetFullDiskPath( result, sizeof( result ), fileName, false ))
{
// 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
==================
*/
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 *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 *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 );
}
/*
=============
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 );
}
}
/*
=============
pfnCVarGetPointer
can return NULL
=============
*/
cvar_t *GAME_EXPORT 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_strncpy( szGetGameDir, GI->gamefolder, sizeof( 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;
}
const char *COM_GetResourceTypeName( resourcetype_t restype )
{
switch( restype )
{
case t_decal: return "decal";
case t_eventscript: return "eventscript";
case t_generic: return "generic";
case t_model: return "model";
case t_skin: return "skin";
case t_sound: return "sound";
case t_world: return "world";
default: return "unknown";
}
}
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( "Sequence_Get: file %s, entry %s\n", fileName, entryName );
return NULL;
}
/*
=============
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 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 )
{
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