This repository has been archived on 2022-06-27. You can view files and clone it, but cannot push or open issues or pull requests.
Xash3DArchive/launch/cmd.c

703 lines
14 KiB
C

//=======================================================================
// Copyright XashXT Group 2007 ©
// cmd.c - script command processing module
//=======================================================================
#include "launch.h"
#define MAX_CMD_BUFFER 16384
#define MAX_CMD_LINE 1024
typedef struct
{
byte *data;
int maxsize;
int cursize;
} cmd_t;
int cmd_wait;
cmd_t cmd_text;
byte cmd_text_buf[MAX_CMD_BUFFER];
/*
=============================================================================
COMMAND BUFFER
=============================================================================
*/
/*
============
Cbuf_Init
============
*/
void Cbuf_Init( void )
{
cmd_text.data = cmd_text_buf;
cmd_text.maxsize = MAX_CMD_BUFFER;
cmd_text.cursize = 0;
}
/*
============
Cbuf_AddText
Adds command text at the end of the buffer
============
*/
void Cbuf_AddText( const char *text )
{
int l;
l = com.strlen( text );
if( cmd_text.cursize + l >= cmd_text.maxsize )
{
MsgDev( D_WARN, "Cbuf_AddText: overflow\n" );
return;
}
Mem_Copy( &cmd_text.data[cmd_text.cursize], text, l );
cmd_text.cursize += l;
}
/*
============
Cbuf_InsertText
Adds command text immediately after the current command
Adds a \n to the text
============
*/
void Cbuf_InsertText( const char *text )
{
int i, len;
len = com.strlen( text ) + 1;
if( len + cmd_text.cursize > cmd_text.maxsize )
{
MsgDev( D_WARN, "Cbuf_InsertText overflowed\n" );
return;
}
// move the existing command text
for( i = cmd_text.cursize - 1; i >= 0; i-- )
{
cmd_text.data[i + len] = cmd_text.data[i];
}
// copy the new text in
Mem_Copy( cmd_text.data, (char *)text, len - 1 );
cmd_text.data[len - 1] = '\n'; // add a \n
cmd_text.cursize += len;
}
/*
============
Cbuf_ExecuteText
============
*/
void Cbuf_ExecuteText( int exec_when, const char *text )
{
switch( exec_when )
{
case EXEC_NOW:
if( text && com.strlen( text ))
Cmd_ExecuteString( text );
else Cbuf_Execute();
break;
case EXEC_INSERT:
Cbuf_InsertText( text );
break;
case EXEC_APPEND:
Cbuf_AddText( text );
break;
default:
MsgDev( D_ERROR, "Cbuf_ExecuteText: bad execute target\n" );
break;
}
}
/*
============
Cbuf_Execute
============
*/
void Cbuf_Execute( void )
{
char *text;
char line[MAX_CMD_LINE];
int i, quotes;
while( cmd_text.cursize )
{
if( cmd_wait )
{
// skip out while text still remains in buffer, leaving it for next frame
cmd_wait--;
break;
}
// find a \n or ; line break
text = (char *)cmd_text.data;
quotes = 0;
for( i = 0; i < cmd_text.cursize; i++ )
{
if( text[i] == '"') quotes++;
if(!( quotes & 1 ) && text[i] == ';' )
break; // don't break if inside a quoted string
if( text[i] == '\n' || text[i] == '\r' ) break;
}
if( i >= MAX_CMD_LINE - 1 )
Sys_Error( "Cbuf_Execute: command string owerflow\n" );
Mem_Copy( line, text, i );
line[i] = 0;
// delete the text from the command buffer and move remaining commands down
// this is necessary because commands (exec) can insert data at the
// beginning of the text buffer
if( i == cmd_text.cursize )
{
cmd_text.cursize = 0;
}
else
{
i++;
cmd_text.cursize -= i;
memmove( text, text + i, cmd_text.cursize );
}
// execute the command line
Cmd_ExecuteString( line );
}
}
/*
==============================================================================
SCRIPT COMMANDS
==============================================================================
*/
/*
===============
Cmd_StuffCmds_f
Adds command line parameters as script statements
Commands lead with a +, and continue until a - or another +
xash +prog jctest.qp +cmd amlev1
xash -nosound +cmd amlev1
===============
*/
void Cmd_StuffCmds_f( void )
{
int i, j, l = 0;
char build[MAX_SYSPATH]; // this is for all commandline options combined (and is bounds checked)
if( Cmd_Argc() != 1 )
{
Msg( "Usage: stuffcmds : execute command line parameters\n" );
return;
}
// no reason to run the commandline arguments twice
if( Sys.stuffcmdsrun ) return;
Sys.stuffcmdsrun = true;
build[0] = 0;
for( i = 0; i < fs_argc; i++ )
{
if( fs_argv[i] && fs_argv[i][0] == '+' && ( fs_argv[i][1] < '0' || fs_argv[i][1] > '9' ) && l + com.strlen( fs_argv[i] ) - 1 <= sizeof( build ) - 1 )
{
j = 1;
while( fs_argv[i][j] )
build[l++] = fs_argv[i][j++];
for( i++; i < fs_argc; i++ )
{
if( !fs_argv[i] ) continue;
if(( fs_argv[i][0] == '+' || fs_argv[i][0] == '-' ) && ( fs_argv[i][1] < '0' || fs_argv[i][1] > '9' ))
break;
if( l + com.strlen( fs_argv[i]) + 4 > sizeof( build ) - 1 )
break;
build[l++] = ' ';
if( com.strchr( fs_argv[i], ' ' ))
build[l++] = '\"';
for( j = 0; fs_argv[i][j]; j++ )
build[l++] = fs_argv[i][j];
if( com.strchr( fs_argv[i], ' ' ))
build[l++] = '\"';
}
build[l++] = '\n';
i--;
}
}
// now terminate the combined string and prepend it to the command buffer
// we already reserved space for the terminator
build[l++] = 0;
Cbuf_InsertText( build );
}
/*
============
Cmd_Wait_f
Causes execution of the remainder of the command buffer to be delayed until
next frame. This allows commands like:
bind g "cmd use rocket ; +attack ; wait ; -attack ; cmd use blaster"
============
*/
void Cmd_Wait_f( void )
{
if( Cmd_Argc() == 1 ) cmd_wait = 1;
else cmd_wait = com.atoi( Cmd_Argv( 1 ));
}
/*
===============
Cmd_Exec_f
===============
*/
void Cmd_Exec_f( void )
{
string cfgpath;
size_t len;
char *f;
if( Cmd_Argc() != 2 )
{
Msg( "Usage: exec <filename>\n" );
return;
}
com.strncpy( cfgpath, Cmd_Argv( 1 ), sizeof( cfgpath ));
FS_DefaultExtension( cfgpath, ".cfg" ); // append as default
f = FS_LoadFile( cfgpath, &len );
if( !f )
{
MsgDev( D_NOTE, "couldn't exec %s\n", Cmd_Argv( 1 ));
return;
}
MsgDev( D_INFO, "execing %s\n", Cmd_Argv( 1 ));
Cbuf_InsertText( f );
Mem_Free( f );
}
/*
===============
Cmd_Echo_f
Just prints the rest of the line to the console
===============
*/
void Cmd_Echo_f( void )
{
int i;
for(i = 1; i < Cmd_Argc(); i++)
Msg("%s ",Cmd_Argv(i));
Msg ("\n");
}
/*
=============================================================================
COMMAND EXECUTION
=============================================================================
*/
#define CMD_EXTDLL BIT( 0 )
typedef struct cmd_function_s
{
struct cmd_function_s *next;
char *name;
char *desc;
xcommand_t function;
int flags;
} cmd_function_t;
static int cmd_argc;
static char *cmd_argv[MAX_CMD_TOKENS];
static char cmd_tokenized[MAX_CMD_BUFFER]; // will have 0 bytes inserted
static cmd_function_t *cmd_functions; // possible commands to execute
/*
============
Cmd_Argc
============
*/
uint Cmd_Argc( void )
{
return cmd_argc;
}
/*
============
Cmd_Argv
============
*/
char *Cmd_Argv( uint arg )
{
if( arg >= cmd_argc )
return "";
return cmd_argv[arg];
}
/*
============
Cmd_Args
Returns a single string containing argv(1) to argv(argc()-1)
============
*/
char *Cmd_Args( void )
{
static char cmd_args[MAX_SYSPATH];
int i;
cmd_args[0] = 0;
// build only for current call
for( i = 1; i < cmd_argc; i++ )
{
com.strcat( cmd_args, cmd_argv[i] );
if( i != cmd_argc - 1 ) com.strcat( cmd_args, " " );
}
return cmd_args;
}
/*
============
Cmd_TokenizeString
Parses the given string into command line tokens.
The text is copied to a seperate buffer and 0 characters
are inserted in the apropriate place, The argv array
will point into this temporary buffer.
============
*/
void Cmd_TokenizeString( const char *text_in )
{
const char *text;
char *textOut;
cmd_argc = 0; // clear previous args
if( !text_in ) return;
text = text_in;
textOut = cmd_tokenized;
while( 1 )
{
// this is usually something malicious
if( cmd_argc == MAX_CMD_TOKENS ) return;
while( 1 )
{
// skip whitespace
while( *text && *text <= ' ' ) text++;
if( !*text ) return; // all tokens parsed
// skip // comments
if( text[0] == '/' && text[1] == '/' ) return; // all tokens parsed
// skip /* */ comments
if( text[0] == '/' && text[1] =='*' )
{
while( *text && ( text[0] != '*' || text[1] != '/' )) text++;
if( !*text ) return; // all tokens parsed
text += 2;
}
else break; // we are ready to parse a token
}
// handle quoted strings
if( *text == '"' )
{
cmd_argv[cmd_argc] = textOut;
cmd_argc++;
text++;
while( *text && *text != '"' )
*textOut++ = *text++;
*textOut++ = 0;
if( !*text ) return; // all tokens parsed
text++;
continue;
}
// regular token
cmd_argv[cmd_argc] = textOut;
cmd_argc++;
// skip until whitespace, quote, or command
while( *text > ' ' )
{
if( text[0] == '"' ) break;
if( text[0] == '/' && text[1] == '/' ) break;
// skip /* */ comments
if( text[0] == '/' && text[1] =='*' ) break;
*textOut++ = *text++;
}
*textOut++ = 0;
if( !*text ) return; // all tokens parsed
}
}
/*
============
Cmd_AddCommand
============
*/
void Cmd_AddCommand( const char *cmd_name, xcommand_t function, const char *cmd_desc )
{
cmd_function_t *cmd;
// fail if the command already exists
if( Cmd_Exists( cmd_name ))
{
MsgDev(D_INFO, "Cmd_AddCommand: %s already defined\n", cmd_name);
return;
}
// use a small malloc to avoid zone fragmentation
cmd = Malloc( sizeof( cmd_function_t ));
cmd->name = copystring( cmd_name );
cmd->desc = copystring( cmd_desc );
cmd->function = function;
cmd->next = cmd_functions;
cmd_functions = cmd;
}
/*
============
Cmd_AddGameCommand
============
*/
void Cmd_AddGameCommand( const char *cmd_name, xcommand_t function )
{
cmd_function_t *cmd;
// fail if the command already exists
if( Cmd_Exists( cmd_name ))
{
MsgDev(D_INFO, "Cmd_AddCommand: %s already defined\n", cmd_name);
return;
}
// use a small malloc to avoid zone fragmentation
cmd = Malloc( sizeof( cmd_function_t ));
cmd->name = copystring( cmd_name );
cmd->function = function;
cmd->flags = CMD_EXTDLL;
cmd->next = cmd_functions;
cmd_functions = cmd;
}
/*
============
Cmd_RemoveCommand
============
*/
void Cmd_RemoveCommand (const char *cmd_name)
{
cmd_function_t *cmd, **back;
back = &cmd_functions;
while( 1 )
{
cmd = *back;
if (!cmd ) return;
if (!strcmp( cmd_name, cmd->name ))
{
*back = cmd->next;
if(cmd->name) Mem_Free(cmd->name);
if(cmd->desc) Mem_Free(cmd->desc);
Mem_Free(cmd);
return;
}
back = &cmd->next;
}
}
/*
============
Cmd_LookupCmds
============
*/
void Cmd_LookupCmds( char *buffer, void *ptr, setpair_t callback )
{
cmd_function_t *cmd;
// nothing to process ?
if( !callback ) return;
for( cmd = cmd_functions; cmd; cmd = cmd->next )
{
if( !buffer ) callback( cmd->name, (char *)cmd->function, cmd->desc, ptr );
else callback( cmd->name, (char *)cmd->function, buffer, ptr );
}
}
/*
============
Cmd_Exists
============
*/
bool Cmd_Exists( const char *cmd_name )
{
cmd_function_t *cmd;
for( cmd = cmd_functions; cmd; cmd = cmd->next )
{
if( !com.strcmp( cmd_name, cmd->name ))
return true;
}
return false;
}
/*
============
Cmd_ExecuteString
A complete command line has been parsed, so try to execute it
============
*/
void Cmd_ExecuteString( const char *text )
{
cmd_function_t *cmd, **prev;
// execute the command line
Cmd_TokenizeString( text );
if( !Cmd_Argc()) return; // no tokens
// check registered command functions
for( prev = &cmd_functions; *prev; prev = &cmd->next )
{
cmd = *prev;
if( !com.stricmp( cmd_argv[0], cmd->name ))
{
// rearrange the links so that the command will be
// near the head of the list next time it is used
*prev = cmd->next;
cmd->next = cmd_functions;
cmd_functions = cmd;
// perform the action
if( !cmd->function )
Cmd_ExecuteString( va( "cmd %s", text ));
else cmd->function();
return;
}
}
// check cvars
if( Cvar_Command()) return;
if( Sys.app_name == HOST_NORMAL && Sys.CmdFwd )
{
// all unrecognized commands will be forwarded to a server
Sys.CmdFwd();
}
else Msg( "Unknown command \"%s\"\n", text );
}
/*
============
Cmd_List_f
============
*/
void Cmd_List_f( void )
{
cmd_function_t *cmd;
int i = 0;
char *match;
if( Cmd_Argc() > 1 ) match = Cmd_Argv( 1 );
else match = NULL;
for( cmd = cmd_functions; cmd; cmd = cmd->next )
{
if( match && !com.stricmpext( match, cmd->name ))
continue;
Msg( "%s\n", cmd->name );
i++;
}
Msg( "%i commands\n", i );
}
/*
============
Cmd_Unlink
unlink all commands with flag CVAR_EXTDLL
============
*/
void Cmd_Unlink( void )
{
cmd_function_t *cmd;
cmd_function_t **prev;
int count = 0;
if( Cvar_VariableInteger( "host_gameloaded" ))
{
Msg( "can't unlink cvars while game is loaded\n" );
return;
}
// unlink commands first
Cmd_Unlink ();
prev = &cmd_functions;
while( 1 )
{
cmd = *prev;
if( !cmd ) break;
if( !( cmd->flags & CMD_EXTDLL ))
{
prev = &cmd->next;
continue;
}
*prev = cmd->next;
if( cmd->name ) Mem_Free( cmd->name );
if( cmd->desc ) Mem_Free( cmd->desc );
Mem_Free( cmd );
count++;
}
}
/*
============
Cmd_Init
============
*/
void Cmd_Init( void )
{
Cbuf_Init();
cmd_functions = NULL;
// register our commands
Cmd_AddCommand ("exec", Cmd_Exec_f, "execute a script file" );
Cmd_AddCommand ("echo", Cmd_Echo_f, "print a message to the console (useful in scripts)" );
Cmd_AddCommand ("wait", Cmd_Wait_f, "make script execution wait for some rendered frames" );
Cmd_AddCommand ("cmdlist", Cmd_List_f, "display all console commands beginning with the specified prefix" );
Cmd_AddCommand ("stuffcmds", Cmd_StuffCmds_f, "execute commandline parameters (must be present in valve.rc script)" );
Memory_Init_Commands(); // memlib stats
}