1 /*
2 ** c_dispatch.cpp
3 ** Functions for executing console commands and aliases
4 **
5 **---------------------------------------------------------------------------
6 ** Copyright 1998-2007 Randy Heit
7 ** All rights reserved.
8 **
9 ** Redistribution and use in source and binary forms, with or without
10 ** modification, are permitted provided that the following conditions
11 ** are met:
12 **
13 ** 1. Redistributions of source code must retain the above copyright
14 **    notice, this list of conditions and the following disclaimer.
15 ** 2. Redistributions in binary form must reproduce the above copyright
16 **    notice, this list of conditions and the following disclaimer in the
17 **    documentation and/or other materials provided with the distribution.
18 ** 3. The name of the author may not be used to endorse or promote products
19 **    derived from this software without specific prior written permission.
20 **
21 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 **---------------------------------------------------------------------------
32 **
33 */
34 
35 // HEADER FILES ------------------------------------------------------------
36 
37 #include <stdlib.h>
38 #include <stdio.h>
39 #include <string.h>
40 #include <ctype.h>
41 #include <assert.h>
42 
43 #include "templates.h"
44 #include "doomtype.h"
45 #include "cmdlib.h"
46 #include "c_console.h"
47 #include "c_dispatch.h"
48 #include "m_argv.h"
49 #include "doomstat.h"
50 #include "d_player.h"
51 #include "configfile.h"
52 #include "m_crc32.h"
53 #include "v_text.h"
54 #include "d_net.h"
55 #include "d_main.h"
56 #include "farchive.h"
57 
58 // MACROS ------------------------------------------------------------------
59 
60 // TYPES -------------------------------------------------------------------
61 
62 class DWaitingCommand : public DThinker
63 {
64 	DECLARE_CLASS (DWaitingCommand, DThinker)
65 public:
66 	DWaitingCommand (const char *cmd, int tics);
67 	~DWaitingCommand ();
68 	void Serialize (FArchive &arc);
69 	void Tick ();
70 
71 private:
72 	DWaitingCommand ();
73 
74 	char *Command;
75 	int TicsLeft;
76 };
77 
78 class DStoredCommand : public DThinker
79 {
80 	DECLARE_CLASS (DStoredCommand, DThinker)
81 public:
82 	DStoredCommand (FConsoleCommand *com, const char *cmd);
83 	~DStoredCommand ();
84 	void Tick ();
85 
86 private:
87 	DStoredCommand ();
88 
89 	FConsoleCommand *Command;
90 	char *Text;
91 };
92 
93 struct FActionMap
94 {
95 	FButtonStatus	*Button;
96 	unsigned int	Key;	// value from passing Name to MakeKey()
97 	char			Name[12];
98 };
99 
100 // EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
101 
102 // PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
103 
104 // PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
105 
106 static long ParseCommandLine (const char *args, int *argc, char **argv, bool no_escapes);
107 static FConsoleCommand *FindNameInHashTable (FConsoleCommand **table, const char *name, size_t namelen);
108 static FConsoleCommand *ScanChainForName (FConsoleCommand *start, const char *name, size_t namelen, FConsoleCommand **prev);
109 
110 // EXTERNAL DATA DECLARATIONS ----------------------------------------------
111 
112 // PUBLIC DATA DEFINITIONS -------------------------------------------------
113 
114 CVAR (Bool, lookspring, true, CVAR_ARCHIVE);	// Generate centerview when -mlook encountered?
115 
116 FConsoleCommand *Commands[FConsoleCommand::HASH_SIZE];
117 FButtonStatus Button_Mlook, Button_Klook, Button_Use, Button_AltAttack,
118 	Button_Attack, Button_Speed, Button_MoveRight, Button_MoveLeft,
119 	Button_Strafe, Button_LookDown, Button_LookUp, Button_Back,
120 	Button_Forward, Button_Right, Button_Left, Button_MoveDown,
121 	Button_MoveUp, Button_Jump, Button_ShowScores, Button_Crouch,
122 	Button_Zoom, Button_Reload,
123 	Button_User1, Button_User2, Button_User3, Button_User4,
124 	Button_AM_PanLeft, Button_AM_PanRight, Button_AM_PanDown, Button_AM_PanUp,
125 	Button_AM_ZoomIn, Button_AM_ZoomOut;
126 
127 bool ParsingKeyConf;
128 
129 // To add new actions, go to the console and type "key <action name>".
130 // This will give you the key value to use in the first column. Then
131 // insert your new action into this list so that the keys remain sorted
132 // in ascending order. No two keys can be identical. If yours matches
133 // an existing key, change the name of your action.
134 
135 FActionMap ActionMaps[] =
136 {
137 	{ &Button_AM_PanLeft,	0x0d52d67b, "am_panleft"},
138 	{ &Button_User2,		0x125f5226, "user2" },
139 	{ &Button_Jump,			0x1eefa611, "jump" },
140 	{ &Button_Right,		0x201f1c55, "right" },
141 	{ &Button_Zoom,			0x20ccc4d5, "zoom" },
142 	{ &Button_Back,			0x23a99cd7, "back" },
143 	{ &Button_AM_ZoomIn,	0x41df90c2, "am_zoomin"},
144 	{ &Button_Reload,		0x426b69e7, "reload" },
145 	{ &Button_LookDown,		0x4463f43a, "lookdown" },
146 	{ &Button_AM_ZoomOut,	0x51f7a334, "am_zoomout"},
147 	{ &Button_User4,		0x534c30ee, "user4" },
148 	{ &Button_Attack,		0x5622bf42, "attack" },
149 	{ &Button_User1,		0x577712d0, "user1" },
150 	{ &Button_Klook,		0x57c25cb2, "klook" },
151 	{ &Button_Forward,		0x59f3e907, "forward" },
152 	{ &Button_MoveDown,		0x6167ce99, "movedown" },
153 	{ &Button_AltAttack,	0x676885b8, "altattack" },
154 	{ &Button_MoveLeft,		0x6fa41b84, "moveleft" },
155 	{ &Button_MoveRight,	0x818f08e6, "moveright" },
156 	{ &Button_AM_PanRight,	0x8197097b, "am_panright"},
157 	{ &Button_AM_PanUp,		0x8d89955e, "am_panup"} ,
158 	{ &Button_Mlook,		0xa2b62d8b, "mlook" },
159 	{ &Button_Crouch,		0xab2c3e71, "crouch" },
160 	{ &Button_Left,			0xb000b483, "left" },
161 	{ &Button_LookUp,		0xb62b1e49, "lookup" },
162 	{ &Button_User3,		0xb6f8fe92, "user3" },
163 	{ &Button_Strafe,		0xb7e6a54b, "strafe" },
164 	{ &Button_AM_PanDown,	0xce301c81, "am_pandown"},
165 	{ &Button_ShowScores,	0xd5897c73, "showscores" },
166 	{ &Button_Speed,		0xe0ccb317, "speed" },
167 	{ &Button_Use,			0xe0cfc260, "use" },
168 	{ &Button_MoveUp,		0xfdd701c7, "moveup" },
169 };
170 #define NUM_ACTIONS countof(ActionMaps)
171 
172 
173 // PRIVATE DATA DEFINITIONS ------------------------------------------------
174 
175 static const char *KeyConfCommands[] =
176 {
177 	"alias",
178 	"defaultbind",
179 	"addkeysection",
180 	"addmenukey",
181 	"addslotdefault",
182 	"weaponsection",
183 	"setslot",
184 	"addplayerclass",
185 	"clearplayerclasses"
186 };
187 
188 // CODE --------------------------------------------------------------------
189 
IMPLEMENT_CLASS(DWaitingCommand)190 IMPLEMENT_CLASS (DWaitingCommand)
191 
192 void DWaitingCommand::Serialize (FArchive &arc)
193 {
194 	Super::Serialize (arc);
195 	arc << Command << TicsLeft;
196 }
197 
DWaitingCommand()198 DWaitingCommand::DWaitingCommand ()
199 {
200 	Command = NULL;
201 	TicsLeft = 1;
202 }
203 
DWaitingCommand(const char * cmd,int tics)204 DWaitingCommand::DWaitingCommand (const char *cmd, int tics)
205 {
206 	Command = copystring (cmd);
207 	TicsLeft = tics+1;
208 }
209 
~DWaitingCommand()210 DWaitingCommand::~DWaitingCommand ()
211 {
212 	if (Command != NULL)
213 	{
214 		delete[] Command;
215 	}
216 }
217 
Tick()218 void DWaitingCommand::Tick ()
219 {
220 	if (--TicsLeft == 0)
221 	{
222 		AddCommandString (Command);
223 		Destroy ();
224 	}
225 }
226 
IMPLEMENT_CLASS(DStoredCommand)227 IMPLEMENT_CLASS (DStoredCommand)
228 
229 DStoredCommand::DStoredCommand ()
230 {
231 	Text = NULL;
232 	Destroy ();
233 }
234 
DStoredCommand(FConsoleCommand * command,const char * args)235 DStoredCommand::DStoredCommand (FConsoleCommand *command, const char *args)
236 {
237 	Command = command;
238 	Text = copystring (args);
239 }
240 
~DStoredCommand()241 DStoredCommand::~DStoredCommand ()
242 {
243 	if (Text != NULL)
244 	{
245 		delete[] Text;
246 	}
247 }
248 
Tick()249 void DStoredCommand::Tick ()
250 {
251 	if (Text != NULL && Command != NULL)
252 	{
253 		FCommandLine args (Text);
254 		Command->Run (args, players[consoleplayer].mo, 0);
255 	}
256 	Destroy ();
257 }
258 
ListActionCommands(const char * pattern)259 static int ListActionCommands (const char *pattern)
260 {
261 	char matcher[16];
262 	unsigned int i;
263 	int count = 0;
264 
265 	for (i = 0; i < NUM_ACTIONS; ++i)
266 	{
267 		if (pattern == NULL || CheckWildcards (pattern,
268 			(mysnprintf (matcher, countof(matcher), "+%s", ActionMaps[i].Name), matcher)))
269 		{
270 			Printf ("+%s\n", ActionMaps[i].Name);
271 			count++;
272 		}
273 		if (pattern == NULL || CheckWildcards (pattern,
274 			(mysnprintf (matcher, countof(matcher), "-%s", ActionMaps[i].Name), matcher)))
275 		{
276 			Printf ("-%s\n", ActionMaps[i].Name);
277 			count++;
278 		}
279 	}
280 	return count;
281 }
282 
283 /* ======================================================================== */
284 
285 /* By Paul Hsieh (C) 2004, 2005.  Covered under the Paul Hsieh derivative
286    license. See:
287    http://www.azillionmonkeys.com/qed/weblicense.html for license details.
288 
289    http://www.azillionmonkeys.com/qed/hash.html */
290 
291 #undef get16bits
292 #if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \
293   || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__)
294 #define get16bits(d) (*((const WORD *) (d)))
295 #endif
296 
297 #if !defined (get16bits)
298 #define get16bits(d) ((((DWORD)(((const BYTE *)(d))[1])) << 8)\
299                        +(DWORD)(((const BYTE *)(d))[0]) )
300 #endif
301 
SuperFastHash(const char * data,size_t len)302 DWORD SuperFastHash (const char *data, size_t len)
303 {
304 	DWORD hash = 0, tmp;
305 	size_t rem;
306 
307 	if (len == 0 || data == NULL) return 0;
308 
309 	rem = len & 3;
310 	len >>= 2;
311 
312 	/* Main loop */
313 	for (;len > 0; len--)
314 	{
315 		hash  += get16bits (data);
316 		tmp    = (get16bits (data+2) << 11) ^ hash;
317 		hash   = (hash << 16) ^ tmp;
318 		data  += 2*sizeof (WORD);
319 		hash  += hash >> 11;
320 	}
321 
322 	/* Handle end cases */
323 	switch (rem)
324 	{
325 		case 3:	hash += get16bits (data);
326 				hash ^= hash << 16;
327 				hash ^= data[sizeof (WORD)] << 18;
328 				hash += hash >> 11;
329 				break;
330 		case 2:	hash += get16bits (data);
331 				hash ^= hash << 11;
332 				hash += hash >> 17;
333 				break;
334 		case 1: hash += *data;
335 				hash ^= hash << 10;
336 				hash += hash >> 1;
337 	}
338 
339 	/* Force "avalanching" of final 127 bits */
340 	hash ^= hash << 3;
341 	hash += hash >> 5;
342 	hash ^= hash << 4;
343 	hash += hash >> 17;
344 	hash ^= hash << 25;
345 	hash += hash >> 6;
346 
347 	return hash;
348 }
349 
350 /* A modified version to do a case-insensitive hash */
351 
352 #undef get16bits
353 #define get16bits(d) ((((DWORD)tolower(((const BYTE *)(d))[1])) << 8)\
354                        +(DWORD)tolower(((const BYTE *)(d))[0]) )
355 
SuperFastHashI(const char * data,size_t len)356 DWORD SuperFastHashI (const char *data, size_t len)
357 {
358 	DWORD hash = 0, tmp;
359 	size_t rem;
360 
361 	if (len <= 0 || data == NULL) return 0;
362 
363 	rem = len & 3;
364 	len >>= 2;
365 
366 	/* Main loop */
367 	for (;len > 0; len--)
368 	{
369 		hash  += get16bits (data);
370 		tmp    = (get16bits (data+2) << 11) ^ hash;
371 		hash   = (hash << 16) ^ tmp;
372 		data  += 2*sizeof (WORD);
373 		hash  += hash >> 11;
374 	}
375 
376 	/* Handle end cases */
377 	switch (rem)
378 	{
379 		case 3:	hash += get16bits (data);
380 				hash ^= hash << 16;
381 				hash ^= tolower(data[sizeof (WORD)]) << 18;
382 				hash += hash >> 11;
383 				break;
384 		case 2:	hash += get16bits (data);
385 				hash ^= hash << 11;
386 				hash += hash >> 17;
387 				break;
388 		case 1: hash += tolower(*data);
389 				hash ^= hash << 10;
390 				hash += hash >> 1;
391 	}
392 
393 	/* Force "avalanching" of final 127 bits */
394 	hash ^= hash << 3;
395 	hash += hash >> 5;
396 	hash ^= hash << 4;
397 	hash += hash >> 17;
398 	hash ^= hash << 25;
399 	hash += hash >> 6;
400 
401 	return hash;
402 }
403 
404 /* ======================================================================== */
405 
MakeKey(const char * s)406 unsigned int MakeKey (const char *s)
407 {
408 	if (s == NULL)
409 	{
410 		return 0;
411 	}
412 	return SuperFastHashI (s, strlen (s));
413 }
414 
MakeKey(const char * s,size_t len)415 unsigned int MakeKey (const char *s, size_t len)
416 {
417 	return SuperFastHashI (s, len);
418 }
419 
420 // FindButton scans through the actionbits[] array
421 // for a matching key and returns an index or -1 if
422 // the key could not be found. This uses binary search,
423 // so actionbits[] must be sorted in ascending order.
424 
FindButton(unsigned int key)425 FButtonStatus *FindButton (unsigned int key)
426 {
427 	const FActionMap *bit;
428 
429 	bit = BinarySearch<FActionMap, unsigned int>
430 			(ActionMaps, NUM_ACTIONS, &FActionMap::Key, key);
431 	return bit ? bit->Button : NULL;
432 }
433 
PressKey(int keynum)434 bool FButtonStatus::PressKey (int keynum)
435 {
436 	int i, open;
437 
438 	keynum &= KEY_DBLCLICKED-1;
439 
440 	if (keynum == 0)
441 	{ // Issued from console instead of a key, so force on
442 		Keys[0] = 0xffff;
443 		for (i = MAX_KEYS-1; i > 0; --i)
444 		{
445 			Keys[i] = 0;
446 		}
447 	}
448 	else
449 	{
450 		for (i = MAX_KEYS-1, open = -1; i >= 0; --i)
451 		{
452 			if (Keys[i] == 0)
453 			{
454 				open = i;
455 			}
456 			else if (Keys[i] == keynum)
457 			{ // Key is already down; do nothing
458 				return false;
459 			}
460 		}
461 		if (open < 0)
462 		{ // No free key slots, so do nothing
463 			Printf ("More than %u keys pressed for a single action!\n", MAX_KEYS);
464 			return false;
465 		}
466 		Keys[open] = keynum;
467 	}
468 	BYTE wasdown = bDown;
469 	bDown = bWentDown = true;
470 	// Returns true if this key caused the button to go down.
471 	return !wasdown;
472 }
473 
ReleaseKey(int keynum)474 bool FButtonStatus::ReleaseKey (int keynum)
475 {
476 	int i, numdown, match;
477 	BYTE wasdown = bDown;
478 
479 	keynum &= KEY_DBLCLICKED-1;
480 
481 	if (keynum == 0)
482 	{ // Issued from console instead of a key, so force off
483 		for (i = MAX_KEYS-1; i >= 0; --i)
484 		{
485 			Keys[i] = 0;
486 		}
487 		bWentUp = true;
488 		bDown = false;
489 	}
490 	else
491 	{
492 		for (i = MAX_KEYS-1, numdown = 0, match = -1; i >= 0; --i)
493 		{
494 			if (Keys[i] != 0)
495 			{
496 				++numdown;
497 				if (Keys[i] == keynum)
498 				{
499 					match = i;
500 				}
501 			}
502 		}
503 		if (match < 0)
504 		{ // Key was not down; do nothing
505 			return false;
506 		}
507 		Keys[match] = 0;
508 		bWentUp = true;
509 		if (--numdown == 0)
510 		{
511 			bDown = false;
512 		}
513 	}
514 	// Returns true if releasing this key caused the button to go up.
515 	return wasdown && !bDown;
516 }
517 
ResetButtonTriggers()518 void ResetButtonTriggers ()
519 {
520 	for (int i = NUM_ACTIONS-1; i >= 0; --i)
521 	{
522 		ActionMaps[i].Button->ResetTriggers ();
523 	}
524 }
525 
ResetButtonStates()526 void ResetButtonStates ()
527 {
528 	for (int i = NUM_ACTIONS-1; i >= 0; --i)
529 	{
530 		FButtonStatus *button = ActionMaps[i].Button;
531 
532 		if (button != &Button_Mlook && button != &Button_Klook)
533 		{
534 			button->ReleaseKey (0);
535 		}
536 		button->ResetTriggers ();
537 	}
538 }
539 
C_DoCommand(const char * cmd,int keynum)540 void C_DoCommand (const char *cmd, int keynum)
541 {
542 	FConsoleCommand *com;
543 	const char *end;
544 	const char *beg;
545 
546 	// Skip any beginning whitespace
547 	while (*cmd && *cmd <= ' ')
548 		cmd++;
549 
550 	// Find end of the command name
551 	if (*cmd == '\"')
552 	{
553 		for (end = beg = cmd+1; *end && *end != '\"'; ++end)
554 			;
555 	}
556 	else
557 	{
558 		beg = cmd;
559 		for (end = cmd+1; *end > ' '; ++end)
560 			;
561 	}
562 
563 	const size_t len = end - beg;
564 
565 	if (ParsingKeyConf)
566 	{
567 		int i;
568 
569 		for (i = countof(KeyConfCommands)-1; i >= 0; --i)
570 		{
571 			if (strnicmp (beg, KeyConfCommands[i], len) == 0 &&
572 				KeyConfCommands[i][len] == 0)
573 			{
574 				break;
575 			}
576 		}
577 		if (i < 0)
578 		{
579 			Printf ("Invalid command for KEYCONF: %s\n", beg);
580 			return;
581 		}
582 	}
583 
584 	// Check if this is an action
585 	if (*beg == '+' || *beg == '-')
586 	{
587 		FButtonStatus *button;
588 
589 		button = FindButton (MakeKey (beg + 1, end - beg - 1));
590 		if (button != NULL)
591 		{
592 			if (*beg == '+')
593 			{
594 				button->PressKey (keynum);
595 			}
596 			else
597 			{
598 				button->ReleaseKey (keynum);
599 				if (button == &Button_Mlook && lookspring)
600 				{
601 					Net_WriteByte (DEM_CENTERVIEW);
602 				}
603 			}
604 			return;
605 		}
606 	}
607 
608 	// Parse it as a normal command
609 	// Checking for matching commands follows this search order:
610 	//	1. Check the Commands[] hash table
611 	//	2. Check the CVars list
612 
613 	if ( (com = FindNameInHashTable (Commands, beg, len)) )
614 	{
615 		if (gamestate != GS_STARTUP || ParsingKeyConf ||
616 			(len == 3 && strnicmp (beg, "set", 3) == 0) ||
617 			(len == 7 && strnicmp (beg, "logfile", 7) == 0) ||
618 			(len == 9 && strnicmp (beg, "unbindall", 9) == 0) ||
619 			(len == 4 && strnicmp (beg, "bind", 4) == 0) ||
620 			(len == 4 && strnicmp (beg, "exec", 4) == 0) ||
621 			(len ==10 && strnicmp (beg, "doublebind", 10) == 0) ||
622 			(len == 6 && strnicmp (beg, "pullin", 6) == 0)
623 			)
624 		{
625 			FCommandLine args (beg);
626 			com->Run (args, players[consoleplayer].mo, keynum);
627 		}
628 		else
629 		{
630 			if (len == 4 && strnicmp(beg, "warp", 4) == 0)
631 			{
632 				StoredWarp = beg;
633 			}
634 			else
635 			{
636 				new DStoredCommand (com, beg);
637 			}
638 		}
639 	}
640 	else
641 	{ // Check for any console vars that match the command
642 		FBaseCVar *var = FindCVarSub (beg, int(len));
643 
644 		if (var != NULL)
645 		{
646 			FCommandLine args (beg);
647 
648 			if (args.argc() >= 2)
649 			{ // Set the variable
650 				var->CmdSet (args[1]);
651 			}
652 			else
653 			{ // Get the variable's value
654 				UCVarValue val = var->GetGenericRep (CVAR_String);
655 				Printf ("\"%s\" is \"%s\"\n", var->GetName(), val.String);
656 			}
657 		}
658 		else
659 		{ // We don't know how to handle this command
660 			Printf ("Unknown command \"%.*s\"\n", (int)len, beg);
661 		}
662 	}
663 }
664 
AddCommandString(char * cmd,int keynum)665 void AddCommandString (char *cmd, int keynum)
666 {
667 	char *brkpt;
668 	int more;
669 
670 	if (cmd)
671 	{
672 		while (*cmd)
673 		{
674 			brkpt = cmd;
675 			while (*brkpt != ';' && *brkpt != '\0')
676 			{
677 				if (*brkpt == '\"')
678 				{
679 					brkpt++;
680 					while (*brkpt != '\0' && (*brkpt != '\"' || *(brkpt-1) == '\\'))
681 						brkpt++;
682 				}
683 				if (*brkpt != '\0')
684 					brkpt++;
685 			}
686 			if (*brkpt == ';')
687 			{
688 				*brkpt = '\0';
689 				more = 1;
690 			}
691 			else
692 			{
693 				more = 0;
694 			}
695 			// Intercept wait commands here. Note: wait must be lowercase
696 			while (*cmd && *cmd <= ' ')
697 				cmd++;
698 			if (*cmd)
699 			{
700 				if (!ParsingKeyConf &&
701 					cmd[0] == 'w' && cmd[1] == 'a' && cmd[2] == 'i' && cmd[3] == 't' &&
702 					(cmd[4] == 0 || cmd[4] == ' '))
703 				{
704 					int tics;
705 
706 					if (cmd[4] == ' ')
707 					{
708 						tics = strtol (cmd + 5, NULL, 0);
709 					}
710 					else
711 					{
712 						tics = 1;
713 					}
714 					if (tics > 0)
715 					{
716 						if (more)
717 						{ // The remainder of the command will be executed later
718 						  // Note that deferred commands lose track of which key
719 						  // (if any) they were pressed from.
720 							*brkpt = ';';
721 							new DWaitingCommand (brkpt, tics);
722 						}
723 						return;
724 					}
725 				}
726 				else
727 				{
728 					C_DoCommand (cmd, keynum);
729 				}
730 			}
731 			if (more)
732 			{
733 				*brkpt = ';';
734 			}
735 			cmd = brkpt + more;
736 		}
737 	}
738 }
739 
740 // ParseCommandLine
741 //
742 // Parse a command line (passed in args). If argc is non-NULL, it will
743 // be set to the number of arguments. If argv is non-NULL, it will be
744 // filled with pointers to each argument; argv[0] should be initialized
745 // to point to a buffer large enough to hold all the arguments. The
746 // return value is the necessary size of this buffer.
747 //
748 // Special processing:
749 //   Inside quoted strings, \" becomes just "
750 //                          \\ becomes just a single backslash
751 //							\c becomes just TEXTCOLOR_ESCAPE
752 //   $<cvar> is replaced by the contents of <cvar>
753 
ParseCommandLine(const char * args,int * argc,char ** argv,bool no_escapes)754 static long ParseCommandLine (const char *args, int *argc, char **argv, bool no_escapes)
755 {
756 	int count;
757 	char *buffplace;
758 
759 	count = 0;
760 	buffplace = NULL;
761 	if (argv != NULL)
762 	{
763 		buffplace = argv[0];
764 	}
765 
766 	for (;;)
767 	{
768 		while (*args <= ' ' && *args)
769 		{ // skip white space
770 			args++;
771 		}
772 		if (*args == 0)
773 		{
774 			break;
775 		}
776 		else if (*args == '\"')
777 		{ // read quoted string
778 			char stuff;
779 			if (argv != NULL)
780 			{
781 				argv[count] = buffplace;
782 			}
783 			count++;
784 			args++;
785 			do
786 			{
787 				stuff = *args++;
788 				if (!no_escapes && stuff == '\\' && *args == '\"')
789 				{
790 					stuff = '\"', args++;
791 				}
792 				else if (!no_escapes && stuff == '\\' && *args == '\\')
793 				{
794 					args++;
795 				}
796 				else if (!no_escapes && stuff == '\\' && *args == 'c')
797 				{
798 					stuff = TEXTCOLOR_ESCAPE, args++;
799 				}
800 				else if (stuff == '\"')
801 				{
802 					stuff = 0;
803 				}
804 				else if (stuff == 0)
805 				{
806 					args--;
807 				}
808 				if (argv != NULL)
809 				{
810 					*buffplace = stuff;
811 				}
812 				buffplace++;
813 			} while (stuff);
814 		}
815 		else
816 		{ // read unquoted string
817 			const char *start = args++, *end;
818 			FBaseCVar *var;
819 			UCVarValue val;
820 
821 			while (*args && *args > ' ' && *args != '\"')
822 				args++;
823 			if (*start == '$' && (var = FindCVarSub (start+1, int(args-start-1))))
824 			{
825 				val = var->GetGenericRep (CVAR_String);
826 				start = val.String;
827 				end = start + strlen (start);
828 			}
829 			else
830 			{
831 				end = args;
832 			}
833 			if (argv != NULL)
834 			{
835 				argv[count] = buffplace;
836 				while (start < end)
837 					*buffplace++ = *start++;
838 				*buffplace++ = 0;
839 			}
840 			else
841 			{
842 				buffplace += end - start + 1;
843 			}
844 			count++;
845 		}
846 	}
847 	if (argc != NULL)
848 	{
849 		*argc = count;
850 	}
851 	return (long)(buffplace - (char *)0);
852 }
853 
FCommandLine(const char * commandline,bool no_escapes)854 FCommandLine::FCommandLine (const char *commandline, bool no_escapes)
855 {
856 	cmd = commandline;
857 	_argc = -1;
858 	_argv = NULL;
859 	noescapes = no_escapes;
860 }
861 
~FCommandLine()862 FCommandLine::~FCommandLine ()
863 {
864 	if (_argv != NULL)
865 	{
866 		delete[] _argv;
867 	}
868 }
869 
Shift()870 void FCommandLine::Shift()
871 {
872 	// Only valid after _argv has been filled.
873 	for (int i = 1; i < _argc; ++i)
874 	{
875 		_argv[i - 1] = _argv[i];
876 	}
877 }
878 
argc()879 int FCommandLine::argc ()
880 {
881 	if (_argc == -1)
882 	{
883 		argsize = ParseCommandLine (cmd, &_argc, NULL, noescapes);
884 	}
885 	return _argc;
886 }
887 
operator [](int i)888 char *FCommandLine::operator[] (int i)
889 {
890 	if (_argv == NULL)
891 	{
892 		int count = argc();
893 		_argv = new char *[count + (argsize+sizeof(char*)-1)/sizeof(char*)];
894 		_argv[0] = (char *)_argv + count*sizeof(char *);
895 		ParseCommandLine (cmd, NULL, _argv, noescapes);
896 	}
897 	return _argv[i];
898 }
899 
ScanChainForName(FConsoleCommand * start,const char * name,size_t namelen,FConsoleCommand ** prev)900 static FConsoleCommand *ScanChainForName (FConsoleCommand *start, const char *name, size_t namelen, FConsoleCommand **prev)
901 {
902 	int comp;
903 
904 	*prev = NULL;
905 	while (start)
906 	{
907 		comp = strnicmp (start->m_Name, name, namelen);
908 		if (comp > 0)
909 			return NULL;
910 		else if (comp == 0 && start->m_Name[namelen] == 0)
911 			return start;
912 
913 		*prev = start;
914 		start = start->m_Next;
915 	}
916 	return NULL;
917 }
918 
FindNameInHashTable(FConsoleCommand ** table,const char * name,size_t namelen)919 static FConsoleCommand *FindNameInHashTable (FConsoleCommand **table, const char *name, size_t namelen)
920 {
921 	FConsoleCommand *dummy;
922 
923 	return ScanChainForName (table[MakeKey (name, namelen) % FConsoleCommand::HASH_SIZE], name, namelen, &dummy);
924 }
925 
AddToHash(FConsoleCommand ** table)926 bool FConsoleCommand::AddToHash (FConsoleCommand **table)
927 {
928 	unsigned int key;
929 	FConsoleCommand *insert, **bucket;
930 
931 	key = MakeKey (m_Name);
932 	bucket = &table[key % HASH_SIZE];
933 
934 	if (ScanChainForName (*bucket, m_Name, strlen (m_Name), &insert))
935 	{
936 		return false;
937 	}
938 	else
939 	{
940 		if (insert)
941 		{
942 			m_Next = insert->m_Next;
943 			if (m_Next)
944 				m_Next->m_Prev = &m_Next;
945 			insert->m_Next = this;
946 			m_Prev = &insert->m_Next;
947 		}
948 		else
949 		{
950 			m_Next = *bucket;
951 			*bucket = this;
952 			m_Prev = bucket;
953 			if (m_Next)
954 				m_Next->m_Prev = &m_Next;
955 		}
956 	}
957 	return true;
958 }
959 
FindByName(const char * name)960 FConsoleCommand* FConsoleCommand::FindByName (const char* name)
961 {
962 	return FindNameInHashTable (Commands, name, strlen (name));
963 }
964 
FConsoleCommand(const char * name,CCmdRun runFunc)965 FConsoleCommand::FConsoleCommand (const char *name, CCmdRun runFunc)
966 	: m_RunFunc (runFunc)
967 {
968 	static bool firstTime = true;
969 
970 	if (firstTime)
971 	{
972 		char tname[16];
973 		unsigned int i;
974 
975 		firstTime = false;
976 
977 		// Add all the action commands for tab completion
978 		for (i = 0; i < NUM_ACTIONS; i++)
979 		{
980 			strcpy (&tname[1], ActionMaps[i].Name);
981 			tname[0] = '+';
982 			C_AddTabCommand (tname);
983 			tname[0] = '-';
984 			C_AddTabCommand (tname);
985 		}
986 	}
987 
988 	int ag = strcmp (name, "kill");
989 	if (ag == 0)
990 		ag=0;
991 	m_Name = copystring (name);
992 
993 	if (!AddToHash (Commands))
994 		Printf ("FConsoleCommand c'tor: %s exists\n", name);
995 	else
996 		C_AddTabCommand (name);
997 }
998 
~FConsoleCommand()999 FConsoleCommand::~FConsoleCommand ()
1000 {
1001 	*m_Prev = m_Next;
1002 	if (m_Next)
1003 		m_Next->m_Prev = m_Prev;
1004 	C_RemoveTabCommand (m_Name);
1005 	delete[] m_Name;
1006 }
1007 
Run(FCommandLine & argv,APlayerPawn * who,int key)1008 void FConsoleCommand::Run (FCommandLine &argv, APlayerPawn *who, int key)
1009 {
1010 	m_RunFunc (argv, who, key);
1011 }
1012 
FConsoleAlias(const char * name,const char * command,bool noSave)1013 FConsoleAlias::FConsoleAlias (const char *name, const char *command, bool noSave)
1014 	: FConsoleCommand (name, NULL),
1015 	  bRunning(false), bKill(false)
1016 {
1017 	m_Command[noSave] = command;
1018 	m_Command[!noSave] = FString();
1019 	// If the command contains % characters, assume they are parameter markers
1020 	// for substitution when the command is executed.
1021 	bDoSubstitution = (strchr (command, '%') != NULL);
1022 }
1023 
~FConsoleAlias()1024 FConsoleAlias::~FConsoleAlias ()
1025 {
1026 	m_Command[1] = m_Command[0] = FString();
1027 }
1028 
1029 // Given an argument vector, reconstitute the command line it could have been produced from.
BuildString(int argc,FString * argv)1030 FString BuildString (int argc, FString *argv)
1031 {
1032 	if (argc == 1)
1033 	{
1034 		return *argv;
1035 	}
1036 	else
1037 	{
1038 		FString buf;
1039 		int arg;
1040 
1041 		for (arg = 0; arg < argc; arg++)
1042 		{
1043 			if (strchr(argv[arg], '"'))
1044 			{ // If it contains one or more quotes, we need to escape them.
1045 				buf << '"';
1046 				long substr_start = 0, quotepos;
1047 				while ((quotepos = argv[arg].IndexOf('"', substr_start)) >= 0)
1048 				{
1049 					if (substr_start < quotepos)
1050 					{
1051 						buf << argv[arg].Mid(substr_start, quotepos - substr_start);
1052 					}
1053 					buf << "\\\"";
1054 					substr_start = quotepos + 1;
1055 				}
1056 				buf << argv[arg].Mid(substr_start) << "\" ";
1057 			}
1058 			else if (strchr(argv[arg], ' '))
1059 			{ // If it contains a space, it needs to be quoted.
1060 				buf << '"' << argv[arg] << "\" ";
1061 			}
1062 			else
1063 			{
1064 				buf << argv[arg] << ' ';
1065 			}
1066 		}
1067 		return buf;
1068 	}
1069 }
1070 
1071 //===========================================================================
1072 //
1073 // SubstituteAliasParams
1074 //
1075 // Given an command line and a set of arguments, replace instances of
1076 // %x or %{x} in the command line with argument x. If argument x does not
1077 // exist, then the empty string is substituted in its place.
1078 //
1079 // Substitution is not done inside of quoted strings, unless that string is
1080 // prepended with a % character.
1081 //
1082 // To avoid a substitution, use %%. The %% will be replaced by a single %.
1083 //
1084 //===========================================================================
1085 
SubstituteAliasParams(FString & command,FCommandLine & args)1086 FString SubstituteAliasParams (FString &command, FCommandLine &args)
1087 {
1088 	// Do substitution by replacing %x with the argument x.
1089 	// If there is no argument x, then %x is simply removed.
1090 
1091 	// For some reason, strtoul's stop parameter is non-const.
1092 	char *p = command.LockBuffer(), *start = p;
1093 	unsigned long argnum;
1094 	FString buf;
1095 	bool inquote = false;
1096 
1097 	while (*p != '\0')
1098 	{
1099 		if (p[0] == '%' && ((p[1] >= '0' && p[1] <= '9') || p[1] == '{'))
1100 		{
1101 			// Do a substitution. Output what came before this.
1102 			buf.AppendCStrPart (start, p - start);
1103 
1104 			// Extract the argument number and substitute the corresponding argument.
1105 			argnum = strtoul (p + 1 + (p[1] == '{'), &start, 10);
1106 			if ((p[1] != '{' || *start == '}') && argnum < (unsigned long)args.argc())
1107 			{
1108 				buf += args[argnum];
1109 			}
1110 			p = (start += (p[1] == '{' && *start == '}'));
1111 		}
1112 		else if (p[0] == '%' && p[1] == '%')
1113 		{
1114 			// Do not substitute. Just collapse to a single %.
1115 			buf.AppendCStrPart (start, p - start + 1);
1116 			start = p = p + 2;
1117 			continue;
1118 		}
1119 		else if (p[0] == '%' && p[1] == '"')
1120 		{
1121 			// Collapse %" to " and remember that we're in a quote so when we
1122 			// see a " character again, we don't start skipping below.
1123 			if (!inquote)
1124 			{
1125 				inquote = true;
1126 				buf.AppendCStrPart(start, p - start);
1127 				start = p + 1;
1128 			}
1129 			else
1130 			{
1131 				inquote = false;
1132 			}
1133 			p += 2;
1134 		}
1135 		else if (p[0] == '\\' && p[1] == '"')
1136 		{
1137 			p += 2;
1138 		}
1139 		else if (p[0] == '"')
1140 		{
1141 			// Don't substitute inside quoted strings if it didn't start
1142 			// with a %"
1143 			if (!inquote)
1144 			{
1145 				p++;
1146 				while (*p != '\0' && (*p != '"' || *(p-1) == '\\'))
1147 					p++;
1148 				if (*p != '\0')
1149 					p++;
1150 			}
1151 			else
1152 			{
1153 				inquote = false;
1154 				p++;
1155 			}
1156 		}
1157 		else
1158 		{
1159 			p++;
1160 		}
1161 	}
1162 	// Return whatever was after the final substitution.
1163 	if (p > start)
1164 	{
1165 		buf.AppendCStrPart (start, p - start);
1166 	}
1167 	command.UnlockBuffer();
1168 
1169 	return buf;
1170 }
1171 
DumpHash(FConsoleCommand ** table,bool aliases,const char * pattern=NULL)1172 static int DumpHash (FConsoleCommand **table, bool aliases, const char *pattern=NULL)
1173 {
1174 	int bucket, count;
1175 	FConsoleCommand *cmd;
1176 
1177 	for (bucket = count = 0; bucket < FConsoleCommand::HASH_SIZE; bucket++)
1178 	{
1179 		cmd = table[bucket];
1180 		while (cmd)
1181 		{
1182 			if (CheckWildcards (pattern, cmd->m_Name))
1183 			{
1184 				if (cmd->IsAlias())
1185 				{
1186 					if (aliases)
1187 					{
1188 						++count;
1189 						static_cast<FConsoleAlias *>(cmd)->PrintAlias ();
1190 					}
1191 				}
1192 				else if (!aliases)
1193 				{
1194 					++count;
1195 					cmd->PrintCommand ();
1196 				}
1197 			}
1198 			cmd = cmd->m_Next;
1199 		}
1200 	}
1201 	return count;
1202 }
1203 
PrintAlias()1204 void FConsoleAlias::PrintAlias ()
1205 {
1206 	if (m_Command[0])
1207 	{
1208 		Printf (TEXTCOLOR_YELLOW "%s : %s\n", m_Name, m_Command[0].GetChars());
1209 	}
1210 	if (m_Command[1])
1211 	{
1212 		Printf (TEXTCOLOR_ORANGE "%s : %s\n", m_Name, m_Command[1].GetChars());
1213 	}
1214 }
1215 
Archive(FConfigFile * f)1216 void FConsoleAlias::Archive (FConfigFile *f)
1217 {
1218 	if (f != NULL && !m_Command[0].IsEmpty())
1219 	{
1220 		f->SetValueForKey ("Name", m_Name, true);
1221 		f->SetValueForKey ("Command", m_Command[0], true);
1222 	}
1223 }
1224 
C_ArchiveAliases(FConfigFile * f)1225 void C_ArchiveAliases (FConfigFile *f)
1226 {
1227 	int bucket;
1228 	FConsoleCommand *alias;
1229 
1230 	for (bucket = 0; bucket < FConsoleCommand::HASH_SIZE; bucket++)
1231 	{
1232 		alias = Commands[bucket];
1233 		while (alias)
1234 		{
1235 			if (alias->IsAlias())
1236 				static_cast<FConsoleAlias *>(alias)->Archive (f);
1237 			alias = alias->m_Next;
1238 		}
1239 	}
1240 }
1241 
C_ClearAliases()1242 void C_ClearAliases ()
1243 {
1244 	int bucket;
1245 	FConsoleCommand *alias;
1246 
1247 	for (bucket = 0; bucket < FConsoleCommand::HASH_SIZE; bucket++)
1248 	{
1249 		alias = Commands[bucket];
1250 		while (alias)
1251 		{
1252 			FConsoleCommand *next = alias->m_Next;
1253 			if (alias->IsAlias())
1254 				static_cast<FConsoleAlias *>(alias)->SafeDelete();
1255 			alias = next;
1256 		}
1257 	}
1258 }
1259 
CCMD(clearaliases)1260 CCMD(clearaliases)
1261 {
1262 	C_ClearAliases();
1263 }
1264 
1265 
1266 // This is called only by the ini parser.
C_SetAlias(const char * name,const char * cmd)1267 void C_SetAlias (const char *name, const char *cmd)
1268 {
1269 	FConsoleCommand *prev, *alias, **chain;
1270 
1271 	chain = &Commands[MakeKey (name) % FConsoleCommand::HASH_SIZE];
1272 	alias = ScanChainForName (*chain, name, strlen (name), &prev);
1273 	if (alias != NULL)
1274 	{
1275 		if (!alias->IsAlias ())
1276 		{
1277 			//Printf (PRINT_BOLD, "%s is a command and cannot be an alias.\n", name);
1278 			return;
1279 		}
1280 		delete alias;
1281 	}
1282 	new FConsoleAlias (name, cmd, false);
1283 }
1284 
CCMD(alias)1285 CCMD (alias)
1286 {
1287 	FConsoleCommand *prev, *alias, **chain;
1288 
1289 	if (argv.argc() == 1)
1290 	{
1291 		Printf ("Current alias commands:\n");
1292 		DumpHash (Commands, true);
1293 	}
1294 	else
1295 	{
1296 		chain = &Commands[MakeKey (argv[1]) % FConsoleCommand::HASH_SIZE];
1297 
1298 		if (argv.argc() == 2)
1299 		{ // Remove the alias
1300 
1301 			if ( (alias = ScanChainForName (*chain, argv[1], strlen (argv[1]), &prev)))
1302 			{
1303 				if (alias->IsAlias ())
1304 				{
1305 					static_cast<FConsoleAlias *> (alias)->SafeDelete ();
1306 				}
1307 				else
1308 				{
1309 					Printf ("%s is a normal command\n", alias->m_Name);
1310 				}
1311 			}
1312 		}
1313 		else
1314 		{ // Add/change the alias
1315 
1316 			alias = ScanChainForName (*chain, argv[1], strlen (argv[1]), &prev);
1317 			if (alias != NULL)
1318 			{
1319 				if (alias->IsAlias ())
1320 				{
1321 					static_cast<FConsoleAlias *> (alias)->Realias (argv[2], ParsingKeyConf);
1322 				}
1323 				else
1324 				{
1325 					Printf ("%s is a normal command\n", alias->m_Name);
1326 					alias = NULL;
1327 				}
1328 			}
1329 			else
1330 			{
1331 				new FConsoleAlias (argv[1], argv[2], ParsingKeyConf);
1332 			}
1333 		}
1334 	}
1335 }
1336 
CCMD(cmdlist)1337 CCMD (cmdlist)
1338 {
1339 	int count;
1340 	const char *filter = (argv.argc() == 1 ? NULL : argv[1]);
1341 
1342 	count = ListActionCommands (filter);
1343 	count += DumpHash (Commands, false, filter);
1344 	Printf ("%d commands\n", count);
1345 }
1346 
CCMD(key)1347 CCMD (key)
1348 {
1349 	if (argv.argc() > 1)
1350 	{
1351 		int i;
1352 
1353 		for (i = 1; i < argv.argc(); ++i)
1354 		{
1355 			unsigned int key = MakeKey (argv[i]);
1356 			Printf (" 0x%08x\n", key);
1357 		}
1358 	}
1359 }
1360 
1361 // Execute any console commands specified on the command line.
1362 // These all begin with '+' as opposed to '-'.
C_ParseCmdLineParams(FExecList * exec)1363 FExecList *C_ParseCmdLineParams(FExecList *exec)
1364 {
1365 	for (int currArg = 1; currArg < Args->NumArgs(); )
1366 	{
1367 		if (*Args->GetArg (currArg++) == '+')
1368 		{
1369 			FString cmdString;
1370 			int cmdlen = 1;
1371 			int argstart = currArg - 1;
1372 
1373 			while (currArg < Args->NumArgs())
1374 			{
1375 				if (*Args->GetArg (currArg) == '-' || *Args->GetArg (currArg) == '+')
1376 					break;
1377 				currArg++;
1378 				cmdlen++;
1379 			}
1380 
1381 			cmdString = BuildString (cmdlen, Args->GetArgList (argstart));
1382 			if (!cmdString.IsEmpty())
1383 			{
1384 				if (exec == NULL)
1385 				{
1386 					exec = new FExecList;
1387 				}
1388 				exec->AddCommand(&cmdString[1]);
1389 			}
1390 		}
1391 	}
1392 	return exec;
1393 }
1394 
IsAlias()1395 bool FConsoleCommand::IsAlias ()
1396 {
1397 	return false;
1398 }
1399 
IsAlias()1400 bool FConsoleAlias::IsAlias ()
1401 {
1402 	return true;
1403 }
1404 
Run(FCommandLine & args,APlayerPawn * who,int key)1405 void FConsoleAlias::Run (FCommandLine &args, APlayerPawn *who, int key)
1406 {
1407 	if (bRunning)
1408 	{
1409 		Printf ("Alias %s tried to recurse.\n", m_Name);
1410 		return;
1411 	}
1412 
1413 	int index = !m_Command[1].IsEmpty();
1414 	FString savedcommand = m_Command[index], mycommand;
1415 	m_Command[index] = FString();
1416 
1417 	if (bDoSubstitution)
1418 	{
1419 		mycommand = SubstituteAliasParams (savedcommand, args);
1420 	}
1421 	else
1422 	{
1423 		mycommand = savedcommand;
1424 	}
1425 
1426 	bRunning = true;
1427 	AddCommandString (mycommand.LockBuffer(), key);
1428 	mycommand.UnlockBuffer();
1429 	bRunning = false;
1430 	if (m_Command[index].IsEmpty())
1431 	{ // The alias is unchanged, so put the command back so it can be used again.
1432 	  // If the command had been non-empty, then that means that executing this
1433 	  // alias caused it to realias itself, so the old command will be forgotten
1434 	  // once this function returns.
1435 		m_Command[index] = savedcommand;
1436 	}
1437 	if (bKill)
1438 	{ // The alias wants to remove itself
1439 		delete this;
1440 	}
1441 }
1442 
Realias(const char * command,bool noSave)1443 void FConsoleAlias::Realias (const char *command, bool noSave)
1444 {
1445 	if (!noSave && !m_Command[1].IsEmpty())
1446 	{
1447 		noSave = true;
1448 	}
1449 	m_Command[noSave] = command;
1450 
1451 	// If the command contains % characters, assume they are parameter markers
1452 	// for substitution when the command is executed.
1453 	bDoSubstitution = (strchr (command, '%') != NULL);
1454 	bKill = false;
1455 }
1456 
SafeDelete()1457 void FConsoleAlias::SafeDelete ()
1458 {
1459 	if (!bRunning)
1460 	{
1461 		delete this;
1462 	}
1463 	else
1464 	{
1465 		bKill = true;
1466 	}
1467 }
1468 
AddCommand(const char * cmd,const char * file)1469 void FExecList::AddCommand(const char *cmd, const char *file)
1470 {
1471 	// Pullins are special and need to be separated from general commands.
1472 	// They also turned out to be a really bad idea, since they make things
1473 	// more complicated. :(
1474 	if (file != NULL && strnicmp(cmd, "pullin", 6) == 0 && isspace(cmd[6]))
1475 	{
1476 		FCommandLine line(cmd);
1477 		C_SearchForPullins(this, file, line);
1478 	}
1479 	// Recursive exec: Parse this file now.
1480 	else if (strnicmp(cmd, "exec", 4) == 0 && isspace(cmd[4]))
1481 	{
1482 		FCommandLine argv(cmd);
1483 		for (int i = 1; i < argv.argc(); ++i)
1484 		{
1485 			C_ParseExecFile(argv[i], this);
1486 		}
1487 	}
1488 	else
1489 	{
1490 		Commands.Push(cmd);
1491 	}
1492 }
1493 
ExecCommands() const1494 void FExecList::ExecCommands() const
1495 {
1496 	for (unsigned i = 0; i < Commands.Size(); ++i)
1497 	{
1498 		AddCommandString(Commands[i].LockBuffer());
1499 		Commands[i].UnlockBuffer();
1500 	}
1501 }
1502 
AddPullins(TArray<FString> & wads) const1503 void FExecList::AddPullins(TArray<FString> &wads) const
1504 {
1505 	for (unsigned i = 0; i < Pullins.Size(); ++i)
1506 	{
1507 		D_AddFile(wads, Pullins[i]);
1508 	}
1509 }
1510 
C_ParseExecFile(const char * file,FExecList * exec)1511 FExecList *C_ParseExecFile(const char *file, FExecList *exec)
1512 {
1513 	FILE *f;
1514 	char cmd[4096];
1515 	int retval = 0;
1516 
1517 	if ( (f = fopen (file, "r")) )
1518 	{
1519 		while (fgets(cmd, countof(cmd)-1, f))
1520 		{
1521 			// Comments begin with //
1522 			char *stop = cmd + strlen(cmd) - 1;
1523 			char *comment = cmd;
1524 			int inQuote = 0;
1525 
1526 			if (*stop == '\n')
1527 				*stop-- = 0;
1528 
1529 			while (comment < stop)
1530 			{
1531 				if (*comment == '\"')
1532 				{
1533 					inQuote ^= 1;
1534 				}
1535 				else if (!inQuote && *comment == '/' && *(comment + 1) == '/')
1536 				{
1537 					break;
1538 				}
1539 				comment++;
1540 			}
1541 			if (comment == cmd)
1542 			{ // Comment at line beginning
1543 				continue;
1544 			}
1545 			else if (comment < stop)
1546 			{ // Comment in middle of line
1547 				*comment = 0;
1548 			}
1549 			if (exec == NULL)
1550 			{
1551 				exec = new FExecList;
1552 			}
1553 			exec->AddCommand(cmd, file);
1554 		}
1555 		if (!feof(f))
1556 		{
1557 			Printf("Error parsing \"%s\"\n", file);
1558 		}
1559 		fclose(f);
1560 	}
1561 	else
1562 	{
1563 		Printf ("Could not open \"%s\"\n", file);
1564 	}
1565 	return exec;
1566 }
1567 
C_ExecFile(const char * file)1568 bool C_ExecFile (const char *file)
1569 {
1570 	FExecList *exec = C_ParseExecFile(file, NULL);
1571 	if (exec != NULL)
1572 	{
1573 		exec->ExecCommands();
1574 		if (exec->Pullins.Size() > 0)
1575 		{
1576 			Printf(TEXTCOLOR_BOLD "Notice: Pullin files were ignored.\n");
1577 		}
1578 		delete exec;
1579 	}
1580 	return exec != NULL;
1581 }
1582 
C_SearchForPullins(FExecList * exec,const char * file,FCommandLine & argv)1583 void C_SearchForPullins(FExecList *exec, const char *file, FCommandLine &argv)
1584 {
1585 	const char *lastSlash;
1586 
1587 	assert(exec != NULL);
1588 	assert(file != NULL);
1589 #ifdef __unix__
1590 	lastSlash = strrchr(file, '/');
1591 #else
1592 	const char *lastSlash1, *lastSlash2;
1593 
1594 	lastSlash1 = strrchr(file, '/');
1595 	lastSlash2 = strrchr(file, '\\');
1596 	lastSlash = MAX(lastSlash1, lastSlash2);
1597 #endif
1598 
1599 	for (int i = 1; i < argv.argc(); ++i)
1600 	{
1601 		// Try looking for the wad in the same directory as the .cfg
1602 		// before looking for it in the current directory.
1603 		if (lastSlash != NULL)
1604 		{
1605 			FString path(file, (lastSlash - file) + 1);
1606 			path += argv[i];
1607 			if (FileExists(path))
1608 			{
1609 				exec->Pullins.Push(path);
1610 				continue;
1611 			}
1612 		}
1613 		exec->Pullins.Push(argv[i]);
1614 	}
1615 }
1616 
CCMD(pullin)1617 CCMD (pullin)
1618 {
1619 	// Actual handling for pullin is now completely special-cased above
1620 	Printf (TEXTCOLOR_BOLD "Pullin" TEXTCOLOR_NORMAL " is only valid from .cfg\n"
1621 			"files and only when used at startup.\n");
1622 }
1623 
1624