OpenTTD Source 20250813-master-g5b5bdd346d
network_command.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 "network_admin.h"
12#include "network_client.h"
13#include "network_server.h"
14#include "../command_func.h"
15#include "../company_func.h"
16#include "../settings_type.h"
17#include "../airport_cmd.h"
18#include "../aircraft_cmd.h"
19#include "../autoreplace_cmd.h"
20#include "../company_cmd.h"
21#include "../depot_cmd.h"
22#include "../dock_cmd.h"
23#include "../economy_cmd.h"
24#include "../engine_cmd.h"
25#include "../error_func.h"
26#include "../goal_cmd.h"
27#include "../group_cmd.h"
28#include "../industry_cmd.h"
29#include "../landscape_cmd.h"
30#include "../league_cmd.h"
31#include "../misc_cmd.h"
32#include "../news_cmd.h"
33#include "../object_cmd.h"
34#include "../order_cmd.h"
35#include "../rail_cmd.h"
36#include "../road_cmd.h"
37#include "../roadveh_cmd.h"
38#include "../settings_cmd.h"
39#include "../signs_cmd.h"
40#include "../station_cmd.h"
41#include "../story_cmd.h"
42#include "../subsidy_cmd.h"
43#include "../terraform_cmd.h"
44#include "../timetable_cmd.h"
45#include "../town_cmd.h"
46#include "../train_cmd.h"
47#include "../tree_cmd.h"
48#include "../tunnelbridge_cmd.h"
49#include "../vehicle_cmd.h"
50#include "../viewport_cmd.h"
51#include "../water_cmd.h"
52#include "../waypoint_cmd.h"
53#include "../script/script_cmd.h"
54
55#include "../safeguards.h"
56
58static constexpr auto _callback_tuple = std::make_tuple(
59 (CommandCallback *)nullptr, // Make sure this is actually a pointer-to-function.
61 &CcBuildAirport,
63 &CcPlaySound_CONSTRUCTION_WATER,
64 &CcBuildDocks,
65 &CcFoundTown,
66 &CcBuildRoadTunnel,
67 &CcBuildRailTunnel,
69 &CcRoadDepot,
70 &CcRailDepot,
72 &CcPlaySound_EXPLOSION,
73 &CcPlaySound_CONSTRUCTION_OTHER,
74 &CcPlaySound_CONSTRUCTION_RAIL,
75 &CcStation,
76 &CcTerraform,
77 &CcAI,
80 &CcFoundRandomTown,
83 &CcGame,
85);
86
87#ifdef SILENCE_GCC_FUNCTION_POINTER_CAST
88/*
89 * We cast specialized function pointers to a generic one, but don't use the
90 * converted value to call the function, which is safe, except that GCC
91 * helpfully thinks it is not.
92 *
93 * "Any pointer to function can be converted to a pointer to a different function type.
94 * Calling the function through a pointer to a different function type is undefined,
95 * but converting such pointer back to pointer to the original function type yields
96 * the pointer to the original function." */
97# pragma GCC diagnostic push
98# pragma GCC diagnostic ignored "-Wcast-function-type"
99#endif
100
101/* Helpers to generate the callback table from the callback list. */
102
103inline constexpr size_t _callback_tuple_size = std::tuple_size_v<decltype(_callback_tuple)>;
104
105template <size_t... i>
106inline auto MakeCallbackTable(std::index_sequence<i...>) noexcept
107{
108 return std::array<CommandCallback *, sizeof...(i)>{{ reinterpret_cast<CommandCallback *>(reinterpret_cast<void(*)()>(std::get<i>(_callback_tuple)))... }}; // MingW64 fails linking when casting a pointer to its own type. To work around, cast it to some other type first.
109}
110
112static const auto _callback_table = MakeCallbackTable(std::make_index_sequence<_callback_tuple_size>{});
113
114template <typename T> struct CallbackArgsHelper;
115template <typename... Targs>
116struct CallbackArgsHelper<void(*const)(Commands, const CommandCost &, Targs...)> {
117 using Args = std::tuple<std::decay_t<Targs>...>;
118};
119
120
121/* Helpers to generate the command dispatch table from the command traits. */
122
123template <Commands Tcmd> static CommandDataBuffer SanitizeCmdStrings(const CommandDataBuffer &data);
124template <Commands Tcmd, size_t cb> static void UnpackNetworkCommand(const CommandPacket &cp);
125template <Commands Tcmd> static void NetworkReplaceCommandClientId(CommandPacket &cp, ClientID client_id);
126using UnpackNetworkCommandProc = void (*)(const CommandPacket &);
127using UnpackDispatchT = std::array<UnpackNetworkCommandProc, _callback_tuple_size>;
129 CommandDataBuffer(*Sanitize)(const CommandDataBuffer &);
130 void (*ReplaceClientId)(CommandPacket &, ClientID);
131 UnpackDispatchT Unpack;
132};
133
134template <Commands Tcmd, size_t Tcb>
135constexpr UnpackNetworkCommandProc MakeUnpackNetworkCommandCallback() noexcept
136{
137 /* Check if the callback matches with the command arguments. If not, don't generate an Unpack proc. */
138 using Tcallback = std::tuple_element_t<Tcb, decltype(_callback_tuple)>;
139 if constexpr (std::is_same_v<Tcallback, CommandCallback * const> || // Callback type is CommandCallback.
140 std::is_same_v<Tcallback, CommandCallbackData * const> || // Callback type is CommandCallbackData.
141 std::is_same_v<typename CommandTraits<Tcmd>::CbArgs, typename CallbackArgsHelper<Tcallback>::Args> || // Callback proc takes all command return values and parameters.
142 (!std::is_void_v<typename CommandTraits<Tcmd>::RetTypes> && std::is_same_v<typename CallbackArgsHelper<typename CommandTraits<Tcmd>::RetCallbackProc const>::Args, typename CallbackArgsHelper<Tcallback>::Args>)) { // Callback return is more than CommandCost and the proc takes all return values.
143 return &UnpackNetworkCommand<Tcmd, Tcb>;
144 } else {
145 return nullptr;
146 }
147}
148
149template <Commands Tcmd, size_t... i>
150constexpr UnpackDispatchT MakeUnpackNetworkCommand(std::index_sequence<i...>) noexcept
151{
152 return UnpackDispatchT{{ MakeUnpackNetworkCommandCallback<Tcmd, i>()...}};
153}
154
155template <typename T, T... i, size_t... j>
156inline constexpr auto MakeDispatchTable(std::integer_sequence<T, i...>, std::index_sequence<j...>) noexcept
157{
158 return std::array<CommandDispatch, sizeof...(i)>{{ { &SanitizeCmdStrings<static_cast<Commands>(i)>, &NetworkReplaceCommandClientId<static_cast<Commands>(i)>, MakeUnpackNetworkCommand<static_cast<Commands>(i)>(std::make_index_sequence<_callback_tuple_size>{}) }... }};
159}
161static constexpr auto _cmd_dispatch = MakeDispatchTable(std::make_integer_sequence<std::underlying_type_t<Commands>, CMD_END>{}, std::make_index_sequence<_callback_tuple_size>{});
162
163#ifdef SILENCE_GCC_FUNCTION_POINTER_CAST
164# pragma GCC diagnostic pop
165#endif
166
171
172
178static size_t FindCallbackIndex(CommandCallback *callback)
179{
180 if (auto it = std::ranges::find(_callback_table, callback); it != std::end(_callback_table)) {
181 return static_cast<size_t>(std::distance(std::begin(_callback_table), it));
182 }
183
184 return std::numeric_limits<size_t>::max();
185}
186
195void NetworkSendCommand(Commands cmd, StringID err_message, CommandCallback *callback, CompanyID company, const CommandDataBuffer &cmd_data)
196{
198 c.company = company;
199 c.cmd = cmd;
200 c.err_msg = err_message;
201 c.callback = callback;
202 c.data = cmd_data;
203
204 if (_network_server) {
205 /* If we are the server, we queue the command in our 'special' queue.
206 * In theory, we could execute the command right away, but then the
207 * client on the server can do everything 1 tick faster than others.
208 * So to keep the game fair, we delay the command with 1 tick
209 * which gives about the same speed as most clients.
210 */
212 c.my_cmd = true;
213
214 _local_wait_queue.push_back(std::move(c));
215 return;
216 }
217
218 c.frame = 0; // The client can't tell which frame, so just make it 0
219
220 /* Clients send their command to the server and forget all about the packet */
222}
223
233void NetworkSyncCommandQueue(NetworkClientSocket *cs)
234{
235 for (auto &p : _local_execution_queue) {
236 CommandPacket &c = cs->outgoing_queue.emplace_back(p);
237 c.callback = nullptr;
238 }
239}
240
245{
246 assert(IsLocalCompany());
247
249
250 auto cp = queue.begin();
251 for (; cp != queue.end(); cp++) {
252 /* The queue is always in order, which means
253 * that the first element will be executed first. */
254 if (_frame_counter < cp->frame) break;
255
256 if (_frame_counter > cp->frame) {
257 /* If we reach here, it means for whatever reason, we've already executed
258 * past the command we need to execute. */
259 FatalError("[net] Trying to execute a packet in the past!");
260 }
261
262 /* We can execute this command */
263 _current_company = cp->company;
264 size_t cb_index = FindCallbackIndex(cp->callback);
265 assert(cb_index < _callback_tuple_size);
266 assert(_cmd_dispatch[cp->cmd].Unpack[cb_index] != nullptr);
267 _cmd_dispatch[cp->cmd].Unpack[cb_index](*cp);
268 }
269 queue.erase(queue.begin(), cp);
270
271 /* Local company may have changed, so we should not restore the old value */
273}
274
283
289static void DistributeCommandPacket(CommandPacket &cp, const NetworkClientSocket *owner)
290{
291 CommandCallback *callback = cp.callback;
292 cp.frame = _frame_counter_max + 1;
293
294 for (NetworkClientSocket *cs : NetworkClientSocket::Iterate()) {
295 if (cs->status >= NetworkClientSocket::STATUS_MAP) {
296 /* Callbacks are only send back to the client who sent them in the
297 * first place. This filters that out. */
298 cp.callback = (cs != owner) ? nullptr : callback;
299 cp.my_cmd = (cs == owner);
300 cs->outgoing_queue.push_back(cp);
301 }
302 }
303
304 cp.callback = (nullptr != owner) ? nullptr : callback;
305 cp.my_cmd = (nullptr == owner);
306 _local_execution_queue.push_back(cp);
307}
308
314static void DistributeQueue(CommandQueue &queue, const NetworkClientSocket *owner)
315{
316#ifdef DEBUG_DUMP_COMMANDS
317 /* When replaying we do not want this limitation. */
318 int to_go = UINT16_MAX;
319#else
321 if (owner == nullptr) {
322 /* This is the server, use the commands_per_frame_server setting if higher */
323 to_go = std::max<int>(to_go, _settings_client.network.commands_per_frame_server);
324 }
325#endif
326
327 /* Not technically the most performant way, but consider clients rarely click more than once per tick. */
328 for (auto cp = queue.begin(); cp != queue.end(); /* removing some items */) {
329 /* Do not distribute commands when paused and the command is not allowed while paused. */
330 if (_pause_mode.Any() && !IsCommandAllowedWhilePaused(cp->cmd)) {
331 ++cp;
332 continue;
333 }
334
335 /* Limit the number of commands per client per tick. */
336 if (--to_go < 0) break;
337
338 DistributeCommandPacket(*cp, owner);
339 NetworkAdminCmdLogging(owner, *cp);
340 cp = queue.erase(cp);
341 }
342}
343
346{
347 /* First send the server's commands. */
349
350 /* Then send the queues of the others. */
351 for (NetworkClientSocket *cs : NetworkClientSocket::Iterate()) {
352 DistributeQueue(cs->incoming_queue, cs);
353 }
354}
355
362std::optional<std::string_view> NetworkGameSocketHandler::ReceiveCommand(Packet &p, CommandPacket &cp)
363{
364 cp.company = (CompanyID)p.Recv_uint8();
365 cp.cmd = static_cast<Commands>(p.Recv_uint16());
366 if (!IsValidCommand(cp.cmd)) return "invalid command";
367 if (GetCommandFlags(cp.cmd).Test(CommandFlag::Offline)) return "single-player only command";
368 cp.err_msg = p.Recv_uint16();
369 cp.data = _cmd_dispatch[cp.cmd].Sanitize(p.Recv_buffer());
370
371 uint8_t callback = p.Recv_uint8();
372 if (callback >= _callback_table.size() || _cmd_dispatch[cp.cmd].Unpack[callback] == nullptr) return "invalid callback";
373
374 cp.callback = _callback_table[callback];
375 return std::nullopt;
376}
377
384{
385 p.Send_uint8(cp.company);
386 p.Send_uint16(cp.cmd);
387 p.Send_uint16(cp.err_msg);
388 p.Send_buffer(cp.data);
389
390 size_t callback = FindCallbackIndex(cp.callback);
391 if (callback > UINT8_MAX || _cmd_dispatch[cp.cmd].Unpack[callback] == nullptr) {
392 Debug(net, 0, "Unknown callback for command; no callback sent (command: {})", cp.cmd);
393 callback = 0; // _callback_table[0] == nullptr
394 }
395 p.Send_uint8 ((uint8_t)callback);
396}
397
399template <class T>
400static inline void SetClientIdHelper(T &data, [[maybe_unused]] ClientID client_id)
401{
402 if constexpr (std::is_same_v<ClientID, T>) {
403 data = client_id;
404 }
405}
406
408template <class Ttuple, size_t... Tindices>
409static inline void SetClientIds(Ttuple &values, ClientID client_id, std::index_sequence<Tindices...>)
410{
411 ((SetClientIdHelper(std::get<Tindices>(values), client_id)), ...);
412}
413
414template <Commands Tcmd>
415static void NetworkReplaceCommandClientId(CommandPacket &cp, ClientID client_id)
416{
417 /* Unpack command parameters. */
418 auto params = EndianBufferReader::ToValue<typename CommandTraits<Tcmd>::Args>(cp.data);
419
420 /* Insert client id. */
421 SetClientIds(params, client_id, std::make_index_sequence<std::tuple_size_v<decltype(params)>>{});
422
423 /* Repack command parameters. */
425}
426
432void NetworkReplaceCommandClientId(CommandPacket &cp, ClientID client_id)
433{
434 _cmd_dispatch[cp.cmd].ReplaceClientId(cp, client_id);
435}
436
437
439template <class T>
440static inline void SanitizeSingleStringHelper([[maybe_unused]] CommandFlags cmd_flags, T &data)
441{
442 if constexpr (std::is_same_v<std::string, T>) {
443 if (!_network_server && cmd_flags.Test(CommandFlag::StrCtrl)) {
445 } else {
447 }
448 }
449}
450
452template <class Ttuple, size_t... Tindices>
453static inline void SanitizeStringsHelper(CommandFlags cmd_flags, Ttuple &values, std::index_sequence<Tindices...>)
454{
455 ((SanitizeSingleStringHelper(cmd_flags, std::get<Tindices>(values))), ...);
456}
457
464template <Commands Tcmd>
466{
467 auto args = EndianBufferReader::ToValue<typename CommandTraits<Tcmd>::Args>(data);
468 SanitizeStringsHelper(CommandTraits<Tcmd>::flags, args, std::make_index_sequence<std::tuple_size_v<typename CommandTraits<Tcmd>::Args>>{});
470}
471
478template <Commands Tcmd, size_t Tcb>
480{
481 auto args = EndianBufferReader::ToValue<typename CommandTraits<Tcmd>::Args>(cp.data);
482 Command<Tcmd>::PostFromNet(cp.err_msg, std::get<Tcb>(_callback_tuple), cp.my_cmd, args);
483}
void CcBuildBridge(Commands, const CommandCost &result, TileIndex end_tile, TileIndex tile_start, TransportType transport_type, BridgeType, uint8_t)
Callback executed after a build Bridge CMD has been called.
constexpr bool Test(Tvalue_type value) const
Test if the value-th bit is set.
constexpr bool Any(const Timpl &other) const
Test if any of the given values are set.
static ClientNetworkGameSocketHandler * my_client
This is us!
static NetworkRecvStatus SendCommand(const CommandPacket &cp)
Send a command to the server.
Common return value for all commands.
Endian-aware buffer adapter that always writes values in little endian order.
std::optional< std::string_view > ReceiveCommand(Packet &p, CommandPacket &cp)
Receives a command from the network.
CommandQueue incoming_queue
The command-queue awaiting handling.
Definition tcp_game.h:489
void SendCommand(Packet &p, const CommandPacket &cp)
Sends a command over the network.
CommandFlags GetCommandFlags(Commands cmd)
This function mask the parameter with CMD_ID_MASK and returns the flags which belongs to the given co...
Definition command.cpp:118
bool IsCommandAllowedWhilePaused(Commands cmd)
Returns whether the command is allowed while the game is paused.
Definition command.cpp:144
bool IsValidCommand(Commands cmd)
This function range-checks a cmd.
Definition command.cpp:106
void CommandCallback(Commands cmd, const CommandCost &result, TileIndex tile)
Define a callback function for the client, after the command is finished.
@ Offline
the command cannot be executed in a multiplayer game; single-player only
@ StrCtrl
the command's string may contain control strings
std::vector< uint8_t > CommandDataBuffer
Storage buffer for serialized command data.
Commands
List of commands.
@ CMD_END
Must ALWAYS be on the end of this list!! (period)
CompanyID _local_company
Company controlled by the human player at this client. Can also be COMPANY_SPECTATOR.
CompanyID _current_company
Company currently doing an action.
bool IsLocalCompany()
Is the current company the local company?
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
void CcCloneVehicle(Commands cmd, const CommandCost &result, VehicleID veh_id)
This is the Callback method after the cloning attempt of a vehicle.
PauseModes _pause_mode
The current pause mode.
Definition gfx.cpp:51
void CcCreateGroup(Commands cmd, const CommandCost &result, GroupID new_group, VehicleType vt, GroupID parent_group)
Opens a 'Rename group' window for newly created group.
void CcAddVehicleNewGroup(Commands cmd, const CommandCost &result, GroupID new_group, GroupID, VehicleID veh_id, bool, const VehicleListIdentifier &)
Open rename window after adding a vehicle to a new group via drag and drop.
uint32_t _frame_counter
The current frame.
Definition network.cpp:80
bool _network_server
network-server is active
Definition network.cpp:68
uint32_t _frame_counter_max
To where we may go with our clients.
Definition network.cpp:79
void NetworkAdminCmdLogging(const NetworkClientSocket *owner, const CommandPacket &cp)
Distribute CommandPacket details over the admin network for logging purposes.
Server part of the admin network protocol.
Client part of the network protocol.
static void SanitizeStringsHelper(CommandFlags cmd_flags, Ttuple &values, std::index_sequence< Tindices... >)
Helper function to perform validation on command data strings.
static constexpr auto _callback_tuple
Typed list of all possible callbacks.
void NetworkFreeLocalCommandQueue()
Free the local command queues.
static void SetClientIds(Ttuple &values, ClientID client_id, std::index_sequence< Tindices... >)
Set all invalid ClientID's to the proper value.
static const auto _callback_table
Type-erased table of callbacks.
static size_t FindCallbackIndex(CommandCallback *callback)
Find the callback index of a callback pointer.
void NetworkExecuteLocalCommandQueue()
Execute all commands on the local command queue that ought to be executed this frame.
void NetworkDistributeCommands()
Distribute the commands of ourself and the clients.
static CommandQueue _local_wait_queue
Local queue of packets waiting for handling.
static void SetClientIdHelper(T &data, ClientID client_id)
Helper to process a single ClientID argument.
static void SanitizeSingleStringHelper(CommandFlags cmd_flags, T &data)
Validate a single string argument coming from network.
static CommandQueue _local_execution_queue
Local queue of packets waiting for execution.
static void DistributeCommandPacket(CommandPacket &cp, const NetworkClientSocket *owner)
"Send" a particular CommandPacket to all clients.
void NetworkSyncCommandQueue(NetworkClientSocket *cs)
Sync our local command queue to the command queue of the given socket.
static void UnpackNetworkCommand(const CommandPacket &cp)
Unpack a generic command packet into its actual typed components.
static void DistributeQueue(CommandQueue &queue, const NetworkClientSocket *owner)
"Send" a particular CommandQueue to all clients.
void NetworkSendCommand(Commands cmd, StringID err_message, CommandCallback *callback, CompanyID company, const CommandDataBuffer &cmd_data)
Prepare a DoCommand to be send over the network.
static CommandDataBuffer SanitizeCmdStrings(const CommandDataBuffer &data)
Validate and sanitize strings in command data.
static constexpr auto _cmd_dispatch
Command dispatch table.
Server part of the network protocol.
ClientID
'Unique' identifier to be given to clients
void CcRoadStop(Commands cmd, const CommandCost &result, TileIndex tile, uint8_t width, uint8_t length, RoadStopType, bool is_drive_through, DiagDirection dir, RoadType, RoadStopClassID spec_class, uint16_t spec_index, StationID, bool)
Command callback for building road stops.
Definition road_gui.cpp:195
ClientSettings _settings_client
The current settings for this game.
Definition settings.cpp:60
void CcPlaceSign(Commands, const CommandCost &result, SignID new_sign)
Callback function that is called after a sign is placed.
void StrMakeValidInPlace(char *str, StringValidationSettings settings)
Scans the string for invalid characters and replaces them with a question mark '?' (if not ignored).
Definition string.cpp:155
@ ReplaceWithQuestionMark
Replace the unknown/bad bits with question marks.
@ AllowControlCode
Allow the special control codes.
uint32_t StringID
Numeric value that represents a string, independent of the selected language.
NetworkSettings network
settings related to the network
Everything we need to know about a command to be able to execute it.
StringID err_msg
string ID of error message to use.
CommandDataBuffer data
command parameters.
CommandCallback * callback
any callback function executed upon successful completion of the command.
uint32_t frame
the frame in which this packet is executed
CompanyID company
company that is executing the command
bool my_cmd
did the command originate from "me"
Commands cmd
command being executed.
Defines the traits of a command.
uint16_t commands_per_frame
how many commands may be sent each frame_freq frames?
uint16_t commands_per_frame_server
how many commands may be sent each frame_freq frames? (server-originating commands)
Internal entity of a packet.
Definition packet.h:43
uint16_t Recv_uint16()
Read a 16 bits integer from the packet.
Definition packet.cpp:332
uint8_t Recv_uint8()
Read a 8 bits integer from the packet.
Definition packet.cpp:318
void Send_uint8(uint8_t data)
Package a 8 bits integer in the packet.
Definition packet.cpp:120
void Send_buffer(const std::vector< uint8_t > &data)
Copy a sized byte buffer into the packet.
Definition packet.cpp:183
std::vector< uint8_t > Recv_buffer()
Extract a sized byte buffer from the packet.
Definition packet.cpp:385
void Send_uint16(uint16_t data)
Package a 16 bits integer in the packet.
Definition packet.cpp:130
std::vector< CommandPacket > CommandQueue
A "queue" of CommandPackets.
Definition tcp_game.h:138
void CcBuildWagon(Commands cmd, const CommandCost &result, VehicleID new_veh_id, uint, uint16_t, CargoArray, TileIndex tile, EngineID, bool, CargoType, ClientID)
Callback for building wagons.
Definition train_gui.cpp:29
void CcStartStopVehicle(Commands cmd, const CommandCost &result, VehicleID veh_id, bool)
This is the Callback method after attempting to start/stop a vehicle.
void CcBuildPrimaryVehicle(Commands cmd, const CommandCost &result, VehicleID new_veh_id, uint, uint16_t, CargoArray)
This is the Callback method after the construction attempt of a primary vehicle.