/* =========================================================================== Copyright (C) 1999 - 2005, Id Software, Inc. Copyright (C) 2000 - 2013, Raven Software, Inc. Copyright (C) 2001 - 2013, Activision, Inc. Copyright (C) 2005 - 2015, ioquake3 contributors Copyright (C) 2013 - 2015, OpenJK contributors This file is part of the OpenJK source code. OpenJK is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. 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, see . =========================================================================== */ // q_shared.c -- stateless support routines that are included in each code dll #include "q_shared.h" /* ------------------------- GetIDForString ------------------------- */ int GetIDForString ( stringID_table_t *table, const char *string ) { int index = 0; while ( ( table[index].name != NULL ) && ( table[index].name[0] != 0 ) ) { if ( !Q_stricmp( table[index].name, string ) ) return table[index].id; index++; } return -1; } /* ------------------------- GetStringForID ------------------------- */ const char *GetStringForID( stringID_table_t *table, int id ) { int index = 0; while ( ( table[index].name != NULL ) && ( table[index].name[0] != 0 ) ) { if ( table[index].id == id ) return table[index].name; index++; } return NULL; } /* ============ 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, '/')) || 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, '/')) || 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 ================== */ void COM_DefaultExtension( char *path, int maxSize, const char *extension ) { const char *dot = strrchr(path, '.'), *slash; if (dot && (!(slash = strrchr(path, '/')) || slash < dot)) return; else Q_strcat(path, maxSize, extension); } /* ============================================================================ PARSING ============================================================================ */ static char com_token[MAX_TOKEN_CHARS]; static char com_parsename[MAX_TOKEN_CHARS]; static int com_lines; static int com_tokenline; 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; } char *COM_Parse( const char **data_p ) { return COM_ParseExt( data_p, qtrue ); } void COM_ParseError( 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( 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. ============== */ const char *SkipWhitespace( const char *data, qboolean *hasNewLines ) { int c; while( (c = *(const unsigned char* /*eurofix*/)data) <= ' ') { if( !c ) { return NULL; } if( c == '\n' ) { com_lines++; *hasNewLines = qtrue; } data++; } return data; } int COM_Compress( char *data_p ) { char *in, *out; int c; qboolean newline = qfalse, whitespace = qfalse; in = out = data_p; if (in) { 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; } 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; out++; in++; } } } *out = 0; } return out - data_p; } 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) { com_token[len] = 0; *data_p = ( char * ) data; return com_token; } if ( c == '\n' ) { com_lines++; } if (len < MAX_TOKEN_CHARS - 1) { com_token[len] = c; len++; } } } // parse a regular word do { if (len < MAX_TOKEN_CHARS - 1) { com_token[len] = c; len++; } data++; c = *data; } while (c>32); com_token[len] = 0; *data_p = ( char * ) data; return com_token; } /* =============== COM_ParseString =============== */ qboolean COM_ParseString( const char **data, const char **s ) { // *s = COM_ParseExt( data, qtrue ); *s = COM_ParseExt( data, qfalse ); if ( s[0] == 0 ) { COM_ParseWarning( "COM_ParseString: unexpected EOF" ); return qtrue; } return qfalse; } /* =============== COM_ParseInt =============== */ qboolean COM_ParseInt( const char **data, int *i ) { const char *token; token = COM_ParseExt( data, qfalse ); if ( token[0] == 0 ) { COM_ParseWarning( "COM_ParseInt: unexpected EOF" ); return qtrue; } *i = atoi( token ); return qfalse; } /* =============== COM_ParseFloat =============== */ qboolean COM_ParseFloat( const char **data, float *f ) { const char *token; token = COM_ParseExt( data, qfalse ); if ( token[0] == 0 ) { COM_ParseWarning( "COM_ParseFloat: unexpected EOF" ); return qtrue; } *f = atof( token ); return qfalse; } /* =============== COM_ParseVec4 =============== */ qboolean COM_ParseVec4( const char **buffer, vec4_t *c) { int i; float f; for (i = 0; i < 4; i++) { if (COM_ParseFloat(buffer, &f)) { return qtrue; } (*c)[i] = f; } return qfalse; } /* ================== COM_MatchToken ================== */ void COM_MatchToken( const char **buf_p, char *match ) { 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) { 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 (qboolean)( depth == 0 ); } /* ================= SkipRestOfLine ================= */ void SkipRestOfLine ( const char **data ) { const char *p; int c; p = *data; if ( !*p ) return; while ( (c = *p++) != 0 ) { if ( c == '\n' ) { com_lines++; break; } } *data = p; } void Parse1DMatrix (const char **buf_p, int x, float *m) { char *token; int i; COM_MatchToken( buf_p, "(" ); for (i = 0 ; i < x ; i++) { token = COM_Parse(buf_p); m[i] = 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, ")" ); } /* =================== Com_HexStrToInt =================== */ int Com_HexStrToInt( const char *str ) { if ( !str || !str[ 0 ] ) return -1; // check for hex code if( str[ 0 ] == '0' && str[ 1 ] == 'x' ) { int n = 0; size_t i; for( i = 2; i < strlen( str ); i++ ) { char digit; n *= 16; digit = tolower( str[ i ] ); if( digit >= '0' && digit <= '9' ) digit -= '0'; else if( digit >= 'a' && digit <= 'f' ) digit = digit - 'a' + 10; else return -1; n += digit; } return n; } return -1; } /* ============================================================================ LIBRARY REPLACEMENT FUNCTIONS ============================================================================ */ int QDECL Com_sprintf( char *dest, int size, const char *fmt, ...) { int len; va_list argptr; va_start (argptr,fmt); len = Q_vsnprintf(dest, size, fmt, argptr); va_end (argptr); if(len >= size) Com_Printf("Com_sprintf: Output length %d too short, require %d bytes.\n", size, len + 1); return len; } int FloatAsInt( float f ) { byteAlias_t fi; fi.f = f; return fi.i; } /* ============ 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 ============ */ #define MAX_VA_STRING 32000 #define MAX_VA_BUFFERS 4 char * QDECL va( const char *format, ... ) { va_list argptr; static char string[MAX_VA_BUFFERS][MAX_VA_STRING]; // in case va is called by nested functions static int index = 0; char *buf; va_start( argptr, format ); buf = (char *)&string[index++ & 3]; Q_vsnprintf( buf, sizeof(*string), format, argptr ); va_end( argptr ); return buf; } /* ============ Com_TruncateLongString Assumes buffer is atleast 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 ===================================================================== */ /* =============== Info_ValueForKey Searches the string for the given key and returns the associated value, or an empty string. FIXME: overflow check? =============== */ char *Info_ValueForKey( const char *s, const char *key ) { char pkey[BIG_INFO_KEY]; static char value[2][BIG_INFO_VALUE]; // use two buffers so compares // work without stomping on each other static int valueindex = 0; char *o; if ( !s || !key ) { return ""; } if ( strlen( s ) >= BIG_INFO_STRING ) { Com_Error( ERR_DROP, "Info_ValueForKey: oversize infostring" ); } valueindex ^= 1; if (*s == '\\') s++; while (1) { o = pkey; while (*s != '\\') { if (!*s) return ""; *o++ = *s++; } *o = 0; s++; o = value[valueindex]; while (*s != '\\' && *s) { *o++ = *s++; } *o = 0; if (!Q_stricmp (key, pkey) ) return value[valueindex]; if (!*s) break; s++; } return ""; } /* =================== Info_NextPair Used to itterate through all the key/value pairs in an info string Return qfalse if we discover the infostring is invalid =================== */ qboolean Info_NextPair( const char **head, char *key, char *value ) { char *o; const char *s = *head; if ( *s == '\\' ) s++; key[0] = 0; value[0] = 0; o = key; while ( *s != '\\' ) { if ( !*s ) { key[0] = 0; *head = s; return qtrue; } *o++ = *s++; } *o = 0; s++; // If they key is empty at this point with a slash after it // then this is considered invalid, possibly an attempt at hacked userinfo strings if ( !key[0] ) return qfalse; o = value; while ( *s != '\\' && *s ) { *o++ = *s++; } *o = 0; *head = s; return qtrue; } /* =================== Info_RemoveKey =================== */ void Info_RemoveKey( char *s, const char *key ) { char *start = NULL, *o = NULL; char pkey[MAX_INFO_KEY] = {0}; char value[MAX_INFO_VALUE] = {0}; if ( strlen( s ) >= MAX_INFO_STRING ) { Com_Error( ERR_DROP, "Info_RemoveKey: oversize infostring" ); return; } if (strchr (key, '\\')) { return; } while (1) { start = s; if (*s == '\\') s++; o = pkey; while (*s != '\\') { if (!*s) return; *o++ = *s++; } *o = 0; s++; o = value; while (*s != '\\' && *s) { if (!*s) return; *o++ = *s++; } *o = 0; //OJKNOTE: static analysis pointed out pkey may not be null-terminated if (!strcmp (key, pkey) ) { memmove(start, s, strlen(s) + 1); // remove this part return; } if (!*s) return; } } /* =================== Info_RemoveKey_Big =================== */ void Info_RemoveKey_Big( char *s, const char *key ) { char *start; static char pkey[BIG_INFO_KEY], value[BIG_INFO_VALUE]; char *o; pkey[0] = value[0] = '\0'; if ( strlen( s ) >= BIG_INFO_STRING ) { Com_Error( ERR_DROP, "Info_RemoveKey_Big: oversize infostring" ); return; } if (strchr (key, '\\')) { return; } while (1) { start = s; if (*s == '\\') s++; o = pkey; while (*s != '\\') { if (!*s) return; *o++ = *s++; } *o = 0; s++; o = value; while (*s != '\\' && *s) { if (!*s) return; *o++ = *s++; } *o = 0; //OJKNOTE: static analysis pointed out pkey may not be null-terminated if (!strcmp (key, pkey) ) { memmove(start, s, strlen(s) + 1); // remove this part return; } if (!*s) return; } } /* ================== Info_Validate Some characters are illegal in info strings because they can mess up the server's parsing ================== */ qboolean Info_Validate( const char *s ) { const char *c = s; while ( *c != '\0' ) { if( !Q_isprint( *c ) ) return qfalse; if( *c == '\"' ) return qfalse; if( *c == ';' ) return qfalse; ++c; } return qtrue; } /* ================== Info_SetValueForKey Changes or adds a key/value pair ================== */ void Info_SetValueForKey( char *s, const char *key, const char *value ) { char newi[MAX_INFO_STRING]; const char* blacklist = "\\;\""; if ( strlen( s ) >= MAX_INFO_STRING ) { Com_Error( ERR_DROP, "Info_SetValueForKey: oversize infostring" ); } for(; *blacklist; ++blacklist) { if (strchr (key, *blacklist) || strchr (value, *blacklist)) { Com_Printf (S_COLOR_YELLOW "Can't use keys or values with a '%c': %s = %s\n", *blacklist, key, value); return; } } Info_RemoveKey (s, key); if (!value || !strlen(value)) return; Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value); if (strlen(newi) + strlen(s) >= MAX_INFO_STRING) { Com_Printf ("Info string length exceeded: %s\n", s); return; } strcat (newi, s); strcpy (s, newi); } /* ================== Info_SetValueForKey_Big Changes or adds a key/value pair Includes and retains zero-length values ================== */ void Info_SetValueForKey_Big( char *s, const char *key, const char *value ) { char newi[BIG_INFO_STRING]; const char* blacklist = "\\;\""; if ( strlen( s ) >= BIG_INFO_STRING ) { Com_Error( ERR_DROP, "Info_SetValueForKey_Big: oversize infostring" ); } for(; *blacklist; ++blacklist) { if (strchr (key, *blacklist) || strchr (value, *blacklist)) { Com_Printf (S_COLOR_YELLOW "Can't use keys or values with a '%c': %s = %s\n", *blacklist, key, value); return; } } Info_RemoveKey_Big (s, key); if (!value) return; Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value); if (strlen(newi) + strlen(s) >= BIG_INFO_STRING) { Com_Printf ("BIG Info string length exceeded\n"); return; } strcat (s, newi); } /* ================== Com_CharIsOneOfCharset ================== */ static qboolean Com_CharIsOneOfCharset( char c, char *set ) { size_t i; for ( i=0; i