Index: code/server/sv_game.c =================================================================== --- code/server/sv_game.c (revision 1469) +++ code/server/sv_game.c (working copy) @@ -322,7 +322,7 @@ Cvar_Update( VMA(1) ); return 0; case G_CVAR_SET: - Cvar_Set( (const char *)VMA(1), (const char *)VMA(2) ); + Cvar_SetVM( (const char *)VMA(1), (const char *)VMA(2) ); return 0; case G_CVAR_VARIABLE_INTEGER_VALUE: return Cvar_VariableIntegerValue( (const char *)VMA(1) ); Index: code/qcommon/q_shared.h =================================================================== --- code/qcommon/q_shared.h (revision 1469) +++ code/qcommon/q_shared.h (working copy) @@ -786,6 +786,7 @@ #define CVAR_NORESTART 1024 // do not clear when a cvar_restart is issued #define CVAR_SERVER_CREATED 2048 // cvar was created by a server the client connected to. +#define CVAR_VMPROTECT 4096 // prevent QVM from modifying this var #define CVAR_NONEXISTENT 0xFFFFFFFF // Cvar doesn't exist. // nothing outside the Cvar_*() functions should modify these fields! Index: code/qcommon/files.c =================================================================== --- code/qcommon/files.c (revision 1469) +++ code/qcommon/files.c (working copy) @@ -2758,14 +2758,14 @@ Com_Printf( "----- FS_Startup -----\n" ); fs_debug = Cvar_Get( "fs_debug", "0", 0 ); - fs_basepath = Cvar_Get ("fs_basepath", Sys_DefaultInstallPath(), CVAR_INIT ); - fs_basegame = Cvar_Get ("fs_basegame", "", CVAR_INIT ); + fs_basepath = Cvar_Get ("fs_basepath", Sys_DefaultInstallPath(), CVAR_INIT|CVAR_VMPROTECT ); + fs_basegame = Cvar_Get ("fs_basegame", "", CVAR_INIT|CVAR_VMPROTECT ); homePath = Sys_DefaultHomePath(); if (!homePath || !homePath[0]) { homePath = fs_basepath->string; } - fs_homepath = Cvar_Get ("fs_homepath", homePath, CVAR_INIT ); - fs_gamedirvar = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO ); + fs_homepath = Cvar_Get ("fs_homepath", homePath, CVAR_INIT|CVAR_VMPROTECT ); + fs_gamedirvar = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO|CVAR_VMPROTECT ); // add search path elements in reverse priority order if (fs_basepath->string[0]) { @@ -2774,7 +2774,7 @@ // fs_homepath is somewhat particular to *nix systems, only add if relevant #ifdef MACOS_X - fs_apppath = Cvar_Get ("fs_apppath", Sys_DefaultAppPath(), CVAR_INIT ); + fs_apppath = Cvar_Get ("fs_apppath", Sys_DefaultAppPath(), CVAR_INIT|CVAR_VMPROTECT ); // Make MacOSX also include the base path included with the .app bundle if (fs_apppath->string[0]) FS_AddGameDirectory(fs_apppath->string, gameName); Index: code/qcommon/cmd.c =================================================================== --- code/qcommon/cmd.c (revision 1469) +++ code/qcommon/cmd.c (working copy) @@ -563,6 +563,20 @@ /* ============ +Cmd_FindCommand +============ +*/ +cmd_function_t *Cmd_FindCommand( const char *cmd_name ) +{ + cmd_function_t *cmd; + for( cmd = cmd_functions; cmd; cmd = cmd->next ) + if( !Q_stricmp( cmd_name, cmd->name ) ) + return cmd; + return NULL; +} + +/* +============ Cmd_AddCommand ============ */ @@ -570,14 +584,12 @@ cmd_function_t *cmd; // fail if the command already exists - for ( cmd = cmd_functions ; cmd ; cmd=cmd->next ) { - if ( !strcmp( cmd_name, cmd->name ) ) { - // allow completion-only commands to be silently doubled - if ( function != NULL ) { - Com_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name); - } - return; - } + if( Cmd_FindCommand( cmd_name ) ) + { + // allow completion-only commands to be silently doubled + if( function != NULL ) + Com_Printf( "Cmd_AddCommand: %s already defined\n", cmd_name ); + return; } // use a small malloc to avoid zone fragmentation @@ -615,7 +627,29 @@ } } +/* +============ +Cmd_RemoveCommandVM +VMs may only remove completion-only commands +============ +*/ +void Cmd_RemoveCommandVM( const char *cmd_name ) +{ + cmd_function_t *cmd = Cmd_FindCommand( cmd_name ); + + if( !cmd ) + return; + if( cmd->function ) + { + Com_Error( ERR_DROP, "VM tried to remove system command " + "\"%s\"\n", cmd_name ); + return; + } + + Cmd_RemoveCommand( cmd_name ); +} + /* ============ Cmd_CommandCompletion Index: code/qcommon/qcommon.h =================================================================== --- code/qcommon/qcommon.h (revision 1469) +++ code/qcommon/qcommon.h (working copy) @@ -421,6 +421,9 @@ void Cmd_RemoveCommand( const char *cmd_name ); +// don't allow VMs to remove system commands +void Cmd_RemoveCommandVM( const char *cmd_name ); + void Cmd_CommandCompletion( void(*callback)(const char *s) ); // callback with each valid string @@ -487,11 +490,15 @@ void Cvar_Set( const char *var_name, const char *value ); // will create the variable with no flags if it doesn't exist +void Cvar_SetVM( const char *var_name, const char *value ); +// used for syscalls: fails when var has the CVAR_VMPROTECT flag + void Cvar_SetLatched( const char *var_name, const char *value); // don't set the cvar immediately void Cvar_SetValue( const char *var_name, float value ); -// expands value to a string and calls Cvar_Set +void Cvar_SetValueVM( const char *var_name, float value ); +// expands value to a string and calls Cvar_Set/Cvar_SetVM float Cvar_VariableValue( const char *var_name ); int Cvar_VariableIntegerValue( const char *var_name ); Index: code/qcommon/cvar.c =================================================================== --- code/qcommon/cvar.c (revision 1469) +++ code/qcommon/cvar.c (working copy) @@ -349,7 +349,8 @@ cvar_modifiedFlags |= flags; } - var->flags |= flags; + // if a var is not being created don't let it pretend to be + var->flags |= flags & ~CVAR_USER_CREATED; // only allow one non-empty reset string without a warning if ( !var->resetString[0] ) { // we don't have a reset string yet @@ -559,6 +560,28 @@ /* ============ +Cvar_SetVM +============ +*/ +void Cvar_SetVM( const char *var_name, const char *value ) +{ + cvar_t *var = Cvar_FindVar( var_name ); + + if( var && var->flags & CVAR_VMPROTECT ) + { + if( value ) + Com_Error( ERR_DROP, "VM tried to set protected var " + "\"%s\" to \"%s\"\n", var_name, value ); + else + Com_Error( ERR_DROP, "VM tried to modify protected var " + "\"%s\"\n", var_name ); + return; + } + Cvar_Set( var_name, value ); +} + +/* +============ Cvar_SetLatched ============ */ @@ -582,7 +605,22 @@ Cvar_Set (var_name, val); } +/* +============ +Cvar_SetValueVM +============ +*/ +void Cvar_SetValueVM( const char *var_name, float value ) +{ + char val[32]; + if( Q_isintegral( value ) ) + Com_sprintf( val, sizeof(val), "%i", (int)value ); + else + Com_sprintf( val, sizeof(val), "%f", value ); + Cvar_SetVM( var_name, val ); +} + /* ============ Cvar_Reset Index: code/client/cl_cgame.c =================================================================== --- code/client/cl_cgame.c (revision 1469) +++ code/client/cl_cgame.c (working copy) @@ -431,7 +431,7 @@ Cvar_Update( VMA(1) ); return 0; case CG_CVAR_SET: - Cvar_Set( VMA(1), VMA(2) ); + Cvar_SetVM( VMA(1), VMA(2) ); return 0; case CG_CVAR_VARIABLESTRINGBUFFER: Cvar_VariableStringBuffer( VMA(1), VMA(2), args[3] ); @@ -464,7 +464,7 @@ CL_AddCgameCommand( VMA(1) ); return 0; case CG_REMOVECOMMAND: - Cmd_RemoveCommand( VMA(1) ); + Cmd_RemoveCommandVM( VMA(1) ); return 0; case CG_SENDCLIENTCOMMAND: CL_AddReliableCommand( VMA(1) ); Index: code/client/cl_ui.c =================================================================== --- code/client/cl_ui.c (revision 1469) +++ code/client/cl_ui.c (working copy) @@ -727,7 +727,7 @@ return 0; case UI_CVAR_SET: - Cvar_Set( VMA(1), VMA(2) ); + Cvar_SetVM( VMA(1), VMA(2) ); return 0; case UI_CVAR_VARIABLEVALUE: @@ -738,7 +738,7 @@ return 0; case UI_CVAR_SETVALUE: - Cvar_SetValue( VMA(1), VMF(2) ); + Cvar_SetValueVM( VMA(1), VMF(2) ); return 0; case UI_CVAR_RESET: