OpenTTD Source 20260218-master-g2123fca5ea
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(bool echo) IConsoleHook
Checks whether the command may be executed.
bool(std::span< std::string_view > argv) IConsoleCmdProc
Entrypoint of a console 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.
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:221
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
–Commands– Commands are commands, or functions.
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