summaryrefslogtreecommitdiff
path: root/src/q_shared.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/q_shared.c')
-rw-r--r--src/q_shared.c1229
1 files changed, 1229 insertions, 0 deletions
diff --git a/src/q_shared.c b/src/q_shared.c
new file mode 100644
index 0000000..a032749
--- /dev/null
+++ b/src/q_shared.c
@@ -0,0 +1,1229 @@
+/*
+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 <config.h>
+#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, "%s: NULL", __func__ );
+ }
+
+ 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;
+}
+
+int QDECL SortStrcmp( const void *p1, const void *p2 ) {
+ return strcmp( *( const char ** )p1, *( const char ** )p2 );
+}
+
+int QDECL SortStricmp( const void *p1, const void *p2 ) {
+ return Q_stricmp( *( const char ** )p1, *( const char ** )p2 );
+}
+
+/*
+================
+COM_strclr
+
+Operates inplace, normalizing high-bit and removing unprintable characters.
+Returns final number of characters, not including the NUL character.
+================
+*/
+size_t COM_strclr( char *s ) {
+ char *p;
+ int c;
+ size_t len;
+
+ p = s;
+ len = 0;
+ while( *s ) {
+ c = *s++;
+ c &= 127;
+ if( Q_isprint( c ) ) {
+ *p++ = c;
+ len++;
+ }
+ }
+
+ *p = 0;
+
+ return len;
+}
+
+qboolean COM_iswhite( const char *s ) {
+ int c;
+
+ while( *s ) {
+ c = *s++;
+ c &= 127;
+ if( Q_isgraph( c ) ) {
+ return qfalse;
+ }
+ }
+
+ return qtrue;
+}
+
+size_t COM_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_SubValidate
+============
+*/
+size_t Info_SubValidate( const char *s ) {
+ size_t len;
+ int c;
+
+ len = 0;
+ while( *s ) {
+ c = *s++;
+ c &= 127; // strip high bits
+ if( c == '\\' || c == '\"' || c == ';' ) {
+ return SIZE_MAX; // illegal characters
+ }
+ if( ++len == MAX_QPATH ) {
+ return MAX_QPATH; // oversize value
+ }
+ }
+
+ return len;
+}
+
+/*
+==================
+Info_SetValueForKey
+==================
+*/
+qboolean Info_SetValueForKey( char *s, const char *key, const char *value ) {
+ char newi[MAX_INFO_STRING], *v;
+ size_t l, kl, vl;
+ int c;
+
+ // validate key
+ kl = Info_SubValidate( key );
+ if( kl >= MAX_QPATH ) {
+ return qfalse;
+ }
+
+ // validate value
+ vl = Info_SubValidate( value );
+ if( vl >= MAX_QPATH ) {
+ return qfalse;
+ }
+
+ Info_RemoveKey( s, key );
+ if( !vl ) {
+ return qtrue;
+ }
+
+ l = 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 <MISSING VALUE>\n", key );
+ }
+ }
+}
+