/* Copyright (C) 1997-2001 Id Software, Inc. 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 2 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include "q_shared.h" vec3_t vec3_origin = { 0, 0, 0 }; void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) { float angle; float sr, sp, sy, cr, cp, cy; angle = angles[YAW] * (M_PI*2 / 360); sy = sin(angle); cy = cos(angle); angle = angles[PITCH] * (M_PI*2 / 360); sp = sin(angle); cp = cos(angle); angle = angles[ROLL] * (M_PI*2 / 360); sr = sin(angle); cr = cos(angle); if (forward) { forward[0] = cp*cy; forward[1] = cp*sy; forward[2] = -sp; } if (right) { right[0] = (-1*sr*sp*cy+-1*cr*-sy); right[1] = (-1*sr*sp*sy+-1*cr*cy); right[2] = -1*sr*cp; } if (up) { up[0] = (cr*sp*cy+-sr*-sy); up[1] = (cr*sp*sy+-sr*cy); up[2] = cr*cp; } } vec_t VectorNormalize (vec3_t v) { float length, ilength; length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; length = sqrt (length); // FIXME if (length) { ilength = 1/length; v[0] *= ilength; v[1] *= ilength; v[2] *= ilength; } return length; } vec_t VectorNormalize2 (vec3_t v, vec3_t out) { float length, ilength; length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; length = sqrt (length); // FIXME if (length) { ilength = 1/length; out[0] = v[0]*ilength; out[1] = v[1]*ilength; out[2] = v[2]*ilength; } return length; } void ClearBounds (vec3_t mins, vec3_t maxs) { mins[0] = mins[1] = mins[2] = 99999; maxs[0] = maxs[1] = maxs[2] = -99999; } void AddPointToBounds (const vec3_t v, vec3_t mins, vec3_t maxs) { int i; vec_t val; for (i=0 ; i<3 ; i++) { val = v[i]; if (val < mins[i]) mins[i] = val; if (val > maxs[i]) maxs[i] = val; } } void UnionBounds( vec3_t a[2], vec3_t b[2], vec3_t c[2] ) { c[0][0] = b[0][0] < a[0][0] ? b[0][0] : a[0][0]; c[0][1] = b[0][1] < a[0][1] ? b[0][1] : a[0][1]; c[0][2] = b[0][2] < a[0][2] ? b[0][2] : a[0][2]; c[1][0] = b[1][0] > a[1][0] ? b[1][0] : a[1][0]; c[1][1] = b[1][1] > a[1][1] ? b[1][1] : a[1][1]; c[1][2] = b[1][2] > a[1][2] ? b[1][2] : a[1][2]; } /* ================= RadiusFromBounds ================= */ vec_t RadiusFromBounds (const vec3_t mins, const vec3_t maxs) { int i; vec3_t corner; vec_t a, b; for (i=0 ; i<3 ; i++) { a = Q_fabs(mins[i]); b = Q_fabs(maxs[i]); corner[i] = a > b ? a : b; } return VectorLength (corner); } //==================================================================================== static const char hexchars[] = "0123456789ABCDEF"; /* ============ COM_SkipPath ============ */ char *COM_SkipPath( const char *pathname ) { char *last; if( !pathname ) { Com_Error( ERR_FATAL, "COM_SkipPath: NULL" ); } last = (char *)pathname; while( *pathname ) { if( *pathname == '/' ) last = (char *)pathname + 1; pathname++; } return last; } /* ============ COM_StripExtension ============ */ void COM_StripExtension( const char *in, char *out, size_t size ) { char *s; Q_strlcpy( out, in, size ); s = out + strlen( out ); while( s != out ) { if( *s == '/' ) { break; } if( *s == '.' ) { *s = 0; break; } s--; } } /* ============ COM_FileExtension ============ */ char *COM_FileExtension( const char *in ) { const char *s; const char *last; if( !in ) { Com_Error( ERR_FATAL, "%s: NULL", __func__ ); } s = in + strlen( in ); last = s; while( s != in ) { if( *s == '/' ) { break; } if( *s == '.' ) { return (char *)s; } s--; } return (char *)last; } /* ============ COM_FileBase ============ */ void COM_FileBase (char *in, char *out) { char *s, *s2; s = in + strlen(in) - 1; while (s != in && *s != '.') s--; for (s2 = s ; s2 != in && *s2 != '/' ; s2--) ; if (s-s2 < 2) out[0] = 0; else { s--; strncpy (out,s2+1, s-s2); out[s-s2] = 0; } } /* ============ COM_FilePath Returns the path up to, but not including the last / ============ */ void COM_FilePath( const char *in, char *out, size_t size ) { char *s; Q_strlcpy( out, in, size ); s = strrchr( out, '/' ); if( s ) { *s = 0; } else { *out = 0; } } /* ================== COM_DefaultExtension if path doesn't have .EXT, append extension (extension should include the .) ================== */ size_t COM_DefaultExtension( char *path, const char *ext, size_t size ) { char *src; size_t len; if( *path ) { len = strlen( path ); src = path + len - 1; while( *src != '/' && src != path ) { if( *src == '.' ) return len; // it has an extension src--; } } len = Q_strlcat( path, ext, size ); return len; } /* ================== COM_IsFloat Returns true if the given string is valid representation of floating point number. ================== */ qboolean COM_IsFloat( const char *s ) { int c, dot = '.'; if( *s == '-' ) { s++; } if( !*s ) { return qfalse; } do { c = *s++; if( c == dot ) { dot = 0; } else if( !Q_isdigit( c ) ) { return qfalse; } } while( *s ); return qtrue; } qboolean COM_IsUint( const char *s ) { int c; if( !*s ) { return qfalse; } do { c = *s++; if( !Q_isdigit( c ) ) { return qfalse; } } while( *s ); return qtrue; } qboolean COM_HasSpaces( const char *s ) { while( *s ) { if( *s <= 32 ) { return qtrue; } s++; } return qfalse; } unsigned COM_ParseHex( const char *s ) { int c; unsigned result; for( result = 0; *s; s++ ) { if( ( c = Q_charhex( *s ) ) == -1 ) { break; } if( result & ~( UINT_MAX >> 4 ) ) { return UINT_MAX; } result = c | ( result << 4 ); } return result; } /* ================= SortStrcmp ================= */ int QDECL SortStrcmp( const void *p1, const void *p2 ) { const char *s1 = *( const char ** )p1; const char *s2 = *( const char ** )p2; return strcmp( s1, s2 ); } int QDECL SortStricmp( const void *p1, const void *p2 ) { const char *s1 = *( const char ** )p1; const char *s2 = *( const char ** )p2; return Q_stricmp( s1, s2 ); } /* ================= Com_WildCmp Wildcard compare. Returns non-zero if matches, zero otherwise. ================= */ int Com_WildCmp( const char *filter, const char *string, qboolean ignoreCase ) { switch( *filter ) { case '\0': return !*string; case '*': return Com_WildCmp( filter + 1, string, ignoreCase ) || (*string && Com_WildCmp( filter, string + 1, ignoreCase )); case '?': return *string && Com_WildCmp( filter + 1, string + 1, ignoreCase ); default: return ((*filter == *string) || (ignoreCase && (Q_toupper( *filter ) == Q_toupper( *string )))) && Com_WildCmp( filter + 1, string + 1, ignoreCase ); } } /* ================ Com_HashString ================ */ unsigned Com_HashString( const char *string, int hashSize ) { unsigned hash, c; hash = 0; while( *string ) { c = *string++; hash = 127 * hash + c; } hash = ( hash >> 20 ) ^ ( hash >> 10 ) ^ hash; return hash & ( hashSize - 1 ); } /* ================ Com_HashPath ================ */ unsigned Com_HashPath( const char *string, int hashSize ) { unsigned hash, c; hash = 0; while( *string ) { c = *string++; if( c == '\\' ) { c = '/'; } else { c = Q_tolower( c ); } hash = 127 * hash + c; } hash = ( hash >> 20 ) ^ ( hash >> 10 ) ^ hash; return hash & ( hashSize - 1 ); } /* ================ Q_ClearStr Removes high-bit and unprintable characters. Return number of characters written, not including the NULL character. ================ */ int Q_ClearStr( char *out, const char *in, int bufsize ) { char *p, *m; int c; if( bufsize < 1 ) { Com_Error( ERR_FATAL, "%s: bad bufsize: %d", __func__, bufsize ); } p = out; m = out + bufsize - 1; while( *in && p < m ) { c = *in++; c &= 127; if( Q_isprint( c ) ) { *p++ = c; } } *p = 0; return p - out; } int Q_HighlightStr( char *out, const char *in, int bufsize ) { char *p, *m; int c; if( bufsize < 1 ) { Com_Error( ERR_FATAL, "%s: bad bufsize: %d", __func__, bufsize ); } p = out; m = out + bufsize - 1; while( *in && p < m ) { c = *in++; c |= 128; *p++ = c; } *p = 0; return p - out; } /* ================ Q_IsWhiteSpace ================ */ qboolean Q_IsWhiteSpace( const char *s ) { int c; while( *s ) { c = *s++; c &= 127; if( Q_isgraph( c ) ) { return qfalse; } } return qtrue; } size_t Q_FormatFileSize( char *dest, size_t bytes, size_t size ) { if( bytes >= 1000000 ) { return Q_snprintf( dest, size, "%2.1fM", ( float )bytes / 1000000 ); } if( bytes >= 1000 ) { return Q_snprintf( dest, size, "%3"PRIz"K", bytes / 1000 ); } return Q_snprintf( dest, size, "%3"PRIz, bytes ); } /* ============ 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 ============ */ char *va( const char *format, ... ) { va_list argptr; static char buffers[2][0x2800]; static int index; index ^= 1; va_start( argptr, format ); Q_vsnprintf( buffers[index], sizeof( buffers[0] ), format, argptr ); va_end( argptr ); return buffers[index]; } static char com_token[4][MAX_TOKEN_CHARS]; static int com_tokidx; /* ============== COM_Parse Parse a token out of a string. Handles C and C++ comments. ============== */ char *COM_Parse( const char **data_p ) { int c; int len; const char *data; char *s = com_token[com_tokidx++ & 3]; data = *data_p; len = 0; s[0] = 0; if( !data ) { *data_p = NULL; return s; } // skip whitespace skipwhite: while( ( c = *data ) <= ' ' ) { if( c == 0 ) { *data_p = NULL; return s; } data++; } // skip // comments if( c == '/' && data[1] == '/' ) { data += 2; while( *data && *data != '\n' ) data++; goto skipwhite; } // skip /* */ comments if( c == '/' && data[1] == '*' ) { data += 2; while( *data ) { if( data[0] == '*' && data[1] == '/' ) { data += 2; break; } data++; } goto skipwhite; } // handle quoted strings specially if( c == '\"' ) { data++; while( 1 ) { c = *data++; if( c == '\"' || !c ) { goto finish; } if( len < MAX_TOKEN_CHARS - 1 ) { s[len++] = c; } } } // parse a regular word do { if( len < MAX_TOKEN_CHARS - 1 ) { s[len++] = c; } data++; c = *data; } while( c > 32 ); finish: s[len] = 0; *data_p = data; return s; } /* ============== COM_Compress Operates in place, removing excess whitespace and comments. Non-contiguous line feeds are preserved. Returns resulting data length. ============== */ int COM_Compress( char *data ) { int c, n = 0; char *s = data, *d = data; while( *s ) { // skip whitespace if( *s <= ' ' ) { n = ' '; do { c = *s++; if( c == '\n' ) { n = '\n'; } if( !c ) { goto finish; } } while( *s <= ' ' ); } // skip // comments if( s[0] == '/' && s[1] == '/' ) { n = ' '; s += 2; while( *s && *s != '\n' ) { s++; } continue; } // skip /* */ comments if( s[0] == '/' && s[1] == '*' ) { n = ' '; s += 2; while( *s ) { if( s[0] == '*' && s[1] == '/' ) { s += 2; break; } s++; } continue; } // add whitespace character if( n ) { *d++ = n; n = 0; } // handle quoted strings specially if( *s == '\"' ) { s++; *d++ = '\"'; do { c = *s++; if( !c ) { goto finish; } *d++ = c; } while( c != '\"' ); continue; } // handle line feed escape if( *s == '\\' && s[1] == '\n' ) { s += 2; continue; } // parse a regular word do { *d++ = *s++; } while( *s > ' ' ); } finish: *d = 0; return d - data; } /* ============================================================================ LIBRARY REPLACEMENT FUNCTIONS ============================================================================ */ int Q_strncasecmp( const char *s1, const char *s2, size_t n ) { int c1, c2; do { c1 = *s1++; c2 = *s2++; if( !n-- ) return 0; /* strings are equal until end point */ if( c1 != c2 ) { c1 = Q_tolower( c1 ); c2 = Q_tolower( c2 ); if( c1 < c2 ) return -1; if( c1 > c2 ) return 1; /* strings not equal */ } } while( c1 ); return 0; /* strings are equal */ } int Q_strcasecmp( const char *s1, const char *s2 ) { int c1, c2; do { c1 = *s1++; c2 = *s2++; if( c1 != c2 ) { c1 = Q_tolower( c1 ); c2 = Q_tolower( c2 ); if( c1 < c2 ) return -1; if( c1 > c2 ) return 1; /* strings not equal */ } } while( c1 ); return 0; /* strings are equal */ } char *Q_strcasestr( const char *s1, const char *s2 ) { size_t l1, l2; l2 = strlen( s2 ); if( !l2 ) { return ( char * )s1; } l1 = strlen( s1 ); while( l1 >= l2 ) { l1--; if( !Q_strncasecmp( s1, s2, l2 ) ) { return ( char * )s1; } s1++; } return NULL; } /* =============== Q_strlcpy Returns length of the source string. =============== */ size_t Q_strlcpy( char *dst, const char *src, size_t size ) { size_t ret = strlen( src ); if( size ) { size_t len = ret >= size ? size - 1 : ret; memcpy( dst, src, len ); dst[len] = 0; } return ret; } /* =============== Q_strlcat Returns length of the source and destinations strings combined. =============== */ size_t Q_strlcat( char *dst, const char *src, size_t size ) { size_t ret, len = strlen( dst ); if( len >= size ) { Com_Error( ERR_FATAL, "%s: already overflowed", __func__ ); } ret = Q_strlcpy( dst + len, src, size - len ); ret += len; return ret; } /* =============== Q_concat Returns number of characters that would be written into the buffer, excluding trailing '\0'. If the returned value is equal to or greater than buffer size, resulting string is truncated. =============== */ size_t Q_concat( char *dest, size_t size, ... ) { va_list argptr; const char *s; size_t len, total = 0; va_start( argptr, size ); while( ( s = va_arg( argptr, const char * ) ) != NULL ) { len = strlen( s ); if( total + len < size ) { memcpy( dest, s, len ); dest += len; } total += len; } va_end( argptr ); if( size ) { *dest = 0; } return total; } /* =============== Q_vsnprintf Returns number of characters that would be written into the buffer, excluding trailing '\0'. If the returned value is equal to or greater than buffer size, resulting string is truncated. =============== */ size_t Q_vsnprintf( char *dest, size_t size, const char *fmt, va_list argptr ) { int ret; if( size > INT_MAX ) { Com_Error( ERR_FATAL, "%s: bad buffer size", __func__ ); } #ifdef _WIN32 // work around broken M$ C runtime semantics. if( !size ) { return 0; } ret = _vsnprintf( dest, size - 1, fmt, argptr ); if( ret >= size - 1 ) { dest[size - 1] = 0; } #else ret = vsnprintf( dest, size, fmt, argptr ); #endif // this shouldn't happen if( ret < 0 ) { if( size ) { *dest = 0; } ret = 0; } return ( size_t )ret; } /* =============== Q_vscnprintf Returns number of characters actually written into the buffer, excluding trailing '\0'. If buffer size is 0, this function does nothing and returns 0. =============== */ size_t Q_vscnprintf( char *dest, size_t size, const char *fmt, va_list argptr ) { size_t ret; if( !size ) { return 0; } ret = Q_vsnprintf( dest, size, fmt, argptr ); return ret >= size ? size - 1 : ret; } /* =============== Q_snprintf Returns number of characters that would be written into the buffer, excluding trailing '\0'. If the returned value is equal to or greater than buffer size, resulting string is truncated. =============== */ size_t Q_snprintf( char *dest, size_t size, const char *fmt, ... ) { va_list argptr; size_t ret; va_start( argptr, fmt ); ret = Q_vsnprintf( dest, size, fmt, argptr ); va_end( argptr ); return ret; } /* =============== Q_scnprintf Returns number of characters actually written into the buffer, excluding trailing '\0'. If buffer size is 0, this function does nothing and returns 0. =============== */ size_t Q_scnprintf( char *dest, size_t size, const char *fmt, ... ) { va_list argptr; size_t ret; va_start( argptr, fmt ); ret = Q_vscnprintf( dest, size, fmt, argptr ); va_end( argptr ); return ret; } char *Q_strchrnul( const char *s, int c ) { while( *s && *s != c ) { s++; } return ( char * )s; } void Q_setenv( const char *name, const char *value ) { #ifdef _WIN32 #ifndef __COREDLL__ if( !value ) { value = ""; } #if( _MSC_VER >= 1400 ) _putenv_s( name, value ); #else _putenv( va( "%s=%s", name, value ) ); #endif #endif #else // _WIN32 if( value ) { setenv( name, value, 1 ); } else { unsetenv( name ); } #endif // !_WIN32 } /* ===================================================================== INFO STRINGS ===================================================================== */ /* =============== Info_ValueForKey Searches the string for the given key and returns the associated value, or an empty string. =============== */ char *Info_ValueForKey( const char *s, const char *key ) { static char value[4][MAX_INFO_STRING]; // use 4 buffers so compares // work without stomping on each other static int valueindex; char pkey[MAX_INFO_STRING]; char *o; valueindex++; if( *s == '\\' ) s++; while( 1 ) { o = pkey; while( *s != '\\' ) { if( !*s ) return ""; *o++ = *s++; } *o = 0; s++; o = value[valueindex & 3]; while( *s != '\\' && *s ) { *o++ = *s++; } *o = 0; if( !strcmp( key, pkey ) ) return value[valueindex & 3]; if( !*s ) return ""; s++; } return ""; } /* ================== Info_RemoveKey ================== */ void Info_RemoveKey( char *s, const char *key ) { char *start; char pkey[MAX_INFO_STRING]; char *o; while( 1 ) { start = s; if( *s == '\\' ) s++; o = pkey; while( *s != '\\' ) { if( !*s ) return; *o++ = *s++; } *o = 0; s++; while( *s != '\\' && *s ) { s++; } if( !strcmp( key, pkey ) ) { o = start; // remove this part while( *s ) { *o++ = *s++; } *o = 0; s = start; continue; // search for duplicates } if( !*s ) return; } } /* ================== Info_Validate Some characters are illegal in info strings because they can mess up the server's parsing. Also checks the length of keys/values and the whole string. ================== */ qboolean Info_Validate( const char *s ) { size_t len, total; int c; total = 0; while( 1 ) { // // validate key // if( *s == '\\' ) { s++; if( ++total == MAX_INFO_STRING ) { return qfalse; // oversize infostring } } if( !*s ) { return qfalse; // missing key } len = 0; while( *s != '\\' ) { c = *s++; if( !Q_isprint( c ) || c == '\"' || c == ';' ) { return qfalse; // illegal characters } if( ++len == MAX_INFO_KEY ) { return qfalse; // oversize key } if( ++total == MAX_INFO_STRING ) { return qfalse; // oversize infostring } if( !*s ) { return qfalse; // missing value } } // // validate value // s++; if( ++total == MAX_INFO_STRING ) { return qfalse; // oversize infostring } if( !*s ) { return qfalse; // missing value } len = 0; while( *s != '\\' ) { c = *s++; if( !Q_isprint( c ) || c == '\"' || c == ';' ) { return qfalse; // illegal characters } if( ++len == MAX_INFO_VALUE ) { return qfalse; // oversize value } if( ++total == MAX_INFO_STRING ) { return qfalse; // oversize infostring } if( !*s ) { return qtrue; // end of string } } } return qfalse; // quiet compiler warning } /* ============ Info_ValidateSubstring ============ */ int Info_SubValidate( const char *s ) { const char *start; int c, len; for( start = s; *s; s++ ) { c = *s & 127; if( c == '\\' || c == '\"' || c == ';' ) { return -1; } } len = s - start; if( len >= MAX_QPATH ) { return -1; } return len; } /* ================== Info_SetValueForKey ================== */ qboolean Info_SetValueForKey( char *s, const char *key, const char *value ) { char newi[MAX_INFO_STRING], *v; int c, l, kl, vl; // validate key kl = Info_SubValidate( key ); if( kl == -1 ) { return qfalse; } // validate value vl = Info_SubValidate( value ); if( vl == -1 ) { return qfalse; } Info_RemoveKey( s, key ); if( !vl ) { return qtrue; } l = ( int )strlen( s ); if( l + kl + vl + 2 >= MAX_INFO_STRING ) { return qfalse; } newi[0] = '\\'; memcpy( newi + 1, key, kl ); newi[kl + 1] = '\\'; memcpy( newi + kl + 2, value, vl + 1 ); // only copy ascii values s += l; v = newi; while( *v ) { c = *v++; c &= 127; // strip high bits if( Q_isprint( c ) ) *s++ = c; } *s = 0; return qtrue; } /* ================== Info_NextPair ================== */ void Info_NextPair( const char **string, char *key, char *value ) { char *o; const char *s; *value = *key = 0; s = *string; if( !s ) { return; } if( *s == '\\' ) s++; if( !*s ) { *string = NULL; return; } o = key; while( *s && *s != '\\' ) { *o++ = *s++; } *o = 0; if( !*s ) { *string = NULL; return; } o = value; s++; while( *s && *s != '\\' ) { *o++ = *s++; } *o = 0; if( *s ) { s++; } *string = s; } /* ================== Info_Print ================== */ void Info_Print( const char *infostring ) { char key[MAX_INFO_STRING]; char value[MAX_INFO_STRING]; while( infostring ) { Info_NextPair( &infostring, key, value ); if( !key[0] ) { break; } if( value[0] ) { Com_Printf( "%-20s %s\n", key, value ); } else { Com_Printf( "%-20s \n", key ); } } }