diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/cmd.cpp | |
| download | archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip | |
Diffstat (limited to 'engine/cmd.cpp')
| -rw-r--r-- | engine/cmd.cpp | 1101 |
1 files changed, 1101 insertions, 0 deletions
diff --git a/engine/cmd.cpp b/engine/cmd.cpp new file mode 100644 index 0000000..20beda0 --- /dev/null +++ b/engine/cmd.cpp @@ -0,0 +1,1101 @@ +//========= 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 = "MOD"; + + 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; + } + } + + 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 + int 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 +} |