2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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/>.
8 /** @file console.cpp Handling of the in-game console. */
11 #include "console_internal.h"
12 #include "network/network.h"
13 #include "network/network_func.h"
14 #include "network/network_admin.h"
16 #include "console_func.h"
17 #include "settings_type.h"
21 #include "safeguards.h"
23 static const uint ICON_TOKEN_COUNT
= 20; ///< Maximum number of tokens in one command
24 static const uint ICON_MAX_RECURSE
= 10; ///< Maximum number of recursion
27 /* static */ IConsole::CommandList
&IConsole::Commands()
29 static IConsole::CommandList cmds
;
33 /* static */ IConsole::AliasList
&IConsole::Aliases()
35 static IConsole::AliasList aliases
;
39 FILE *_iconsole_output_file
;
43 _iconsole_output_file
= nullptr;
44 _redirect_console_to_client
= INVALID_CLIENT_ID
;
45 _redirect_console_to_admin
= INVALID_ADMIN_ID
;
49 IConsoleStdLibRegister();
52 static void IConsoleWriteToLogFile(const char *string
)
54 if (_iconsole_output_file
!= nullptr) {
55 /* if there is an console output file ... also print it there */
56 const char *header
= GetLogPrefix();
57 if ((strlen(header
) != 0 && fwrite(header
, strlen(header
), 1, _iconsole_output_file
) != 1) ||
58 fwrite(string
, strlen(string
), 1, _iconsole_output_file
) != 1 ||
59 fwrite("\n", 1, 1, _iconsole_output_file
) != 1) {
60 fclose(_iconsole_output_file
);
61 _iconsole_output_file
= nullptr;
62 IConsolePrint(CC_ERROR
, "Cannot write to console log file; closing the log file.");
67 bool CloseConsoleLogIfActive()
69 if (_iconsole_output_file
!= nullptr) {
70 IConsolePrint(CC_INFO
, "Console log file closed.");
71 fclose(_iconsole_output_file
);
72 _iconsole_output_file
= nullptr;
82 CloseConsoleLogIfActive();
86 * Handle the printing of text entered into the console or redirected there
87 * by any other means. Text can be redirected to other clients in a network game
88 * as well as to a logfile. If the network server is a dedicated server, all activities
89 * are also logged. All lines to print are added to a temporary buffer which can be
90 * used as a history to print them onscreen
91 * @param colour_code The colour of the command.
92 * @param string The message to output on the console (notice, error, etc.)
94 void IConsolePrint(TextColour colour_code
, const std::string
&string
)
96 assert(IsValidConsoleColour(colour_code
));
98 if (_redirect_console_to_client
!= INVALID_CLIENT_ID
) {
99 /* Redirect the string to the client */
100 NetworkServerSendRcon(_redirect_console_to_client
, colour_code
, string
);
104 if (_redirect_console_to_admin
!= INVALID_ADMIN_ID
) {
105 NetworkServerSendAdminRcon(_redirect_console_to_admin
, colour_code
, string
);
109 /* Create a copy of the string, strip if of colours and invalid
110 * characters and (when applicable) assign it to the console buffer */
111 char *str
= stredup(string
.c_str());
112 str_strip_colours(str
);
113 StrMakeValidInPlace(str
);
115 if (_network_dedicated
) {
116 NetworkAdminConsole("console", str
);
117 fprintf(stdout
, "%s%s\n", GetLogPrefix(), str
);
119 IConsoleWriteToLogFile(str
);
120 free(str
); // free duplicated string since it's not used anymore
124 IConsoleWriteToLogFile(str
);
125 IConsoleGUIPrint(colour_code
, str
);
129 * Change a string into its number representation. Supports
130 * decimal and hexadecimal numbers as well as 'on'/'off' 'true'/'false'
131 * @param *value the variable a successful conversion will be put in
132 * @param *arg the string to be converted
133 * @return Return true on success or false on failure
135 bool GetArgumentInteger(uint32
*value
, const char *arg
)
139 if (strcmp(arg
, "on") == 0 || strcmp(arg
, "true") == 0) {
143 if (strcmp(arg
, "off") == 0 || strcmp(arg
, "false") == 0) {
148 *value
= strtoul(arg
, &endptr
, 0);
149 return arg
!= endptr
;
153 * Creates a copy of a string with underscores removed from it
154 * @param name String to remove the underscores from.
155 * @return A copy of \a name, without underscores.
157 static std::string
RemoveUnderscores(std::string name
)
159 name
.erase(std::remove(name
.begin(), name
.end(), '_'), name
.end());
164 * Register a new command to be used in the console
165 * @param name name of the command that will be used
166 * @param proc function that will be called upon execution of command
168 /* static */ void IConsole::CmdRegister(const std::string
&name
, IConsoleCmdProc
*proc
, IConsoleHook
*hook
)
170 IConsole::Commands().try_emplace(RemoveUnderscores(name
), name
, proc
, hook
);
174 * Find the command pointed to by its string
175 * @param name command to be found
176 * @return return Cmdstruct of the found command, or nullptr on failure
178 /* static */ IConsoleCmd
*IConsole::CmdGet(const std::string
&name
)
180 auto item
= IConsole::Commands().find(RemoveUnderscores(name
));
181 if (item
!= IConsole::Commands().end()) return &item
->second
;
186 * Register a an alias for an already existing command in the console
187 * @param name name of the alias that will be used
188 * @param cmd name of the command that 'name' will be alias of
190 /* static */ void IConsole::AliasRegister(const std::string
&name
, const std::string
&cmd
)
192 auto result
= IConsole::Aliases().try_emplace(RemoveUnderscores(name
), name
, cmd
);
193 if (!result
.second
) IConsolePrint(CC_ERROR
, "An alias with the name '{}' already exists.", name
);
197 * Find the alias pointed to by its string
198 * @param name alias to be found
199 * @return return Aliasstruct of the found alias, or nullptr on failure
201 /* static */ IConsoleAlias
*IConsole::AliasGet(const std::string
&name
)
203 auto item
= IConsole::Aliases().find(RemoveUnderscores(name
));
204 if (item
!= IConsole::Aliases().end()) return &item
->second
;
209 * An alias is just another name for a command, or for more commands
210 * Execute it as well.
211 * @param *alias is the alias of the command
212 * @param tokencount the number of parameters passed
213 * @param *tokens are the parameters given to the original command (0 is the first param)
215 static void IConsoleAliasExec(const IConsoleAlias
*alias
, byte tokencount
, char *tokens
[ICON_TOKEN_COUNT
], const uint recurse_count
)
217 char alias_buffer
[ICON_MAX_STREAMSIZE
] = { '\0' };
218 char *alias_stream
= alias_buffer
;
220 Debug(console
, 6, "Requested command is an alias; parsing...");
222 if (recurse_count
> ICON_MAX_RECURSE
) {
223 IConsolePrint(CC_ERROR
, "Too many alias expansions, recursion limit reached.");
227 for (const char *cmdptr
= alias
->cmdline
.c_str(); *cmdptr
!= '\0'; cmdptr
++) {
229 case '\'': // ' will double for ""
230 alias_stream
= strecpy(alias_stream
, "\"", lastof(alias_buffer
));
233 case ';': // Cmd separator; execute previous and start new command
234 IConsoleCmdExec(alias_buffer
, recurse_count
);
236 alias_stream
= alias_buffer
;
237 *alias_stream
= '\0'; // Make sure the new command is terminated.
242 case '%': // Some or all parameters
245 case '+': { // All parameters separated: "[param 1]" "[param 2]"
246 for (uint i
= 0; i
!= tokencount
; i
++) {
247 if (i
!= 0) alias_stream
= strecpy(alias_stream
, " ", lastof(alias_buffer
));
248 alias_stream
= strecpy(alias_stream
, "\"", lastof(alias_buffer
));
249 alias_stream
= strecpy(alias_stream
, tokens
[i
], lastof(alias_buffer
));
250 alias_stream
= strecpy(alias_stream
, "\"", lastof(alias_buffer
));
255 case '!': { // Merge the parameters to one: "[param 1] [param 2] [param 3...]"
256 alias_stream
= strecpy(alias_stream
, "\"", lastof(alias_buffer
));
257 for (uint i
= 0; i
!= tokencount
; i
++) {
258 if (i
!= 0) alias_stream
= strecpy(alias_stream
, " ", lastof(alias_buffer
));
259 alias_stream
= strecpy(alias_stream
, tokens
[i
], lastof(alias_buffer
));
261 alias_stream
= strecpy(alias_stream
, "\"", lastof(alias_buffer
));
265 default: { // One specific parameter: %A = [param 1] %B = [param 2] ...
266 int param
= *cmdptr
- 'A';
268 if (param
< 0 || param
>= tokencount
) {
269 IConsolePrint(CC_ERROR
, "Too many or wrong amount of parameters passed to alias.");
270 IConsolePrint(CC_HELP
, "Usage of alias '{}': '{}'.", alias
->name
, alias
->cmdline
);
274 alias_stream
= strecpy(alias_stream
, "\"", lastof(alias_buffer
));
275 alias_stream
= strecpy(alias_stream
, tokens
[param
], lastof(alias_buffer
));
276 alias_stream
= strecpy(alias_stream
, "\"", lastof(alias_buffer
));
283 *alias_stream
++ = *cmdptr
;
284 *alias_stream
= '\0';
288 if (alias_stream
>= lastof(alias_buffer
) - 1) {
289 IConsolePrint(CC_ERROR
, "Requested alias execution would overflow execution buffer.");
294 IConsoleCmdExec(alias_buffer
, recurse_count
);
298 * Execute a given command passed to us. First chop it up into
299 * individual tokens (separated by spaces), then execute it if possible
300 * @param cmdstr string to be parsed and executed
302 void IConsoleCmdExec(const char *cmdstr
, const uint recurse_count
)
305 char *tokens
[ICON_TOKEN_COUNT
], tokenstream
[ICON_MAX_STREAMSIZE
];
306 uint t_index
, tstream_i
;
308 bool longtoken
= false;
309 bool foundtoken
= false;
311 if (cmdstr
[0] == '#') return; // comments
313 for (cmdptr
= cmdstr
; *cmdptr
!= '\0'; cmdptr
++) {
314 if (!IsValidChar(*cmdptr
, CS_ALPHANUMERAL
)) {
315 IConsolePrint(CC_ERROR
, "Command '{}' contains malformed characters.", cmdstr
);
320 Debug(console
, 4, "Executing cmdline: '{}'", cmdstr
);
322 memset(&tokens
, 0, sizeof(tokens
));
323 memset(&tokenstream
, 0, sizeof(tokenstream
));
325 /* 1. Split up commandline into tokens, separated by spaces, commands
326 * enclosed in "" are taken as one token. We can only go as far as the amount
327 * of characters in our stream or the max amount of tokens we can handle */
328 for (cmdptr
= cmdstr
, t_index
= 0, tstream_i
= 0; *cmdptr
!= '\0'; cmdptr
++) {
329 if (tstream_i
>= lengthof(tokenstream
)) {
330 IConsolePrint(CC_ERROR
, "Command line too long.");
335 case ' ': // Token separator
336 if (!foundtoken
) break;
339 tokenstream
[tstream_i
] = *cmdptr
;
341 tokenstream
[tstream_i
] = '\0';
347 case '"': // Tokens enclosed in "" are one token
348 longtoken
= !longtoken
;
350 if (t_index
>= lengthof(tokens
)) {
351 IConsolePrint(CC_ERROR
, "Command line too long.");
354 tokens
[t_index
++] = &tokenstream
[tstream_i
];
358 case '\\': // Escape character for ""
359 if (cmdptr
[1] == '"' && tstream_i
+ 1 < lengthof(tokenstream
)) {
360 tokenstream
[tstream_i
++] = *++cmdptr
;
364 default: // Normal character
365 tokenstream
[tstream_i
++] = *cmdptr
;
368 if (t_index
>= lengthof(tokens
)) {
369 IConsolePrint(CC_ERROR
, "Command line too long.");
372 tokens
[t_index
++] = &tokenstream
[tstream_i
- 1];
379 for (uint i
= 0; i
< lengthof(tokens
) && tokens
[i
] != nullptr; i
++) {
380 Debug(console
, 8, "Token {} is: '{}'", i
, tokens
[i
]);
383 if (StrEmpty(tokens
[0])) return; // don't execute empty commands
384 /* 2. Determine type of command (cmd or alias) and execute
385 * First try commands, then aliases. Execute
386 * the found action taking into account its hooking code
388 IConsoleCmd
*cmd
= IConsole::CmdGet(tokens
[0]);
389 if (cmd
!= nullptr) {
390 ConsoleHookResult chr
= (cmd
->hook
== nullptr ? CHR_ALLOW
: cmd
->hook(true));
393 if (!cmd
->proc(t_index
, tokens
)) { // index started with 0
394 cmd
->proc(0, nullptr); // if command failed, give help
398 case CHR_DISALLOW
: return;
399 case CHR_HIDE
: break;
404 IConsoleAlias
*alias
= IConsole::AliasGet(tokens
[0]);
405 if (alias
!= nullptr) {
406 IConsoleAliasExec(alias
, t_index
, &tokens
[1], recurse_count
+ 1);
410 IConsolePrint(CC_ERROR
, "Command '{}' not found.", tokens
[0]);