diff options
Diffstat (limited to 'src/cmd.c')
-rw-r--r-- | src/cmd.c | 1803 |
1 files changed, 1803 insertions, 0 deletions
diff --git a/src/cmd.c b/src/cmd.c new file mode 100644 index 0000000..44098c5 --- /dev/null +++ b/src/cmd.c @@ -0,0 +1,1803 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cmd.c -- Quake script command processing module + +#include "com_local.h" +#include "files.h" +#include "q_list.h" +#if USE_CLIENT +#include "cl_public.h" +#endif + +#define Cmd_Malloc( size ) Z_TagMalloc( size, TAG_CMD ) +#define Cmd_CopyString( string ) Z_TagCopyString( string, TAG_CMD ) + +/* +============================================================================= + + COMMAND BUFFER + +============================================================================= +*/ + +char cmd_buffer_text[CMD_BUFFER_SIZE]; +cmdbuf_t cmd_buffer; + +// points to the buffer current command is executed from +cmdbuf_t *cmd_current; + +/* +============ +Cmd_Wait_f + +Causes execution of the remainder of the command buffer to be delayed until +next frame. This allows commands like: +bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2" +============ +*/ +static void Cmd_Wait_f( void ) { + int count; + + count = atoi( Cmd_Argv( 1 ) ); + if( count < 1 ) { + count = 1; + } + cmd_current->waitCount += count; +} + +/* +============ +Cbuf_Init +============ +*/ +void Cbuf_Init( void ) { + memset( &cmd_buffer, 0, sizeof( cmd_buffer ) ); + cmd_buffer.from = FROM_CONSOLE; + cmd_buffer.text = cmd_buffer_text; + cmd_buffer.maxsize = sizeof( cmd_buffer_text ); + cmd_buffer.exec = Cmd_ExecuteString; +} + +/* +============ +Cbuf_AddText + +Adds command text at the end of the buffer +============ +*/ +void Cbuf_AddText( cmdbuf_t *buf, const char *text ) { + size_t l = strlen( text ); + + if( buf->cursize + l > buf->maxsize ) { + Com_WPrintf( "%s: overflow\n", __func__ ); + return; + } + memcpy( buf->text + buf->cursize, text, l ); + buf->cursize += l; +} + +char *Cbuf_Alloc( cmdbuf_t *buf, size_t len ) { + char *text; + + if( buf->cursize + len > buf->maxsize ) { + return NULL; + } + text = buf->text + buf->cursize; + buf->cursize += len; + + return text; +} + +/* +============ +Cbuf_InsertText + +Adds command text at the beginning of command buffer. +Adds a \n to the text. +============ +*/ +void Cbuf_InsertText( cmdbuf_t *buf, const char *text ) { + size_t l = strlen( text ); + +// add the entire text of the file + if( !l ) { + return; + } + if( buf->cursize + l + 1 > buf->maxsize ) { + Com_WPrintf( "%s: overflow\n", __func__ ); + return; + } + + memmove( buf->text + l + 1, buf->text, buf->cursize ); + memcpy( buf->text, text, l ); + buf->text[l] = '\n'; + buf->cursize += l + 1; +} + +/* +============ +Cbuf_Execute +============ +*/ +void Cbuf_Execute( cmdbuf_t *buf ) { + int i; + char *text; + char line[MAX_STRING_CHARS]; + int quotes; + + while( buf->cursize ) { + if( buf->waitCount > 0 ) { + // skip out while text still remains in buffer, leaving it + // for next frame (counter is decremented externally now) + return; + } + +// find a \n or ; line break + text = buf->text; + + quotes = 0; + for( i = 0; i < buf->cursize; i++ ) { + if( text[i] == '"' ) + quotes++; + if( !( quotes & 1 ) && text[i] == ';' ) + break; // don't break if inside a quoted string + if( text[i] == '\n' ) + break; + } + + // check for overflow + if( i > sizeof( line ) - 1 ) { + i = sizeof( line ) - 1; + } + + memcpy( line, text, i ); + line[i] = 0; + +// delete the text from the command buffer and move remaining commands down +// this is necessary because commands (exec, alias) can insert data at the +// beginning of the text buffer + if( i == buf->cursize ) { + buf->cursize = 0; + } else { + i++; + buf->cursize -= i; + memmove( text, text + i, buf->cursize ); + } + +// execute the command line + cmd_current = buf; + buf->exec( buf, line ); + } + + buf->aliasCount = 0; // don't allow infinite alias loops +} + +/* +============================================================================== + + SCRIPT COMMANDS + +============================================================================== +*/ + +#define ALIAS_HASH_SIZE 64 + +#define FOR_EACH_ALIAS_HASH( alias, hash ) \ + LIST_FOR_EACH( cmdalias_t, alias, &cmd_aliasHash[hash], hashEntry ) +#define FOR_EACH_ALIAS( alias ) \ + LIST_FOR_EACH( cmdalias_t, alias, &cmd_alias, listEntry ) + +typedef struct cmdalias_s { + list_t hashEntry; + list_t listEntry; + char *value; + char name[1]; +} cmdalias_t; + +static list_t cmd_alias; +static list_t cmd_aliasHash[ALIAS_HASH_SIZE]; + +/* +=============== +Cmd_AliasFind +=============== +*/ +cmdalias_t *Cmd_AliasFind( const char *name ) { + unsigned hash; + cmdalias_t *alias; + + hash = Com_HashString( name, ALIAS_HASH_SIZE ); + FOR_EACH_ALIAS_HASH( alias, hash ) { + if( !strcmp( name, alias->name ) ) { + return alias; + } + } + + return NULL; +} + +char *Cmd_AliasCommand( const char *name ) { + cmdalias_t *a; + + a = Cmd_AliasFind( name ); + if( !a ) { + return NULL; + } + + return a->value; +} + +void Cmd_AliasSet( const char *name, const char *cmd ) { + cmdalias_t *a; + unsigned hash; + size_t len; + + // if the alias already exists, reuse it + a = Cmd_AliasFind( name ); + if( a ) { + Z_Free( a->value ); + a->value = Cmd_CopyString( cmd ); + return; + } + + len = strlen( name ); + a = Cmd_Malloc( sizeof( cmdalias_t ) + len ); + memcpy( a->name, name, len + 1 ); + a->value = Cmd_CopyString( cmd ); + + List_Append( &cmd_alias, &a->listEntry ); + + hash = Com_HashString( name, ALIAS_HASH_SIZE ); + List_Append( &cmd_aliasHash[hash], &a->hashEntry ); +} + +void Cmd_Alias_g( genctx_t *ctx ) { + cmdalias_t *a; + + FOR_EACH_ALIAS( a ) { + if( !Prompt_AddMatch( ctx, a->name ) ) { + break; + } + } +} + + +/* +=============== +Cmd_Alias_f + +Creates a new command that executes a command string (possibly ; seperated) +=============== +*/ +void Cmd_Alias_f( void ) { + cmdalias_t *a; + char *s, *cmd; + + if( Cmd_Argc() < 2 ) { + if( LIST_EMPTY( &cmd_alias ) ) { + Com_Printf( "No alias commands registered.\n" ); + return; + } + Com_Printf( "Registered alias commands:\n" ); + FOR_EACH_ALIAS( a ) { + Com_Printf( "\"%s\" = \"%s\"\n", a->name, a->value ); + } + return; + } + + s = Cmd_Argv( 1 ); + if( Cmd_Exists( s ) ) { + Com_Printf( "\"%s\" already defined as a command\n", s ); + return; + } + + if( Cvar_Exists( s ) ) { + Com_Printf( "\"%s\" already defined as a cvar\n", s ); + return; + } + + if( Cmd_Argc() < 3 ) { + a = Cmd_AliasFind( s ); + if( a ) { + Com_Printf( "\"%s\" = \"%s\"\n", a->name, a->value ); + } else { + Com_Printf( "\"%s\" is undefined\n", s ); + } + return; + } + + // copy the rest of the command line + cmd = Cmd_ArgsFrom( 2 ); + Cmd_AliasSet( s, cmd ); +} + +static void Cmd_UnAlias_f( void ) { + static const cmd_option_t options[] = { + { "h", "help", "display this message" }, + { "a", "all", "delete everything" }, + { 0 } + }; + char *s; + cmdalias_t *a, *n; + unsigned hash; + int c; + + while( ( c = Cmd_ParseOptions( options ) ) != -1 ) { + switch( c ) { + case 'h': + Com_Printf( "Usage: %s [-ha] [name]\n", Cmd_Argv( 0 ) ); + Cmd_PrintHelp( options ); + return; + case 'a': + LIST_FOR_EACH_SAFE( cmdalias_t, a, n, &cmd_alias, listEntry ) { + Z_Free( a->value ); + Z_Free( a ); + } + for( hash = 0; hash < ALIAS_HASH_SIZE; hash++ ) { + List_Init( &cmd_aliasHash[hash] ); + } + List_Init( &cmd_alias ); + Com_Printf( "Removed all alias commands.\n" ); + return; + default: + return; + } + } + + if( !cmd_optarg[0] ) { + Com_Printf( "Missing alias name.\n" + "Try %s --help for more information.\n", + Cmd_Argv( 0 ) ); + return; + } + + s = Cmd_Argv( 1 ); + a = Cmd_AliasFind( s ); + if( !a ) { + Com_Printf( "\"%s\" is undefined.\n", s ); + return; + } + + List_Delete( &a->listEntry ); + List_Delete( &a->hashEntry ); + + Z_Free( a->value ); + Z_Free( a ); +} + +#if USE_CLIENT +void Cmd_WriteAliases( qhandle_t f ) { + cmdalias_t *a; + + FOR_EACH_ALIAS( a ) { + FS_FPrintf( f, "alias \"%s\" \"%s\"\n", a->name, a->value ); + } +} +#endif + +static void Cmd_Alias_c( genctx_t *ctx, int argnum ) { + if( argnum == 1 ) { + Cmd_Alias_g( ctx ); + } else { + Com_Generic_c( ctx, argnum - 2 ); + } +} + +static void Cmd_UnAlias_c( genctx_t *ctx, int argnum ) { + if( argnum == 1 ) { + Cmd_Alias_g( ctx ); + } +} + +/* +============================================================================= + +MESSAGE TRIGGERS + +============================================================================= +*/ + +#define FOR_EACH_TRIGGER( trig ) \ + LIST_FOR_EACH( cmd_trigger_t, trig, &cmd_triggers, entry ) +#define FOR_EACH_TRIGGER_SAFE( trig, next ) \ + LIST_FOR_EACH_SAFE( cmd_trigger_t, trig, next, &cmd_triggers, entry ) + +typedef struct { + list_t entry; + char *match; + char *command; +} cmd_trigger_t; + +static list_t cmd_triggers; + +static cmd_trigger_t *find_trigger( const char *command, const char *match ) { + cmd_trigger_t *trigger; + + FOR_EACH_TRIGGER( trigger ) { + if( !strcmp( trigger->command, command ) && !strcmp( trigger->match, match ) ) { + return trigger; + } + } + + return NULL; +} + +static void list_triggers( void ) { + cmd_trigger_t *trigger; + + if( LIST_EMPTY( &cmd_triggers ) ) { + Com_Printf( "No current message triggers\n" ); + return; + } + + Com_Printf( "Current message triggers:\n" ); + FOR_EACH_TRIGGER( trigger ) { + Com_Printf( "\"%s\" = \"%s\"\n", trigger->command, trigger->match ); + } +} + +/* +============ +Cmd_Trigger_f +============ +*/ +static void Cmd_Trigger_f( void ) { + cmd_trigger_t *trigger; + const char *command, *match; + size_t cmdlen, matchlen; + + if( Cmd_Argc() == 1 ) { + list_triggers(); + return; + } + + if( Cmd_Argc() < 3 ) { + Com_Printf( "Usage: %s <command> <match>\n", Cmd_Argv(0) ); + return; + } + + command = Cmd_Argv( 1 ); + match = Cmd_ArgsFrom( 2 ); + + // don't create the same trigger twice + if( find_trigger( command, match ) ) { + return; + } + + cmdlen = strlen( command ) + 1; + matchlen = strlen( match ) + 1; + if( matchlen < 4 ) { + Com_Printf( "Match string is too short\n" ); + return; + } + + trigger = Z_Malloc( sizeof( *trigger ) + cmdlen + matchlen ); + trigger->command = (char *)(trigger + 1); + trigger->match = trigger->command + cmdlen; + memcpy( trigger->command, command, cmdlen ); + memcpy( trigger->match, match, matchlen ); + List_Append( &cmd_triggers, &trigger->entry ); +} + +static void Cmd_UnTrigger_f( void ) { + cmd_trigger_t *trigger, *next; + const char *command, *match; + + if( Cmd_Argc() == 1 ) { + list_triggers(); + return; + } + + if( LIST_EMPTY( &cmd_triggers ) ) { + Com_Printf( "No current message triggers\n" ); + return; + } + + if( Cmd_Argc() == 2 ) { + if( !Q_stricmp( Cmd_Argv( 1 ), "all" ) ) { + int count = 0; + + FOR_EACH_TRIGGER_SAFE( trigger, next ) { + Z_Free( trigger ); + count++; + } + + Com_Printf( "Removed %d trigger%s\n", count, count == 1 ? "" : "s" ); + List_Init( &cmd_triggers ); + return; + } + + Com_Printf( "Usage: %s <command> <match>\n", Cmd_Argv(0) ); + return; + } + + command = Cmd_Argv( 1 ); + match = Cmd_ArgsFrom( 2 ); + + trigger = find_trigger( command, match ); + if( !trigger ) { + Com_Printf( "Can't find trigger \"%s\" = \"%s\"\n", command, match ); + return; + } + + List_Remove( &trigger->entry ); + Z_Free( trigger ); +} + +/* +============ +Cmd_ExecTrigger +============ +*/ +void Cmd_ExecTrigger( const char *string ) { + cmd_trigger_t *trigger; + char *match; + + // execute matching triggers + FOR_EACH_TRIGGER( trigger ) { + match = Cmd_MacroExpandString( trigger->match, qfalse ); + if( match && Com_WildCmp( match, string, qfalse ) ) { + Cbuf_AddText( &cmd_buffer, trigger->command ); + Cbuf_AddText( &cmd_buffer, "\n" ); + } + } +} + +/* +============================================================================= + + MACRO EXECUTION + +============================================================================= +*/ + +#define MACRO_HASH_SIZE 64 + +static cmd_macro_t *cmd_macros; +static cmd_macro_t *cmd_macroHash[MACRO_HASH_SIZE]; + +/* +============ +Cmd_FindMacro +============ +*/ +cmd_macro_t *Cmd_FindMacro( const char *name ) { + cmd_macro_t *macro; + unsigned hash; + + hash = Com_HashString( name, MACRO_HASH_SIZE ); + for( macro = cmd_macroHash[hash]; macro; macro = macro->hashNext ) { + if( !strcmp( macro->name, name ) ) { + return macro; + } + } + + return NULL; +} + +void Cmd_Macro_g( genctx_t *ctx ) { + cmd_macro_t *m; + + for( m = cmd_macros; m; m = m->next ) { + if( !Prompt_AddMatch( ctx, m->name ) ) { + break; + } + } +} + +/* +============ +Cmd_AddMacro +============ +*/ +void Cmd_AddMacro( const char *name, xmacro_t function ) { + cmd_macro_t *macro; + cvar_t *var; + unsigned hash; + + var = Cvar_FindVar( name ); + if( var && !( var->flags & (CVAR_CUSTOM|CVAR_VOLATILE) ) ) { + Com_WPrintf( "Cmd_AddMacro: %s already defined as a cvar\n", name ); + return; + } + +// fail if the macro already exists + if( Cmd_FindMacro( name ) ) { + Com_WPrintf( "Cmd_AddMacro: %s already defined\n", name ); + return; + } + + macro = Cmd_Malloc( sizeof( cmd_macro_t ) ); + macro->name = name; + macro->function = function; + macro->next = cmd_macros; + cmd_macros = macro; + + hash = Com_HashString( name, MACRO_HASH_SIZE ); + macro->hashNext = cmd_macroHash[hash]; + cmd_macroHash[hash] = macro; +} + + +/* +============================================================================= + + COMMAND EXECUTION + +============================================================================= +*/ + +#define CMD_HASH_SIZE 128 + +#define FOR_EACH_CMD_HASH( cmd, hash ) \ + LIST_FOR_EACH( cmd_function_t, cmd, &cmd_hash[hash], hashEntry ) +#define FOR_EACH_CMD( cmd ) \ + LIST_FOR_EACH( cmd_function_t, cmd, &cmd_functions, listEntry ) + +typedef struct cmd_function_s { + list_t hashEntry; + list_t listEntry; + + xcommand_t function; + xcompleter_t completer; + char *name; +} cmd_function_t; + +static list_t cmd_functions; // possible commands to execute +static list_t cmd_hash[CMD_HASH_SIZE]; + +static int cmd_argc; +static char *cmd_argv[MAX_STRING_TOKENS]; // pointers to cmd_data[] +static char *cmd_null_string = ""; + +// complete command string, left untouched +static char cmd_string[MAX_STRING_CHARS]; +static size_t cmd_string_len; +size_t cmd_string_tail; + +// offsets of individual tokens into cmd_string +static size_t cmd_offsets[MAX_STRING_TOKENS]; + +// sequence of NULL-terminated, normalized tokens +static char cmd_data[MAX_STRING_CHARS]; + +// normalized command arguments +static char cmd_args[MAX_STRING_CHARS]; + +int cmd_optind; +char *cmd_optarg; +char *cmd_optopt; + +from_t Cmd_From( void ) { + return cmd_current->from; +} + +size_t Cmd_ArgOffset( int arg ) { + if( arg < 0 ) { + return 0; + } + if( arg >= cmd_argc ) { + return cmd_string_len; + } + return cmd_offsets[arg]; +} + +int Cmd_FindArgForOffset( size_t offset ) { + int i; + + for( i = 1; i < cmd_argc; i++ ) { + if( offset < cmd_offsets[i] ) { + break; + } + } + return i - 1; +} + +/* +============ +Cmd_Argc +============ +*/ +int Cmd_Argc( void ) { + return cmd_argc; +} + +/* +============ +Cmd_Argv +============ +*/ +char *Cmd_Argv( int arg ) { + if( arg < 0 || arg >= cmd_argc ) { + return cmd_null_string; + } + return cmd_argv[arg]; +} + +/* +============ +Cmd_ArgvBuffer +============ +*/ +size_t Cmd_ArgvBuffer( int arg, char *buffer, size_t size ) { + char *s; + + if( arg < 0 || arg >= cmd_argc ) { + s = cmd_null_string; + } else { + s = cmd_argv[arg]; + } + + return Q_strlcpy( buffer, s, size ); +} + + +/* +============ +Cmd_Args + +Returns a single string containing argv(1) to argv(argc()-1) +============ +*/ +char *Cmd_Args( void ) { + int i; + + if( cmd_argc < 2 ) { + return cmd_null_string; + } + + cmd_args[0] = 0; + for( i = 1; i < cmd_argc - 1; i++ ) { + strcat( cmd_args, cmd_argv[i] ); + strcat( cmd_args, " " ); + } + strcat( cmd_args, cmd_argv[i] ); + + return cmd_args; +} + +char *Cmd_RawArgs( void ) { + if( cmd_argc < 2 ) { + return cmd_null_string; + } + return cmd_string + cmd_offsets[1]; +} + +char *Cmd_RawString( void ) { + return cmd_string; +} + +/* +============ +Cmd_ArgsBuffer +============ +*/ +size_t Cmd_ArgsBuffer( char *buffer, size_t size ) { + return Q_strlcpy( buffer, Cmd_Args(), size ); +} + +/* +============ +Cmd_ArgsFrom + +Returns a single string containing argv(1) to argv(from-1) +============ +*/ +char *Cmd_ArgsFrom( int from ) { + int i; + + if( from < 0 || from >= cmd_argc ) { + return cmd_null_string; + } + + cmd_args[0] = 0; + for( i = from; i < cmd_argc - 1; i++ ) { + strcat( cmd_args, cmd_argv[i] ); + strcat( cmd_args, " " ); + } + strcat( cmd_args, cmd_argv[i] ); + + return cmd_args; +} + +char *Cmd_ArgsRange( int from, int to ) { + int i; + + if( from < 0 || from >= cmd_argc ) { + return cmd_null_string; + } + + if( to > cmd_argc - 1 ) { + to = cmd_argc - 1; + } + + cmd_args[0] = 0; + for( i = from; i < to; i++ ) { + strcat( cmd_args, cmd_argv[i] ); + strcat( cmd_args, " " ); + } + strcat( cmd_args, cmd_argv[i] ); + + return cmd_args; +} + +char *Cmd_RawArgsFrom( int from ) { + size_t offset; + + if( from < 0 || from >= cmd_argc ) { + return cmd_null_string; + } + + offset = cmd_offsets[from]; + + return cmd_string + offset; +} + +void Cmd_Shift( void ) { + int i; + + if( !cmd_argc ) { + return; + } + + if( cmd_argc == 1 ) { + cmd_string[0] = 0; + return; + } + + cmd_argc--; + for( i = 0; i < cmd_argc; i++ ) { + cmd_offsets[i] = cmd_offsets[ i + 1 ]; + cmd_argv[i] = cmd_argv[ i + 1 ]; + } + + memmove( cmd_string, cmd_string + cmd_offsets[1], + MAX_STRING_CHARS - cmd_offsets[1] ); +} + +int Cmd_ParseOptions( const cmd_option_t *opt ) { + const cmd_option_t *o; + char *s, *p; + + cmd_optopt = cmd_null_string; + + if( cmd_optind == cmd_argc ) { + cmd_optarg = cmd_null_string; + return -1; // no more arguments + } + + s = cmd_argv[cmd_optind]; + if( *s != '-' ) { + cmd_optarg = s; + return -1; // non-option argument + } + cmd_optopt = s++; + + if( *s == '-' ) { + s++; + if( *s == 0 ) { + if( ++cmd_optind < cmd_argc ) { + cmd_optarg = cmd_argv[cmd_optind]; + } else { + cmd_optarg = cmd_null_string; + } + return -1; // special terminator + } + if( ( p = strchr( s, '=' ) ) != NULL ) { + *p = 0; + } + for( o = opt; o->sh; o++ ) { + if( !strcmp( o->lo, s ) ) { + break; + } + } + if( p ) { + if( o->sh[1] == ':' ) { + cmd_optarg = p + 1; + } else { + Com_Printf( "%s does not take an argument.\n", o->lo ); + Cmd_PrintHint(); + } + *p = 0; + } + } else { + p = NULL; + if( s[1] ) { + goto unknown; + } + for( o = opt; o->sh; o++ ) { + if( o->sh[0] == *s ) { + break; + } + } + } + + if( !o->sh ) { +unknown: + Com_Printf( "Unknown option: %s.\n", cmd_argv[cmd_optind] ); + Cmd_PrintHint(); + return '?'; + } + + // parse option argument + if( !p && o->sh[1] == ':' ) { + if( cmd_optind + 1 == cmd_argc ) { + Com_Printf( "Missing argument to %s.\n", cmd_argv[cmd_optind] ); + Cmd_PrintHint(); + return ':'; + } + cmd_optarg = cmd_argv[++cmd_optind]; + } + + cmd_optind++; + + return o->sh[0]; +} + +void Cmd_PrintUsage( const cmd_option_t *opt, const char *suffix ) { + Com_Printf( "Usage: %s [-", cmd_argv[0] ); + while( opt->sh ) { + Com_Printf( "%c", opt->sh[0] ); + if( opt->sh[1] == ':' ) { + Com_Printf( ":" ); + } + opt++; + } + if( suffix ) { + Com_Printf( "] %s\n", suffix ); + } else { + Com_Printf( "]\n" ); + } +} + +void Cmd_PrintHelp( const cmd_option_t *opt ) { + char buffer[32]; + + Com_Printf( "\nAvailable options:\n" ); + while( opt->sh ) { + if( opt->sh[1] == ':' ) { + Q_concat( buffer, sizeof( buffer ), + opt->lo, "=<", opt->sh + 2, ">", NULL ); + } else { + Q_strlcpy( buffer, opt->lo, sizeof( buffer ) ); + } + Com_Printf( "-%c | --%-16.16s | %s\n", opt->sh[0], buffer, opt->help ); + opt++; + } + Com_Printf( "\n" ); +} + +void Cmd_PrintHint( void ) { + Com_Printf( "Try '%s --help' for more information.\n", cmd_argv[0] ); +} + +void Cmd_Option_c( const cmd_option_t *opt, xgenerator_t g, genctx_t *ctx, int argnum ) { + if( ctx->partial[0] == '-' ) { + for( ; opt->sh; opt++ ) { + if( ctx->count >= ctx->size ) { + break; + } + if( ctx->partial[1] == '-' ) { + if( !strncmp( opt->lo, ctx->partial + 2, ctx->length - 2 ) ) { + ctx->matches[ctx->count++] = Z_CopyString( va( "--%s", opt->lo ) ); + } + } else if( !ctx->partial[1] || opt->sh[0] == ctx->partial[1] ) { + ctx->matches[ctx->count++] = Z_CopyString( va( "-%c", opt->sh[0] ) ); + } + } + } else { + /* if( argnum > 1 ) { + s = cmd_argv[argnum - 1]; + }*/ + if( g ) { + g( ctx ); + } else if( !ctx->partial[0] && ctx->count < ctx->size ) { + ctx->matches[ctx->count++] = Z_CopyString( "-" ); + } + } +} + + +/* +====================== +Cmd_MacroExpandString +====================== +*/ +char *Cmd_MacroExpandString( const char *text, qboolean aliasHack ) { + size_t i, j, len; + int count; + qboolean inquote; + char *scan, *start; + static char expanded[MAX_STRING_CHARS]; + char temporary[MAX_STRING_CHARS]; + char buffer[MAX_STRING_CHARS]; + char *token; + cmd_macro_t *macro; + cvar_t *var; + qboolean rescan; + + len = strlen( text ); + if( len >= MAX_STRING_CHARS ) { + Com_Printf( "Line exceeded %i chars, discarded.\n", MAX_STRING_CHARS ); + return NULL; + } + + scan = memcpy( expanded, text, len + 1 ); + + inquote = qfalse; + count = 0; + + for( i = 0; i < len; i++ ) { + if( !scan[i] ) { + break; + } + if( scan[i] == '"' ) { + inquote ^= 1; + } + if( inquote ) { + continue; // don't expand inside quotes + } + if( scan[i] != '$' ) { + continue; + } + + // scan out the complete macro + start = scan + i + 1; + + if( *start == 0 ) { + break; // end of string + } + + // allow $$ escape syntax + if( *start == '$' ) { + memmove( scan + i, start, len - i ); + continue; + } + + // skip leading spaces + while( *start && *start <= 32 ) { + start++; + } + + token = temporary; + + if( *start == '{' ) { + // allow ${variable} syntax + start++; + if( *start == '$' ) { // allow ${$variable} syntax + start++; + } + while( *start ) { + if( *start == '}' ) { + start++; + break; + } + *token++ = *start++; + } + } else { + // parse single word + while( *start > 32 ) { + if( *start == '$' ) { // allow $var$ syntax + start++; + break; + } + *token++ = *start++; + } + } + + *token = 0; + + if( token == temporary ) { + continue; + } + + rescan = qfalse; + + if( aliasHack ) { + // expand positional parameters only + if( !strcmp( temporary, "@" ) ) { + token = Cmd_Args(); + } else { + int arg1, arg2; + char *s; + + // parse {arg1-arg2} format for ranges + arg1 = strtoul( temporary, &s, 10 ); + if( s[0] == '-' ) { + if( s[1] ) { + arg2 = strtoul( s + 1, &s, 10 ); + if( s[0] ) { + continue; // second part is not a number + } + } else { + arg2 = cmd_argc - 1; + } + token = Cmd_ArgsRange( arg1, arg2 ); + } else if( s[0] == 0 ) { + token = Cmd_Argv( arg1 ); + } else { + continue; // first part is not a number + } + } + } else { + // check for macros first + macro = Cmd_FindMacro( temporary ); + if( macro ) { + macro->function( buffer, MAX_STRING_CHARS - len ); + token = buffer; + } else { + // than variables + var = Cvar_FindVar( temporary ); + if( var && !( var->flags & CVAR_PRIVATE ) ) { + token = var->string; + rescan = qtrue; + } else if( !strcmp( temporary, "qt" ) ) { + token = "\""; + } else if( !strcmp( temporary, "sc" ) ) { + token = ";"; + } else { + token = ""; + } + } + } + + j = strlen( token ); + len += j; + if( len >= MAX_STRING_CHARS ) { + Com_Printf( "Expanded line exceeded %i chars, discarded.\n", + MAX_STRING_CHARS ); + return NULL; + } + + strncpy( temporary, scan, i ); + strcpy( temporary + i, token ); + strcpy( temporary + i + j, start ); + + strcpy( expanded, temporary ); + scan = expanded; + if( !rescan ) { + i += j; + } + i--; + + if( ++count == 100 ) { + Com_Printf( "Macro expansion loop, discarded.\n" ); + return NULL; + } + } + + if( inquote ) { + Com_Printf( "Line has unmatched quote, discarded.\n" ); + return NULL; + } + + return scan; +} + +/* +============ +Cmd_TokenizeString + +Parses the given string into command line tokens. +$Cvars will be expanded unless they are in a quoted token +============ +*/ +void Cmd_TokenizeString( const char *text, qboolean macroExpand ) { + int i; + char *data, *start, *dest; + +// clear the args from the last string + for( i = 0; i < cmd_argc; i++ ) { + cmd_argv[i] = NULL; + cmd_offsets[i] = 0; + } + + cmd_argc = 0; + cmd_string[0] = 0; + cmd_optind = 1; + cmd_optarg = cmd_optopt = cmd_null_string; + + if( !text[0] ) { + return; + } + +// macro expand the text + if( macroExpand ) { + text = Cmd_MacroExpandString( text, qfalse ); + if( !text ) { + return; + } + } + + cmd_string_len = Q_strlcpy( cmd_string, text, sizeof( cmd_string ) ); + if( cmd_string_len >= sizeof( cmd_string ) ) { + Com_Printf( "Line exceeded %i chars, discarded.\n", MAX_STRING_CHARS ); + return; + } + +// strip off any trailing whitespace + cmd_string_tail = 0; + while( cmd_string_len ) { + if( cmd_string[ cmd_string_len - 1 ] > ' ' ) { + break; + } + cmd_string[ cmd_string_len - 1 ] = 0; + cmd_string_len--; + cmd_string_tail++; + } + + dest = cmd_data; + start = data = cmd_string; + while( cmd_argc < MAX_STRING_TOKENS ) { +// skip whitespace up to a /n + while( *data <= ' ' ) { + if( *data == 0 ) { + return; // end of text + } + if( *data == '\n' ) { + return; // a newline seperates commands in the buffer + } + data++; + } + +// add new argument + cmd_offsets[cmd_argc] = data - start; + cmd_argv[cmd_argc] = dest; + cmd_argc++; + + if( *data == ';' ) { + data++; + *dest++ = ';'; + *dest++ = 0; + continue; + } + +// parse quoted string + if( *data == '\"' ) { + data++; + while( *data != '\"' ) { + if( *data == 0 ) { + return; // end of data + } + *dest++ = *data++; + } + data++; + *dest++ = 0; + continue; + } + +// parse reqular token + while( *data > ' ' ) { + if( *data == '\"' ) { + break; + } + if( *data == ';' ) { + break; + } + *dest++ = *data++; + } + *dest++ = 0; + + if( *data == 0 ) { + return; // end of text + } + } +} + +/* +============ +Cmd_Find +============ +*/ +cmd_function_t *Cmd_Find( const char *name ) { + cmd_function_t *cmd; + unsigned hash; + + hash = Com_HashString( name, CMD_HASH_SIZE ); + FOR_EACH_CMD_HASH( cmd, hash ) { + if( !strcmp( cmd->name, name ) ) { + return cmd; + } + } + + return NULL; +} + +static void Cmd_RegCommand( const cmdreg_t *reg ) { + cmd_function_t *cmd; + cvar_t *var; + unsigned hash; + +// fail if the command is a variable name + var = Cvar_FindVar( reg->name ); + if( var && !( var->flags & (CVAR_CUSTOM|CVAR_VOLATILE) ) ) { + Com_WPrintf( "%s: %s already defined as a cvar\n", __func__, reg->name ); + return; + } + +// fail if the command already exists + cmd = Cmd_Find( reg->name ); + if( cmd ) { + if( cmd->function ) { + Com_WPrintf( "%s: %s already defined\n", __func__, reg->name ); + return; + } + cmd->function = reg->function; + cmd->completer = reg->completer; + return; + } + + cmd = Cmd_Malloc( sizeof( *cmd ) ); + cmd->name = ( char * )reg->name; + cmd->function = reg->function; + cmd->completer = reg->completer; + + List_Append( &cmd_functions, &cmd->listEntry ); + + hash = Com_HashString( reg->name, CMD_HASH_SIZE ); + List_Append( &cmd_hash[hash], &cmd->hashEntry ); +} + +/* +============ +Cmd_AddCommand +============ +*/ +void Cmd_AddCommand( const char *name, xcommand_t function ) { + cmdreg_t reg; + + reg.name = name; + reg.function = function; + reg.completer = NULL; + Cmd_RegCommand( ® ); +} + +void Cmd_Register( const cmdreg_t *reg ) { + while( reg->name ) { + Cmd_RegCommand( reg ); + reg++; + } +} + +void Cmd_Deregister( const cmdreg_t *reg ) { + while( reg->name ) { + Cmd_RemoveCommand( reg->name ); + reg++; + } +} + +/* +============ +Cmd_RemoveCommand +============ +*/ +void Cmd_RemoveCommand( const char *name ) { + cmd_function_t *cmd; + + cmd = Cmd_Find( name ); + if( !cmd ) { + Com_DPrintf( "%s: %s not added\n", __func__, name ); + return; + } + + List_Delete( &cmd->listEntry ); + List_Delete( &cmd->hashEntry ); + Z_Free( cmd ); + +} + +/* +============ +Cmd_Exists +============ +*/ +qboolean Cmd_Exists( const char *name ) { + cmd_function_t *cmd = Cmd_Find( name ); + + return cmd ? qtrue : qfalse; +} + +xcommand_t Cmd_FindFunction( const char *name ) { + cmd_function_t *cmd = Cmd_Find( name ); + + return cmd ? cmd->function : NULL; +} + +xcompleter_t Cmd_FindCompleter( const char *name ) { + cmd_function_t *cmd = Cmd_Find( name ); + + return cmd ? cmd->completer : NULL; +} + +void Cmd_Command_g( genctx_t *ctx ) { + cmd_function_t *cmd; + + FOR_EACH_CMD( cmd ) { + if( !Prompt_AddMatch( ctx, cmd->name ) ) { + break; + } + } +} + +void Cmd_ExecuteCommand( cmdbuf_t *buf ) { + cmd_function_t *cmd; + cmdalias_t *a; + cvar_t *v; + char *text; + + // check functions + cmd = Cmd_Find( cmd_argv[0] ); + if( cmd ) { + if( cmd->function ) { + cmd->function(); + } else +#if USE_CLIENT + if( !CL_ForwardToServer() ) +#endif + { + Com_Printf( "Can't \"%s\", not connected\n", cmd_argv[0] ); + } + return; + } + + // check aliases + a = Cmd_AliasFind( cmd_argv[0] ); + if( a ) { + if( buf->aliasCount == ALIAS_LOOP_COUNT ) { + Com_WPrintf( "Runaway alias loop\n" ); + return; + } + text = Cmd_MacroExpandString( a->value, qtrue ); + if( text ) { + buf->aliasCount++; + Cbuf_InsertText( buf, text ); + } + return; + } + + // check variables + v = Cvar_FindVar( cmd_argv[0] ); + if( v ) { + Cvar_Command( v ); + return; + } + +#if USE_CLIENT + // send it as a server command if we are connected + if( !CL_ForwardToServer() ) +#endif + { + Com_Printf( "Unknown command \"%s\"\n", cmd_argv[0] ); + } +} + +/* +============ +Cmd_ExecuteString + +A complete command line has been parsed, so try to execute it +============ +*/ +void Cmd_ExecuteString( cmdbuf_t *buf, const char *text ) { + Cmd_TokenizeString( text, qtrue ); + + // execute the command line + if( !cmd_argc ) { + return; // no tokens + } + + Cmd_ExecuteCommand( buf ); +} + +/* +=============== +Cmd_Exec_f +=============== +*/ +static void Cmd_Exec_f( void ) { + char buffer[MAX_QPATH]; + char *f; + ssize_t len; + + if( Cmd_Argc() != 2 ) { + Com_Printf( "%s <filename> : execute a script file\n", Cmd_Argv( 0 ) ); + return; + } + + Cmd_ArgvBuffer( 1, buffer, sizeof( buffer ) ); + + len = FS_LoadFile( buffer, ( void ** )&f ); + if( !f ) { + // try with *.cfg extension + COM_DefaultExtension( buffer, ".cfg", sizeof( buffer ) ); + len = FS_LoadFile( buffer, ( void ** )&f ); + if( !f ) { + Com_Printf( "Couldn't exec %s: %s\n", buffer, Q_ErrorString( len ) ); + return; + } + } + + if( memchr( f, 0, len ) ) { + Com_Printf( "Refusing to exec binary file %s\n", buffer ); + goto finish; + } + + Com_Printf( "Execing %s\n", buffer ); + + // FIXME: bad thing to do in place + COM_Compress( f ); + + // FIXME: always insert into generic command buffer + Cbuf_InsertText( &cmd_buffer, f ); + +finish: + FS_FreeFile( f ); +} + +void Cmd_Config_g( genctx_t *ctx ) { + FS_File_g( "", "*.cfg", FS_SEARCH_SAVEPATH | FS_SEARCH_BYFILTER | 0x80000000, ctx ); +} + +static void Cmd_Exec_c( genctx_t *ctx, int argnum ) { + if( argnum == 1 ) { + Cmd_Config_g( ctx ); + } +} + +/* +=============== +Cmd_Echo_f + +Just prints the rest of the line to the console +=============== +*/ +static void Cmd_Echo_f( void ) { + Com_Printf( "%s\n", Cmd_Args() ); +} + +static const cmd_option_t o_echo[] = { + { "h", "help", "display this message" }, + { "e", "escapes", "enable interpretation of backslash escapes" }, + { "c:color", "color", "print text in this color" }, + { "n", "no-newline", "do not output the trailing newline" }, + { NULL } +}; + +static void Cmd_EchoEx_c( genctx_t *ctx, int argnum ) { + Cmd_Option_c( o_echo, NULL, ctx, argnum ); +} + +static char *unescape_string( char *dst, const char *src ) { + int c1, c2; + char *p = dst; + + while( *src ) { + if( src[0] == '\\' && src[1] ) { + switch( src[1] ) { + case 'a': *p++ = '\a'; break; + case 'b': *p++ = '\b'; break; + case 't': *p++ = '\t'; break; + case 'n': *p++ = '\n'; break; + case 'v': *p++ = '\v'; break; + case 'f': *p++ = '\f'; break; + case 'r': *p++ = '\r'; break; + case '\\': *p++ = '\\'; break; + case 'x': + if( ( c1 = Q_charhex( src[2] ) ) == -1 ) { + break; + } + if( ( c2 = Q_charhex( src[3] ) ) == -1 ) { + break; + } + *p++ = ( c1 << 4 ) | c2; + src += 2; + break; + default: + *p++ = src[1]; + break; + } + src += 2; + } else { + *p++ = *src++; + } + } + *p = 0; + + return dst; +} + +static void Cmd_EchoEx_f( void ) { + char buffer[MAX_STRING_CHARS], *s; + qboolean escapes = qfalse; + color_index_t color = COLOR_NONE; + const char *newline = "\n"; + int c; + + while( ( c = Cmd_ParseOptions( o_echo ) ) != -1 ) { + switch( c ) { + case 'h': + Cmd_PrintUsage( o_echo, "[text]" ); + Com_Printf( "Print a line of text into the console.\n" ); + Cmd_PrintHelp( o_echo ); + return; + case 'e': + escapes = qtrue; + break; + case 'c': + color = Com_ParseColor( cmd_optarg, COLOR_NONE ); + break; + case 'n': + newline = ""; + break; + default: + return; + } + } + + s = Cmd_RawArgsFrom( cmd_optind ); + if( escapes ) { + s = unescape_string( buffer, s ); + } + + Com_SetColor( color ); + Com_Printf( "%s%s", s, newline ); + Com_SetColor( COLOR_NONE ); +} + +/* +============ +Cmd_List_f +============ +*/ +static void Cmd_List_f( void ) { + cmd_function_t *cmd; + int i, total; + char *filter = NULL; + + if( cmd_argc > 1 ) { + filter = cmd_argv[1]; + } + + i = total = 0; + FOR_EACH_CMD( cmd ) { + total++; + if( filter && !Com_WildCmp( filter, cmd->name, qfalse ) ) { + continue; + } + Com_Printf( "%s\n", cmd->name ); + i++; + } + Com_Printf( "%i of %i commands\n", i, total ); +} + +/* +============ +Cmd_MacroList_f +============ +*/ +static void Cmd_MacroList_f( void ) { + cmd_macro_t *macro; + int i, total; + char *filter = NULL; + char buffer[MAX_QPATH]; + + if( cmd_argc > 1 ) { + filter = cmd_argv[1]; + } + + i = 0; + for( macro = cmd_macros, total = 0; macro; macro = macro->next, total++ ) { + if( filter && !Com_WildCmp( filter, macro->name, qfalse ) ) { + continue; + } + macro->function( buffer, sizeof( buffer ) ); + Com_Printf( "%-16s %s\n", macro->name, buffer ); + i++; + } + Com_Printf( "%i of %i macros\n", i, total ); +} + +static void Cmd_Text_f( void ) { + Cbuf_AddText( cmd_current, Cmd_Args() ); + Cbuf_AddText( cmd_current, "\n" ); +} + +static void Cmd_Complete_f( void ) { + cmd_function_t *cmd; + char *name; + unsigned hash; + size_t len; + + if( cmd_argc < 2 ) { + Com_Printf( "Usage: %s <command>", cmd_argv[0] ); + return; + } + + name = cmd_argv[1]; + +// fail if the command is a variable name + if( Cvar_Exists( name ) ) { + Com_Printf( "%s is already defined as a cvar\n", name ); + return; + } + +// fail if the command already exists + cmd = Cmd_Find( name ); + if( cmd ) { + //Com_Printf( "%s is already defined\n", name ); + return; + } + + len = strlen( name ) + 1; + cmd = Cmd_Malloc( sizeof( *cmd ) + len ); + cmd->name = ( char * )( cmd + 1 ); + memcpy( cmd->name, name, len ); + cmd->function = NULL; + cmd->completer = NULL; + + List_Append( &cmd_functions, &cmd->listEntry ); + + hash = Com_HashString( name, CMD_HASH_SIZE ); + List_Append( &cmd_hash[hash], &cmd->hashEntry ); +} + +void Com_Mixed_c( genctx_t *ctx, int argnum ) { +} + +static const cmdreg_t c_cmd[] = { + { "cmdlist", Cmd_List_f }, + { "macrolist", Cmd_MacroList_f }, + { "exec", Cmd_Exec_f, Cmd_Exec_c }, + { "echo", Cmd_Echo_f }, + { "_echo", Cmd_EchoEx_f, Cmd_EchoEx_c }, + { "alias", Cmd_Alias_f, Cmd_Alias_c }, + { "unalias", Cmd_UnAlias_f, Cmd_UnAlias_c }, + { "wait", Cmd_Wait_f }, + { "text", Cmd_Text_f }, + { "complete", Cmd_Complete_f }, + { "trigger", Cmd_Trigger_f }, + { "untrigger", Cmd_UnTrigger_f }, + + { NULL } +}; + +/* +============ +Cmd_Init +============ +*/ +void Cmd_Init( void ) { + int i; + + List_Init( &cmd_functions ); + for( i = 0; i < CMD_HASH_SIZE; i++ ) { + List_Init( &cmd_hash[i] ); + } + + List_Init( &cmd_alias ); + for( i = 0; i < ALIAS_HASH_SIZE; i++ ) { + List_Init( &cmd_aliasHash[i] ); + } + + List_Init( &cmd_triggers ); + + Cmd_Register( c_cmd ); +} + |