/* =========================================================================== Return to Castle Wolfenstein single player GPL Source Code Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. This file is part of the Return to Castle Wolfenstein single player GPL Source Code (“RTCW SP Source Code”). RTCW SP 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 3 of the License, or (at your option) any later version. RTCW SP 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 RTCW SP Source Code. If not, see . In addition, the RTCW SP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW SP Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ // cvar.c -- dynamic variable tracking #include "../game/q_shared.h" #include "qcommon.h" cvar_t *cvar_vars; cvar_t *cvar_cheats; int cvar_modifiedFlags; #define MAX_CVARS 1024 cvar_t cvar_indexes[MAX_CVARS]; int cvar_numIndexes; #define FILE_HASH_SIZE 256 static cvar_t* hashTable[FILE_HASH_SIZE]; cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force ); /* ================ return a hash value for the filename ================ */ static long generateHashValue( const char *fname ) { int i; long hash; char letter; if ( !fname ) { Com_Error( ERR_DROP, "null name in generateHashValue" ); //gjd } hash = 0; i = 0; while ( fname[i] != '\0' ) { letter = tolower( fname[i] ); hash += (long)( letter ) * ( i + 119 ); i++; } hash &= ( FILE_HASH_SIZE - 1 ); return hash; } /* ============ Cvar_ValidateString ============ */ static qboolean Cvar_ValidateString( const char *s ) { if ( !s ) { return qfalse; } if ( strchr( s, '\\' ) ) { return qfalse; } if ( strchr( s, '\"' ) ) { return qfalse; } if ( strchr( s, ';' ) ) { return qfalse; } return qtrue; } /* ============ Cvar_FindVar ============ */ static cvar_t *Cvar_FindVar( const char *var_name ) { cvar_t *var; long hash; hash = generateHashValue( var_name ); for ( var = hashTable[hash] ; var ; var = var->hashNext ) { if ( !Q_stricmp( var_name, var->name ) ) { return var; } } return NULL; } /* ============ Cvar_VariableValue ============ */ float Cvar_VariableValue( const char *var_name ) { cvar_t *var; var = Cvar_FindVar( var_name ); if ( !var ) { return 0; } return var->value; } /* ============ Cvar_VariableIntegerValue ============ */ int Cvar_VariableIntegerValue( const char *var_name ) { cvar_t *var; var = Cvar_FindVar( var_name ); if ( !var ) { return 0; } return var->integer; } /* ============ Cvar_VariableString ============ */ char *Cvar_VariableString( const char *var_name ) { cvar_t *var; var = Cvar_FindVar( var_name ); if ( !var ) { return ""; } return var->string; } /* ============ Cvar_VariableStringBuffer ============ */ void Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) { cvar_t *var; var = Cvar_FindVar( var_name ); if ( !var ) { *buffer = 0; } else { Q_strncpyz( buffer, var->string, bufsize ); } } /* ============ Cvar_CommandCompletion ============ */ void Cvar_CommandCompletion( void ( *callback )(const char *s) ) { cvar_t *cvar; for ( cvar = cvar_vars ; cvar ; cvar = cvar->next ) { callback( cvar->name ); } } /* ============ Cvar_Get If the variable already exists, the value will not be set unless CVAR_ROM The flags will be or'ed in if the variable exists. ============ */ cvar_t *Cvar_Get( const char *var_name, const char *var_value, int flags ) { cvar_t *var; long hash; if ( !var_name || !var_value ) { Com_Error( ERR_FATAL, "Cvar_Get: NULL parameter" ); } if ( !Cvar_ValidateString( var_name ) ) { Com_Printf( "invalid cvar name string: %s\n", var_name ); var_name = "BADNAME"; } #if 0 // FIXME: values with backslash happen if ( !Cvar_ValidateString( var_value ) ) { Com_Printf( "invalid cvar value string: %s\n", var_value ); var_value = "BADVALUE"; } #endif var = Cvar_FindVar( var_name ); if ( var ) { // if the C code is now specifying a variable that the user already // set a value for, take the new value as the reset value if ( ( var->flags & CVAR_USER_CREATED ) && !( flags & CVAR_USER_CREATED ) && var_value[0] ) { var->flags &= ~CVAR_USER_CREATED; Z_Free( var->resetString ); var->resetString = CopyString( var_value ); // ZOID--needs to be set so that cvars the game sets as // SERVERINFO get sent to clients cvar_modifiedFlags |= flags; } var->flags |= flags; // only allow one non-empty reset string without a warning if ( !var->resetString[0] ) { // we don't have a reset string yet Z_Free( var->resetString ); var->resetString = CopyString( var_value ); } else if ( var_value[0] && strcmp( var->resetString, var_value ) ) { Com_DPrintf( "Warning: cvar \"%s\" given initial values: \"%s\" and \"%s\"\n", var_name, var->resetString, var_value ); } // if we have a latched string, take that value now if ( var->latchedString ) { char *s; s = var->latchedString; var->latchedString = NULL; // otherwise cvar_set2 would free it Cvar_Set2( var_name, s, qtrue ); Z_Free( s ); } // use a CVAR_SET for rom sets, get won't override #if 0 // CVAR_ROM always overrides if ( flags & CVAR_ROM ) { Cvar_Set2( var_name, var_value, qtrue ); } #endif return var; } // // allocate a new cvar // if ( cvar_numIndexes >= MAX_CVARS ) { Com_Error( ERR_FATAL, "MAX_CVARS" ); } var = &cvar_indexes[cvar_numIndexes]; cvar_numIndexes++; var->name = CopyString( var_name ); var->string = CopyString( var_value ); var->modified = qtrue; var->modificationCount = 1; var->value = atof( var->string ); var->integer = atoi( var->string ); var->resetString = CopyString( var_value ); // link the variable in var->next = cvar_vars; cvar_vars = var; var->flags = flags; hash = generateHashValue( var_name ); var->hashNext = hashTable[hash]; hashTable[hash] = var; return var; } /* ============ Cvar_Set2 ============ */ cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force ) { cvar_t *var; Com_DPrintf( "Cvar_Set2: %s %s\n", var_name, value ); if ( !Cvar_ValidateString( var_name ) ) { Com_Printf( "invalid cvar name string: %s\n", var_name ); var_name = "BADNAME"; } #if 0 // FIXME if ( value && !Cvar_ValidateString( value ) ) { Com_Printf( "invalid cvar value string: %s\n", value ); var_value = "BADVALUE"; } #endif var = Cvar_FindVar( var_name ); if ( !var ) { if ( !value ) { return NULL; } // create it if ( !force ) { return Cvar_Get( var_name, value, CVAR_USER_CREATED ); } else { return Cvar_Get( var_name, value, 0 ); } } if ( !value ) { value = var->resetString; } if ( !strcmp( value,var->string ) ) { return var; } // note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo) cvar_modifiedFlags |= var->flags; if ( !force ) { if ( var->flags & CVAR_ROM ) { Com_Printf( "%s is read only.\n", var_name ); return var; } if ( var->flags & CVAR_INIT ) { Com_Printf( "%s is write protected.\n", var_name ); return var; } if ( var->flags & CVAR_LATCH ) { if ( var->latchedString ) { if ( strcmp( value, var->latchedString ) == 0 ) { return var; } Z_Free( var->latchedString ); } else { if ( strcmp( value, var->string ) == 0 ) { return var; } } Com_Printf( "%s will be changed upon restarting.\n", var_name ); var->latchedString = CopyString( value ); var->modified = qtrue; var->modificationCount++; return var; } if ( ( var->flags & CVAR_CHEAT ) && !cvar_cheats->integer ) { Com_Printf( "%s is cheat protected.\n", var_name ); return var; } } else { if ( var->latchedString ) { Z_Free( var->latchedString ); var->latchedString = NULL; } } if ( !strcmp( value, var->string ) ) { return var; // not changed } var->modified = qtrue; var->modificationCount++; Z_Free( var->string ); // free the old value string var->string = CopyString( value ); var->value = atof( var->string ); var->integer = atoi( var->string ); return var; } /* ============ Cvar_Set ============ */ void Cvar_Set( const char *var_name, const char *value ) { Cvar_Set2( var_name, value, qtrue ); } /* ============ Cvar_SetLatched ============ */ void Cvar_SetLatched( const char *var_name, const char *value ) { Cvar_Set2( var_name, value, qfalse ); } /* ============ Cvar_SetValue ============ */ void Cvar_SetValue( const char *var_name, float value ) { char val[32]; if ( value == (int)value ) { Com_sprintf( val, sizeof( val ), "%i",(int)value ); } else { Com_sprintf( val, sizeof( val ), "%f",value ); } Cvar_Set( var_name, val ); } /* ============ Cvar_Reset ============ */ void Cvar_Reset( const char *var_name ) { Cvar_Set2( var_name, NULL, qfalse ); } /* ============ Cvar_SetCheatState Any testing variables will be reset to the safe values ============ */ void Cvar_SetCheatState( void ) { cvar_t *var; // set all default vars to the safe value for ( var = cvar_vars ; var ; var = var->next ) { if ( var->flags & CVAR_CHEAT ) { if ( strcmp( var->resetString,var->string ) ) { Cvar_Set( var->name, var->resetString ); } } } } /* ============ Cvar_Command Handles variable inspection and changing from the console ============ */ qboolean Cvar_Command( void ) { cvar_t *v; // check variables v = Cvar_FindVar( Cmd_Argv( 0 ) ); if ( !v ) { return qfalse; } // perform a variable print or set if ( Cmd_Argc() == 1 ) { Com_Printf( "\"%s\" is:\"%s" S_COLOR_WHITE "\" default:\"%s" S_COLOR_WHITE "\"\n", v->name, v->string, v->resetString ); if ( v->latchedString ) { Com_Printf( "latched: \"%s\"\n", v->latchedString ); } return qtrue; } // set the value if forcing isn't required Cvar_Set2( v->name, Cmd_Argv( 1 ), qfalse ); return qtrue; } /* ============ Cvar_Toggle_f Toggles a cvar for easy single key binding ============ */ void Cvar_Toggle_f( void ) { int v; if ( Cmd_Argc() != 2 ) { Com_Printf( "usage: toggle \n" ); return; } v = Cvar_VariableValue( Cmd_Argv( 1 ) ); v = !v; Cvar_Set2( Cmd_Argv( 1 ), va( "%i", v ), qfalse ); } /* ============ Cvar_Set_f Allows setting and defining of arbitrary cvars from console, even if they weren't declared in C code. ============ */ void Cvar_Set_f( void ) { int i, c, l, len; char combined[MAX_STRING_TOKENS]; c = Cmd_Argc(); if ( c < 3 ) { Com_Printf( "usage: set \n" ); return; } combined[0] = 0; l = 0; for ( i = 2 ; i < c ; i++ ) { len = strlen( Cmd_Argv( i ) + 1 ); if ( l + len >= MAX_STRING_TOKENS - 2 ) { break; } strcat( combined, Cmd_Argv( i ) ); if ( i != c - 1 ) { strcat( combined, " " ); } l += len; } Cvar_Set2( Cmd_Argv( 1 ), combined, qfalse ); } /* ============ Cvar_SetU_f As Cvar_Set, but also flags it as userinfo ============ */ void Cvar_SetU_f( void ) { cvar_t *v; if ( Cmd_Argc() != 3 ) { Com_Printf( "usage: setu \n" ); return; } Cvar_Set_f(); v = Cvar_FindVar( Cmd_Argv( 1 ) ); if ( !v ) { return; } v->flags |= CVAR_USERINFO; } /* ============ Cvar_SetS_f As Cvar_Set, but also flags it as userinfo ============ */ void Cvar_SetS_f( void ) { cvar_t *v; if ( Cmd_Argc() != 3 ) { Com_Printf( "usage: sets \n" ); return; } Cvar_Set_f(); v = Cvar_FindVar( Cmd_Argv( 1 ) ); if ( !v ) { return; } v->flags |= CVAR_SERVERINFO; } /* ============ Cvar_SetA_f As Cvar_Set, but also flags it as archived ============ */ void Cvar_SetA_f( void ) { cvar_t *v; if ( Cmd_Argc() != 3 ) { Com_Printf( "usage: seta \n" ); return; } Cvar_Set_f(); v = Cvar_FindVar( Cmd_Argv( 1 ) ); if ( !v ) { return; } v->flags |= CVAR_ARCHIVE; } /* ============ Cvar_Reset_f ============ */ void Cvar_Reset_f( void ) { if ( Cmd_Argc() != 2 ) { Com_Printf( "usage: reset \n" ); return; } Cvar_Reset( Cmd_Argv( 1 ) ); } /* ============ Cvar_WriteVariables Appends lines containing "set variable value" for all variables with the archive flag set to qtrue. ============ */ void Cvar_WriteVariables( fileHandle_t f ) { cvar_t *var; char buffer[1024]; for ( var = cvar_vars ; var ; var = var->next ) { if ( Q_stricmp( var->name, "cl_cdkey" ) == 0 ) { continue; } if ( var->flags & CVAR_ARCHIVE ) { // write the latched value, even if it hasn't taken effect yet if ( var->latchedString ) { Com_sprintf( buffer, sizeof( buffer ), "seta %s \"%s\"\n", var->name, var->latchedString ); } else { Com_sprintf( buffer, sizeof( buffer ), "seta %s \"%s\"\n", var->name, var->string ); } FS_Printf( f, "%s", buffer ); } } } /* ============ Cvar_List_f ============ */ void Cvar_List_f( void ) { cvar_t *var; int i; char *match; if ( Cmd_Argc() > 1 ) { match = Cmd_Argv( 1 ); } else { match = NULL; } i = 0; for ( var = cvar_vars ; var ; var = var->next, i++ ) { if ( match && !Com_Filter( match, var->name, qfalse ) ) { continue; } if ( var->flags & CVAR_SERVERINFO ) { Com_Printf( "S" ); } else { Com_Printf( " " ); } if ( var->flags & CVAR_USERINFO ) { Com_Printf( "U" ); } else { Com_Printf( " " ); } if ( var->flags & CVAR_ROM ) { Com_Printf( "R" ); } else { Com_Printf( " " ); } if ( var->flags & CVAR_INIT ) { Com_Printf( "I" ); } else { Com_Printf( " " ); } if ( var->flags & CVAR_ARCHIVE ) { Com_Printf( "A" ); } else { Com_Printf( " " ); } if ( var->flags & CVAR_LATCH ) { Com_Printf( "L" ); } else { Com_Printf( " " ); } if ( var->flags & CVAR_CHEAT ) { Com_Printf( "C" ); } else { Com_Printf( " " ); } Com_Printf( " %s \"%s\"\n", var->name, var->string ); } Com_Printf( "\n%i total cvars\n", i ); Com_Printf( "%i cvar indexes\n", cvar_numIndexes ); } /* ============ Cvar_Restart_f Resets all cvars to their hardcoded values ============ */ void Cvar_Restart_f( void ) { cvar_t *var; cvar_t **prev; prev = &cvar_vars; while ( 1 ) { var = *prev; if ( !var ) { break; } // don't mess with rom values, or some inter-module // communication will get broken (com_cl_running, etc) if ( var->flags & ( CVAR_ROM | CVAR_INIT | CVAR_NORESTART ) ) { prev = &var->next; continue; } // throw out any variables the user created if ( var->flags & CVAR_USER_CREATED ) { *prev = var->next; if ( var->name ) { Z_Free( var->name ); } if ( var->string ) { Z_Free( var->string ); } if ( var->latchedString ) { Z_Free( var->latchedString ); } if ( var->resetString ) { Z_Free( var->resetString ); } // clear the var completely, since we // can't remove the index from the list memset( var, 0, sizeof( var ) ); continue; } Cvar_Set( var->name, var->resetString ); prev = &var->next; } } /* ===================== Cvar_InfoString ===================== */ char *Cvar_InfoString( int bit ) { static char info[MAX_INFO_STRING]; cvar_t *var; info[0] = 0; for ( var = cvar_vars ; var ; var = var->next ) { if ( var->flags & bit ) { Info_SetValueForKey( info, var->name, var->string ); } } return info; } /* ===================== Cvar_InfoString_Big handles large info strings ( CS_SYSTEMINFO ) ===================== */ char *Cvar_InfoString_Big( int bit ) { static char info[BIG_INFO_STRING]; cvar_t *var; info[0] = 0; for ( var = cvar_vars ; var ; var = var->next ) { if ( var->flags & bit ) { Info_SetValueForKey_Big( info, var->name, var->string ); } } return info; } /* ===================== Cvar_InfoStringBuffer ===================== */ void Cvar_InfoStringBuffer( int bit, char* buff, int buffsize ) { Q_strncpyz( buff,Cvar_InfoString( bit ),buffsize ); } /* ===================== Cvar_Register basically a slightly modified Cvar_Get for the interpreted modules ===================== */ void Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ) { cvar_t *cv; cv = Cvar_Get( varName, defaultValue, flags ); if ( !vmCvar ) { return; } vmCvar->handle = cv - cvar_indexes; vmCvar->modificationCount = -1; Cvar_Update( vmCvar ); } /* ===================== Cvar_Update updates an interpreted modules' version of a cvar ===================== */ void Cvar_Update( vmCvar_t *vmCvar ) { cvar_t *cv = NULL; // bk001129 assert( vmCvar ); // bk if ( (unsigned)vmCvar->handle >= cvar_numIndexes ) { Com_Error( ERR_DROP, "Cvar_Update: handle out of range" ); } cv = cvar_indexes + vmCvar->handle; if ( cv->modificationCount == vmCvar->modificationCount ) { return; } if ( !cv->string ) { return; // variable might have been cleared by a cvar_restart } vmCvar->modificationCount = cv->modificationCount; // bk001129 - mismatches. if ( strlen( cv->string ) + 1 > MAX_CVAR_VALUE_STRING ) { Com_Error( ERR_DROP, "Cvar_Update: src %s length %d exceeds MAX_CVAR_VALUE_STRING", cv->string, strlen( cv->string ), sizeof( vmCvar->string ) ); } // bk001212 - Q_strncpyz guarantees zero padding and dest[MAX_CVAR_VALUE_STRING-1]==0 // bk001129 - paranoia. Never trust the destination string. // bk001129 - beware, sizeof(char*) is always 4 (for cv->string). // sizeof(vmCvar->string) always MAX_CVAR_VALUE_STRING //Q_strncpyz( vmCvar->string, cv->string, sizeof( vmCvar->string ) ); // id Q_strncpyz( vmCvar->string, cv->string, MAX_CVAR_VALUE_STRING ); vmCvar->value = cv->value; vmCvar->integer = cv->integer; } /* ============ Cvar_Init Reads in all archived cvars ============ */ void Cvar_Init( void ) { cvar_cheats = Cvar_Get( "sv_cheats", "0", CVAR_ROM | CVAR_SYSTEMINFO ); Cmd_AddCommand( "toggle", Cvar_Toggle_f ); Cmd_AddCommand( "set", Cvar_Set_f ); Cmd_AddCommand( "sets", Cvar_SetS_f ); Cmd_AddCommand( "setu", Cvar_SetU_f ); Cmd_AddCommand( "seta", Cvar_SetA_f ); Cmd_AddCommand( "reset", Cvar_Reset_f ); Cmd_AddCommand( "cvarlist", Cvar_List_f ); Cmd_AddCommand( "cvar_restart", Cvar_Restart_f ); }