/* =========================================================================== Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). Doom 3 Source Code 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 3 of the License, or (at your option) any later version. Doom 3 Source Code 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 Doom 3 Source Code. If not, see . In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "../idlib/precompiled.h" #pragma hdrstop /* =============================================================================== idCmdSystemLocal =============================================================================== */ typedef struct commandDef_s { struct commandDef_s * next; char * name; cmdFunction_t function; argCompletion_t argCompletion; int flags; char * description; } commandDef_t; class idCmdSystemLocal : public idCmdSystem { public: virtual void Init( void ); virtual void Shutdown( void ); virtual void AddCommand( const char *cmdName, cmdFunction_t function, int flags, const char *description, argCompletion_t argCompletion = NULL ); virtual void RemoveCommand( const char *cmdName ); virtual void RemoveFlaggedCommands( int flags ); virtual void CommandCompletion( void(*callback)( const char *s ) ); virtual void ArgCompletion( const char *cmdString, void(*callback)( const char *s ) ); virtual void BufferCommandText( cmdExecution_t exec, const char *text ); virtual void ExecuteCommandBuffer( void ); virtual void ArgCompletion_FolderExtension( const idCmdArgs &args, void(*callback)( const char *s ), const char *folder, bool stripFolder, ... ); virtual void ArgCompletion_DeclName( const idCmdArgs &args, void(*callback)( const char *s ), int type ); virtual void BufferCommandArgs( cmdExecution_t exec, const idCmdArgs &args ); virtual void SetupReloadEngine( const idCmdArgs &args ); virtual bool PostReloadEngine( void ); void SetWait( int numFrames ) { wait = numFrames; } commandDef_t * GetCommands( void ) const { return commands; } private: static const int MAX_CMD_BUFFER = 0x10000; commandDef_t * commands; int wait; int textLength; byte textBuf[MAX_CMD_BUFFER]; idStr completionString; idStrList completionParms; // piggybacks on the text buffer, avoids tokenize again and screwing it up idList tokenizedCmds; // a command stored to be executed after a reloadEngine and all associated commands have been processed idCmdArgs postReload; private: void ExecuteTokenizedString( const idCmdArgs &args ); void ExecuteCommandText( const char *text ); void InsertCommandText( const char *text ); void AppendCommandText( const char *text ); static void ListByFlags( const idCmdArgs &args, cmdFlags_t flags ); static void List_f( const idCmdArgs &args ); static void SystemList_f( const idCmdArgs &args ); static void RendererList_f( const idCmdArgs &args ); static void SoundList_f( const idCmdArgs &args ); static void GameList_f( const idCmdArgs &args ); static void ToolList_f( const idCmdArgs &args ); static void Exec_f( const idCmdArgs &args ); static void Vstr_f( const idCmdArgs &args ); static void Echo_f( const idCmdArgs &args ); static void Parse_f( const idCmdArgs &args ); static void Wait_f( const idCmdArgs &args ); static void PrintMemInfo_f( const idCmdArgs &args ); }; idCmdSystemLocal cmdSystemLocal; idCmdSystem * cmdSystem = &cmdSystemLocal; /* ============ idCmdSystemLocal::ListByFlags ============ */ // NOTE: the const wonkyness is required to make msvc happy template<> ID_INLINE int idListSortCompare( const commandDef_t * const *a, const commandDef_t * const *b ) { return idStr::Icmp( (*a)->name, (*b)->name ); } void idCmdSystemLocal::ListByFlags( const idCmdArgs &args, cmdFlags_t flags ) { int i; idStr match; const commandDef_t *cmd; idList cmdList; if ( args.Argc() > 1 ) { match = args.Args( 1, -1 ); match.Replace( " ", "" ); } else { match = ""; } for ( cmd = cmdSystemLocal.GetCommands(); cmd; cmd = cmd->next ) { if ( !( cmd->flags & flags ) ) { continue; } if ( match.Length() && idStr( cmd->name ).Filter( match, false ) == 0 ) { continue; } cmdList.Append( cmd ); } cmdList.Sort(); for ( i = 0; i < cmdList.Num(); i++ ) { cmd = cmdList[i]; common->Printf( " %-21s %s\n", cmd->name, cmd->description ); } common->Printf( "%i commands\n", cmdList.Num() ); } /* ============ idCmdSystemLocal::List_f ============ */ void idCmdSystemLocal::List_f( const idCmdArgs &args ) { idCmdSystemLocal::ListByFlags( args, CMD_FL_ALL ); } /* ============ idCmdSystemLocal::SystemList_f ============ */ void idCmdSystemLocal::SystemList_f( const idCmdArgs &args ) { idCmdSystemLocal::ListByFlags( args, CMD_FL_SYSTEM ); } /* ============ idCmdSystemLocal::RendererList_f ============ */ void idCmdSystemLocal::RendererList_f( const idCmdArgs &args ) { idCmdSystemLocal::ListByFlags( args, CMD_FL_RENDERER ); } /* ============ idCmdSystemLocal::SoundList_f ============ */ void idCmdSystemLocal::SoundList_f( const idCmdArgs &args ) { idCmdSystemLocal::ListByFlags( args, CMD_FL_SOUND ); } /* ============ idCmdSystemLocal::GameList_f ============ */ void idCmdSystemLocal::GameList_f( const idCmdArgs &args ) { idCmdSystemLocal::ListByFlags( args, CMD_FL_GAME ); } /* ============ idCmdSystemLocal::ToolList_f ============ */ void idCmdSystemLocal::ToolList_f( const idCmdArgs &args ) { idCmdSystemLocal::ListByFlags( args, CMD_FL_TOOL ); } /* =============== idCmdSystemLocal::Exec_f =============== */ void idCmdSystemLocal::Exec_f( const idCmdArgs &args ) { char * f; int len; idStr filename; if ( args.Argc () != 2 ) { common->Printf( "exec : execute a script file\n" ); return; } filename = args.Argv(1); filename.DefaultFileExtension( ".cfg" ); len = fileSystem->ReadFile( filename, reinterpret_cast(&f), NULL ); if ( !f ) { common->Printf( "couldn't exec %s\n", args.Argv(1) ); return; } common->Printf( "execing %s\n", args.Argv(1) ); cmdSystemLocal.BufferCommandText( CMD_EXEC_INSERT, f ); fileSystem->FreeFile( f ); } /* =============== idCmdSystemLocal::Vstr_f Inserts the current value of a cvar as command text =============== */ void idCmdSystemLocal::Vstr_f( const idCmdArgs &args ) { const char *v; if ( args.Argc () != 2 ) { common->Printf( "vstr : execute a variable command\n" ); return; } v = cvarSystem->GetCVarString( args.Argv( 1 ) ); cmdSystemLocal.BufferCommandText( CMD_EXEC_APPEND, va( "%s\n", v ) ); } /* =============== idCmdSystemLocal::Echo_f Just prints the rest of the line to the console =============== */ void idCmdSystemLocal::Echo_f( const idCmdArgs &args ) { int i; for ( i = 1; i < args.Argc(); i++ ) { common->Printf( "%s ", args.Argv( i ) ); } common->Printf( "\n" ); } /* ============ idCmdSystemLocal::Wait_f Causes execution of the remainder of the command buffer to be delayed until next frame. ============ */ void idCmdSystemLocal::Wait_f( const idCmdArgs &args ) { if ( args.Argc() == 2 ) { cmdSystemLocal.SetWait( atoi( args.Argv( 1 ) ) ); } else { cmdSystemLocal.SetWait( 1 ); } } /* ============ idCmdSystemLocal::Parse_f This just prints out how the rest of the line was parsed, as a debugging tool. ============ */ void idCmdSystemLocal::Parse_f( const idCmdArgs &args ) { int i; for ( i = 0; i < args.Argc(); i++ ) { common->Printf( "%i: %s\n", i, args.Argv(i) ); } } /* ============ idCmdSystemLocal::Init ============ */ void idCmdSystemLocal::Init( void ) { AddCommand( "listCmds", List_f, CMD_FL_SYSTEM, "lists commands" ); AddCommand( "listSystemCmds", SystemList_f, CMD_FL_SYSTEM, "lists system commands" ); AddCommand( "listRendererCmds", RendererList_f, CMD_FL_SYSTEM, "lists renderer commands" ); AddCommand( "listSoundCmds", SoundList_f, CMD_FL_SYSTEM, "lists sound commands" ); AddCommand( "listGameCmds", GameList_f, CMD_FL_SYSTEM, "lists game commands" ); AddCommand( "listToolCmds", ToolList_f, CMD_FL_SYSTEM, "lists tool commands" ); AddCommand( "exec", Exec_f, CMD_FL_SYSTEM, "executes a config file", ArgCompletion_ConfigName ); AddCommand( "vstr", Vstr_f, CMD_FL_SYSTEM, "inserts the current value of a cvar as command text" ); AddCommand( "echo", Echo_f, CMD_FL_SYSTEM, "prints text" ); AddCommand( "parse", Parse_f, CMD_FL_SYSTEM, "prints tokenized string" ); AddCommand( "wait", Wait_f, CMD_FL_SYSTEM, "delays remaining buffered commands one or more frames" ); completionString = "*"; textLength = 0; } /* ============ idCmdSystemLocal::Shutdown ============ */ void idCmdSystemLocal::Shutdown( void ) { commandDef_t *cmd; for ( cmd = commands; cmd; cmd = commands ) { commands = commands->next; Mem_Free( cmd->name ); Mem_Free( cmd->description ); delete cmd; } completionString.Clear(); completionParms.Clear(); tokenizedCmds.Clear(); postReload.Clear(); } /* ============ idCmdSystemLocal::AddCommand ============ */ void idCmdSystemLocal::AddCommand( const char *cmdName, cmdFunction_t function, int flags, const char *description, argCompletion_t argCompletion ) { commandDef_t *cmd; // fail if the command already exists for ( cmd = commands; cmd; cmd = cmd->next ) { if ( idStr::Cmp( cmdName, cmd->name ) == 0 ) { if ( function != cmd->function ) { common->Printf( "idCmdSystemLocal::AddCommand: %s already defined\n", cmdName ); } return; } } cmd = new commandDef_t; cmd->name = Mem_CopyString( cmdName ); cmd->function = function; cmd->argCompletion = argCompletion; cmd->flags = flags; cmd->description = Mem_CopyString( description ); cmd->next = commands; commands = cmd; } /* ============ idCmdSystemLocal::RemoveCommand ============ */ void idCmdSystemLocal::RemoveCommand( const char *cmdName ) { commandDef_t *cmd, **last; for ( last = &commands, cmd = *last; cmd; cmd = *last ) { if ( idStr::Cmp( cmdName, cmd->name ) == 0 ) { *last = cmd->next; Mem_Free( cmd->name ); Mem_Free( cmd->description ); delete cmd; return; } last = &cmd->next; } } /* ============ idCmdSystemLocal::RemoveFlaggedCommands ============ */ void idCmdSystemLocal::RemoveFlaggedCommands( int flags ) { commandDef_t *cmd, **last; for ( last = &commands, cmd = *last; cmd; cmd = *last ) { if ( cmd->flags & flags ) { *last = cmd->next; Mem_Free( cmd->name ); Mem_Free( cmd->description ); delete cmd; continue; } last = &cmd->next; } } /* ============ idCmdSystemLocal::CommandCompletion ============ */ void idCmdSystemLocal::CommandCompletion( void(*callback)( const char *s ) ) { commandDef_t *cmd; for ( cmd = commands; cmd; cmd = cmd->next ) { callback( cmd->name ); } } /* ============ idCmdSystemLocal::ArgCompletion ============ */ void idCmdSystemLocal::ArgCompletion( const char *cmdString, void(*callback)( const char *s ) ) { commandDef_t *cmd; idCmdArgs args; args.TokenizeString( cmdString, false ); for ( cmd = commands; cmd; cmd = cmd->next ) { if ( !cmd->argCompletion ) { continue; } if ( idStr::Icmp( args.Argv( 0 ), cmd->name ) == 0 ) { cmd->argCompletion( args, callback ); break; } } } /* ============ idCmdSystemLocal::ExecuteTokenizedString ============ */ void idCmdSystemLocal::ExecuteTokenizedString( const idCmdArgs &args ) { commandDef_t *cmd, **prev; // execute the command line if ( !args.Argc() ) { return; // no tokens } // check registered command functions for ( prev = &commands; *prev; prev = &cmd->next ) { cmd = *prev; if ( idStr::Icmp( args.Argv( 0 ), cmd->name ) == 0 ) { // 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 = commands; commands = cmd; if ( ( cmd->flags & (CMD_FL_CHEAT|CMD_FL_TOOL) ) && session && session->IsMultiplayer() && !cvarSystem->GetCVarBool( "net_allowCheats" ) ) { common->Printf( "Command '%s' not valid in multiplayer mode.\n", cmd->name ); return; } // perform the action if ( !cmd->function ) { break; } else { cmd->function( args ); } return; } } // check cvars if ( cvarSystem->Command( args ) ) { return; } common->Printf( "Unknown command '%s'\n", args.Argv( 0 ) ); } /* ============ idCmdSystemLocal::ExecuteCommandText Tokenizes, then executes. ============ */ void idCmdSystemLocal::ExecuteCommandText( const char *text ) { ExecuteTokenizedString( idCmdArgs( text, false ) ); } /* ============ idCmdSystemLocal::InsertCommandText Adds command text immediately after the current command Adds a \n to the text ============ */ void idCmdSystemLocal::InsertCommandText( const char *text ) { int len; int i; len = strlen( text ) + 1; if ( len + textLength > (int)sizeof( textBuf ) ) { common->Printf( "idCmdSystemLocal::InsertText: buffer overflow\n" ); return; } // move the existing command text for ( i = textLength - 1; i >= 0; i-- ) { textBuf[ i + len ] = textBuf[ i ]; } // copy the new text in memcpy( textBuf, text, len - 1 ); // add a \n textBuf[ len - 1 ] = '\n'; textLength += len; } /* ============ idCmdSystemLocal::AppendCommandText Adds command text at the end of the buffer, does NOT add a final \n ============ */ void idCmdSystemLocal::AppendCommandText( const char *text ) { int l; l = strlen( text ); if ( textLength + l >= (int)sizeof( textBuf ) ) { common->Printf( "idCmdSystemLocal::AppendText: buffer overflow\n" ); return; } memcpy( textBuf + textLength, text, l ); textLength += l; } /* ============ idCmdSystemLocal::BufferCommandText ============ */ void idCmdSystemLocal::BufferCommandText( cmdExecution_t exec, const char *text ) { switch( exec ) { case CMD_EXEC_NOW: { ExecuteCommandText( text ); break; } case CMD_EXEC_INSERT: { InsertCommandText( text ); break; } case CMD_EXEC_APPEND: { AppendCommandText( text ); break; } default: { common->FatalError( "idCmdSystemLocal::BufferCommandText: bad exec type" ); } } } /* ============ idCmdSystemLocal::BufferCommandArgs ============ */ void idCmdSystemLocal::BufferCommandArgs( cmdExecution_t exec, const idCmdArgs &args ) { switch ( exec ) { case CMD_EXEC_NOW: { ExecuteTokenizedString( args ); break; } case CMD_EXEC_APPEND: { AppendCommandText( "_execTokenized\n" ); tokenizedCmds.Append( args ); break; } default: { common->FatalError( "idCmdSystemLocal::BufferCommandArgs: bad exec type" ); } } } /* ============ idCmdSystemLocal::ExecuteCommandBuffer ============ */ void idCmdSystemLocal::ExecuteCommandBuffer( void ) { int i; char * text; int quotes; idCmdArgs args; while( textLength ) { if ( wait ) { // skip out while text still remains in buffer, leaving it for next frame wait--; break; } // find a \n or ; line break text = (char *)textBuf; quotes = 0; for ( i = 0; i < textLength; 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; } } text[i] = 0; if ( !idStr::Cmp( text, "_execTokenized" ) ) { args = tokenizedCmds[ 0 ]; tokenizedCmds.RemoveIndex( 0 ); } else { args.TokenizeString( text, false ); } // 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 == textLength ) { textLength = 0; } else { i++; textLength -= i; memmove( text, text+i, textLength ); } // execute the command line that we have already tokenized ExecuteTokenizedString( args ); } } /* ============ idCmdSystemLocal::ArgCompletion_FolderExtension ============ */ void idCmdSystemLocal::ArgCompletion_FolderExtension( const idCmdArgs &args, void(*callback)( const char *s ), const char *folder, bool stripFolder, ... ) { int i; idStr string; const char *extension; va_list argPtr; string = args.Argv( 0 ); string += " "; string += args.Argv( 1 ); if ( string.Icmp( completionString ) != 0 ) { idStr parm, path; idFileList *names; completionString = string; completionParms.Clear(); parm = args.Argv( 1 ); parm.ExtractFilePath( path ); if ( stripFolder || path.Length() == 0 ) { path = folder + path; } path.StripTrailing( '/' ); // list folders names = fileSystem->ListFiles( path, "/", true, true ); for ( i = 0; i < names->GetNumFiles(); i++ ) { idStr name = names->GetFile( i ); if ( stripFolder ) { name.Strip( folder ); } else { name.Strip( "/" ); } name = args.Argv( 0 ) + ( " " + name ) + "/"; completionParms.Append( name ); } fileSystem->FreeFileList( names ); // list files va_start( argPtr, stripFolder ); for ( extension = va_arg( argPtr, const char * ); extension; extension = va_arg( argPtr, const char * ) ) { names = fileSystem->ListFiles( path, extension, true, true ); for ( i = 0; i < names->GetNumFiles(); i++ ) { idStr name = names->GetFile( i ); if ( stripFolder ) { name.Strip( folder ); } else { name.Strip( "/" ); } name = args.Argv( 0 ) + ( " " + name ); completionParms.Append( name ); } fileSystem->FreeFileList( names ); } va_end( argPtr ); } for ( i = 0; i < completionParms.Num(); i++ ) { callback( completionParms[i] ); } } /* ============ idCmdSystemLocal::ArgCompletion_DeclName ============ */ void idCmdSystemLocal::ArgCompletion_DeclName( const idCmdArgs &args, void(*callback)( const char *s ), int type ) { int i, num; if ( declManager == NULL ) { return; } num = declManager->GetNumDecls( (declType_t)type ); for ( i = 0; i < num; i++ ) { callback( idStr( args.Argv( 0 ) ) + " " + declManager->DeclByIndex( (declType_t)type, i , false )->GetName() ); } } /* ============ idCmdSystemLocal::SetupReloadEngine ============ */ void idCmdSystemLocal::SetupReloadEngine( const idCmdArgs &args ) { BufferCommandText( CMD_EXEC_APPEND, "reloadEngine\n" ); postReload = args; } /* ============ idCmdSystemLocal::PostReloadEngine ============ */ bool idCmdSystemLocal::PostReloadEngine( void ) { if ( !postReload.Argc() ) { return false; } BufferCommandArgs( CMD_EXEC_APPEND, postReload ); postReload.Clear(); return true; }