2019-11-04 22:01:50 +00:00
/*
* * c_dispatch . cpp
* * Functions for executing console commands and aliases
* *
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* * Copyright 1998 - 2007 Randy Heit
* * All rights reserved .
* *
* * Redistribution and use in source and binary forms , with or without
* * modification , are permitted provided that the following conditions
* * are met :
* *
* * 1. Redistributions of source code must retain the above copyright
* * notice , this list of conditions and the following disclaimer .
* * 2. Redistributions in binary form must reproduce the above copyright
* * notice , this list of conditions and the following disclaimer in the
* * documentation and / or other materials provided with the distribution .
* * 3. The name of the author may not be used to endorse or promote products
* * derived from this software without specific prior written permission .
* *
* * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ` ` AS IS ' ' AND ANY EXPRESS OR
* * IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES
* * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED .
* * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT , INDIRECT ,
* * INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT
* * NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE ,
* * DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY
* * THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
* * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF
* * THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* *
*/
// HEADER FILES ------------------------------------------------------------
# include <stdlib.h>
# include <stdio.h>
# include <string.h>
# include <ctype.h>
# include "templates.h"
# include "cmdlib.h"
# include "c_console.h"
# include "c_dispatch.h"
# include "m_argv.h"
2020-04-11 22:04:02 +00:00
# include "gamestate.h"
2019-11-04 22:01:50 +00:00
# include "configfile.h"
# include "printf.h"
# include "c_cvars.h"
# include "c_buttons.h"
2020-04-11 22:07:52 +00:00
# include "findfile.h"
2020-04-11 22:04:02 +00:00
// Todo: Get rid of
# include "inputstate.h"
2019-11-04 22:01:50 +00:00
// MACROS ------------------------------------------------------------------
// TYPES -------------------------------------------------------------------
class FDelayedCommand
{
public :
virtual ~ FDelayedCommand ( ) { }
protected :
virtual bool Tick ( ) = 0 ;
friend class FDelayedCommandQueue ;
} ;
class FWaitingCommand : public FDelayedCommand
{
public :
2020-04-11 22:04:02 +00:00
FWaitingCommand ( const char * cmd , int tics , bool unsafe )
: Command ( cmd ) , TicsLeft ( tics + 1 ) , IsUnsafe ( unsafe )
2019-11-04 22:01:50 +00:00
{ }
bool Tick ( ) override
{
if ( - - TicsLeft = = 0 )
{
2020-04-11 22:04:02 +00:00
UnsafeExecutionScope scope ( IsUnsafe ) ;
2019-11-04 22:01:50 +00:00
AddCommandString ( Command ) ;
return true ;
}
return false ;
}
FString Command ;
int TicsLeft ;
2020-04-11 22:04:02 +00:00
bool IsUnsafe ;
2019-11-04 22:01:50 +00:00
} ;
class FStoredCommand : public FDelayedCommand
{
public :
FStoredCommand ( FConsoleCommand * com , const char * cmd )
: Command ( com ) , Text ( cmd )
{ }
bool Tick ( ) override
{
if ( Text . IsNotEmpty ( ) & & Command ! = nullptr )
{
FCommandLine args ( Text ) ;
2020-04-11 22:04:02 +00:00
Command - > Run ( args , 0 ) ;
2019-11-04 22:01:50 +00:00
}
return true ;
}
private :
FConsoleCommand * Command ;
FString Text ;
} ;
class FDelayedCommandQueue
{
TDeletingArray < FDelayedCommand * > delayedCommands ;
public :
void Run ( )
{
for ( unsigned i = 0 ; i < delayedCommands . Size ( ) ; i + + )
{
if ( delayedCommands [ i ] - > Tick ( ) )
{
delete delayedCommands [ i ] ;
delayedCommands . Delete ( i ) ;
i - - ;
}
}
}
void Clear ( )
{
delayedCommands . DeleteAndClear ( ) ;
}
void AddCommand ( FDelayedCommand * cmd )
{
delayedCommands . Push ( cmd ) ;
}
} ;
static FDelayedCommandQueue delayedCommandQueue ;
void C_RunDelayedCommands ( )
{
delayedCommandQueue . Run ( ) ;
}
void C_ClearDelayedCommands ( )
{
delayedCommandQueue . Clear ( ) ;
}
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
static FConsoleCommand * FindNameInHashTable ( FConsoleCommand * * table , const char * name , size_t namelen ) ;
static FConsoleCommand * ScanChainForName ( FConsoleCommand * start , const char * name , size_t namelen , FConsoleCommand * * prev ) ;
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
// PUBLIC DATA DEFINITIONS -------------------------------------------------
2020-04-11 22:04:02 +00:00
bool ParsingKeyConf , UnsafeExecutionContext ;
FString StoredWarp ;
2019-11-04 22:01:50 +00:00
2020-04-11 22:04:02 +00:00
FConsoleCommand * Commands [ FConsoleCommand : : HASH_SIZE ] ;
2019-11-04 22:01:50 +00:00
2020-04-11 22:04:02 +00:00
CVAR ( Bool , lookspring , true , CVAR_ARCHIVE ) ; // Generate centerview when -mlook encountered?
2019-11-04 22:01:50 +00:00
// PRIVATE DATA DEFINITIONS ------------------------------------------------
2020-04-11 22:04:02 +00:00
static const char * KeyConfCommands [ ] =
{
" alias " ,
" defaultbind " ,
" addkeysection " ,
" addmenukey " ,
" addslotdefault " ,
" weaponsection " ,
" setslot " ,
" addplayerclass " ,
" clearplayerclasses "
} ;
2019-11-04 22:01:50 +00:00
// CODE --------------------------------------------------------------------
void C_DoCommand ( const char * cmd , int keynum )
{
FConsoleCommand * com ;
const char * end ;
const char * beg ;
// Skip any beginning whitespace
while ( * cmd > 0 & & * cmd < = ' ' )
cmd + + ;
// Find end of the command name
if ( * cmd = = ' \" ' )
{
for ( end = beg = cmd + 1 ; * end & & * end ! = ' \" ' ; + + end )
;
}
else
{
beg = cmd ;
for ( end = cmd + 1 ; * end > ' ' | | * end < 0 ; + + end )
;
}
const size_t len = end - beg ;
2020-04-11 22:04:02 +00:00
if ( ParsingKeyConf )
{
int i ;
for ( i = countof ( KeyConfCommands ) - 1 ; i > = 0 ; - - i )
{
if ( strnicmp ( beg , KeyConfCommands [ i ] , len ) = = 0 & &
KeyConfCommands [ i ] [ len ] = = 0 )
{
break ;
}
}
if ( i < 0 )
{
Printf ( " Invalid command for KEYCONF: %s \n " , beg ) ;
return ;
}
}
2019-11-04 22:01:50 +00:00
// Check if this is an action
if ( * beg = = ' + ' | | * beg = = ' - ' )
{
auto button = buttonMap . FindButton ( beg + 1 ) ;
if ( button )
{
if ( * beg = = ' + ' )
{
button - > PressKey ( keynum ) ;
}
else
{
button - > ReleaseKey ( keynum ) ;
/*
if ( button = = & Button_Mlook & & lookspring )
{
Net_WriteByte ( DEM_CENTERVIEW ) ;
}
*/
}
return ;
}
}
// Parse it as a normal command
// Checking for matching commands follows this search order:
// 1. Check the Commands[] hash table
// 2. Check the CVars list
if ( ( com = FindNameInHashTable ( Commands , beg , len ) ) )
{
2020-04-11 22:04:02 +00:00
if ( gamestate ! = GS_STARTUP | | ParsingKeyConf | |
2019-11-04 22:01:50 +00:00
( len = = 3 & & strnicmp ( beg , " set " , 3 ) = = 0 ) | |
( len = = 7 & & strnicmp ( beg , " logfile " , 7 ) = = 0 ) | |
( len = = 9 & & strnicmp ( beg , " unbindall " , 9 ) = = 0 ) | |
( len = = 4 & & strnicmp ( beg , " bind " , 4 ) = = 0 ) | |
( len = = 4 & & strnicmp ( beg , " exec " , 4 ) = = 0 ) | |
2020-04-11 22:04:02 +00:00
( len = = 10 & & strnicmp ( beg , " doublebind " , 10 ) = = 0 ) | |
( len = = 6 & & strnicmp ( beg , " pullin " , 6 ) = = 0 )
2019-11-04 22:01:50 +00:00
)
{
FCommandLine args ( beg ) ;
2020-04-11 22:04:02 +00:00
com - > Run ( args , keynum ) ;
2019-11-04 22:01:50 +00:00
}
else
{
if ( len = = 4 & & strnicmp ( beg , " warp " , 4 ) = = 0 )
{
2020-04-11 22:04:02 +00:00
StoredWarp = beg ;
2019-11-04 22:01:50 +00:00
}
else
{
auto cmd = new FStoredCommand ( com , beg ) ;
delayedCommandQueue . AddCommand ( cmd ) ;
}
}
}
else
{ // Check for any console vars that match the command
FBaseCVar * var = FindCVarSub ( beg , int ( len ) ) ;
if ( var ! = NULL )
{
FCommandLine args ( beg ) ;
if ( args . argc ( ) > = 2 )
{ // Set the variable
var - > CmdSet ( args [ 1 ] ) ;
}
else
{ // Get the variable's value
Printf ( " \" %s \" is \" %s \" \n " , var - > GetName ( ) , var - > GetHumanString ( ) ) ;
}
}
else
{ // We don't know how to handle this command
Printf ( " Unknown command \" %.*s \" \n " , ( int ) len , beg ) ;
}
}
}
void AddCommandString ( const char * text , int keynum )
{
// Operate on a local copy instead of messing around with the data that's being passed in here.
TArray < char > buffer ( strlen ( text ) + 1 , true ) ;
memcpy ( buffer . Data ( ) , text , buffer . Size ( ) ) ;
char * cmd = buffer . Data ( ) ;
char * brkpt ;
int more ;
if ( cmd )
{
while ( * cmd )
{
brkpt = cmd ;
while ( * brkpt ! = ' ; ' & & * brkpt ! = ' \0 ' )
{
if ( * brkpt = = ' \" ' )
{
brkpt + + ;
while ( * brkpt ! = ' \0 ' & & ( * brkpt ! = ' \" ' | | * ( brkpt - 1 ) = = ' \\ ' ) )
brkpt + + ;
}
if ( * brkpt ! = ' \0 ' )
brkpt + + ;
}
if ( * brkpt = = ' ; ' )
{
* brkpt = ' \0 ' ;
more = 1 ;
}
else
{
more = 0 ;
}
// Intercept wait commands here. Note: wait must be lowercase
while ( * cmd > 0 & & * cmd < = ' ' )
cmd + + ;
if ( * cmd )
{
2020-04-11 22:04:02 +00:00
if ( ! ParsingKeyConf & &
cmd [ 0 ] = = ' w ' & & cmd [ 1 ] = = ' a ' & & cmd [ 2 ] = = ' i ' & & cmd [ 3 ] = = ' t ' & &
2019-11-04 22:01:50 +00:00
( cmd [ 4 ] = = 0 | | cmd [ 4 ] = = ' ' ) )
{
int tics ;
if ( cmd [ 4 ] = = ' ' )
{
tics = ( int ) strtoll ( cmd + 5 , NULL , 0 ) ;
}
else
{
tics = 1 ;
}
if ( tics > 0 )
{
if ( more )
{ // The remainder of the command will be executed later
// Note that deferred commands lose track of which key
// (if any) they were pressed from.
* brkpt = ' ; ' ;
2020-04-11 22:04:02 +00:00
auto cmd = new FWaitingCommand ( brkpt , tics , UnsafeExecutionContext ) ;
2019-11-04 22:01:50 +00:00
delayedCommandQueue . AddCommand ( cmd ) ;
}
return ;
}
}
else
{
C_DoCommand ( cmd , keynum ) ;
}
}
if ( more )
{
* brkpt = ' ; ' ;
}
cmd = brkpt + more ;
}
}
}
static FConsoleCommand * ScanChainForName ( FConsoleCommand * start , const char * name , size_t namelen , FConsoleCommand * * prev )
{
int comp ;
* prev = NULL ;
while ( start )
{
comp = strnicmp ( start - > m_Name , name , namelen ) ;
if ( comp > 0 )
return NULL ;
else if ( comp = = 0 & & start - > m_Name [ namelen ] = = 0 )
return start ;
* prev = start ;
start = start - > m_Next ;
}
return NULL ;
}
static FConsoleCommand * FindNameInHashTable ( FConsoleCommand * * table , const char * name , size_t namelen )
{
FConsoleCommand * dummy ;
return ScanChainForName ( table [ MakeKey ( name , namelen ) % FConsoleCommand : : HASH_SIZE ] , name , namelen , & dummy ) ;
}
bool FConsoleCommand : : AddToHash ( FConsoleCommand * * table )
{
unsigned int key ;
FConsoleCommand * insert , * * bucket ;
key = MakeKey ( m_Name ) ;
bucket = & table [ key % HASH_SIZE ] ;
if ( ScanChainForName ( * bucket , m_Name , strlen ( m_Name ) , & insert ) )
{
return false ;
}
else
{
if ( insert )
{
m_Next = insert - > m_Next ;
if ( m_Next )
m_Next - > m_Prev = & m_Next ;
insert - > m_Next = this ;
m_Prev = & insert - > m_Next ;
}
else
{
m_Next = * bucket ;
* bucket = this ;
m_Prev = bucket ;
if ( m_Next )
m_Next - > m_Prev = & m_Next ;
}
}
return true ;
}
FConsoleCommand * FConsoleCommand : : FindByName ( const char * name )
{
return FindNameInHashTable ( Commands , name , strlen ( name ) ) ;
}
2019-11-04 22:11:10 +00:00
const char * StaticGetButtonName ( int32_t func ) ;
2019-11-04 22:01:50 +00:00
FConsoleCommand : : FConsoleCommand ( const char * name , CCmdRun runFunc )
: m_RunFunc ( runFunc )
{
static bool firstTime = true ;
if ( firstTime )
{
unsigned int i ;
firstTime = false ;
// Add all the action commands for tab completion
for ( i = 0 ; i < buttonMap . NumButtons ( ) ; i + + )
{
2019-11-04 22:11:10 +00:00
FString tname = " + " ; tname < < StaticGetButtonName ( i ) ;
2019-11-04 22:01:50 +00:00
C_AddTabCommand ( tname ) ;
tname . Substitute ( ' + ' , ' - ' ) ;
C_AddTabCommand ( tname ) ;
}
}
int ag = strcmp ( name , " kill " ) ;
if ( ag = = 0 )
ag = 0 ;
m_Name = name ;
if ( ! AddToHash ( Commands ) )
Printf ( " Adding CCMD %s twice. \n " , name ) ;
else
C_AddTabCommand ( name ) ;
}
FConsoleCommand : : ~ FConsoleCommand ( )
{
* m_Prev = m_Next ;
if ( m_Next )
m_Next - > m_Prev = m_Prev ;
C_RemoveTabCommand ( m_Name ) ;
}
2020-04-11 22:04:02 +00:00
void FConsoleCommand : : Run ( FCommandLine & argv , int key )
2019-11-04 22:01:50 +00:00
{
2020-04-11 22:04:02 +00:00
m_RunFunc ( argv , key ) ;
}
void FUnsafeConsoleCommand : : Run ( FCommandLine & args , int key )
{
if ( UnsafeExecutionContext )
{
Printf ( TEXTCOLOR_RED " Cannot execute unsafe command " TEXTCOLOR_GOLD " %s \n " , m_Name ) ;
return ;
}
FConsoleCommand : : Run ( args , key ) ;
2019-11-04 22:01:50 +00:00
}
FConsoleAlias : : FConsoleAlias ( const char * name , const char * command , bool noSave )
: FConsoleCommand ( name , NULL ) ,
bRunning ( false ) , bKill ( false )
{
m_Command [ noSave ] = command ;
m_Command [ ! noSave ] = FString ( ) ;
// If the command contains % characters, assume they are parameter markers
// for substitution when the command is executed.
bDoSubstitution = ( strchr ( command , ' % ' ) ! = NULL ) ;
}
FConsoleAlias : : ~ FConsoleAlias ( )
{
m_Command [ 1 ] = m_Command [ 0 ] = FString ( ) ;
}
// Given an argument vector, reconstitute the command line it could have been produced from.
FString BuildString ( int argc , FString * argv )
{
if ( argc = = 1 )
{
return * argv ;
}
else
{
FString buf ;
int arg ;
for ( arg = 0 ; arg < argc ; arg + + )
{
if ( argv [ arg ] [ 0 ] = = ' \0 ' )
{ // It's an empty argument, we need to convert it to '""'
buf < < " \" \" " ;
}
else if ( strchr ( argv [ arg ] , ' " ' ) )
{ // If it contains one or more quotes, we need to escape them.
buf < < ' " ' ;
long substr_start = 0 , quotepos ;
while ( ( quotepos = argv [ arg ] . IndexOf ( ' " ' , substr_start ) ) > = 0 )
{
if ( substr_start < quotepos )
{
buf < < argv [ arg ] . Mid ( substr_start , quotepos - substr_start ) ;
}
buf < < " \\ \" " ;
substr_start = quotepos + 1 ;
}
buf < < argv [ arg ] . Mid ( substr_start ) < < " \" " ;
}
else if ( strchr ( argv [ arg ] , ' ' ) )
{ // If it contains a space, it needs to be quoted.
buf < < ' " ' < < argv [ arg ] < < " \" " ;
}
else
{
buf < < argv [ arg ] < < ' ' ;
}
}
return buf ;
}
}
//===========================================================================
//
// SubstituteAliasParams
//
// Given an command line and a set of arguments, replace instances of
// %x or %{x} in the command line with argument x. If argument x does not
// exist, then the empty string is substituted in its place.
//
// Substitution is not done inside of quoted strings, unless that string is
// prepended with a % character.
//
// To avoid a substitution, use %%. The %% will be replaced by a single %.
//
//===========================================================================
2020-04-11 22:04:02 +00:00
void FConsoleCommand : : PrintCommand ( )
{
Printf ( " %s \n " , m_Name . GetChars ( ) ) ;
}
2019-11-04 22:01:50 +00:00
FString SubstituteAliasParams ( FString & command , FCommandLine & args )
{
// Do substitution by replacing %x with the argument x.
// If there is no argument x, then %x is simply removed.
// For some reason, strtoul's stop parameter is non-const.
char * p = command . LockBuffer ( ) , * start = p ;
unsigned long argnum ;
FString buf ;
bool inquote = false ;
while ( * p ! = ' \0 ' )
{
if ( p [ 0 ] = = ' % ' & & ( ( p [ 1 ] > = ' 0 ' & & p [ 1 ] < = ' 9 ' ) | | p [ 1 ] = = ' { ' ) )
{
// Do a substitution. Output what came before this.
buf . AppendCStrPart ( start , p - start ) ;
// Extract the argument number and substitute the corresponding argument.
argnum = strtoul ( p + 1 + ( p [ 1 ] = = ' { ' ) , & start , 10 ) ;
if ( ( p [ 1 ] ! = ' { ' | | * start = = ' } ' ) & & argnum < ( unsigned long ) args . argc ( ) )
{
buf + = args [ argnum ] ;
}
p = ( start + = ( p [ 1 ] = = ' { ' & & * start = = ' } ' ) ) ;
}
else if ( p [ 0 ] = = ' % ' & & p [ 1 ] = = ' % ' )
{
// Do not substitute. Just collapse to a single %.
buf . AppendCStrPart ( start , p - start + 1 ) ;
start = p = p + 2 ;
continue ;
}
else if ( p [ 0 ] = = ' % ' & & p [ 1 ] = = ' " ' )
{
// Collapse %" to " and remember that we're in a quote so when we
// see a " character again, we don't start skipping below.
if ( ! inquote )
{
inquote = true ;
buf . AppendCStrPart ( start , p - start ) ;
start = p + 1 ;
}
else
{
inquote = false ;
}
p + = 2 ;
}
else if ( p [ 0 ] = = ' \\ ' & & p [ 1 ] = = ' " ' )
{
p + = 2 ;
}
else if ( p [ 0 ] = = ' " ' )
{
// Don't substitute inside quoted strings if it didn't start
// with a %"
if ( ! inquote )
{
p + + ;
while ( * p ! = ' \0 ' & & ( * p ! = ' " ' | | * ( p - 1 ) = = ' \\ ' ) )
p + + ;
if ( * p ! = ' \0 ' )
p + + ;
}
else
{
inquote = false ;
p + + ;
}
}
else
{
p + + ;
}
}
// Return whatever was after the final substitution.
if ( p > start )
{
buf . AppendCStrPart ( start , p - start ) ;
}
command . UnlockBuffer ( ) ;
return buf ;
}
static int DumpHash ( FConsoleCommand * * table , bool aliases , const char * pattern = NULL )
{
int bucket , count ;
FConsoleCommand * cmd ;
for ( bucket = count = 0 ; bucket < FConsoleCommand : : HASH_SIZE ; bucket + + )
{
cmd = table [ bucket ] ;
while ( cmd )
{
if ( CheckWildcards ( pattern , cmd - > m_Name ) )
{
if ( cmd - > IsAlias ( ) )
{
if ( aliases )
{
+ + count ;
static_cast < FConsoleAlias * > ( cmd ) - > PrintAlias ( ) ;
}
}
else if ( ! aliases )
{
+ + count ;
cmd - > PrintCommand ( ) ;
}
}
cmd = cmd - > m_Next ;
}
}
return count ;
}
void FConsoleAlias : : PrintAlias ( )
{
if ( m_Command [ 0 ] . IsNotEmpty ( ) )
{
2020-01-07 00:11:19 +00:00
Printf ( TEXTCOLOR_YELLOW " %s : %s \n " , m_Name . GetChars ( ) , m_Command [ 0 ] . GetChars ( ) ) ;
2019-11-04 22:01:50 +00:00
}
if ( m_Command [ 1 ] . IsNotEmpty ( ) )
{
2020-01-07 00:11:19 +00:00
Printf ( TEXTCOLOR_ORANGE " %s : %s \n " , m_Name . GetChars ( ) , m_Command [ 1 ] . GetChars ( ) ) ;
2019-11-04 22:01:50 +00:00
}
}
void FConsoleAlias : : Archive ( FConfigFile * f )
{
if ( f ! = NULL & & ! m_Command [ 0 ] . IsEmpty ( ) )
{
f - > SetValueForKey ( " Name " , m_Name , true ) ;
f - > SetValueForKey ( " Command " , m_Command [ 0 ] , true ) ;
}
}
void C_ArchiveAliases ( FConfigFile * f )
{
int bucket ;
FConsoleCommand * alias ;
for ( bucket = 0 ; bucket < FConsoleCommand : : HASH_SIZE ; bucket + + )
{
alias = Commands [ bucket ] ;
while ( alias )
{
if ( alias - > IsAlias ( ) )
static_cast < FConsoleAlias * > ( alias ) - > Archive ( f ) ;
alias = alias - > m_Next ;
}
}
}
void C_ClearAliases ( )
{
int bucket ;
FConsoleCommand * alias ;
for ( bucket = 0 ; bucket < FConsoleCommand : : HASH_SIZE ; bucket + + )
{
alias = Commands [ bucket ] ;
while ( alias )
{
FConsoleCommand * next = alias - > m_Next ;
if ( alias - > IsAlias ( ) )
static_cast < FConsoleAlias * > ( alias ) - > SafeDelete ( ) ;
alias = next ;
}
}
}
CCMD ( clearaliases )
{
C_ClearAliases ( ) ;
}
// This is called only by the ini parser.
void C_SetAlias ( const char * name , const char * cmd )
{
FConsoleCommand * prev , * alias , * * chain ;
chain = & Commands [ MakeKey ( name ) % FConsoleCommand : : HASH_SIZE ] ;
alias = ScanChainForName ( * chain , name , strlen ( name ) , & prev ) ;
if ( alias ! = NULL )
{
if ( ! alias - > IsAlias ( ) )
{
//Printf (PRINT_BOLD, "%s is a command and cannot be an alias.\n", name);
return ;
}
delete alias ;
}
new FConsoleAlias ( name , cmd , false ) ;
}
CCMD ( alias )
{
FConsoleCommand * prev , * alias , * * chain ;
if ( argv . argc ( ) = = 1 )
{
Printf ( " Current alias commands: \n " ) ;
DumpHash ( Commands , true ) ;
}
else
{
chain = & Commands [ MakeKey ( argv [ 1 ] ) % FConsoleCommand : : HASH_SIZE ] ;
if ( argv . argc ( ) = = 2 )
{ // Remove the alias
if ( ( alias = ScanChainForName ( * chain , argv [ 1 ] , strlen ( argv [ 1 ] ) , & prev ) ) )
{
if ( alias - > IsAlias ( ) )
{
static_cast < FConsoleAlias * > ( alias ) - > SafeDelete ( ) ;
}
else
{
2020-01-07 00:11:19 +00:00
Printf ( " %s is a normal command \n " , alias - > m_Name . GetChars ( ) ) ;
2019-11-04 22:01:50 +00:00
}
}
}
else
{ // Add/change the alias
alias = ScanChainForName ( * chain , argv [ 1 ] , strlen ( argv [ 1 ] ) , & prev ) ;
if ( alias ! = NULL )
{
if ( alias - > IsAlias ( ) )
{
2020-04-11 22:04:02 +00:00
static_cast < FConsoleAlias * > ( alias ) - > Realias ( argv [ 2 ] , ParsingKeyConf ) ;
2019-11-04 22:01:50 +00:00
}
else
{
2020-01-07 00:11:19 +00:00
Printf ( " %s is a normal command \n " , alias - > m_Name . GetChars ( ) ) ;
2019-11-04 22:01:50 +00:00
alias = NULL ;
}
}
2020-04-11 22:04:02 +00:00
else if ( ParsingKeyConf )
{
new FUnsafeConsoleAlias ( argv [ 1 ] , argv [ 2 ] ) ;
}
2019-11-04 22:01:50 +00:00
else
{
new FConsoleAlias ( argv [ 1 ] , argv [ 2 ] , false ) ;
}
}
}
}
CCMD ( cmdlist )
{
int count ;
const char * filter = ( argv . argc ( ) = = 1 ? NULL : argv [ 1 ] ) ;
count = buttonMap . ListActionCommands ( filter ) ;
count + = DumpHash ( Commands , false , filter ) ;
Printf ( " %d commands \n " , count ) ;
}
CCMD ( key )
{
if ( argv . argc ( ) > 1 )
{
int i ;
for ( i = 1 ; i < argv . argc ( ) ; + + i )
{
unsigned int key = MakeKey ( argv [ i ] ) ;
Printf ( " 0x%08x \n " , key ) ;
}
}
}
// Execute any console commands specified on the command line.
// These all begin with '+' as opposed to '-'.
FExecList * C_ParseCmdLineParams ( FExecList * exec )
{
for ( int currArg = 1 ; currArg < Args - > NumArgs ( ) ; )
{
if ( * Args - > GetArg ( currArg + + ) = = ' + ' )
{
FString cmdString ;
int cmdlen = 1 ;
int argstart = currArg - 1 ;
while ( currArg < Args - > NumArgs ( ) )
{
if ( * Args - > GetArg ( currArg ) = = ' - ' | | * Args - > GetArg ( currArg ) = = ' + ' )
break ;
currArg + + ;
cmdlen + + ;
}
cmdString = BuildString ( cmdlen , Args - > GetArgList ( argstart ) ) ;
if ( ! cmdString . IsEmpty ( ) )
{
if ( exec = = NULL )
{
exec = new FExecList ;
}
exec - > AddCommand ( & cmdString [ 1 ] ) ;
}
}
}
return exec ;
}
bool FConsoleCommand : : IsAlias ( )
{
return false ;
}
bool FConsoleAlias : : IsAlias ( )
{
return true ;
}
2020-04-11 22:04:02 +00:00
void FConsoleAlias : : Run ( FCommandLine & args , int key )
2019-11-04 22:01:50 +00:00
{
if ( bRunning )
{
2020-01-07 00:11:19 +00:00
Printf ( " Alias %s tried to recurse. \n " , m_Name . GetChars ( ) ) ;
2019-11-04 22:01:50 +00:00
return ;
}
int index = ! m_Command [ 1 ] . IsEmpty ( ) ;
FString savedcommand = m_Command [ index ] , mycommand ;
m_Command [ index ] = FString ( ) ;
if ( bDoSubstitution )
{
mycommand = SubstituteAliasParams ( savedcommand , args ) ;
}
else
{
mycommand = savedcommand ;
}
bRunning = true ;
AddCommandString ( mycommand , key ) ;
bRunning = false ;
if ( m_Command [ index ] . IsEmpty ( ) )
{ // The alias is unchanged, so put the command back so it can be used again.
// If the command had been non-empty, then that means that executing this
// alias caused it to realias itself, so the old command will be forgotten
// once this function returns.
m_Command [ index ] = savedcommand ;
}
if ( bKill )
{ // The alias wants to remove itself
delete this ;
}
}
void FConsoleAlias : : Realias ( const char * command , bool noSave )
{
if ( ! noSave & & ! m_Command [ 1 ] . IsEmpty ( ) )
{
noSave = true ;
}
m_Command [ noSave ] = command ;
// If the command contains % characters, assume they are parameter markers
// for substitution when the command is executed.
bDoSubstitution = ( strchr ( command , ' % ' ) ! = NULL ) ;
bKill = false ;
}
void FConsoleAlias : : SafeDelete ( )
{
if ( ! bRunning )
{
delete this ;
}
else
{
bKill = true ;
}
}
2020-04-11 22:04:02 +00:00
void FUnsafeConsoleAlias : : Run ( FCommandLine & args , int key )
{
UnsafeExecutionScope scope ;
FConsoleAlias : : Run ( args , key ) ;
}
2019-11-04 22:01:50 +00:00
void FExecList : : AddCommand ( const char * cmd , const char * file )
{
2020-04-11 22:04:02 +00:00
// Pullins are special and need to be separated from general commands.
// They also turned out to be a really bad idea, since they make things
// more complicated. :(
if ( file ! = NULL & & strnicmp ( cmd , " pullin " , 6 ) = = 0 & & isspace ( cmd [ 6 ] ) )
{
FCommandLine line ( cmd ) ;
C_SearchForPullins ( this , file , line ) ;
}
2019-11-04 22:01:50 +00:00
// Recursive exec: Parse this file now.
2020-04-11 22:04:02 +00:00
else if ( strnicmp ( cmd , " exec " , 4 ) = = 0 & & isspace ( cmd [ 4 ] ) )
2019-11-04 22:01:50 +00:00
{
FCommandLine argv ( cmd ) ;
for ( int i = 1 ; i < argv . argc ( ) ; + + i )
{
C_ParseExecFile ( argv [ i ] , this ) ;
}
}
else
{
Commands . Push ( cmd ) ;
}
}
void FExecList : : ExecCommands ( ) const
{
for ( unsigned i = 0 ; i < Commands . Size ( ) ; + + i )
{
AddCommandString ( Commands [ i ] ) ;
}
}
2020-04-11 22:07:52 +00:00
void FExecList : : AddPullins ( TArray < FString > & wads , FConfigFile * config ) const
2020-04-11 22:04:02 +00:00
{
for ( unsigned i = 0 ; i < Pullins . Size ( ) ; + + i )
{
2020-04-11 22:07:52 +00:00
D_AddFile ( wads , Pullins [ i ] , true , - 1 , config ) ;
2020-04-11 22:04:02 +00:00
}
}
2019-11-04 22:01:50 +00:00
FExecList * C_ParseExecFile ( const char * file , FExecList * exec )
{
char cmd [ 4096 ] ;
int retval = 0 ;
FileReader fr ;
if ( ( fr . OpenFile ( file ) ) )
{
while ( fr . Gets ( cmd , countof ( cmd ) - 1 ) )
{
// Comments begin with //
char * stop = cmd + strlen ( cmd ) - 1 ;
char * comment = cmd ;
int inQuote = 0 ;
if ( * stop = = ' \n ' )
* stop - - = 0 ;
while ( comment < stop )
{
if ( * comment = = ' \" ' )
{
inQuote ^ = 1 ;
}
else if ( ! inQuote & & * comment = = ' / ' & & * ( comment + 1 ) = = ' / ' )
{
break ;
}
comment + + ;
}
if ( comment = = cmd )
{ // Comment at line beginning
continue ;
}
else if ( comment < stop )
{ // Comment in middle of line
* comment = 0 ;
}
if ( exec = = NULL )
{
exec = new FExecList ;
}
exec - > AddCommand ( cmd , file ) ;
}
}
else
{
Printf ( " Could not open \" %s \" \n " , file ) ;
}
return exec ;
}
bool C_ExecFile ( const char * file )
{
FExecList * exec = C_ParseExecFile ( file , NULL ) ;
if ( exec ! = NULL )
{
exec - > ExecCommands ( ) ;
2020-04-11 22:04:02 +00:00
if ( exec - > Pullins . Size ( ) > 0 )
{
Printf ( TEXTCOLOR_BOLD " Notice: Pullin files were ignored. \n " ) ;
}
2019-11-04 22:01:50 +00:00
delete exec ;
}
return exec ! = NULL ;
}
2020-04-11 22:04:02 +00:00
void C_SearchForPullins ( FExecList * exec , const char * file , FCommandLine & argv )
{
const char * lastSlash ;
assert ( exec ! = NULL ) ;
assert ( file ! = NULL ) ;
# ifdef __unix__
lastSlash = strrchr ( file , ' / ' ) ;
# else
const char * lastSlash1 , * lastSlash2 ;
lastSlash1 = strrchr ( file , ' / ' ) ;
lastSlash2 = strrchr ( file , ' \\ ' ) ;
lastSlash = MAX ( lastSlash1 , lastSlash2 ) ;
# endif
for ( int i = 1 ; i < argv . argc ( ) ; + + i )
{
// Try looking for the wad in the same directory as the .cfg
// before looking for it in the current directory.
if ( lastSlash ! = NULL )
{
FString path ( file , ( lastSlash - file ) + 1 ) ;
path + = argv [ i ] ;
if ( FileExists ( path ) )
{
exec - > Pullins . Push ( path ) ;
continue ;
}
}
exec - > Pullins . Push ( argv [ i ] ) ;
}
}
2019-10-24 22:20:19 +00:00
static TArray < FConsoleCommand * > dynccmds ; // This needs to be explicitly deleted before shutdown - the names in here may not be valid during the exit handler.
//
2020-04-11 22:04:02 +00:00
// C_RegisterFunction() -- dynamically register a CCMD.
2019-10-24 22:20:19 +00:00
//
2020-04-11 22:04:02 +00:00
int C_RegisterFunction ( const char * pszName , const char * pszDesc , int ( * func ) ( CCmdFuncPtr ) )
2019-10-24 22:20:19 +00:00
{
FString nname = pszName ;
2020-04-11 22:04:02 +00:00
auto callback = [ nname , pszDesc , func ] ( FCommandLine & args , int key )
2019-10-24 22:20:19 +00:00
{
if ( args . argc ( ) > 0 ) args . operator [ ] ( 0 ) ;
2020-04-11 22:04:02 +00:00
CCmdFuncParm param = { args . argc ( ) - 1 , nname . GetChars ( ) , ( const char * * ) args . _argv + 1 , args . cmd } ;
if ( func ( & param ) ! = CCMD_OK )
2019-10-24 22:20:19 +00:00
{
Printf ( " %s \n " , pszDesc ) ;
}
} ;
auto ccmd = new FConsoleCommand ( pszName , callback ) ;
dynccmds . Push ( ccmd ) ;
return 0 ;
}
2019-12-24 17:53:29 +00:00
void C_ClearDynCCmds ( )
{
for ( auto ccmd : dynccmds )
{
delete ccmd ;
}
dynccmds . Clear ( ) ;
}
2020-04-11 22:04:02 +00:00
CCMD ( pullin )
2019-11-04 23:48:27 +00:00
{
2020-04-11 22:04:02 +00:00
// Actual handling for pullin is now completely special-cased above
Printf ( TEXTCOLOR_BOLD " Pullin " TEXTCOLOR_NORMAL " is only valid from .cfg \n "
" files and only when used at startup. \n " ) ;
2019-11-04 23:48:27 +00:00
}