You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1101 lines
30 KiB
1101 lines
30 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $Workfile: $ |
|
// $Date: $ |
|
// $NoKeywords: $ |
|
//===========================================================================// |
|
|
|
// support QueryPerformanceCounter |
|
#if defined(_WIN32) && !defined(_X360) |
|
#define WIN32_LEAN_AND_MEAN |
|
#include <windows.h> |
|
#endif |
|
|
|
#include "quakedef.h" |
|
#include "zone.h" |
|
#include "tier0/vcrmode.h" |
|
#include "demo.h" |
|
#include "filesystem.h" |
|
#include "filesystem_engine.h" |
|
#include "eiface.h" |
|
#include "server.h" |
|
#include "sys.h" |
|
#include "baseautocompletefilelist.h" |
|
#include "tier0/icommandline.h" |
|
#include "tier1/utlbuffer.h" |
|
#include "gl_cvars.h" |
|
#include "tier0/memalloc.h" |
|
#include "netmessages.h" |
|
#include "client.h" |
|
#include "sv_plugin.h" |
|
#include "tier1/CommandBuffer.h" |
|
#include "cvar.h" |
|
#include "vstdlib/random.h" |
|
#include "tier1/utldict.h" |
|
#include "tier0/etwprof.h" |
|
#include "tier0/vprof.h" |
|
#include "gl_matsysiface.h" // update materialsystem config |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
|
|
// This denotes an execution marker in the command stream. |
|
#define CMDSTR_ADD_EXECUTION_MARKER "[$&*,`]" |
|
|
|
|
|
#ifdef _DEBUG |
|
ConVar cl_debug_respect_cheat_vars( "cl_debug_respect_cheat_vars", "0", 0, "(debug builds only) - when set to 0, the client can change cheat vars." ); |
|
#endif |
|
|
|
|
|
extern ConVar sv_allow_wait_command; |
|
|
|
|
|
#define MAX_ALIAS_NAME 32 |
|
#define MAX_COMMAND_LENGTH 1024 |
|
|
|
struct cmdalias_t |
|
{ |
|
cmdalias_t *next; |
|
char name[ MAX_ALIAS_NAME ]; |
|
char *value; |
|
}; |
|
|
|
static cmdalias_t *cmd_alias = NULL; |
|
|
|
static CCommandBuffer s_CommandBuffer; |
|
static CThreadFastMutex s_CommandBufferMutex; |
|
#define LOCK_COMMAND_BUFFER() AUTO_LOCK(s_CommandBufferMutex) |
|
|
|
static FileAssociationInfo g_FileAssociations[] = |
|
{ |
|
{ ".dem", "playdemo" }, |
|
{ ".sav", "load" }, |
|
{ ".bsp", "map" }, |
|
}; |
|
|
|
|
|
int g_iFilterCommandsByServerCanExecute = 0; // If this is nonzero, then they want us to only run commands marked with FCVAR_SERVER_CAN_EXECUTE. |
|
int g_iFilterCommandsByClientCmdCanExecute = 0; // If this is nonzero, then they want us to only run commands marked with FCVAR_CLIENTCMD_CAN_EXECUTE. |
|
|
|
// This is a list of cvars that are in the client DLL that we want FCVAR_CLIENTCMD_CAN_EXECUTE set on. |
|
// In order to avoid patching the client DLL, we setup this list. Whenever the client DLL has gone out with the |
|
// FCVAR_CLIENTCMD_CAN_EXECUTE flag set, we can get rid of this list. |
|
CUtlDict<int,int> g_ExtraClientCmdCanExecuteCvars; |
|
|
|
void Cmd_AddClientCmdCanExecuteVar( const char *pName ) |
|
{ |
|
if ( g_ExtraClientCmdCanExecuteCvars.Find( pName ) == g_ExtraClientCmdCanExecuteCvars.InvalidIndex() ) |
|
g_ExtraClientCmdCanExecuteCvars.Insert( pName ); |
|
} |
|
|
|
|
|
//============================================================================= |
|
// These functions manage a list of execution markers that we use to verify |
|
// special commands in the command buffer. |
|
//============================================================================= |
|
|
|
static CUtlVector<int> g_ExecutionMarkers; |
|
static CUniformRandomStream g_ExecutionMarkerStream; |
|
static bool g_bExecutionMarkerStreamInitialized = false; |
|
|
|
static int CreateExecutionMarker() |
|
{ |
|
if ( !g_bExecutionMarkerStreamInitialized ) |
|
{ |
|
int iSeed; |
|
if ( IsPosix() ) |
|
{ |
|
float flFloatTime = (float)Plat_FloatTime(); |
|
iSeed = *(int*)(&flFloatTime); |
|
} |
|
else |
|
{ |
|
#if defined(_WIN32) && !defined(_X360) |
|
LARGE_INTEGER CurrentTime; |
|
|
|
QueryPerformanceCounter( &CurrentTime ); |
|
iSeed = CurrentTime.LowPart ^ CurrentTime.HighPart; |
|
#else |
|
float flFloatTime = (float)Plat_FloatTime(); |
|
iSeed = *(int*)(&flFloatTime); |
|
#endif |
|
} |
|
|
|
g_ExecutionMarkerStream.SetSeed( iSeed ); |
|
g_bExecutionMarkerStreamInitialized = true; |
|
} |
|
|
|
if ( g_ExecutionMarkers.Count() >= MAX_EXECUTION_MARKERS ) |
|
{ |
|
// Callers to this function should call Cbuf_HasRoomForExecutionMarkers first. |
|
Host_Error( "CreateExecutionMarker called, but the max has already been reached." ); |
|
} |
|
|
|
int nRandomNumber; |
|
int nIndex = g_ExecutionMarkers.InvalidIndex(); |
|
|
|
// Pick a random number that doesn't already exist in the list |
|
do |
|
{ |
|
// We don't have CCrypto :( |
|
// if ( CCrypto::GenerateRandomBlock( (uint8 *)&nRandomNumber, sizeof(nRandomNumber) ) ) |
|
// { |
|
// nRandomNumber = nRandomNumber & 0x7FFFFFFF; |
|
// } |
|
// else |
|
// { |
|
nRandomNumber = g_ExecutionMarkerStream.RandomInt( 0, 1<<30 ); |
|
// } |
|
|
|
nIndex = g_ExecutionMarkers.Find( nRandomNumber ); |
|
} while ( nIndex != g_ExecutionMarkers.InvalidIndex() ); |
|
|
|
int i = g_ExecutionMarkers.AddToTail( nRandomNumber ); |
|
return g_ExecutionMarkers[i]; |
|
} |
|
|
|
static bool FindAndRemoveExecutionMarker( int iCode ) |
|
{ |
|
int i = g_ExecutionMarkers.Find( iCode ); |
|
if ( i == g_ExecutionMarkers.InvalidIndex() ) |
|
return false; |
|
|
|
g_ExecutionMarkers.Remove( i ); |
|
return true; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Used to allow cheats even if cheats aren't theoretically allowed |
|
//----------------------------------------------------------------------------- |
|
static bool g_bRPTActive = false; |
|
void Cmd_SetRptActive( bool bActive ) |
|
{ |
|
g_bRPTActive = bActive; |
|
} |
|
|
|
bool Cmd_IsRptActive() |
|
{ |
|
return g_bRPTActive; |
|
} |
|
|
|
|
|
//============================================================================= |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Just translates BindToggle <key> <cvar> into: bind <key> "increment var <cvar> 0 1 1" |
|
//----------------------------------------------------------------------------- |
|
CON_COMMAND( BindToggle, "Performs a bind <key> \"increment var <cvar> 0 1 1\"" ) |
|
{ |
|
if( args.ArgC() <= 2 ) |
|
{ |
|
ConMsg( "BindToggle <key> <cvar>: invalid syntax specified\n" ); |
|
return; |
|
} |
|
|
|
char newCmd[MAX_COMMAND_LENGTH]; |
|
Q_snprintf( newCmd, sizeof(newCmd), "bind %s \"incrementvar %s 0 1 1\"\n", args[1], args[2] ); |
|
|
|
Cbuf_InsertText( newCmd ); |
|
} |
|
|
|
CON_COMMAND_F( PerfMark, "inserts a telemetry marker into the stream. If args are provided, they will be included.", FCVAR_NONE ) |
|
{ |
|
// Nothing to do, we had our message written out by Cbuf_ExecuteCommand. |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Init, shutdown |
|
//----------------------------------------------------------------------------- |
|
void Cbuf_Init() |
|
{ |
|
// Wait for 1 execute time |
|
s_CommandBuffer.SetWaitDelayTime( 1 ); |
|
} |
|
|
|
void Cbuf_Shutdown() |
|
{ |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Clears the command buffer |
|
//----------------------------------------------------------------------------- |
|
void Cbuf_Clear() |
|
{ |
|
Cbuf_Init(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Adds command text at the end of the buffer |
|
//----------------------------------------------------------------------------- |
|
void Cbuf_AddText( const char *pText ) |
|
{ |
|
LOCK_COMMAND_BUFFER(); |
|
if ( !s_CommandBuffer.AddText( pText ) ) |
|
{ |
|
ConMsg( "Cbuf_AddText: buffer overflow\n" ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Escape an argument for a command. This *can* fail as many characters cannot |
|
// actually be passed through the old command syntax... |
|
//----------------------------------------------------------------------------- |
|
bool Cbuf_EscapeCommandArg( const char *pText, char *pOut, unsigned int nOut ) |
|
{ |
|
// Okay, so, to be honest, all we can do with the super limited syntax is ensure we don't have quotes or control |
|
// characters, and then wrap the string in quotes. Hence escape *argument*. Anything more advanced than that is just |
|
// not allowable. |
|
if ( !pText || !*pText ) |
|
return false; |
|
|
|
for (const char *pChar = pText; pChar && *pChar; pChar++ ) |
|
{ |
|
// ASCII control characters (codepoints <= 31) are just not sane to pass to this system. This includes \n and |
|
// \r. |
|
if ( *pChar <= (char)31 ) |
|
return false; |
|
|
|
// Can't quote these with current syntax. |
|
if ( *pChar == '"' ) |
|
return false; |
|
} |
|
|
|
// Room for quotes+null in out? |
|
if ( !pOut || nOut < (unsigned int)V_strlen( pText ) + 3 ) |
|
return false; |
|
|
|
V_snprintf( pOut, nOut, "\"%s\"", pText ); |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Adds command text at the beginning of the buffer |
|
//----------------------------------------------------------------------------- |
|
void Cbuf_InsertText( const char *pText ) |
|
{ |
|
LOCK_COMMAND_BUFFER(); |
|
// NOTE: This operation is only allowed when the command buffer |
|
// is in the middle of processing. If this assertion never triggers, |
|
// it's safe to eliminate Cbuf_InsertText altogether. |
|
// Otherwise, I have to add a feature to CCommandBuffer |
|
Assert( s_CommandBuffer.IsProcessingCommands() ); |
|
Cbuf_AddText( pText ); |
|
} |
|
|
|
|
|
|
|
bool Cbuf_AddExecutionMarker( ECmdExecutionMarker marker, const char *pszMarkerCode ) |
|
{ |
|
if ( marker == eCmdExecutionMarker_Enable_FCVAR_SERVER_CAN_EXECUTE ) |
|
{ |
|
s_CommandBuffer.SetWaitEnabled( false ); |
|
} |
|
else if ( marker == eCmdExecutionMarker_Disable_FCVAR_SERVER_CAN_EXECUTE ) |
|
{ |
|
s_CommandBuffer.SetWaitEnabled( true ); |
|
} |
|
|
|
return s_CommandBuffer.AddText( pszMarkerCode ); |
|
} |
|
|
|
|
|
bool Cbuf_AddTextWithMarkers( ECmdExecutionMarker markerLeft, const char *text, ECmdExecutionMarker markerRight ) |
|
{ |
|
if ( !Cbuf_HasRoomForExecutionMarkers( 2 ) ) |
|
{ |
|
ConMsg( "Cbuf_AddTextWithMarkers: execution marker overflow\n" ); |
|
return false; |
|
} |
|
|
|
int iMarkerCodeLeft = CreateExecutionMarker(); |
|
int iMarkerCodeRight = CreateExecutionMarker(); |
|
|
|
// CMDCHAR_ADD_EXECUTION_MARKER tells us there's a special execution thing here. |
|
// (char)marker tells it what to turn on |
|
// iRandomCode is for security, so only our code can stuff this command into the buffer. |
|
char szMarkerLeft[512], szMarkerRight[512]; |
|
V_sprintf_safe( szMarkerLeft, ";%s %c %d;", CMDSTR_ADD_EXECUTION_MARKER, (char)markerLeft, iMarkerCodeLeft ); |
|
V_sprintf_safe( szMarkerRight, ";%s %c %d;", CMDSTR_ADD_EXECUTION_MARKER, (char)markerRight, iMarkerCodeRight ); |
|
|
|
// Due to the behavior of the command buffer, we may be over-estimating the amount of space required. |
|
int cTextToBeAdded = strlen( szMarkerLeft ) + strlen( szMarkerRight ) + strlen( text ) + 3; |
|
|
|
LOCK_COMMAND_BUFFER(); |
|
if ( s_CommandBuffer.GetArgumentBufferSize() + cTextToBeAdded + 1 > s_CommandBuffer.GetMaxArgumentBufferSize() ) |
|
{ |
|
// cleanup |
|
FindAndRemoveExecutionMarker( iMarkerCodeLeft ); |
|
FindAndRemoveExecutionMarker( iMarkerCodeRight ); |
|
|
|
ConMsg( "Cbuf_AddTextWithMarkers: buffer overflow\n" ); |
|
return false; |
|
} |
|
|
|
bool bSuccess = Cbuf_AddExecutionMarker( markerLeft, szMarkerLeft ) && |
|
s_CommandBuffer.AddText( text ) && |
|
Cbuf_AddExecutionMarker( markerRight, szMarkerRight ); |
|
if ( !bSuccess ) |
|
{ |
|
// If we screwed up the validation, don't allow us to get stuck in a bad state. |
|
Host_Error( "Cbuf_AddTextWithMarkers: buffer overflow\n" ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
bool Cbuf_HasRoomForExecutionMarkers( int cExecutionMarkers ) |
|
{ |
|
return ( g_ExecutionMarkers.Count() + cExecutionMarkers ) < MAX_EXECUTION_MARKERS; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Executes commands in the buffer |
|
//----------------------------------------------------------------------------- |
|
static void Cbuf_ExecuteCommand( const CCommand &args, cmd_source_t source ) |
|
{ |
|
// Note: If you remove this, PerfMark needs to do the same logic--so don't do that. |
|
tmMessage( TELEMETRY_LEVEL0, TMMF_SEVERITY_LOG | TMMF_ICON_NOTE, "(source/command) %s", tmDynamicString( TELEMETRY_LEVEL0, args.GetCommandString() ) ); |
|
// Add the command text to the ETW stream to give better context to traces. |
|
ETWMark( args.GetCommandString() ); |
|
|
|
// execute the command line |
|
const ConCommandBase *pCmd = Cmd_ExecuteCommand( args, source ); |
|
|
|
#if !defined(SWDS) && !defined(_XBOX) |
|
if ( pCmd && !pCmd->IsFlagSet( FCVAR_DONTRECORD ) ) |
|
{ |
|
demorecorder->RecordCommand( args.GetCommandString() ); |
|
} |
|
#endif |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Executes commands in the buffer |
|
//----------------------------------------------------------------------------- |
|
void Cbuf_Execute() |
|
{ |
|
VPROF("Cbuf_Execute"); |
|
tmZoneFiltered( TELEMETRY_LEVEL0, 50, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
if ( !ThreadInMainThread() ) |
|
{ |
|
Warning( "Executing command outside main loop thread\n" ); |
|
ExecuteOnce( DebuggerBreakIfDebugging() ); |
|
} |
|
|
|
LOCK_COMMAND_BUFFER(); |
|
|
|
// If text was added with Cbuf_AddText and then Cbuf_Execute gets called from within handler, we're going |
|
// to execute the new commands anyway, so we can ignore this extra execute call here. |
|
if ( s_CommandBuffer.IsProcessingCommands() ) |
|
return; |
|
|
|
// Allow/Don't allow wait commands. |
|
if ( sv_allow_wait_command.GetBool() != s_CommandBuffer.IsWaitEnabled() ) |
|
{ |
|
s_CommandBuffer.SetWaitEnabled( sv_allow_wait_command.GetBool() ); |
|
} |
|
|
|
// NOTE: The command buffer knows about execution time related to commands, |
|
// but since HL2 doesn't, we're going to spoof the command time to simply |
|
// be the the number of times Cbuf_Execute is called. |
|
s_CommandBuffer.BeginProcessingCommands( 1 ); |
|
while ( s_CommandBuffer.DequeueNextCommand( ) ) |
|
{ |
|
Cbuf_ExecuteCommand( s_CommandBuffer.GetCommand(), src_command ); |
|
} |
|
s_CommandBuffer.EndProcessingCommands( ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *param - |
|
// Output : static char const |
|
//----------------------------------------------------------------------------- |
|
static char const *Cmd_TranslateFileAssociation(char const *param ) |
|
{ |
|
static char sz[ 512 ]; |
|
char *retval = NULL; |
|
|
|
char temp[ 512 ]; |
|
Q_strncpy( temp, param, sizeof( temp ) ); |
|
Q_FixSlashes( temp ); |
|
Q_strlower( temp ); |
|
|
|
const char *extension = V_GetFileExtension(temp); |
|
// must have an extension to map |
|
if (!extension) |
|
return retval; |
|
|
|
int c = ARRAYSIZE( g_FileAssociations ); |
|
for ( int i = 0; i < c; i++ ) |
|
{ |
|
FileAssociationInfo& info = g_FileAssociations[ i ]; |
|
|
|
if ( ! Q_strcmp( extension, info.extension+1 ) && |
|
! CommandLine()->FindParm(va( "+%s", info.command_to_issue ) ) ) |
|
{ |
|
// Translate if haven't already got one of these commands |
|
Q_strncpy( sz, temp, sizeof( sz ) ); |
|
Q_FileBase( sz, temp, sizeof( sz ) ); |
|
|
|
Q_snprintf( sz, sizeof( sz ), "%s %s", info.command_to_issue, temp ); |
|
retval = sz; |
|
break; |
|
} |
|
} |
|
|
|
// return null if no translation, otherwise return commands |
|
return retval; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Adds command line parameters as script statements |
|
// Commands lead with a +, and continue until a - or another + |
|
// Also automatically converts .dem, .bsp, and .sav files to +playdemo etc command line options |
|
// hl2 +cmd amlev1 |
|
// hl2 -nosound +cmd amlev1 |
|
// Output : void Cmd_StuffCmds_f |
|
//----------------------------------------------------------------------------- |
|
CON_COMMAND( stuffcmds, "Parses and stuffs command line + commands to command buffer." ) |
|
{ |
|
if ( args.ArgC() != 1 ) |
|
{ |
|
ConMsg( "stuffcmds : execute command line parameters\n" ); |
|
return; |
|
} |
|
|
|
MEM_ALLOC_CREDIT(); |
|
|
|
CUtlBuffer build( 0, 0, CUtlBuffer::TEXT_BUFFER ); |
|
|
|
// arg[0] is the executable name |
|
for ( int i=1; i < CommandLine()->ParmCount(); i++ ) |
|
{ |
|
const char *szParm = CommandLine()->GetParm(i); |
|
if (!szParm) continue; |
|
|
|
if (szParm[0] == '-') |
|
{ |
|
// skip -XXX options and eat their args |
|
const char *szValue = CommandLine()->ParmValueByIndex( i ); |
|
if ( szValue ) |
|
i++; |
|
continue; |
|
} |
|
if (szParm[0] == '+') |
|
{ |
|
// convert +XXX options and stuff them into the build buffer |
|
const char *szValue = CommandLine()->ParmValueByIndex( i ); |
|
if (szValue) |
|
{ |
|
build.PutString(va("%s %s\n", szParm+1, szValue)); |
|
i++; |
|
} |
|
else |
|
{ |
|
build.PutString(szParm+1); |
|
build.PutChar('\n'); |
|
} |
|
} |
|
else |
|
{ |
|
// singleton values, convert to command |
|
char const *translated = Cmd_TranslateFileAssociation( CommandLine()->GetParm( i ) ); |
|
if (translated) |
|
{ |
|
build.PutString(translated); |
|
build.PutChar('\n'); |
|
} |
|
} |
|
} |
|
|
|
build.PutChar( '\0' ); |
|
|
|
if ( build.TellPut() > 1 ) |
|
{ |
|
Cbuf_InsertText( (char *)build.Base() ); |
|
} |
|
} |
|
|
|
|
|
|
|
bool IsValidFileExtension( const char *pszFilename ) |
|
{ |
|
if ( !pszFilename ) |
|
{ |
|
return false; |
|
} |
|
|
|
if ( Q_strstr( pszFilename, ".exe" ) || |
|
Q_strstr( pszFilename, ".vbs" ) || |
|
Q_strstr( pszFilename, ".com" ) || |
|
Q_strstr( pszFilename, ".bat" ) || |
|
Q_strstr( pszFilename, ".dll" ) || |
|
Q_strstr( pszFilename, ".ini" ) || |
|
Q_strstr( pszFilename, ".gcf" ) || |
|
Q_strstr( pszFilename, ".sys" ) || |
|
Q_strstr( pszFilename, ".blob" ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
/* |
|
=============== |
|
Cmd_Exec_f |
|
=============== |
|
*/ |
|
|
|
void Cmd_Exec_f( const CCommand &args ) |
|
{ |
|
LOCK_COMMAND_BUFFER(); |
|
char fileName[MAX_OSPATH]; |
|
|
|
int argc = args.ArgC(); |
|
|
|
if ( argc != 2 ) |
|
{ |
|
ConMsg( "exec <filename>: execute a script file\n" ); |
|
return; |
|
} |
|
|
|
const char *szFile = args[1]; |
|
|
|
const char *pPathID = "*"; |
|
|
|
Q_snprintf( fileName, sizeof( fileName ), "//%s/cfg/%s", pPathID, szFile ); |
|
Q_DefaultExtension( fileName, ".cfg", sizeof( fileName ) ); |
|
|
|
// check path validity |
|
if ( !COM_IsValidPath( fileName ) ) |
|
{ |
|
ConMsg( "exec %s: invalid path.\n", fileName ); |
|
return; |
|
} |
|
|
|
// check for invalid file extensions |
|
if ( !IsValidFileExtension( fileName ) ) |
|
{ |
|
ConMsg( "exec %s: invalid file type.\n", fileName ); |
|
return; |
|
} |
|
|
|
// 360 doesn't need to do costly existence checks |
|
if ( IsPC() ) |
|
{ |
|
if ( g_pFileSystem->FileExists( fileName ) ) |
|
{ |
|
// don't want to exec files larger than 1 MB |
|
// probably not a valid file to exec |
|
unsigned int size = g_pFileSystem->Size( fileName ); |
|
if ( size > 1*1024*1024 ) |
|
{ |
|
ConMsg( "exec %s: file size larger than 1 MB!\n", szFile ); |
|
return; |
|
} |
|
} |
|
else |
|
{ |
|
// Many exec files are optional. make the error message slightly |
|
// more informative and less like there is a problem. |
|
if ( !V_stristr( szFile, "autoexec.cfg" ) && !V_stristr( szFile, "joystick.cfg" ) && !V_stristr( szFile, "game.cfg" ) ) |
|
{ |
|
ConMsg( "'%s' not present; not executing.\n", szFile ); |
|
} |
|
return; |
|
} |
|
} |
|
|
|
static char buf[16384] = { 0 }; |
|
int len = 0; |
|
char *f = (char *)COM_LoadStackFile( fileName, buf, sizeof( buf ), len ); |
|
if ( !f ) |
|
{ |
|
ConMsg( "exec: couldn't exec %s\n", szFile ); |
|
return; |
|
} |
|
|
|
char *original_f = f; |
|
VCRHook_Cmd_Exec(&f); |
|
|
|
// In case f was allocated and VCR mode spoofed f, free the old one we allocated earlier. |
|
if ( original_f != buf && original_f != f ) |
|
{ |
|
free( original_f ); |
|
} |
|
|
|
ConDMsg( "execing %s\n", szFile ); |
|
|
|
// check to make sure we're not going to overflow the cmd_text buffer |
|
CommandHandle_t hCommand = s_CommandBuffer.GetNextCommandHandle(); |
|
|
|
// Execute each command immediately |
|
const char *pszDataPtr = f; |
|
while( true ) |
|
{ |
|
// parse a line out of the source |
|
pszDataPtr = COM_ParseLine( pszDataPtr ); |
|
|
|
// no more tokens |
|
if ( Q_strlen( com_token ) <= 0 ) |
|
break; |
|
|
|
Cbuf_InsertText( com_token ); |
|
|
|
// Execute all commands provoked by the current line read from the file |
|
while ( s_CommandBuffer.GetNextCommandHandle() != hCommand ) |
|
{ |
|
if( s_CommandBuffer.DequeueNextCommand( ) ) |
|
{ |
|
Cbuf_ExecuteCommand( s_CommandBuffer.GetCommand(), src_command ); |
|
} |
|
else |
|
{ |
|
Assert( 0 ); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if ( f != buf ) |
|
{ |
|
// Hack for VCR playback. vcrmode allocates the memory but doesn't use the debug memory allocator, |
|
// so we don't want to free what it allocated. |
|
if ( f == original_f ) |
|
{ |
|
free( f ); |
|
} |
|
} |
|
// force any queued convar changes to flush before reading/writing them |
|
UpdateMaterialSystemConfig(); |
|
} |
|
|
|
|
|
/* |
|
=============== |
|
Cmd_Echo_f |
|
|
|
Just prints the rest of the line to the console |
|
=============== |
|
*/ |
|
CON_COMMAND_F( echo, "Echo text to console.", FCVAR_SERVER_CAN_EXECUTE ) |
|
{ |
|
int argc = args.ArgC(); |
|
for ( int i=1; i<argc; i++ ) |
|
{ |
|
ConMsg ("%s ", args[i] ); |
|
} |
|
ConMsg ("\n"); |
|
} |
|
|
|
// Users can alias these commands to remove their functionality because the game systems use convars to implement these features |
|
// The blacklist prevents that exploit |
|
static const char *g_pBlacklistedCommands[] = |
|
{ |
|
"dsp_player", |
|
"room_type", |
|
}; |
|
/* |
|
=============== |
|
Cmd_Alias_f |
|
|
|
Creates a new command that executes a command string (possibly ; seperated) |
|
=============== |
|
*/ |
|
CON_COMMAND( alias, "Alias a command." ) |
|
{ |
|
cmdalias_t *a; |
|
char cmd[MAX_COMMAND_LENGTH]; |
|
int c; |
|
const char *s; |
|
|
|
int argc = args.ArgC(); |
|
if ( argc == 1 ) |
|
{ |
|
ConMsg ("Current alias commands:\n"); |
|
for (a = cmd_alias ; a ; a=a->next) |
|
{ |
|
ConMsg ("%s : %s\n", a->name, a->value); |
|
} |
|
return; |
|
} |
|
|
|
s = args[1]; |
|
if ( Q_strlen(s) >= MAX_ALIAS_NAME ) |
|
{ |
|
ConMsg ("Alias name is too long\n"); |
|
return; |
|
} |
|
|
|
for ( int i = 0; i < ARRAYSIZE(g_pBlacklistedCommands); i++ ) |
|
{ |
|
if ( !V_stricmp( g_pBlacklistedCommands[i], s) ) |
|
{ |
|
ConMsg("Can't alias %s\n", g_pBlacklistedCommands[i] ); |
|
return; |
|
} |
|
} |
|
// copy the rest of the command line |
|
cmd[0] = 0; // start out with a null string |
|
c = argc; |
|
for ( int i=2 ; i< c ; i++) |
|
{ |
|
Q_strncat(cmd, args[i], sizeof( cmd ), COPY_ALL_CHARACTERS); |
|
if (i != c) |
|
{ |
|
Q_strncat (cmd, " ", sizeof( cmd ), COPY_ALL_CHARACTERS ); |
|
} |
|
} |
|
Q_strncat (cmd, "\n", sizeof( cmd ), COPY_ALL_CHARACTERS); |
|
|
|
// if the alias already exists, reuse it |
|
for (a = cmd_alias ; a ; a=a->next) |
|
{ |
|
if (!Q_strcmp(s, a->name)) |
|
{ |
|
if ( !Q_strcmp( a->value, cmd ) ) // Re-alias the same thing |
|
return; |
|
|
|
delete[] a->value; |
|
break; |
|
} |
|
} |
|
|
|
if (!a) |
|
{ |
|
a = (cmdalias_t *)new cmdalias_t; |
|
a->next = cmd_alias; |
|
cmd_alias = a; |
|
} |
|
Q_strncpy (a->name, s, sizeof( a->name ) ); |
|
|
|
a->value = COM_StringCopy(cmd); |
|
} |
|
|
|
/* |
|
============================================================================= |
|
|
|
COMMAND EXECUTION |
|
|
|
============================================================================= |
|
*/ |
|
|
|
cmd_source_t cmd_source; |
|
int cmd_clientslot = -1; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : void Cmd_Init |
|
//----------------------------------------------------------------------------- |
|
CON_COMMAND( cmd, "Forward command to server." ) |
|
{ |
|
Cmd_ForwardToServer( args ); |
|
} |
|
|
|
CON_COMMAND_AUTOCOMPLETEFILE( exec, Cmd_Exec_f, "Execute script file.", "cfg", cfg ); |
|
|
|
|
|
|
|
|
|
void Cmd_Init( void ) |
|
{ |
|
Sys_CreateFileAssociations( ARRAYSIZE( g_FileAssociations ), g_FileAssociations ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void Cmd_Shutdown( void ) |
|
{ |
|
// TODO, cleanup |
|
while ( cmd_alias ) |
|
{ |
|
cmdalias_t *next = cmd_alias->next; |
|
delete cmd_alias->value; // created by StringCopy() |
|
delete cmd_alias; |
|
cmd_alias = next; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// FIXME: Remove this! This is a temporary hack to deal with backward compat |
|
//----------------------------------------------------------------------------- |
|
void Cmd_Dispatch( const ConCommandBase *pCommand, const CCommand &command ) |
|
{ |
|
ConCommand *pConCommand = const_cast<ConCommand*>( static_cast<const ConCommand*>( pCommand ) ); |
|
pConCommand->Dispatch( command ); |
|
} |
|
|
|
|
|
static void HandleExecutionMarker( const char *pCommand, const char *pMarkerCode ) |
|
{ |
|
char cCommand = pCommand[0]; |
|
int iMarkerCode = atoi( pMarkerCode ); |
|
|
|
// Validate.. |
|
if ( FindAndRemoveExecutionMarker( iMarkerCode ) ) |
|
{ |
|
// Ok, now it's validated, so do the command. |
|
if ( cCommand == eCmdExecutionMarker_Enable_FCVAR_SERVER_CAN_EXECUTE ) |
|
++g_iFilterCommandsByServerCanExecute; |
|
else if ( cCommand == eCmdExecutionMarker_Disable_FCVAR_SERVER_CAN_EXECUTE ) |
|
--g_iFilterCommandsByServerCanExecute; |
|
|
|
else if ( cCommand == eCmdExecutionMarker_Enable_FCVAR_CLIENTCMD_CAN_EXECUTE ) |
|
++g_iFilterCommandsByClientCmdCanExecute; |
|
else if ( cCommand == eCmdExecutionMarker_Disable_FCVAR_CLIENTCMD_CAN_EXECUTE ) |
|
--g_iFilterCommandsByClientCmdCanExecute; |
|
} |
|
else |
|
{ |
|
static int cnt = 0; |
|
if ( ++cnt < 3 ) |
|
Warning( "Invalid execution marker code.\n" ); |
|
} |
|
} |
|
|
|
static bool ShouldPreventServerCommand( const ConCommandBase *pCommand ) |
|
{ |
|
if ( !Host_IsSinglePlayerGame() ) |
|
{ |
|
if ( g_iFilterCommandsByServerCanExecute > 0 ) |
|
{ |
|
// If we don't understand the command, and it came from a server command, then forward it back to the server. |
|
// Lots of server plugins use this. They use engine->ClientCommand() to have a client execute a command that |
|
// they have hooked on the server. We disabled it once and they freaked. It IS redundant since they could just |
|
// code the call to their command on the server, but they complained loudly enough that we're adding it back in |
|
// since there's no exploit that we know of by allowing it. |
|
if ( !pCommand ) |
|
return false; |
|
|
|
if ( !pCommand->IsFlagSet( FCVAR_SERVER_CAN_EXECUTE ) ) |
|
{ |
|
Warning( "FCVAR_SERVER_CAN_EXECUTE prevented server running command: %s\n", pCommand->GetName() ); |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
static bool ShouldPreventClientCommand( const ConCommandBase *pCommand ) |
|
{ |
|
if ( g_iFilterCommandsByClientCmdCanExecute > 0 && |
|
!pCommand->IsFlagSet( FCVAR_CLIENTCMD_CAN_EXECUTE ) && |
|
g_ExtraClientCmdCanExecuteCvars.Find( pCommand->GetName() ) == g_ExtraClientCmdCanExecuteCvars.InvalidIndex() ) |
|
{ |
|
// If this command is in the game DLL, don't mention it because we're going to forward this |
|
// request to the server and let the server handle it. |
|
if ( !pCommand->IsFlagSet( FCVAR_GAMEDLL ) ) |
|
{ |
|
Warning( "FCVAR_CLIENTCMD_CAN_EXECUTE prevented running command: %s\n", pCommand->GetName() ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// A complete command line has been parsed, so try to execute it |
|
// FIXME: lookupnoadd the token to speed search? |
|
//----------------------------------------------------------------------------- |
|
const ConCommandBase *Cmd_ExecuteCommand( const CCommand &command, cmd_source_t src, int nClientSlot ) |
|
{ |
|
// execute the command line |
|
if ( !command.ArgC() ) |
|
return NULL; // no tokens |
|
|
|
// First, check for execution markers. |
|
if ( Q_strcmp( command[0], CMDSTR_ADD_EXECUTION_MARKER ) == 0 ) |
|
{ |
|
if ( command.ArgC() == 3 ) |
|
{ |
|
HandleExecutionMarker( command[1], command[2] ); |
|
} |
|
else |
|
{ |
|
Warning( "WARNING: INVALID EXECUTION MARKER.\n" ); |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
// check alias |
|
cmdalias_t *a; |
|
for ( a=cmd_alias; a; a=a->next ) |
|
{ |
|
if ( !Q_strcasecmp( command[0], a->name ) ) |
|
{ |
|
Cbuf_InsertText( a->value ); |
|
return NULL; |
|
} |
|
} |
|
|
|
cmd_source = src; |
|
cmd_clientslot = nClientSlot; |
|
|
|
// check ConCommands |
|
const ConCommandBase *pCommand = g_pCVar->FindCommandBase( command[ 0 ] ); |
|
|
|
// If we prevent a server command due to FCVAR_SERVER_CAN_EXECUTE not being set, then we get out immediately. |
|
if ( ShouldPreventServerCommand( pCommand ) ) |
|
return NULL; |
|
|
|
if ( pCommand && pCommand->IsCommand() ) |
|
{ |
|
if ( !ShouldPreventClientCommand( pCommand ) && pCommand->IsCommand() ) |
|
{ |
|
bool isServerCommand = ( pCommand->IsFlagSet( FCVAR_GAMEDLL ) && |
|
// Typed at console |
|
cmd_source == src_command && |
|
// Not HLDS |
|
!sv.IsDedicated() ); |
|
|
|
// Hook to allow game .dll to figure out who type the message on a listen server |
|
if ( serverGameClients ) |
|
{ |
|
// We're actually the server, so set it up locally |
|
if ( sv.IsActive() ) |
|
{ |
|
g_pServerPluginHandler->SetCommandClient( cmd_source == src_client ? nClientSlot : -1 ); |
|
|
|
#ifndef SWDS |
|
// Special processing for listen server player |
|
if ( isServerCommand ) |
|
{ |
|
g_pServerPluginHandler->SetCommandClient( cl.m_nPlayerSlot ); |
|
} |
|
#endif |
|
} |
|
// We're not the server, but we've been a listen server (game .dll loaded) |
|
// forward this command tot he server instead of running it locally if we're still |
|
// connected |
|
// Otherwise, things like "say" won't work unless you quit and restart |
|
else if ( isServerCommand ) |
|
{ |
|
if ( cl.IsConnected() ) |
|
{ |
|
Cmd_ForwardToServer( command ); |
|
return NULL; |
|
} |
|
|
|
// It's a server command, but we're not connected to a server. Don't try to execute it. |
|
return NULL; |
|
} |
|
} |
|
|
|
// Allow cheat commands in singleplayer, debug, or multiplayer with sv_cheats on |
|
if ( pCommand->IsFlagSet( FCVAR_CHEAT ) ) |
|
{ |
|
if ( !Host_IsSinglePlayerGame() && !CanCheat() ) |
|
{ |
|
// But.. if the server is allowed to run this command and the server DID run this command, then let it through. |
|
// (used by soundscape_flush) |
|
if ( g_iFilterCommandsByServerCanExecute == 0 || !pCommand->IsFlagSet( FCVAR_SERVER_CAN_EXECUTE ) ) |
|
{ |
|
Msg( "Can't use cheat command %s in multiplayer, unless the server has sv_cheats set to 1.\n", pCommand->GetName() ); |
|
return NULL; |
|
} |
|
} |
|
} |
|
|
|
if ( pCommand->IsFlagSet( FCVAR_SPONLY ) ) |
|
{ |
|
if ( !Host_IsSinglePlayerGame() ) |
|
{ |
|
Msg( "Can't use command %s in multiplayer.\n", pCommand->GetName() ); |
|
return NULL; |
|
} |
|
} |
|
|
|
if ( pCommand->IsFlagSet( FCVAR_DEVELOPMENTONLY ) ) |
|
{ |
|
Msg( "Unknown command \"%s\"\n", pCommand->GetName() ); |
|
return NULL; |
|
} |
|
|
|
Cmd_Dispatch( pCommand, command ); |
|
return pCommand; |
|
} |
|
} |
|
|
|
// Bail out before we update convars if we're runnign in default mode. |
|
if ( pCommand && src == src_command && CommandLine()->CheckParm( "-default" ) && !pCommand->IsFlagSet( FCVAR_EXEC_DESPITE_DEFAULT ) ) |
|
{ |
|
Msg( "Ignoring cvar \"%s\" due to -default on command line\n", pCommand->GetName() ); |
|
return NULL; |
|
} |
|
|
|
// check cvars |
|
if ( cv->IsCommand( command ) ) |
|
return pCommand; |
|
|
|
// forward the command line to the server, so the entity DLL can parse it |
|
if ( cmd_source == src_command ) |
|
{ |
|
if ( cl.IsConnected() ) |
|
{ |
|
Cmd_ForwardToServer( command ); |
|
return NULL; |
|
} |
|
} |
|
|
|
Msg( "Unknown command \"%s\"\n", command[0] ); |
|
return NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Sends the entire command line over to the server |
|
//----------------------------------------------------------------------------- |
|
void Cmd_ForwardToServer( const CCommand &args, bool bReliable ) |
|
{ |
|
// YWB 6/3/98 Don't forward if this is a dedicated server |
|
#ifndef SWDS |
|
char str[1024]; |
|
|
|
#ifndef _XBOX |
|
if ( demoplayer->IsPlayingBack() ) |
|
return; // not really connected |
|
#endif |
|
|
|
str[0] = 0; |
|
if ( Q_strcasecmp( args[0], "cmd") != 0 ) |
|
{ |
|
Q_strncat( str, args[0], sizeof( str ), COPY_ALL_CHARACTERS ); |
|
Q_strncat( str, " ", sizeof( str ), COPY_ALL_CHARACTERS ); |
|
} |
|
|
|
if ( args.ArgC() > 1) |
|
{ |
|
Q_strncat( str, args.ArgS(), sizeof( str ), COPY_ALL_CHARACTERS ); |
|
} |
|
|
|
cl.SendStringCmd( str ); |
|
#endif |
|
}
|
|
|