1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2005 - 2015, ioquake3 contributors
7 Copyright (C) 2013 - 2015, OpenJK contributors
8 
9 This file is part of the OpenJK source code.
10 
11 OpenJK is free software; you can redistribute it and/or modify it
12 under the terms of the GNU General Public License version 2 as
13 published by the Free Software Foundation.
14 
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, see <http://www.gnu.org/licenses/>.
22 ===========================================================================
23 */
24 
25 // cmd.c -- Quake script command processing module
26 
27 #include "qcommon/qcommon.h"
28 
29 #include <vector>
30 #include <algorithm>
31 
32 #define	MAX_CMD_BUFFER	128*1024
33 #define	MAX_CMD_LINE	1024
34 
35 typedef struct cmd_s {
36 	byte	*data;
37 	int		maxsize;
38 	int		cursize;
39 } cmd_t;
40 
41 int			cmd_wait;
42 cmd_t		cmd_text;
43 byte		cmd_text_buf[MAX_CMD_BUFFER];
44 
45 //=============================================================================
46 
47 /*
48 ============
49 Cmd_Wait_f
50 
51 Causes execution of the remainder of the command buffer to be delayed until
52 next frame.  This allows commands like:
53 bind g "cmd use rocket ; +attack ; wait ; -attack ; cmd use blaster"
54 ============
55 */
Cmd_Wait_f(void)56 static void Cmd_Wait_f( void ) {
57 	if ( Cmd_Argc() == 2 ) {
58 		cmd_wait = atoi( Cmd_Argv( 1 ) );
59 		if ( cmd_wait < 0 )
60 			cmd_wait = 1; // ignore the argument
61 	} else {
62 		cmd_wait = 1;
63 	}
64 }
65 
66 
67 /*
68 =============================================================================
69 
70 						COMMAND BUFFER
71 
72 =============================================================================
73 */
74 
75 /*
76 ============
77 Cbuf_Init
78 ============
79 */
Cbuf_Init(void)80 void Cbuf_Init (void)
81 {
82 	cmd_text.data = cmd_text_buf;
83 	cmd_text.maxsize = MAX_CMD_BUFFER;
84 	cmd_text.cursize = 0;
85 }
86 
87 /*
88 ============
89 Cbuf_AddText
90 
91 Adds command text at the end of the buffer, does NOT add a final \n
92 ============
93 */
Cbuf_AddText(const char * text)94 void Cbuf_AddText( const char *text ) {
95 	int		l;
96 
97 	l = strlen (text);
98 
99 	if (cmd_text.cursize + l >= cmd_text.maxsize)
100 	{
101 		Com_Printf ("Cbuf_AddText: overflow\n");
102 		return;
103 	}
104 	Com_Memcpy(&cmd_text.data[cmd_text.cursize], text, l);
105 	cmd_text.cursize += l;
106 }
107 
108 
109 /*
110 ============
111 Cbuf_InsertText
112 
113 Adds command text immediately after the current command
114 Adds a \n to the text
115 ============
116 */
Cbuf_InsertText(const char * text)117 void Cbuf_InsertText( const char *text ) {
118 	int		len;
119 	int		i;
120 
121 	len = strlen( text ) + 1;
122 	if ( len + cmd_text.cursize > cmd_text.maxsize ) {
123 		Com_Printf( "Cbuf_InsertText overflowed\n" );
124 		return;
125 	}
126 
127 	// move the existing command text
128 	for ( i = cmd_text.cursize - 1 ; i >= 0 ; i-- ) {
129 		cmd_text.data[ i + len ] = cmd_text.data[ i ];
130 	}
131 
132 	// copy the new text in
133 	Com_Memcpy( cmd_text.data, text, len - 1 );
134 
135 	// add a \n
136 	cmd_text.data[ len - 1 ] = '\n';
137 
138 	cmd_text.cursize += len;
139 }
140 
141 
142 /*
143 ============
144 Cbuf_ExecuteText
145 ============
146 */
Cbuf_ExecuteText(int exec_when,const char * text)147 void Cbuf_ExecuteText (int exec_when, const char *text)
148 {
149 	switch (exec_when)
150 	{
151 	case EXEC_NOW:
152 		if (text && strlen(text) > 0) {
153 			Com_DPrintf(S_COLOR_YELLOW "EXEC_NOW %s\n", text);
154 			Cmd_ExecuteString (text);
155 		} else {
156 			Cbuf_Execute();
157 			Com_DPrintf(S_COLOR_YELLOW "EXEC_NOW %s\n", cmd_text.data);
158 		}
159 		break;
160 	case EXEC_INSERT:
161 		Cbuf_InsertText (text);
162 		break;
163 	case EXEC_APPEND:
164 		Cbuf_AddText (text);
165 		break;
166 	default:
167 		Com_Error (ERR_FATAL, "Cbuf_ExecuteText: bad exec_when");
168 	}
169 }
170 
171 /*
172 ============
173 Cbuf_Execute
174 ============
175 */
Cbuf_Execute(void)176 void Cbuf_Execute (void)
177 {
178 	int		i;
179 	char	*text;
180 	char	line[MAX_CMD_LINE];
181 	int		quotes;
182 
183 	// This will keep // style comments all on one line by not breaking on
184 	// a semicolon.  It will keep /* ... */ style comments all on one line by not
185 	// breaking it for semicolon or newline.
186 	qboolean in_star_comment = qfalse;
187 	qboolean in_slash_comment = qfalse;
188 
189 	while (cmd_text.cursize)
190 	{
191 		if ( cmd_wait > 0 ) {
192 			// skip out while text still remains in buffer, leaving it
193 			// for next frame
194 			cmd_wait--;
195 			break;
196 		}
197 
198 		// find a \n or ; line break or comment: // or /* */
199 		text = (char *)cmd_text.data;
200 
201 		quotes = 0;
202 		for (i=0 ; i< cmd_text.cursize ; i++)
203 		{
204 			if (text[i] == '"')
205 				quotes++;
206 
207 			if ( !(quotes&1)) {
208 				if (i < cmd_text.cursize - 1) {
209 					if (! in_star_comment && text[i] == '/' && text[i+1] == '/')
210 						in_slash_comment = qtrue;
211 					else if (! in_slash_comment && text[i] == '/' && text[i+1] == '*')
212 						in_star_comment = qtrue;
213 					else if (in_star_comment && text[i] == '*' && text[i+1] == '/') {
214 						in_star_comment = qfalse;
215 						// If we are in a star comment, then the part after it is valid
216 						// Note: This will cause it to NUL out the terminating '/'
217 						// but ExecuteString doesn't require it anyway.
218 						i++;
219 						break;
220 					}
221 				}
222 				if (! in_slash_comment && ! in_star_comment && text[i] == ';')
223 					break;
224 			}
225 			if (! in_star_comment && (text[i] == '\n' || text[i] == '\r')) {
226 				in_slash_comment = qfalse;
227 				break;
228 			}
229 		}
230 
231 		if( i >= (MAX_CMD_LINE - 1)) {
232 			i = MAX_CMD_LINE - 1;
233 		}
234 
235 		Com_Memcpy (line, text, i);
236 		line[i] = 0;
237 
238 // delete the text from the command buffer and move remaining commands down
239 // this is necessary because commands (exec) can insert data at the
240 // beginning of the text buffer
241 
242 		if (i == cmd_text.cursize)
243 			cmd_text.cursize = 0;
244 		else
245 		{
246 			i++;
247 			cmd_text.cursize -= i;
248 			memmove (text, text+i, cmd_text.cursize);
249 		}
250 
251 // execute the command line
252 
253 		Cmd_ExecuteString (line);
254 	}
255 }
256 
257 
258 /*
259 ==============================================================================
260 
261 						SCRIPT COMMANDS
262 
263 ==============================================================================
264 */
265 
266 
267 /*
268 ===============
269 Cmd_Exec_f
270 ===============
271 */
Cmd_Exec_f(void)272 static void Cmd_Exec_f( void ) {
273 	bool quiet;
274 	fileBuffer_t f;
275 	char	filename[MAX_QPATH];
276 
277 	quiet = !Q_stricmp(Cmd_Argv(0), "execq");
278 
279 	if (Cmd_Argc () != 2) {
280 		Com_Printf ("exec%s <filename> : execute a script file%s\n",
281 		            quiet ? "q" : "", quiet ? " without notification" : "");
282 		return;
283 	}
284 
285 	Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) );
286 	COM_DefaultExtension( filename, sizeof( filename ), ".cfg" );
287 	FS_ReadFile( filename, &f.v);
288 	if (!f.c) {
289 		Com_Printf ("couldn't exec %s\n", filename);
290 		return;
291 	}
292 	if (!quiet)
293 		Com_Printf ("execing %s\n", filename);
294 
295 	Cbuf_InsertText (f.c);
296 
297 	FS_FreeFile (f.v);
298 }
299 
300 
301 /*
302 ===============
303 Cmd_Vstr_f
304 
305 Inserts the current value of a variable as command text
306 ===============
307 */
Cmd_Vstr_f(void)308 static void Cmd_Vstr_f( void ) {
309 	char	*v;
310 
311 	if (Cmd_Argc () != 2) {
312 		Com_Printf ("vstr <variablename> : execute a variable command\n");
313 		return;
314 	}
315 
316 	v = Cvar_VariableString( Cmd_Argv( 1 ) );
317 	Cbuf_InsertText( va("%s\n", v ) );
318 }
319 
320 
321 /*
322 ===============
323 Cmd_Echo_f
324 
325 Just prints the rest of the line to the console
326 ===============
327 */
Cmd_Echo_f(void)328 static void Cmd_Echo_f (void)
329 {
330 	Com_Printf ("%s\n", Cmd_Args());
331 }
332 
333 
334 /*
335 =============================================================================
336 
337 					COMMAND EXECUTION
338 
339 =============================================================================
340 */
341 
342 typedef struct cmd_function_s
343 {
344 	struct cmd_function_s	*next;
345 	char					*name;
346 	char					*description;
347 	xcommand_t				function;
348 	completionFunc_t		complete;
349 } cmd_function_t;
350 
351 
352 
353 static	int			cmd_argc;
354 static	char		*cmd_argv[MAX_STRING_TOKENS];		// points into cmd_tokenized
355 static	char		cmd_tokenized[BIG_INFO_STRING+MAX_STRING_TOKENS];	// will have 0 bytes inserted
356 static	char		cmd_cmd[BIG_INFO_STRING]; // the original command we received (no token processing)
357 
358 static	cmd_function_t	*cmd_functions;		// possible commands to execute
359 
360 
361 /*
362 ============
363 Cmd_Argc
364 ============
365 */
Cmd_Argc(void)366 int		Cmd_Argc( void ) {
367 	return cmd_argc;
368 }
369 
370 /*
371 ============
372 Cmd_Argv
373 ============
374 */
Cmd_Argv(int arg)375 char	*Cmd_Argv( int arg ) {
376 	if ( (unsigned)arg >= (unsigned)cmd_argc )
377 		return "";
378 
379 	return cmd_argv[arg];
380 }
381 
382 /*
383 ============
384 Cmd_ArgvBuffer
385 
386 The interpreted versions use this because
387 they can't have pointers returned to them
388 ============
389 */
Cmd_ArgvBuffer(int arg,char * buffer,int bufferLength)390 void	Cmd_ArgvBuffer( int arg, char *buffer, int bufferLength ) {
391 	Q_strncpyz( buffer, Cmd_Argv( arg ), bufferLength );
392 }
393 
394 /*
395 ============
396 Cmd_ArgsFrom
397 
398 Returns a single string containing argv(arg) to argv(argc()-1)
399 ============
400 */
Cmd_ArgsFrom(int arg)401 char *Cmd_ArgsFrom( int arg ) {
402 	static	char	cmd_args[BIG_INFO_STRING];
403 	int		i;
404 
405 	cmd_args[0] = '\0';
406 	if (arg < 0)
407 		arg = 0;
408 	for ( i = arg ; i < cmd_argc ; i++ ) {
409 		Q_strcat( cmd_args, sizeof( cmd_args ), cmd_argv[i] );
410 		if ( i != cmd_argc-1 ) {
411 			Q_strcat( cmd_args, sizeof( cmd_args ), " " );
412 		}
413 	}
414 
415 	return cmd_args;
416 }
417 
418 /*
419 ============
420 Cmd_Args
421 
422 Returns a single string containing argv(1) to argv(argc()-1)
423 ============
424 */
Cmd_Args(void)425 char	*Cmd_Args( void ) {
426 	return Cmd_ArgsFrom( 1 );
427 }
428 
429 /*
430 ============
431 Cmd_ArgsBuffer
432 
433 The interpreted versions use this because
434 they can't have pointers returned to them
435 ============
436 */
Cmd_ArgsBuffer(char * buffer,int bufferLength)437 void	Cmd_ArgsBuffer( char *buffer, int bufferLength ) {
438 	Q_strncpyz( buffer, Cmd_ArgsFrom( 1 ), bufferLength );
439 }
440 
441 /*
442 ============
443 Cmd_ArgsFromBuffer
444 
445 The interpreted versions use this because
446 they can't have pointers returned to them
447 ============
448 */
Cmd_ArgsFromBuffer(int arg,char * buffer,int bufferLength)449 void	Cmd_ArgsFromBuffer( int arg, char *buffer, int bufferLength ) {
450 	Q_strncpyz( buffer, Cmd_ArgsFrom( arg ), bufferLength );
451 }
452 
453 /*
454 ============
455 Cmd_Cmd
456 
457 Retrieve the unmodified command string
458 For rcon use when you want to transmit without altering quoting
459 https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543
460 ============
461 */
Cmd_Cmd(void)462 char *Cmd_Cmd(void)
463 {
464 	return cmd_cmd;
465 }
466 
467 /*
468    Replace command separators with space to prevent interpretation
469    This is a hack to protect buggy qvms
470    https://bugzilla.icculus.org/show_bug.cgi?id=3593
471    https://bugzilla.icculus.org/show_bug.cgi?id=4769
472 */
473 
474 /*void Cmd_Args_Sanitize( void ) {
475 	for ( int i=1; i<cmd_argc; i++ )
476 	{
477 		char *c = cmd_argv[i];
478 
479 		if ( strlen( c ) >= MAX_CVAR_VALUE_STRING )
480 			c[MAX_CVAR_VALUE_STRING-1] = '\0';
481 
482 		while ( (c=strpbrk( c, "\n\r;" )) ) {
483 			*c = ' ';
484 			++c;
485 		}
486 	}
487 }*/
488 
Cmd_Args_Sanitize(size_t length,const char * strip,const char * repl)489 void Cmd_Args_Sanitize( size_t length, const char *strip, const char *repl )
490 {
491 	for ( int i = 1; i < cmd_argc; i++ )
492 	{
493 		char *c = cmd_argv[i];
494 
495 		if ( length > 0 && strlen( c ) >= length )
496 			c[length - 1] = '\0';
497 
498 		if ( VALIDSTRING( strip ) && VALIDSTRING( repl ) )
499 			Q_strstrip( c, strip, repl );
500 	}
501 }
502 
503 /*
504 ============
505 Cmd_TokenizeString
506 
507 Parses the given string into command line tokens.
508 The text is copied to a seperate buffer and 0 characters
509 are inserted in the appropriate place, The argv array
510 will point into this temporary buffer.
511 ============
512 */
513 // NOTE TTimo define that to track tokenization issues
514 //#define TKN_DBG
Cmd_TokenizeString2(const char * text_in,qboolean ignoreQuotes)515 static void Cmd_TokenizeString2( const char *text_in, qboolean ignoreQuotes ) {
516 	const char	*text;
517 	char	*textOut;
518 
519 #ifdef TKN_DBG
520   // FIXME TTimo blunt hook to try to find the tokenization of userinfo
521   Com_DPrintf("Cmd_TokenizeString: %s\n", text_in);
522 #endif
523 
524 	// clear previous args
525 	cmd_argc = 0;
526 
527 	if ( !text_in ) {
528 		return;
529 	}
530 
531 	Q_strncpyz( cmd_cmd, text_in, sizeof(cmd_cmd) );
532 
533 	text = text_in;
534 	textOut = cmd_tokenized;
535 
536 	while ( 1 ) {
537 		if ( cmd_argc == MAX_STRING_TOKENS ) {
538 			return;			// this is usually something malicious
539 		}
540 
541 		while ( 1 ) {
542 			// skip whitespace
543 			while ( *text && *(const unsigned char* /*eurofix*/)text <= ' ' ) {
544 				text++;
545 			}
546 			if ( !*text ) {
547 				return;			// all tokens parsed
548 			}
549 
550 			// skip // comments
551 			if ( text[0] == '/' && text[1] == '/' ) {
552 				return;			// all tokens parsed
553 			}
554 
555 			// skip /* */ comments
556 			if ( text[0] == '/' && text[1] =='*' ) {
557 				while ( *text && ( text[0] != '*' || text[1] != '/' ) ) {
558 					text++;
559 				}
560 				if ( !*text ) {
561 					return;		// all tokens parsed
562 				}
563 				text += 2;
564 			} else {
565 				break;			// we are ready to parse a token
566 			}
567 		}
568 
569 		// handle quoted strings
570     // NOTE TTimo this doesn't handle \" escaping
571 		if ( !ignoreQuotes && *text == '"' ) {
572 			cmd_argv[cmd_argc] = textOut;
573 			cmd_argc++;
574 			text++;
575 			while ( *text && *text != '"' ) {
576 				*textOut++ = *text++;
577 			}
578 			*textOut++ = 0;
579 			if ( !*text ) {
580 				return;		// all tokens parsed
581 			}
582 			text++;
583 			continue;
584 		}
585 
586 		// regular token
587 		cmd_argv[cmd_argc] = textOut;
588 		cmd_argc++;
589 
590 		// skip until whitespace, quote, or command
591 		while ( *(const unsigned char* /*eurofix*/)text > ' ' ) {
592 			if ( !ignoreQuotes && text[0] == '"' ) {
593 				break;
594 			}
595 
596 			if ( text[0] == '/' && text[1] == '/' ) {
597 				break;
598 			}
599 
600 			// skip /* */ comments
601 			if ( text[0] == '/' && text[1] =='*' ) {
602 				break;
603 			}
604 
605 			*textOut++ = *text++;
606 		}
607 
608 		*textOut++ = 0;
609 
610 		if ( !*text ) {
611 			return;		// all tokens parsed
612 		}
613 	}
614 
615 }
616 
617 /*
618 ============
619 Cmd_TokenizeString
620 ============
621 */
Cmd_TokenizeString(const char * text_in)622 void Cmd_TokenizeString( const char *text_in ) {
623 	Cmd_TokenizeString2( text_in, qfalse );
624 }
625 
626 /*
627 ============
628 Cmd_TokenizeStringIgnoreQuotes
629 ============
630 */
Cmd_TokenizeStringIgnoreQuotes(const char * text_in)631 void Cmd_TokenizeStringIgnoreQuotes( const char *text_in ) {
632 	Cmd_TokenizeString2( text_in, qtrue );
633 }
634 
635 /*
636 ============
637 Cmd_FindCommand
638 ============
639 */
Cmd_FindCommand(const char * cmd_name)640 cmd_function_t *Cmd_FindCommand( const char *cmd_name )
641 {
642 	cmd_function_t *cmd;
643 	for( cmd = cmd_functions; cmd; cmd = cmd->next )
644 		if( !Q_stricmp( cmd_name, cmd->name ) )
645 			return cmd;
646 	return NULL;
647 }
648 
649 /*
650 ============
651 Cmd_AddCommand
652 ============
653 */
Cmd_AddCommand(const char * cmd_name,xcommand_t function,const char * cmd_desc)654 void	Cmd_AddCommand( const char *cmd_name, xcommand_t function, const char *cmd_desc ) {
655 	cmd_function_t	*cmd;
656 
657 	// fail if the command already exists
658 	if( Cmd_FindCommand( cmd_name ) )
659 	{
660 		// allow completion-only commands to be silently doubled
661 		if ( function != NULL ) {
662 			Com_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name);
663 		}
664 		return;
665 	}
666 
667 	// use a small malloc to avoid zone fragmentation
668 	cmd = (struct cmd_function_s *)S_Malloc (sizeof(cmd_function_t));
669 	cmd->name = CopyString( cmd_name );
670 	if ( VALIDSTRING( cmd_desc ) )
671 		cmd->description = CopyString( cmd_desc );
672 	else
673 		cmd->description = NULL;
674 	cmd->function = function;
675 	cmd->complete = NULL;
676 	cmd->next = cmd_functions;
677 	cmd_functions = cmd;
678 }
679 
Cmd_AddCommandList(const cmdList_t * cmdList)680 void Cmd_AddCommandList( const cmdList_t *cmdList )
681 {
682 	for ( const cmdList_t *cmd = cmdList; cmd && cmd->name; cmd++ )
683 	{
684 		Cmd_AddCommand( cmd->name, cmd->func, cmd->description );
685 		if ( cmd->complete )
686 			Cmd_SetCommandCompletionFunc( cmd->name, cmd->complete );
687 	}
688 }
689 
Cmd_RemoveCommandList(const cmdList_t * cmdList)690 void Cmd_RemoveCommandList( const cmdList_t *cmdList )
691 {
692 	for ( const cmdList_t *cmd = cmdList; cmd && cmd->name; cmd++ )
693 	{
694 		Cmd_RemoveCommand( cmd->name );
695 	}
696 }
697 
698 /*
699 ============
700 Cmd_SetCommandCompletionFunc
701 ============
702 */
Cmd_SetCommandCompletionFunc(const char * command,completionFunc_t complete)703 void Cmd_SetCommandCompletionFunc( const char *command, completionFunc_t complete ) {
704 	for ( cmd_function_t *cmd=cmd_functions; cmd; cmd=cmd->next ) {
705 		if ( !Q_stricmp( command, cmd->name ) )
706 			cmd->complete = complete;
707 	}
708 }
709 
710 /*
711 ============
712 Cmd_RemoveCommand
713 ============
714 */
Cmd_RemoveCommand(const char * cmd_name)715 void	Cmd_RemoveCommand( const char *cmd_name ) {
716 	cmd_function_t	*cmd, **back;
717 
718 	back = &cmd_functions;
719 	while( 1 ) {
720 		cmd = *back;
721 		if ( !cmd ) {
722 			// command wasn't active
723 			return;
724 		}
725 		if ( !strcmp( cmd_name, cmd->name ) ) {
726 			*back = cmd->next;
727 			Z_Free(cmd->name);
728 			Z_Free(cmd->description);
729 			Z_Free (cmd);
730 			return;
731 		}
732 		back = &cmd->next;
733 	}
734 }
735 
736 /*
737 ============
738 Cmd_VM_RemoveCommand
739 
740 Only remove commands with no associated function
741 ============
742 */
Cmd_VM_RemoveCommand(const char * cmd_name,vmSlots_t vmslot)743 void Cmd_VM_RemoveCommand( const char *cmd_name, vmSlots_t vmslot ) {
744 	cmd_function_t *cmd = Cmd_FindCommand( cmd_name );
745 
746 	if( !cmd )
747 		return;
748 
749 	if( cmd->function )
750 	{
751 		Com_Printf( "%s tried to remove system command \"%s\"", vmStrs[vmslot], cmd_name );
752 		return;
753 	}
754 
755 	Cmd_RemoveCommand( cmd_name );
756 }
757 
758 /*
759 ============
760 Cmd_DescriptionString
761 ============
762 */
Cmd_DescriptionString(const char * cmd_name)763 char *Cmd_DescriptionString( const char *cmd_name )
764 {
765 	const cmd_function_t *cmd = Cmd_FindCommand( cmd_name );
766 
767 	if ( !cmd || !VALIDSTRING( cmd->description ) )
768 		return "";
769 	return cmd->description;
770 }
771 
772 /*
773 ============
774 Cmd_Print
775 
776 ============
777 */
Cmd_Print(const cmd_function_t * cmd)778 void Cmd_Print( const cmd_function_t *cmd )
779 {
780 	Com_Printf( S_COLOR_GREY "Cmd " S_COLOR_WHITE "%s", cmd->name );
781 
782 	if ( VALIDSTRING( cmd->description ) )
783 	{
784 		Com_Printf( S_COLOR_GREEN " - %s" S_COLOR_WHITE, cmd->description );
785 	}
786 
787 	Com_Printf( "\n" );
788 }
789 
790 /*
791 ============
792 Cmd_CommandCompletion
793 ============
794 */
Cmd_CommandCompletion(callbackFunc_t callback)795 void	Cmd_CommandCompletion( callbackFunc_t callback ) {
796 	cmd_function_t	*cmd;
797 
798 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next) {
799 		callback( cmd->name );
800 	}
801 }
802 
803 /*
804 ============
805 Cmd_CompleteArgument
806 ============
807 */
Cmd_CompleteArgument(const char * command,char * args,int argNum)808 void Cmd_CompleteArgument( const char *command, char *args, int argNum ) {
809 	for ( cmd_function_t *cmd=cmd_functions; cmd; cmd=cmd->next ) {
810 		if ( !Q_stricmp( command, cmd->name ) && cmd->complete )
811 			cmd->complete( args, argNum );
812 	}
813 }
814 
815 /*
816 ============
817 Cmd_ExecuteString
818 
819 A complete command line has been parsed, so try to execute it
820 ============
821 */
Cmd_ExecuteString(const char * text)822 void	Cmd_ExecuteString( const char *text ) {
823 	cmd_function_t	*cmd, **prev;
824 
825 	// execute the command line
826 	Cmd_TokenizeString( text );
827 	if ( !Cmd_Argc() ) {
828 		return;		// no tokens
829 	}
830 
831 	// check registered command functions
832 	for ( prev = &cmd_functions ; *prev ; prev = &cmd->next ) {
833 		cmd = *prev;
834 		if ( !Q_stricmp( Cmd_Argv(0), cmd->name ) ) {
835 			// rearrange the links so that the command will be
836 			// near the head of the list next time it is used
837 			*prev = cmd->next;
838 			cmd->next = cmd_functions;
839 			cmd_functions = cmd;
840 
841 			// perform the action
842 			if ( !cmd->function ) {
843 				// let the cgame or game handle it
844 				break;
845 			} else {
846 				cmd->function ();
847 			}
848 			return;
849 		}
850 	}
851 
852 	// check cvars
853 	if ( Cvar_Command() ) {
854 		return;
855 	}
856 
857 	// check client game commands
858 	if ( com_cl_running && com_cl_running->integer && CL_GameCommand() ) {
859 		return;
860 	}
861 
862 	// check server game commands
863 	if ( com_sv_running && com_sv_running->integer && SV_GameCommand() ) {
864 		return;
865 	}
866 
867 	// check ui commands
868 	if ( com_cl_running && com_cl_running->integer && UI_GameCommand() ) {
869 		return;
870 	}
871 
872 	// send it as a server command if we are connected
873 	// this will usually result in a chat message
874 	//CL_ForwardCommandToServer ( text );
875 	CL_ForwardCommandToServer ( text );
876 }
877 
878 typedef std::vector<const cmd_function_t *> CmdFuncVector;
879 
CmdSort(const cmd_function_t * cmd1,const cmd_function_t * cmd2)880 bool CmdSort( const cmd_function_t *cmd1, const cmd_function_t *cmd2 )
881 {
882 	return Q_stricmp( cmd1->name, cmd2->name ) < 0;
883 }
884 
885 /*
886 ============
887 Cmd_List_f
888 ============
889 */
Cmd_List_f(void)890 static void Cmd_List_f (void)
891 {
892 	const cmd_function_t	*cmd = NULL;
893 	int				i, j;
894 	char			*match = NULL;
895 	CmdFuncVector	cmds;
896 
897 	if ( Cmd_Argc() > 1 ) {
898 		match = Cmd_Argv( 1 );
899 	}
900 
901 	for ( cmd=cmd_functions, i=0, j=0;
902 		cmd;
903 		cmd=cmd->next, i++ )
904 	{
905 		if ( !cmd->name || (match && !Com_Filter( match, cmd->name, qfalse )) )
906 			continue;
907 
908 		cmds.push_back( cmd );
909 		j++;
910 	}
911 
912 	// sort list alphabetically
913 	std::sort( cmds.begin(), cmds.end(), CmdSort );
914 
915 	CmdFuncVector::const_iterator itr;
916 	for ( itr = cmds.begin();
917 	itr != cmds.end();
918 		++itr )
919 	{
920 		cmd = (*itr);
921 		if ( VALIDSTRING( cmd->description ) )
922 			Com_Printf( " %s" S_COLOR_GREEN " - %s" S_COLOR_WHITE "\n", cmd->name, cmd->description );
923 		else
924 			Com_Printf( " %s\n", cmd->name );
925 	}
926 
927 	Com_Printf ("\n%i total commands\n", i);
928 	if ( i != j )
929 		Com_Printf( "%i matching commands\n", j );
930 }
931 
Cmd_PrintHelp_f(void)932 static void Cmd_PrintHelp_f( void )
933 {
934 	if ( Cmd_Argc() != 2 )
935 	{
936 		Com_Printf( "usage: help <command or alias>\n" );
937 		return;
938 	}
939 
940 	const char *name = Cmd_Argv( 1 );
941 	const cmd_function_t *cmd = Cmd_FindCommand( name );
942 
943 	if ( cmd )
944 		Cmd_Print( cmd );
945 	else
946 		Com_Printf( "Command %s does not exist.\n", name );
947 }
948 
949 /*
950 ==================
951 Cmd_CompleteCmdName
952 ==================
953 */
Cmd_CompleteCmdName(char * args,int argNum)954 void Cmd_CompleteCmdName( char *args, int argNum )
955 {
956 	if ( argNum == 2 )
957 	{
958 		// Skip "<cmd> "
959 		char *p = Com_SkipTokens( args, 1, " " );
960 
961 		if ( p > args )
962 			Field_CompleteCommand( p, qtrue, qfalse );
963 	}
964 }
965 
966 /*
967 ==================
968 Cmd_CompleteCfgName
969 ==================
970 */
Cmd_CompleteCfgName(char * args,int argNum)971 void Cmd_CompleteCfgName( char *args, int argNum ) {
972 	if( argNum == 2 ) {
973 		Field_CompleteFilename( "", "cfg", qfalse, qtrue );
974 	}
975 }
976 
977 /*
978 ============
979 Cmd_Init
980 ============
981 */
Cmd_Init(void)982 void Cmd_Init (void) {
983 	Cmd_AddCommand( "cmdlist", Cmd_List_f, "List all commands to console" );
984 	Cmd_AddCommand( "help", Cmd_PrintHelp_f, "Print command help" );
985 	Cmd_SetCommandCompletionFunc( "help", Cmd_CompleteCmdName );
986 	Cmd_AddCommand( "echo", Cmd_Echo_f, "Print message to console" );
987 	Cmd_AddCommand( "exec", Cmd_Exec_f, "Execute a script file" );
988 	Cmd_AddCommand( "execq", Cmd_Exec_f, "Execute a script file without displaying a message" );
989 	Cmd_SetCommandCompletionFunc( "exec", Cmd_CompleteCfgName );
990 	Cmd_SetCommandCompletionFunc( "execq", Cmd_CompleteCfgName );
991 	Cmd_AddCommand( "vstr", Cmd_Vstr_f, "Execute the value of a cvar" );
992 	Cmd_SetCommandCompletionFunc( "vstr", Cvar_CompleteCvarName );
993 	Cmd_AddCommand( "wait", Cmd_Wait_f, "Pause command buffer execution" );
994 }
995 
996