1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 */
20 // cmd.c -- Quake script command processing module
21 
22 #include "quakedef.h"
23 
24 void Cmd_ForwardToServer (void);
25 
26 #define	MAX_ALIAS_NAME	32
27 
28 typedef struct cmdalias_s
29 {
30 	struct cmdalias_s	*next;
31 	char	name[MAX_ALIAS_NAME];
32 	char	*value;
33 } cmdalias_t;
34 
35 cmdalias_t	*cmd_alias;
36 
37 int trashtest;
38 int *trashspot;
39 
40 qboolean	cmd_wait;
41 
42 //=============================================================================
43 
44 /*
45 ============
46 Cmd_Wait_f
47 
48 Causes execution of the remainder of the command buffer to be delayed until
49 next frame.  This allows commands like:
50 bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2"
51 ============
52 */
Cmd_Wait_f(void)53 void Cmd_Wait_f (void)
54 {
55 	cmd_wait = true;
56 }
57 
58 /*
59 =============================================================================
60 
61 						COMMAND BUFFER
62 
63 =============================================================================
64 */
65 
66 sizebuf_t	cmd_text;
67 
68 /*
69 ============
70 Cbuf_Init
71 ============
72 */
Cbuf_Init(void)73 void Cbuf_Init (void)
74 {
75 	SZ_Alloc (&cmd_text, 8192);		// space for commands and script files
76 }
77 
78 
79 /*
80 ============
81 Cbuf_AddText
82 
83 Adds command text at the end of the buffer
84 ============
85 */
Cbuf_AddText(char * text)86 void Cbuf_AddText (char *text)
87 {
88 	int		l;
89 
90 	l = Q_strlen (text);
91 
92 	if (cmd_text.cursize + l >= cmd_text.maxsize)
93 	{
94 		Con_Printf ("Cbuf_AddText: overflow\n");
95 		return;
96 	}
97 
98 	SZ_Write (&cmd_text, text, Q_strlen (text));
99 }
100 
101 
102 /*
103 ============
104 Cbuf_InsertText
105 
106 Adds command text immediately after the current command
107 Adds a \n to the text
108 FIXME: actually change the command buffer to do less copying
109 ============
110 */
Cbuf_InsertText(char * text)111 void Cbuf_InsertText (char *text)
112 {
113 	char	*temp;
114 	int		templen;
115 
116 // copy off any commands still remaining in the exec buffer
117 	templen = cmd_text.cursize;
118 	if (templen)
119 	{
120 		temp = Z_Malloc (templen);
121 		Q_memcpy (temp, cmd_text.data, templen);
122 		SZ_Clear (&cmd_text);
123 	}
124 	else
125 		temp = NULL;	// shut up compiler
126 
127 // add the entire text of the file
128 	Cbuf_AddText (text);
129 
130 // add the copied off data
131 	if (templen)
132 	{
133 		SZ_Write (&cmd_text, temp, templen);
134 		Z_Free (temp);
135 	}
136 }
137 
138 /*
139 ============
140 Cbuf_Execute
141 ============
142 */
Cbuf_Execute(void)143 void Cbuf_Execute (void)
144 {
145 	int		i;
146 	char	*text;
147 	char	line[1024];
148 	int		quotes;
149 
150 	while (cmd_text.cursize)
151 	{
152 // find a \n or ; line break
153 		text = (char *)cmd_text.data;
154 
155 		quotes = 0;
156 		for (i=0 ; i< cmd_text.cursize ; i++)
157 		{
158 			if (text[i] == '"')
159 				quotes++;
160 			if ( !(quotes&1) &&  text[i] == ';')
161 				break;	// don't break if inside a quoted string
162 			if (text[i] == '\n')
163 				break;
164 		}
165 
166 
167 		memcpy (line, text, i);
168 		line[i] = 0;
169 
170 // delete the text from the command buffer and move remaining commands down
171 // this is necessary because commands (exec, alias) can insert data at the
172 // beginning of the text buffer
173 
174 		if (i == cmd_text.cursize)
175 			cmd_text.cursize = 0;
176 		else
177 		{
178 			i++;
179 			cmd_text.cursize -= i;
180 			Q_memcpy (text, text+i, cmd_text.cursize);
181 		}
182 
183 // execute the command line
184 		Cmd_ExecuteString (line, src_command);
185 
186 		if (cmd_wait)
187 		{	// skip out while text still remains in buffer, leaving it
188 			// for next frame
189 			cmd_wait = false;
190 			break;
191 		}
192 	}
193 }
194 
195 /*
196 ==============================================================================
197 
198 						SCRIPT COMMANDS
199 
200 ==============================================================================
201 */
202 
203 /*
204 ===============
205 Cmd_StuffCmds_f
206 
207 Adds command line parameters as script statements
208 Commands lead with a +, and continue until a - or another +
209 quake +prog jctest.qp +cmd amlev1
210 quake -nosound +cmd amlev1
211 ===============
212 */
Cmd_StuffCmds_f(void)213 void Cmd_StuffCmds_f (void)
214 {
215 	int		i, j;
216 	int		s;
217 	char	*text, *build, c;
218 
219 	if (Cmd_Argc () != 1)
220 	{
221 		Con_Printf ("stuffcmds : execute command line parameters\n");
222 		return;
223 	}
224 
225 // build the combined string to parse from
226 	s = 0;
227 	for (i=1 ; i<com_argc ; i++)
228 	{
229 		if (!com_argv[i])
230 			continue;		// NEXTSTEP nulls out -NXHost
231 		s += Q_strlen (com_argv[i]) + 1;
232 	}
233 	if (!s)
234 		return;
235 
236 	text = Z_Malloc (s+1);
237 	text[0] = 0;
238 	for (i=1 ; i<com_argc ; i++)
239 	{
240 		if (!com_argv[i])
241 			continue;		// NEXTSTEP nulls out -NXHost
242 		Q_strcat (text,com_argv[i]);
243 		if (i != com_argc-1)
244 			Q_strcat (text, " ");
245 	}
246 
247 // pull out the commands
248 	build = Z_Malloc (s+1);
249 	build[0] = 0;
250 
251 	for (i=0 ; i<s-1 ; i++)
252 	{
253 		if (text[i] == '+')
254 		{
255 			i++;
256 
257 			for (j=i ; (text[j] != '+') && (text[j] != '-') && (text[j] != 0) ; j++)
258 				;
259 
260 			c = text[j];
261 			text[j] = 0;
262 
263 			Q_strcat (build, text+i);
264 			Q_strcat (build, "\n");
265 			text[j] = c;
266 			i = j-1;
267 		}
268 	}
269 
270 	if (build[0])
271 		Cbuf_InsertText (build);
272 
273 	Z_Free (text);
274 	Z_Free (build);
275 }
276 
277 
278 /*
279 ===============
280 Cmd_Exec_f
281 ===============
282 */
Cmd_Exec_f(void)283 void Cmd_Exec_f (void)
284 {
285 	char	*f;
286 	int		mark;
287 
288 	if (Cmd_Argc () != 2)
289 	{
290 		Con_Printf ("exec <filename> : execute a script file\n");
291 		return;
292 	}
293 
294 	mark = Hunk_LowMark ();
295 	f = (char *)COM_LoadHunkFile (Cmd_Argv(1));
296 	if (!f)
297 	{
298 		Con_Printf ("couldn't exec %s\n",Cmd_Argv(1));
299 		return;
300 	}
301 #if defined (__APPLE__) || defined (MACOSX)
302 
303         // <AWE> Fix for stupid Windows linebreaks.
304         {
305             char *	myData = f;
306 
307             while (*myData != 0x00)
308             {
309                 if (*myData == 0x0D)
310                 {
311                     *myData = 0x0A;
312                 }
313                 myData++;
314             }
315         }
316 
317 #endif /* __APPLE__ ||�MACOSX */
318 	Con_Printf ("execing %s\n",Cmd_Argv(1));
319 
320 	Cbuf_InsertText (f);
321 	Hunk_FreeToLowMark (mark);
322 }
323 
324 
325 /*
326 ===============
327 Cmd_Echo_f
328 
329 Just prints the rest of the line to the console
330 ===============
331 */
Cmd_Echo_f(void)332 void Cmd_Echo_f (void)
333 {
334 	int		i;
335 
336 	for (i=1 ; i<Cmd_Argc() ; i++)
337 		Con_Printf ("%s ",Cmd_Argv(i));
338 	Con_Printf ("\n");
339 }
340 
341 /*
342 ===============
343 Cmd_Alias_f
344 
345 Creates a new command that executes a command string (possibly ; seperated)
346 ===============
347 */
348 
CopyString(char * in)349 char *CopyString (char *in)
350 {
351 	char	*out;
352 
353 	out = Z_Malloc (strlen(in)+1);
354 	strcpy (out, in);
355 	return out;
356 }
357 
Cmd_Alias_f(void)358 void Cmd_Alias_f (void)
359 {
360 	cmdalias_t	*a;
361 	char		cmd[1024];
362 	int			i, c;
363 	char		*s;
364 
365 	if (Cmd_Argc() == 1)
366 	{
367 		Con_Printf ("Current alias commands:\n");
368 		for (a = cmd_alias ; a ; a=a->next)
369 			Con_Printf ("%s : %s\n", a->name, a->value);
370 		return;
371 	}
372 
373 	s = Cmd_Argv(1);
374 	if (strlen(s) >= MAX_ALIAS_NAME)
375 	{
376 		Con_Printf ("Alias name is too long\n");
377 		return;
378 	}
379 
380 	// if the alias allready exists, reuse it
381 	for (a = cmd_alias ; a ; a=a->next)
382 	{
383 		if (!strcmp(s, a->name))
384 		{
385 			Z_Free (a->value);
386 			break;
387 		}
388 	}
389 
390 	if (!a)
391 	{
392 		a = Z_Malloc (sizeof(cmdalias_t));
393 		a->next = cmd_alias;
394 		cmd_alias = a;
395 	}
396 	strcpy (a->name, s);
397 
398 // copy the rest of the command line
399 	cmd[0] = 0;		// start out with a null string
400 	c = Cmd_Argc();
401 	for (i=2 ; i< c ; i++)
402 	{
403 		strcat (cmd, Cmd_Argv(i));
404 		if (i != c)
405 			strcat (cmd, " ");
406 	}
407 	strcat (cmd, "\n");
408 
409 	a->value = CopyString (cmd);
410 }
411 
412 /*
413 =============================================================================
414 
415 					COMMAND EXECUTION
416 
417 =============================================================================
418 */
419 
420 typedef struct cmd_function_s
421 {
422 	struct cmd_function_s	*next;
423 	char					*name;
424 	xcommand_t				function;
425 } cmd_function_t;
426 
427 
428 #define	MAX_ARGS		80
429 
430 static	int			cmd_argc;
431 static	char		*cmd_argv[MAX_ARGS];
432 static	char		*cmd_null_string = "";
433 static	char		*cmd_args = NULL;
434 
435 cmd_source_t	cmd_source;
436 
437 
438 static	cmd_function_t	*cmd_functions;		// possible commands to execute
439 
440 /*
441 ============
442 Cmd_Init
443 ============
444 */
Cmd_Init(void)445 void Cmd_Init (void)
446 {
447 //
448 // register our commands
449 //
450 	Cmd_AddCommand ("stuffcmds",Cmd_StuffCmds_f);
451 	Cmd_AddCommand ("exec",Cmd_Exec_f);
452 	Cmd_AddCommand ("echo",Cmd_Echo_f);
453 	Cmd_AddCommand ("alias",Cmd_Alias_f);
454 	Cmd_AddCommand ("cmd", Cmd_ForwardToServer);
455 	Cmd_AddCommand ("wait", Cmd_Wait_f);
456 	Cmd_AddCommand ("cmdlist", Cmd_List_f); //Cmdlist - Eradicator
457 	Cmd_AddCommand ("cvarlist", Cvar_List_f); //Cvarlist - Eradicator
458 }
459 
460 /*
461 ============
462 Cmd_Argc
463 ============
464 */
Cmd_Argc(void)465 int		Cmd_Argc (void)
466 {
467 	return cmd_argc;
468 }
469 
470 /*
471 ============
472 Cmd_Argv
473 ============
474 */
Cmd_Argv(int arg)475 char	*Cmd_Argv (int arg)
476 {
477 	if ( (unsigned)arg >= cmd_argc )
478 		return cmd_null_string;
479 	return cmd_argv[arg];
480 }
481 
482 /*
483 ============
484 Cmd_Args
485 ============
486 */
Cmd_Args(void)487 char		*Cmd_Args (void)
488 {
489 	return cmd_args;
490 }
491 
492 
493 /*
494 ============
495 Cmd_TokenizeString
496 
497 Parses the given string into command line tokens.
498 ============
499 */
Cmd_TokenizeString(char * text)500 void Cmd_TokenizeString (char *text)
501 {
502 	int		i;
503 
504 // clear the args from the last string
505 	for (i=0 ; i<cmd_argc ; i++)
506 		Z_Free (cmd_argv[i]);
507 
508 	cmd_argc = 0;
509 	cmd_args = NULL;
510 
511 	while (1)
512 	{
513 // skip whitespace up to a /n
514 		while (*text && *text <= ' ' && *text != '\n')
515 		{
516 			text++;
517 		}
518 
519 		if (*text == '\n')
520 		{	// a newline seperates commands in the buffer
521 			text++;
522 			break;
523 		}
524 
525 		if (!*text)
526 			return;
527 
528 		if (cmd_argc == 1)
529 			 cmd_args = text;
530 
531 		text = COM_Parse (text);
532 		if (!text)
533 			return;
534 
535 		if (cmd_argc < MAX_ARGS)
536 		{
537 			cmd_argv[cmd_argc] = Z_Malloc (Q_strlen(com_token)+1);
538 			Q_strcpy (cmd_argv[cmd_argc], com_token);
539 			cmd_argc++;
540 		}
541 	}
542 
543 }
544 
545 
546 /*
547 ============
548 Cmd_AddCommand
549 ============
550 */
Cmd_AddCommand(char * cmd_name,xcommand_t function)551 void	Cmd_AddCommand (char *cmd_name, xcommand_t function)
552 {
553 	cmd_function_t	*cmd;
554 
555 	if (host_initialized)	// because hunk allocation would get stomped
556 		Sys_Error ("Cmd_AddCommand after host_initialized");
557 
558 // fail if the command is a variable name
559 	if (Cvar_VariableString(cmd_name)[0])
560 	{
561 		Con_Printf ("Cmd_AddCommand: %s already defined as a var\n", cmd_name);
562 		return;
563 	}
564 
565 // fail if the command already exists
566 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
567 	{
568 		if (!Q_strcmp (cmd_name, cmd->name))
569 		{
570 			Con_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name);
571 			return;
572 		}
573 	}
574 
575 	cmd = Hunk_Alloc (sizeof(cmd_function_t));
576 	cmd->name = cmd_name;
577 	cmd->function = function;
578 	cmd->next = cmd_functions;
579 	cmd_functions = cmd;
580 }
581 
582 /*
583 ============
584 Cmd_Exists
585 ============
586 */
Cmd_Exists(char * cmd_name)587 qboolean	Cmd_Exists (char *cmd_name)
588 {
589 	cmd_function_t	*cmd;
590 
591 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
592 	{
593 		if (!Q_strcmp (cmd_name,cmd->name))
594 			return true;
595 	}
596 
597 	return false;
598 }
599 
600 
601 
602 /*
603 ============
604 Cmd_CompleteCommand
605 ============
606 */
Cmd_CompleteCommand(char * partial)607 char *Cmd_CompleteCommand (char *partial)
608 {
609 	cmd_function_t	*cmd;
610 	int				len;
611 
612 	len = Q_strlen(partial);
613 
614 	if (!len)
615 		return NULL;
616 
617 // check functions
618 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
619 		if (!Q_strncmp (partial,cmd->name, len))
620 			return cmd->name;
621 
622 	return NULL;
623 }
624 
625 /*
626 ============
627 Cmd_CompleteCountPossible
628 ============
629 */
Cmd_CompleteCountPossible(char * partial)630 int Cmd_CompleteCountPossible (char *partial)
631 {
632 	cmd_function_t	*cmd;
633 	int	len;
634 	int	h;
635 
636 	h=0;
637 
638 	len = Q_strlen(partial);
639 
640 	if (!len)
641 		return 0;
642 
643 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
644 		if (!Q_strncmp (partial,cmd->name, len))
645 			h++;
646 	return h;
647 }
648 
649 /*
650 ============
651 Cmd_CompletePrintPossible
652 ============
653 */
Cmd_CompletePrintPossible(char * partial)654 void Cmd_CompletePrintPossible (char *partial)
655 {
656 	cmd_function_t	*cmd;
657 	int	len;
658 	int	lpos;
659 	int	out;
660 	int	con_linewidth;
661 	char	sout[32];
662 	char	lout[2048];
663 
664 	lpos = 0;
665 	len = Q_strlen(partial);
666 	Q_strcpy(lout,"");
667 
668 	con_linewidth = (vid.width >> 3) - 3;
669 
670 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
671 		if (!Q_strncmp (partial,cmd->name, len))
672 		{
673 			Q_strcpy(sout, cmd->name);
674 
675 			out = Q_strlen(sout);
676 			lpos += out;
677 
678 			for (out; out<20; out++)
679 			{
680 				if (lpos < con_linewidth)
681 					Q_strcat (sout, " ");
682 
683 				lpos++;
684 			}
685 
686 			Q_strcat (lout, sout);
687 
688 			if (lpos > con_linewidth - 24)
689 				for  (lpos; lpos < con_linewidth; lpos++)
690 					Q_strcat(lout, " ");
691 
692 			if (lpos >= con_linewidth)
693 				lpos = 0;
694 		}
695 
696 	Con_Printf ("%s\n\n", lout);
697 }
698 
699 /*
700 ============
701 Cmd_CompleteAlias
702 
703 Alias Command Tab Completion - Eradicator
704 ============
705 */
Cmd_CompleteAlias(char * partial)706 char *Cmd_CompleteAlias (char *partial)
707 {
708 	cmdalias_t		*alias;
709 	int				len;
710 
711 	len = Q_strlen(partial);
712 
713 	if (!len)
714 		return NULL;
715 
716 	for (alias=cmd_alias ; alias ; alias=alias->next)
717 		if (!Q_strncmp (partial,alias->name, len))
718 			return alias->name;
719 	return NULL;
720 }
721 
722 /*
723 ============
724 Cmd_CompleteAliasCountPossible
725 ============
726 */
Cmd_CompleteAliasCountPossible(char * partial)727 int Cmd_CompleteAliasCountPossible (char *partial)
728 {
729 	cmdalias_t		*alias;
730 	int				len;
731 	int				h;
732 
733 	h=0;
734 
735 	len = Q_strlen(partial);
736 
737 	if (!len)
738 		return 0;
739 
740 	for (alias=cmd_alias ; alias ; alias=alias->next)
741 		if (!Q_strncmp (partial,alias->name, len))
742 			h++;
743 	return h;
744 }
745 
746 /*
747 ============
748 Cmd_CompleteAliasPrintPossible
749 ============
750 */
Cmd_CompleteAliasPrintPossible(char * partial)751 void Cmd_CompleteAliasPrintPossible (char *partial)
752 {
753 	cmdalias_t	*alias;
754 	int		len;
755 	int		lpos;
756 	int		out;
757 	int		con_linewidth;
758 	char		sout[32];
759 	char		lout[2048];
760 
761 	lpos = 0;
762 	len = Q_strlen(partial);
763 	Q_strcpy(lout,"");
764 
765 	con_linewidth = (vid.width >> 3) - 3;
766 
767 	for (alias=cmd_alias ; alias ; alias=alias->next)
768 		if (!Q_strncmp (partial,alias->name, len))
769 		{
770 			Q_strcpy(sout, alias->name);
771 
772 			out = Q_strlen(sout);
773 			lpos += out;
774 
775 			for (out; out<20; out++)
776 			{
777 				if (lpos < con_linewidth)
778 					Q_strcat (sout, " ");
779 
780 				lpos++;
781 			}
782 
783 			Q_strcat (lout, sout);
784 
785 			if (lpos > con_linewidth - 24)
786 				for  (lpos; lpos < con_linewidth; lpos++)
787 					Q_strcat(lout, " ");
788 
789 			if (lpos >= con_linewidth)
790 				lpos = 0;
791 		}
792 	Con_Printf ("%s\n\n", lout);
793 }
794 
795 /*
796 ============
797 Cmd_ExecuteString
798 
799 A complete command line has been parsed, so try to execute it
800 FIXME: lookupnoadd the token to speed search?
801 ============
802 */
Cmd_ExecuteString(char * text,cmd_source_t src)803 void	Cmd_ExecuteString (char *text, cmd_source_t src)
804 {
805 	cmd_function_t	*cmd;
806 	cmdalias_t		*a;
807 
808 	cmd_source = src;
809 	Cmd_TokenizeString (text);
810 
811 // execute the command line
812 	if (!Cmd_Argc())
813 		return;		// no tokens
814 
815 // check functions
816 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
817 	{
818 		if (!Q_strcasecmp (cmd_argv[0],cmd->name))
819 		{
820 			cmd->function ();
821 			return;
822 		}
823 	}
824 
825 // check alias
826 	for (a=cmd_alias ; a ; a=a->next)
827 	{
828 		if (!Q_strcasecmp (cmd_argv[0], a->name))
829 		{
830 			Cbuf_InsertText (a->value);
831 			return;
832 		}
833 	}
834 
835 // check cvars
836 	if (!Cvar_Command ())
837 		Con_Printf ("Unknown command \"%s\"\n", Cmd_Argv(0));
838 
839 }
840 
841 
842 /*
843 ===================
844 Cmd_ForwardToServer
845 
846 Sends the entire command line over to the server
847 ===================
848 */
Cmd_ForwardToServer(void)849 void Cmd_ForwardToServer (void)
850 {
851 	if (cls.state != ca_connected)
852 	{
853 		Con_Printf ("Can't \"%s\", not connected\n", Cmd_Argv(0));
854 		return;
855 	}
856 
857 	if (cls.demoplayback)
858 		return;		// not really connected
859 
860 	MSG_WriteByte (&cls.message, clc_stringcmd);
861 	if (Q_strcasecmp(Cmd_Argv(0), "cmd") != 0)
862 	{
863 		SZ_Print (&cls.message, Cmd_Argv(0));
864 		SZ_Print (&cls.message, " ");
865 	}
866 	if (Cmd_Argc() > 1)
867 		SZ_Print (&cls.message, Cmd_Args());
868 	else
869 		SZ_Print (&cls.message, "\n");
870 }
871 
872 
873 /*
874 ================
875 Cmd_CheckParm
876 
877 Returns the position (1 to argc-1) in the command's argument list
878 where the given parameter apears, or 0 if not present
879 ================
880 */
881 
Cmd_CheckParm(char * parm)882 int Cmd_CheckParm (char *parm)
883 {
884 	int i;
885 
886 	if (!parm)
887 		Sys_Error ("Cmd_CheckParm: NULL");
888 
889 	for (i = 1; i < Cmd_Argc (); i++)
890 		if (! Q_strcasecmp (parm, Cmd_Argv (i)))
891 			return i;
892 
893 	return 0;
894 }
895 
896 /*
897 ========
898 Cmd_List
899 
900 Displays a list of all the commands similar to the Quake3 cmdlist - Eradicator
901 ========
902 */
Cmd_List_f(void)903 void Cmd_List_f (void)
904 {
905 	cmd_function_t	*cmd;
906 	char 		*partial;
907 	int		len;
908 	int		count;
909 
910 	if (Cmd_Argc() > 1)
911 	{
912 		partial = Cmd_Argv (1);
913 		len = Q_strlen(partial);
914 	}
915 	else
916 	{
917 		partial = NULL;
918 		len = 0;
919 	}
920 
921 	count=0;
922 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
923 	{
924 		if (partial && Q_strncmp (partial,cmd->name, len))
925 		{
926 			continue;
927 		}
928 		Con_Printf ("\"%s\"\n", cmd->name);
929 		count++;
930 	}
931 
932 	Con_Printf ("%i command(s)", count);
933 	if (partial)
934 	{
935 		Con_Printf (" beginning with \"%s\"", partial);
936 	}
937 	Con_Printf ("\n");
938 }
939