+static const char *Cmd_GetDirectCvarValue(const char *varname, cmdalias_t *alias, qboolean *is_multiple)
+{
+ cvar_t *cvar;
+ long argno;
+ char *endptr;
+ char vabuf[1024];
+
+ if(is_multiple)
+ *is_multiple = false;
+
+ if(!varname || !*varname)
+ return NULL;
+
+ if(alias)
+ {
+ if(!strcmp(varname, "*"))
+ {
+ if(is_multiple)
+ *is_multiple = true;
+ return Cmd_Args();
+ }
+ else if(!strcmp(varname, "#"))
+ {
+ return va(vabuf, sizeof(vabuf), "%d", Cmd_Argc());
+ }
+ else if(varname[strlen(varname) - 1] == '-')
+ {
+ argno = strtol(varname, &endptr, 10);
+ if(endptr == varname + strlen(varname) - 1)
+ {
+ // whole string is a number, apart from the -
+ const char *p = Cmd_Args();
+ for(; argno > 1; --argno)
+ if(!COM_ParseToken_Console(&p))
+ break;
+ if(p)
+ {
+ if(is_multiple)
+ *is_multiple = true;
+
+ // kill pre-argument whitespace
+ for (;*p && ISWHITESPACE(*p);p++)
+ ;
+
+ return p;
+ }
+ }
+ }
+ else
+ {
+ argno = strtol(varname, &endptr, 10);
+ if(*endptr == 0)
+ {
+ // whole string is a number
+ // NOTE: we already made sure we don't have an empty cvar name!
+ if(argno >= 0 && argno < Cmd_Argc())
+ return Cmd_Argv(argno);
+ }
+ }
+ }
+
+ if((cvar = Cvar_FindVar(varname)) && !(cvar->flags & CVAR_PRIVATE))
+ return cvar->string;
+
+ return NULL;
+}
+
+qboolean Cmd_QuoteString(char *out, size_t outlen, const char *in, const char *quoteset, qboolean putquotes)
+{
+ qboolean quote_quot = !!strchr(quoteset, '"');
+ qboolean quote_backslash = !!strchr(quoteset, '\\');
+ qboolean quote_dollar = !!strchr(quoteset, '$');
+
+ if(putquotes)
+ {
+ if(outlen <= 2)
+ {
+ *out++ = 0;
+ return false;
+ }
+ *out++ = '"'; --outlen;
+ --outlen;
+ }
+
+ while(*in)
+ {
+ if(*in == '"' && quote_quot)
+ {
+ if(outlen <= 2)
+ goto fail;
+ *out++ = '\\'; --outlen;
+ *out++ = '"'; --outlen;
+ }
+ else if(*in == '\\' && quote_backslash)
+ {
+ if(outlen <= 2)
+ goto fail;
+ *out++ = '\\'; --outlen;
+ *out++ = '\\'; --outlen;
+ }
+ else if(*in == '$' && quote_dollar)
+ {
+ if(outlen <= 2)
+ goto fail;
+ *out++ = '$'; --outlen;
+ *out++ = '$'; --outlen;
+ }
+ else
+ {
+ if(outlen <= 1)
+ goto fail;
+ *out++ = *in; --outlen;
+ }
+ ++in;
+ }
+ if(putquotes)
+ *out++ = '"';
+ *out++ = 0;
+ return true;
+fail:
+ if(putquotes)
+ *out++ = '"';
+ *out++ = 0;
+ return false;
+}
+
+static const char *Cmd_GetCvarValue(const char *var, size_t varlen, cmdalias_t *alias)
+{
+ static char varname[MAX_INPUTLINE]; // cmd_mutex
+ static char varval[MAX_INPUTLINE]; // cmd_mutex
+ const char *varstr = NULL;
+ char *varfunc;
+ qboolean required = false;
+ qboolean optional = false;
+ static char asis[] = "asis"; // just to suppress const char warnings
+
+ if(varlen >= MAX_INPUTLINE)
+ varlen = MAX_INPUTLINE - 1;
+ memcpy(varname, var, varlen);
+ varname[varlen] = 0;
+ varfunc = strchr(varname, ' ');
+
+ if(varfunc)
+ {
+ *varfunc = 0;
+ ++varfunc;
+ }
+
+ if(*var == 0)
+ {
+ // empty cvar name?
+ if(alias)
+ Con_Printf("Warning: Could not expand $ in alias %s\n", alias->name);
+ else
+ Con_Printf("Warning: Could not expand $\n");
+ return "$";
+ }
+
+ if(varfunc)
+ {
+ char *p;
+ // ? means optional
+ while((p = strchr(varfunc, '?')))
+ {
+ optional = true;
+ memmove(p, p+1, strlen(p)); // with final NUL
+ }
+ // ! means required
+ while((p = strchr(varfunc, '!')))
+ {
+ required = true;
+ memmove(p, p+1, strlen(p)); // with final NUL
+ }
+ // kill spaces
+ while((p = strchr(varfunc, ' ')))
+ {
+ memmove(p, p+1, strlen(p)); // with final NUL
+ }
+ // if no function is left, NULL it
+ if(!*varfunc)
+ varfunc = NULL;
+ }
+
+ if(varname[0] == '$')
+ varstr = Cmd_GetDirectCvarValue(Cmd_GetDirectCvarValue(varname + 1, alias, NULL), alias, NULL);
+ else
+ {
+ qboolean is_multiple = false;
+ // Exception: $* and $n- don't use the quoted form by default
+ varstr = Cmd_GetDirectCvarValue(varname, alias, &is_multiple);
+ if(is_multiple)
+ if(!varfunc)
+ varfunc = asis;
+ }
+
+ if(!varstr)
+ {
+ if(required)
+ {
+ if(alias)
+ Con_Printf("Error: Could not expand $%s in alias %s\n", varname, alias->name);
+ else
+ Con_Printf("Error: Could not expand $%s\n", varname);
+ return NULL;
+ }
+ else if(optional)
+ {
+ return "";
+ }
+ else
+ {
+ if(alias)
+ Con_Printf("Warning: Could not expand $%s in alias %s\n", varname, alias->name);
+ else
+ Con_Printf("Warning: Could not expand $%s\n", varname);
+ dpsnprintf(varval, sizeof(varval), "$%s", varname);
+ return varval;
+ }
+ }
+
+ if(!varfunc || !strcmp(varfunc, "q")) // note: quoted form is default, use "asis" to override!
+ {
+ // quote it so it can be used inside double quotes
+ // we just need to replace " by \", and of course, double backslashes
+ Cmd_QuoteString(varval, sizeof(varval), varstr, "\"\\", false);
+ return varval;
+ }
+ else if(!strcmp(varfunc, "asis"))
+ {
+ return varstr;
+ }
+ else
+ Con_Printf("Unknown variable function %s\n", varfunc);
+
+ return varstr;
+}
+
+/*
+Cmd_PreprocessString
+
+Preprocesses strings and replaces $*, $param#, $cvar accordingly. Also strips comments.
+*/
+static qboolean Cmd_PreprocessString( const char *intext, char *outtext, unsigned maxoutlen, cmdalias_t *alias ) {
+ const char *in;
+ size_t eat, varlen;
+ unsigned outlen;
+ const char *val;
+
+ // don't crash if there's no room in the outtext buffer
+ if( maxoutlen == 0 ) {
+ return false;
+ }
+ maxoutlen--; // because of \0
+
+ in = intext;
+ outlen = 0;
+
+ while( *in && outlen < maxoutlen ) {
+ if( *in == '$' ) {
+ // this is some kind of expansion, see what comes after the $
+ in++;
+
+ // The console does the following preprocessing:
+ //
+ // - $$ is transformed to a single dollar sign.
+ // - $var or ${var} are expanded to the contents of the named cvar,
+ // with quotation marks and backslashes quoted so it can safely
+ // be used inside quotation marks (and it should always be used
+ // that way)
+ // - ${var asis} inserts the cvar value as is, without doing this
+ // quoting
+ // - ${var ?} silently expands to the empty string if
+ // $var does not exist
+ // - ${var !} fails expansion and executes nothing if
+ // $var does not exist
+ // - prefix the cvar name with a dollar sign to do indirection;
+ // for example, if $x has the value timelimit, ${$x} will return
+ // the value of $timelimit
+ // - when expanding an alias, the special variable name $* refers
+ // to all alias parameters, and a number refers to that numbered
+ // alias parameter, where the name of the alias is $0, the first
+ // parameter is $1 and so on; as a special case, $* inserts all
+ // parameters, without extra quoting, so one can use $* to just
+ // pass all parameters around. All parameters starting from $n
+ // can be referred to as $n- (so $* is equivalent to $1-).
+ // - ${* q} and ${n- q} force quoting anyway
+ //
+ // Note: when expanding an alias, cvar expansion is done in the SAME step
+ // as alias expansion so that alias parameters or cvar values containing
+ // dollar signs have no unwanted bad side effects. However, this needs to
+ // be accounted for when writing complex aliases. For example,
+ // alias foo "set x NEW; echo $x"
+ // actually expands to
+ // "set x NEW; echo OLD"
+ // and will print OLD! To work around this, use a second alias:
+ // alias foo "set x NEW; foo2"
+ // alias foo2 "echo $x"
+ //
+ // Also note: lines starting with alias are exempt from cvar expansion.
+ // If you want cvar expansion, write "alias" instead:
+ //
+ // set x 1
+ // alias foo "echo $x"
+ // "alias" bar "echo $x"
+ // set x 2
+ //
+ // foo will print 2, because the variable $x will be expanded when the alias
+ // gets expanded. bar will print 1, because the variable $x was expanded
+ // at definition time. foo can be equivalently defined as
+ //
+ // "alias" foo "echo $$x"
+ //
+ // because at definition time, $$ will get replaced to a single $.
+
+ if( *in == '$' ) {
+ val = "$";
+ eat = 1;
+ } else if(*in == '{') {
+ varlen = strcspn(in + 1, "}");
+ if(in[varlen + 1] == '}')
+ {
+ val = Cmd_GetCvarValue(in + 1, varlen, alias);
+ if(!val)
+ return false;
+ eat = varlen + 2;
+ }
+ else
+ {
+ // ran out of data?
+ val = NULL;
+ eat = varlen + 1;
+ }
+ } else {
+ varlen = strspn(in, "#*0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-");
+ val = Cmd_GetCvarValue(in, varlen, alias);
+ if(!val)
+ return false;
+ eat = varlen;
+ }
+ if(val)
+ {
+ // insert the cvar value
+ while(*val && outlen < maxoutlen)
+ outtext[outlen++] = *val++;
+ in += eat;
+ }
+ else
+ {
+ // copy the unexpanded text
+ outtext[outlen++] = '$';
+ while(eat && outlen < maxoutlen)
+ {
+ outtext[outlen++] = *in++;
+ --eat;
+ }
+ }
+ }
+ else
+ outtext[outlen++] = *in++;
+ }
+ outtext[outlen] = 0;
+ return true;
+}
+
+/*
+============
+Cmd_ExecuteAlias
+
+Called for aliases and fills in the alias into the cbuffer
+============
+*/
+static void Cmd_ExecuteAlias (cmdalias_t *alias)
+{
+ static char buffer[ MAX_INPUTLINE ]; // cmd_mutex
+ static char buffer2[ MAX_INPUTLINE ]; // cmd_mutex
+ qboolean ret = Cmd_PreprocessString( alias->value, buffer, sizeof(buffer) - 2, alias );
+ if(!ret)
+ return;
+ // insert at start of command buffer, so that aliases execute in order
+ // (fixes bug introduced by Black on 20050705)
+
+ // Note: Cbuf_PreprocessString will be called on this string AGAIN! So we
+ // have to make sure that no second variable expansion takes place, otherwise
+ // alias parameters containing dollar signs can have bad effects.
+ Cmd_QuoteString(buffer2, sizeof(buffer2), buffer, "$", false);
+ Cbuf_InsertText( buffer2 );
+}
+