//======================================================================= // Copyright XashXT Group 2007 © // cmd.c - script command processing module //======================================================================= #include "common.h" #include "client.h" #include "server.h" #define MAX_CMD_BUFFER 16384 #define MAX_CMD_LINE 1024 #define MAX_ALIAS_NAME 32 typedef struct cmdalias_s { struct cmdalias_s *next; char name[MAX_ALIAS_NAME]; char *value; } cmdalias_t; typedef struct { byte *data; int cursize; int maxsize; } cmdbuf_t; qboolean cmd_wait; cmdbuf_t cmd_text; byte cmd_text_buf[MAX_CMD_BUFFER]; cmdalias_t *cmd_alias; /* ============================================================================= COMMAND BUFFER ============================================================================= */ /* ============ Cbuf_Init ============ */ void Cbuf_Init( void ) { cmd_text.data = cmd_text_buf; cmd_text.maxsize = MAX_CMD_BUFFER; cmd_text.cursize = 0; } void *Cbuf_GetSpace( cmdbuf_t *buf, int length ) { void *data; if( buf->cursize + length > buf->maxsize ) { buf->cursize = 0; Host_Error( "Cbuf_GetSpace: overflow\n" ); } data = buf->data + buf->cursize; buf->cursize += length; return data; } /* ============ Cbuf_AddText Adds command text at the end of the buffer ============ */ void Cbuf_AddText( const char *text ) { int l; l = Q_strlen( text ); if( cmd_text.cursize + l >= cmd_text.maxsize ) { MsgDev( D_WARN, "Cbuf_AddText: overflow\n" ); return; } Q_memcpy( Cbuf_GetSpace( &cmd_text, l ), text, 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 ) { char *temp; int templen; // copy off any commands still remaining in the exec buffer templen = cmd_text.cursize; if( templen ) { temp = Z_Malloc( templen ); Q_memcpy( temp, cmd_text.data, templen ); cmd_text.cursize = 0; } else temp = NULL; // add the entire text of the file Cbuf_AddText( text ); // add the copied off data if( templen ) { Q_memcpy( Cbuf_GetSpace( &cmd_text, templen ), temp, templen ); Z_Free( temp ); } } /* ============ Cbuf_Execute ============ */ void Cbuf_Execute( void ) { char *text; char line[MAX_CMD_LINE]; int i, quotes; while( cmd_text.cursize ) { // 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" ); Q_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) 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; Q_memcpy( text, text + i, cmd_text.cursize ); } // execute the command line Cmd_ExecuteString( line, src_command ); if( cmd_wait ) { // skip out while text still remains in buffer, // leaving it for next frame cmd_wait = false; break; } } } /* ============================================================================== 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_CMD_LINE]; // 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( host.stuffcmdsrun ) return; host.stuffcmdsrun = true; build[0] = 0; for( i = 0; i < host.argc; i++ ) { if( host.argv[i] && host.argv[i][0] == '+' && ( host.argv[i][1] < '0' || host.argv[i][1] > '9' ) && l + Q_strlen( host.argv[i] ) - 1 <= sizeof( build ) - 1 ) { j = 1; while( host.argv[i][j] ) build[l++] = host.argv[i][j++]; for( i++; i < host.argc; i++ ) { if( !host.argv[i] ) continue; if(( host.argv[i][0] == '+' || host.argv[i][0] == '-' ) && ( host.argv[i][1] < '0' || host.argv[i][1] > '9' )) break; if( l + Q_strlen( host.argv[i] ) + 4 > sizeof( build ) - 1 ) break; build[l++] = ' '; if( Q_strchr( host.argv[i], ' ' )) build[l++] = '\"'; for( j = 0; host.argv[i][j]; j++ ) build[l++] = host.argv[i][j]; if( Q_strchr( host.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 ) { cmd_wait = true; } /* =============== 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++ ) Sys_Print( Cmd_Argv( i )); Sys_Print( "\n" ); } /* =============== Cmd_Alias_f Creates a new command that executes a command string (possibly ; seperated) =============== */ void Cmd_Alias_f( void ) { cmdalias_t *a; char cmd[MAX_CMD_LINE]; int i, c; char *s; if( Cmd_Argc() == 1 ) { Msg( "Current alias commands:\n" ); for( a = cmd_alias; a; a = a->next ) Msg( "^2%s^7 : ^3%s^7\n", a->name, a->value ); return; } s = Cmd_Argv( 1 ); if( Q_strlen( s ) >= MAX_ALIAS_NAME ) { Msg( "Alias name is too long\n" ); return; } // if the alias allready exists, reuse it for( a = cmd_alias; a; a = a->next ) { if( !Q_strcmp( s, a->name )) { Z_Free( a->value ); break; } } if( !a ) { a = Z_Malloc( sizeof( cmdalias_t )); a->next = cmd_alias; cmd_alias = a; } Q_strncpy( a->name, s, sizeof( a->name )); // copy the rest of the command line cmd[0] = 0; // start out with a null string c = Cmd_Argc(); for( i = 2; i < c; i++ ) { Q_strcat( cmd, Cmd_Argv( i )); if( i != c ) Q_strcat( cmd, " " ); } Q_strcat( cmd, "\n" ); a->value = copystring( cmd ); } /* ============================================================================= COMMAND EXECUTION ============================================================================= */ #define CMD_EXTDLL BIT( 0 ) // added by game.dll #define CMD_CLIENTDLL BIT( 1 ) // added by client.dll typedef struct cmd_function_s { struct cmd_function_s *next; char *name; xcommand_t function; char *desc; int flags; } cmd_function_t; static int cmd_argc; static char *cmd_args = NULL; 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_source_t cmd_source; /* ============ Cmd_Argc ============ */ uint Cmd_Argc( void ) { return cmd_argc; } /* ============ Cmd_Argv ============ */ char *Cmd_Argv( int arg ) { if((uint)arg >= cmd_argc ) return ""; return cmd_argv[arg]; } /* ============ Cmd_Args ============ */ char *Cmd_Args( void ) { 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( char *text ) { int i; char cmd_token[MAX_CMD_BUFFER]; // clear the args from the last string for( i = 0; i < cmd_argc; i++ ) Z_Free( cmd_argv[i] ); cmd_argc = 0; // clear previous args cmd_args = NULL; if( !text ) return; while( 1 ) { // skip whitespace up to a /n while( *text && *text <= ' ' && *text != '\n' ) text++; if( *text == '\n' ) { // a newline seperates commands in the buffer text++; break; } if( !*text ) return; if( cmd_argc == 1 ) cmd_args = text; text = COM_ParseFile( text, cmd_token ); if( !text ) return; if( cmd_argc < MAX_CMD_TOKENS ) { cmd_argv[cmd_argc] = copystring( cmd_token ); cmd_argc++; } } } /* ============ Cmd_AddCommand ============ */ void Cmd_AddCommand( const char *cmd_name, xcommand_t function, const char *cmd_desc ) { cmd_function_t *cmd; // fail if the command is a variable name if( Cvar_FindVar( cmd_name )) { MsgDev( D_INFO, "Cmd_AddCommand: %s already defined as a var\n", cmd_name ); return; } // 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 = Z_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 is a variable name if( Cvar_FindVar( cmd_name )) { MsgDev( D_INFO, "Cmd_AddCommand: %s already defined as a var\n", cmd_name ); return; } // 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 = Z_Malloc( sizeof( cmd_function_t )); cmd->name = copystring( cmd_name ); cmd->desc = copystring( "game command" ); cmd->function = function; cmd->flags = CMD_EXTDLL; cmd->next = cmd_functions; cmd_functions = cmd; } /* ============ Cmd_AddClientCommand ============ */ void Cmd_AddClientCommand( const char *cmd_name, xcommand_t function ) { cmd_function_t *cmd; // fail if the command is a variable name if( Cvar_FindVar( cmd_name )) { MsgDev( D_INFO, "Cmd_AddCommand: %s already defined as a var\n", cmd_name ); return; } // 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 = Z_Malloc( sizeof( cmd_function_t )); cmd->name = copystring( cmd_name ); cmd->desc = copystring( "client command" ); cmd->function = function; cmd->flags = CMD_CLIENTDLL; cmd->next = cmd_functions; cmd_functions = cmd; } /* ============ Cmd_RemoveCommand ============ */ void Cmd_RemoveCommand( const char *cmd_name ) { cmd_function_t *cmd, **back; if( !cmd_name || !*cmd_name ) return; back = &cmd_functions; while( 1 ) { cmd = *back; if( !cmd ) return; if( !Q_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 ============ */ qboolean Cmd_Exists( const char *cmd_name ) { cmd_function_t *cmd; for( cmd = cmd_functions; cmd; cmd = cmd->next ) { if( !Q_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( char *text, cmd_source_t src ) { qboolean isServerCommand = false; qboolean isDLLCommand = false; cmd_function_t *cmd; cmdalias_t *a; // set cmd source cmd_source = src; // execute the command line Cmd_TokenizeString( text ); if( !Cmd_Argc()) return; // no tokens // check alias for( a = cmd_alias; a; a = a->next ) { if( !Q_stricmp( cmd_argv[0], a->name )) { Cbuf_InsertText( a->value ); return; } } // check functions for( cmd = cmd_functions; cmd; cmd = cmd->next ) { if( !Q_stricmp( cmd_argv[0], cmd->name ) && cmd->function ) { cmd->function(); return; } } // check cvars if( Cvar_Command( )) return; // forward the command line to the server, so the entity DLL can parse it if( cmd_source == src_command && host.type == HOST_NORMAL ) { if( cls.state >= ca_connected ) { Cmd_ForwardToServer(); return; } } else if( text[0] != '@' && host.type == HOST_NORMAL ) { // commands with leading '@' are hidden system commands MsgDev( D_INFO, "Unknown command \"%s\"\n", text ); } } /* =================== Cmd_ForwardToServer adds the current command line as a clc_stringcmd to the client message. things like godmode, noclip, etc, are commands directed to the server, so when they are typed in at the console, they will need to be forwarded. =================== */ void Cmd_ForwardToServer( void ) { char str[MAX_CMD_BUFFER]; if( cls.demoplayback ) { if( !Q_stricmp( Cmd_Argv( 1 ), "pause" )) cl.refdef.paused ^= 1; return; } if( cls.state != ca_connected && cls.state != ca_active ) { MsgDev( D_INFO, "Can't \"%s\", not connected\n", Cmd_Argv( 0 )); return; // not connected } BF_WriteByte( &cls.netchan.message, clc_stringcmd ); str[0] = 0; if( Q_stricmp( Cmd_Argv( 0 ), "cmd" )) { Q_strcat( str, Cmd_Argv( 0 )); Q_strcat( str, " " ); } if( Cmd_Argc() > 1 ) Q_strcat( str, Cmd_Args( )); else Q_strcat( str, "\n" ); BF_WriteString( &cls.netchan.message, str ); } /* ============ 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 && !Q_stricmpext( match, cmd->name )) continue; Msg( "%10s %s\n", cmd->name, cmd->desc ); 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; } 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 ("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, va( "execute commandline parameters (must be present in %s.rc script)", SI.ModuleName )); Cmd_AddCommand ("cmd", Cmd_ForwardToServer, "send a console commandline to the server" ); Cmd_AddCommand ("alias", Cmd_Alias_f, "create a script function. Without arguments show the list of all alias" ); }