rallyunlimited-engine/code/qcommon/q_shared.c
2024-02-02 19:46:17 +03:00

2167 lines
38 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena source code 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 2 of the License,
or (at your option) any later version.
Quake III Arena source code 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.
You should have received a copy of the GNU General Public License
along with Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
//
// q_shared.c -- stateless support routines that are included in each code dll
#include "q_shared.h"
float Com_Clamp( float min, float max, float value ) {
if ( value < min ) {
return min;
}
if ( value > max ) {
return max;
}
return value;
}
/*
============
COM_SkipPath
============
*/
char *COM_SkipPath (char *pathname)
{
char *last;
last = pathname;
while (*pathname)
{
if (*pathname=='/')
last = pathname+1;
pathname++;
}
return last;
}
/*
============
COM_GetExtension
============
*/
const char *COM_GetExtension( const char *name )
{
const char *dot = strrchr(name, '.'), *slash;
if (dot && ((slash = strrchr(name, '/')) == NULL || slash < dot))
return dot + 1;
else
return "";
}
/*
============
COM_StripExtension
============
*/
void COM_StripExtension( const char *in, char *out, int destsize )
{
const char *dot = strrchr(in, '.'), *slash;
if (dot && ((slash = strrchr(in, '/')) == NULL || slash < dot))
destsize = (destsize < dot-in+1 ? destsize : dot-in+1);
if ( in == out && destsize > 1 )
out[destsize-1] = '\0';
else
Q_strncpyz(out, in, destsize);
}
/*
============
COM_CompareExtension
string compare the end of the strings and return qtrue if strings match
============
*/
qboolean COM_CompareExtension(const char *in, const char *ext)
{
int inlen, extlen;
inlen = strlen(in);
extlen = strlen(ext);
if(extlen <= inlen)
{
in += inlen - extlen;
if(!Q_stricmp(in, ext))
return qtrue;
}
return qfalse;
}
/*
==================
COM_DefaultExtension
if path doesn't have an extension, then append
the specified one (which should include the .)
==================
*/
void COM_DefaultExtension( char *path, int maxSize, const char *extension )
{
const char *dot = strrchr(path, '.'), *slash;
if (dot && ((slash = strrchr(path, '/')) == NULL || slash < dot))
return;
else
Q_strcat(path, maxSize, extension);
}
/*
==================
COM_GenerateHashValue
used in renderer and filesystem
==================
*/
// ASCII lowcase conversion table with '\\' turned to '/' and '.' to '\0'
static const byte hash_locase[ 256 ] =
{
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,
0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,
0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,
0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,
0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x00,0x2f,
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,
0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,
0x40,0x61,0x62,0x63,0x64,0x65,0x66,0x67,
0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,
0x78,0x79,0x7a,0x5b,0x2f,0x5d,0x5e,0x5f,
0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,
0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,
0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,
0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,
0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,
0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f,
0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,
0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf,
0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,
0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf,
0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,
0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf,
0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,
0xd8,0xd9,0xda,0xdb,0xdc,0xdd,0xde,0xdf,
0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,
0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef,
0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,
0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff
};
unsigned long Com_GenerateHashValue( const char *fname, const unsigned int size )
{
const byte *s;
unsigned long hash;
int c;
s = (byte*)fname;
hash = 0;
while ( (c = hash_locase[(byte)*s++]) != '\0' ) {
hash = hash * 101 + c;
}
hash = (hash ^ (hash >> 10) ^ (hash >> 20));
hash &= (size-1);
return hash;
}
/*
============
Com_Split
============
*/
int Com_Split( char *in, char **out, int outsz, int delim )
{
int c;
char **o = out, **end = out + outsz;
// skip leading spaces
if ( delim >= ' ' ) {
while( (c = *in) != '\0' && c <= ' ' )
in++;
}
*out = in; out++;
while( out < end ) {
while( (c = *in) != '\0' && c != delim )
in++;
*in = '\0';
if ( !c ) {
// don't count last null value
if ( out[-1][0] == '\0' )
out--;
break;
}
in++;
// skip leading spaces
if ( delim >= ' ' ) {
while( (c = *in) != '\0' && c <= ' ' )
in++;
}
*out = in; out++;
}
// sanitize last value
while( (c = *in) != '\0' && c != delim )
in++;
*in = '\0';
c = out - o;
// set remaining out pointers
while( out < end ) {
*out = in; out++;
}
return c;
}
/*
==================
crc32_buffer
==================
*/
unsigned int crc32_buffer( const byte *buf, unsigned int len ) {
static unsigned int crc32_table[256];
static qboolean crc32_inited = qfalse;
unsigned int crc = 0xFFFFFFFFUL;
if ( !crc32_inited )
{
unsigned int c;
int i, j;
for (i = 0; i < 256; i++)
{
c = i;
for ( j = 0; j < 8; j++ )
c = (c & 1) ? (c >> 1) ^ 0xEDB88320UL : c >> 1;
crc32_table[i] = c;
}
crc32_inited = qtrue;
}
while ( len-- )
{
crc = crc32_table[(crc ^ *buf++) & 0xFF] ^ (crc >> 8);
}
return crc ^ 0xFFFFFFFFUL;
}
/*
============================================================================
BYTE ORDER FUNCTIONS
============================================================================
*/
/*
// can't just use function pointers, or dll linkage can
// mess up when qcommon is included in multiple places
static short (*_BigShort) (short l);
static short (*_LittleShort) (short l);
static int (*_BigLong) (int l);
static int (*_LittleLong) (int l);
static qint64 (*_BigLong64) (qint64 l);
static qint64 (*_LittleLong64) (qint64 l);
static float (*_BigFloat) (const float *l);
static float (*_LittleFloat) (const float *l);
short BigShort(short l){return _BigShort(l);}
short LittleShort(short l) {return _LittleShort(l);}
int BigLong (int l) {return _BigLong(l);}
int LittleLong (int l) {return _LittleLong(l);}
qint64 BigLong64 (qint64 l) {return _BigLong64(l);}
qint64 LittleLong64 (qint64 l) {return _LittleLong64(l);}
float BigFloat (const float *l) {return _BigFloat(l);}
float LittleFloat (const float *l) {return _LittleFloat(l);}
*/
void CopyShortSwap(void *dest, void *src)
{
byte *to = dest, *from = src;
to[0] = from[1];
to[1] = from[0];
}
void CopyLongSwap(void *dest, void *src)
{
byte *to = dest, *from = src;
to[0] = from[3];
to[1] = from[2];
to[2] = from[1];
to[3] = from[0];
}
short ShortSwap (short l)
{
byte b1,b2;
b1 = l&255;
b2 = (l>>8)&255;
return (b1<<8) + b2;
}
short ShortNoSwap (short l)
{
return l;
}
int LongSwap (int l)
{
byte b1,b2,b3,b4;
b1 = l&255;
b2 = (l>>8)&255;
b3 = (l>>16)&255;
b4 = (l>>24)&255;
return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4;
}
int LongNoSwap (int l)
{
return l;
}
qint64 Long64Swap (qint64 ll)
{
qint64 result;
result.b0 = ll.b7;
result.b1 = ll.b6;
result.b2 = ll.b5;
result.b3 = ll.b4;
result.b4 = ll.b3;
result.b5 = ll.b2;
result.b6 = ll.b1;
result.b7 = ll.b0;
return result;
}
qint64 Long64NoSwap( qint64 ll )
{
return ll;
}
float FloatSwap( const float *f )
{
floatint_t out;
out.f = *f;
out.i = LongSwap( out.i );
return out.f;
}
float FloatNoSwap( const float *f )
{
return *f;
}
/*
================
Swap_Init
================
*/
/*
void Swap_Init (void)
{
byte swaptest[2] = {1,0};
// set the byte swapping variables in a portable manner
if ( *(short *)swaptest == 1)
{
_BigShort = ShortSwap;
_LittleShort = ShortNoSwap;
_BigLong = LongSwap;
_LittleLong = LongNoSwap;
_BigLong64 = Long64Swap;
_LittleLong64 = Long64NoSwap;
_BigFloat = FloatSwap;
_LittleFloat = FloatNoSwap;
}
else
{
_BigShort = ShortNoSwap;
_LittleShort = ShortSwap;
_BigLong = LongNoSwap;
_LittleLong = LongSwap;
_BigLong64 = Long64NoSwap;
_LittleLong64 = Long64Swap;
_BigFloat = FloatNoSwap;
_LittleFloat = FloatSwap;
}
}
*/
/*
============================================================================
PARSING
============================================================================
*/
static char com_token[MAX_TOKEN_CHARS];
static char com_parsename[MAX_TOKEN_CHARS];
static int com_lines;
static int com_tokenline;
// for complex parser
tokenType_t com_tokentype;
void COM_BeginParseSession( const char *name )
{
com_lines = 1;
com_tokenline = 0;
Com_sprintf(com_parsename, sizeof(com_parsename), "%s", name);
}
int COM_GetCurrentParseLine( void )
{
if ( com_tokenline )
{
return com_tokenline;
}
return com_lines;
}
const char *COM_Parse( const char **data_p )
{
return COM_ParseExt( data_p, qtrue );
}
void COM_ParseError( const char *format, ... )
{
va_list argptr;
static char string[4096];
va_start( argptr, format );
Q_vsnprintf (string, sizeof(string), format, argptr);
va_end( argptr );
Com_Printf( "ERROR: %s, line %d: %s\n", com_parsename, COM_GetCurrentParseLine(), string );
}
void COM_ParseWarning( const char *format, ... )
{
va_list argptr;
static char string[4096];
va_start( argptr, format );
Q_vsnprintf (string, sizeof(string), format, argptr);
va_end( argptr );
Com_Printf( "WARNING: %s, line %d: %s\n", com_parsename, COM_GetCurrentParseLine(), string );
}
/*
==============
COM_Parse
Parse a token out of a string
Will never return NULL, just empty strings
If "allowLineBreaks" is qtrue then an empty
string will be returned if the next token is
a newline.
==============
*/
static const char *SkipWhitespace( const char *data, qboolean *hasNewLines ) {
int c;
while( (c = *data) <= ' ') {
if( !c ) {
return NULL;
}
if( c == '\n' ) {
com_lines++;
*hasNewLines = qtrue;
}
data++;
}
return data;
}
int COM_Compress( char *data_p ) {
const char *in;
char *out;
int c;
qboolean newline = qfalse, whitespace = qfalse;
in = out = data_p;
while ((c = *in) != '\0') {
// skip double slash comments
if ( c == '/' && in[1] == '/' ) {
while (*in && *in != '\n') {
in++;
}
// skip /* */ comments
} else if ( c == '/' && in[1] == '*' ) {
while ( *in && ( *in != '*' || in[1] != '/' ) )
in++;
if ( *in )
in += 2;
// record when we hit a newline
} else if ( c == '\n' || c == '\r' ) {
newline = qtrue;
in++;
// record when we hit whitespace
} else if ( c == ' ' || c == '\t') {
whitespace = qtrue;
in++;
// an actual token
} else {
// if we have a pending newline, emit it (and it counts as whitespace)
if (newline) {
*out++ = '\n';
newline = qfalse;
whitespace = qfalse;
} else if (whitespace) {
*out++ = ' ';
whitespace = qfalse;
}
// copy quoted strings unmolested
if (c == '"') {
*out++ = c;
in++;
while (1) {
c = *in;
if (c && c != '"') {
*out++ = c;
in++;
} else {
break;
}
}
if (c == '"') {
*out++ = c;
in++;
}
} else {
*out++ = c;
in++;
}
}
}
*out = '\0';
return out - data_p;
}
const char *COM_ParseExt( const char **data_p, qboolean allowLineBreaks )
{
int c = 0, len;
qboolean hasNewLines = qfalse;
const char *data;
data = *data_p;
len = 0;
com_token[0] = '\0';
com_tokenline = 0;
// make sure incoming data is valid
if ( !data )
{
*data_p = NULL;
return com_token;
}
while ( 1 )
{
// skip whitespace
data = SkipWhitespace( data, &hasNewLines );
if ( !data )
{
*data_p = NULL;
return com_token;
}
if ( hasNewLines && !allowLineBreaks )
{
*data_p = data;
return com_token;
}
c = *data;
// skip double slash comments
if ( c == '/' && data[1] == '/' )
{
data += 2;
while (*data && *data != '\n') {
data++;
}
}
// skip /* */ comments
else if ( c == '/' && data[1] == '*' )
{
data += 2;
while ( *data && ( *data != '*' || data[1] != '/' ) )
{
if ( *data == '\n' )
{
com_lines++;
}
data++;
}
if ( *data )
{
data += 2;
}
}
else
{
break;
}
}
// token starts on this line
com_tokenline = com_lines;
// handle quoted strings
if ( c == '"' )
{
data++;
while ( 1 )
{
c = *data;
if ( c == '"' || c == '\0' )
{
if ( c == '"' )
data++;
com_token[ len ] = '\0';
*data_p = data;
return com_token;
}
data++;
if ( c == '\n' )
{
com_lines++;
}
if ( len < ARRAY_LEN( com_token )-1 )
{
com_token[ len ] = c;
len++;
}
}
}
// parse a regular word
do
{
if ( len < ARRAY_LEN( com_token )-1 )
{
com_token[ len ] = c;
len++;
}
data++;
c = *data;
} while ( c > ' ' );
com_token[ len ] = '\0';
*data_p = data;
return com_token;
}
/*
==============
COM_ParseComplex
==============
*/
char *COM_ParseComplex( const char **data_p, qboolean allowLineBreaks )
{
static const byte is_separator[ 256 ] =
{
// \0 . . . . . . .\b\t\n . .\r . .
1,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,
// . . . . . . . . . . . . . . . .
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
// ! " # $ % & ' ( ) * + , - . /
1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0, // excl. '-' '.' '/'
// 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,
// @ A B C D E F G H I J K L M N O
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
// P Q R S T U V W X Y Z [ \ ] ^ _
0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,0, // excl. '\\' '_'
// ` a b c d e f g h i j k l m n o
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
// p q r s t u v w x y z { | } ~
0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1
};
int c, len, shift;
const byte *str;
str = (byte*)*data_p;
len = 0;
shift = 0; // token line shift relative to com_lines
com_tokentype = TK_GENEGIC;
__reswitch:
switch ( *str )
{
case '\0':
com_tokentype = TK_EOF;
break;
// whitespace
case ' ':
case '\t':
str++;
while ( (c = *str) == ' ' || c == '\t' )
str++;
goto __reswitch;
// newlines
case '\n':
case '\r':
com_lines++;
if ( *str == '\r' && str[1] == '\n' )
str += 2; // CR+LF
else
str++;
if ( !allowLineBreaks ) {
com_tokentype = TK_NEWLINE;
break;
}
goto __reswitch;
// comments, single slash
case '/':
// until end of line
if ( str[1] == '/' ) {
str += 2;
while ( (c = *str) != '\0' && c != '\n' && c != '\r' )
str++;
goto __reswitch;
}
// comment
if ( str[1] == '*' ) {
str += 2;
while ( (c = *str) != '\0' && ( c != '*' || str[1] != '/' ) ) {
if ( c == '\n' || c == '\r' ) {
com_lines++;
if ( c == '\r' && str[1] == '\n' ) // CR+LF?
str++;
}
str++;
}
if ( c != '\0' && str[1] != '\0' ) {
str += 2;
} else {
// FIXME: unterminated comment?
}
goto __reswitch;
}
// single slash
com_token[ len++ ] = *str++;
break;
// quoted string?
case '"':
str++; // skip leading '"'
//com_tokenline = com_lines;
while ( (c = *str) != '\0' && c != '"' ) {
if ( c == '\n' || c == '\r' ) {
com_lines++; // FIXME: unterminated quoted string?
shift++;
}
if ( len < MAX_TOKEN_CHARS-1 ) // overflow check
com_token[ len++ ] = c;
str++;
}
if ( c != '\0' ) {
str++; // skip ending '"'
} else {
// FIXME: unterminated quoted string?
}
com_tokentype = TK_QUOTED;
break;
// single tokens:
case '+': case '`':
/*case '*':*/ case '~':
case '{': case '}':
case '[': case ']':
case '?': case ',':
case ':': case ';':
case '%': case '^':
com_token[ len++ ] = *str++;
break;
case '*':
com_token[ len++ ] = *str++;
com_tokentype = TK_MATCH;
break;
case '(':
com_token[ len++ ] = *str++;
com_tokentype = TK_SCOPE_OPEN;
break;
case ')':
com_token[ len++ ] = *str++;
com_tokentype = TK_SCOPE_CLOSE;
break;
// !, !=
case '!':
com_token[ len++ ] = *str++;
if ( *str == '=' ) {
com_token[ len++ ] = *str++;
com_tokentype = TK_NEQ;
}
break;
// =, ==
case '=':
com_token[ len++ ] = *str++;
if ( *str == '=' ) {
com_token[ len++ ] = *str++;
com_tokentype = TK_EQ;
}
break;
// >, >=
case '>':
com_token[ len++ ] = *str++;
if ( *str == '=' ) {
com_token[ len++ ] = *str++;
com_tokentype = TK_GTE;
} else {
com_tokentype = TK_GT;
}
break;
// <, <=
case '<':
com_token[ len++ ] = *str++;
if ( *str == '=' ) {
com_token[ len++ ] = *str++;
com_tokentype = TK_LTE;
} else {
com_tokentype = TK_LT;
}
break;
// |, ||
case '|':
com_token[ len++ ] = *str++;
if ( *str == '|' ) {
com_token[ len++ ] = *str++;
com_tokentype = TK_OR;
}
break;
// &, &&
case '&':
com_token[ len++ ] = *str++;
if ( *str == '&' ) {
com_token[ len++ ] = *str++;
com_tokentype = TK_AND;
}
break;
// rest of the charset
default:
com_token[ len++ ] = *str++;
while ( !is_separator[ (c = *str) ] ) {
if ( len < MAX_TOKEN_CHARS-1 )
com_token[ len++ ] = c;
str++;
}
com_tokentype = TK_STRING;
break;
} // switch ( *str )
com_tokenline = com_lines - shift;
com_token[ len ] = '\0';
*data_p = ( char * )str;
return com_token;
}
/*
==================
COM_MatchToken
==================
*/
static void COM_MatchToken( const char **buf_p, const char *match ) {
const char *token;
token = COM_Parse( buf_p );
if ( strcmp( token, match ) ) {
Com_Error( ERR_DROP, "MatchToken: %s != %s", token, match );
}
}
/*
=================
SkipBracedSection
The next token should be an open brace or set depth to 1 if already parsed it.
Skips until a matching close brace is found.
Internal brace depths are properly skipped.
=================
*/
qboolean SkipBracedSection( const char **program, int depth ) {
const char *token;
do {
token = COM_ParseExt( program, qtrue );
if( token[1] == 0 ) {
if( token[0] == '{' ) {
depth++;
}
else if( token[0] == '}' ) {
depth--;
}
}
} while( depth && *program );
return ( depth == 0 );
}
/*
=================
SkipRestOfLine
=================
*/
void SkipRestOfLine( const char **data ) {
const char *p;
int c;
p = *data;
if ( !*p )
return;
while ( (c = *p) != '\0' ) {
p++;
if ( c == '\n' ) {
com_lines++;
break;
}
}
*data = p;
}
void Parse1DMatrix( const char **buf_p, int x, float *m ) {
const char *token;
int i;
COM_MatchToken( buf_p, "(" );
for (i = 0 ; i < x; i++) {
token = COM_Parse( buf_p );
m[i] = Q_atof( token );
}
COM_MatchToken( buf_p, ")" );
}
void Parse2DMatrix( const char **buf_p, int y, int x, float *m ) {
int i;
COM_MatchToken( buf_p, "(" );
for (i = 0 ; i < y ; i++) {
Parse1DMatrix (buf_p, x, m + i * x);
}
COM_MatchToken( buf_p, ")" );
}
void Parse3DMatrix( const char **buf_p, int z, int y, int x, float *m ) {
int i;
COM_MatchToken( buf_p, "(" );
for (i = 0 ; i < z ; i++) {
Parse2DMatrix (buf_p, y, x, m + i * x*y);
}
COM_MatchToken( buf_p, ")" );
}
static int Hex( char c )
{
if ( c >= '0' && c <= '9' ) {
return c - '0';
}
else
if ( c >= 'A' && c <= 'F' ) {
return 10 + c - 'A';
}
else
if ( c >= 'a' && c <= 'f' ) {
return 10 + c - 'a';
}
return -1;
}
/*
===================
Com_HexStrToInt
===================
*/
int Com_HexStrToInt( const char *str )
{
if ( !str )
return -1;
// check for hex code
if ( str[ 0 ] == '0' && str[ 1 ] == 'x' && str[ 2 ] != '\0' )
{
int i, digit, n = 0, len = strlen( str );
for( i = 2; i < len; i++ )
{
n *= 16;
digit = Hex( str[ i ] );
if ( digit < 0 )
return -1;
n += digit;
}
return n;
}
return -1;
}
qboolean Com_GetHashColor( const char *str, byte *color )
{
int i, len, hex[6];
color[0] = color[1] = color[2] = 0;
if ( *str++ != '#' ) {
return qfalse;
}
len = (int)strlen( str );
if ( len <= 0 || len > 6 ) {
return qfalse;
}
for ( i = 0; i < len; i++ ) {
hex[i] = Hex( str[i] );
if ( hex[i] < 0 ) {
return qfalse;
}
}
switch ( len ) {
case 3: // #rgb
color[0] = hex[0] << 4 | hex[0];
color[1] = hex[1] << 4 | hex[1];
color[2] = hex[2] << 4 | hex[2];
break;
case 6: // #rrggbb
color[0] = hex[0] << 4 | hex[1];
color[1] = hex[2] << 4 | hex[3];
color[2] = hex[4] << 4 | hex[5];
break;
default: // unsupported format
return qfalse;
}
return qtrue;
}
/*
============================================================================
LIBRARY REPLACEMENT FUNCTIONS
============================================================================
*/
const byte locase[ 256 ] = {
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,
0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,
0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,
0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,
0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,
0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,
0x40,0x61,0x62,0x63,0x64,0x65,0x66,0x67,
0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,
0x78,0x79,0x7a,0x5b,0x5c,0x5d,0x5e,0x5f,
0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,
0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,
0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,
0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,
0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,
0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f,
0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,
0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf,
0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,
0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf,
0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,
0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf,
0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,
0xd8,0xd9,0xda,0xdb,0xdc,0xdd,0xde,0xdf,
0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,
0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef,
0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,
0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff
};
int Q_isprint( int c )
{
if ( c >= 0x20 && c <= 0x7E )
return ( 1 );
return ( 0 );
}
int Q_islower( int c )
{
if (c >= 'a' && c <= 'z')
return ( 1 );
return ( 0 );
}
int Q_isupper( int c )
{
if (c >= 'A' && c <= 'Z')
return ( 1 );
return ( 0 );
}
int Q_isalpha( int c )
{
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
return ( 1 );
return ( 0 );
}
qboolean Q_isanumber( const char *s )
{
#ifdef Q3_VM
//FIXME: implement
return qfalse;
#else
char *p;
if( *s == '\0' )
return qfalse;
strtod( s, &p );
return *p == '\0';
#endif
}
qboolean Q_isintegral( float f )
{
return (int)f == f;
}
#ifdef _WIN32
/*
=============
Q_vsnprintf
Special wrapper function for Microsoft's broken _vsnprintf() function. mingw-w64
however, uses Microsoft's broken _vsnprintf() function.
=============
*/
int Q_vsnprintf( char *str, size_t size, const char *format, va_list ap )
{
int retval;
retval = _vsnprintf( str, size, format, ap );
if ( retval < 0 || (size_t)retval == size )
{
// Microsoft doesn't adhere to the C99 standard of vsnprintf,
// which states that the return value must be the number of
// bytes written if the output string had sufficient length.
//
// Obviously we cannot determine that value from Microsoft's
// implementation, so we have no choice but to return size.
str[size - 1] = '\0';
return size;
}
return retval;
}
#endif
/*
=============
Q_strncpyz
Safe strncpy that ensures a trailing zero
=============
*/
void Q_strncpyz( char *dest, const char *src, int destsize )
{
if ( !dest )
{
Com_Error( ERR_FATAL, "Q_strncpyz: NULL dest" );
}
if ( !src )
{
Com_Error( ERR_FATAL, "Q_strncpyz: NULL src" );
}
if ( destsize < 1 )
{
Com_Error(ERR_FATAL,"Q_strncpyz: destsize < 1" );
}
#if 1
// do not fill whole remaining buffer with zeros
// this is obvious behavior change but actually it may affect only buggy QVMs
// which passes overlapping or short buffers to cvar reading routines
// what is rather good than bad because it will no longer cause overwrites, maybe
while ( --destsize > 0 && (*dest++ = *src++) != '\0' )
;
*dest = '\0';
#else
strncpy( dest, src, destsize-1 );
dest[ destsize-1 ] = '\0';
#endif
}
int Q_stricmpn (const char *s1, const char *s2, int n) {
int c1, c2;
// bk001129 - moved in 1.17 fix not in id codebase
if ( s1 == NULL ) {
if ( s2 == NULL )
return 0;
else
return -1;
}
else if ( s2==NULL )
return 1;
do {
c1 = *s1++;
c2 = *s2++;
if (!n--) {
return 0; // strings are equal until end point
}
if (c1 != c2) {
if (c1 >= 'a' && c1 <= 'z') {
c1 -= ('a' - 'A');
}
if (c2 >= 'a' && c2 <= 'z') {
c2 -= ('a' - 'A');
}
if (c1 != c2) {
return c1 < c2 ? -1 : 1;
}
}
} while (c1);
return 0; // strings are equal
}
int Q_strncmp( const char *s1, const char *s2, int n ) {
int c1, c2;
do {
c1 = *s1++;
c2 = *s2++;
if (!n--) {
return 0; // strings are equal until end point
}
if (c1 != c2) {
return c1 < c2 ? -1 : 1;
}
} while (c1);
return 0; // strings are equal
}
qboolean Q_streq( const char *s1, const char *s2 ) {
int c1, c2;
do {
c1 = *s1++;
c2 = *s2++;
if ( c1 != c2 ) {
return qfalse;
}
} while ( c1 != '\0' );
return qtrue;
}
int Q_stricmp( const char *s1, const char *s2 )
{
unsigned char c1, c2;
if ( s1 == NULL )
{
if ( s2 == NULL )
return 0;
else
return -1;
}
else if ( s2 == NULL )
return 1;
do
{
c1 = *s1++;
c2 = *s2++;
if ( c1 != c2 )
{
if ( c1 <= 'Z' && c1 >= 'A' )
c1 += ('a' - 'A');
if ( c2 <= 'Z' && c2 >= 'A' )
c2 += ('a' - 'A');
if ( c1 != c2 )
return c1 < c2 ? -1 : 1;
}
}
while ( c1 != '\0' );
return 0;
}
char *Q_strlwr( char *s1 ) {
char *s;
s = s1;
while ( *s ) {
*s = locase[(byte)*s];
s++;
}
return s1;
}
char *Q_strupr( char *s1 ) {
char *s;
s = s1;
while ( *s ) {
if ( *s >= 'a' && *s <= 'z' )
*s = *s - 'a' + 'A';
s++;
}
return s1;
}
// never goes past bounds or leaves without a terminating 0
void Q_strcat( char *dest, int size, const char *src ) {
int l1;
l1 = strlen( dest );
if ( l1 >= size ) {
Com_Error( ERR_FATAL, "Q_strcat: already overflowed" );
}
Q_strncpyz( dest + l1, src, size - l1 );
}
char *Q_stradd( char *dst, const char *src )
{
char c;
while ( (c = *src++) != '\0' )
*dst++ = c;
*dst = '\0';
return dst;
}
/*
* Find the first occurrence of find in s.
*/
const char *Q_stristr( const char *s, const char *find)
{
char c, sc;
size_t len;
if ((c = *find++) != 0)
{
if (c >= 'a' && c <= 'z')
{
c -= ('a' - 'A');
}
len = strlen(find);
do
{
do
{
if ((sc = *s++) == 0)
return NULL;
if (sc >= 'a' && sc <= 'z')
{
sc -= ('a' - 'A');
}
} while (sc != c);
} while (Q_stricmpn(s, find, len) != 0);
s--;
}
return s;
}
int Q_replace( const char *str1, const char *str2, char *src, int max_len )
{
int len1, len2, d, count;
const char *s0, *s1, *s2, *max;
char *match, *dst;
match = strstr( src, str1 );
if ( !match )
return 0;
count = 0; // replace count
len1 = strlen( str1 );
len2 = strlen( str2 );
d = len2 - len1;
if ( d > 0 ) // expand and replace mode
{
max = src + max_len;
src += strlen( src );
do
{
// expand source string
s1 = src;
src += d;
if ( src >= max )
return count;
dst = src;
s0 = match + len1;
while ( s1 >= s0 )
*dst-- = *s1--;
// replace match
s2 = str2;
while ( *s2 ) {
*match++ = *s2++;
}
match = strstr( match, str1 );
count++;
}
while ( match );
return count;
}
else
if ( d < 0 ) // shrink and replace mode
{
do
{
// shrink source string
s1 = match + len1;
dst = match + len2;
while ( (*dst++ = *s1++) != '\0' );
//replace match
s2 = str2;
while ( *s2 ) {
*match++ = *s2++;
}
match = strstr( match, str1 );
count++;
}
while ( match );
return count;
}
else
do // just replace match
{
s2 = str2;
while ( *s2 ) {
*match++ = *s2++;
}
match = strstr( match, str1 );
count++;
}
while ( match );
return count;
}
int Q_PrintStrlen( const char *string ) {
int len;
const char *p;
if( !string ) {
return 0;
}
len = 0;
p = string;
while( *p ) {
if( Q_IsColorString( p ) ) {
p += 2;
continue;
}
p++;
len++;
}
return len;
}
char *Q_CleanStr( char *string ) {
char* d;
char* s;
int c;
s = string;
d = string;
while ((c = *s) != 0 ) {
if ( Q_IsColorString( s ) ) {
s++;
}
else if ( c >= 0x20 && c <= 0x7E ) {
*d++ = c;
}
s++;
}
*d = '\0';
return string;
}
int Q_CountChar(const char *string, char tocount)
{
int count;
for(count = 0; *string; string++)
{
if(*string == tocount)
count++;
}
return count;
}
#if defined(_DEBUG) && defined(_WIN32)
#include <windows.h>
#endif
int QDECL Com_sprintf( char *dest, int size, const char *fmt, ...)
{
int len;
va_list argptr;
char bigbuffer[32000]; // big, but small enough to fit in PPC stack
if ( !dest )
{
Com_Error( ERR_FATAL, "Com_sprintf: NULL dest" );
#if defined(_DEBUG) && defined(_WIN32)
DebugBreak();
#endif
return 0;
}
va_start( argptr, fmt );
len = vsprintf( bigbuffer, fmt, argptr );
va_end( argptr );
if ( len >= sizeof( bigbuffer ) || len < 0 )
{
Com_Error( ERR_FATAL, "Com_sprintf: overflowed bigbuffer" );
#if defined(_DEBUG) && defined(_WIN32)
DebugBreak();
#endif
return 0;
}
if ( len >= size )
{
Com_Printf( S_COLOR_YELLOW "Com_sprintf: overflow of %i in %i\n", len, size );
#if defined(_DEBUG) && defined(_WIN32)
DebugBreak();
#endif
len = size - 1;
}
//Q_strncpyz( dest, bigbuffer, size );
//strncpy( dest, bigbuffer, len );
memcpy( dest, bigbuffer, len );
dest[ len ] = '\0';
return len;
}
/*
============
va
does a varargs printf into a temp buffer, so I don't need to have
varargs versions of all text functions.
FIXME: make this buffer size safe someday
============
*/
const char *QDECL va( const char *format, ... )
{
char *buf;
va_list argptr;
static int index = 0;
static char string[2][32000]; // in case va is called by nested functions
buf = string[ index ];
index ^= 1;
va_start( argptr, format );
vsprintf( buf, format, argptr );
va_end( argptr );
return buf;
}
/*
============
Com_TruncateLongString
Assumes buffer is at least TRUNCATE_LENGTH big
============
*/
void Com_TruncateLongString( char *buffer, const char *s )
{
int length = strlen( s );
if( length <= TRUNCATE_LENGTH )
Q_strncpyz( buffer, s, TRUNCATE_LENGTH );
else
{
Q_strncpyz( buffer, s, ( TRUNCATE_LENGTH / 2 ) - 3 );
Q_strcat( buffer, TRUNCATE_LENGTH, " ... " );
Q_strcat( buffer, TRUNCATE_LENGTH, s + length - ( TRUNCATE_LENGTH / 2 ) + 3 );
}
}
/*
=====================================================================
INFO STRINGS
=====================================================================
*/
static qboolean Q_strkey( const char *str, const char *key, int key_len )
{
int i;
for ( i = 0; i < key_len; i++ )
{
if ( locase[ (byte)str[i] ] != locase[ (byte)key[i] ] )
{
return qfalse;
}
}
return qtrue;
}
/*
===============
Info_ValueForKey
Searches the string for the given
key and returns the associated value, or an empty string.
===============
*/
const char *Info_ValueForKey( const char *s, const char *key )
{
static char value[2][BIG_INFO_VALUE]; // use two buffers so compares
// work without stomping on each other
static int valueindex = 0;
const char *v, *pkey;
char *o, *o2;
int klen, len;
if ( !s || !key || !*key )
return "";
klen = (int)strlen( key );
if ( *s == '\\' )
s++;
while (1)
{
pkey = s;
while ( *s != '\\' )
{
if ( *s == '\0' )
return "";
++s;
}
len = (int)(s - pkey);
s++; // skip '\\'
v = s;
while ( *s != '\\' && *s !='\0' )
s++;
if ( len == klen && Q_strkey( pkey, key, klen ) )
{
o = o2 = value[ valueindex ^= 1 ];
if ( (int)(s - v) >= BIG_INFO_VALUE )
{
Com_Error( ERR_DROP, "Info_ValueForKey: oversize infostring" );
}
else
{
while ( v < s )
*o++ = *v++;
}
*o = '\0';
return o2;
}
if ( *s == '\0' )
break;
s++;
}
return "";
}
#define MAX_INFO_TOKENS ((MAX_INFO_STRING/3)+2)
static const char *info_keys[ MAX_INFO_TOKENS ];
static const char *info_values[ MAX_INFO_TOKENS ];
static int info_tokens;
/*
===================
Info_Tokenize
Tokenizes all key/value pairs from specified infostring
NOT suitable for big infostrings
===================
*/
void Info_Tokenize( const char *s )
{
static char tokenBuffer[ MAX_INFO_STRING ];
char *o = tokenBuffer;
info_tokens = 0;
*o = '\0';
for ( ;; )
{
while ( *s == '\\' ) // skip leading/trailing separators
s++;
if ( *s == '\0' )
break;
info_keys[ info_tokens ] = o;
while ( *s != '\\' )
{
if ( *s == '\0' )
{
*o = '\0'; // terminate key
info_values[ info_tokens++ ] = o;
return;
}
*o++ = *s++;
}
*o++ = '\0'; // terminate key
s++; // skip '\\'
info_values[ info_tokens++ ] = o;
while ( *s != '\\' && *s != '\0' )
{
*o++ = *s++;
}
*o++ = '\0';
}
}
/*
===================
Info_ValueForKeyToken
Fast lookup from tokenized infostring
===================
*/
const char *Info_ValueForKeyToken( const char *key )
{
int i;
for ( i = 0; i < info_tokens; i++ )
{
if ( Q_stricmp( info_keys[ i ], key ) == 0 )
{
return info_values[ i ];
}
}
return "";
}
/*
===================
Info_NextPair
Used to iterate through all the key/value pairs in an info string
===================
*/
const char *Info_NextPair( const char *s, char *key, char *value ) {
char *o;
if ( *s == '\\' ) {
s++;
}
key[0] = '\0';
value[0] = '\0';
o = key;
while ( *s != '\\' ) {
if ( !*s ) {
*o = '\0';
return s;
}
*o++ = *s++;
}
*o = '\0';
s++;
o = value;
while ( *s != '\\' && *s ) {
*o++ = *s++;
}
*o = '\0';
return s;
}
/*
===================
Info_RemoveKey
===================
*/
int Info_RemoveKey( char *s, const char *key )
{
char *start;
const char *pkey;
int key_len, len;
key_len = (int) strlen( key );
while (1)
{
start = s;
if ( *s == '\\' )
s++;
pkey = s;
while ( *s != '\\' )
{
if ( *s == '\0' )
return 0;
++s;
}
len = (int)(s - pkey);
++s; // skip '\\'
while ( *s != '\\' && *s != '\0' )
++s;
if ( len == key_len && Q_strkey( pkey, key, key_len ) )
{
memmove( start, s, strlen( s ) + 1 ); // remove this part
return (int)(s - start);
}
if ( *s == '\0' )
break;
}
return 0;
}
/*
==================
Info_Validate
Some characters are illegal in info strings because they
can mess up the server's parsing
==================
*/
qboolean Info_Validate( const char *s )
{
for ( ;; )
{
switch ( *s++ )
{
case '\0':
return qtrue;
case '\"':
case ';':
return qfalse;
default:
break;
}
}
}
/*
==================
Info_ValidateKeyValue
Some characters are illegal in key values because they
can mess up the server's parsing
==================
*/
qboolean Info_ValidateKeyValue( const char *s )
{
for ( ;; )
{
switch ( *s++ )
{
case '\0':
return qtrue;
case '\\':
case '\"':
case ';':
return qfalse;
default:
break;
}
}
}
/*
==================
Info_SetValueForKey_s
Changes or adds a key/value pair
==================
*/
qboolean Info_SetValueForKey_s( char *s, int slen, const char *key, const char *value ) {
char newi[BIG_INFO_STRING+2];
int len1, len2;
len1 = (int)strlen( s );
if ( len1 >= slen ) {
Com_Printf( S_COLOR_YELLOW "Info_SetValueForKey(%s): oversize infostring\n", key );
return qfalse;
}
if ( !key || !Info_ValidateKeyValue( key ) || *key == '\0' ) {
Com_Printf( S_COLOR_YELLOW "Invalid key name: '%s'\n", key );
return qfalse;
}
if ( value && !Info_ValidateKeyValue( value ) ) {
Com_Printf( S_COLOR_YELLOW "Invalid value name: '%s'\n", value );
return qfalse;
}
len1 -= Info_RemoveKey( s, key );
if ( !value || !*value )
return qtrue;
len2 = Com_sprintf( newi, sizeof( newi ), "\\%s\\%s", key, value );
if ( len1 + len2 >= slen )
{
Com_Printf( S_COLOR_YELLOW "Info string length exceeded for key '%s'\n", key );
return qfalse;
}
strcpy( s + len1, newi );
return qtrue;
}
//====================================================================
/*
==================
Com_CharIsOneOfCharset
==================
*/
static qboolean Com_CharIsOneOfCharset( char c, const char *set )
{
int i, n = (int)(strlen(set));
for( i = 0; i < n; i++ )
{
if( set[ i ] == c )
return qtrue;
}
return qfalse;
}
/*
==================
Com_SkipCharset
==================
*/
const char *Com_SkipCharset( const char *s, const char *sep )
{
const char *p = s;
while( p )
{
if( Com_CharIsOneOfCharset( *p, sep ) )
p++;
else
break;
}
return p;
}
/*
==================
Com_SkipTokens
==================
*/
const char *Com_SkipTokens( const char *s, int numTokens, const char *sep )
{
int sepCount = 0;
const char *p = s;
while( sepCount < numTokens )
{
if( Com_CharIsOneOfCharset( *p++, sep ) )
{
sepCount++;
while( Com_CharIsOneOfCharset( *p, sep ) )
p++;
}
else if( *p == '\0' )
break;
}
if( sepCount == numTokens )
return p;
else
return s;
}