]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - cmd.c
rewrote Cmd_StuffCmds_f (commandline parser), it now properly handles quoted strings...
[xonotic/darkplaces.git] / cmd.c
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 #define MAX_ALIAS_NAME  32
25
26 typedef struct cmdalias_s
27 {
28         struct cmdalias_s *next;
29         char name[MAX_ALIAS_NAME];
30         char *value;
31 } cmdalias_t;
32
33 static cmdalias_t *cmd_alias;
34
35 static qboolean cmd_wait;
36
37 static mempool_t *cmd_mempool;
38
39 #define CMD_TOKENIZELENGTH 4096
40 static char cmd_tokenizebuffer[CMD_TOKENIZELENGTH];
41 static int cmd_tokenizebufferpos = 0;
42
43 //=============================================================================
44
45 /*
46 ============
47 Cmd_Wait_f
48
49 Causes execution of the remainder of the command buffer to be delayed until
50 next frame.  This allows commands like:
51 bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2"
52 ============
53 */
54 static void Cmd_Wait_f (void)
55 {
56         cmd_wait = true;
57 }
58
59 /*
60 =============================================================================
61
62                                                 COMMAND BUFFER
63
64 =============================================================================
65 */
66
67 static sizebuf_t        cmd_text;
68
69 /*
70 ============
71 Cbuf_Init
72 ============
73 */
74 void Cbuf_Init (void)
75 {
76         // LordHavoc: inreased this from 8192 to 32768
77         SZ_Alloc (&cmd_text, 32768, "command buffer"); // space for commands and script files
78 }
79
80
81 /*
82 ============
83 Cbuf_AddText
84
85 Adds command text at the end of the buffer
86 ============
87 */
88 void Cbuf_AddText (const char *text)
89 {
90         int             l;
91
92         l = strlen (text);
93
94         if (cmd_text.cursize + l >= cmd_text.maxsize)
95         {
96                 Con_Print("Cbuf_AddText: overflow\n");
97                 return;
98         }
99
100         SZ_Write (&cmd_text, text, strlen (text));
101 }
102
103
104 /*
105 ============
106 Cbuf_InsertText
107
108 Adds command text immediately after the current command
109 Adds a \n to the text
110 FIXME: actually change the command buffer to do less copying
111 ============
112 */
113 void Cbuf_InsertText (const char *text)
114 {
115         char    *temp;
116         int             templen;
117
118         // copy off any commands still remaining in the exec buffer
119         templen = cmd_text.cursize;
120         if (templen)
121         {
122                 temp = Mem_Alloc (tempmempool, templen);
123                 memcpy (temp, cmd_text.data, templen);
124                 SZ_Clear (&cmd_text);
125         }
126         else
127                 temp = NULL;
128
129         // add the entire text of the file
130         Cbuf_AddText (text);
131
132         // add the copied off data
133         if (temp != NULL)
134         {
135                 SZ_Write (&cmd_text, temp, templen);
136                 Mem_Free (temp);
137         }
138 }
139
140 /*
141 ============
142 Cbuf_Execute
143 ============
144 */
145 void Cbuf_Execute (void)
146 {
147         int i;
148         char *text;
149         char line[1024];
150         int quotes;
151
152         // LordHavoc: making sure the tokenizebuffer doesn't get filled up by repeated crashes
153         cmd_tokenizebufferpos = 0;
154
155         while (cmd_text.cursize)
156         {
157 // find a \n or ; line break
158                 text = (char *)cmd_text.data;
159
160                 quotes = 0;
161                 for (i=0 ; i< cmd_text.cursize ; i++)
162                 {
163                         if (text[i] == '"')
164                                 quotes++;
165                         if ( !(quotes&1) &&  text[i] == ';')
166                                 break;  // don't break if inside a quoted string
167                         if (text[i] == '\r' || text[i] == '\n')
168                                 break;
169                 }
170
171                 memcpy (line, text, i);
172                 line[i] = 0;
173
174 // delete the text from the command buffer and move remaining commands down
175 // this is necessary because commands (exec, alias) can insert data at the
176 // beginning of the text buffer
177
178                 if (i == cmd_text.cursize)
179                         cmd_text.cursize = 0;
180                 else
181                 {
182                         i++;
183                         cmd_text.cursize -= i;
184                         memcpy (cmd_text.data, text+i, cmd_text.cursize);
185                 }
186
187 // execute the command line
188                 Cmd_ExecuteString (line, src_command);
189
190                 if (cmd_wait)
191                 {       // skip out while text still remains in buffer, leaving it
192                         // for next frame
193                         cmd_wait = false;
194                         break;
195                 }
196         }
197 }
198
199 /*
200 ==============================================================================
201
202                                                 SCRIPT COMMANDS
203
204 ==============================================================================
205 */
206
207 /*
208 ===============
209 Cmd_StuffCmds_f
210
211 Adds command line parameters as script statements
212 Commands lead with a +, and continue until a - or another +
213 quake +prog jctest.qp +cmd amlev1
214 quake -nosound +cmd amlev1
215 ===============
216 */
217 void Cmd_StuffCmds_f (void)
218 {
219         int             i, j, l;
220         // this is per command, and bounds checked (no buffer overflows)
221         char    build[2048];
222
223         if (Cmd_Argc () != 1)
224         {
225                 Con_Print("stuffcmds : execute command line parameters\n");
226                 return;
227         }
228
229         for (i = 0;i < com_argc;i++)
230         {
231                 if (com_argv[i] && com_argv[i][0] == '+' && (com_argv[i][1] < '0' || com_argv[i][1] > '9'))
232                 {
233                         l = 0;
234                         j = 1;
235                         while (com_argv[i][j])
236                                 build[l++] = com_argv[i][j++];
237                         i++;
238                         for (;i < com_argc;i++)
239                         {
240                                 if (!com_argv[i])
241                                         continue;
242                                 if ((com_argv[i][0] == '+' || com_argv[i][0] == '-') && (com_argv[i][1] < '0' || com_argv[i][1] > '9'))
243                                         break;
244                                 if (l + strlen(com_argv[i]) + 5 > sizeof(build))
245                                         break;
246                                 build[l++] = ' ';
247                                 build[l++] = '\"';
248                                 for (j = 0;com_argv[i][j];j++)
249                                         build[l++] = com_argv[i][j];
250                                 build[l++] = '\"';
251                         }
252                         build[l++] = '\n';
253                         build[l++] = 0;
254                         Cbuf_InsertText (build);
255                 }
256         }
257 }
258
259
260 /*
261 ===============
262 Cmd_Exec_f
263 ===============
264 */
265 static void Cmd_Exec_f (void)
266 {
267         char *f;
268
269         if (Cmd_Argc () != 2)
270         {
271                 Con_Print("exec <filename> : execute a script file\n");
272                 return;
273         }
274
275         f = (char *)FS_LoadFile (Cmd_Argv(1), tempmempool, false);
276         if (!f)
277         {
278                 Con_Printf("couldn't exec %s\n",Cmd_Argv(1));
279                 return;
280         }
281         Con_DPrintf("execing %s\n",Cmd_Argv(1));
282
283         Cbuf_InsertText (f);
284         Mem_Free(f);
285 }
286
287
288 /*
289 ===============
290 Cmd_Echo_f
291
292 Just prints the rest of the line to the console
293 ===============
294 */
295 static void Cmd_Echo_f (void)
296 {
297         int             i;
298
299         for (i=1 ; i<Cmd_Argc() ; i++)
300                 Con_Printf("%s ",Cmd_Argv(i));
301         Con_Print("\n");
302 }
303
304 /*
305 ===============
306 Cmd_Alias_f
307
308 Creates a new command that executes a command string (possibly ; seperated)
309 ===============
310 */
311 static void Cmd_Alias_f (void)
312 {
313         cmdalias_t      *a;
314         char            cmd[1024];
315         int                     i, c;
316         const char              *s;
317
318         if (Cmd_Argc() == 1)
319         {
320                 Con_Print("Current alias commands:\n");
321                 for (a = cmd_alias ; a ; a=a->next)
322                         Con_Printf("%s : %s\n", a->name, a->value);
323                 return;
324         }
325
326         s = Cmd_Argv(1);
327         if (strlen(s) >= MAX_ALIAS_NAME)
328         {
329                 Con_Print("Alias name is too long\n");
330                 return;
331         }
332
333         // if the alias already exists, reuse it
334         for (a = cmd_alias ; a ; a=a->next)
335         {
336                 if (!strcmp(s, a->name))
337                 {
338                         Z_Free (a->value);
339                         break;
340                 }
341         }
342
343         if (!a)
344         {
345                 a = Z_Malloc (sizeof(cmdalias_t));
346                 a->next = cmd_alias;
347                 cmd_alias = a;
348         }
349         strlcpy (a->name, s, sizeof (a->name));
350
351 // copy the rest of the command line
352         cmd[0] = 0;             // start out with a null string
353         c = Cmd_Argc();
354         for (i=2 ; i< c ; i++)
355         {
356                 strlcat (cmd, Cmd_Argv(i), sizeof (cmd));
357                 if (i != c)
358                         strlcat (cmd, " ", sizeof (cmd));
359         }
360         strlcat (cmd, "\n", sizeof (cmd));
361
362         a->value = Z_Malloc (strlen (cmd) + 1);
363         strcpy (a->value, cmd);
364 }
365
366 /*
367 =============================================================================
368
369                                         COMMAND EXECUTION
370
371 =============================================================================
372 */
373
374 typedef struct cmd_function_s
375 {
376         struct cmd_function_s *next;
377         const char *name;
378         xcommand_t function;
379 } cmd_function_t;
380
381
382 #define MAX_ARGS                80
383
384 static int cmd_argc;
385 static const char *cmd_argv[MAX_ARGS];
386 static const char *cmd_null_string = "";
387 static const char *cmd_args = NULL;
388
389 cmd_source_t cmd_source;
390
391
392 static cmd_function_t *cmd_functions;           // possible commands to execute
393
394 /*
395 ========
396 Cmd_List
397
398         CmdList Added by EvilTypeGuy eviltypeguy@qeradiant.com
399         Thanks to Matthias "Maddes" Buecher, http://www.inside3d.com/qip/
400
401 ========
402 */
403 static void Cmd_List_f (void)
404 {
405         cmd_function_t *cmd;
406         const char *partial;
407         int len, count;
408
409         if (Cmd_Argc() > 1)
410         {
411                 partial = Cmd_Argv (1);
412                 len = strlen(partial);
413         }
414         else
415         {
416                 partial = NULL;
417                 len = 0;
418         }
419
420         count = 0;
421         for (cmd = cmd_functions; cmd; cmd = cmd->next)
422         {
423                 if (partial && strncmp(partial, cmd->name, len))
424                         continue;
425                 Con_Printf("%s\n", cmd->name);
426                 count++;
427         }
428
429         Con_Printf("%i Command%s", count, (count > 1) ? "s" : "");
430         if (partial)
431                 Con_Printf(" beginning with \"%s\"", partial);
432
433         Con_Print("\n\n");
434 }
435
436 /*
437 ============
438 Cmd_Init
439 ============
440 */
441 void Cmd_Init (void)
442 {
443         cmd_mempool = Mem_AllocPool("commands", 0, NULL);
444
445 //
446 // register our commands
447 //
448         Cmd_AddCommand ("stuffcmds",Cmd_StuffCmds_f);
449         Cmd_AddCommand ("exec",Cmd_Exec_f);
450         Cmd_AddCommand ("echo",Cmd_Echo_f);
451         Cmd_AddCommand ("alias",Cmd_Alias_f);
452         Cmd_AddCommand ("cmd", Cmd_ForwardToServer);
453         Cmd_AddCommand ("wait", Cmd_Wait_f);
454         Cmd_AddCommand ("cmdlist", Cmd_List_f);         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
455         Cmd_AddCommand ("cvarlist", Cvar_List_f);       // 2000-01-09 CmdList, CvarList commands
456                                                                                                 // By Matthias "Maddes" Buecher
457 }
458
459 /*
460 ============
461 Cmd_Argc
462 ============
463 */
464 int             Cmd_Argc (void)
465 {
466         return cmd_argc;
467 }
468
469 /*
470 ============
471 Cmd_Argv
472 ============
473 */
474 const char *Cmd_Argv (int arg)
475 {
476         if (arg >= cmd_argc )
477                 return cmd_null_string;
478         return cmd_argv[arg];
479 }
480
481 /*
482 ============
483 Cmd_Args
484 ============
485 */
486 const char *Cmd_Args (void)
487 {
488         return cmd_args;
489 }
490
491
492 /*
493 ============
494 Cmd_TokenizeString
495
496 Parses the given string into command line tokens.
497 ============
498 */
499 static void Cmd_TokenizeString (const char *text)
500 {
501         int l;
502
503         cmd_argc = 0;
504         cmd_args = NULL;
505
506         while (1)
507         {
508                 // skip whitespace up to a /n
509                 while (*text && *text <= ' ' && *text != '\n')
510                         text++;
511
512                 if (*text == '\n')
513                 {
514                         // a newline seperates commands in the buffer
515                         text++;
516                         break;
517                 }
518
519                 if (!*text)
520                         return;
521
522                 if (cmd_argc == 1)
523                          cmd_args = text;
524
525                 if (!COM_ParseTokenConsole(&text))
526                         return;
527
528                 if (cmd_argc < MAX_ARGS)
529                 {
530                         l = strlen(com_token) + 1;
531                         if (cmd_tokenizebufferpos + l > CMD_TOKENIZELENGTH)
532                                 Sys_Error("Cmd_TokenizeString: ran out of %i character buffer space for command arguements\n", CMD_TOKENIZELENGTH);
533                         strcpy (cmd_tokenizebuffer + cmd_tokenizebufferpos, com_token);
534                         cmd_argv[cmd_argc] = cmd_tokenizebuffer + cmd_tokenizebufferpos;
535                         cmd_tokenizebufferpos += l;
536                         cmd_argc++;
537                 }
538         }
539
540 }
541
542
543 /*
544 ============
545 Cmd_AddCommand
546 ============
547 */
548 void Cmd_AddCommand (const char *cmd_name, xcommand_t function)
549 {
550         cmd_function_t *cmd;
551
552 // fail if the command is a variable name
553         if (Cvar_VariableString(cmd_name)[0])
554         {
555                 Con_Printf("Cmd_AddCommand: %s already defined as a var\n", cmd_name);
556                 return;
557         }
558
559 // fail if the command already exists
560         for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
561         {
562                 if (!strcmp (cmd_name, cmd->name))
563                 {
564                         Con_Printf("Cmd_AddCommand: %s already defined\n", cmd_name);
565                         return;
566                 }
567         }
568
569         cmd = Mem_Alloc(cmd_mempool, sizeof(cmd_function_t));
570         cmd->name = cmd_name;
571         cmd->function = function;
572         cmd->next = cmd_functions;
573         cmd_functions = cmd;
574 }
575
576 /*
577 ============
578 Cmd_Exists
579 ============
580 */
581 qboolean Cmd_Exists (const char *cmd_name)
582 {
583         cmd_function_t  *cmd;
584
585         for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
586                 if (!strcmp (cmd_name,cmd->name))
587                         return true;
588
589         return false;
590 }
591
592
593 /*
594 ============
595 Cmd_CompleteCommand
596 ============
597 */
598 const char *Cmd_CompleteCommand (const char *partial)
599 {
600         cmd_function_t *cmd;
601         int len;
602
603         len = strlen(partial);
604
605         if (!len)
606                 return NULL;
607
608 // check functions
609         for (cmd = cmd_functions; cmd; cmd = cmd->next)
610                 if (!strncmp(partial, cmd->name, len))
611                         return cmd->name;
612
613         return NULL;
614 }
615
616 /*
617         Cmd_CompleteCountPossible
618
619         New function for tab-completion system
620         Added by EvilTypeGuy
621         Thanks to Fett erich@heintz.com
622         Thanks to taniwha
623
624 */
625 int Cmd_CompleteCountPossible (const char *partial)
626 {
627         cmd_function_t *cmd;
628         int len, h;
629
630         h = 0;
631         len = strlen(partial);
632
633         if (!len)
634                 return 0;
635
636         // Loop through the command list and count all partial matches
637         for (cmd = cmd_functions; cmd; cmd = cmd->next)
638                 if (!strncasecmp(partial, cmd->name, len))
639                         h++;
640
641         return h;
642 }
643
644 /*
645         Cmd_CompleteBuildList
646
647         New function for tab-completion system
648         Added by EvilTypeGuy
649         Thanks to Fett erich@heintz.com
650         Thanks to taniwha
651
652 */
653 const char **Cmd_CompleteBuildList (const char *partial)
654 {
655         cmd_function_t *cmd;
656         int len = 0;
657         int bpos = 0;
658         int sizeofbuf = (Cmd_CompleteCountPossible (partial) + 1) * sizeof (const char *);
659         const char **buf;
660
661         len = strlen(partial);
662         buf = Mem_Alloc(tempmempool, sizeofbuf + sizeof (const char *));
663         // Loop through the alias list and print all matches
664         for (cmd = cmd_functions; cmd; cmd = cmd->next)
665                 if (!strncasecmp(partial, cmd->name, len))
666                         buf[bpos++] = cmd->name;
667
668         buf[bpos] = NULL;
669         return buf;
670 }
671
672 /*
673         Cmd_CompleteAlias
674
675         New function for tab-completion system
676         Added by EvilTypeGuy
677         Thanks to Fett erich@heintz.com
678         Thanks to taniwha
679
680 */
681 const char *Cmd_CompleteAlias (const char *partial)
682 {
683         cmdalias_t *alias;
684         int len;
685
686         len = strlen(partial);
687
688         if (!len)
689                 return NULL;
690
691         // Check functions
692         for (alias = cmd_alias; alias; alias = alias->next)
693                 if (!strncasecmp(partial, alias->name, len))
694                         return alias->name;
695
696         return NULL;
697 }
698
699 /*
700         Cmd_CompleteAliasCountPossible
701
702         New function for tab-completion system
703         Added by EvilTypeGuy
704         Thanks to Fett erich@heintz.com
705         Thanks to taniwha
706
707 */
708 int Cmd_CompleteAliasCountPossible (const char *partial)
709 {
710         cmdalias_t      *alias;
711         int                     len;
712         int                     h;
713
714         h = 0;
715
716         len = strlen(partial);
717
718         if (!len)
719                 return 0;
720
721         // Loop through the command list and count all partial matches
722         for (alias = cmd_alias; alias; alias = alias->next)
723                 if (!strncasecmp(partial, alias->name, len))
724                         h++;
725
726         return h;
727 }
728
729 /*
730         Cmd_CompleteAliasBuildList
731
732         New function for tab-completion system
733         Added by EvilTypeGuy
734         Thanks to Fett erich@heintz.com
735         Thanks to taniwha
736
737 */
738 const char **Cmd_CompleteAliasBuildList (const char *partial)
739 {
740         cmdalias_t *alias;
741         int len = 0;
742         int bpos = 0;
743         int sizeofbuf = (Cmd_CompleteAliasCountPossible (partial) + 1) * sizeof (const char *);
744         const char **buf;
745
746         len = strlen(partial);
747         buf = Mem_Alloc(tempmempool, sizeofbuf + sizeof (const char *));
748         // Loop through the alias list and print all matches
749         for (alias = cmd_alias; alias; alias = alias->next)
750                 if (!strncasecmp(partial, alias->name, len))
751                         buf[bpos++] = alias->name;
752
753         buf[bpos] = NULL;
754         return buf;
755 }
756
757 /*
758 ============
759 Cmd_ExecuteString
760
761 A complete command line has been parsed, so try to execute it
762 FIXME: lookupnoadd the token to speed search?
763 ============
764 */
765 void Cmd_ExecuteString (const char *text, cmd_source_t src)
766 {
767         int oldpos;
768         cmd_function_t *cmd;
769         cmdalias_t *a;
770
771         oldpos = cmd_tokenizebufferpos;
772         cmd_source = src;
773         Cmd_TokenizeString (text);
774
775 // execute the command line
776         if (!Cmd_Argc())
777         {
778                 cmd_tokenizebufferpos = oldpos;
779                 return;         // no tokens
780         }
781
782 // check functions (only after host_initialized)
783         if (host_initialized || !strcasecmp(cmd_argv[0], "exec"))
784         {
785                 for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
786                 {
787                         if (!strcasecmp (cmd_argv[0],cmd->name))
788                         {
789                                 cmd->function ();
790                                 cmd_tokenizebufferpos = oldpos;
791                                 return;
792                         }
793                 }
794         }
795
796 // check alias (only after host_initialized)
797         if (host_initialized)
798         {
799                 for (a=cmd_alias ; a ; a=a->next)
800                 {
801                         if (!strcasecmp (cmd_argv[0], a->name))
802                         {
803                                 Cbuf_InsertText (a->value);
804                                 cmd_tokenizebufferpos = oldpos;
805                                 return;
806                         }
807                 }
808         }
809
810 // check cvars (always)
811         if (!Cvar_Command () && host_initialized)
812                 Con_Printf("Unknown command \"%s\"\n", Cmd_Argv(0));
813
814         cmd_tokenizebufferpos = oldpos;
815 }
816
817
818 /*
819 ===================
820 Cmd_ForwardToServer
821
822 Sends the entire command line over to the server
823 ===================
824 */
825 void Cmd_ForwardToServer (void)
826 {
827         const char *s;
828         if (cls.state != ca_connected)
829         {
830                 Con_Printf("Can't \"%s\", not connected\n", Cmd_Argv(0));
831                 return;
832         }
833
834         if (cls.demoplayback)
835                 return;         // not really connected
836
837         // LordHavoc: thanks to Fuh for bringing the pure evil of SZ_Print to my
838         // attention, it has been eradicated from here, its only (former) use in
839         // all of darkplaces.
840         if (strcasecmp(Cmd_Argv(0), "cmd") != 0)
841                 s = va("%s %s", Cmd_Argv(0), Cmd_Argc() > 1 ? Cmd_Args() : "\n");
842         else
843                 s = Cmd_Argc() > 1 ? Cmd_Args() : "\n";
844         MSG_WriteByte(&cls.message, clc_stringcmd);
845         SZ_Write(&cls.message, s, strlen(s) + 1);
846 }
847
848
849 /*
850 ================
851 Cmd_CheckParm
852
853 Returns the position (1 to argc-1) in the command's argument list
854 where the given parameter apears, or 0 if not present
855 ================
856 */
857
858 int Cmd_CheckParm (const char *parm)
859 {
860         int i;
861
862         if (!parm)
863                 Sys_Error ("Cmd_CheckParm: NULL");
864
865         for (i = 1; i < Cmd_Argc (); i++)
866                 if (!strcasecmp (parm, Cmd_Argv (i)))
867                         return i;
868
869         return 0;
870 }
871