/* * Copyright (C) 1997-2001 Id Software, Inc. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. * * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ /* cmd.c -- Quake script command processing module */ #include "qcommon.h" #define MAX_ALIAS_NAME 32 typedef struct cmdalias_s { struct cmdalias_s *next; char name[MAX_ALIAS_NAME]; char *value; } cmdalias_t; cmdalias_t *cmd_alias; qboolean cmd_wait; #define ALIAS_LOOP_COUNT 16 int alias_count; /* for detecting runaway loops */ /* * ============ Cmd_Wait_f * * Causes execution of the remainder of the command buffer to be delayed until * next frame. This allows commands like: bind g "impulse 5 ; +attack ; wait * ; -attack ; impulse 2" ============ */ void Cmd_Wait_f(void) { cmd_wait = true; } /* * * ============================================================================ * * COMMAND BUFFER * * ============================================================================ */ sizebuf_t cmd_text; byte cmd_text_buf[8192]; char defer_text_buf[8192]; /* * ============ Cbuf_Init ============ */ void Cbuf_Init(void) { SZ_Init(&cmd_text, cmd_text_buf, sizeof(cmd_text_buf)); } /* * ============ Cbuf_AddText * * Adds command text at the end of the buffer ============ */ void Cbuf_AddText(char *text) { int l; l = strlen(text); if (cmd_text.cursize + l >= cmd_text.maxsize) { Com_Printf("Cbuf_AddText: overflow\n"); return; } SZ_Write(&cmd_text, text, strlen(text)); } /* * ============ Cbuf_InsertText * * Adds command text immediately after the current command Adds a \n to the text * FIXME: actually change the command buffer to do less copying ============ */ void Cbuf_InsertText(char *text) { char *temp; int templen; /* copy off any commands still remaining in the exec buffer */ templen = cmd_text.cursize; if (templen) { temp = Z_Malloc(templen); memcpy(temp, cmd_text.data, templen); SZ_Clear(&cmd_text); } else temp = NULL; /* shut up compiler */ /* add the entire text of the file */ Cbuf_AddText(text); /* add the copied off data */ if (templen) { SZ_Write(&cmd_text, temp, templen); Z_Free(temp); } } /* * ============ Cbuf_CopyToDefer ============ */ void Cbuf_CopyToDefer(void) { memcpy(defer_text_buf, cmd_text_buf, cmd_text.cursize); defer_text_buf[cmd_text.cursize] = 0; cmd_text.cursize = 0; } /* * ============ Cbuf_InsertFromDefer ============ */ void Cbuf_InsertFromDefer(void) { Cbuf_InsertText(defer_text_buf); defer_text_buf[0] = 0; } /* * ============ Cbuf_ExecuteText ============ */ void Cbuf_ExecuteText(int exec_when, char *text) { switch (exec_when) { case EXEC_NOW: Cmd_ExecuteString(text); break; case EXEC_INSERT: Cbuf_InsertText(text); break; case EXEC_APPEND: Cbuf_AddText(text); break; default: Com_Error(ERR_FATAL, "Cbuf_ExecuteText: bad exec_when"); } } /* * ============ Cbuf_Execute ============ */ void Cbuf_Execute(void) { int i; char *text; char line[1024]; int quotes; alias_count = 0; /* don't allow infinite alias loops */ while (cmd_text.cursize) { /* find a \n or ; line break */ text = (char *)cmd_text.data; quotes = 0; for (i = 0; i < cmd_text.cursize; i++) { if (text[i] == '"') quotes++; if (!(quotes & 1) && text[i] == ';') break; /* don't break if inside a quoted string */ if (text[i] == '\n') break; } /* sku - removed potentional buffer overflow vulnerability */ if (i > sizeof(line) - 1) { i = sizeof(line) - 1; } memcpy(line, text, i); line[i] = 0; /* * delete the text from the command buffer and move remaining * commands down */ /* * this is necessary because commands (exec, alias) can * insert data at the */ /* beginning of the text buffer */ if (i == cmd_text.cursize) cmd_text.cursize = 0; else { i++; cmd_text.cursize -= i; memmove(text, text + i, cmd_text.cursize); } /* execute the command line */ Cmd_ExecuteString(line); if (cmd_wait) { /* * skip out while text still remains in buffer, * leaving it */ /* for next frame */ cmd_wait = false; break; } } } /* * =============== Cbuf_AddEarlyCommands * * Adds command line parameters as script statements Commands lead with a +, and * continue until another + * * Set commands are added early, so they are guaranteed to be set before the * client and server initialize for the first time. * * Other commands are added late, after all initialization is complete. * =============== */ void Cbuf_AddEarlyCommands(qboolean clear) { int i; char *s; for (i = 0; i < COM_Argc(); i++) { s = COM_Argv(i); if (strcmp(s, "+set")) continue; Cbuf_AddText(va("set %s %s\n", COM_Argv(i + 1), COM_Argv(i + 2))); if (clear) { COM_ClearArgv(i); COM_ClearArgv(i + 1); COM_ClearArgv(i + 2); } i += 2; } } /* * ================= Cbuf_AddLateCommands * * Adds command line parameters as script statements Commands lead with a + and * continue until another + or - quake +vid_ref gl +map amlev1 * * Returns true if any late commands were added, which will keep the demoloop * from immediately starting ================= */ qboolean Cbuf_AddLateCommands(void) { int i, j; int s; char *text, *build, c; int argc; qboolean ret; /* build the combined string to parse from */ s = 0; argc = COM_Argc(); for (i = 1; i < argc; i++) { s += strlen(COM_Argv(i)) + 1; } if (!s) return false; text = Z_Malloc(s + 1); text[0] = 0; for (i = 1; i < argc; i++) { strcat(text, COM_Argv(i)); if (i != argc - 1) strcat(text, " "); } /* pull out the commands */ build = Z_Malloc(s + 1); build[0] = 0; for (i = 0; i < s - 1; i++) { if (text[i] == '+') { i++; for (j = i; (text[j] != '+') && (text[j] != '-') && (text[j] != 0); j++); c = text[j]; text[j] = 0; strcat(build, text + i); strcat(build, "\n"); text[j] = c; i = j - 1; } } ret = (build[0] != 0); if (ret) Cbuf_AddText(build); Z_Free(text); Z_Free(build); return ret; } /* * * ============================================================================ * == * * SCRIPT COMMANDS * * =============== * ============================================================== */ /* * =============== Cmd_Exec_f =============== */ void Cmd_Exec_f(void) { char *f, *f2; int len; if (Cmd_Argc() != 2) { Com_Printf("exec : execute a script file\n"); return; } len = FS_LoadFile(Cmd_Argv(1), (void **)&f); if (!f) { Com_Printf("couldn't exec %s\n", Cmd_Argv(1)); return; } Com_Printf("execing %s\n", Cmd_Argv(1)); /* the file doesn't have a trailing 0, so we need to copy it off */ f2 = Z_Malloc(len + 1); memcpy(f2, f, len); f2[len] = 0; Cbuf_InsertText(f2); Z_Free(f2); FS_FreeFile(f); } /* * =============== Cmd_Echo_f * * Just prints the rest of the line to the console =============== */ void Cmd_Echo_f(void) { int i; for (i = 1; i < Cmd_Argc(); i++) Com_Printf("%s ", Cmd_Argv(i)); Com_Printf("\n"); } /* * =============== Cmd_Alias_f * * Creates a new command that executes a command string (possibly ; seperated) * =============== */ void Cmd_Alias_f(void) { cmdalias_t *a; char cmd[1024]; int i, c; char *s; if (Cmd_Argc() == 1) { Com_Printf("Current alias commands:\n"); for (a = cmd_alias; a; a = a->next) Com_Printf("%s : %s\n", a->name, a->value); return; } s = Cmd_Argv(1); if (strlen(s) >= MAX_ALIAS_NAME) { Com_Printf("Alias name is too long\n"); return; } /* if the alias already exists, reuse it */ for (a = cmd_alias; a; a = a->next) { if (!strcmp(s, a->name)) { Z_Free(a->value); break; } } if (!a) { a = Z_Malloc(sizeof(cmdalias_t)); a->next = cmd_alias; cmd_alias = a; } Q_strncpyz(a->name, s, sizeof(a->name)); /* copy the rest of the command line */ cmd[0] = '\0'; /* start out with a null string */ c = Cmd_Argc(); for (i = 2; i < c; i++) { strncat(cmd, Cmd_Argv(i), sizeof(cmd) - strlen(cmd) - 1); if (i != (c - 1)) strncat(cmd, " ", sizeof(cmd) - strlen(cmd) - 1); } strncat(cmd, "\n", sizeof(cmd) - strlen(cmd) - 1); a->value = CopyString(cmd); } /* * =========================================================================== * * COMMAND EXECUTION * * =========================================================================== */ typedef struct cmd_function_s { struct cmd_function_s *next; char *name; xcommand_t function; } cmd_function_t; static int cmd_argc; static char *cmd_argv[MAX_STRING_TOKENS]; static char *cmd_null_string = ""; static char cmd_args[MAX_STRING_CHARS]; static cmd_function_t *cmd_functions; /* possible commands to execute */ /* * ============ Cmd_Argc ============ */ int Cmd_Argc(void) { return cmd_argc; } /* * ============ Cmd_Argv ============ */ char * Cmd_Argv(int arg) { if ((unsigned)arg >= cmd_argc) return cmd_null_string; return cmd_argv[arg]; } /* * ============ Cmd_Args * * Returns a single string containing argv(1) to argv(argc()-1) ============ */ char * Cmd_Args(void) { return cmd_args; } /* * ====================== Cmd_MacroExpandString ====================== */ char * Cmd_MacroExpandString(char *text) { int i, j, count, len; qboolean inquote; char *scan; static char expanded[MAX_STRING_CHARS]; char temporary[MAX_STRING_CHARS]; char *token, *start; inquote = false; scan = text; len = strlen(scan); if (len >= MAX_STRING_CHARS) { Com_Printf("Line exceeded %i chars, discarded.\n", MAX_STRING_CHARS); return NULL; } count = 0; for (i = 0; i < len; i++) { if (scan[i] == '"') inquote ^= 1; if (inquote) continue; /* don't expand inside quotes */ if (scan[i] != '$') continue; /* scan out the complete macro */ start = scan + i + 1; token = COM_Parse(&start); if (!start) continue; token = Cvar_VariableString(token); j = strlen(token); len += j; if (len >= MAX_STRING_CHARS) { Com_Printf("Expanded line exceeded %i chars, discarded.\n", MAX_STRING_CHARS); return NULL; } strncpy(temporary, scan, i); strcpy(temporary + i, token); strcpy(temporary + i + j, start); strcpy(expanded, temporary); scan = expanded; i--; if (++count == 100) { Com_Printf("Macro expansion loop, discarded.\n"); return NULL; } } if (inquote) { Com_Printf("Line has unmatched quote, discarded.\n"); return NULL; } return scan; } /* * ============ Cmd_TokenizeString * * Parses the given string into command line tokens. $Cvars will be expanded * unless they are in a quoted token ============ */ void Cmd_TokenizeString(char *text, qboolean macroExpand) { int i; char *com_token; /* clear the args from the last string */ for (i = 0; i < cmd_argc; i++) Z_Free(cmd_argv[i]); cmd_argc = 0; cmd_args[0] = 0; /* macro expand the text */ if (macroExpand) text = Cmd_MacroExpandString(text); if (!text) return; while (1) { /* skip whitespace up to a /n */ while (*text && *text <= ' ' && *text != '\n') { text++; } if (*text == '\n') { /* a newline seperates commands in * the buffer */ text++; break; } if (!*text) return; /* set cmd_args to everything after the first arg */ if (cmd_argc == 1) { int l; /* strcpy (cmd_args, text); */ /* * sku - removed potentional buffer overflow * vulnerability */ Q_strncpyz(cmd_args, text, sizeof(cmd_args)); /* strip off any trailing whitespace */ l = strlen(cmd_args) - 1; for (; l >= 0; l--) if (cmd_args[l] <= ' ') cmd_args[l] = 0; else break; } com_token = COM_Parse(&text); if (!text) return; if (cmd_argc < MAX_STRING_TOKENS) { cmd_argv[cmd_argc] = Z_Malloc(strlen(com_token) + 1); Q_strncpyz(cmd_argv[cmd_argc], com_token, strlen(com_token) + 1); cmd_argc++; } } } /* * ============ Cmd_AddCommand ============ */ void Cmd_AddCommand(char *cmd_name, xcommand_t function) { cmd_function_t *cmd; /* fail if the command is a variable name */ if (Cvar_VariableString(cmd_name)[0]) { Com_Printf("Cmd_AddCommand: %s already defined as a var\n", cmd_name); return; } /* fail if the command already exists */ for (cmd = cmd_functions; cmd; cmd = cmd->next) { if (!strcmp(cmd_name, cmd->name)) { Com_Printf("Cmd_AddCommand: %s already defined\n", cmd_name); return; } } cmd = Z_Malloc(sizeof(cmd_function_t)); cmd->name = cmd_name; cmd->function = function; cmd->next = cmd_functions; cmd_functions = cmd; } /* * ============ Cmd_RemoveCommand ============ */ void Cmd_RemoveCommand(char *cmd_name) { cmd_function_t *cmd, **back; back = &cmd_functions; while (1) { cmd = *back; if (!cmd) { Com_Printf("Cmd_RemoveCommand: %s not added\n", cmd_name); return; } if (!strcmp(cmd_name, cmd->name)) { *back = cmd->next; Z_Free(cmd); return; } back = &cmd->next; } } /* * ============ Cmd_Exists ============ */ qboolean Cmd_Exists(char *cmd_name) { cmd_function_t *cmd; for (cmd = cmd_functions; cmd; cmd = cmd->next) { if (!strcmp(cmd_name, cmd->name)) return true; } return false; } /* * ============ Cmd_CompleteCommand ============ */ char retval[256]; char * Cmd_CompleteCommand(char *partial) { cmd_function_t *cmd; int len, i, o, p; cmdalias_t *a; cvar_t *cvar; char *pmatch[1024]; qboolean diff = false; len = strlen(partial); if (!len) return NULL; /* check for exact match */ for (cmd = cmd_functions; cmd; cmd = cmd->next) if (!strcmp(partial, cmd->name)) return cmd->name; for (a = cmd_alias; a; a = a->next) if (!strcmp(partial, a->name)) return a->name; for (cvar = cvar_vars; cvar; cvar = cvar->next) if (!strcmp(partial, cvar->name)) return cvar->name; for (i = 0; i < 1024; i++) pmatch[i] = NULL; i = 0; /* check for partial match */ for (cmd = cmd_functions; cmd; cmd = cmd->next) if (!strncmp(partial, cmd->name, len)) { pmatch[i] = cmd->name; i++; } for (a = cmd_alias; a; a = a->next) if (!strncmp(partial, a->name, len)) { pmatch[i] = a->name; i++; } for (cvar = cvar_vars; cvar; cvar = cvar->next) if (!strncmp(partial, cvar->name, len)) { pmatch[i] = cvar->name; i++; } if (i) { if (i == 1) return pmatch[0]; Com_Printf("\nListing matches for '%s'...\n", partial); for (o = 0; o < i; o++) Com_Printf(" %s\n", pmatch[o]); strcpy(retval, ""); p = 0; while (!diff && p < 256) { retval[p] = pmatch[0][p]; for (o = 0; o < i; o++) { if (p > strlen(pmatch[o])) continue; if (retval[p] != pmatch[o][p]) { retval[p] = 0; diff = true; } } p++; } Com_Printf("Found %i matches\n", i); return retval; } return NULL; } qboolean Cmd_IsComplete(char *command) { cmd_function_t *cmd; cmdalias_t *a; cvar_t *cvar; /* check for exact match */ for (cmd = cmd_functions; cmd; cmd = cmd->next) if (!strcmp(command, cmd->name)) return true; for (a = cmd_alias; a; a = a->next) if (!strcmp(command, a->name)) return true; for (cvar = cvar_vars; cvar; cvar = cvar->next) if (!strcmp(command, cvar->name)) return true; return false; } /* * ============ Cmd_ExecuteString * * A complete command line has been parsed, so try to execute it FIXME: * lookupnoadd the token to speed search? ============ */ void Cmd_ExecuteString(char *text) { cmd_function_t *cmd; cmdalias_t *a; Cmd_TokenizeString(text, true); /* execute the command line */ if (!Cmd_Argc()) return; /* no tokens */ /* check functions */ for (cmd = cmd_functions; cmd; cmd = cmd->next) { if (!Q_strcasecmp(cmd_argv[0], cmd->name)) { if (!cmd->function) { /* forward to server command */ Cmd_ExecuteString(va("cmd %s", text)); } else cmd->function(); return; } } /* check alias */ for (a = cmd_alias; a; a = a->next) { if (!Q_strcasecmp(cmd_argv[0], a->name)) { if (++alias_count == ALIAS_LOOP_COUNT) { Com_Printf("ALIAS_LOOP_COUNT\n"); return; } Cbuf_InsertText(a->value); return; } } /* check cvars */ if (Cvar_Command()) return; /* send it as a server command if we are connected */ Cmd_ForwardToServer(); } /* * ============ Cmd_List_f ============ */ void Cmd_List_f(void) { cmd_function_t *cmd; int i; i = 0; for (cmd = cmd_functions; cmd; cmd = cmd->next, i++) Com_Printf("%s\n", cmd->name); Com_Printf("%i commands\n", i); } /* * ============ Cmd_Init ============ */ void Cmd_Init(void) { // /* register our commands */ // Cmd_AddCommand("cmdlist", Cmd_List_f); Cmd_AddCommand("exec", Cmd_Exec_f); Cmd_AddCommand("echo", Cmd_Echo_f); Cmd_AddCommand("alias", Cmd_Alias_f); Cmd_AddCommand("wait", Cmd_Wait_f); }