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	CMDTEXTSIZE	65536	// space for commands and script files
27 #define	MAX_ALIAS_NAME	32
28 
29 typedef struct cmdalias_s
30 {
31 	struct cmdalias_s   *next;
32 	char		    name[MAX_ALIAS_NAME];
33 	char		    *value;
34 } cmdalias_t;
35 
36 cmdalias_t	*cmd_alias;
37 qboolean	cmd_wait;
38 
39 //=============================================================================
40 
41 /*
42 ============
43 Cmd_Wait_f
44 
45 Causes execution of the remainder of the command buffer to be delayed until
46 next frame.  This allows commands like:
47 bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2"
48 ============
49 */
Cmd_Wait_f(void)50 void Cmd_Wait_f (void)
51 {
52 	cmd_wait = true;
53 }
54 
55 /*
56 =============================================================================
57 
58 						COMMAND BUFFER
59 
60 =============================================================================
61 */
62 
63 sizebuf_t	cmd_text;
64 
65 /*
66 ============
67 Cbuf_Init
68 ============
69 */
Cbuf_Init(void)70 void Cbuf_Init (void)
71 {
72 	SZ_Alloc (&cmd_text, CMDTEXTSIZE); //8192	// space for commands and script files
73 }
74 
75 /*
76 ============
77 Cbuf_AddText2
78 
79 Adds command text at the end of the buffer
80 ============
81 */
Cbuf_AddText2(char * text)82 void Cbuf_AddText2 (char *text)
83 {
84 	if (*text == 'p' && !strncmp(text, "playdemo ", 9))
85 	{
86 		if (cmd_text.cursize > 0 && cmd_text.data[cmd_text.cursize - 1] != '\n')
87 		{
88 			// Gross hack to parse corrupt demos
89 			Cbuf_AddText ("\n");
90 			Con_DPrintf ("Cbuf_AddText2: possibly corrupt stufftext found\n");
91 		}
92 
93 		SCR_BeginLoadingPlaque (); // Ease transition to next demo
94 	}
95 
96 	Cbuf_AddText (text);
97 }
98 
99 /*
100 ============
101 Cbuf_AddText
102 
103 Adds command text at the end of the buffer
104 ============
105 */
Cbuf_AddText(char * text)106 void Cbuf_AddText (char *text)
107 {
108 	int		l;
109 
110 	l = Q_strlen (text);
111 
112 	if (cmd_text.cursize + l >= cmd_text.maxsize)
113 	{
114 		Con_Printf ("Cbuf_AddText: overflow\n");
115 		return;
116 	}
117 
118 	SZ_Write (&cmd_text, text, Q_strlen (text));
119 }
120 
121 
122 /*
123 ============
124 Cbuf_InsertText
125 
126 Adds command text immediately after the current command
127 Adds a \n to the text
128 FIXME: actually change the command buffer to do less copying
129 ============
130 */
Cbuf_InsertText(char * text)131 void Cbuf_InsertText (char *text)
132 {
133 	char	*temp;
134 	int	templen, len;
135 
136 	len = Q_strlen (text);
137 
138 	if (len == 0)
139 		return;
140 
141 // copy off any commands still remaining in the exec buffer
142 	templen = cmd_text.cursize;
143 	if (templen)
144 	{
145 		temp = Z_Malloc (templen);
146 		Q_memcpy (temp, cmd_text.data, templen);
147 		SZ_Clear (&cmd_text);
148 	}
149 	else
150 		temp = NULL;	// shut up compiler
151 
152 // add the entire text of the file
153 	Cbuf_AddText (text);
154 
155 	// Add newline if missing
156 	if (text[len - 1] != '\n')
157 		Cbuf_AddText ("\n");
158 
159 // add the copied off data
160 	if (templen)
161 	{
162 		SZ_Write (&cmd_text, temp, templen);
163 		Z_Free (temp);
164 	}
165 }
166 
167 /*
168 ============
169 Cbuf_Execute
170 ============
171 */
Cbuf_Execute(void)172 void Cbuf_Execute (void)
173 {
174 	int	i;
175 	char	*text;
176 	char	line[CMDTEXTSIZE];
177 	int	quotes;
178 
179 	while (cmd_text.cursize)
180 	{
181 // find a \n or ; line break
182 		text = (char *)cmd_text.data;
183 
184 		quotes = 0;
185 		for (i=0 ; i< cmd_text.cursize ; i++)
186 		{
187 			if (text[i] == '"')
188 				quotes++;
189 			if ( !(quotes&1) &&  text[i] == ';')
190 				break;	// don't break if inside a quoted string
191 			if (text[i] == '\n')
192 				break;
193 		}
194 
195 
196 		memcpy (line, text, i);
197 		line[i] = 0;
198 
199 // delete the text from the command buffer and move remaining commands down
200 // this is necessary because commands (exec, alias) can insert data at the
201 // beginning of the text buffer
202 
203 		if (i == cmd_text.cursize)
204 			cmd_text.cursize = 0;
205 		else
206 		{
207 			i++;
208 			cmd_text.cursize -= i;
209 			Q_memcpy (text, text+i, cmd_text.cursize);
210 		}
211 
212 // execute the command line
213 		Cmd_ExecuteString (line, src_command);
214 
215 		if (cmd_wait)
216 		{	// skip out while text still remains in buffer, leaving it
217 			// for next frame
218 			cmd_wait = false;
219 			break;
220 		}
221 	}
222 }
223 
224 /*
225 ==============================================================================
226 
227 						SCRIPT COMMANDS
228 
229 ==============================================================================
230 */
231 
232 /*
233 ===============
234 Cmd_StuffCmds_f
235 
236 Adds command line parameters as script statements
237 Commands lead with a +, and continue until a - or another +
238 quake +prog jctest.qp +cmd amlev1
239 quake -nosound +cmd amlev1
240 ===============
241 */
Cmd_StuffCmds_f(void)242 void Cmd_StuffCmds_f (void)
243 {
244 	int		i, j;
245 	int		s;
246 	char	*text, *build, c;
247 
248 	if (Cmd_Argc () != 1)
249 	{
250 		Con_Printf ("stuffcmds : execute command line parameters\n");
251 		return;
252 	}
253 
254 // build the combined string to parse from
255 	s = 0;
256 	for (i=1 ; i<com_argc ; i++)
257 	{
258 		if (!com_argv[i])
259 			continue;		// NEXTSTEP nulls out -NXHost
260 		s += Q_strlen (com_argv[i]) + 1;
261 	}
262 	if (!s)
263 		return;
264 
265 	text = Z_Malloc (s+1);
266 	text[0] = 0;
267 	for (i=1 ; i<com_argc ; i++)
268 	{
269 		if (!com_argv[i])
270 			continue;		// NEXTSTEP nulls out -NXHost
271 		Q_strcat (text,com_argv[i]);
272 		if (i != com_argc-1)
273 			Q_strcat (text, " ");
274 	}
275 
276 // pull out the commands
277 	build = Z_Malloc (s+1);
278 	build[0] = 0;
279 
280 	for (i=0 ; i<s-1 ; i++)
281 	{
282 		if (text[i] == '+')
283 		{
284 			i++;
285 
286 			for (j=i ; (text[j] != '+') && (text[j] != '-' || j > i && text[j - 1] != ' ') && (text[j] != 0) ; j++)
287 				;
288 
289 			c = text[j];
290 			text[j] = 0;
291 
292 			Q_strcat (build, text+i);
293 			Q_strcat (build, "\n");
294 			text[j] = c;
295 			i = j-1;
296 		}
297 	}
298 
299 	if (build[0])
300 		Cbuf_InsertText (build);
301 
302 	Z_Free (text);
303 	Z_Free (build);
304 }
305 
306 
307 /*
308 ===============
309 Cmd_Exec_f
310 ===============
311 */
Cmd_Exec_f(void)312 void Cmd_Exec_f (void)
313 {
314 	char	*f;
315 	int		mark;
316 
317 	if (Cmd_Argc () != 2)
318 	{
319 		Con_Printf ("exec <filename> : execute a script file\n");
320 		return;
321 	}
322 
323 	mark = Hunk_LowMark ();
324 	f = (char *)COM_LoadHunkFile (Cmd_Argv(1));
325 	if (!f)
326 	{
327 		Con_Printf ("couldn't exec %s\n",Cmd_Argv(1));
328 		return;
329 	}
330 	Con_Printf ("execing %s\n",Cmd_Argv(1));
331 
332 	Cbuf_InsertText (f);
333 	Hunk_FreeToLowMark (mark);
334 }
335 
336 
337 /*
338 ===============
339 Cmd_Echo_f
340 
341 Just prints the rest of the line to the console
342 ===============
343 */
Cmd_Echo_f(void)344 void Cmd_Echo_f (void)
345 {
346 	int		i;
347 
348 	for (i=1 ; i<Cmd_Argc() ; i++)
349 		Con_Printf ("%s ",Cmd_Argv(i));
350 	Con_Printf ("\n");
351 }
352 
353 /*
354 ===============
355 Cmd_Alias_f
356 
357 Creates a new command that executes a command string (possibly ; seperated)
358 ===============
359 */
360 
Cmd_Alias_f(void)361 void Cmd_Alias_f (void)
362 {
363 	cmdalias_t	*a;
364 	int		i, c, len;
365 	char		*s;
366 
367 	if (Cmd_Argc() == 1)
368 	{
369 		Con_Printf ("Current alias commands:\n");
370 		for (a = cmd_alias ; a ; a=a->next)
371 			Con_Printf ("%s : %s\n", a->name, a->value);
372 		return;
373 	}
374 
375 	s = Cmd_Argv(1);
376 	if (strlen(s) >= MAX_ALIAS_NAME)
377 	{
378 		Con_Printf ("Alias name is too long\n");
379 		return;
380 	}
381 
382 	// if the alias allready exists, reuse it
383 	for (a = cmd_alias ; a ; a=a->next)
384 	{
385 		if (!strcmp(s, a->name))
386 		{
387 			Z_Free (a->value);
388 			break;
389 		}
390 	}
391 
392 	if (!a)
393 	{
394 		a = Z_Malloc (sizeof(cmdalias_t));
395 		a->next = cmd_alias;
396 		cmd_alias = a;
397 	}
398 	strcpy (a->name, s);
399 
400 // copy the rest of the command line
401 	c = Cmd_Argc();
402 
403 	for (i = 2, len = 0; i < c; i++)
404 		len += strlen (Cmd_Argv(i)) + 1;
405 
406 	a->value = Z_Malloc (len + 1);
407 
408 	for (i=2 ; i< c ; i++)
409 	{
410 		strcat (a->value, Cmd_Argv(i));
411 		if (i != c)
412 			strcat (a->value, " ");
413 	}
414 	strcat (a->value, "\n");
415 }
416 
417 /*
418 =============================================================================
419 
420 					COMMAND EXECUTION
421 
422 =============================================================================
423 */
424 
425 typedef struct cmd_function_s
426 {
427 	struct cmd_function_s	*next;
428 	char					*name;
429 	xcommand_t				function;
430 } cmd_function_t;
431 
432 
433 #define	MAX_ARGS		80
434 
435 static	int			cmd_argc;
436 static	char		*cmd_argv[MAX_ARGS];
437 static	char		*cmd_null_string = "";
438 static	char		*cmd_args = NULL;
439 
440 cmd_source_t	cmd_source;
441 
442 
443 static	cmd_function_t	*cmd_functions;		// possible commands to execute
444 
445 /*
446 ============
447 Cmd_Init
448 ============
449 */
Cmd_Init(void)450 void Cmd_Init (void)
451 {
452 //
453 // register our commands
454 //
455 	Cmd_AddCommand ("stuffcmds",Cmd_StuffCmds_f);
456 	Cmd_AddCommand ("exec",Cmd_Exec_f);
457 	Cmd_AddCommand ("echo",Cmd_Echo_f);
458 	Cmd_AddCommand ("alias",Cmd_Alias_f);
459 	Cmd_AddCommand ("cmd", Cmd_ForwardToServer);
460 	Cmd_AddCommand ("wait", Cmd_Wait_f);
461 }
462 
463 /*
464 ============
465 Cmd_Argc
466 ============
467 */
Cmd_Argc(void)468 int		Cmd_Argc (void)
469 {
470 	return cmd_argc;
471 }
472 
473 /*
474 ============
475 Cmd_Argv
476 ============
477 */
Cmd_Argv(int arg)478 char	*Cmd_Argv (int arg)
479 {
480 	if ( (unsigned)arg >= cmd_argc )
481 		return cmd_null_string;
482 	return cmd_argv[arg];
483 }
484 
485 /*
486 ============
487 Cmd_Args
488 ============
489 */
Cmd_Args(void)490 char		*Cmd_Args (void)
491 {
492 	return cmd_args;
493 }
494 
495 
496 /*
497 ============
498 Cmd_TokenizeString
499 
500 Parses the given string into command line tokens.
501 ============
502 */
Cmd_TokenizeString(char * text)503 void Cmd_TokenizeString (char *text)
504 {
505 	int		i;
506 
507 // clear the args from the last string
508 	for (i=0 ; i<cmd_argc ; i++)
509 		Z_Free (cmd_argv[i]);
510 
511 	cmd_argc = 0;
512 	cmd_args = NULL;
513 
514 	while (1)
515 	{
516 // skip whitespace up to a /n
517 		while (*text && *text <= ' ' && *text != '\n')
518 		{
519 			text++;
520 		}
521 
522 		if (*text == '\n')
523 		{	// a newline seperates commands in the buffer
524 			text++;
525 			break;
526 		}
527 
528 		if (!*text)
529 			return;
530 
531 		if (cmd_argc == 1)
532 			 cmd_args = text;
533 
534 		text = COM_Parse (text);
535 		if (!text)
536 			return;
537 
538 		if (cmd_argc < MAX_ARGS)
539 		{
540 			cmd_argv[cmd_argc] = Z_Malloc (Q_strlen(com_token)+1);
541 			Q_strcpy (cmd_argv[cmd_argc], com_token);
542 			cmd_argc++;
543 		}
544 	}
545 
546 }
547 
548 
549 /*
550 ============
551 Cmd_AddCommand
552 ============
553 */
Cmd_AddCommand(char * cmd_name,xcommand_t function)554 void	Cmd_AddCommand (char *cmd_name, xcommand_t function)
555 {
556 	cmd_function_t	*cmd;
557 
558 	if (host_initialized)	// because hunk allocation would get stomped
559 		Sys_Error ("Cmd_AddCommand after host_initialized");
560 
561 // fail if the command is a variable name
562 	if (Cvar_VariableString(cmd_name)[0])
563 	{
564 		Con_Printf ("Cmd_AddCommand: %s already defined as a var\n", cmd_name);
565 		return;
566 	}
567 
568 // fail if the command already exists
569 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
570 	{
571 		if (!Q_strcmp (cmd_name, cmd->name))
572 		{
573 			Con_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name);
574 			return;
575 		}
576 	}
577 
578 	cmd = Hunk_Alloc (sizeof(cmd_function_t));
579 	cmd->name = cmd_name;
580 	cmd->function = function;
581 	cmd->next = cmd_functions;
582 	cmd_functions = cmd;
583 }
584 
585 /*
586 ============
587 Cmd_Exists
588 ============
589 */
Cmd_Exists(char * cmd_name)590 qboolean	Cmd_Exists (char *cmd_name)
591 {
592 	cmd_function_t	*cmd;
593 
594 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
595 	{
596 		if (!Q_strcmp (cmd_name,cmd->name))
597 			return true;
598 	}
599 
600 	return false;
601 }
602 
603 
604 
605 /*
606 ============
607 Cmd_CompleteCommand
608 ============
609 */
Cmd_CompleteCommand(char * partial)610 char *Cmd_CompleteCommand (char *partial)
611 {
612 	cmd_function_t	*cmd;
613 	int				len;
614 
615 	len = Q_strlen(partial);
616 
617 	if (!len)
618 		return NULL;
619 
620 // check functions
621 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
622 		if (!Q_strncmp (partial,cmd->name, len))
623 			return cmd->name;
624 
625 	return NULL;
626 }
627 
628 /*
629 ============
630 Cmd_ExecuteString
631 
632 A complete command line has been parsed, so try to execute it
633 FIXME: lookupnoadd the token to speed search?
634 ============
635 */
Cmd_ExecuteString(char * text,cmd_source_t src)636 void	Cmd_ExecuteString (char *text, cmd_source_t src)
637 {
638 	cmd_function_t	*cmd;
639 	cmdalias_t		*a;
640 
641 	cmd_source = src;
642 	Cmd_TokenizeString (text);
643 
644 // execute the command line
645 	if (!Cmd_Argc())
646 		return;		// no tokens
647 
648 // check functions
649 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
650 	{
651 		if (!Q_strcasecmp (cmd_argv[0],cmd->name))
652 		{
653 			cmd->function ();
654 			return;
655 		}
656 	}
657 
658 // check alias
659 	for (a=cmd_alias ; a ; a=a->next)
660 	{
661 		if (!Q_strcasecmp (cmd_argv[0], a->name))
662 		{
663 			Cbuf_InsertText (a->value);
664 			return;
665 		}
666 	}
667 
668 // check cvars
669 	if (!Cvar_Command ())
670 		Con_SafePrintf ("Unknown command \"%s\"\n", Cmd_Argv(0));
671 
672 }
673 
674 
675 /*
676 ===================
677 Cmd_ForwardToServer
678 
679 Sends the entire command line over to the server
680 ===================
681 */
Cmd_ForwardToServer(void)682 void Cmd_ForwardToServer (void)
683 {
684 	if (cls.state != ca_connected)
685 	{
686 		Con_Printf ("Can't \"%s\", not connected\n", Cmd_Argv(0));
687 		return;
688 	}
689 
690 	if (cls.demoplayback)
691 		return;		// not really connected
692 
693 	MSG_WriteByte (&cls.message, clc_stringcmd);
694 	if (Q_strcasecmp(Cmd_Argv(0), "cmd") != 0)
695 	{
696 		SZ_Print (&cls.message, Cmd_Argv(0));
697 		SZ_Print (&cls.message, " ");
698 	}
699 	if (Cmd_Argc() > 1)
700 		SZ_Print (&cls.message, Cmd_Args());
701 	else
702 		SZ_Print (&cls.message, "\n");
703 }
704 
705 
706 /*
707 ================
708 Cmd_CheckParm
709 
710 Returns the position (1 to argc-1) in the command's argument list
711 where the given parameter apears, or 0 if not present
712 ================
713 */
714 
Cmd_CheckParm(char * parm)715 int Cmd_CheckParm (char *parm)
716 {
717 	int i;
718 
719 	if (!parm)
720 		Sys_Error ("Cmd_CheckParm: NULL");
721 
722 	for (i = 1; i < Cmd_Argc (); i++)
723 		if (! Q_strcasecmp (parm, Cmd_Argv (i)))
724 			return i;
725 
726 	return 0;
727 }
728