//======================================================================= // Copyright XashXT Group 2007 © // cvar.c -- dynamic variable tracking //======================================================================= #include "launch.h" #define MAX_CVARS 2048 #define FILE_HASH_SIZE 256 int cvar_numIndexes; int cvar_modifiedFlags; cvar_t cvar_indexes[MAX_CVARS]; cvar_t *cvar_vars; static cvar_t* hashTable[FILE_HASH_SIZE]; /* ================ return a hash value for the filename ================ */ static long Cvar_GetHashValue( const char *fname ) { int i = 0; long hash = 0; char letter; while (fname[i] != '\0') { letter = com_tolower(fname[i]); hash += (long)(letter)*(i + 119); i++; } hash &= (FILE_HASH_SIZE - 1); return hash; } /* ============ Cvar_InfoValidate ============ */ static bool Cvar_ValidateString(const char *s, bool isvalue ) { if ( !s ) return false; if (strstr(s, "\\") && !isvalue) return false; if (strstr(s, "\"")) return false; if (strstr(s, ";")) return false; return true; } /* ============ Cvar_FindVar ============ */ cvar_t *Cvar_FindVar( const char *var_name ) { cvar_t *var; long hash; hash = Cvar_GetHashValue(var_name); for(var = hashTable[hash]; var; var = var->hash) { if (!com_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_VariableInteger( 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_CommandCompletion ============ */ void Cvar_CommandCompletion( void(*callback)(const char *s, const char *m)) { cvar_t *cvar; for( cvar = cvar_vars; cvar; cvar = cvar->next ) callback( cvar->name, cvar->description ); } /* ============ Cvar_Get If the variable already exists, the value will not be set 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, const char *var_desc ) { cvar_t *var; long hash; if(!var_name || !var_value) { MsgDev( D_ERROR, "Cvar_Get: NULL parameter" ); return NULL; } if (!Cvar_ValidateString(var_name, false)) { MsgDev(D_WARN, "invalid info cvar name string %s\n", var_name ); var_value = "noname"; } if(!Cvar_ValidateString( var_value, true )) { MsgDev(D_WARN, "invalid cvar value string: %s\n", var_value ); var_value = "default"; } // check for command coexisting if(Cmd_Exists(var_name)) { MsgDev(D_WARN, "Cvar_Get: %s is a command\n", var_name ); return NULL; } 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; Mem_Free( var->reset_string ); var->reset_string = copystring( var_value ); cvar_modifiedFlags |= flags; } var->flags |= flags; // only allow one non-empty reset string without a warning if( !var->reset_string[0] ) { // we don't have a reset string yet Mem_Free( var->reset_string ); var->reset_string = copystring( var_value ); } else if ( var_value[0] && strcmp( var->reset_string, var_value )) { MsgDev(D_WARN, "cvar \"%s\" given initial values: \"%s\" and \"%s\"\n", var_name, var->reset_string, var_value ); } // if we have a latched string, take that value now if ( var->latched_string ) { char *s; s = var->latched_string; var->latched_string = NULL; // otherwise cvar_set2 would free it Cvar_Set2( var_name, s, true ); Mem_Free( s ); } return var; } // allocate a new cvar if( cvar_numIndexes >= MAX_CVARS ) { MsgWarn("Cvar_Get: MAX_CVARS limit exceeded\n" ); return NULL; } var = &cvar_indexes[cvar_numIndexes]; cvar_numIndexes++; var->name = copystring(var_name); var->string = copystring(var_value); var->description = copystring(var_desc); var->modified = true; var->modificationCount = 1; var->value = com_atof(var->string); var->integer = com_atoi(var->string); var->reset_string = copystring( var_value ); // link the variable in var->next = cvar_vars; cvar_vars = var; var->flags = flags; hash = Cvar_GetHashValue(var_name); var->hash = hashTable[hash]; hashTable[hash] = var; return var; } /* ============ Cvar_Set2 ============ */ cvar_t *Cvar_Set2 (const char *var_name, const char *value, bool force) { cvar_t *var; if( !Cvar_ValidateString( var_name, false )) { MsgDev(D_WARN, "invalid cvar name string: %s\n", var_name ); var_name = "unknown"; } if ( value && !Cvar_ValidateString( value, true )) { MsgDev(D_WARN, "invalid cvar value string: %s\n", value ); value = "default"; } var = Cvar_FindVar (var_name); if(!var) { if( !value ) return NULL; // create it if ( !force ) return Cvar_Get( var_name, value, CVAR_USER_CREATED, "user variable" ); else return Cvar_Get (var_name, value, 0, "" ); } if(!value ) value = var->reset_string; if(!com_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_READ_ONLY) { MsgDev(D_INFO, "%s is read only.\n", var_name); return var; } if (var->flags & CVAR_INIT) { MsgDev(D_INFO, "%s is write protected.\n", var_name); return var; } if (var->flags & CVAR_LATCH) { if (var->latched_string) { if (!com_strcmp(value, var->latched_string)) return var; Mem_Free (var->latched_string); } else { if (!com_strcmp(value, var->string)) return var; } MsgDev(D_INFO, "%s will be changed after restarting.\n", var_name); var->latched_string = copystring(value); var->modified = true; var->modificationCount++; return var; } if ( (var->flags & CVAR_CHEAT) && !Cvar_VariableInteger("host_cheats")) { MsgDev(D_INFO, "%s is cheat protected.\n", var_name); return var; } } else { if (var->latched_string) { Mem_Free(var->latched_string); var->latched_string = NULL; } } // nothing to change if(!com_strcmp(value, var->string)) return var; var->modified = true; var->modificationCount++; // free the old value string Mem_Free (var->string); var->string = copystring(value); var->value = com_atof (var->string); var->integer = com_atoi(var->string); return var; } /* ============ Cvar_Set ============ */ void Cvar_Set( const char *var_name, const char *value ) { Cvar_Set2 (var_name, value, true); } /* ============ Cvar_SetLatched ============ */ void Cvar_SetLatched( const char *var_name, const char *value ) { Cvar_Set2 (var_name, value, false); } /* ============ Cvar_FullSet ============ */ void Cvar_FullSet( char *var_name, char *value, int flags ) { cvar_t *var; var = Cvar_FindVar (var_name); if (!var) { // create it Cvar_Get(var_name, value, flags, "" ); return; } var->modified = true; if (var->flags & CVAR_USERINFO) { // transmit at next oportunity std.userinfo_modified = true; } Mem_Free( var->string ); // free the old value string var->string = copystring(value); var->value = com_atof(var->string); var->integer = com_atoi(var->string); var->flags = flags; } /* ============ Cvar_SetValue ============ */ void Cvar_SetValue( const char *var_name, float value ) { char val[32]; if( value == (int)value ) com_sprintf (val, "%i", (int)value); else com_sprintf (val, "%f", value); Cvar_Set(var_name, val); } /* ============ Cvar_Reset ============ */ void Cvar_Reset( const char *var_name ) { Cvar_Set2( var_name, NULL, false ); } /* ============ 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 ) { // the CVAR_LATCHED|CVAR_CHEAT vars might escape the reset here // because of a different var->latched_string if(var->latched_string) { Mem_Free(var->latched_string); var->latched_string = NULL; } if(com_strcmp(var->reset_string, var->string)) { Cvar_Set( var->name, var->reset_string ); } } } } /* ============ Cvar_Command Handles variable inspection and changing from the console ============ */ bool Cvar_Command( void ) { cvar_t *v; // check variables v = Cvar_FindVar (Cmd_Argv(0)); if (!v) return false; // perform a variable print or set if( Cmd_Argc() == 1 ) { if(v->flags & CVAR_INIT) Msg("%s: %s\n", v->name, v->string ); else Msg("%s: %s ( ^3%s^7 )\n", v->name, v->string, v->reset_string ); if ( v->latched_string ) Msg( "%s: %s\n", v->name, v->latched_string ); return true; } // set the value if forcing isn't required Cvar_Set2 (v->name, Cmd_Argv(1), false); return true; } /* ============ Cvar_Toggle_f Toggles a cvar for easy single key binding ============ */ void Cvar_Toggle_f( void ) { int v; if( Cmd_Argc() != 2 ) { Msg("usage: toggle \n"); return; } v = Cvar_VariableValue(Cmd_Argv( 1 )); v = !v; Cvar_Set2 (Cmd_Argv(1), va("%i", v), false); } /* ============ 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 = 0, len; char combined[MAX_STRING_TOKENS]; c = Cmd_Argc(); if ( c < 3 ) { Msg("usage: set \n"); return; } combined[0] = 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, false); } /* ============ Cvar_SetU_f As Cvar_Set, but also flags it as userinfo ============ */ void Cvar_SetU_f( void ) { cvar_t *v; if ( Cmd_Argc() != 3 ) { Msg("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 ) { Msg("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 ) { Msg("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 ) { Msg("usage: reset \n"); return; } Cvar_Reset( Cmd_Argv( 1 )); } /* ============ Cvar_List_f ============ */ void Cvar_List_f( void ) { cvar_t *var; char *match; int i = 0; if ( Cmd_Argc() > 1 ) match = Cmd_Argv(1); else match = NULL; for (var = cvar_vars; var; var = var->next, i++) { if (match && !SC_FilterToken(match, var->name, false)) continue; if (var->flags & CVAR_SERVERINFO) Msg("S"); else Msg(" "); if (var->flags & CVAR_USERINFO) Msg("U"); else Msg(" "); if (var->flags & CVAR_READ_ONLY) Msg("R"); else Msg(" "); if (var->flags & CVAR_INIT) Msg("I"); else Msg(" "); if (var->flags & CVAR_ARCHIVE) Msg("A"); else Msg(" "); if (var->flags & CVAR_LATCH) Msg("L"); else Msg(" "); if (var->flags & CVAR_CHEAT) Msg("C"); else Msg(" "); Msg (" %s \"%s\"\n", var->name, var->string); } Msg("\n%i total cvars\n", i ); Msg("%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_READ_ONLY | CVAR_INIT )) { prev = &var->next; continue; } // throw out any variables the user created if ( var->flags & CVAR_USER_CREATED ) { *prev = var->next; if( var->name ) Mem_Free( var->name ); if( var->string ) Mem_Free( var->string ); if( var->latched_string ) Mem_Free( var->latched_string ); if( var->reset_string ) Mem_Free( var->reset_string ); if( var->description ) Mem_Free( var->description ); // clear the var completely, since we // can't remove the index from the list Mem_Set( var, 0, sizeof( var )); continue; } Cvar_Set( var->name, var->reset_string ); prev = &var->next; } } /* ============ Cvar_Init Reads in all archived cvars ============ */ void Cvar_Init (void) { Cmd_AddCommand ("toggle", Cvar_Toggle_f, "toggles a console variable's values (use for more info)" ); Cmd_AddCommand ("set", Cvar_Set_f, "create or change the value of a console variable" ); Cmd_AddCommand ("sets", Cvar_SetS_f, "create or change the value of a serverinfo variable"); Cmd_AddCommand ("setu", Cvar_SetU_f, "create or change the value of a userinfo variable"); Cmd_AddCommand ("seta", Cvar_SetA_f, "create or change the value of a console variable that will be saved to vars.rc"); Cmd_AddCommand ("reset", Cvar_Reset_f, "reset any type variable to initial value" ); Cmd_AddCommand ("cvarlist", Cvar_List_f, "display all console variables beginning with the specified prefix" ); Cmd_AddCommand ("unsetall", Cvar_Restart_f, "reset all console variables to their default values" ); }