613 lines
13 KiB
C
613 lines
13 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 = 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], (char *)text, l);
|
|
cmd_text.cursize += l;
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Cbuf_InsertText
|
|
|
|
Adds command text immediately after the current command
|
|
Adds a \n to the text
|
|
FIXME: actually change the command buffer to do less copying
|
|
============
|
|
*/
|
|
void Cbuf_InsertText (const char *text)
|
|
{
|
|
int i, len;
|
|
|
|
len = 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 && strlen(text))
|
|
Cmd_ExecuteString(text);
|
|
else Cbuf_Execute();
|
|
break;
|
|
case EXEC_INSERT:
|
|
Cbuf_InsertText (text);
|
|
break;
|
|
case EXEC_APPEND:
|
|
Cbuf_AddText (text);
|
|
break;
|
|
default:
|
|
MsgWarn("Cbuf_ExecuteText: bad execute target\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cbuf_Execute
|
|
============
|
|
*/
|
|
void Cbuf_Execute( void )
|
|
{
|
|
int i;
|
|
char *text;
|
|
char line[MAX_CMD_LINE];
|
|
int 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)) i = MAX_CMD_LINE - 1;
|
|
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_INPUTLINE]; // this is for all commandline options combined (and is bounds checked)
|
|
|
|
if(Cmd_Argc() != 1)
|
|
{
|
|
Msg("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 + strlen(fs_argv[i]) - 1 <= sizeof(build) - 1)
|
|
{
|
|
j = 1;
|
|
while (fs_argv[i][j]) build[l++] = fs_argv[i][j++];
|
|
|
|
i++;
|
|
for ( ; 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 + strlen(fs_argv[i]) + 4 > sizeof(build) - 1)
|
|
break;
|
|
build[l++] = ' ';
|
|
if (strchr(fs_argv[i], ' ')) build[l++] = '\"';
|
|
for (j = 0; fs_argv[i][j]; j++) build[l++] = fs_argv[i][j];
|
|
if (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)
|
|
{
|
|
char *f, rcpath[MAX_QPATH];
|
|
int len;
|
|
|
|
if (Cmd_Argc () != 2)
|
|
{
|
|
Msg("exec <filename> : execute a script file\n");
|
|
return;
|
|
}
|
|
|
|
com_sprintf(rcpath, "scripts/config/%s", Cmd_Argv(1));
|
|
FS_DefaultExtension(rcpath, ".rc" ); // append as default
|
|
|
|
f = FS_LoadFile(rcpath, &len );
|
|
if (!f)
|
|
{
|
|
MsgWarn("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
|
|
|
|
=============================================================================
|
|
*/
|
|
|
|
typedef struct cmd_function_s
|
|
{
|
|
struct cmd_function_s *next;
|
|
char *name;
|
|
char *desc;
|
|
xcommand_t function;
|
|
} cmd_function_t;
|
|
|
|
static int cmd_argc;
|
|
static char *cmd_argv[MAX_STRING_TOKENS];
|
|
static char cmd_tokenized[MAX_INPUTLINE+MAX_STRING_TOKENS]; // 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_STRING_CHARS];
|
|
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_STRING_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_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_CommandCompletion
|
|
============
|
|
*/
|
|
void Cmd_CommandCompletion( void(*callback)(const char *s, const char *m))
|
|
{
|
|
cmd_function_t *cmd;
|
|
|
|
for (cmd = cmd_functions; cmd; cmd = cmd->next)
|
|
callback( cmd->name, cmd->desc );
|
|
}
|
|
|
|
/*
|
|
============
|
|
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 )
|
|
{ // forward to server command
|
|
Cmd_ExecuteString(va("cmd %s", text));
|
|
}
|
|
else cmd->function();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// check cvars
|
|
if(Cvar_Command()) return;
|
|
|
|
// send it as a server command if we are connected
|
|
if(std.Cmd_ForwardToServer) std.Cmd_ForwardToServer();
|
|
else MsgDev( D_ERROR, "Cmd_ExecuteString: can't send command to server\n");
|
|
}
|
|
|
|
/*
|
|
============
|
|
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 && !SC_FilterToken(match, cmd->name, false))
|
|
continue;
|
|
Msg("%s\n", cmd->name);
|
|
i++;
|
|
}
|
|
Msg("%i commands\n", i);
|
|
}
|
|
|
|
/*
|
|
============
|
|
Cmd_Init
|
|
============
|
|
*/
|
|
void Cmd_Init( void )
|
|
{
|
|
Cbuf_Init();
|
|
|
|
// 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 init.rc script)" );
|
|
}
|
|
|