tron@2186: /* $Id$ */ tron@2186: rubidium@10429: /** @file console.cpp Handling of the in-game console. */ belugas@6449: darkvater@135: #include "stdafx.h" rubidium@8626: #include "core/alloc_func.hpp" rubidium@8710: #include "string_func.h" rubidium@10783: #include "strings_type.h" rubidium@10783: #include "core/math_func.hpp" rubidium@10792: #include "console_internal.h" rubidium@10792: #include "network/network.h" rubidium@10792: #include "network/network_func.h" smatz@10402: #include "rev.h" signde@274: rubidium@10687: #include rubidium@10687: rubidium@8760: #include "table/strings.h" rubidium@8760: darkvater@301: #define ICON_BUFFER 79 Darkvater@1739: #define ICON_HISTORY_SIZE 20 darkvater@301: #define ICON_LINE_HEIGHT 12 truelight@543: #define ICON_RIGHT_BORDERWIDTH 10 truelight@543: #define ICON_BOTTOM_BORDERWIDTH 12 truelight@634: #define ICON_MAX_ALIAS_LINES 40 Darkvater@1739: #define ICON_TOKEN_COUNT 20 darkvater@289: rubidium@8764: /* console parser */ rubidium@8764: IConsoleCmd *_iconsole_cmds; ///< list of registred commands rubidium@8764: IConsoleVar *_iconsole_vars; ///< list of registred vars rubidium@8764: IConsoleAlias *_iconsole_aliases; ///< list of registred aliases rubidium@8764: belugas@6449: /* ** stdlib ** */ darkvater@289: byte _stdlib_developer = 1; darkvater@289: bool _stdlib_con_developer = false; Darkvater@1739: FILE *_iconsole_output_file; signde@220: rubidium@6573: void IConsoleInit() darkvater@135: { darkvater@247: _iconsole_output_file = NULL; darkvater@1243: #ifdef ENABLE_NETWORK /* Initialize network only variables */ darkvater@1243: _redirect_console_to_client = 0; darkvater@1243: #endif darkvater@1243: rubidium@10687: IConsoleGUIInit(); darkvater@1243: signde@220: IConsoleStdLibRegister(); darkvater@135: } darkvater@135: belugas@4171: static void IConsoleWriteToLogFile(const char *string) darkvater@1046: { darkvater@1046: if (_iconsole_output_file != NULL) { belugas@6449: /* if there is an console output file ... also print it there */ rubidium@10418: if (fwrite(string, strlen(string), 1, _iconsole_output_file) != 1 || rubidium@10418: fwrite("\n", 1, 1, _iconsole_output_file) != 1) { rubidium@10418: fclose(_iconsole_output_file); rubidium@10418: _iconsole_output_file = NULL; rubidium@10685: IConsolePrintF(CC_DEFAULT, "cannot write to log file"); rubidium@10418: } darkvater@1046: } darkvater@1046: } darkvater@1046: rubidium@6573: bool CloseConsoleLogIfActive() darkvater@1046: { darkvater@1046: if (_iconsole_output_file != NULL) { rubidium@10685: IConsolePrintF(CC_DEFAULT, "file output complete"); darkvater@1046: fclose(_iconsole_output_file); darkvater@1127: _iconsole_output_file = NULL; darkvater@1046: return true; darkvater@1046: } darkvater@1046: darkvater@1046: return false; darkvater@1046: } darkvater@1046: rubidium@6573: void IConsoleFree() darkvater@135: { rubidium@10687: IConsoleGUIFree(); darkvater@1046: CloseConsoleLogIfActive(); darkvater@135: } darkvater@135: Darkvater@1588: /** Darkvater@1588: * Handle the printing of text entered into the console or redirected there Darkvater@1588: * by any other means. Text can be redirected to other players in a network game Darkvater@1588: * as well as to a logfile. If the network server is a dedicated server, all activities Darkvater@1588: * are also logged. All lines to print are added to a temporary buffer which can be Darkvater@1588: * used as a history to print them onscreen Darkvater@1588: * @param color_code the colour of the command. Red in case of errors, etc. Darkvater@1588: * @param string the message entered or output on the console (notice, error, etc.) Darkvater@1588: */ rubidium@10685: void IConsolePrint(ConsoleColour color_code, const char *string) darkvater@135: { Darkvater@5182: char *str; darkvater@1243: #ifdef ENABLE_NETWORK truelight@1026: if (_redirect_console_to_client != 0) { truelight@1026: /* Redirect the string to the client */ rubidium@10792: NetworkServerSendRcon(_redirect_console_to_client, color_code, string); truelight@1026: return; truelight@1026: } darkvater@1243: #endif truelight@1026: Darkvater@5182: /* Create a copy of the string, strip if of colours and invalid Darkvater@5182: * characters and (when applicable) assign it to the console buffer */ Darkvater@5182: str = strdup(string); Darkvater@5182: str_strip_colours(str); Darkvater@5182: str_validate(str); Darkvater@5182: truelight@543: if (_network_dedicated) { truelight@7527: fprintf(stdout, "%s\n", str); truelight@7527: fflush(stdout); Darkvater@5182: IConsoleWriteToLogFile(str); Darkvater@5182: free(str); // free duplicated string since it's not used anymore truelight@543: return; truelight@543: } truelight@543: rubidium@10687: IConsoleWriteToLogFile(str); rubidium@10687: IConsoleGUIPrint(color_code, str); darkvater@135: } darkvater@135: Darkvater@1739: /** Darkvater@1739: * Handle the printing of text entered into the console or redirected there Darkvater@1739: * by any other means. Uses printf() style format, for more information look belugas@6918: * at IConsolePrint() Darkvater@1739: */ rubidium@10685: void CDECL IConsolePrintF(ConsoleColour color_code, const char *s, ...) darkvater@135: { darkvater@135: va_list va; Darkvater@1739: char buf[ICON_MAX_STREAMSIZE]; darkvater@247: darkvater@135: va_start(va, s); Darkvater@1739: vsnprintf(buf, sizeof(buf), s, va); darkvater@135: va_end(va); darkvater@247: darkvater@289: IConsolePrint(color_code, buf); darkvater@135: } darkvater@135: Darkvater@1739: /** Darkvater@1739: * It is possible to print debugging information to the console, Darkvater@1739: * which is achieved by using this function. Can only be used by belugas@6912: * debug() in debug.cpp. You need at least a level 2 (developer) for debugging Darkvater@1739: * messages to show up Darkvater@5568: * @param dbg debugging category Darkvater@5568: * @param string debugging message Darkvater@1739: */ Darkvater@5568: void IConsoleDebug(const char *dbg, const char *string) signde@220: { darkvater@289: if (_stdlib_developer > 1) rubidium@10685: IConsolePrintF(CC_DEBUG, "dbg: [%s] %s", dbg, string); darkvater@247: } darkvater@135: Darkvater@1739: /** Darkvater@1739: * It is possible to print warnings to the console. These are mostly Darkvater@1739: * errors or mishaps, but non-fatal. You need at least a level 1 (developer) for Darkvater@1739: * debugging messages to show up Darkvater@1739: */ belugas@4171: void IConsoleWarning(const char *string) darkvater@289: { darkvater@289: if (_stdlib_developer > 0) rubidium@10685: IConsolePrintF(CC_WARNING, "WARNING: %s", string); darkvater@289: } darkvater@289: Darkvater@1739: /** Darkvater@1739: * It is possible to print error information to the console. This can include Darkvater@1739: * game errors, or errors in general you would want the user to notice Darkvater@1739: */ belugas@4171: void IConsoleError(const char *string) darkvater@289: { rubidium@10685: IConsolePrintF(CC_ERROR, "ERROR: %s", string); signde@220: } signde@220: Darkvater@1739: /** Darkvater@1739: * Change a string into its number representation. Supports Darkvater@1739: * decimal and hexadecimal numbers as well as 'on'/'off' 'true'/'false' peter1138@7868: * @param *value the variable a successful conversion will be put in Darkvater@1739: * @param *arg the string to be converted Darkvater@1739: * @return Return true on success or false on failure Darkvater@1739: */ Darkvater@1739: bool GetArgumentInteger(uint32 *value, const char *arg) signde@220: { Darkvater@1773: char *endptr; signde@220: Darkvater@1773: if (strcmp(arg, "on") == 0 || strcmp(arg, "true") == 0) { Darkvater@1773: *value = 1; Darkvater@1773: return true; Darkvater@1773: } Darkvater@1773: if (strcmp(arg, "off") == 0 || strcmp(arg, "false") == 0) { Darkvater@1773: *value = 0; Darkvater@1773: return true; Darkvater@1773: } Darkvater@1739: Darkvater@1773: *value = strtoul(arg, &endptr, 0); tron@1899: return arg != endptr; signde@220: } signde@220: belugas@6449: /* * ************************* belugas@6449: * hooking code * belugas@6449: * *************************/ belugas@6449: Darkvater@1833: /** Darkvater@1833: * General internal hooking code that is the same for both commands and variables belugas@6912: * @param hooks IConsoleHooks structure that will be set according to Darkvater@1833: * @param type type access trigger Darkvater@1833: * @param proc function called when the hook criteria is met Darkvater@1833: */ Darkvater@1833: static void IConsoleHookAdd(IConsoleHooks *hooks, IConsoleHookTypes type, IConsoleHook *proc) Darkvater@1833: { Darkvater@1833: if (hooks == NULL || proc == NULL) return; Darkvater@1833: Darkvater@1833: switch (type) { Darkvater@1833: case ICONSOLE_HOOK_ACCESS: Darkvater@1833: hooks->access = proc; Darkvater@1833: break; Darkvater@1833: case ICONSOLE_HOOK_PRE_ACTION: Darkvater@1833: hooks->pre = proc; Darkvater@1833: break; Darkvater@1833: case ICONSOLE_HOOK_POST_ACTION: Darkvater@1833: hooks->post = proc; Darkvater@1833: break; Darkvater@1889: default: NOT_REACHED(); Darkvater@1833: } Darkvater@1833: } Darkvater@1833: Darkvater@1833: /** Darkvater@1833: * Handle any special hook triggers. If the hook type is met check if Darkvater@1833: * there is a function associated with that and if so, execute it belugas@6912: * @param hooks IConsoleHooks structure that will be checked Darkvater@1833: * @param type type of hook, trigger that needs to be activated peter1138@7868: * @return true on a successful execution of the hook command or if there Darkvater@1833: * is no hook/trigger present at all. False otherwise Darkvater@1833: */ Darkvater@1833: static bool IConsoleHookHandle(const IConsoleHooks *hooks, IConsoleHookTypes type) Darkvater@1833: { Darkvater@1833: IConsoleHook *proc = NULL; Darkvater@1833: if (hooks == NULL) return false; Darkvater@1833: Darkvater@1833: switch (type) { Darkvater@1833: case ICONSOLE_HOOK_ACCESS: Darkvater@1833: proc = hooks->access; Darkvater@1833: break; Darkvater@1833: case ICONSOLE_HOOK_PRE_ACTION: Darkvater@1833: proc = hooks->pre; Darkvater@1833: break; Darkvater@1833: case ICONSOLE_HOOK_POST_ACTION: Darkvater@1833: proc = hooks->post; Darkvater@1833: break; Darkvater@1889: default: NOT_REACHED(); Darkvater@1833: } Darkvater@1833: Darkvater@1833: return (proc == NULL) ? true : proc(); Darkvater@1833: } Darkvater@1833: Darkvater@1833: /** Darkvater@1833: * Add a hook to a command that will be triggered at certain points Darkvater@1833: * @param name name of the command that the hook is added to Darkvater@1833: * @param type type of hook that is added (ACCESS, BEFORE and AFTER change) Darkvater@1833: * @param proc function called when the hook criteria is met Darkvater@1833: */ Darkvater@1833: void IConsoleCmdHookAdd(const char *name, IConsoleHookTypes type, IConsoleHook *proc) Darkvater@1833: { Darkvater@1833: IConsoleCmd *cmd = IConsoleCmdGet(name); Darkvater@1833: if (cmd == NULL) return; Darkvater@1833: IConsoleHookAdd(&cmd->hook, type, proc); Darkvater@1833: } Darkvater@1833: Darkvater@1833: /** Darkvater@1833: * Add a hook to a variable that will be triggered at certain points Darkvater@1833: * @param name name of the variable that the hook is added to Darkvater@1833: * @param type type of hook that is added (ACCESS, BEFORE and AFTER change) Darkvater@1833: * @param proc function called when the hook criteria is met Darkvater@1833: */ Darkvater@1833: void IConsoleVarHookAdd(const char *name, IConsoleHookTypes type, IConsoleHook *proc) Darkvater@1833: { Darkvater@1833: IConsoleVar *var = IConsoleVarGet(name); Darkvater@1833: if (var == NULL) return; Darkvater@1833: IConsoleHookAdd(&var->hook, type, proc); Darkvater@1833: } Darkvater@1833: Darkvater@1739: /** Darkvater@1739: * Perhaps ugly macro, but this saves us the trouble of writing the same function Darkvater@1739: * three types, just with different variables. Yes, templates would be handy. It was Darkvater@1739: * either this define or an even more ugly void* magic function Darkvater@1739: */ tron@4077: #define IConsoleAddSorted(_base, item_new, IConsoleType, type) \ tron@4077: { \ tron@4077: IConsoleType *item, *item_before; \ tron@4077: /* first command */ \ tron@4077: if (_base == NULL) { \ tron@4077: _base = item_new; \ tron@4077: return; \ tron@4077: } \ tron@4077: \ tron@4077: item_before = NULL; \ tron@4077: item = _base; \ tron@4077: \ tron@4077: /* BEGIN - Alphabetically insert the commands into the linked list */ \ tron@4077: while (item != NULL) { \ tron@4077: int i = strcmp(item->name, item_new->name); \ tron@4077: if (i == 0) { \ tron@4077: IConsoleError(type " with this name already exists; insertion aborted"); \ tron@4077: free(item_new); \ tron@4077: return; \ tron@4077: } \ tron@4077: \ tron@4077: if (i > 0) break; /* insert at this position */ \ tron@4077: \ tron@4077: item_before = item; \ tron@4077: item = item->next; \ tron@4077: } \ tron@4077: \ tron@4077: if (item_before == NULL) { \ tron@4077: _base = item_new; \ tron@4077: } else { \ tron@4077: item_before->next = item_new; \ tron@4077: } \ tron@4077: \ tron@4077: item_new->next = item; \ tron@4077: /* END - Alphabetical insert */ \ truelight@634: } truelight@634: Darkvater@1739: /** Darkvater@1739: * Register a new command to be used in the console Darkvater@1739: * @param name name of the command that will be used Darkvater@1739: * @param proc function that will be called upon execution of command Darkvater@1739: */ Darkvater@1739: void IConsoleCmdRegister(const char *name, IConsoleCmdProc *proc) truelight@634: { Darkvater@1739: char *new_cmd = strdup(name); KUDr@5860: IConsoleCmd *item_new = MallocT(1); truelight@634: Darkvater@1739: item_new->next = NULL; Darkvater@1739: item_new->proc = proc; Darkvater@1739: item_new->name = new_cmd; Darkvater@1739: Darkvater@1739: item_new->hook.access = NULL; Darkvater@1739: item_new->hook.pre = NULL; Darkvater@1739: item_new->hook.post = NULL; Darkvater@1739: Darkvater@1739: IConsoleAddSorted(_iconsole_cmds, item_new, IConsoleCmd, "a command"); Darkvater@1739: } Darkvater@1739: Darkvater@1739: /** Darkvater@1739: * Find the command pointed to by its string Darkvater@1739: * @param name command to be found Darkvater@1739: * @return return Cmdstruct of the found command, or NULL on failure Darkvater@1739: */ Darkvater@1739: IConsoleCmd *IConsoleCmdGet(const char *name) Darkvater@1739: { Darkvater@1739: IConsoleCmd *item; Darkvater@1739: Darkvater@1739: for (item = _iconsole_cmds; item != NULL; item = item->next) { truelight@634: if (strcmp(item->name, name) == 0) return item; truelight@634: } truelight@634: return NULL; truelight@634: } truelight@634: Darkvater@1739: /** Darkvater@1739: * Register a an alias for an already existing command in the console Darkvater@1739: * @param name name of the alias that will be used Darkvater@1739: * @param cmd name of the command that 'name' will be alias of Darkvater@1739: */ Darkvater@1739: void IConsoleAliasRegister(const char *name, const char *cmd) Darkvater@1725: { Darkvater@1739: char *new_alias = strdup(name); Darkvater@1739: char *cmd_aliased = strdup(cmd); KUDr@5860: IConsoleAlias *item_new = MallocT(1); truelight@634: Darkvater@1739: item_new->next = NULL; Darkvater@1739: item_new->cmdline = cmd_aliased; Darkvater@1739: item_new->name = new_alias; Darkvater@1739: Darkvater@1739: IConsoleAddSorted(_iconsole_aliases, item_new, IConsoleAlias, "an alias"); Darkvater@1739: } Darkvater@1739: Darkvater@1739: /** Darkvater@1739: * Find the alias pointed to by its string Darkvater@1739: * @param name alias to be found Darkvater@1739: * @return return Aliasstruct of the found alias, or NULL on failure Darkvater@1739: */ Darkvater@1739: IConsoleAlias *IConsoleAliasGet(const char *name) Darkvater@1739: { Darkvater@1739: IConsoleAlias* item; Darkvater@1739: Darkvater@1739: for (item = _iconsole_aliases; item != NULL; item = item->next) { Darkvater@1739: if (strcmp(item->name, name) == 0) return item; truelight@634: } truelight@634: signde@220: return NULL; darkvater@135: } darkvater@135: Darkvater@1739: /** copy in an argument into the aliasstream */ Darkvater@1739: static inline int IConsoleCopyInParams(char *dst, const char *src, uint bufpos) signde@220: { truelight@4325: int len = min(ICON_MAX_STREAMSIZE - bufpos, (uint)strlen(src)); Darkvater@1739: strncpy(dst, src, len); Darkvater@1739: Darkvater@1739: return len; Darkvater@1739: } Darkvater@1739: Darkvater@1739: /** Darkvater@1739: * An alias is just another name for a command, or for more commands Darkvater@1739: * Execute it as well. Darkvater@1739: * @param *alias is the alias of the command Darkvater@1739: * @param tokencount the number of parameters passed Darkvater@1739: * @param *tokens are the parameters given to the original command (0 is the first param) Darkvater@1739: */ belugas@4171: static void IConsoleAliasExec(const IConsoleAlias *alias, byte tokencount, char *tokens[ICON_TOKEN_COUNT]) Darkvater@1739: { Darkvater@1739: const char *cmdptr; Darkvater@1739: char *aliases[ICON_MAX_ALIAS_LINES], aliasstream[ICON_MAX_STREAMSIZE]; tron@3017: uint i; Darkvater@1739: uint a_index, astream_i; Darkvater@1739: Darkvater@1739: memset(&aliases, 0, sizeof(aliases)); Darkvater@1739: memset(&aliasstream, 0, sizeof(aliasstream)); Darkvater@1739: Darkvater@1866: if (_stdlib_con_developer) rubidium@10685: IConsolePrintF(CC_DEBUG, "condbg: requested command is an alias; parsing..."); Darkvater@1866: Darkvater@1739: aliases[0] = aliasstream; tron@1819: for (cmdptr = alias->cmdline, a_index = 0, astream_i = 0; *cmdptr != '\0'; cmdptr++) { Darkvater@1739: if (a_index >= lengthof(aliases) || astream_i >= lengthof(aliasstream)) break; Darkvater@1739: Darkvater@1739: switch (*cmdptr) { belugas@6449: case '\'': // ' will double for "" Darkvater@1739: aliasstream[astream_i++] = '"'; darkvater@289: break; belugas@6449: case ';': // Cmd seperator, start new command Darkvater@1739: aliasstream[astream_i] = '\0'; Darkvater@1739: aliases[++a_index] = &aliasstream[++astream_i]; tron@1819: cmdptr++; darkvater@289: break; belugas@6449: case '%': // Some or all parameters tron@1819: cmdptr++; Darkvater@1739: switch (*cmdptr) { belugas@6449: case '+': { // All parameters seperated: "[param 1]" "[param 2]" Darkvater@1739: for (i = 0; i != tokencount; i++) { Darkvater@1739: aliasstream[astream_i++] = '"'; Darkvater@1739: astream_i += IConsoleCopyInParams(&aliasstream[astream_i], tokens[i], astream_i); Darkvater@1739: aliasstream[astream_i++] = '"'; Darkvater@1739: aliasstream[astream_i++] = ' '; Darkvater@1739: } Darkvater@1739: } break; belugas@6449: case '!': { // Merge the parameters to one: "[param 1] [param 2] [param 3...]" Darkvater@1739: aliasstream[astream_i++] = '"'; Darkvater@1739: for (i = 0; i != tokencount; i++) { Darkvater@1739: astream_i += IConsoleCopyInParams(&aliasstream[astream_i], tokens[i], astream_i); Darkvater@1739: aliasstream[astream_i++] = ' '; Darkvater@1739: } Darkvater@1739: aliasstream[astream_i++] = '"'; Darkvater@1739: Darkvater@1739: } break; belugas@6449: default: { // One specific parameter: %A = [param 1] %B = [param 2] ... Darkvater@1739: int param = *cmdptr - 'A'; Darkvater@1739: Darkvater@1739: if (param < 0 || param >= tokencount) { Darkvater@1739: IConsoleError("too many or wrong amount of parameters passed to alias, aborting"); rubidium@10685: IConsolePrintF(CC_WARNING, "Usage of alias '%s': %s", alias->name, alias->cmdline); Darkvater@1739: return; Darkvater@1739: } Darkvater@1739: Darkvater@1739: aliasstream[astream_i++] = '"'; Darkvater@1739: astream_i += IConsoleCopyInParams(&aliasstream[astream_i], tokens[param], astream_i); Darkvater@1739: aliasstream[astream_i++] = '"'; Darkvater@1739: } break; Darkvater@1739: } break; Darkvater@1739: Darkvater@1739: default: Darkvater@1739: aliasstream[astream_i++] = *cmdptr; darkvater@289: break; Darkvater@1739: } darkvater@289: } signde@220: tron@3017: for (i = 0; i <= a_index; i++) IConsoleCmdExec(aliases[i]); // execute each alias in turn darkvater@222: } signde@220: Darkvater@1739: /** Darkvater@1739: * Special function for adding string-type variables. They in addition Darkvater@1739: * also need a 'size' value saying how long their string buffer is. belugas@6912: * @param name name of the variable that will be used belugas@6912: * @param addr memory location the variable will point to Darkvater@1739: * @param size the length of the string buffer belugas@6912: * @param help the help string shown for the variable belugas@6912: * For more information see IConsoleVarRegister() Darkvater@1739: */ Darkvater@1739: void IConsoleVarStringRegister(const char *name, void *addr, uint32 size, const char *help) Darkvater@1739: { Darkvater@1739: IConsoleVar *var; Darkvater@1739: IConsoleVarRegister(name, addr, ICONSOLE_VAR_STRING, help); Darkvater@1739: var = IConsoleVarGet(name); Darkvater@1739: var->size = size; Darkvater@1739: } Darkvater@1739: Darkvater@1739: /** Darkvater@1739: * Register a new variable to be used in the console Darkvater@1739: * @param name name of the variable that will be used Darkvater@1739: * @param addr memory location the variable will point to Darkvater@1739: * @param help the help string shown for the variable Darkvater@1739: * @param type the type of the variable (simple atomic) so we know which values it can get Darkvater@1739: */ Darkvater@1739: void IConsoleVarRegister(const char *name, void *addr, IConsoleVarTypes type, const char *help) Darkvater@1739: { Darkvater@1739: char *new_cmd = strdup(name); KUDr@5860: IConsoleVar *item_new = MallocT(1); Darkvater@1739: Darkvater@1739: item_new->help = (help != NULL) ? strdup(help) : NULL; Darkvater@1739: Darkvater@1739: item_new->next = NULL; Darkvater@1739: item_new->name = new_cmd; Darkvater@1739: item_new->addr = addr; Darkvater@1739: item_new->proc = NULL; Darkvater@1739: item_new->type = type; Darkvater@1739: Darkvater@1739: item_new->hook.access = NULL; Darkvater@1739: item_new->hook.pre = NULL; Darkvater@1739: item_new->hook.post = NULL; Darkvater@1739: Darkvater@1739: IConsoleAddSorted(_iconsole_vars, item_new, IConsoleVar, "a variable"); Darkvater@1739: } Darkvater@1739: Darkvater@1739: /** Darkvater@1739: * Find the variable pointed to by its string Darkvater@1739: * @param name variable to be found Darkvater@1739: * @return return Varstruct of the found variable, or NULL on failure Darkvater@1739: */ Darkvater@1739: IConsoleVar *IConsoleVarGet(const char *name) Darkvater@1739: { Darkvater@1739: IConsoleVar *item; Darkvater@1739: for (item = _iconsole_vars; item != NULL; item = item->next) { Darkvater@1739: if (strcmp(item->name, name) == 0) return item; Darkvater@1739: } Darkvater@1739: Darkvater@1739: return NULL; Darkvater@1739: } Darkvater@1739: Darkvater@1739: /** Darkvater@1739: * Set a new value to a console variable Darkvater@1739: * @param *var the variable being set/changed Darkvater@1739: * @param value the new value given to the variable, cast properly Darkvater@1739: */ Darkvater@1739: static void IConsoleVarSetValue(const IConsoleVar *var, uint32 value) Darkvater@1739: { Darkvater@1833: IConsoleHookHandle(&var->hook, ICONSOLE_HOOK_PRE_ACTION); signde@220: switch (var->type) { darkvater@289: case ICONSOLE_VAR_BOOLEAN: Darkvater@1739: *(bool*)var->addr = (value != 0); darkvater@289: break; darkvater@289: case ICONSOLE_VAR_BYTE: Darkvater@1739: *(byte*)var->addr = (byte)value; darkvater@289: break; darkvater@289: case ICONSOLE_VAR_UINT16: tron@2145: *(uint16*)var->addr = (uint16)value; Darkvater@1739: break; Darkvater@1739: case ICONSOLE_VAR_INT16: Darkvater@1739: *(int16*)var->addr = (int16)value; darkvater@289: break; darkvater@289: case ICONSOLE_VAR_UINT32: Darkvater@1739: *(uint32*)var->addr = (uint32)value; darkvater@289: break; darkvater@289: case ICONSOLE_VAR_INT32: Darkvater@1739: *(int32*)var->addr = (int32)value; darkvater@289: break; Darkvater@1739: default: NOT_REACHED(); darkvater@222: } Darkvater@1739: Darkvater@1833: IConsoleHookHandle(&var->hook, ICONSOLE_HOOK_POST_ACTION); Darkvater@1739: IConsoleVarPrintSetValue(var); darkvater@135: } darkvater@135: Darkvater@1739: /** Darkvater@1739: * Set a new value to a string-type variable. Basically this Darkvater@1739: * means to copy the new value over to the container. Darkvater@1739: * @param *var the variable in question Darkvater@1739: * @param *value the new value Darkvater@1739: */ belugas@4171: static void IConsoleVarSetStringvalue(const IConsoleVar *var, const char *value) signde@220: { Darkvater@1739: if (var->type != ICONSOLE_VAR_STRING || var->addr == NULL) return; Darkvater@1739: Darkvater@1833: IConsoleHookHandle(&var->hook, ICONSOLE_HOOK_PRE_ACTION); rubidium@5838: ttd_strlcpy((char*)var->addr, value, var->size); Darkvater@1833: IConsoleHookHandle(&var->hook, ICONSOLE_HOOK_POST_ACTION); Darkvater@1739: IConsoleVarPrintSetValue(var); // print out the new value, giving feedback Darkvater@1739: return; Darkvater@1739: } Darkvater@1739: Darkvater@1739: /** Darkvater@1739: * Query the current value of a variable and return it Darkvater@1739: * @param *var the variable queried Darkvater@1739: * @return current value of the variable Darkvater@1739: */ Darkvater@1739: static uint32 IConsoleVarGetValue(const IConsoleVar *var) Darkvater@1739: { Darkvater@1739: uint32 result = 0; darkvater@135: darkvater@135: switch (var->type) { darkvater@135: case ICONSOLE_VAR_BOOLEAN: Darkvater@1739: result = *(bool*)var->addr; darkvater@289: break; darkvater@135: case ICONSOLE_VAR_BYTE: Darkvater@1739: result = *(byte*)var->addr; darkvater@289: break; darkvater@135: case ICONSOLE_VAR_UINT16: Darkvater@1739: result = *(uint16*)var->addr; darkvater@289: break; darkvater@135: case ICONSOLE_VAR_INT16: Darkvater@1739: result = *(int16*)var->addr; Darkvater@1739: break; Darkvater@1739: case ICONSOLE_VAR_UINT32: Darkvater@1739: result = *(uint32*)var->addr; darkvater@289: break; darkvater@135: case ICONSOLE_VAR_INT32: Darkvater@1739: result = *(int32*)var->addr; Darkvater@1739: break; Darkvater@1739: default: NOT_REACHED(); Darkvater@1739: } Darkvater@1739: return result; Darkvater@1739: } Darkvater@1739: Darkvater@1739: /** Darkvater@1739: * Get the value of the variable and put it into a printable Darkvater@1739: * string form so we can use it for printing Darkvater@1739: */ Darkvater@1739: static char *IConsoleVarGetStringValue(const IConsoleVar *var) Darkvater@1739: { Darkvater@1739: static char tempres[50]; Darkvater@1739: char *value = tempres; Darkvater@1739: Darkvater@1739: switch (var->type) { Darkvater@1739: case ICONSOLE_VAR_BOOLEAN: Darkvater@1739: snprintf(tempres, sizeof(tempres), "%s", (*(bool*)var->addr) ? "on" : "off"); Darkvater@1739: break; Darkvater@1739: case ICONSOLE_VAR_BYTE: Darkvater@1739: snprintf(tempres, sizeof(tempres), "%u", *(byte*)var->addr); Darkvater@1739: break; Darkvater@1739: case ICONSOLE_VAR_UINT16: Darkvater@1739: snprintf(tempres, sizeof(tempres), "%u", *(uint16*)var->addr); Darkvater@1739: break; Darkvater@1739: case ICONSOLE_VAR_UINT32: Darkvater@1739: snprintf(tempres, sizeof(tempres), "%u", *(uint32*)var->addr); Darkvater@1739: break; Darkvater@1739: case ICONSOLE_VAR_INT16: Darkvater@1739: snprintf(tempres, sizeof(tempres), "%i", *(int16*)var->addr); Darkvater@1739: break; Darkvater@1739: case ICONSOLE_VAR_INT32: Darkvater@1739: snprintf(tempres, sizeof(tempres), "%i", *(int32*)var->addr); darkvater@289: break; darkvater@135: case ICONSOLE_VAR_STRING: Darkvater@1739: value = (char*)var->addr; Darkvater@1889: break; Darkvater@1739: default: NOT_REACHED(); darkvater@289: } Darkvater@1739: Darkvater@1739: return value; Darkvater@1739: } Darkvater@1739: Darkvater@1739: /** Darkvater@1739: * Print out the value of the variable when asked Darkvater@1739: */ Darkvater@1739: void IConsoleVarPrintGetValue(const IConsoleVar *var) Darkvater@1739: { Darkvater@1739: char *value; Darkvater@1739: /* Some variables need really specific handling, handle this in its Darkvater@1739: * callback function */ Darkvater@1739: if (var->proc != NULL) { Darkvater@1739: var->proc(0, NULL); Darkvater@1739: return; Darkvater@1739: } Darkvater@1739: Darkvater@1739: value = IConsoleVarGetStringValue(var); rubidium@10685: IConsolePrintF(CC_WARNING, "Current value for '%s' is: %s", var->name, value); Darkvater@1739: } Darkvater@1739: Darkvater@1739: /** Darkvater@1739: * Print out the value of the variable after it has been assigned Darkvater@1739: * a new value, thus giving us feedback on the action Darkvater@1739: */ Darkvater@1739: void IConsoleVarPrintSetValue(const IConsoleVar *var) Darkvater@1739: { Darkvater@1739: char *value = IConsoleVarGetStringValue(var); rubidium@10685: IConsolePrintF(CC_WARNING, "'%s' changed to: %s", var->name, value); Darkvater@1739: } Darkvater@1739: Darkvater@1739: /** Darkvater@1739: * Execute a variable command. Without any parameters, print out its value Darkvater@1739: * with parameters it assigns a new value to the variable Darkvater@1739: * @param *var the variable that we will be querying/changing Darkvater@1739: * @param tokencount how many additional parameters have been given to the commandline Darkvater@1739: * @param *token the actual parameters the variable was called with Darkvater@1739: */ Darkvater@1739: void IConsoleVarExec(const IConsoleVar *var, byte tokencount, char *token[ICON_TOKEN_COUNT]) Darkvater@1739: { Darkvater@1739: const char *tokenptr = token[0]; Darkvater@1739: byte t_index = tokencount; Darkvater@1739: uint32 value; Darkvater@1739: Darkvater@1866: if (_stdlib_con_developer) rubidium@10685: IConsolePrintF(CC_DEBUG, "condbg: requested command is a variable"); Darkvater@1866: Darkvater@1739: if (tokencount == 0) { /* Just print out value */ Darkvater@1739: IConsoleVarPrintGetValue(var); Darkvater@1739: return; Darkvater@1739: } Darkvater@1739: Darkvater@1739: /* Use of assignment sign is not mandatory but supported, so just 'ignore it appropiately' */ Darkvater@1739: if (strcmp(tokenptr, "=") == 0) tokencount--; Darkvater@1739: Darkvater@1739: if (tokencount == 1) { Darkvater@1739: /* Some variables need really special handling, handle it in their callback procedure */ Darkvater@1739: if (var->proc != NULL) { Darkvater@1739: var->proc(tokencount, &token[t_index - tokencount]); // set the new value Darkvater@1739: return; Darkvater@1739: } Darkvater@1739: /* Strings need special processing. No need to convert the argument to Darkvater@1739: * an integer value, just copy over the argument on a one-by-one basis */ Darkvater@1739: if (var->type == ICONSOLE_VAR_STRING) { Darkvater@1739: IConsoleVarSetStringvalue(var, token[t_index - tokencount]); Darkvater@1739: return; Darkvater@1739: } else if (GetArgumentInteger(&value, token[t_index - tokencount])) { Darkvater@1739: IConsoleVarSetValue(var, value); Darkvater@1739: return; Darkvater@1739: } Darkvater@1739: Darkvater@1739: /* Increase or decrease the value by one. This of course can only happen to 'number' types */ Darkvater@1739: if (strcmp(tokenptr, "++") == 0 && var->type != ICONSOLE_VAR_STRING) { Darkvater@1739: IConsoleVarSetValue(var, IConsoleVarGetValue(var) + 1); Darkvater@1739: return; Darkvater@1739: } Darkvater@1739: Darkvater@1739: if (strcmp(tokenptr, "--") == 0 && var->type != ICONSOLE_VAR_STRING) { Darkvater@1739: IConsoleVarSetValue(var, IConsoleVarGetValue(var) - 1); Darkvater@1739: return; Darkvater@1739: } Darkvater@1739: } Darkvater@1739: Darkvater@1739: IConsoleError("invalid variable assignment"); darkvater@135: } darkvater@135: Darkvater@1739: /** Darkvater@1739: * Add a callback function to the variable. Some variables need Darkvater@1739: * very special processing, which can only be done with custom code Darkvater@1739: * @param name name of the variable the callback function is added to Darkvater@1739: * @param proc the function called Darkvater@1739: */ Darkvater@1739: void IConsoleVarProcAdd(const char *name, IConsoleCmdProc *proc) Darkvater@1739: { Darkvater@1739: IConsoleVar *var = IConsoleVarGet(name); Darkvater@1739: if (var == NULL) return; Darkvater@1739: var->proc = proc; Darkvater@1739: } tron@1109: Darkvater@1739: /** Darkvater@1739: * Execute a given command passed to us. First chop it up into Darkvater@1739: * individual tokens (seperated by spaces), then execute it if possible Darkvater@1739: * @param cmdstr string to be parsed and executed Darkvater@1739: */ Darkvater@1739: void IConsoleCmdExec(const char *cmdstr) Darkvater@1739: { Darkvater@1739: IConsoleCmd *cmd = NULL; Darkvater@1739: IConsoleAlias *alias = NULL; Darkvater@1739: IConsoleVar *var = NULL; Darkvater@1726: Darkvater@1739: const char *cmdptr; Darkvater@1739: char *tokens[ICON_TOKEN_COUNT], tokenstream[ICON_MAX_STREAMSIZE]; Darkvater@1739: uint t_index, tstream_i; Darkvater@1739: Darkvater@1739: bool longtoken = false; Darkvater@1739: bool foundtoken = false; Darkvater@1739: Darkvater@1866: if (cmdstr[0] == '#') return; // comments Darkvater@1866: tron@1819: for (cmdptr = cmdstr; *cmdptr != '\0'; cmdptr++) { peter1138@5108: if (!IsValidChar(*cmdptr, CS_ALPHANUMERAL)) { Darkvater@1739: IConsoleError("command contains malformed characters, aborting"); rubidium@10685: IConsolePrintF(CC_ERROR, "ERROR: command was: '%s'", cmdstr); Darkvater@1739: return; tron@1379: } tron@1379: } signde@220: dominik@640: if (_stdlib_con_developer) rubidium@10685: IConsolePrintF(CC_DEBUG, "condbg: executing cmdline: '%s'", cmdstr); signde@220: Darkvater@1739: memset(&tokens, 0, sizeof(tokens)); Darkvater@1739: memset(&tokenstream, 0, sizeof(tokenstream)); signde@220: Darkvater@1739: /* 1. Split up commandline into tokens, seperated by spaces, commands Darkvater@1739: * enclosed in "" are taken as one token. We can only go as far as the amount Darkvater@1739: * of characters in our stream or the max amount of tokens we can handle */ tron@1819: for (cmdptr = cmdstr, t_index = 0, tstream_i = 0; *cmdptr != '\0'; cmdptr++) { Darkvater@1739: if (t_index >= lengthof(tokens) || tstream_i >= lengthof(tokenstream)) break; Darkvater@1739: Darkvater@1739: switch (*cmdptr) { Darkvater@1739: case ' ': /* Token seperator */ Darkvater@1739: if (!foundtoken) break; Darkvater@1739: Darkvater@1746: if (longtoken) { Darkvater@1746: tokenstream[tstream_i] = *cmdptr; Darkvater@1746: } else { Darkvater@1746: tokenstream[tstream_i] = '\0'; Darkvater@1746: foundtoken = false; Darkvater@1746: } Darkvater@1739: Darkvater@1739: tstream_i++; Darkvater@1739: break; belugas@6449: case '"': // Tokens enclosed in "" are one token Darkvater@1739: longtoken = !longtoken; Darkvater@1739: break; belugas@6449: case '\\': // Escape character for "" Darkvater@4905: if (cmdptr[1] == '"' && tstream_i + 1 < lengthof(tokenstream)) { Darkvater@4905: tokenstream[tstream_i++] = *++cmdptr; Darkvater@4905: break; Darkvater@4905: } Darkvater@4905: /* fallthrough */ belugas@6449: default: // Normal character Darkvater@1739: tokenstream[tstream_i++] = *cmdptr; Darkvater@1739: Darkvater@1739: if (!foundtoken) { Darkvater@1739: tokens[t_index++] = &tokenstream[tstream_i - 1]; Darkvater@1739: foundtoken = true; darkvater@135: } Darkvater@1739: break; signde@220: } darkvater@289: } signde@220: Darkvater@1739: if (_stdlib_con_developer) { Darkvater@1739: uint i; tron@4077: tron@4077: for (i = 0; tokens[i] != NULL; i++) { rubidium@10685: IConsolePrintF(CC_DEBUG, "condbg: token %d is: '%s'", i, tokens[i]); tron@4077: } darkvater@135: } darkvater@135: Darkvater@1828: if (tokens[0] == '\0') return; // don't execute empty commands Darkvater@1739: /* 2. Determine type of command (cmd, alias or variable) and execute Darkvater@1739: * First try commands, then aliases, and finally variables. Execute Darkvater@1739: * the found action taking into account its hooking code Darkvater@1739: */ tron@4077: cmd = IConsoleCmdGet(tokens[0]); tron@4077: if (cmd != NULL) { Darkvater@1739: if (IConsoleHookHandle(&cmd->hook, ICONSOLE_HOOK_ACCESS)) { Darkvater@1739: IConsoleHookHandle(&cmd->hook, ICONSOLE_HOOK_PRE_ACTION); Darkvater@1739: if (cmd->proc(t_index, tokens)) { // index started with 0 Darkvater@1739: IConsoleHookHandle(&cmd->hook, ICONSOLE_HOOK_POST_ACTION); tron@4077: } else { tron@4077: cmd->proc(0, NULL); // if command failed, give help tron@4077: } Darkvater@1739: } tron@4077: return; tron@4077: } Darkvater@1739: tron@4077: t_index--; // ignore the variable-name for comfort for both aliases and variaables tron@4077: alias = IConsoleAliasGet(tokens[0]); tron@4077: if (alias != NULL) { tron@4077: IConsoleAliasExec(alias, t_index, &tokens[1]); tron@4077: return; tron@4077: } Darkvater@1739: tron@4077: var = IConsoleVarGet(tokens[0]); tron@4077: if (var != NULL) { tron@4077: if (IConsoleHookHandle(&var->hook, ICONSOLE_HOOK_ACCESS)) { tron@4077: IConsoleVarExec(var, t_index, &tokens[1]); tron@4077: } tron@4077: return; tron@4077: } Darkvater@1833: tron@4077: IConsoleError("command or variable not found"); darkvater@135: }