OpenTTD Source 20260206-master-g4d4e37dbf1
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 <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
10#include "stdafx.h"
13#include "console_internal.h"
14#include "network/network.h"
17#include "debug.h"
18#include "console_func.h"
19#include "settings_type.h"
20
21#include "safeguards.h"
22
23static const uint ICON_MAX_RECURSE = 10;
24
25/* console parser */
26/* static */ IConsole::CommandList &IConsole::Commands()
27{
28 static IConsole::CommandList cmds;
29 return cmds;
30}
31
32/* static */ IConsole::AliasList &IConsole::Aliases()
33{
34 static IConsole::AliasList aliases;
35 return aliases;
36}
37
38std::optional<FileHandle> _iconsole_output_file;
39
40void IConsoleInit()
41{
42 _iconsole_output_file = std::nullopt;
44 _redirect_console_to_admin = AdminID::Invalid();
45
46 IConsoleGUIInit();
47
48 IConsoleStdLibRegister();
49}
50
51static void IConsoleWriteToLogFile(const std::string &string)
52{
53 if (_iconsole_output_file.has_value()) {
54 /* if there is an console output file ... also print it there */
55 try {
56 fmt::print(*_iconsole_output_file, "{}{}\n", GetLogPrefix(), string);
57 } catch (const std::system_error &) {
58 _iconsole_output_file.reset();
59 IConsolePrint(CC_ERROR, "Cannot write to console log file; closing the log file.");
60 }
61 }
62}
63
64bool CloseConsoleLogIfActive()
65{
66 if (_iconsole_output_file.has_value()) {
67 IConsolePrint(CC_INFO, "Console log file closed.");
68 _iconsole_output_file.reset();
69 return true;
70 }
71
72 return false;
73}
74
75void IConsoleFree()
76{
77 IConsoleGUIFree();
78 CloseConsoleLogIfActive();
79}
80
90void IConsolePrint(TextColour colour_code, const std::string &string)
91{
92 assert(IsValidConsoleColour(colour_code));
93
95 /* Redirect the string to the client */
97 return;
98 }
99
100 if (_redirect_console_to_admin != AdminID::Invalid()) {
102 return;
103 }
104
105 /* Create a copy of the string, strip it of colours and invalid
106 * characters and (when applicable) assign it to the console buffer */
107 std::string str = StrMakeValid(string, {});
108
109 if (_network_dedicated) {
110 NetworkAdminConsole("console", str);
111 fmt::print("{}{}\n", GetLogPrefix(), str);
112 fflush(stdout);
113 IConsoleWriteToLogFile(str);
114 return;
115 }
116
117 IConsoleWriteToLogFile(str);
118 IConsoleGUIPrint(colour_code, str);
119}
120
126static std::string RemoveUnderscores(std::string name)
127{
128 name.erase(std::remove(name.begin(), name.end(), '_'), name.end());
129 return name;
130}
131
138/* static */ void IConsole::CmdRegister(const std::string &name, IConsoleCmdProc *proc, IConsoleHook *hook)
139{
140 IConsole::Commands().try_emplace(RemoveUnderscores(name), name, proc, hook);
141}
142
148/* static */ IConsoleCmd *IConsole::CmdGet(const std::string &name)
149{
150 auto item = IConsole::Commands().find(RemoveUnderscores(name));
151 if (item != IConsole::Commands().end()) return &item->second;
152 return nullptr;
153}
154
160/* static */ void IConsole::AliasRegister(const std::string &name, std::string_view cmd)
161{
162 auto result = IConsole::Aliases().try_emplace(RemoveUnderscores(name), name, cmd);
163 if (!result.second) IConsolePrint(CC_ERROR, "An alias with the name '{}' already exists.", name);
164}
165
171/* static */ IConsoleAlias *IConsole::AliasGet(const std::string &name)
172{
173 auto item = IConsole::Aliases().find(RemoveUnderscores(name));
174 if (item != IConsole::Aliases().end()) return &item->second;
175 return nullptr;
176}
177
185static void IConsoleAliasExec(const IConsoleAlias *alias, std::span<std::string> tokens, uint recurse_count)
186{
187 Debug(console, 6, "Requested command is an alias; parsing...");
188
189 if (recurse_count > ICON_MAX_RECURSE) {
190 IConsolePrint(CC_ERROR, "Too many alias expansions, recursion limit reached.");
191 return;
192 }
193
194 std::string buffer;
195 StringBuilder builder{buffer};
196
197 StringConsumer consumer{alias->cmdline};
198 while (consumer.AnyBytesLeft()) {
199 auto c = consumer.TryReadUtf8();
200 if (!c.has_value()) {
201 IConsolePrint(CC_ERROR, "Alias '{}' ('{}') contains malformed characters.", alias->name, alias->cmdline);
202 return;
203 }
204
205 switch (*c) {
206 case '\'': // ' will double for ""
207 builder.PutChar('\"');
208 break;
209
210 case ';': // Cmd separator; execute previous and start new command
211 IConsoleCmdExec(builder.GetString(), recurse_count);
212
213 buffer.clear();
214 break;
215
216 case '%': // Some or all parameters
217 c = consumer.ReadUtf8();
218 switch (*c) {
219 case '+': { // All parameters separated: "[param 1]" "[param 2]"
220 for (size_t i = 0; i < tokens.size(); ++i) {
221 if (i != 0) builder.PutChar(' ');
222 builder.PutChar('\"');
223 builder += tokens[i];
224 builder.PutChar('\"');
225 }
226 break;
227 }
228
229 case '!': { // Merge the parameters to one: "[param 1] [param 2] [param 3...]"
230 builder.PutChar('\"');
231 for (size_t i = 0; i < tokens.size(); ++i) {
232 if (i != 0) builder.PutChar(' ');
233 builder += tokens[i];
234 }
235 builder.PutChar('\"');
236 break;
237 }
238
239 default: { // One specific parameter: %A = [param 1] %B = [param 2] ...
240 size_t param = *c - 'A';
241
242 if (param >= tokens.size()) {
243 IConsolePrint(CC_ERROR, "Too many or wrong amount of parameters passed to alias.");
244 IConsolePrint(CC_HELP, "Usage of alias '{}': '{}'.", alias->name, alias->cmdline);
245 return;
246 }
247
248 builder.PutChar('\"');
249 builder += tokens[param];
250 builder.PutChar('\"');
251 break;
252 }
253 }
254 break;
255
256 default:
257 builder.PutUtf8(*c);
258 break;
259 }
260 }
261
262 IConsoleCmdExec(builder.GetString(), recurse_count);
263}
264
271void IConsoleCmdExec(std::string_view command_string, const uint recurse_count)
272{
273 if (command_string[0] == '#') return; // comments
274
275 Debug(console, 4, "Executing cmdline: '{}'", command_string);
276
277 std::string buffer;
278 StringBuilder builder{buffer};
279 StringConsumer consumer{command_string};
280
281 std::vector<std::string> tokens;
282 bool found_token = false;
283 bool in_quotes = false;
284
285 /* 1. Split up commandline into tokens, separated by spaces, commands
286 * enclosed in "" are taken as one token. We can only go as far as the amount
287 * of characters in our stream or the max amount of tokens we can handle */
288 while (consumer.AnyBytesLeft()) {
289 auto c = consumer.TryReadUtf8();
290 if (!c.has_value()) {
291 IConsolePrint(CC_ERROR, "Command '{}' contains malformed characters.", command_string);
292 return;
293 }
294
295 switch (*c) {
296 case ' ': // Token separator
297 if (!found_token) break;
298
299 if (in_quotes) {
300 builder.PutUtf8(*c);
301 break;
302 }
303
304 tokens.emplace_back(std::move(buffer));
305 buffer.clear();
306 found_token = false;
307 break;
308
309 case '"': // Tokens enclosed in "" are one token
310 in_quotes = !in_quotes;
311 found_token = true;
312 break;
313
314 case '\\': // Escape character for ""
315 if (consumer.ReadUtf8If('"')) {
316 builder.PutUtf8('"');
317 break;
318 }
319 [[fallthrough]];
320
321 default: // Normal character
322 builder.PutUtf8(*c);
323 found_token = true;
324 break;
325 }
326 }
327
328 if (found_token) {
329 tokens.emplace_back(std::move(buffer));
330 buffer.clear();
331 }
332
333 for (size_t i = 0; i < tokens.size(); i++) {
334 Debug(console, 8, "Token {} is: '{}'", i, tokens[i]);
335 }
336
337 if (tokens.empty() || tokens[0].empty()) return; // don't execute empty commands
338 /* 2. Determine type of command (cmd or alias) and execute
339 * First try commands, then aliases. Execute
340 * the found action taking into account its hooking code
341 */
342 IConsoleCmd *cmd = IConsole::CmdGet(tokens[0]);
343 if (cmd != nullptr) {
344 ConsoleHookResult chr = (cmd->hook == nullptr ? CHR_ALLOW : cmd->hook(true));
345 switch (chr) {
346 case CHR_ALLOW: {
347 std::vector<std::string_view> views;
348 for (auto &token : tokens) views.emplace_back(token);
349 if (!cmd->proc(views)) { // index started with 0
350 cmd->proc({}); // if command failed, give help
351 }
352 return;
353 }
354
355 case CHR_DISALLOW: return;
356 case CHR_HIDE: break;
357 }
358 }
359
360 IConsoleAlias *alias = IConsole::AliasGet(tokens[0]);
361 if (alias != nullptr) {
362 IConsoleAliasExec(alias, std::span(tokens).subspan(1), recurse_count + 1);
363 return;
364 }
365
366 IConsolePrint(CC_ERROR, "Command '{}' not found.", tokens[0]);
367}
void PutUtf8(char32_t c)
Append UTF.8 char.
void PutChar(char c)
Append 8-bit char.
Compose data into a growing std::string.
std::string & GetString() noexcept
Get mutable already written data.
Parse data from a string / buffer.
char32_t ReadUtf8(char32_t def='?')
Read UTF-8 character, and advance reader.
bool AnyBytesLeft() const noexcept
Check whether any bytes left to read.
std::optional< char32_t > TryReadUtf8()
Try to read a UTF-8 character, and then advance reader.
bool ReadUtf8If(char32_t c)
Check whether the next UTF-8 char matches 'c', and skip it.
static std::string RemoveUnderscores(std::string name)
Creates a copy of a string with underscores removed from it.
Definition console.cpp:126
void IConsoleCmdExec(std::string_view command_string, const uint recurse_count)
Execute a given command passed to us.
Definition console.cpp:271
static void IConsoleAliasExec(const IConsoleAlias *alias, std::span< std::string > tokens, uint recurse_count)
An alias is just another name for a command, or for more commands Execute it as well.
Definition console.cpp:185
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:90
static const uint ICON_MAX_RECURSE
Maximum number of recursion.
Definition console.cpp:23
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.
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(std::span< std::string_view >) IConsoleCmdProc
–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:220
Functions related to debugging.
#define Debug(category, level, format_string,...)
Output 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:307
ClientID _redirect_console_to_client
If not invalid, redirect the console output to a client.
Definition network.cpp:72
bool _network_dedicated
are we a dedicated server?
Definition network.cpp:69
Basic functions/variables used all over the place.
void NetworkServerSendAdminRcon(AdminID admin_index, TextColour colour_code, std::string_view string)
Pass the rcon reply to the admin.
void NetworkAdminConsole(std::string_view origin, std::string_view string)
Send console to the admin network (if they did opt in for the respective update).
AdminID _redirect_console_to_admin
Redirection of the (remote) console 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, std::string_view string)
Send an rcon reply to the client.
@ 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.
static void StrMakeValid(Builder &builder, StringConsumer &consumer, StringValidationSettings settings)
Copies the valid (UTF-8) characters from consumer to the builder.
Definition string.cpp:119
Compose strings from textual and binary data.
Parse strings.
–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 void AliasRegister(const std::string &name, std::string_view cmd)
Register a an alias for an already existing command in the console.
Definition console.cpp:160
static IConsoleAlias * AliasGet(const std::string &name)
Find the alias pointed to by its string.
Definition console.cpp:171
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:138
static IConsoleCmd * CmdGet(const std::string &name)
Find the command pointed to by its string.
Definition console.cpp:148