/* =========================================================================== 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 "../game/common_headers.h" /* ============ 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]; //JLFCALLOUT MPNOTUSED int parseDataCount = -1; const int MAX_PARSE_DATA = 5; parseData_t parseData[MAX_PARSE_DATA]; void COM_ParseInit( void ) { memset(parseData, 0, sizeof(parseData)); parseDataCount = -1; } void COM_BeginParseSession( void ) { parseDataCount++; #ifdef _DEBUG if ( parseDataCount >= MAX_PARSE_DATA ) { Com_Error (ERR_FATAL, "COM_BeginParseSession: cannot nest more than %d parsing sessions.\n", MAX_PARSE_DATA); } #endif parseData[parseDataCount].com_lines = 1; parseData[parseDataCount].com_tokenline = 0; } void COM_EndParseSession( void ) { parseDataCount--; #ifdef _DEBUG assert (parseDataCount >= -1 && "COM_EndParseSession: called without a starting COM_BeginParseSession.\n"); #endif } int COM_GetCurrentParseLine( int index ) { if(parseDataCount < 0) Com_Error(ERR_FATAL, "COM_GetCurrentParseLine: parseDataCount < 0 (be sure to call COM_BeginParseSession!)"); if ( parseData[parseDataCount].com_tokenline ) return parseData[parseDataCount].com_tokenline; return parseData[parseDataCount].com_lines; } char *COM_Parse( const char **data_p ) { return COM_ParseExt( data_p, qtrue ); } /* ============== 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; if(parseDataCount < 0) Com_Error(ERR_FATAL, "SkipWhitespace: parseDataCount < 0"); while( (c = *(const unsigned char* /*eurofix*/)data) <= ' ') { if( !c ) { return NULL; } if( c == '\n' ) { parseData[parseDataCount].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; if(parseDataCount >= 0) parseData[parseDataCount].com_tokenline = 0; // make sure incoming data is valid if ( !data ) { *data_p = NULL; return com_token; } if(parseDataCount < 0) Com_Error(ERR_FATAL, "COM_ParseExt: parseDataCount < 0 (be sure to call COM_BeginParseSession!)"); 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' ) { parseData[parseDataCount].com_lines++; } data++; } if ( *data ) { data += 2; } } else { break; } } // token starts on this line parseData[parseDataCount].com_tokenline = parseData[parseDataCount].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' ) { parseData[parseDataCount].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, qfalse ); if ( s[0] == 0 ) { Com_Printf("unexpected EOF in COM_ParseString\n"); 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_Printf( "unexpected EOF in COM_ParseInt\n" ); 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_Printf( "unexpected EOF in COM_ParseFloat\n" ); 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, 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. Skips until a matching close brace is found. Internal brace depths are properly skipped. ================= */ void SkipBracedSection ( const char **program) { const char *token; int depth=0; if (com_token[0]=='{') { //for tr_shader which just ate the brace depth = 1; } do { token = COM_ParseExt( program, qtrue ); if( token[1] == 0 ) { if( token[0] == '{' ) { depth++; } else if( token[0] == '}' ) { depth--; } } } while (depth && *program); } /* ================= SkipRestOfLine ================= */ void SkipRestOfLine ( const char **data ) { const char *p; int c; if(parseDataCount < 0) Com_Error(ERR_FATAL, "SkipRestOfLine: parseDataCount < 0"); p = *data; if ( !*p ) return; while ( (c = *p++) != 0 ) { if ( c == '\n' ) { parseData[parseDataCount].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] = 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' ) { size_t i, n = 0; 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; } /* ============ 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? =============== */ const char *Info_ValueForKey( const char *s, const char *key ) { char pkey[MAX_INFO_KEY]; static char value[2][MAX_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 ) >= MAX_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 =================== */ void Info_NextPair( const char **head, char key[MAX_INFO_KEY], char value[MAX_INFO_VALUE] ) { char *o; const char *s; s = *head; if ( *s == '\\' ) { s++; } key[0] = 0; value[0] = 0; o = key; while ( *s != '\\' ) { if ( !*s ) { *o = 0; *head = s; return; } *o++ = *s++; } *o = 0; s++; o = value; while ( *s != '\\' && *s ) { *o++ = *s++; } *o = 0; *head = s; } /* =================== Info_RemoveKey =================== */ void Info_RemoveKey( char *s, const char *key ) { char *start; char pkey[MAX_INFO_KEY]; char value[MAX_INFO_VALUE]; char *o; if ( strlen( s ) >= MAX_INFO_STRING ) { Com_Error( ERR_DROP, "Info_RemoveKey: oversize infostring" ); } 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 ) { if ( strchr( s, '\"' ) ) { return qfalse; } if ( strchr( s, ';' ) ) { return qfalse; } 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\n"); return; } strcat (newi, s); strcpy (s, newi); } /* ================== Com_CharIsOneOfCharset ================== */ static qboolean Com_CharIsOneOfCharset( char c, char *set ) { size_t i; for ( i=0; i