1 // Emacs style mode select   -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id: command.c 1542 2020-08-22 02:35:24Z wesleyjohnson $
5 //
6 // Copyright (C) 1998-2016 by DooM Legacy Team.
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License
10 // as published by the Free Software Foundation; either version 2
11 // of the License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 // GNU General Public License for more details.
17 //
18 //
19 // $Log: command.c,v $
20 // Revision 1.15  2005/05/21 08:41:23  iori_
21 // May 19, 2005 - PlayerArmor FS function;  1.43 can be compiled again.
22 //
23 // Revision 1.14  2004/08/26 23:15:45  hurdler
24 // add FS functions in console (+ minor linux fixes)
25 //
26 // Revision 1.13  2003/05/30 22:44:08  hurdler
27 // add checkcvar function to FS
28 //
29 // Revision 1.12  2001/12/27 22:50:25  hurdler
30 // fix a colormap bug, add scrolling floor/ceiling in hw mode
31 //
32 // Revision 1.11  2001/02/24 13:35:19  bpereira
33 //
34 // Revision 1.10  2001/01/25 22:15:41  bpereira
35 // added heretic support
36 //
37 // Revision 1.9  2000/11/11 13:59:45  bpereira
38 // Revision 1.8  2000/11/02 19:49:35  bpereira
39 // Revision 1.7  2000/10/08 13:29:59  bpereira
40 // Revision 1.6  2000/09/28 20:57:14  bpereira
41 // Revision 1.5  2000/08/31 14:30:54  bpereira
42 // Revision 1.4  2000/08/03 17:57:41  bpereira
43 // Revision 1.3  2000/02/27 00:42:10  hurdler
44 // Revision 1.2  2000/02/26 00:28:42  hurdler
45 // Mostly bug fix (see borislog.txt 23-2-2000, 24-2-2000)
46 //
47 //
48 // DESCRIPTION:
49 //      parse and execute commands from console input/scripts/
50 //      and remote server.
51 //
52 //      handles console variables, which is a simplified version
53 //      of commands, each consvar can have a function called when
54 //      it is modified.. thus it acts nearly as commands.
55 //
56 //      code shamelessly inspired by the QuakeC sources, thanks Id :)
57 //
58 //-----------------------------------------------------------------------------
59 
60 
61 #include "doomincl.h"
62 #include "doomstat.h"
63 #include "command.h"
64 #include "console.h"
65 #include "z_zone.h"
66 #include "d_clisrv.h"
67 #include "d_netcmd.h"
68 #include "m_misc.h"
69 #include "m_fixed.h"
70 #include "byteptr.h"
71 #include "p_saveg.h"
72 
73 // Hurdler: add FS functionnality to console command
74 #include "t_vari.h"
75 //void run_string(char *data);
76 
77 //========
78 // protos.
79 //========
80 static boolean COM_Exists (const char * com_name);
81 static void    COM_ExecuteString (const char * text, boolean script, byte cfg);
82 
83 static void    COM_Alias_f (void);
84 static void    COM_Echo_f (void);
85 static void    COM_Exec_f (void);
86 static void    COM_Wait_f (void);
87 static void    COM_Help_f (void);
88 static void    COM_Toggle_f (void);
89 
90 static boolean CV_Var_Command ( byte cfg );
91 static char *  CV_StringValue (const char * var_name);
92 static consvar_t  *consvar_vars;       // list of registered console variables
93 
94 #define COM_TOKEN_MAX   1024
95 static char    com_token[COM_TOKEN_MAX];
96 static const char *  COM_Parse (const char * data, boolean script);
97 
98 CV_PossibleValue_t CV_OnOff[] =    {{0,"Off"}, {1,"On"},    {0,NULL}};
99 CV_PossibleValue_t CV_YesNo[] =     {{0,"No"} , {1,"Yes"},   {0,NULL}};
100 CV_PossibleValue_t CV_uint16[]=   {{0,"MIN"}, {0xFFFF,"MAX"}, {0,NULL}};
101 CV_PossibleValue_t CV_Unsigned[]=   {{0,"MIN"}, {999999999,"MAX"}, {0,NULL}};
102 CV_PossibleValue_t CV_byte[]=   {{0,"MIN"}, {255,"MAX"}, {0,NULL}};
103 
104 #define COM_BUF_SIZE    8192   // command buffer size
105 
106 static int com_wait;       // one command per frame (for cmd sequences)
107 
108 
109 // command aliases
110 //
111 typedef struct cmdalias_s
112 {
113     struct cmdalias_s   *next;
114     char    *name;
115     char    *value;     // the command string to replace the alias
116 } cmd_alias_t;
117 
118 static cmd_alias_t *com_alias; // aliases list
119 
120 
121 // =========================================================================
122 //                            COMMAND BUFFER
123 // =========================================================================
124 
125 
126 static vsbuf_t com_text;     // variable sized buffer
127 
128 
129 //  Add text (a NUL-terminated string) in the command buffer (for later execution)
130 //
COM_BufAddText(const char * text)131 void COM_BufAddText (const char *text)
132 {
133   if (!VS_Print(&com_text, text))
134     CONS_Printf ("Command buffer full!\n");
135 }
136 
137 
138 // Adds command text immediately after the current command
139 // Adds a \n to the text
140 //
COM_BufInsertText(const char * text)141 void COM_BufInsertText (const char *text)
142 {
143     char    *temp;
144 
145     // copy off any commands still remaining in the exec buffer
146     int templen = com_text.cursize;
147     if (templen)
148     {
149       // add a trailing NUL (TODO why do we even allow non-string data in a vsbuf_t?)
150       temp = Z_Malloc (templen + 1, PU_STATIC, NULL);
151       temp[templen] = '\0';
152       memcpy (temp, com_text.data, templen);
153       VS_Clear (&com_text);
154     }
155     else
156         temp = NULL;    // shut up compiler
157 
158     // add the entire text of the file (or alias)
159     COM_BufAddText (text);
160 
161     // add the copied off data
162     if (templen)
163     {
164       if (!VS_Print(&com_text, temp))
165         CONS_Printf ("Command buffer full!!\n");
166 
167       Z_Free (temp);
168     }
169 }
170 
171 
172 //  Flush (execute) console commands in buffer.
173 //
174 //   cfg : cv_config_e, to mark the cvar as to the source
175 //
176 //  Global: com_wait : does nothing until com_wait ticks down
177 //
178 // Called by: TryRunTics( CFG_none )
179 // Called by: P_Load_LevelInfo( CFG_none )
180 // Called by: M_LoadConfig( cfg )
181 // Was Called by: M_Choose_to_quit_Response, but that is disabled now.
COM_BufExecute(byte cfg)182 void COM_BufExecute( byte cfg )
183 {
184   int     i;
185   boolean script = 1;
186   char line[1024];
187 
188   if (com_wait)
189   {
190         com_wait--;
191         return;
192   }
193 
194   while (com_text.cursize)
195   {
196       // find a '\n' or ; line break
197       char *text = (char *)com_text.data;
198       boolean in_quote = false;
199 
200       // This is called without a clue as to what commands are present.
201       // Scripts have quoted strings,
202       // exec have quoted filenames with backslash: exec "c:\doomdir\".
203       // The while loop continues into the exec which can have two levels
204       // of quoted strings:
205       //      alias zoom_in "fov 15; bind \"mouse 3\" zoom_out"
206       script =
207         ( ( strncmp(text,"exec",4) == 0 )
208           ||( strncmp(text,"map",3) == 0 )
209           ||( strncmp(text,"playdemo",8) == 0 )
210           ||( strncmp(text,"addfile",7) == 0 )
211           ||( strncmp(text,"loadconfig",10) == 0 )
212           ||( strncmp(text,"saveconfig",10) == 0 )
213           ) ? 0 : 1;  // has filename : is script with quoted strings
214 
215       for (i=0; i < com_text.cursize; i++)
216       {
217         register char ch = text[i];
218         if (ch == '"') // non-escaped quote
219           in_quote = !in_quote;
220         else if( in_quote )
221         {
222           if (script && (ch == '\\')) // escape sequence
223           {
224 #if 1
225               // [WDJ] Only doublequote and backslash really matter
226               i += 1;  // skip it, because other parser does too
227               continue;
228 #else
229               switch (text[i+1])
230               {
231                 case '\\': // backslash
232                 case '"':  // double quote
233                 case 't':  // tab
234                 case 'n':  // newline
235                   i += 1;  // skip it
236                   break;
237 
238                 default:
239                   // unknown sequence, parser will give an error later on.
240                   break;
241               }
242               continue;
243 #endif
244           }
245         }
246         else
247         { // not in quoted string
248           if (ch == ';') // semicolon separates commands
249             break;
250           if (ch == '\n' || ch == '\r') // always separate commands
251             break;
252         }
253       }
254 
255 
256       if( i > 1023 )  i = 1023;  // overrun of line
257       memcpy (line, text, i);
258       line[i] = 0;
259 
260       // flush the command text from the command buffer, _BEFORE_
261       // executing, to avoid that 'recursive' aliases overflow the
262       // command text buffer, in that case, new commands are inserted
263       // at the beginning, in place of the actual, so it doesn't
264       // overflow
265       if (i == com_text.cursize)
266       {
267             // the last command was just flushed
268             com_text.cursize = 0;
269       }
270       else
271       {
272             i++;
273             com_text.cursize -= i;
274             // Shuffle text, overlap copy.  Bug fix by Ryan bug_0626.
275             memmove(text, text+i, com_text.cursize);
276       }
277 
278       // execute the command line, marking the cvar values as to the source
279       COM_ExecuteString( line, script, cfg );
280 
281       // delay following commands if a wait was encountered
282       if (com_wait)
283       {
284             com_wait--;
285             break;
286       }
287   }
288 }
289 
290 
291 // =========================================================================
292 //                            COMMAND EXECUTION
293 // =========================================================================
294 
295 typedef struct xcommand_s
296 {
297     const char       * name;
298     struct xcommand_s * next;
299     com_func_t         function;
300     byte    cctype; // classification for help
301 } xcommand_t;
302 
303 static  xcommand_t  *com_commands = NULL;     // current commands
304 
305 
306 #define MAX_ARGS        80
307 static int         com_argc;
308 static char        *com_argv[MAX_ARGS];
309 static char        *com_null_string = "";
310 static const char * com_args = NULL;          // current command args or NULL
311 
312 
313 //  Initialize command buffer and add basic commands
314 //
315 // Called only once
COM_Init(void)316 void COM_Init (void)
317 {
318     int i;
319     for( i=0; i<MAX_ARGS; i++ )  com_argv[i] = com_null_string;
320     com_argc = 0;
321 
322     // allocate command buffer
323     VS_Alloc (&com_text, COM_BUF_SIZE);
324 
325     // add standard commands
326     COM_AddCommand ("alias",COM_Alias_f, CC_command);
327     COM_AddCommand ("echo", COM_Echo_f, CC_command);
328     COM_AddCommand ("exec", COM_Exec_f, CC_command);
329     COM_AddCommand ("wait", COM_Wait_f, CC_command);
330     COM_AddCommand ("toggle", COM_Toggle_f, CC_command);
331     COM_AddCommand ("help", COM_Help_f, CC_info);
332     Register_NetXCmd(XD_NETVAR, Got_NetXCmd_NetVar);
333 }
334 
335 
336 // Returns how many args for last command
337 //
COM_Argc(void)338 int COM_Argc (void)
339 {
340     return com_argc;
341 }
342 
343 
344 // Returns string pointer for given argument number
345 //
COM_Argv(int arg)346 char * COM_Argv (int arg)
347 {
348     if ( arg >= com_argc || arg < 0 )
349         return com_null_string;
350     return com_argv[arg];
351 }
352 
353 // get some args
354 // More efficient, but preserves read only interface
COM_Args(COM_args_t * comargs)355 void  COM_Args( COM_args_t * comargs )
356 {
357     int i;
358     comargs->num = com_argc;
359     for( i=0; i<4; i++ )
360     {
361         comargs->arg[i] = com_argv[i];
362     }
363 }
364 
365 #if 0
366 // [WDJ] Unused
367 // Returns string pointer of all command args
368 //
369 char *COM_Args (void)
370 {
371     return com_args;
372 }
373 #endif
374 
375 
COM_CheckParm(const char * check)376 int COM_CheckParm (const char *check)
377 {
378     int         i;
379 
380     for (i = 1; i<com_argc; i++)
381     {
382         if( strcasecmp(check, com_argv[i]) == 0 )
383             return i;
384     }
385     return 0;
386 }
387 
388 
389 // Parses the given string into command line tokens.
390 //
391 // Takes a null terminated string.  Does not need to be /n terminated.
392 // breaks the string up into arg tokens.
COM_TokenizeString(const char * text,boolean script)393 static void COM_TokenizeString (const char * text, boolean script)
394 {
395     int  i;
396 
397 // clear the args from the last string
398     for (i=0 ; i<com_argc ; i++)
399     {
400         Z_Free (com_argv[i]);
401         com_argv[i] = com_null_string;  // never leave behind ptrs to old mem
402     }
403 
404     com_argc = 0;
405     com_args = NULL;
406 
407     while (1)
408     {
409 // skip whitespace up to a /n
410         while (*text && *text <= ' ' && *text != '\n')
411             text++;
412 
413         if (*text == '\n')
414         {   // a newline means end of command in buffer,
415             // thus end of this command's args too
416             text++;
417             break;
418         }
419 
420         if (!*text)
421             return;
422 
423         if (com_argc == 1)
424             com_args = text;
425 
426         text = COM_Parse (text, script);
427         if (!text)
428             return;
429 
430         if (com_argc < MAX_ARGS)
431         {
432             com_argv[com_argc] = Z_Malloc (strlen(com_token)+1, PU_STATIC, NULL);
433             strcpy (com_argv[com_argc], com_token);
434             com_argc++;
435         }
436     }
437 
438 }
439 
440 
441 // Add a command before existing ones.
442 //
COM_AddCommand(const char * name,com_func_t func,byte command_type)443 void COM_AddCommand( const char *name, com_func_t func, byte command_type )
444 {
445     xcommand_t * cmd;
446 
447     // fail if the command is a variable name
448     if (CV_StringValue(name)[0])
449     {
450         CONS_Printf ("%s is a variable name\n", name);
451         return;
452     }
453 
454     // fail if the command already exists
455     for (cmd=com_commands ; cmd ; cmd=cmd->next)
456     {
457         if (!strcmp (name, cmd->name))
458         {
459             CONS_Printf ("Command %s already exists\n", name);
460             return;
461         }
462     }
463 
464     cmd = Z_Malloc (sizeof(xcommand_t), PU_STATIC, NULL);
465     cmd->name = name;
466     cmd->function = func;
467     cmd->cctype = command_type;
468     cmd->next = com_commands;
469     com_commands = cmd;
470 }
471 
472 
473 //  Returns true if a command by the name given exists
474 //
COM_Exists(const char * com_name)475 static boolean COM_Exists (const char * com_name)
476 {
477     xcommand_t * cmd;
478 
479     for (cmd=com_commands ; cmd ; cmd=cmd->next)
480     {
481         if (!strcmp (com_name,cmd->name))
482             return true;
483     }
484 
485     return false;
486 }
487 
488 
489 //  Command completion using TAB key like '4dos'
490 //  Will skip 'skips' commands
491 //
492 //  partial : a partial keyword
COM_CompleteCommand(const char * partial,int skips)493 const char * COM_CompleteCommand (const char *partial, int skips)
494 {
495     xcommand_t  *cmd;
496     int        len;
497 
498     len = strlen(partial);
499 
500     if (!len)
501         return NULL;
502 
503 // check functions
504     for (cmd=com_commands ; cmd ; cmd=cmd->next)
505     {
506         if (!strncmp (partial,cmd->name, len))
507         {
508             if (!skips--)
509                 return cmd->name;
510         }
511     }
512 
513     return NULL;
514 }
515 
516 
517 
518 // Parses a single line of text into arguments and tries to execute it.
519 // The text can come from the command buffer, a remote client, or stdin.
520 //
521 //   cfg : cv_config_e, to mark the cvar as to the source
522 // Called by: COM_BufExecute
COM_ExecuteString(const char * text,boolean script,byte cfg)523 static void COM_ExecuteString (const char *text, boolean script, byte cfg )
524 {
525     xcommand_t  *cmd;
526     cmd_alias_t *a;
527 
528     COM_TokenizeString (text, script);
529 
530 // execute the command line
531     if (com_argc==0)
532         return;     // no tokens
533 
534 // check functions
535     for (cmd=com_commands ; cmd ; cmd=cmd->next)
536     {
537         if (!strcmp (com_argv[0],cmd->name))
538         {
539             cmd->function ();
540             return;
541         }
542     }
543 
544 // check aliases
545     for (a=com_alias ; a ; a=a->next)
546     {
547         if (!strcmp (com_argv[0], a->name))
548         {
549             COM_BufInsertText (a->value);
550             return;
551         }
552     }
553 
554 // check FraggleScript functions
555     if (find_variable(com_argv[0])) // if this is a potential FS function, try to execute it
556     {
557 //        run_string(text);
558         return;
559     }
560 
561     // check cvars
562     // Hurdler: added at Ebola's request ;)
563     // (don't flood the console in software mode with bad gr_xxx command)
564     if (!CV_Var_Command( cfg ) && con_destlines)
565     {
566         CONS_Printf ("Unknown command '%s'\n", com_argv[0]);
567     }
568 }
569 
570 
571 
572 // =========================================================================
573 //                            SCRIPT COMMANDS
574 // =========================================================================
575 
576 
577 // alias command : a command name that replaces another command
578 //
COM_Alias_f(void)579 static void COM_Alias_f (void)
580 {
581     cmd_alias_t  *a;
582     char        cmd[1024];
583     int         i;
584     COM_args_t  carg;
585 
586     COM_Args( &carg );
587 
588     if ( carg.num < 3 )
589     {
590         CONS_Printf("alias <name> <command>\n");
591         return;
592     }
593 
594     a = Z_Malloc (sizeof(cmd_alias_t), PU_STATIC, NULL);
595     a->next = com_alias;
596     com_alias = a;
597 
598     a->name = Z_StrDup (carg.arg[1]);
599 
600 // copy the rest of the command line
601     cmd[0] = 0;     // start out with a null string
602     for (i=2 ; i<carg.num ; i++)
603     {
604         register int n = 1020 - strlen( cmd );  // free space, with " " and "\n"
605         strncat (cmd, COM_Argv(i), n);
606         if (i != carg.num)
607             strcat (cmd, " ");
608     }
609     strcat (cmd, "\n");
610 
611     a->value = Z_StrDup (cmd);
612 }
613 
614 
615 // Echo a line of text to console
616 //
COM_Echo_f(void)617 static void COM_Echo_f (void)
618 {
619     int     i;
620     COM_args_t  carg;
621 
622     COM_Args( &carg );
623 
624     for (i=1 ; i<carg.num ; i++)
625         CONS_Printf ("%s ",COM_Argv(i));
626     CONS_Printf ("\n");
627 }
628 
629 
630 // Execute a script file
631 //
COM_Exec_f(void)632 static void COM_Exec_f (void)
633 {
634     byte*   buf=NULL;
635     COM_args_t  carg;
636 
637     COM_Args( &carg );
638 
639     if (carg.num != 2)
640     {
641         CONS_Printf ("exec <filename> : run a script file\n");
642         return;
643     }
644 
645 // load file
646 
647 #ifdef DEBUG_EXEC_FILE_LENGTH
648     int length;
649     length = FIL_ReadFile (carg.arg[1], &buf);
650     debug_Printf ("EXEC: file length = %d\n", length);
651 #else
652     FIL_ReadFile (carg.arg[1], &buf);
653 #endif
654 
655     if (!buf)
656     {
657         CONS_Printf ("couldn't execute file %s\n", carg.arg[1]);
658         return;
659     }
660 
661     CONS_Printf ("executing %s\n", carg.arg[1]);
662 
663 // insert text file into the command buffer
664 
665     COM_BufInsertText((char *)buf);
666 
667 // free buffer
668 
669     Z_Free(buf);
670 }
671 
672 
673 // Delay execution of the rest of the commands to the next frame,
674 // allows sequences of commands like "jump; fire; backward"
675 //
COM_Wait_f(void)676 static void COM_Wait_f (void)
677 {
678     COM_args_t  carg;
679 
680     COM_Args( &carg );
681     if (carg.num>1)
682         com_wait = atoi( carg.arg[1] );
683     else
684         com_wait = 1;   // 1 frame
685 }
686 
687 
688 // [WDJ] Categorized help.
689 typedef struct {
690   const char * str;
691   byte  cctype;  // cc type
692   byte  varflag; // cvar flags
693 } help_cat_t;
694 
695 #define NUM_HELP_CAT   13
696 static help_cat_t  helpcat_table[ NUM_HELP_CAT ] =
697 {
698    {"INFO", CC_info, 0},
699    {"CHEAT", CC_cheat, 0},
700    {"COMMAND", CC_command, 0},
701    {"SAVEGAME", CC_savegame, 0},
702    {"CONFIG", CC_config, 0},
703    {"CONTROL", CC_control, 0},
704    {"FS", CC_fs, 0},  // fragglescript
705    {"CHAT", CC_chat, 0},
706    {"NET", CC_net, 0},
707    {"CONSOLE", CC_console, 0},
708    {"VAR", 0xFF, 0xFF},
709    {"NETVAR", 0xFF, CV_NETVAR},
710    {"CFGVAR", 0xFF, CV_SAVE},
711 };
712 
COM_Help_f(void)713 static void COM_Help_f (void)
714 {
715     xcommand_t  *cmd;
716     consvar_t  *cvar;
717     int i, k;
718     uint32_t   varflag = 0;
719     byte  cctype = 0;
720 
721     COM_args_t  carg;
722 
723     COM_Args( &carg );
724 
725     // [WDJ] Categorized help.
726     if( carg.num < 2 )
727     {
728         CONS_Printf ("HELP <category>\n" );
729         CONS_Printf ("   INFO CHEAT COMMAND SAVEGAME CONFIG CONTROL FS CHAT NET CONSOLE\n" );
730         CONS_Printf ("   VAR NETVAR CFGVAR\n" );
731         CONS_Printf ("HELP <varname>\n");
732         return;
733     }
734 
735     for( i = 1; i < carg.num; i++ )
736     {
737         for( k = 0; k < NUM_HELP_CAT; k++ )
738         {
739             if( strcasecmp( carg.arg[i], helpcat_table[k].str ) == 0 )
740             {
741                 if( helpcat_table[k].cctype < 20 )
742                 {
743                     cctype = helpcat_table[k].cctype;
744                     break;
745                 }
746                 varflag |= helpcat_table[k].varflag;
747                 cctype = 0xF0; // var listing
748             }
749         }
750     }
751 
752     if((carg.num>1) && (cctype == 0))
753     {
754         cvar = CV_FindVar (carg.arg[1]);
755         if( cvar )
756         {
757             con_Printf("Variable %s:\n",cvar->name);
758             con_Printf("  flags :");
759             if( cvar->flags & CV_SAVE )
760                 con_Printf("AUTOSAVE ");
761             if( cvar->flags & CV_FLOAT )
762                 con_Printf("FLOAT ");
763             if( cvar->flags & CV_NETVAR )
764                 con_Printf("NETVAR ");
765             if( cvar->flags & CV_CALL )
766                 con_Printf("ACTION ");
767             con_Printf("\n");
768 
769             if( cvar->PossibleValue )
770             {
771                 CV_PossibleValue_t * pv0 = cvar->PossibleValue;
772                 CV_PossibleValue_t * pv;
773                 if( strcasecmp(pv0->strvalue,"MIN") == 0 )
774                 {
775                     // search for MAX
776                     for( pv = pv0+1; pv->strvalue; pv++)
777                     {
778                         if( strcasecmp(pv->strvalue,"MAX") == 0 )
779                             break;
780                     }
781                     con_Printf("  range from %d to %d\n", pv0->value, pv->value);
782                 }
783                 else
784                 {
785                     con_Printf("  possible value :\n",cvar->name);
786                     for( pv = pv0; pv->strvalue; pv++)
787                     {
788                         con_Printf("    %-2d : %s\n", pv->value, pv->strvalue);
789                     }
790                 }
791             }
792         }
793         else
794             con_Printf("No Help for this command/variable\n");
795 
796         return;
797     }
798 
799     i = 0; // cnt vars and commands
800     if( cctype == 0 )
801         varflag = 0xFFFF;  // all variables
802 
803     if( cctype < 20 )
804     {
805         // commands
806         con_Printf("\2Commands\n");
807         for (cmd=com_commands ; cmd ; cmd=cmd->next)
808         {
809           if( (cctype == 0) || (cctype == cmd->cctype) )
810           {
811             con_Printf("%s ",cmd->name);
812             i++;
813           }
814         }
815     }
816 
817     if( varflag )
818     {
819         // variable
820         con_Printf("\2\nVariables\n");
821         for (cvar=consvar_vars; cvar; cvar = cvar->next)
822         {
823           if( cvar->flags & varflag )
824           {
825             con_Printf("%s ",cvar->name);
826             i++;
827           }
828         }
829     }
830 
831     con_Printf("\2\nRead the console docs for more or type help <command or variable>\n");
832 
833     if( devparm > 1 )
834             con_Printf("\2Total : %d\n",i);
835 }
836 
COM_Toggle_f(void)837 static void COM_Toggle_f(void)
838 {
839     consvar_t  *cvar;
840     COM_args_t  carg;
841 
842     COM_Args( &carg );
843 
844     if(carg.num!=2 && carg.num!=3)
845     {
846         CONS_Printf("Toggle <cvar_name> [-1]\n"
847                     "Toggle the value of a cvar\n");
848         return;
849     }
850     cvar = CV_FindVar (carg.arg[1]);
851     if(!cvar)
852     {
853         CONS_Printf("%s is not a cvar\n", carg.arg[1]);
854         return;
855     }
856 
857     // netcvar don't change imediately
858     cvar->flags |= CV_SHOWMODIF_ONCE;  // show modification, reset flag
859     if( carg.num==3 )
860         CV_ValueIncDec(cvar, atol( carg.arg[2] ));
861     else
862         CV_ValueIncDec(cvar,+1);
863 }
864 
865 // =========================================================================
866 //                      VARIABLE SIZE BUFFERS
867 // =========================================================================
868 
869 #define VSBUFMINSIZE   256
870 
VS_Alloc(vsbuf_t * buf,int initsize)871 void VS_Alloc (vsbuf_t *buf, int initsize)
872 {
873     if (initsize < VSBUFMINSIZE)
874         initsize = VSBUFMINSIZE;
875     buf->data = Z_Malloc (initsize, PU_STATIC, NULL);
876     buf->maxsize = initsize;
877     buf->cursize = 0;
878     buf->allowoverflow = false;
879 }
880 
881 
VS_Free(vsbuf_t * buf)882 void VS_Free (vsbuf_t *buf)
883 {
884 //  Z_Free (buf->data);
885     buf->cursize = 0;
886 }
887 
888 
VS_Clear(vsbuf_t * buf)889 void VS_Clear (vsbuf_t *buf)
890 {
891     buf->cursize = 0;
892 }
893 
894 
895 // Add length to the space in use.  Detect overflow.
VS_GetSpace(vsbuf_t * buf,int length)896 void *VS_GetSpace (vsbuf_t *buf, int length)
897 {
898     if (buf->cursize + length > buf->maxsize)
899     {
900         if (!buf->allowoverflow)
901           return NULL;
902 
903         if (length > buf->maxsize)
904           return NULL;
905 
906         buf->overflowed = true;
907         CONS_Printf ("VS buffer overflow");
908         VS_Clear (buf);
909     }
910 
911     void *data = buf->data + buf->cursize;
912     buf->cursize += length;
913 
914     return data;
915 }
916 
917 
918 #if 0
919 // [WDJ] Unused
920 //  Copy data at end of variable sized buffer
921 //
922 boolean VS_Write (vsbuf_t *buf, void *data, int length)
923 {
924   void *to = VS_GetSpace(buf, length);
925   if (!to)
926     return false;
927 
928   memcpy(to, data, length);
929   return true;
930 }
931 #endif
932 
933 
934 //  Print text in variable size buffer, like VS_Write + trailing 0
935 //
VS_Print(vsbuf_t * buf,const char * data)936 boolean VS_Print (vsbuf_t *buf, const char *data)
937 {
938   int len = strlen(data) + 1;
939   int old_size = buf->cursize;  // VS_GetSpace modifies cursize
940 
941   // Remove trailing 0 before any consideration.
942   // Otherwise the extra accumulates until garbage gets between the appends.
943   if( old_size )
944   {
945       if( buf->data[old_size-1] == 0 )
946          buf->cursize --;
947   }
948 
949   // len-1 would be enough if there already is a trailing zero, but...
950   byte *to = (byte *)VS_GetSpace(buf, len);
951   if (!to)  goto fail_cleanup;
952 
953   memcpy(to, data, len);
954   return true;
955 
956 fail_cleanup:
957   // Restore
958   buf->cursize = old_size;
959   return false;
960 }
961 
962 // =========================================================================
963 //
964 //                           CONSOLE VARIABLES
965 //
966 //   console variables are a simple way of changing variables of the game
967 //   through the console or code, at run time.
968 //
969 //   console vars acts like simplified commands, because a function can be
970 //   attached to them, and called whenever a console var is modified
971 //
972 // =========================================================================
973 
974 static char       *cv_null_string = "";
975 byte    command_EV_param = 0;
976 
977 static byte  OnChange_user_enable = 0;
978 
979 static byte  CV_Pop_Config( consvar_t * cvar );
980 static void  CV_set_str_value( consvar_t * cvar, const char * valstr, byte call_enable, byte user_enable );
981 
982 
983 //  Search if a variable has been registered
984 //  returns true if given variable has been registered
985 //
CV_FindVar(const char * name)986 consvar_t * CV_FindVar (const char * name)
987 {
988     consvar_t  *cvar;
989 
990     for (cvar=consvar_vars; cvar; cvar = cvar->next)
991     {
992         if ( !strcmp(name,cvar->name) )
993             return cvar;
994     }
995 
996     return NULL;
997 }
998 
999 
1000 //  Build a unique Net Variable identifier number, that is used
1001 //  in network packets instead of the fullname
1002 //
CV_ComputeNetid(const char * s)1003 uint16_t  CV_ComputeNetid (const char * s)
1004 {
1005     uint16_t ret;
1006     static byte premiers[16] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};
1007     int i;
1008 
1009     ret=0;
1010     i=0;
1011     while(*s)
1012     {
1013         ret += ((byte)(*s)) * ((unsigned int) premiers[i]);
1014         s++;
1015         i = (i+1)%16;
1016     }
1017     return ret;
1018 }
1019 
1020 
1021 //  Return the Net Variable, from it's identifier number
1022 //
CV_FindNetVar(uint16_t netid)1023 static consvar_t * CV_FindNetVar (uint16_t netid)
1024 {
1025     consvar_t  *cvar;
1026 
1027     for (cvar=consvar_vars; cvar; cvar = cvar->next)
1028     {
1029         if (cvar->netid==netid)
1030             return cvar;
1031     }
1032 
1033     return NULL;
1034 }
1035 
1036 
1037 //  Register a variable, that can be used later at the console
1038 //
CV_RegisterVar(consvar_t * cvar)1039 void CV_RegisterVar (consvar_t *cvar)
1040 {
1041     // first check to see if it has already been defined
1042     if (CV_FindVar (cvar->name))
1043     {
1044         CONS_Printf ("Variable %s is already defined\n", cvar->name);
1045         return;
1046     }
1047 
1048     // check for overlap with a command
1049     if (COM_Exists (cvar->name))
1050     {
1051         CONS_Printf ("%s is a command name\n", cvar->name);
1052         return;
1053     }
1054 
1055     cvar->string = NULL;
1056     cvar->state = 0;
1057 
1058     // check net cvars
1059     if (cvar->flags & CV_NETVAR)
1060     {
1061         cvar->netid = CV_ComputeNetid (cvar->name);
1062         if (CV_FindNetVar(cvar->netid))
1063             I_Error("Variable %s has duplicate netid\n",cvar->name);
1064     }
1065 
1066     // link the cvar in
1067     if( !(cvar->flags & CV_HIDEN) )
1068     {
1069         cvar->next = consvar_vars;
1070         consvar_vars = cvar;
1071     }
1072 
1073 #ifdef PARANOIA
1074     if ((cvar->flags & CV_NOINIT) && !(cvar->flags & CV_CALL))
1075         I_Error("variable %s has CV_NOINIT without CV_CALL\n",cvar->name);
1076     if ((cvar->flags & CV_CALL) && !cvar->func)
1077         I_Error("variable %s has CV_CALL without func",cvar->name);
1078 #endif
1079     CV_set_str_value( cvar, cvar->defaultvalue,
1080                      ((cvar->flags & CV_NOINIT) == 0), // call_enable
1081                      1 );  // user_enable, default is a user setting
1082 
1083 
1084     // CV_set_str_value will have set this bit
1085     cvar->state &= ~CS_MODIFIED;
1086 }
1087 
1088 
1089 //  Returns the string value of a console var
1090 //
CV_StringValue(const char * var_name)1091 static char * CV_StringValue (const char * var_name)
1092 {
1093     consvar_t *cvar;
1094 
1095     cvar = CV_FindVar (var_name);
1096     if (!cvar)
1097         return cv_null_string;
1098     return cvar->string;
1099 }
1100 
1101 
1102 //  Completes the name of a console var
1103 //
CV_CompleteVar(const char * partial,int skips)1104 const char * CV_CompleteVar (const char * partial, int skips)
1105 {
1106     consvar_t  *cvar;
1107     int         len;
1108 
1109     len = strlen(partial);
1110 
1111     if (!len)
1112         return NULL;
1113 
1114     // check functions
1115     for (cvar=consvar_vars ; cvar ; cvar=cvar->next)
1116     {
1117         if (!strncmp (partial,cvar->name, len))
1118         {
1119             if (!skips--)
1120                 return cvar->name;
1121         }
1122     }
1123 
1124     return NULL;
1125 }
1126 
1127 // [WDJ] Hard to tell yet which of the two methods has the least problems.
1128 // For now, they both work, and are about the same size.
1129 #define COMMAND_RECOVER_STRING
1130 #ifdef COMMAND_RECOVER_STRING
1131 static const char * cvar_string_min = NULL;
1132 static const char * cvar_string_max = NULL;
1133 #endif
1134 
1135 // Free this string allocation, when it is not a PossibleValue const.
1136 static
CV_Free_cvar_string_param(consvar_t * cvar,char * str)1137 void  CV_Free_cvar_string_param( consvar_t * cvar, char * str )
1138 {
1139 #ifdef COMMAND_RECOVER_STRING
1140     // Check if is in bounds of allocated cvar strings.
1141     if( cvar_string_min
1142         && str >= cvar_string_min
1143         && str <= cvar_string_max )
1144     {
1145         // was allocated
1146         Z_Free( str );
1147     }
1148 #else
1149     // It is Z_StrDup or a string from PossibleValue
1150     CV_PossibleValue_t * pv = cvar->PossibleValue;
1151     if( pv )
1152     {
1153         for( ; pv->strvalue ; pv++ )
1154         {
1155             if( str == pv->strvalue )
1156                 return;  // was a ptr to a PossibleValue string
1157         }
1158     }
1159 
1160     // not in PossibleValue
1161     Z_Free( str );
1162 #endif
1163 }
1164 
1165 // Free the cvar string allocation, when it is not a PossibleValue const.
CV_Free_cvar_string(consvar_t * cvar)1166 void  CV_Free_cvar_string( consvar_t * cvar )
1167 {
1168     if( cvar->string )
1169     {
1170         CV_Free_cvar_string_param( cvar, cvar->string );
1171 
1172         cvar->string = NULL;
1173     }
1174 }
1175 
1176 // Makes a copy of the string, and handles PossibleValue string values.
1177 //   str : a reference to a string, it will be copied.
CV_Set_cvar_string(consvar_t * cvar,const char * str)1178 void  CV_Set_cvar_string( consvar_t * cvar, const char * str )
1179 {
1180     CV_PossibleValue_t * pv;
1181 
1182     // Free an allocated existing string.
1183     CV_Free_cvar_string( cvar );
1184 
1185     // Check if str is Z_StrDup or a string from PossibleValue
1186     pv = cvar->PossibleValue;
1187     if( pv )
1188     {
1189         for( ; pv->strvalue ; pv++ )
1190         {
1191             // Only if it is a pointer to a PossibleValue string.
1192             if( str == pv->strvalue )
1193                 goto update;  // just point to it, by reference
1194         }
1195     }
1196 
1197     // Have to copy it.
1198     str = Z_StrDup( str );
1199 #ifdef COMMAND_RECOVER_STRING
1200     // Track range of allocated cvar strings.
1201     if( cvar_string_min == NULL || str < cvar_string_min )
1202         cvar_string_min = str;
1203     if( str > cvar_string_max )
1204         cvar_string_max = str;
1205 #endif
1206 
1207 update:
1208     // current cvar
1209     cvar->string = (char*) str;
1210     return;
1211 }
1212 
1213 
1214 // Do the CV_CALL, with validity tests, and enforcing user_enable rules.
1215 static
CV_cvar_call(consvar_t * cvar,byte user_enable)1216 void  CV_cvar_call( consvar_t *cvar, byte user_enable )
1217 {
1218     // Call the CV_CALL func to restore state dependent upon this setting.
1219     // Set the 'on change' code.
1220     if((cvar->flags & CV_CALL) && (cvar->func))
1221     {
1222         // Handle recursive OnChange calls. Propagate a valid user_enable.
1223         byte cfg = cvar->state & CS_CONFIG;
1224         OnChange_user_enable = user_enable && ((cfg >= CFG_main) && (cfg <= CFG_drawmode));
1225         cvar->func();
1226     }
1227 }
1228 
1229 
1230 // Set variable value, for user settings, save games, and network settings.
1231 // Updates value and EV.
1232 // Does NOT relay NETVAR to clients.
1233 //  call_enable : when 0, blocks CV_CALL
1234 //  user_enable : enable setting the string value which gets saved in config files.
1235 static
CV_set_str_value(consvar_t * cvar,const char * valstr,byte call_enable,byte user_enable)1236 void  CV_set_str_value( consvar_t * cvar, const char * valstr, byte call_enable, byte user_enable )
1237 {
1238     char  value_str[64];  // print %d cannot exceed 64
1239     CV_PossibleValue_t * pv0, * pv;
1240     int  ival;
1241     byte is_a_number = 0;
1242 
1243 #ifdef PARANOIA
1244     if( valstr == NULL )
1245     {
1246         I_SoftError( "CV_set_str_value passed NULL string: %s\n", cvar->name );
1247         return;
1248     }
1249 #endif
1250 
1251     // [WDJ] If the value is a float, then all comparisons must be fixed_t.
1252     // Any PossibleValues would be fixed_t too.
1253     if( cvar->flags & CV_FLOAT )
1254     {
1255         // store as fixed_t
1256         double d = atof( valstr );
1257         ival = (int)(d * FRACUNIT);
1258     }
1259     else
1260     {
1261         ival = atoi(valstr);  // enum and integer values
1262     }
1263 
1264     if( ival )
1265     {
1266         is_a_number = 1;  // atoi or atof found a number
1267     }
1268     else
1269     {
1270         // Deal with the case where there are leading spaces.
1271         const char * c = valstr;
1272         while( *c == ' ' )  c++;  // skip spaces
1273         is_a_number = (*c >= '0' && *c <= '9');
1274     }
1275 
1276     pv0 = cvar->PossibleValue;
1277     if( pv0 )
1278     {
1279         if( strcasecmp(pv0->strvalue,"MIN") == 0 )
1280         {   // bounded cvar
1281             // search for MAX
1282             for( pv = pv0+1; pv->strvalue; pv++)
1283             {
1284                 if( strcasecmp(pv->strvalue,"MAX") == 0 )
1285                     break;
1286             }
1287 
1288 #ifdef PARANOIA
1289             if( pv->strvalue == NULL )
1290                 I_Error("Bounded cvar \"%s\" without MAX !", cvar->name);
1291 #endif
1292             // PossibleValue is MIN MAX, so value must be a number.
1293             if( ! is_a_number )  goto error;
1294 
1295             // [WDJ] Cannot print into const string.
1296             if(ival < pv0->value)  // MIN value
1297             {
1298                 ival = pv0->value;
1299                 sprintf(value_str,"%d", ival);
1300                 valstr = value_str;
1301             }
1302             if(ival > pv->value)  // supposedly MAX value
1303             {
1304                 ival = pv->value;
1305                 sprintf(value_str,"%d", ival);
1306                 valstr = value_str;
1307             }
1308         }
1309         else
1310         {
1311             // waw spaghetti programming ! :)
1312 
1313             // check for string match
1314             for( pv = pv0; pv->strvalue; pv++)
1315             {
1316                 if( strcasecmp(pv->strvalue, valstr) == 0 )
1317                     goto found_possible_value;
1318             }
1319 
1320             // If valstr is not a number, then it cannot be used as a PossibleValue.
1321             if( ! is_a_number )  goto error;
1322 
1323             // check as PossibleValue number
1324             for( pv = pv0; pv->strvalue; pv++)
1325             {
1326                 if( ival == pv->value )
1327                     goto found_possible_value;
1328             }
1329             goto error;
1330 
1331     found_possible_value:
1332             ival = pv->value;
1333             if( user_enable )
1334             {
1335                 // [WDJ] Used to assume existing was a const string, whenever the new string
1336                 // was a const string.  Cannot prove that assumption so call CV_Free.
1337                 CV_Free_cvar_string( cvar );
1338                 cvar->value = ival;
1339                 // When value is from PossibleValue, string is a const char *.
1340                 cvar->string = (char*) pv->strvalue;
1341             }
1342             goto finish;
1343         }
1344     }
1345 
1346     // CV_STRING has no temp values, and is used for network addresses.
1347     // Block it for security reasons, to prevent redirecting.
1348     // Only change the cvar string when user is making the change.
1349     if( user_enable )
1350     {
1351         // free the old value string, set the new value
1352         CV_Set_cvar_string( cvar, valstr );
1353     }
1354 
1355     // Update value when set by user, or if flagged as numeric value.
1356     // CV_uint16, CV_Unsigned values may not fit into EV.
1357     if( cvar->flags & (CV_FLOAT | CV_VALUE) )
1358     {
1359         if( ! is_a_number )  goto error;
1360         cvar->value = ival;
1361     }
1362     else if( user_enable )
1363     {
1364         cvar->value = ival;
1365     }
1366 
1367 
1368 finish:
1369     // The SHOWMODIF is display of CV_Set, and not other set paths.
1370     if( cvar->flags & (CV_SHOWMODIF | CV_SHOWMODIF_ONCE) )
1371     {
1372         CONS_Printf("%s set to %s\n", cvar->name, valstr );
1373         cvar->flags &= ~CV_SHOWMODIF_ONCE;
1374     }
1375     DEBFILE(va("%s set to %s\n", cvar->name, cvar->string));
1376 
1377     cvar->state |= CS_MODIFIED;
1378     cvar->EV = ival;  // user setting of active value
1379 
1380     // raise 'on change' code
1381     if( call_enable )
1382         CV_cvar_call( cvar, user_enable );
1383     return;
1384 
1385 error: // not found
1386     CONS_Printf("\"%s\" is not a possible value for \"%s\"\n", valstr, cvar->name);
1387     if( strcasecmp(cvar->defaultvalue, valstr) == 0 )
1388     {
1389         I_SoftError("Variable %s default value \"%s\" is not a possible value\n",
1390                     cvar->name, cvar->defaultvalue);
1391     }
1392     return;
1393 }
1394 
1395 // Called after demo to restore the user settings.
1396 // Copies value to EV.
CV_Restore_User_Settings(void)1397 void CV_Restore_User_Settings( void )
1398 {
1399     consvar_t * cvar;
1400 
1401     // Check for modified cvar
1402     for (cvar=consvar_vars; cvar; cvar = cvar->next)
1403     {
1404         if( cvar->state & CS_EV_PROT )  // protected EV value
1405             continue;
1406 
1407         if((cvar->state & CS_CONFIG) > CFG_other )
1408         {
1409             // Undo a push of NETVAR
1410             CV_Pop_Config( cvar );  // CV_CALL
1411             cvar->state &= ~CS_EV_PARAM;
1412             continue;
1413         }
1414 
1415         if( cvar->flags & CV_VALUE )
1416         {
1417             cvar->value = atoi( cvar->string );
1418         }
1419 
1420         if( (cvar->EV != (byte)cvar->value)
1421             || (cvar->value >> 8)
1422             || (cvar->state & CS_EV_PARAM) )  // command line param in EV
1423         {
1424             cvar->EV = cvar->value;  // user setting of active value
1425             CV_cvar_call( cvar, 1 );
1426             cvar->state &= ~CS_EV_PARAM;
1427         }
1428     }
1429     command_EV_param = 0;
1430 }
1431 
1432 
1433 //
1434 // Use XD_NETVAR argument :
1435 //      2 byte for variable identification
1436 //      then the value of the variable followed with a 0 byte (like str)
1437 //
1438 // Receive network game settings, or restore save game.
Got_NetXCmd_NetVar(xcmd_t * xc)1439 void Got_NetXCmd_NetVar(xcmd_t * xc)
1440 {
1441     byte * bp = xc->curpos;	// macros READ,SKIP want byte*
1442 
1443     consvar_t *cvar = CV_FindNetVar(READU16(bp));  // netvar id
1444     char *svalue = (char *)bp;  // after netvar id
1445 
1446     while( *(bp++) ) {  // find 0 term
1447        if( bp > xc->endpos )  goto buff_overrun;  // bad string
1448     }
1449     xc->curpos = bp;	// return updated ptr only once
1450 
1451     if(cvar==NULL)
1452     {
1453         CONS_Printf("\2Netvar not found\n");
1454         return;
1455     }
1456 
1457     if( cvar->flags & (CV_FLOAT | CV_VALUE | CV_STRING))
1458     {
1459         // Netvar value will not fit in EV, so use NETVAR push.
1460         CV_Put_Config_string( cvar, CFG_netvar, svalue );
1461         // Current config is netvar setting (not saved).
1462     }
1463     else
1464     {
1465         // Put netvar value in EV.
1466         CV_set_str_value(cvar, svalue, 1, 0);  // CV_CALL, temp
1467         // Current config is netvar setting (not saved).
1468         // Not visible to menu. Menu displays the string.
1469     }
1470     return;
1471 
1472 buff_overrun:
1473     xc->curpos = xc->endpos+2;  // indicate overrun
1474     return;
1475 }
1476 
1477 
1478 // Called by SV_Send_ServerConfig, P_Savegame_Save_game.
CV_SaveNetVars(xcmd_t * xc)1479 void CV_SaveNetVars(xcmd_t * xc)
1480 {
1481     char buf[32];
1482     char * vp;
1483     consvar_t  *cvar;
1484     byte * bp = xc->curpos;	// macros want byte*
1485 
1486 
1487     // We must send all NETVAR cvar, because on another machine,
1488     // some of them may have a different value.
1489     for (cvar=consvar_vars; cvar; cvar = cvar->next)
1490     {
1491         if( ! (cvar->flags & CV_NETVAR) )  continue;
1492 
1493         // Command line settings goto network games and savegames.
1494         // CV_STRING do not have temp values.
1495         if( cvar->state & CS_EV_PARAM )  // command line param in EV
1496         {
1497             // Send the EV param value instead.
1498             sprintf (buf, "%d", cvar->EV);
1499             vp = buf;
1500         }
1501         else if( cvar->flags & CV_VALUE )
1502         {
1503             // Value has precedence over the string.
1504             sprintf (buf, "%d", cvar->value);
1505             vp = buf;
1506         }
1507         else
1508         {
1509             vp = cvar->string;
1510         }
1511         // potential buffer overrun test
1512         if((bp + 2 + strlen(vp)) > xc->endpos )  goto buff_overrun;
1513         // Format:  netid uint16, var_string str0.
1514         WRITE16(bp,cvar->netid);
1515         bp = write_string(bp, vp);
1516     }
1517     xc->curpos = bp;	// return updated ptr only once
1518     return;
1519 
1520 buff_overrun:
1521     I_SoftError( "Net Vars overrun available packet space\n" );
1522     return;
1523 }
1524 
1525 // Client: Receive server netvars state.  Server config.
CV_LoadNetVars(xcmd_t * xc)1526 void CV_LoadNetVars(xcmd_t * xc)
1527 {
1528     consvar_t  *cvar;
1529 
1530     // Read netvar from byte stream, identified by netid.
1531     for (cvar=consvar_vars; cvar; cvar = cvar->next)
1532     {
1533         if (cvar->flags & CV_NETVAR)
1534             Got_NetXCmd_NetVar( xc );
1535         // curpos on last read can go to endpos+1
1536         if(xc->curpos > xc->endpos+1)  goto buff_overrun;
1537     }
1538     return;
1539 
1540 buff_overrun:
1541     I_SoftError( "Load Net Vars overran packet buffer\n" );
1542     return;
1543 }
1544 
1545 #define SET_BUFSIZE 128
1546 
1547 // PUBLIC
1548 
1549 // Sets a var to a string value.
1550 // called by CV_Var_Command to handle "<varname> <value>" entered at the console
CV_Set(consvar_t * cvar,const char * str_value)1551 void CV_Set (consvar_t *cvar, const char *str_value)
1552 {
1553     //changed = strcmp(var->string, value);
1554 #ifdef PARANOIA
1555     if(!cvar)
1556         I_Error("CV_Set : no variable\n");
1557 
1558     // Not an error if cvar does not have string.
1559 #endif
1560 
1561     if( cvar->string )
1562     {
1563       if( strcasecmp(cvar->string, str_value) == 0 )
1564         return; // no changes
1565     }
1566 
1567     if (netgame)
1568     {
1569       // in a netgame, certain cvars are handled differently
1570       if (cvar->flags & CV_NET_LOCK)
1571       {
1572         CONS_Printf("This variable cannot be changed during a netgame.\n");
1573         return;
1574       }
1575 
1576       if( cvar->flags & CV_NETVAR )
1577       {
1578         if (!server)
1579         {
1580             CONS_Printf("Only the server can change this variable.\n");
1581             return;
1582         }
1583 
1584         // Change user settings too, but want only one CV_CALL.
1585         CV_set_str_value(cvar, str_value, 0, 1); // no CALL, user
1586 
1587         // send the value of the variable
1588         byte buf[SET_BUFSIZE], *p; // macros want byte*
1589         p = buf;
1590         // Format:  netid uint16, var_string str0.
1591         WRITEU16(p, cvar->netid);
1592         p = write_stringn(p, str_value, SET_BUFSIZE-2-1);
1593         SV_Send_NetXCmd(XD_NETVAR, buf, (p - buf));  // as server
1594         // NetXCmd will set as netvar, CV_CALL, not user.
1595         // This NetXCmd is also used by savegame restore, so it cannot block server.
1596         return;
1597       }
1598     }
1599 
1600     // Single player
1601     CV_set_str_value(cvar, str_value, 1, 1);  // CALL, user
1602 }
1603 
1604 
1605 //  Expands value to string before calling CV_Set ()
1606 //
CV_SetValue(consvar_t * cvar,int value)1607 void CV_SetValue (consvar_t *cvar, int value)
1608 {
1609     char    val[32];
1610 
1611     sprintf (val, "%d", value);
1612     CV_Set (cvar, val);
1613 }
1614 
1615 // Set a command line parameter value (temporary).
1616 // This should not affect owner saved values.
CV_SetParam(consvar_t * cvar,int value)1617 void CV_SetParam (consvar_t *cvar, int value)
1618 {
1619     command_EV_param = 1;  // flag to undo these later
1620     cvar->EV = value;   // temp setting, during game play
1621     cvar->state |= CS_EV_PARAM;
1622     CV_cvar_call( cvar, 0 );  // not user
1623 }
1624 
1625 // If a OnChange func tries to change other values,
1626 // this function should be used.
1627 // It will determine the same user_enable.
CV_Set_by_OnChange(consvar_t * cvar,int value)1628 void CV_Set_by_OnChange (consvar_t *cvar, int value)
1629 {
1630     byte saved_user_enable = OnChange_user_enable;
1631     if( OnChange_user_enable )
1632     {
1633         CV_SetValue( cvar, value );
1634     }
1635     else
1636     {
1637         CV_SetParam( cvar, value );
1638     }
1639     OnChange_user_enable = saved_user_enable;
1640 }
1641 
1642 
1643 
CV_ValueIncDec(consvar_t * cvar,int increment)1644 void CV_ValueIncDec (consvar_t *cvar, int increment)
1645 {
1646     int   newvalue = cvar->value + increment;
1647     CV_PossibleValue_t *  pv0 = cvar->PossibleValue;  // array of
1648 
1649     if( pv0 )
1650     {
1651         // If first item in list is "MIN"
1652         if( strcmp( pv0->strvalue,"MIN") == 0 )
1653         {
1654             // MIN .. MAX
1655             int min_value = pv0->value;  // MIN value
1656             int max_value = INT_MAX;
1657             CV_PossibleValue_t *  pv;
1658 
1659             // Search the list for MAX value, or INC.
1660             for( pv = pv0; pv->strvalue ; pv++ )
1661             {
1662                 if( strcmp(pv->strvalue,"INC") == 0 )
1663                 {
1664                     // Has an INC
1665                     newvalue = cvar->value + (increment * pv->value);
1666                 }
1667                 else
1668                 {
1669                     max_value = pv->value;  // last value is assumed "MAX"
1670                 }
1671             }
1672 
1673             if( newvalue < min_value )
1674             {
1675                 // To accomodate negative increment.
1676                 newvalue += max_value - min_value + 1;   // add the max+1
1677             }
1678             newvalue = min_value
1679              + ((newvalue - min_value) % (max_value - min_value + 1));
1680 
1681             CV_SetValue(cvar,newvalue);
1682         }
1683         else
1684         {
1685             // List of Values
1686             int max, currentindice=-1;
1687 
1688             // this code do not support more than same value for differant PossibleValue
1689             for( max = 0; ; max++ )
1690             {
1691                 if( pv0[max].strvalue == NULL )  break;  // end of list
1692                 if( pv0[max].value == cvar->value )
1693                     currentindice = max;
1694             }
1695             // max is at NULL, has count of possible value list
1696 #ifdef PARANOIA
1697             if( currentindice == -1 )
1698             {
1699                 I_SoftError("CV_ValueIncDec : current value %d not found in possible value\n", cvar->value);
1700                 return;
1701             }
1702 #endif
1703             // calculate position in possiblevalue
1704             // max is list count
1705             // To accommodate neg increment, add extra list count.
1706             // Modulo result back into the possible value range,
1707             int newindice = ( currentindice + increment + max) % (max);
1708             CV_Set(cvar, pv0[newindice].strvalue);
1709         }
1710     }
1711     else
1712     {
1713         CV_SetValue(cvar,newvalue);
1714     }
1715 }
1716 
1717 
1718 // =================
1719 // Pushed cvar values
1720 
1721 typedef struct cv_pushed_s {
1722     struct cv_pushed_s * next;
1723     consvar_t  * parent;
1724     char *  string;  // value in string
1725     int32_t  value;  // for int and fixed_t
1726     byte     state;  // cv_state_e
1727 } cv_pushed_t;
1728 
1729 cv_pushed_t *  cvar_pushed_list = NULL;  // malloc
1730 
1731 
1732 // Frees the string and the pushed record.
1733 static
release_pushed_cvar(cv_pushed_t * pp_rel)1734 void  release_pushed_cvar( cv_pushed_t * pp_rel )
1735 {
1736     // unlink from pushed list
1737     if( cvar_pushed_list == pp_rel )
1738     {
1739         cvar_pushed_list = pp_rel->next;
1740     }
1741     else
1742     {
1743         // find the pp_rel in the list.
1744         cv_pushed_t  * pp;
1745         for( pp = cvar_pushed_list; pp ; pp = pp->next )
1746         {
1747             if( pp->next == pp_rel )
1748             {
1749                 // found it, unlink it.
1750                 pp->next = pp_rel->next;
1751                 break;
1752             }
1753         }
1754     }
1755 
1756     // The string must be freed.
1757     if( pp_rel->string )
1758         CV_Free_cvar_string_param( pp_rel->parent, pp_rel->string );
1759 
1760     free( pp_rel );
1761 }
1762 
1763 static
create_pushed_cvar(consvar_t * parent_cvar)1764 cv_pushed_t * create_pushed_cvar( consvar_t * parent_cvar )
1765 {
1766     cv_pushed_t * pp = (cv_pushed_t*) malloc( sizeof(cv_pushed_t) );
1767     if( pp )
1768     {
1769         // link into the push list
1770         pp->next = cvar_pushed_list;
1771         cvar_pushed_list = pp;
1772 
1773         pp->parent = parent_cvar;
1774     }
1775     return pp;
1776 }
1777 
1778 // Search the pushed cv for a matching config.
1779 static
find_pushed_cvar(consvar_t * cvar,byte cfg)1780 cv_pushed_t *  find_pushed_cvar( consvar_t * cvar, byte cfg )
1781 {
1782     cv_pushed_t * pp;
1783     for( pp = cvar_pushed_list; pp ; pp = pp->next )
1784     {
1785         if( (pp->parent == cvar)
1786             && ((pp->state & CS_CONFIG) == cfg) )
1787         {
1788              return pp;
1789         }
1790     }
1791     return NULL;  // not found
1792 }
1793 
1794 // update the CS_PUSHED flag
1795 static
update_pushed_cvar(consvar_t * cvar)1796 void  update_pushed_cvar( consvar_t * cvar )
1797 {
1798     cv_pushed_t * pp;
1799     for( pp = cvar_pushed_list; pp ; pp = pp->next )
1800     {
1801         if( pp->parent == cvar )
1802         {
1803              cvar->state |= CS_PUSHED;
1804              return;
1805         }
1806     }
1807     // none found
1808     cvar->state &= ~CS_PUSHED;
1809 }
1810 
1811 //  new_cfg : the new config that is causing the push
1812 static
CV_Push_Config(consvar_t * cvar,byte new_cfg)1813 void  CV_Push_Config( consvar_t * cvar, byte new_cfg )
1814 {
1815     cv_pushed_t * pp = create_pushed_cvar( cvar );
1816     if( ! pp )
1817         return;
1818 
1819     // save cvar values
1820     pp->string = cvar->string;  // move the string
1821     cvar->string = NULL;
1822     pp->value = cvar->value;
1823     pp->state = cvar->state;
1824 
1825     // update the current cvar
1826     cvar->state = (cvar->state & ~CS_CONFIG) | new_cfg | CS_PUSHED;
1827 }
1828 
1829 // return 1 if pop succeeded, 0 if no pop.
1830 static
CV_Pop_Config(consvar_t * cvar)1831 byte  CV_Pop_Config( consvar_t * cvar )
1832 {
1833     cv_pushed_t * pp;
1834     byte old_config = (cvar->state & CS_CONFIG);
1835     while( --old_config )
1836     {
1837         pp = find_pushed_cvar( cvar, old_config );
1838         if( pp )  goto restore_cvar;
1839     }
1840     return 0;
1841 
1842 restore_cvar:
1843     // restore cvar values
1844     // Move the pushed string to the cvar.
1845     CV_Free_cvar_string( cvar );
1846     cvar->string = pp->string;  // move string, as pushed cvar will be released
1847     pp->string = NULL;  // because the string in the pushed record will be freed.
1848 
1849     // If this happens during a demo or other usage, protections have already been applied.
1850     cvar->EV = pp->value;
1851     cvar->value = pp->value;
1852     cvar->state = pp->state;
1853 
1854     release_pushed_cvar( pp );
1855     update_pushed_cvar( cvar );
1856 
1857     CV_cvar_call( cvar, 1 );  // Pop brings user settings back into force.
1858     return 1;
1859 }
1860 
1861 // Public
1862 
1863 // Get the values of a pushed cvar, into the temp cvar.
1864 //   pushed_cvar : copy of the cvar values, is assumed to be uninitialized,
1865 //                 any lingering string value will not be freed
1866 //   temp_cvar : an uninitialized cvar to receive the value
1867 //               If NULL, then is just a check on existance.
1868 // Return false if not found.
CV_Get_Pushed_cvar(consvar_t * cvar,byte cfg,consvar_t * temp_cvar)1869 boolean  CV_Get_Pushed_cvar( consvar_t * cvar, byte cfg, /*OUT*/ consvar_t * temp_cvar )
1870 {
1871     cv_pushed_t * pp = find_pushed_cvar( cvar, cfg );
1872     if( !pp )
1873         return false;
1874 
1875     if( temp_cvar )
1876     {
1877         memcpy( temp_cvar, cvar, sizeof(consvar_t) );  // setup values
1878 
1879         temp_cvar->string = NULL;  // must be Z_StrDup, not copied
1880         CV_Set_cvar_string( temp_cvar, pp->string );   // may get edited
1881 
1882         temp_cvar->value = pp->value;
1883         temp_cvar->state = pp->state;
1884         // EV is not needed
1885     }
1886     return true;
1887 }
1888 
1889 
1890 // Put the values in the temp cvar, into the pushed or current cvar.
1891 //   temp_cvar : copy of the cvar values, cannot be NULL
1892 // The string value of temp_cvar will be stolen.
CV_Put_Config_cvar(consvar_t * cvar,byte cfg,consvar_t * temp_cvar)1893 void  CV_Put_Config_cvar( consvar_t * cvar, byte cfg, /*IN*/ consvar_t * temp_cvar )
1894 {
1895     cv_pushed_t * pp;
1896     byte current_cfg = cvar->state & CS_CONFIG;
1897 
1898     if( cfg == current_cfg )
1899         goto update_cvar;  // put to the current cvar
1900 
1901     if( cfg > current_cfg )
1902     {
1903         // push the current cvar
1904         CV_Push_Config( cvar, cfg ); // change current to cfg
1905         goto update_cvar;  // put to the new current cvar
1906     }
1907 
1908     // assert (cfg < current_cfg)
1909     // Put to the pushed cvar record.
1910     pp = find_pushed_cvar( cvar, cfg );
1911     if( pp )
1912     {
1913         // Free the existing string value first.
1914         CV_Free_cvar_string_param( cvar, pp->string );
1915     }
1916     else
1917     {
1918         // Not found, make a new one.
1919         pp = create_pushed_cvar( cvar );
1920         if( pp == NULL )  return;
1921     }
1922     // assert (pp->string == NULL or invalid)
1923     // Move the temp_cvar string to the pushed record.
1924     pp->string = temp_cvar->string;
1925     temp_cvar->string = NULL;
1926 
1927     pp->value = temp_cvar->value;
1928     pp->state = (temp_cvar->state & ~CS_CONFIG) | cfg;
1929     cvar->state |= CS_PUSHED;  // Mark existance of pushed cfg.
1930     return;
1931 
1932 update_cvar:
1933     // Update current cvar
1934     CV_Free_cvar_string( cvar );
1935     cvar->string = temp_cvar->string; // move the string
1936     temp_cvar->string = NULL;
1937 
1938     // If this happens during a demo or other usage, protections have already been applied.
1939     cvar->value = temp_cvar->value;
1940     cvar->EV = temp_cvar->value;
1941     // preserve CS_PUSHED
1942     cvar->state = (cvar->state & ~CS_CONFIG) | cfg | (temp_cvar->state & CS_MODIFIED);
1943 
1944     // Current cvar always does CV_CALL.
1945     CV_cvar_call( cvar, 1 );  // user_enable detected in new config.
1946     return;
1947 }
1948 
1949 
1950 // Return the string value of the config var, current or pushed.
1951 // Return NULL if not found.
CV_Get_Config_string(consvar_t * cvar,byte cfg)1952 const char *  CV_Get_Config_string( consvar_t * cvar, byte cfg )
1953 {
1954     // Check the current cvar first.
1955     if( (cvar->state & CS_CONFIG) == cfg )
1956         return cvar->string;
1957 
1958     if( cvar->state & CS_PUSHED )
1959     {
1960         cv_pushed_t * pp = find_pushed_cvar( cvar, cfg );
1961         if( pp )
1962             return pp->string;
1963     }
1964 
1965     return NULL;  // not found
1966 }
1967 
1968 // Put the string value to the pushed or current cvar.
1969 // This will create or push, as needed.
1970 //   str : str value, will be copied.  Will set numeric value too.
CV_Put_Config_string(consvar_t * cvar,byte cfg,const char * str)1971 void  CV_Put_Config_string( consvar_t * cvar, byte cfg, const char * str )
1972 {
1973     consvar_t  new_cvar;
1974 
1975     // Copy the parent cvar, need PossibleValue and flags.
1976     memcpy( &new_cvar, cvar, sizeof(consvar_t) );
1977     new_cvar.string = NULL;
1978     if( (cvar->state & CS_CONFIG) > cfg )
1979     {
1980         // Create as pushed cvar value.
1981         // Kill any effects that only the current cvar should perform.
1982         // Pushed cvar does not save these flags, flags will be gotten from parent.
1983         new_cvar.flags &= ~( CV_CALL | CV_NETVAR | CV_SHOWMODIF | CV_SHOWMODIF_ONCE );
1984     }
1985 
1986     // Copy the new value into the temp cvar.
1987     CV_set_str_value( &new_cvar, str, 0, 1 );
1988 
1989     // Create the cvar value, even if it is pushed and not current.
1990     // This will steal the string value from temp_cvar.  Do not need to Z_Free it.
1991     CV_Put_Config_cvar( cvar, cfg, &new_cvar );  // does CV_CALL
1992 }
1993 
1994 
1995 
1996 // Remove the cvar value for the config.
CV_Delete_Config_cvar(consvar_t * cvar,byte cfg)1997 void  CV_Delete_Config_cvar( consvar_t * cvar, byte cfg )
1998 {
1999     // Check current cvar first
2000     if( (cvar->state & CS_CONFIG) == cfg )
2001     {
2002         // Remove the current cvar
2003         if( CV_Pop_Config( cvar ) == 0 )
2004         {
2005             // No pushed value found
2006             cvar->state &= ~CS_CONFIG; // set config to 0
2007         }
2008     }
2009     else
2010     {
2011         // Remove the pushed cvar.
2012         cv_pushed_t * pp = find_pushed_cvar( cvar, cfg );
2013         if( pp )
2014         {
2015             release_pushed_cvar( pp );  // free the string too
2016         }
2017     }
2018 }
2019 
2020 
2021 // Clear all values that came from the config file.
CV_Clear_Config(byte cfg)2022 void  CV_Clear_Config( byte cfg )
2023 {
2024     consvar_t * cv;
2025 
2026     // Clear from pushed list
2027     cv_pushed_t * pp = cvar_pushed_list;
2028     while( pp )
2029     {
2030         cv_pushed_t * ppn = pp->next;
2031         if((pp->state & CS_CONFIG) == cfg )
2032         {
2033             cv = pp->parent;  // get it before releasing
2034             release_pushed_cvar( pp );  // free the string too
2035             update_pushed_cvar( cv );  // update CS_PUSHED
2036         }
2037         pp = ppn;
2038     }
2039 
2040     // Clear from current cv vars.
2041     for( cv = CV_IteratorFirst(); cv ; cv = CV_Iterator(cv))
2042     {
2043         if( (cv->state & CS_CONFIG) == cfg )
2044             CV_Pop_Config(cv);
2045     }
2046 }
2047 
2048 
2049 // Check for drawmode CV variables.
2050 // Return true if any value of the config is current, or pushed.
CV_Config_check(byte cfg)2051 boolean CV_Config_check( byte cfg )
2052 {
2053     consvar_t * cv;
2054     for( cv = CV_IteratorFirst(); cv ; cv = CV_Iterator( cv ) )
2055     {
2056         if( (cv->flags & CV_SAVE) == 0 )   continue;
2057 
2058         if( (cv->state & CS_CONFIG) == cfg )
2059             return true;
2060 
2061         if( cv->state & CS_PUSHED )
2062         {
2063             if( CV_Get_Pushed_cvar( cv, cfg, NULL ) )
2064                 return true;
2065         }
2066     }
2067     return false;
2068 }
2069 
2070 
2071 
2072 // =================
2073 //
2074 
2075 
2076 //  Allow display of variable content or change from the console
2077 //
2078 //  Returns false if the passed command was not recognised as
2079 //  console variable.
2080 //
2081 //   cfg : cv_config_e
CV_Var_Command(byte cfg)2082 static boolean CV_Var_Command ( byte cfg )
2083 {
2084     consvar_t   *cvar;
2085     const char * tstr;
2086     COM_args_t  carg;
2087     int tval;
2088 
2089     COM_Args( &carg );
2090 
2091     // check variables
2092     cvar = CV_FindVar ( carg.arg[0] );
2093     if(!cvar)
2094         return false;
2095 
2096     // perform a variable print or set
2097     if ( carg.num == 1 )  goto show_value;
2098 
2099     // Set value
2100     cfg &= CS_CONFIG;  // only config selection
2101     if( cfg )
2102     {
2103         if( cvar->flags & CV_CFG1 )
2104         {
2105             // A restricted var cannot be loaded from the other config files.
2106             if( cfg != CFG_main )
2107                 return false;
2108         }
2109         if( cvar->flags & CV_NETVAR )
2110         {
2111             // A NETVAR cannot be loaded from the drawmode config file.
2112             if( cfg == CFG_drawmode )
2113                 return false;
2114         }
2115 
2116         byte old_cfg = cvar->state & CS_CONFIG;
2117         if( old_cfg )
2118         {
2119             // current cvar values are not the default values.
2120             if( old_cfg < cfg )
2121             {
2122                 // Push the current value
2123                 CV_Push_Config( cvar, cfg );
2124             }
2125             else if( old_cfg > cfg )
2126             {
2127                 // It likely was pushed already.
2128                 // Do not use CV_Set, because this will not become the current value.
2129                 CV_Put_Config_string( cvar, cfg, carg.arg[1] );
2130                 return true;
2131             }
2132         }
2133 
2134         // Record which config file the setting comes from.
2135         cvar->state &= ~CS_CONFIG;
2136         cvar->state |= cfg;
2137     }
2138 
2139     CV_Set (cvar, carg.arg[1] );
2140     return true;
2141 
2142 show_value:
2143     if( cvar->flags & CV_STRING )  goto std_show_str;
2144     if( cvar->flags & CV_VALUE )
2145     {
2146         if( cvar->value == atoi(cvar->string) )  goto std_show_str;
2147         tval = cvar->value;
2148     }
2149     else if( (cvar->state & CS_EV_PARAM)  // command line param in EV
2150         || (cvar->EV != (byte)cvar->value) )
2151     {
2152         tval = cvar->EV;
2153     }
2154     else goto std_show_str;
2155 
2156     if( cvar->PossibleValue )
2157     {
2158         // Search the PossibleValue for the value.
2159         CV_PossibleValue_t * pv;
2160         for( pv = cvar->PossibleValue; pv->strvalue; pv++)
2161         {
2162             if( pv->value == tval )
2163             {
2164                 tstr = pv->strvalue;
2165                 goto show_by_str;
2166             }
2167         }
2168     }
2169 
2170     // show by value
2171     CONS_Printf ("\"%s\" is \"%i\" config \"%s\" default is \"%s\"\n",
2172                  cvar->name, tval, cvar->string, cvar->defaultvalue);
2173     return true;
2174 
2175 show_by_str:
2176     CONS_Printf ("\"%s\" is \"%s\" config \"%s\" default is \"%s\"\n",
2177                  cvar->name, tstr, cvar->string, cvar->defaultvalue);
2178     return true;
2179 
2180 std_show_str:
2181     CONS_Printf ("\"%s\" is \"%s\" default is \"%s\"\n",
2182                  cvar->name, cvar->string, cvar->defaultvalue);
2183     return true;
2184 }
2185 
2186 
2187 
2188 //  Support for saving the console variables that have the CV_SAVE flag set.
2189 //  This has less splitting of the logic between three functions,
2190 //  and does not require passing a FILE ptr around.
CV_IteratorFirst(void)2191 consvar_t *  CV_IteratorFirst( void )
2192 {
2193     return consvar_vars;
2194 }
2195 
CV_Iterator(consvar_t * cv)2196 consvar_t *  CV_Iterator( consvar_t * cv )
2197 {
2198     return cv->next;
2199 }
2200 
2201 
2202 
2203 //============================================================================
2204 //                            SCRIPT PARSE
2205 //============================================================================
2206 
2207 //  Parse a token out of a string, handles script files too
2208 //  returns the data pointer after the token
2209 //  Do not mangle filenames, set script only where strings might have '\' escapes.
COM_Parse(const char * data,boolean script)2210 static const char * COM_Parse (const char * data, boolean script)
2211 {
2212     int c;
2213     int len = 0;
2214     com_token[0] = '\0';
2215 
2216     if (!data)
2217         return NULL;
2218 
2219 // skip whitespace
2220 skipwhite:
2221     while ( (c = *data) <= ' ')
2222     {
2223         if (!c)
2224             return NULL;            // end of file;
2225         data++;
2226     }
2227 
2228 // skip // comments
2229     // Also may be Linux filename: //home/user/.legacy
2230     if ( script && (c == '/' && data[1] == '/'))
2231     {
2232         while (*data && *data != '\n')
2233             data++;
2234         goto skipwhite;
2235     }
2236 
2237 
2238 // handle quoted strings specially
2239     if (c == '"')
2240     {
2241         data++;
2242         while ( len < COM_TOKEN_MAX-1 )
2243         {
2244             c = *data++;
2245             if (!c)
2246             {
2247               // NUL in the middle of a quoted string. Missing closing quote?
2248               CONS_Printf("Error: Quoted string ended prematurely.\n");
2249               goto term_done;
2250             }
2251 
2252             if (c == '"') // closing quote
2253               goto term_done;
2254 
2255             if ( script && (c == '\\')) // c-like escape sequence
2256             {
2257               switch (*data)
2258               {
2259               case '\\':  // backslash
2260                 com_token[len++] = '\\'; break;
2261 
2262               case '"':  // double quote
2263                 com_token[len++] = '"'; break;
2264 
2265               case 't':  // tab
2266                 com_token[len++] = '\t'; break;
2267 
2268               case 'n':  // newline
2269                 com_token[len++] = '\n'; break;
2270 
2271               default:
2272                 CONS_Printf("Error: Unknown escape sequence '\\%c'\n", *data);
2273                 break;
2274               }
2275 
2276               data++;
2277               continue;
2278             }
2279 
2280             // normal char
2281             com_token[len++] = c;
2282         }
2283     }
2284 
2285 // parse single characters
2286     // Also ':' can appear in WIN path names
2287     if (script && (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':'))
2288     {
2289       if( len >= COM_TOKEN_MAX-2 )  goto term_done;
2290       com_token[len++] = c;
2291       data++;
2292       goto term_done;
2293     }
2294 
2295 // parse a regular word
2296     do
2297     {
2298       if( len >= COM_TOKEN_MAX-2 )  goto term_done;
2299       com_token[len++] = c;
2300       data++;
2301       c = *data;
2302       if (script && (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':'))
2303         break;
2304     } while (c > ' ');
2305 
2306 term_done:
2307     com_token[len] = '\0';
2308     return data;
2309 }
2310