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