OpenTTD Source: src/console.cpp Source File

OpenTTD Source 13.2.1

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"
13 #include "network/network_func.h"
14 #include "network/network_admin.h"
15 #include "debug.h"
16 #include "console_func.h"
17 #include "settings_type.h"
18
19 #include <stdarg.h>
20
21 #include "safeguards.h"
22
23 static const uint ICON_TOKEN_COUNT = 20;
24 static const uint ICON_MAX_RECURSE = 10;
25
26 /* console parser */
27 /* static */ IConsole::CommandList &IConsole::Commands()
28 {
29 static IConsole::CommandList cmds;
30 return cmds;
31 }
32
33 /* static */ IConsole::AliasList &IConsole::Aliases()
34 {
35 static IConsole::AliasList aliases;
36 return aliases;
37 }
38
39 FILE *_iconsole_output_file;
40
41 void IConsoleInit()
42 {
43 _iconsole_output_file = nullptr;
44 _redirect_console_to_client = INVALID_CLIENT_ID;
45 _redirect_console_to_admin = INVALID_ADMIN_ID;
46
47 IConsoleGUIInit();
48
49 IConsoleStdLibRegister();
50 }
51
52 static void IConsoleWriteToLogFile(const char *string)
53 {
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.");
63 }
64 }
65 }
66
67 bool CloseConsoleLogIfActive()
68 {
69 if (_iconsole_output_file != nullptr) {
70 IConsolePrint(CC_INFO, "Console log file closed.");
71 fclose(_iconsole_output_file);
72 _iconsole_output_file = nullptr;
73 return true;
74 }
75
76 return false;
77 }
78
79 void IConsoleFree()
80 {
81 IConsoleGUIFree();
82 CloseConsoleLogIfActive();
83 }
84
94 void IConsolePrint(TextColour colour_code, const std::string &string)
95 {
96 assert(IsValidConsoleColour(colour_code));
97
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);
101 return;
102 }
103
104 if (_redirect_console_to_admin != INVALID_ADMIN_ID) {
105 NetworkServerSendAdminRcon(_redirect_console_to_admin, colour_code, string);
106 return;
107 }
108
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);
114
115 if (_network_dedicated) {
116 NetworkAdminConsole("console", str);
117 fprintf(stdout, "%s%s\n", GetLogPrefix(), str);
118 fflush(stdout);
119 IConsoleWriteToLogFile(str);
120 free(str); // free duplicated string since it's not used anymore
121 return;
122 }
123
124 IConsoleWriteToLogFile(str);
125 IConsoleGUIPrint(colour_code, str);
126 free(str);
127 }
128
136 bool GetArgumentInteger(uint32 *value, const char *arg)
137 {
138 char *endptr;
139
140 if (strcmp(arg, "on") == 0 || strcmp(arg, "true") == 0) {
141 *value = 1;
142 return true;
143 }
144 if (strcmp(arg, "off") == 0 || strcmp(arg, "false") == 0) {
145 *value = 0;
146 return true;
147 }
148
149 *value = strtoul(arg, &endptr, 0);
150 return arg != endptr;
151 }
152
158 static std::string RemoveUnderscores(std::string name)
159 {
160 name.erase(std::remove(name.begin(), name.end(), '_'), name.end());
161 return name;
162 }
163
169 /* static */ void IConsole::CmdRegister(const std::string &name, IConsoleCmdProc *proc, IConsoleHook *hook)
170 {
171 IConsole::Commands().try_emplace(RemoveUnderscores(name), name, proc, hook);
172 }
173
179 /* static */ IConsoleCmd *IConsole::CmdGet(const std::string &name)
180 {
181 auto item = IConsole::Commands().find(RemoveUnderscores(name));
182 if (item != IConsole::Commands().end()) return &item->second;
183 return nullptr;
184 }
185
191 /* static */ void IConsole::AliasRegister(const std::string &name, const std::string &cmd)
192 {
193 auto result = IConsole::Aliases().try_emplace(RemoveUnderscores(name), name, cmd);
194 if (!result.second) IConsolePrint(CC_ERROR, "An alias with the name '{}' already exists.", name);
195 }
196
202 /* static */ IConsoleAlias *IConsole::AliasGet(const std::string &name)
203 {
204 auto item = IConsole::Aliases().find(RemoveUnderscores(name));
205 if (item != IConsole::Aliases().end()) return &item->second;
206 return nullptr;
207 }
208
216 static void IConsoleAliasExec(const IConsoleAlias *alias, byte tokencount, char *tokens[ICON_TOKEN_COUNT], const uint recurse_count)
217 {
218 char alias_buffer[ICON_MAX_STREAMSIZE] = { '\0' };
219 char *alias_stream = alias_buffer;
220
221 Debug(console, 6, "Requested command is an alias; parsing...");
222
223 if (recurse_count > ICON_MAX_RECURSE) {
224 IConsolePrint(CC_ERROR, "Too many alias expansions, recursion limit reached.");
225 return;
226 }
227
228 for (const char *cmdptr = alias->cmdline.c_str(); *cmdptr != '\0'; cmdptr++) {
229 switch (*cmdptr) {
230 case '\'': // ' will double for ""
231 alias_stream = strecpy(alias_stream, "\"", lastof(alias_buffer));
232 break;
233
234 case ';': // Cmd separator; execute previous and start new command
235 IConsoleCmdExec(alias_buffer, recurse_count);
236
237 alias_stream = alias_buffer;
238 *alias_stream = '\0'; // Make sure the new command is terminated.
239
240 cmdptr++;
241 break;
242
243 case '%': // Some or all parameters
244 cmdptr++;
245 switch (*cmdptr) {
246 case '+': { // All parameters separated: "[param 1]" "[param 2]"
247 for (uint i = 0; i != tokencount; i++) {
248 if (i != 0) alias_stream = strecpy(alias_stream, " ", lastof(alias_buffer));
249 alias_stream = strecpy(alias_stream, "\"", lastof(alias_buffer));
250 alias_stream = strecpy(alias_stream, tokens[i], lastof(alias_buffer));
251 alias_stream = strecpy(alias_stream, "\"", lastof(alias_buffer));
252 }
253 break;
254 }
255
256 case '!': { // Merge the parameters to one: "[param 1] [param 2] [param 3...]"
257 alias_stream = strecpy(alias_stream, "\"", lastof(alias_buffer));
258 for (uint i = 0; i != tokencount; i++) {
259 if (i != 0) alias_stream = strecpy(alias_stream, " ", lastof(alias_buffer));
260 alias_stream = strecpy(alias_stream, tokens[i], lastof(alias_buffer));
261 }
262 alias_stream = strecpy(alias_stream, "\"", lastof(alias_buffer));
263 break;
264 }
265
266 default: { // One specific parameter: %A = [param 1] %B = [param 2] ...
267 int param = *cmdptr - 'A';
268
269 if (param < 0 || param >= tokencount) {
270 IConsolePrint(CC_ERROR, "Too many or wrong amount of parameters passed to alias.");
271 IConsolePrint(CC_HELP, "Usage of alias '{}': '{}'.", alias->name, alias->cmdline);
272 return;
273 }
274
275 alias_stream = strecpy(alias_stream, "\"", lastof(alias_buffer));
276 alias_stream = strecpy(alias_stream, tokens[param], lastof(alias_buffer));
277 alias_stream = strecpy(alias_stream, "\"", lastof(alias_buffer));
278 break;
279 }
280 }
281 break;
282
283 default:
284 *alias_stream++ = *cmdptr;
285 *alias_stream = '\0';
286 break;
287 }
288
289 if (alias_stream >= lastof(alias_buffer) - 1) {
290 IConsolePrint(CC_ERROR, "Requested alias execution would overflow execution buffer.");
291 return;
292 }
293 }
294
295 IConsoleCmdExec(alias_buffer, recurse_count);
296 }
297
303 void IConsoleCmdExec(const char *cmdstr, const uint recurse_count)
304 {
305 const char *cmdptr;
306 char *tokens[ICON_TOKEN_COUNT], tokenstream[ICON_MAX_STREAMSIZE];
307 uint t_index, tstream_i;
308
309 bool longtoken = false;
310 bool foundtoken = false;
311
312 if (cmdstr[0] == '#') return; // comments
313
314 for (cmdptr = cmdstr; *cmdptr != '\0'; cmdptr++) {
315 if (!IsValidChar(*cmdptr, CS_ALPHANUMERAL)) {
316 IConsolePrint(CC_ERROR, "Command '{}' contains malformed characters.", cmdstr);
317 return;
318 }
319 }
320
321 Debug(console, 4, "Executing cmdline: '{}'", cmdstr);
322
323 memset(&tokens, 0, sizeof(tokens));
324 memset(&tokenstream, 0, sizeof(tokenstream));
325
326 /* 1. Split up commandline into tokens, separated by spaces, commands
327 * enclosed in "" are taken as one token. We can only go as far as the amount
328 * of characters in our stream or the max amount of tokens we can handle */
329 for (cmdptr = cmdstr, t_index = 0, tstream_i = 0; *cmdptr != '\0'; cmdptr++) {
330 if (tstream_i >= lengthof(tokenstream)) {
331 IConsolePrint(CC_ERROR, "Command line too long.");
332 return;
333 }
334
335 switch (*cmdptr) {
336 case ' ': // Token separator
337 if (!foundtoken) break;
338
339 if (longtoken) {
340 tokenstream[tstream_i] = *cmdptr;
341 } else {
342 tokenstream[tstream_i] = '\0';
343 foundtoken = false;
344 }
345
346 tstream_i++;
347 break;
348 case '"': // Tokens enclosed in "" are one token
349 longtoken = !longtoken;
350 if (!foundtoken) {
351 if (t_index >= lengthof(tokens)) {
352 IConsolePrint(CC_ERROR, "Command line too long.");
353 return;
354 }
355 tokens[t_index++] = &tokenstream[tstream_i];
356 foundtoken = true;
357 }
358 break;
359 case '\\': // Escape character for ""
360 if (cmdptr[1] == '"' && tstream_i + 1 < lengthof(tokenstream)) {
361 tokenstream[tstream_i++] = *++cmdptr;
362 break;
363 }
364 FALLTHROUGH;
365 default: // Normal character
366 tokenstream[tstream_i++] = *cmdptr;
367
368 if (!foundtoken) {
369 if (t_index >= lengthof(tokens)) {
370 IConsolePrint(CC_ERROR, "Command line too long.");
371 return;
372 }
373 tokens[t_index++] = &tokenstream[tstream_i - 1];
374 foundtoken = true;
375 }
376 break;
377 }
378 }
379
380 for (uint i = 0; i < lengthof(tokens) && tokens[i] != nullptr; i++) {
381 Debug(console, 8, "Token {} is: '{}'", i, tokens[i]);
382 }
383
384 if (StrEmpty(tokens[0])) return; // don't execute empty commands
385 /* 2. Determine type of command (cmd or alias) and execute
386 * First try commands, then aliases. Execute
387 * the found action taking into account its hooking code
388 */
389 IConsoleCmd *cmd = IConsole::CmdGet(tokens[0]);
390 if (cmd != nullptr) {
391 ConsoleHookResult chr = (cmd->hook == nullptr ? CHR_ALLOW : cmd->hook(true));
392 switch (chr) {
393 case CHR_ALLOW:
394 if (!cmd->proc(t_index, tokens)) { // index started with 0
395 cmd->proc(0, nullptr); // if command failed, give help
396 }
397 return;
398
399 case CHR_DISALLOW: return;
400 case CHR_HIDE: break;
401 }
402 }
403
404 t_index--;
405 IConsoleAlias *alias = IConsole::AliasGet(tokens[0]);
406 if (alias != nullptr) {
407 IConsoleAliasExec(alias, t_index, &tokens[1], recurse_count + 1);
408 return;
409 }
410
411 IConsolePrint(CC_ERROR, "Command '{}' not found.", tokens[0]);
412 }
CC_INFO
static const TextColour CC_INFO
Colour for information lines.
Definition: console_type.h:27
IConsoleCmd::proc
IConsoleCmdProc * proc
process executed when command is typed
Definition: console_internal.h:40
INVALID_CLIENT_ID
@ INVALID_CLIENT_ID
Client is not part of anything.
Definition: network_type.h:48
IConsole::AliasGet
static IConsoleAlias * AliasGet(const std::string &name)
Find the alias pointed to by its string.
Definition: console.cpp:202
TextColour
TextColour
Colour of the strings, see _string_colourmap in table/string_colours.h or docs/ottd-colourtext-palett...
Definition: gfx_type.h:253
CompanyProperties::name
std::string name
Name of the company if the user changed it.
Definition: company_base.h:59
_redirect_console_to_client
ClientID _redirect_console_to_client
If not invalid, redirect the console output to a client.
Definition: network.cpp:65
IConsoleGUIPrint
void IConsoleGUIPrint(TextColour colour_code, char *str)
Handle the printing of text entered into the console or redirected there by any other means.
Definition: console_gui.cpp:470
IConsoleAlias::cmdline
std::string cmdline
command(s) that is/are being aliased
Definition: console_internal.h:60
INVALID_ADMIN_ID
static const AdminIndex INVALID_ADMIN_ID
An invalid admin marker.
Definition: network_type.h:62
_redirect_console_to_admin
AdminIndex _redirect_console_to_admin
Redirection of the (remote) console to the admin.
Definition: network_admin.cpp:31
IConsoleCmd::hook
IConsoleHook * hook
any special trigger action that needs executing
Definition: console_internal.h:41
NetworkAdminConsole
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).
Definition: network_admin.cpp:950
ICON_TOKEN_COUNT
static const uint ICON_TOKEN_COUNT
Maximum number of tokens in one command.
Definition: console.cpp:23
IConsole::CmdRegister
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:169
ICON_MAX_STREAMSIZE
static const uint ICON_MAX_STREAMSIZE
maximum length of a totally expanded command
Definition: console_internal.h:17
GetLogPrefix
const char * GetLogPrefix()
Get the prefix for logs; if show_date_in_logs is enabled it returns the date, otherwise it returns no...
Definition: debug.cpp:252
CC_HELP
static const TextColour CC_HELP
Colour for help lines.
Definition: console_type.h:26
StrMakeValidInPlace
void StrMakeValidInPlace(char *str, const char *last, StringValidationSettings settings)
Scans the string for invalid characters and replaces then with a question mark '?' (if not ignored).
Definition: string.cpp:273
console_internal.h
IConsoleCmd
Definition: console_internal.h:36
str_strip_colours
void str_strip_colours(char *str)
Scans the string for colour codes and strips them.
Definition: string.cpp:407
CHR_ALLOW
@ CHR_ALLOW
Allow command execution.
Definition: console_internal.h:21
GetArgumentInteger
bool GetArgumentInteger(uint32 *value, const char *arg)
Change a string into its number representation.
Definition: console.cpp:136
safeguards.h
IsValidConsoleColour
bool IsValidConsoleColour(TextColour c)
Check whether the given TextColour is valid for console usage.
Definition: console_gui.cpp:510
IsValidChar
bool IsValidChar(WChar key, CharSetFilter afilter)
Only allow certain keys.
Definition: string.cpp:494
StrEmpty
static bool StrEmpty(const char *s)
Check if a string buffer is empty.
Definition: string_func.h:67
settings_type.h
ICON_MAX_RECURSE
static const uint ICON_MAX_RECURSE
Maximum number of recursion.
Definition: console.cpp:24
_network_dedicated
bool _network_dedicated
are we a dedicated server?
Definition: network.cpp:61
stdafx.h
CS_ALPHANUMERAL
@ CS_ALPHANUMERAL
Both numeric and alphabetic and spaces and stuff.
Definition: string_type.h:27
IConsoleAlias::name
std::string name
name of the alias
Definition: console_internal.h:59
RemoveUnderscores
static std::string RemoveUnderscores(std::string name)
Creates a copy of a string with underscores removed from it.
Definition: console.cpp:158
IConsoleAliasExec
static void IConsoleAliasExec(const IConsoleAlias *alias, byte 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:216
IConsole::AliasRegister
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:191
NetworkServerSendRcon
void NetworkServerSendRcon(ClientID client_id, TextColour colour_code, const std::string &string)
Send an rcon reply to the client.
Definition: network_server.cpp:1951
IConsoleAlias
–Aliases– Aliases are like shortcuts for complex functions, variable assignments, etc.
Definition: console_internal.h:56
CC_ERROR
static const TextColour CC_ERROR
Colour for error lines.
Definition: console_type.h:24
stredup
char * stredup(const char *s, const char *last)
Create a duplicate of the given string.
Definition: string.cpp:138
CHR_DISALLOW
@ CHR_DISALLOW
Disallow command execution.
Definition: console_internal.h:22
network.h
Debug
#define Debug(name, level, format_string,...)
Ouptut a line of debugging information.
Definition: debug.h:37
lengthof
#define lengthof(x)
Return the length of an fixed size array.
Definition: stdafx.h:386
CHR_HIDE
@ CHR_HIDE
Hide the existence of the command.
Definition: console_internal.h:23
IConsoleCmdExec
void IConsoleCmdExec(const char *cmdstr, const uint recurse_count)
Execute a given command passed to us.
Definition: console.cpp:303
IConsoleCmdProc
bool IConsoleCmdProc(byte argc, char *argv[])
–Commands– Commands are commands, or functions.
Definition: console_internal.h:34
network_admin.h
console_func.h
strecpy
char * strecpy(char *dst, const char *src, const char *last)
Copies characters from one buffer to another.
Definition: string.cpp:113
free
static void free(const void *ptr)
Version of the standard free that accepts const pointers.
Definition: stdafx.h:470
lastof
#define lastof(x)
Get the last element of an fixed size array.
Definition: stdafx.h:402
network_func.h
ConsoleHookResult
ConsoleHookResult
Return values of console hooks (#IConsoleHook).
Definition: console_internal.h:20
IConsole::CmdGet
static IConsoleCmd * CmdGet(const std::string &name)
Find the command pointed to by its string.
Definition: console.cpp:179
debug.h
NetworkServerSendAdminRcon
void NetworkServerSendAdminRcon(AdminIndex admin_index, TextColour colour_code, const std::string_view string)
Pass the rcon reply to the admin.
Definition: network_admin.cpp:940
IConsolePrint
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:94