//======================================================================= // 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 \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 }