OpenTTD Source 20241224-master-gee860a5c8e
console.cpp
Go to the documentation of this file.
1/*
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/>.
6 */
7
10#include "stdafx.h"
11#include "console_internal.h"
12#include "network/network.h"
15#include "debug.h"
16#include "console_func.h"
17#include "settings_type.h"
18
19#include "safeguards.h"
20
21static const uint ICON_TOKEN_COUNT = 20;
22static const uint ICON_MAX_RECURSE = 10;
23
24/* console parser */
25/* static */ IConsole::CommandList &IConsole::Commands()
26{
27 static IConsole::CommandList cmds;
28 return cmds;
29}
30
31/* static */ IConsole::AliasList &IConsole::Aliases()
32{
33 static IConsole::AliasList aliases;
34 return aliases;
35}
36
37std::optional<FileHandle> _iconsole_output_file;
38
39void IConsoleInit()
40{
41 _iconsole_output_file = std::nullopt;
44
45 IConsoleGUIInit();
46
47 IConsoleStdLibRegister();
48}
49
50static void IConsoleWriteToLogFile(const std::string &string)
51{
52 if (_iconsole_output_file.has_value()) {
53 /* if there is an console output file ... also print it there */
54 try {
55 fmt::print(*_iconsole_output_file, "{}{}\n", GetLogPrefix(), string);
56 } catch (const std::system_error &) {
57 _iconsole_output_file.reset();
58 IConsolePrint(CC_ERROR, "Cannot write to console log file; closing the log file.");
59 }
60 }
61}
62
63bool CloseConsoleLogIfActive()
64{
65 if (_iconsole_output_file.has_value()) {
66 IConsolePrint(CC_INFO, "Console log file closed.");
67 _iconsole_output_file.reset();
68 return true;
69 }
70
71 return false;
72}
73
74void IConsoleFree()
75{
76 IConsoleGUIFree();
77 CloseConsoleLogIfActive();
78}
79
89void IConsolePrint(TextColour colour_code, const std::string &string)
90{
91 assert(IsValidConsoleColour(colour_code));
92
94 /* Redirect the string to the client */
96 return;
97 }
98
101 return;
102 }
103
104 /* Create a copy of the string, strip it of colours and invalid
105 * characters and (when applicable) assign it to the console buffer */
106 std::string str = StrMakeValid(string, SVS_NONE);
107
108 if (_network_dedicated) {
109 NetworkAdminConsole("console", str);
110 fmt::print("{}{}\n", GetLogPrefix(), str);
111 fflush(stdout);
112 IConsoleWriteToLogFile(str);
113 return;
114 }
115
116 IConsoleWriteToLogFile(str);
117 IConsoleGUIPrint(colour_code, str);
118}
119
127bool GetArgumentInteger(uint32_t *value, const char *arg)
128{
129 char *endptr;
130
131 if (strcmp(arg, "on") == 0 || strcmp(arg, "true") == 0) {
132 *value = 1;
133 return true;
134 }
135 if (strcmp(arg, "off") == 0 || strcmp(arg, "false") == 0) {
136 *value = 0;
137 return true;
138 }
139
140 *value = std::strtoul(arg, &endptr, 0);
141 return arg != endptr;
142}
143
149static std::string RemoveUnderscores(std::string name)
150{
151 name.erase(std::remove(name.begin(), name.end(), '_'), name.end());
152 return name;
153}
154
160/* static */ void IConsole::CmdRegister(const std::string &name, IConsoleCmdProc *proc, IConsoleHook *hook)
161{
162 IConsole::Commands().try_emplace(RemoveUnderscores(name), name, proc, hook);
163}
164
170/* static */ IConsoleCmd *IConsole::CmdGet(const std::string &name)
171{
172 auto item = IConsole::Commands().find(RemoveUnderscores(name));
173 if (item != IConsole::Commands().end()) return &item->second;
174 return nullptr;
175}
176
182/* static */ void IConsole::AliasRegister(const std::string &name, const std::string &cmd)
183{
184 auto result = IConsole::Aliases().try_emplace(RemoveUnderscores(name), name, cmd);
185 if (!result.second) IConsolePrint(CC_ERROR, "An alias with the name '{}' already exists.", name);
186}
187
193/* static */ IConsoleAlias *IConsole::AliasGet(const std::string &name)
194{
195 auto item = IConsole::Aliases().find(RemoveUnderscores(name));
196 if (item != IConsole::Aliases().end()) return &item->second;
197 return nullptr;
198}
199
207static void IConsoleAliasExec(const IConsoleAlias *alias, uint8_t tokencount, char *tokens[ICON_TOKEN_COUNT], const uint recurse_count)
208{
209 std::string alias_buffer;
210
211 Debug(console, 6, "Requested command is an alias; parsing...");
212
213 if (recurse_count > ICON_MAX_RECURSE) {
214 IConsolePrint(CC_ERROR, "Too many alias expansions, recursion limit reached.");
215 return;
216 }
217
218 for (const char *cmdptr = alias->cmdline.c_str(); *cmdptr != '\0'; cmdptr++) {
219 switch (*cmdptr) {
220 case '\'': // ' will double for ""
221 alias_buffer += '\"';
222 break;
223
224 case ';': // Cmd separator; execute previous and start new command
225 IConsoleCmdExec(alias_buffer, recurse_count);
226
227 alias_buffer.clear();
228
229 cmdptr++;
230 break;
231
232 case '%': // Some or all parameters
233 cmdptr++;
234 switch (*cmdptr) {
235 case '+': { // All parameters separated: "[param 1]" "[param 2]"
236 for (uint i = 0; i != tokencount; i++) {
237 if (i != 0) alias_buffer += ' ';
238 alias_buffer += '\"';
239 alias_buffer += tokens[i];
240 alias_buffer += '\"';
241 }
242 break;
243 }
244
245 case '!': { // Merge the parameters to one: "[param 1] [param 2] [param 3...]"
246 alias_buffer += '\"';
247 for (uint i = 0; i != tokencount; i++) {
248 if (i != 0) alias_buffer += " ";
249 alias_buffer += tokens[i];
250 }
251 alias_buffer += '\"';
252 break;
253 }
254
255 default: { // One specific parameter: %A = [param 1] %B = [param 2] ...
256 int param = *cmdptr - 'A';
257
258 if (param < 0 || param >= tokencount) {
259 IConsolePrint(CC_ERROR, "Too many or wrong amount of parameters passed to alias.");
260 IConsolePrint(CC_HELP, "Usage of alias '{}': '{}'.", alias->name, alias->cmdline);
261 return;
262 }
263
264 alias_buffer += '\"';
265 alias_buffer += tokens[param];
266 alias_buffer += '\"';
267 break;
268 }
269 }
270 break;
271
272 default:
273 alias_buffer += *cmdptr;
274 break;
275 }
276
277 if (alias_buffer.size() >= ICON_MAX_STREAMSIZE - 1) {
278 IConsolePrint(CC_ERROR, "Requested alias execution would overflow execution buffer.");
279 return;
280 }
281 }
282
283 IConsoleCmdExec(alias_buffer, recurse_count);
284}
285
291void IConsoleCmdExec(const std::string &command_string, const uint recurse_count)
292{
293 const char *cmdptr;
294 char *tokens[ICON_TOKEN_COUNT], tokenstream[ICON_MAX_STREAMSIZE];
295 uint t_index, tstream_i;
296
297 bool longtoken = false;
298 bool foundtoken = false;
299
300 if (command_string[0] == '#') return; // comments
301
302 for (cmdptr = command_string.c_str(); *cmdptr != '\0'; cmdptr++) {
303 if (!IsValidChar(*cmdptr, CS_ALPHANUMERAL)) {
304 IConsolePrint(CC_ERROR, "Command '{}' contains malformed characters.", command_string);
305 return;
306 }
307 }
308
309 Debug(console, 4, "Executing cmdline: '{}'", command_string);
310
311 memset(&tokens, 0, sizeof(tokens));
312 memset(&tokenstream, 0, sizeof(tokenstream));
313
314 /* 1. Split up commandline into tokens, separated by spaces, commands
315 * enclosed in "" are taken as one token. We can only go as far as the amount
316 * of characters in our stream or the max amount of tokens we can handle */
317 for (cmdptr = command_string.c_str(), t_index = 0, tstream_i = 0; *cmdptr != '\0'; cmdptr++) {
318 if (tstream_i >= lengthof(tokenstream)) {
319 IConsolePrint(CC_ERROR, "Command line too long.");
320 return;
321 }
322
323 switch (*cmdptr) {
324 case ' ': // Token separator
325 if (!foundtoken) break;
326
327 if (longtoken) {
328 tokenstream[tstream_i] = *cmdptr;
329 } else {
330 tokenstream[tstream_i] = '\0';
331 foundtoken = false;
332 }
333
334 tstream_i++;
335 break;
336 case '"': // Tokens enclosed in "" are one token
337 longtoken = !longtoken;
338 if (!foundtoken) {
339 if (t_index >= lengthof(tokens)) {
340 IConsolePrint(CC_ERROR, "Command line too long.");
341 return;
342 }
343 tokens[t_index++] = &tokenstream[tstream_i];
344 foundtoken = true;
345 }
346 break;
347 case '\\': // Escape character for ""
348 if (cmdptr[1] == '"' && tstream_i + 1 < lengthof(tokenstream)) {
349 tokenstream[tstream_i++] = *++cmdptr;
350 break;
351 }
352 [[fallthrough]];
353 default: // Normal character
354 tokenstream[tstream_i++] = *cmdptr;
355
356 if (!foundtoken) {
357 if (t_index >= lengthof(tokens)) {
358 IConsolePrint(CC_ERROR, "Command line too long.");
359 return;
360 }
361 tokens[t_index++] = &tokenstream[tstream_i - 1];
362 foundtoken = true;
363 }
364 break;
365 }
366 }
367
368 for (uint i = 0; i < lengthof(tokens) && tokens[i] != nullptr; i++) {
369 Debug(console, 8, "Token {} is: '{}'", i, tokens[i]);
370 }
371
372 if (StrEmpty(tokens[0])) return; // don't execute empty commands
373 /* 2. Determine type of command (cmd or alias) and execute
374 * First try commands, then aliases. Execute
375 * the found action taking into account its hooking code
376 */
377 IConsoleCmd *cmd = IConsole::CmdGet(tokens[0]);
378 if (cmd != nullptr) {
379 ConsoleHookResult chr = (cmd->hook == nullptr ? CHR_ALLOW : cmd->hook(true));
380 switch (chr) {
381 case CHR_ALLOW:
382 if (!cmd->proc(t_index, tokens)) { // index started with 0
383 cmd->proc(0, nullptr); // if command failed, give help
384 }
385 return;
386
387 case CHR_DISALLOW: return;
388 case CHR_HIDE: break;
389 }
390 }
391
392 t_index--;
393 IConsoleAlias *alias = IConsole::AliasGet(tokens[0]);
394 if (alias != nullptr) {
395 IConsoleAliasExec(alias, t_index, &tokens[1], recurse_count + 1);
396 return;
397 }
398
399 IConsolePrint(CC_ERROR, "Command '{}' not found.", tokens[0]);
400}
bool GetArgumentInteger(uint32_t *value, const char *arg)
Change a string into its number representation.
Definition console.cpp:127
static std::string RemoveUnderscores(std::string name)
Creates a copy of a string with underscores removed from it.
Definition console.cpp:149
void IConsoleCmdExec(const std::string &command_string, const uint recurse_count)
Execute a given command passed to us.
Definition console.cpp:291
static const uint ICON_TOKEN_COUNT
Maximum number of tokens in one command.
Definition console.cpp:21
static void IConsoleAliasExec(const IConsoleAlias *alias, uint8_t tokencount, char *tokens[ICON_TOKEN_COUNT], const uint recurse_count)
An alias is just another name for a command, or for more commands Execute it as well.
Definition console.cpp:207
void IConsolePrint(TextColour colour_code, const std::string &string)
Handle the printing of text entered into the console or redirected there by any other means.
Definition console.cpp:89
static const uint ICON_MAX_RECURSE
Maximum number of recursion.
Definition console.cpp:22
Console functions used outside of the console code.
bool IsValidConsoleColour(TextColour c)
Check whether the given TextColour is valid for console usage.
void IConsoleGUIPrint(TextColour colour_code, const std::string &str)
Handle the printing of text entered into the console or redirected there by any other means.
Internally used functions for the console.
static const uint ICON_MAX_STREAMSIZE
maximum length of a totally expanded command
ConsoleHookResult
Return values of console hooks (IConsoleHook).
@ CHR_HIDE
Hide the existence of the command.
@ CHR_DISALLOW
Disallow command execution.
@ CHR_ALLOW
Allow command execution.
bool IConsoleCmdProc(uint8_t argc, char *argv[])
–Commands– Commands are commands, or functions.
static const TextColour CC_HELP
Colour for help lines.
static const TextColour CC_INFO
Colour for information lines.
static const TextColour CC_ERROR
Colour for error lines.
std::string GetLogPrefix(bool force)
Get the prefix for logs.
Definition debug.cpp:224
Functions related to debugging.
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
Definition debug.h:37
TextColour
Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palett...
Definition gfx_type.h:260
ClientID _redirect_console_to_client
If not invalid, redirect the console output to a client.
Definition network.cpp:71
bool _network_dedicated
are we a dedicated server?
Definition network.cpp:68
Basic functions/variables used all over the place.
AdminIndex _redirect_console_to_admin
Redirection of the (remote) console to the admin.
void NetworkAdminConsole(const std::string_view origin, const std::string_view string)
Send console to the admin network (if they did opt in for the respective update).
void NetworkServerSendAdminRcon(AdminIndex admin_index, TextColour colour_code, const std::string_view string)
Pass the rcon reply to the admin.
Server part of the admin network protocol.
Network functions used by other parts of OpenTTD.
void NetworkServerSendRcon(ClientID client_id, TextColour colour_code, const std::string &string)
Send an rcon reply to the client.
static const AdminIndex INVALID_ADMIN_ID
An invalid admin marker.
@ INVALID_CLIENT_ID
Client is not part of anything.
A number of safeguards to prevent using unsafe methods.
Types related to global configuration settings.
Definition of base types and functions in a cross-platform compatible way.
#define lengthof(array)
Return the length of an fixed size array.
Definition stdafx.h:280
static void StrMakeValid(T &dst, const char *str, const char *last, StringValidationSettings settings)
Copies the valid (UTF-8) characters from str up to last to the dst.
Definition string.cpp:107
bool IsValidChar(char32_t key, CharSetFilter afilter)
Only allow certain keys.
Definition string.cpp:396
bool StrEmpty(const char *s)
Check if a string buffer is empty.
Definition string_func.h:57
@ CS_ALPHANUMERAL
Both numeric and alphabetic and spaces and stuff.
Definition string_type.h:25
@ SVS_NONE
Allow nothing and replace nothing.
Definition string_type.h:45
–Aliases– Aliases are like shortcuts for complex functions, variable assignments, etc.
std::string cmdline
command(s) that is/are being aliased
std::string name
name of the alias
IConsoleCmdProc * proc
process executed when command is typed
IConsoleHook * hook
any special trigger action that needs executing
static IConsoleAlias * AliasGet(const std::string &name)
Find the alias pointed to by its string.
Definition console.cpp:193
static void CmdRegister(const std::string &name, IConsoleCmdProc *proc, IConsoleHook *hook=nullptr)
Register a new command to be used in the console.
Definition console.cpp:160
static IConsoleCmd * CmdGet(const std::string &name)
Find the command pointed to by its string.
Definition console.cpp:170
static void AliasRegister(const std::string &name, const std::string &cmd)
Register a an alias for an already existing command in the console.
Definition console.cpp:182