4 * This file is part of OpenTTD.
5 * OpenTTD 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, version 2.
6 * OpenTTD 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.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
10 /** @file console.cpp Handling of the in-game console. */
13 #include "console_internal.h"
14 #include "network/network.h"
15 #include "network/network_func.h"
16 #include "network/network_admin.h"
18 #include "console_func.h"
19 #include "settings_type.h"
23 static const uint ICON_TOKEN_COUNT
= 20; ///< Maximum number of tokens in one command
24 static const uint ICON_MAX_ALIAS_LINES
= 40; ///< Maximum number of commands executed by one alias
27 IConsoleCmd
*_iconsole_cmds
; ///< list of registered commands
28 IConsoleAlias
*_iconsole_aliases
; ///< list of registered aliases
30 FILE *_iconsole_output_file
;
34 _iconsole_output_file
= NULL
;
35 #ifdef ENABLE_NETWORK /* Initialize network only variables */
36 _redirect_console_to_client
= INVALID_CLIENT_ID
;
37 _redirect_console_to_admin
= INVALID_ADMIN_ID
;
42 IConsoleStdLibRegister();
45 static void IConsoleWriteToLogFile(const char *string
)
47 if (_iconsole_output_file
!= NULL
) {
48 /* if there is an console output file ... also print it there */
49 const char *header
= GetLogPrefix();
50 if ((strlen(header
) != 0 && fwrite(header
, strlen(header
), 1, _iconsole_output_file
) != 1) ||
51 fwrite(string
, strlen(string
), 1, _iconsole_output_file
) != 1 ||
52 fwrite("\n", 1, 1, _iconsole_output_file
) != 1) {
53 fclose(_iconsole_output_file
);
54 _iconsole_output_file
= NULL
;
55 IConsolePrintF(CC_DEFAULT
, "cannot write to log file");
60 bool CloseConsoleLogIfActive()
62 if (_iconsole_output_file
!= NULL
) {
63 IConsolePrintF(CC_DEFAULT
, "file output complete");
64 fclose(_iconsole_output_file
);
65 _iconsole_output_file
= NULL
;
75 CloseConsoleLogIfActive();
79 * Handle the printing of text entered into the console or redirected there
80 * by any other means. Text can be redirected to other clients in a network game
81 * as well as to a logfile. If the network server is a dedicated server, all activities
82 * are also logged. All lines to print are added to a temporary buffer which can be
83 * used as a history to print them onscreen
84 * @param colour_code the colour of the command. Red in case of errors, etc.
85 * @param string the message entered or output on the console (notice, error, etc.)
87 void IConsolePrint(TextColour colour_code
, const char *string
)
89 assert(IsValidConsoleColour(colour_code
));
93 if (_redirect_console_to_client
!= INVALID_CLIENT_ID
) {
94 /* Redirect the string to the client */
95 NetworkServerSendRcon(_redirect_console_to_client
, colour_code
, string
);
99 if (_redirect_console_to_admin
!= INVALID_ADMIN_ID
) {
100 NetworkServerSendAdminRcon(_redirect_console_to_admin
, colour_code
, string
);
105 /* Create a copy of the string, strip if of colours and invalid
106 * characters and (when applicable) assign it to the console buffer */
107 str
= strdup(string
);
108 str_strip_colours(str
);
109 str_validate(str
, str
+ strlen(str
));
111 if (_network_dedicated
) {
112 #ifdef ENABLE_NETWORK
113 NetworkAdminConsole("console", str
);
114 #endif /* ENABLE_NETWORK */
115 fprintf(stdout
, "%s%s\n", GetLogPrefix(), str
);
117 IConsoleWriteToLogFile(str
);
118 free(str
); // free duplicated string since it's not used anymore
122 IConsoleWriteToLogFile(str
);
123 IConsoleGUIPrint(colour_code
, str
);
127 * Handle the printing of text entered into the console or redirected there
128 * by any other means. Uses printf() style format, for more information look
131 void CDECL
IConsolePrintF(TextColour colour_code
, const char *format
, ...)
133 assert(IsValidConsoleColour(colour_code
));
136 char buf
[ICON_MAX_STREAMSIZE
];
138 va_start(va
, format
);
139 vsnprintf(buf
, sizeof(buf
), format
, va
);
142 IConsolePrint(colour_code
, buf
);
146 * It is possible to print debugging information to the console,
147 * which is achieved by using this function. Can only be used by
148 * debug() in debug.cpp. You need at least a level 2 (developer) for debugging
149 * messages to show up
150 * @param dbg debugging category
151 * @param string debugging message
153 void IConsoleDebug(const char *dbg
, const char *string
)
155 if (_settings_client
.gui
.developer
<= 1) return;
156 IConsolePrintF(CC_DEBUG
, "dbg: [%s] %s", dbg
, string
);
160 * It is possible to print warnings to the console. These are mostly
161 * errors or mishaps, but non-fatal. You need at least a level 1 (developer) for
162 * debugging messages to show up
164 void IConsoleWarning(const char *string
)
166 if (_settings_client
.gui
.developer
== 0) return;
167 IConsolePrintF(CC_WARNING
, "WARNING: %s", string
);
171 * It is possible to print error information to the console. This can include
172 * game errors, or errors in general you would want the user to notice
174 void IConsoleError(const char *string
)
176 IConsolePrintF(CC_ERROR
, "ERROR: %s", string
);
180 * Change a string into its number representation. Supports
181 * decimal and hexadecimal numbers as well as 'on'/'off' 'true'/'false'
182 * @param *value the variable a successful conversion will be put in
183 * @param *arg the string to be converted
184 * @return Return true on success or false on failure
186 bool GetArgumentInteger(uint32
*value
, const char *arg
)
190 if (strcmp(arg
, "on") == 0 || strcmp(arg
, "true") == 0) {
194 if (strcmp(arg
, "off") == 0 || strcmp(arg
, "false") == 0) {
199 *value
= strtoul(arg
, &endptr
, 0);
200 return arg
!= endptr
;
204 * Add an item to an alphabetically sorted list.
205 * @param base first item of the list
206 * @param item_new the item to add
209 void IConsoleAddSorted(T
**base
, T
*item_new
)
216 T
*item_before
= NULL
;
218 /* The list is alphabetically sorted, insert the new item at the correct location */
219 while (item
!= NULL
) {
220 if (strcmp(item
->name
, item_new
->name
) > 0) break; // insert here
226 if (item_before
== NULL
) {
229 item_before
->next
= item_new
;
232 item_new
->next
= item
;
236 * Remove underscores from a string; the string will be modified!
237 * @param name The string to remove the underscores from.
240 char *RemoveUnderscores(char *name
)
243 for (const char *p
= name
; *p
!= '\0'; p
++) {
244 if (*p
!= '_') *q
++ = *p
;
251 * Register a new command to be used in the console
252 * @param name name of the command that will be used
253 * @param proc function that will be called upon execution of command
255 void IConsoleCmdRegister(const char *name
, IConsoleCmdProc
*proc
, IConsoleHook
*hook
)
257 IConsoleCmd
*item_new
= MallocT
<IConsoleCmd
>(1);
258 item_new
->name
= RemoveUnderscores(strdup(name
));
259 item_new
->next
= NULL
;
260 item_new
->proc
= proc
;
261 item_new
->hook
= hook
;
263 IConsoleAddSorted(&_iconsole_cmds
, item_new
);
267 * Find the command pointed to by its string
268 * @param name command to be found
269 * @return return Cmdstruct of the found command, or NULL on failure
271 IConsoleCmd
*IConsoleCmdGet(const char *name
)
275 for (item
= _iconsole_cmds
; item
!= NULL
; item
= item
->next
) {
276 if (strcmp(item
->name
, name
) == 0) return item
;
282 * Register a an alias for an already existing command in the console
283 * @param name name of the alias that will be used
284 * @param cmd name of the command that 'name' will be alias of
286 void IConsoleAliasRegister(const char *name
, const char *cmd
)
288 if (IConsoleAliasGet(name
) != NULL
) {
289 IConsoleError("an alias with this name already exists; insertion aborted");
293 char *new_alias
= RemoveUnderscores(strdup(name
));
294 char *cmd_aliased
= strdup(cmd
);
295 IConsoleAlias
*item_new
= MallocT
<IConsoleAlias
>(1);
297 item_new
->next
= NULL
;
298 item_new
->cmdline
= cmd_aliased
;
299 item_new
->name
= new_alias
;
301 IConsoleAddSorted(&_iconsole_aliases
, item_new
);
305 * Find the alias pointed to by its string
306 * @param name alias to be found
307 * @return return Aliasstruct of the found alias, or NULL on failure
309 IConsoleAlias
*IConsoleAliasGet(const char *name
)
313 for (item
= _iconsole_aliases
; item
!= NULL
; item
= item
->next
) {
314 if (strcmp(item
->name
, name
) == 0) return item
;
320 /** copy in an argument into the aliasstream */
321 static inline int IConsoleCopyInParams(char *dst
, const char *src
, uint bufpos
)
323 /* len is the amount of bytes to add excluding the '\0'-termination */
324 int len
= min(ICON_MAX_STREAMSIZE
- bufpos
- 1, (uint
)strlen(src
));
325 strecpy(dst
, src
, dst
+ len
);
331 * An alias is just another name for a command, or for more commands
332 * Execute it as well.
333 * @param *alias is the alias of the command
334 * @param tokencount the number of parameters passed
335 * @param *tokens are the parameters given to the original command (0 is the first param)
337 static void IConsoleAliasExec(const IConsoleAlias
*alias
, byte tokencount
, char *tokens
[ICON_TOKEN_COUNT
])
340 char *aliases
[ICON_MAX_ALIAS_LINES
], aliasstream
[ICON_MAX_STREAMSIZE
];
342 uint a_index
, astream_i
;
344 memset(&aliases
, 0, sizeof(aliases
));
345 memset(&aliasstream
, 0, sizeof(aliasstream
));
347 DEBUG(console
, 6, "Requested command is an alias; parsing...");
349 aliases
[0] = aliasstream
;
350 for (cmdptr
= alias
->cmdline
, a_index
= 0, astream_i
= 0; *cmdptr
!= '\0'; cmdptr
++) {
351 if (a_index
>= lengthof(aliases
) || astream_i
>= lengthof(aliasstream
)) break;
354 case '\'': // ' will double for ""
355 aliasstream
[astream_i
++] = '"';
358 case ';': // Cmd separator, start new command
359 aliasstream
[astream_i
] = '\0';
360 aliases
[++a_index
] = &aliasstream
[++astream_i
];
364 case '%': // Some or all parameters
367 case '+': { // All parameters separated: "[param 1]" "[param 2]"
368 for (i
= 0; i
!= tokencount
; i
++) {
369 aliasstream
[astream_i
++] = '"';
370 astream_i
+= IConsoleCopyInParams(&aliasstream
[astream_i
], tokens
[i
], astream_i
);
371 aliasstream
[astream_i
++] = '"';
372 aliasstream
[astream_i
++] = ' ';
377 case '!': { // Merge the parameters to one: "[param 1] [param 2] [param 3...]"
378 aliasstream
[astream_i
++] = '"';
379 for (i
= 0; i
!= tokencount
; i
++) {
380 astream_i
+= IConsoleCopyInParams(&aliasstream
[astream_i
], tokens
[i
], astream_i
);
381 aliasstream
[astream_i
++] = ' ';
383 aliasstream
[astream_i
++] = '"';
387 default: { // One specific parameter: %A = [param 1] %B = [param 2] ...
388 int param
= *cmdptr
- 'A';
390 if (param
< 0 || param
>= tokencount
) {
391 IConsoleError("too many or wrong amount of parameters passed to alias, aborting");
392 IConsolePrintF(CC_WARNING
, "Usage of alias '%s': %s", alias
->name
, alias
->cmdline
);
396 aliasstream
[astream_i
++] = '"';
397 astream_i
+= IConsoleCopyInParams(&aliasstream
[astream_i
], tokens
[param
], astream_i
);
398 aliasstream
[astream_i
++] = '"';
405 aliasstream
[astream_i
++] = *cmdptr
;
410 for (i
= 0; i
<= a_index
; i
++) IConsoleCmdExec(aliases
[i
]); // execute each alias in turn
414 * Execute a given command passed to us. First chop it up into
415 * individual tokens (separated by spaces), then execute it if possible
416 * @param cmdstr string to be parsed and executed
418 void IConsoleCmdExec(const char *cmdstr
)
421 char *tokens
[ICON_TOKEN_COUNT
], tokenstream
[ICON_MAX_STREAMSIZE
];
422 uint t_index
, tstream_i
;
424 bool longtoken
= false;
425 bool foundtoken
= false;
427 if (cmdstr
[0] == '#') return; // comments
429 for (cmdptr
= cmdstr
; *cmdptr
!= '\0'; cmdptr
++) {
430 if (!IsValidChar(*cmdptr
, CS_ALPHANUMERAL
)) {
431 IConsoleError("command contains malformed characters, aborting");
432 IConsolePrintF(CC_ERROR
, "ERROR: command was: '%s'", cmdstr
);
437 DEBUG(console
, 4, "Executing cmdline: '%s'", cmdstr
);
439 memset(&tokens
, 0, sizeof(tokens
));
440 memset(&tokenstream
, 0, sizeof(tokenstream
));
442 /* 1. Split up commandline into tokens, separated by spaces, commands
443 * enclosed in "" are taken as one token. We can only go as far as the amount
444 * of characters in our stream or the max amount of tokens we can handle */
445 for (cmdptr
= cmdstr
, t_index
= 0, tstream_i
= 0; *cmdptr
!= '\0'; cmdptr
++) {
446 if (t_index
>= lengthof(tokens
) || tstream_i
>= lengthof(tokenstream
)) break;
449 case ' ': // Token separator
450 if (!foundtoken
) break;
453 tokenstream
[tstream_i
] = *cmdptr
;
455 tokenstream
[tstream_i
] = '\0';
461 case '"': // Tokens enclosed in "" are one token
462 longtoken
= !longtoken
;
464 tokens
[t_index
++] = &tokenstream
[tstream_i
];
468 case '\\': // Escape character for ""
469 if (cmdptr
[1] == '"' && tstream_i
+ 1 < lengthof(tokenstream
)) {
470 tokenstream
[tstream_i
++] = *++cmdptr
;
474 default: // Normal character
475 tokenstream
[tstream_i
++] = *cmdptr
;
478 tokens
[t_index
++] = &tokenstream
[tstream_i
- 1];
485 for (uint i
= 0; tokens
[i
] != NULL
; i
++) {
486 DEBUG(console
, 8, "Token %d is: '%s'", i
, tokens
[i
]);
489 if (tokens
[0] == '\0') return; // don't execute empty commands
490 /* 2. Determine type of command (cmd or alias) and execute
491 * First try commands, then aliases. Execute
492 * the found action taking into account its hooking code
494 RemoveUnderscores(tokens
[0]);
495 IConsoleCmd
*cmd
= IConsoleCmdGet(tokens
[0]);
497 ConsoleHookResult chr
= (cmd
->hook
== NULL
? CHR_ALLOW
: cmd
->hook(true));
500 if (!cmd
->proc(t_index
, tokens
)) { // index started with 0
501 cmd
->proc(0, NULL
); // if command failed, give help
505 case CHR_DISALLOW
: return;
506 case CHR_HIDE
: break;
511 IConsoleAlias
*alias
= IConsoleAliasGet(tokens
[0]);
513 IConsoleAliasExec(alias
, t_index
, &tokens
[1]);
517 IConsoleError("command not found");