1 /*
2 * Copyright(c) 1997-2001 Id Software, Inc.
3 * Copyright(c) 2002 The Quakeforge Project.
4 * Copyright(c) 2006 Quetoo.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or(at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 *
15 * See the GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 */
21
22 #include "qcommon.h"
23
24 void Cmd_ForwardToServer(void);
25
26 #define MAX_ALIAS_NAME 32
27
28 typedef struct cmdalias_s {
29 struct cmdalias_s *next;
30 char name[MAX_ALIAS_NAME];
31 char *value;
32 } cmdalias_t;
33
34 cmdalias_t *cmd_alias;
35
36 qboolean cmd_wait;
37
38 #define ALIAS_LOOP_COUNT 16
39 int alias_count; // for detecting runaway loops
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 */
Cmd_Wait_f(void)49 void Cmd_Wait_f(void){
50 cmd_wait = true;
51 }
52
53
54 /*
55
56 COMMAND BUFFER
57
58 */
59
60 sizebuf_t cmd_text;
61 byte cmd_text_buf[8192];
62
63 char defer_text_buf[8192];
64
65 /*
66 Cbuf_Init
67 */
Cbuf_Init(void)68 void Cbuf_Init(void){
69 SZ_Init(&cmd_text, cmd_text_buf, sizeof(cmd_text_buf));
70 }
71
72 /*
73 Cbuf_AddText
74
75 Adds command text at the end of the buffer
76 */
Cbuf_AddText(char * text)77 void Cbuf_AddText(char *text){
78 int l;
79
80 l = strlen(text);
81
82 if(cmd_text.cursize + l >= cmd_text.maxsize){
83 Com_Printf("Cbuf_AddText: overflow\n");
84 return;
85 }
86 SZ_Write(&cmd_text, text, strlen(text));
87 }
88
89
90 /*
91 Cbuf_InsertText
92
93 Adds command text immediately after the current command
94 Adds a \n to the text
95 FIXME: actually change the command buffer to do less copying
96 */
Cbuf_InsertText(char * text)97 void Cbuf_InsertText(char *text){
98 char *temp;
99 int templen;
100
101 // copy off any commands still remaining in the exec buffer
102 templen = cmd_text.cursize;
103 if(templen){
104 temp = Z_Malloc(templen);
105 memcpy(temp, cmd_text.data, templen);
106 SZ_Clear(&cmd_text);
107 } else
108 temp = NULL; // shut up compiler
109
110 // add the entire text of the file
111 Cbuf_AddText(text);
112
113 // add the copied off data
114 if(templen){
115 SZ_Write(&cmd_text, temp, templen);
116 Z_Free(temp);
117 }
118 }
119
120
121 /*
122 Cbuf_CopyToDefer
123 */
Cbuf_CopyToDefer(void)124 void Cbuf_CopyToDefer(void){
125 memcpy(defer_text_buf, cmd_text_buf, cmd_text.cursize);
126 defer_text_buf[cmd_text.cursize] = 0;
127 cmd_text.cursize = 0;
128 }
129
130 /*
131 Cbuf_InsertFromDefer
132 */
Cbuf_InsertFromDefer(void)133 void Cbuf_InsertFromDefer(void){
134 Cbuf_InsertText(defer_text_buf);
135 defer_text_buf[0] = 0;
136 }
137
138
139 /*
140 Cbuf_Execute
141 */
Cbuf_Execute(void)142 void Cbuf_Execute(void){
143 int i;
144 char *text;
145 char line[MAX_STRING_CHARS];
146 int quotes;
147
148 alias_count = 0; // don't allow infinite alias loops
149
150 while(cmd_text.cursize){
151 // find a \n or; line break
152 text =(char *)cmd_text.data;
153
154 quotes = 0;
155 for(i = 0; i < cmd_text.cursize; i++){
156 if(text[i] == '"')
157 quotes++;
158 if(!(quotes&1) && text[i] == ';')
159 break; // don't break if inside a quoted string
160 if(text[i] == '\n')
161 break;
162 }
163
164 if(i > MAX_STRING_CHARS){ //length check each command
165 Com_Printf("Command exceeded %i chars, discarded\n", MAX_STRING_CHARS);
166 return;
167 }
168
169 memcpy(line, text, i);
170 line[i] = 0;
171
172 // delete the text from the command buffer and move remaining commands down
173 // this is necessary because commands(exec, alias) can insert data at the
174 // beginning of the text buffer
175
176 if(i == cmd_text.cursize)
177 cmd_text.cursize = 0;
178 else {
179 i++;
180 cmd_text.cursize -= i;
181 memmove(text, text + i, cmd_text.cursize);
182 }
183
184 // execute the command line
185 Cmd_ExecuteString(line);
186
187 if(cmd_wait){
188 // skip out while text still remains in buffer, leaving it
189 // for next frame
190 #ifdef BUILD_CLIENT
191 extern qboolean send_packet_now;
192 send_packet_now = true;
193 #endif
194 cmd_wait = false;
195 break;
196 }
197 }
198 }
199
200
201 /*
202 Cbuf_AddEarlyCommands
203
204 Adds command line parameters as script statements
205 Commands lead with a +, and continue until another +
206
207 Set commands are added early, so they are guaranteed to be set before
208 the client and server initialize for the first time.
209
210 Other commands are added late, after all initialization is complete.
211 */
Cbuf_AddEarlyCommands(qboolean clear)212 void Cbuf_AddEarlyCommands(qboolean clear){
213 int i;
214 char *s;
215
216 for(i = 0; i < COM_Argc(); i++){
217 s = COM_Argv(i);
218 if(strcmp(s, "+set"))
219 continue;
220 Cbuf_AddText(va("set %s %s\n", COM_Argv(i + 1), COM_Argv(i + 2)));
221 if(clear){
222 COM_ClearArgv(i);
223 COM_ClearArgv(i + 1);
224 COM_ClearArgv(i + 2);
225 }
226 i += 2;
227 }
228 }
229
230 /*
231 Cbuf_AddLateCommands
232
233 Adds command line parameters as script statements
234 Commands lead with a + and continue until another + or -
235 quake +vid_ref gl +map amlev1
236
237 Returns true if any late commands were added, which
238 will keep the demoloop from immediately starting
239 */
Cbuf_AddLateCommands(void)240 qboolean Cbuf_AddLateCommands(void){
241 int i, j;
242 int s;
243 char *text, *build, c;
244 int argc;
245 qboolean ret;
246
247 // build the combined string to parse from
248 s = 0;
249 argc = COM_Argc();
250 for(i = 1; i < argc; i++){
251 s += strlen(COM_Argv(i)) + 1;
252 }
253 if(!s)
254 return false;
255
256 text = Z_Malloc(s + 1);
257 text[0] = 0;
258 for(i = 1; i < argc; i++){
259 strcat(text, COM_Argv(i));
260 if(i != argc - 1)
261 strcat(text, " ");
262 }
263
264 // pull out the commands
265 build = Z_Malloc(s + 1);
266 build[0] = 0;
267
268 for(i = 0; i < s - 1; i++){
269 if(text[i] == '+'){
270 i++;
271
272 for(j = i;(text[j] != '+') &&(text[j] != '-') &&(text[j] != 0); j++)
273 ;
274
275 c = text[j];
276 text[j] = 0;
277
278 strcat(build, text + i);
279 strcat(build, "\n");
280 text[j] = c;
281 i = j - 1;
282 }
283 }
284
285 ret =(build[0] != 0);
286 if(ret)
287 Cbuf_AddText(build);
288
289 Z_Free(text);
290 Z_Free(build);
291
292 return ret;
293 }
294
295
296 /*
297
298 SCRIPT COMMANDS
299
300 */
301
302
303 /*
304 Cmd_Exec_f
305 */
Cmd_Exec_f(void)306 void Cmd_Exec_f(void){
307 char *f, *f2;
308 int len;
309
310 if(Cmd_Argc() != 2){
311 Com_Printf("exec <filename> : execute a script file\n");
312 return;
313 }
314
315 len = FS_LoadFile(Cmd_Argv(1),(void **)(char *) & f);
316 if(!f){
317 Com_Printf("couldn't exec %s\n", Cmd_Argv(1));
318 return;
319 }
320
321 // the file doesn't have a trailing 0, so we need to copy it off
322 f2 = Z_Malloc(len + 1);
323 memcpy(f2, f, len);
324 f2[len] = 0;
325
326 Cbuf_InsertText(f2);
327
328 Z_Free(f2);
329 FS_FreeFile(f);
330 }
331
332
333 /*
334 Cmd_Echo_f
335
336 Just prints the rest of the line to the console
337 */
Cmd_Echo_f(void)338 void Cmd_Echo_f(void){
339 int i;
340
341 for(i = 1; i < Cmd_Argc(); i++)
342 Com_Printf("%s ", Cmd_Argv(i));
343 Com_Printf("\n");
344 }
345
346 /*
347 Cmd_Alias_f
348
349 Creates a new command that executes a command string(possibly; seperated)
350 */
Cmd_Alias_f(void)351 void Cmd_Alias_f(void){
352 cmdalias_t *a;
353 char cmd[MAX_STRING_CHARS];
354 int i, c;
355 char *s;
356
357 if(Cmd_Argc() == 1){
358 Com_Printf("Current alias commands:\n");
359 for(a = cmd_alias; a; a = a->next)
360 Com_Printf("%s : %s\n", a->name, a->value);
361 return;
362 }
363
364 s = Cmd_Argv(1);
365 if(strlen(s) >= MAX_ALIAS_NAME){
366 Com_Printf("Alias name is too long\n");
367 return;
368 }
369
370 // if the alias already exists, reuse it
371 for(a = cmd_alias; a; a = a->next){
372 if(!strcmp(s, a->name)){
373 Z_Free(a->value);
374 break;
375 }
376 }
377
378 if(!a){
379 a = Z_Malloc(sizeof(cmdalias_t));
380 a->next = cmd_alias;
381 cmd_alias = a;
382 }
383 strcpy(a->name, s);
384
385 // copy the rest of the command line
386 cmd[0] = 0; // start out with a null string
387 c = Cmd_Argc();
388 for(i = 2; i < c; i++){
389 strcat(cmd, Cmd_Argv(i));
390 if(i !=(c - 1))
391 strcat(cmd, " ");
392 }
393 strcat(cmd, "\n");
394
395 a->value = CopyString(cmd);
396 }
397
398 /*
399
400 COMMAND EXECUTION
401
402 */
403
404 typedef struct cmd_function_s {
405 struct cmd_function_s *next;
406 char *name;
407 xcommand_t function;
408 } cmd_function_t;
409
410
411 static int cmd_argc;
412 static char *cmd_argv[MAX_STRING_TOKENS];
413 static char *cmd_null_string = "";
414 static char cmd_args[MAX_STRING_CHARS];
415
416 static cmd_function_t *cmd_functions; // possible commands to execute
417
418 /*
419 Cmd_Argc
420 */
Cmd_Argc(void)421 int Cmd_Argc(void){
422 return cmd_argc;
423 }
424
425 /*
426 Cmd_Argv
427 */
Cmd_Argv(int arg)428 char *Cmd_Argv(int arg){
429 if((unsigned)arg >= cmd_argc)
430 return cmd_null_string;
431 return cmd_argv[arg];
432 }
433
434 /*
435 Cmd_Args
436
437 Returns a single string containing argv(1) to argv(argc()-1)
438 */
Cmd_Args(void)439 char *Cmd_Args(void){
440 return cmd_args;
441 }
442
443
444 extern char *LOC_LocationHere(void);
445 extern char *LOC_LocationThere(void);
446
447 /*
448 Cmd_MacroExpandString
449 */
Cmd_MacroExpandString(char * text)450 char *Cmd_MacroExpandString(char *text){
451 int i, j, count, len;
452 qboolean inquote;
453 char *scan;
454 static char expanded[MAX_STRING_CHARS];
455 char temporary[MAX_STRING_CHARS];
456 char *token, *start;
457
458 inquote = false;
459 scan = text;
460
461 len = strlen(scan);
462 if(len >= MAX_STRING_CHARS){
463 Com_Printf("Line exceeded %i chars, discarded.\n", MAX_STRING_CHARS);
464 return NULL;
465 }
466
467 count = 0;
468
469 for(i = 0; i < len; i++){
470 if(scan[i] == '"')
471 inquote ^= 1;
472 if(inquote)
473 continue; // don't expand inside quotes
474 if(scan[i] != '$')
475 continue;
476 // scan out the complete macro
477 start = scan + i + 1;
478 token = COM_Parse(&start);
479 if(!start)
480 continue;
481
482 if(!strcmp(token, "loc_here")) // where am i?
483 token = LOC_LocationHere();
484 else if(!strcmp(token, "loc_there")) // what're you lookin at?
485 token = LOC_LocationThere();
486 else
487 token = Cvar_VariableString(token);
488
489 j = strlen(token);
490 len += j;
491 if(len >= MAX_STRING_CHARS){
492 Com_Printf("Expanded line exceeded %i chars, discarded.\n", MAX_STRING_CHARS);
493 return NULL;
494 }
495
496 strncpy(temporary, scan, i);
497 strcpy(temporary + i, token);
498 strcpy(temporary + i + j, start);
499
500 strcpy(expanded, temporary);
501 scan = expanded;
502 i--;
503
504 if(++count == 100){
505 Com_Printf("Macro expansion loop, discarded.\n");
506 return NULL;
507 }
508 }
509
510 if(inquote){
511 Com_Printf("Line has unmatched quote, discarded.\n");
512 return NULL;
513 }
514
515 return scan;
516 }
517
518
519 /*
520 Cmd_TokenizeString
521
522 Parses the given string into command line tokens.
523 $Cvars will be expanded unless they are in a quoted token
524 */
Cmd_TokenizeString(char * text,qboolean macroExpand)525 void Cmd_TokenizeString(char *text, qboolean macroExpand){
526 int i, l, cmd_pointer;
527 char *com_token;
528
529 // clear the args from the last string
530 for(i = 0; i < cmd_argc; i++)
531 Z_Free(cmd_argv[i]);
532
533 cmd_argc = 0;
534 cmd_args[0] = 0;
535 cmd_pointer = 0;
536
537 // macro expand the text
538 if(macroExpand && strchr(text, '$'))
539 text = Cmd_MacroExpandString(text);
540
541 if(!text)
542 return;
543
544 while(1){
545 // skip whitespace up to a \n
546 while(*text && *text <= ' ' && *text != '\n'){
547 text++;
548 }
549
550 if(*text == '\n'){ // a newline seperates commands in the buffer
551 text++;
552 break;
553 }
554
555 if(!*text)
556 return;
557
558 // set cmd_args to everything after the first arg
559 if(cmd_argc == 1){
560
561 strncpy(cmd_args, text, MAX_STRING_CHARS);
562
563 if((l = strlen(cmd_args) - 1) == MAX_STRING_CHARS - 1)
564 return; // catch maliciously long tokens
565
566 // strip off any trailing whitespace
567 for(; l >= 0; l--)
568 if(cmd_args[l] <= ' ')
569 cmd_args[l] = 0;
570 else
571 break;
572 }
573
574 com_token = COM_Parse(&text);
575 if(!text)
576 return;
577
578 if(cmd_argc < MAX_STRING_TOKENS){
579 if((l = strlen(com_token)) == MAX_STRING_CHARS)
580 return; // catch maliciously long tokens
581
582 cmd_argv[cmd_argc] = Z_Malloc(l + 1);
583 strcpy(cmd_argv[cmd_argc], com_token);
584 cmd_argc++;
585 }
586 }
587 }
588
589
590 /*
591 Cmd_AddCommand
592 */
Cmd_AddCommand(char * cmd_name,xcommand_t function)593 void Cmd_AddCommand(char *cmd_name, xcommand_t function){
594 cmd_function_t *cmd;
595
596 // fail if the command is a variable name
597 if(Cvar_VariableString(cmd_name)[0]){
598 Com_DPrintf("Cmd_AddCommand: %s already defined as a var\n", cmd_name);
599 return;
600 }
601
602 // fail if the command already exists
603 for(cmd = cmd_functions; cmd; cmd = cmd->next){
604 if(!strcmp(cmd_name, cmd->name)){
605 Com_DPrintf("Cmd_AddCommand: %s already defined\n", cmd_name);
606 return;
607 }
608 }
609
610 cmd = Z_Malloc(sizeof(cmd_function_t));
611 cmd->name = cmd_name;
612 cmd->function = function;
613 cmd->next = cmd_functions;
614 cmd_functions = cmd;
615 }
616
617 /*
618 Cmd_RemoveCommand
619 */
Cmd_RemoveCommand(char * cmd_name)620 void Cmd_RemoveCommand(char *cmd_name){
621 cmd_function_t *cmd, **back;
622
623 back = &cmd_functions;
624 while(1){
625 cmd = *back;
626 if(!cmd){
627 Com_DPrintf("Cmd_RemoveCommand: %s not added\n", cmd_name);
628 return;
629 }
630 if(!strcmp(cmd_name, cmd->name)){
631 *back = cmd->next;
632 Z_Free(cmd);
633 return;
634 }
635 back = &cmd->next;
636 }
637 }
638
639 /*
640 Cmd_Exists
641 */
Cmd_Exists(char * cmd_name)642 qboolean Cmd_Exists(char *cmd_name){
643 cmd_function_t *cmd;
644
645 for(cmd = cmd_functions; cmd; cmd = cmd->next){
646 if(!strcmp(cmd_name, cmd->name))
647 return true;
648 }
649
650 return false;
651 }
652
653
654
655 /*
656 Cmd_CompleteCommand
657 */
Cmd_CompleteCommand(char * partial,int * matches)658 char *Cmd_CompleteCommand(char *partial, int *matches){
659
660 cmd_function_t *cmd;
661 cmdalias_t *a;
662
663 char *match = NULL;
664 int len;
665
666 len = strlen(partial);
667 (*matches) = 0;
668
669 if(!len)
670 return NULL;
671
672 // check for exact match in commands
673 for(cmd = cmd_functions; cmd; cmd = cmd->next)
674 if(!strcmp(partial, cmd->name))
675 return cmd->name;
676
677 // and then aliases
678 for(a = cmd_alias; a; a = a->next)
679 if(!strcmp(partial, a->name))
680 return a->name;
681
682 // check for partial matches in commands
683 for(cmd = cmd_functions; cmd; cmd = cmd->next){
684 if(!strncmp(partial, cmd->name, len)){
685 Com_Printf("%c%s\n", (char)1, cmd->name);
686 match = cmd->name;
687 (*matches)++;
688 }
689 }
690
691 // and then aliases
692 for(a = cmd_alias; a; a = a->next){
693 if(!strncmp(partial, a->name, len)){
694 Com_Printf("%c%s\n", (char)1, a->name);
695 match = a->name;
696 (*matches)++;
697 }
698 }
699
700 return match;
701 }
702
703
704 /*
705 Cmd_ExecuteString
706
707 A complete command line has been parsed, so try to execute it
708 */
Cmd_ExecuteString(char * text)709 void Cmd_ExecuteString(char *text){
710 cmd_function_t *cmd;
711 cmdalias_t *a;
712
713 Cmd_TokenizeString(text, true);
714
715 // execute the command line
716 if(!Cmd_Argc())
717 return; // no tokens
718
719 // check functions
720 for(cmd = cmd_functions; cmd; cmd = cmd->next){
721 if(!Q_stricmp(cmd_argv[0], cmd->name)){
722 if(!cmd->function){ // forward to server command
723 Cmd_ExecuteString(va("cmd %s", text));
724 } else
725 cmd->function();
726 return;
727 }
728 }
729
730 // check alias
731 for(a = cmd_alias; a; a = a->next){
732 if(!Q_stricmp(cmd_argv[0], a->name)){
733 if(++alias_count == ALIAS_LOOP_COUNT){
734 Com_Printf("ALIAS_LOOP_COUNT\n");
735 return;
736 }
737 Cbuf_InsertText(a->value);
738 return;
739 }
740 }
741
742 // check cvars
743 if(Cvar_Command())
744 return;
745
746 // send it as a server command if we are connected
747 Cmd_ForwardToServer();
748 }
749
750 /*
751 Cmd_List_f
752 */
Cmd_List_f(void)753 void Cmd_List_f(void){
754 cmd_function_t *cmd;
755 int i;
756
757 i = 0;
758 for(cmd = cmd_functions; cmd; cmd = cmd->next, i++)
759 Com_Printf("%s\n", cmd->name);
760 Com_Printf("%i commands\n", i);
761 }
762
763 /*
764 Cmd_Init
765 */
Cmd_Init(void)766 void Cmd_Init(void){
767 //
768 // register our commands
769 //
770 Cmd_AddCommand("cmdlist", Cmd_List_f);
771 Cmd_AddCommand("exec", Cmd_Exec_f);
772 Cmd_AddCommand("echo", Cmd_Echo_f);
773 Cmd_AddCommand("alias", Cmd_Alias_f);
774 Cmd_AddCommand("wait", Cmd_Wait_f);
775 }
776
777