/* =========================================================================== 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 #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; }