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"
19 #include "safeguards.h"
21 static const uint ICON_TOKEN_COUNT
= 20; ///< Maximum number of tokens in one command
22 static const uint ICON_MAX_RECURSE
= 10; ///< Maximum number of recursion
25 /* static */ IConsole::CommandList
&IConsole::Commands()
27 static IConsole::CommandList cmds
;
31 /* static */ IConsole::AliasList
&IConsole::Aliases()
33 static IConsole::AliasList aliases
;
37 FILE *_iconsole_output_file
;
41 _iconsole_output_file
= nullptr;
42 _redirect_console_to_client
= INVALID_CLIENT_ID
;
43 _redirect_console_to_admin
= INVALID_ADMIN_ID
;
47 IConsoleStdLibRegister();
50 static void IConsoleWriteToLogFile(const std::string
&string
)
52 if (_iconsole_output_file
!= nullptr) {
53 /* if there is an console output file ... also print it there */
55 fmt::print(_iconsole_output_file
, "{}{}\n", GetLogPrefix(), string
);
56 } catch (const std::system_error
&) {
57 fclose(_iconsole_output_file
);
58 _iconsole_output_file
= nullptr;
59 IConsolePrint(CC_ERROR
, "Cannot write to console log file; closing the log file.");
64 bool CloseConsoleLogIfActive()
66 if (_iconsole_output_file
!= nullptr) {
67 IConsolePrint(CC_INFO
, "Console log file closed.");
68 fclose(_iconsole_output_file
);
69 _iconsole_output_file
= nullptr;
79 CloseConsoleLogIfActive();
83 * Handle the printing of text entered into the console or redirected there
84 * by any other means. Text can be redirected to other clients in a network game
85 * as well as to a logfile. If the network server is a dedicated server, all activities
86 * are also logged. All lines to print are added to a temporary buffer which can be
87 * used as a history to print them onscreen
88 * @param colour_code The colour of the command.
89 * @param string The message to output on the console (notice, error, etc.)
91 void IConsolePrint(TextColour colour_code
, const std::string
&string
)
93 assert(IsValidConsoleColour(colour_code
));
95 if (_redirect_console_to_client
!= INVALID_CLIENT_ID
) {
96 /* Redirect the string to the client */
97 NetworkServerSendRcon(_redirect_console_to_client
, colour_code
, string
);
101 if (_redirect_console_to_admin
!= INVALID_ADMIN_ID
) {
102 NetworkServerSendAdminRcon(_redirect_console_to_admin
, colour_code
, string
);
106 /* Create a copy of the string, strip it of colours and invalid
107 * characters and (when applicable) assign it to the console buffer */
108 std::string str
= StrMakeValid(string
, SVS_NONE
);
110 if (_network_dedicated
) {
111 NetworkAdminConsole("console", str
);
112 fmt::print("{}{}\n", GetLogPrefix(), str
);
114 IConsoleWriteToLogFile(str
);
118 IConsoleWriteToLogFile(str
);
119 IConsoleGUIPrint(colour_code
, str
);
123 * Change a string into its number representation. Supports
124 * decimal and hexadecimal numbers as well as 'on'/'off' 'true'/'false'
125 * @param *value the variable a successful conversion will be put in
126 * @param *arg the string to be converted
127 * @return Return true on success or false on failure
129 bool GetArgumentInteger(uint32_t *value
, const char *arg
)
133 if (strcmp(arg
, "on") == 0 || strcmp(arg
, "true") == 0) {
137 if (strcmp(arg
, "off") == 0 || strcmp(arg
, "false") == 0) {
142 *value
= std::strtoul(arg
, &endptr
, 0);
143 return arg
!= endptr
;
147 * Creates a copy of a string with underscores removed from it
148 * @param name String to remove the underscores from.
149 * @return A copy of \a name, without underscores.
151 static std::string
RemoveUnderscores(std::string name
)
153 name
.erase(std::remove(name
.begin(), name
.end(), '_'), name
.end());
158 * Register a new command to be used in the console
159 * @param name name of the command that will be used
160 * @param proc function that will be called upon execution of command
162 /* static */ void IConsole::CmdRegister(const std::string
&name
, IConsoleCmdProc
*proc
, IConsoleHook
*hook
)
164 IConsole::Commands().try_emplace(RemoveUnderscores(name
), name
, proc
, hook
);
168 * Find the command pointed to by its string
169 * @param name command to be found
170 * @return return Cmdstruct of the found command, or nullptr on failure
172 /* static */ IConsoleCmd
*IConsole::CmdGet(const std::string
&name
)
174 auto item
= IConsole::Commands().find(RemoveUnderscores(name
));
175 if (item
!= IConsole::Commands().end()) return &item
->second
;
180 * Register a an alias for an already existing command in the console
181 * @param name name of the alias that will be used
182 * @param cmd name of the command that 'name' will be alias of
184 /* static */ void IConsole::AliasRegister(const std::string
&name
, const std::string
&cmd
)
186 auto result
= IConsole::Aliases().try_emplace(RemoveUnderscores(name
), name
, cmd
);
187 if (!result
.second
) IConsolePrint(CC_ERROR
, "An alias with the name '{}' already exists.", name
);
191 * Find the alias pointed to by its string
192 * @param name alias to be found
193 * @return return Aliasstruct of the found alias, or nullptr on failure
195 /* static */ IConsoleAlias
*IConsole::AliasGet(const std::string
&name
)
197 auto item
= IConsole::Aliases().find(RemoveUnderscores(name
));
198 if (item
!= IConsole::Aliases().end()) return &item
->second
;
203 * An alias is just another name for a command, or for more commands
204 * Execute it as well.
205 * @param *alias is the alias of the command
206 * @param tokencount the number of parameters passed
207 * @param *tokens are the parameters given to the original command (0 is the first param)
209 static void IConsoleAliasExec(const IConsoleAlias
*alias
, byte tokencount
, char *tokens
[ICON_TOKEN_COUNT
], const uint recurse_count
)
211 std::string alias_buffer
;
213 Debug(console
, 6, "Requested command is an alias; parsing...");
215 if (recurse_count
> ICON_MAX_RECURSE
) {
216 IConsolePrint(CC_ERROR
, "Too many alias expansions, recursion limit reached.");
220 for (const char *cmdptr
= alias
->cmdline
.c_str(); *cmdptr
!= '\0'; cmdptr
++) {
222 case '\'': // ' will double for ""
223 alias_buffer
+= '\"';
226 case ';': // Cmd separator; execute previous and start new command
227 IConsoleCmdExec(alias_buffer
, recurse_count
);
229 alias_buffer
.clear();
234 case '%': // Some or all parameters
237 case '+': { // All parameters separated: "[param 1]" "[param 2]"
238 for (uint i
= 0; i
!= tokencount
; i
++) {
239 if (i
!= 0) alias_buffer
+= ' ';
240 alias_buffer
+= '\"';
241 alias_buffer
+= tokens
[i
];
242 alias_buffer
+= '\"';
247 case '!': { // Merge the parameters to one: "[param 1] [param 2] [param 3...]"
248 alias_buffer
+= '\"';
249 for (uint i
= 0; i
!= tokencount
; i
++) {
250 if (i
!= 0) alias_buffer
+= " ";
251 alias_buffer
+= tokens
[i
];
253 alias_buffer
+= '\"';
257 default: { // One specific parameter: %A = [param 1] %B = [param 2] ...
258 int param
= *cmdptr
- 'A';
260 if (param
< 0 || param
>= tokencount
) {
261 IConsolePrint(CC_ERROR
, "Too many or wrong amount of parameters passed to alias.");
262 IConsolePrint(CC_HELP
, "Usage of alias '{}': '{}'.", alias
->name
, alias
->cmdline
);
266 alias_buffer
+= '\"';
267 alias_buffer
+= tokens
[param
];
268 alias_buffer
+= '\"';
275 alias_buffer
+= *cmdptr
;
279 if (alias_buffer
.size() >= ICON_MAX_STREAMSIZE
- 1) {
280 IConsolePrint(CC_ERROR
, "Requested alias execution would overflow execution buffer.");
285 IConsoleCmdExec(alias_buffer
, recurse_count
);
289 * Execute a given command passed to us. First chop it up into
290 * individual tokens (separated by spaces), then execute it if possible
291 * @param command_string string to be parsed and executed
293 void IConsoleCmdExec(const std::string
&command_string
, const uint recurse_count
)
296 char *tokens
[ICON_TOKEN_COUNT
], tokenstream
[ICON_MAX_STREAMSIZE
];
297 uint t_index
, tstream_i
;
299 bool longtoken
= false;
300 bool foundtoken
= false;
302 if (command_string
[0] == '#') return; // comments
304 for (cmdptr
= command_string
.c_str(); *cmdptr
!= '\0'; cmdptr
++) {
305 if (!IsValidChar(*cmdptr
, CS_ALPHANUMERAL
)) {
306 IConsolePrint(CC_ERROR
, "Command '{}' contains malformed characters.", command_string
);
311 Debug(console
, 4, "Executing cmdline: '{}'", command_string
);
313 memset(&tokens
, 0, sizeof(tokens
));
314 memset(&tokenstream
, 0, sizeof(tokenstream
));
316 /* 1. Split up commandline into tokens, separated by spaces, commands
317 * enclosed in "" are taken as one token. We can only go as far as the amount
318 * of characters in our stream or the max amount of tokens we can handle */
319 for (cmdptr
= command_string
.c_str(), t_index
= 0, tstream_i
= 0; *cmdptr
!= '\0'; cmdptr
++) {
320 if (tstream_i
>= lengthof(tokenstream
)) {
321 IConsolePrint(CC_ERROR
, "Command line too long.");
326 case ' ': // Token separator
327 if (!foundtoken
) break;
330 tokenstream
[tstream_i
] = *cmdptr
;
332 tokenstream
[tstream_i
] = '\0';
338 case '"': // Tokens enclosed in "" are one token
339 longtoken
= !longtoken
;
341 if (t_index
>= lengthof(tokens
)) {
342 IConsolePrint(CC_ERROR
, "Command line too long.");
345 tokens
[t_index
++] = &tokenstream
[tstream_i
];
349 case '\\': // Escape character for ""
350 if (cmdptr
[1] == '"' && tstream_i
+ 1 < lengthof(tokenstream
)) {
351 tokenstream
[tstream_i
++] = *++cmdptr
;
355 default: // Normal character
356 tokenstream
[tstream_i
++] = *cmdptr
;
359 if (t_index
>= lengthof(tokens
)) {
360 IConsolePrint(CC_ERROR
, "Command line too long.");
363 tokens
[t_index
++] = &tokenstream
[tstream_i
- 1];
370 for (uint i
= 0; i
< lengthof(tokens
) && tokens
[i
] != nullptr; i
++) {
371 Debug(console
, 8, "Token {} is: '{}'", i
, tokens
[i
]);
374 if (StrEmpty(tokens
[0])) return; // don't execute empty commands
375 /* 2. Determine type of command (cmd or alias) and execute
376 * First try commands, then aliases. Execute
377 * the found action taking into account its hooking code
379 IConsoleCmd
*cmd
= IConsole::CmdGet(tokens
[0]);
380 if (cmd
!= nullptr) {
381 ConsoleHookResult chr
= (cmd
->hook
== nullptr ? CHR_ALLOW
: cmd
->hook(true));
384 if (!cmd
->proc(t_index
, tokens
)) { // index started with 0
385 cmd
->proc(0, nullptr); // if command failed, give help
389 case CHR_DISALLOW
: return;
390 case CHR_HIDE
: break;
395 IConsoleAlias
*alias
= IConsole::AliasGet(tokens
[0]);
396 if (alias
!= nullptr) {
397 IConsoleAliasExec(alias
, t_index
, &tokens
[1], recurse_count
+ 1);
401 IConsolePrint(CC_ERROR
, "Command '{}' not found.", tokens
[0]);