1 /*
2 This file is part of Warzone 2100.
3 Copyright (C) 1999-2004 Eidos Interactive
4 Copyright (C) 2005-2020 Warzone 2100 Project
5
6 Warzone 2100 is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 Warzone 2100 is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Warzone 2100; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20 /**
21 * @file netplay.c
22 *
23 * Basic netcode.
24 */
25
26 #include "lib/framework/frame.h"
27 #include "lib/framework/wzapp.h"
28 #include "lib/framework/string_ext.h"
29 #include "lib/framework/crc.h"
30 #include "lib/framework/file.h"
31 #include "lib/gamelib/gtime.h"
32 #include "lib/exceptionhandler/dumpinfo.h"
33 #include "src/console.h"
34 #include "src/component.h" // FIXME: we need to handle this better
35 #include "src/modding.h" // FIXME: we need to handle this better
36
37 #include <time.h> // for stats
38 #include <physfs.h>
39 #include "lib/framework/physfs_ext.h"
40 #include <string.h>
41 #include <memory>
42 #include <thread>
43 #include <atomic>
44 #include <limits>
45 #include <sodium.h>
46 #include <re2/re2.h>
47
48 #include "netplay.h"
49 #include "netlog.h"
50 #include "netsocket.h"
51
52 #include <miniupnpc/miniwget.h>
53 #if (defined(__GNUC__) || defined(__clang__)) && !defined(__INTEL_COMPILER)
54 # pragma GCC diagnostic push
55 # pragma GCC diagnostic ignored "-Wpedantic"
56 #endif
57 #include <miniupnpc/miniupnpc.h>
58 #if (defined(__GNUC__) || defined(__clang__)) && !defined(__INTEL_COMPILER)
59 # pragma GCC diagnostic pop
60 #endif
61 #include <miniupnpc/upnpcommands.h>
62
63 // Enforce minimum MINIUPNPC_API_VERSION
64 #if !defined(MINIUPNPC_API_VERSION) || (MINIUPNPC_API_VERSION < 9)
65 #error lib/netplay requires MINIUPNPC_API_VERSION >= 9
66 #endif
67
68 #include "src/multistat.h"
69 #include "src/multijoin.h"
70 #include "src/multiint.h"
71 #include "src/multiplay.h"
72 #include "src/warzoneconfig.h"
73 #include "src/version.h"
74 #include "src/loadsave.h"
75 #include "src/activity.h"
76
77 #if defined (WZ_OS_MAC)
78 # include "lib/framework/cocoa_wrapper.h"
79 #endif
80
81 #if defined(WZ_OS_LINUX) && defined(__GLIBC__)
82 #include <execinfo.h> // Nonfatal runtime backtraces.
83 #endif // defined(WZ_OS_LINUX) && defined(__GLIBC__)
84
85 // WARNING !!! This is initialised via configuration.c !!!
86 char masterserver_name[255] = {'\0'};
87 static unsigned int masterserver_port = 0, gameserver_port = 0;
88 static bool bJoinPrefTryIPv6First = true;
89
90 // This is for command line argument override
91 // Disables port saving and reading from/to config
92 bool netGameserverPortOverride = false;
93
94 #define NET_TIMEOUT_DELAY 2500 // we wait this amount of time for socket activity
95 #define NET_READ_TIMEOUT 0
96 /*
97 * === Using new socket code, this might not hold true any longer ===
98 * NOTE /rant: If the buffer size isn't big enough, it will invalidate the socket.
99 * Which means that we need to allocate a buffer big enough to handle worst case
100 * situations.
101 * reference: MaxMsgSize in netplay.h (currently set to 16K)
102 *
103 */
104 #define NET_BUFFER_SIZE (MaxMsgSize) // Would be 16K
105
106 #define UPNP_SUCCESS 1
107 #define UPNP_ERROR_DEVICE_NOT_FOUND -1
108 #define UPNP_ERROR_CONTROL_NOT_AVAILABLE -2
109
110 // ////////////////////////////////////////////////////////////////////////
111 // Function prototypes
112 static void NETplayerLeaving(UDWORD player); // Cleanup sockets on player leaving (nicely)
113 static void NETplayerDropped(UDWORD player); // Broadcast NET_PLAYER_DROPPED & cleanup
114 static void NETallowJoining();
115 static void recvDebugSync(NETQUEUE queue);
116 static bool onBanList(const char *ip);
117 static void addToBanList(const char *ip, const char *name);
118 static void NETfixPlayerCount();
119 /*
120 * Network globals, these are part of the new network API
121 */
122 SYNC_COUNTER sync_counter; // keeps track on how well we are in sync
123 // ////////////////////////////////////////////////////////////////////////
124 // Types
125
126 std::atomic_int upnp_status;
127
128 struct Statistic
129 {
130 size_t sent;
131 size_t received;
132 };
133
134 struct NETSTATS // data regarding the last one second or so.
135 {
136 Statistic rawBytes; // Number of actual bytes, in about 1 sec.
137 Statistic uncompressedBytes; // Number of bytes sent, before compression, in about 1 sec.
138 Statistic packets; // Number of calls to writeAll, in about 1 sec.
139 };
140
141 struct NET_PLAYER_DATA
142 {
143 uint16_t size;
144 void *data;
145 size_t buffer_size;
146 };
147
148 #define SERVER_UPDATE_MIN_INTERVAL 7 * GAME_TICKS_PER_SEC
149 #define SERVER_UPDATE_MAX_INTERVAL 25 * GAME_TICKS_PER_SEC
150
151 class LobbyServerConnectionHandler
152 {
153 public:
154 bool connect();
155 bool disconnect();
156 void sendUpdate();
157 void run();
158 private:
159 void sendUpdateNow();
160 void sendKeepAlive();
161
canSendServerUpdateNow()162 inline bool canSendServerUpdateNow()
163 {
164 return (currentState == LobbyConnectionState::Connected)
165 && (realTime - lastServerUpdate >= SERVER_UPDATE_MIN_INTERVAL);
166 }
167
shouldSendServerKeepAliveNow()168 inline bool shouldSendServerKeepAliveNow()
169 {
170 return (currentState == LobbyConnectionState::Connected)
171 && (realTime - lastServerUpdate >= SERVER_UPDATE_MAX_INTERVAL);
172 }
173 private:
174 enum class LobbyConnectionState
175 {
176 Disconnected,
177 Connecting_WaitingForResponse,
178 Connected
179 };
180 LobbyConnectionState currentState = LobbyConnectionState::Disconnected;
181 Socket *rs_socket = nullptr;
182 SocketSet* waitingForConnectionFinalize = nullptr;
183 uint32_t lastConnectionTime = 0;
184 uint32_t lastServerUpdate = 0;
185 bool queuedServerUpdate = false;
186 };
187
188 // ////////////////////////////////////////////////////////////////////////
189 // Variables
190
191 NETPLAY NetPlay;
192 PLAYER_IP *IPlist = nullptr;
193 static int IPlistLast = 0;
194 static bool allow_joining = false;
195 static bool server_not_there = false;
196 static GAMESTRUCT gamestruct;
197
198 // update flags
199 bool netPlayersUpdated;
200
201
202 /**
203 * Socket used for these purposes:
204 * * Host a game, be a server.
205 * * Connect to the lobby server.
206 * * Join a server for a game.
207 */
208 static Socket *tcp_socket = nullptr; ///< Socket used to talk to a lobby server (hosts also seem to temporarily act as lobby servers while the client negotiates joining the game), or to listen for clients (if we're the host, in which case we use rs_socket (declaration hidden somewhere inside a function) to talk to the lobby server).
209
210 static Socket *bsocket = nullptr; ///< Socket used to talk to the host (clients only). If bsocket != NULL, then tcp_socket == NULL.
211 static Socket *connected_bsocket[MAX_CONNECTED_PLAYERS] = { nullptr }; ///< Sockets used to talk to clients (host only).
212 static SocketSet *socket_set = nullptr;
213
214 WZ_THREAD *upnpdiscover;
215
216 static struct UPNPUrls urls;
217 static struct IGDdatas data;
218
219 // local ip address
220 static char lanaddr[40];
221 static char externalIPAddress[40];
222 /**
223 * Used for connections with clients.
224 */
225 static Socket *tmp_socket[MAX_TMP_SOCKETS] = { nullptr }; ///< Sockets used to talk to clients which have not yet been assigned a player number (host only).
226
227 static SocketSet *tmp_socket_set = nullptr;
228 static int32_t NetGameFlags[4] = { 0, 0, 0, 0 };
229 char iptoconnect[PATH_MAX] = "\0"; // holds IP/hostname from command line
230
231 static NETSTATS nStats = {{0, 0}, {0, 0}, {0, 0}};
232 static NETSTATS nStatsLastSec = {{0, 0}, {0, 0}, {0, 0}};
233 static NETSTATS nStatsSecondLastSec = {{0, 0}, {0, 0}, {0, 0}};
234 static const NETSTATS nZeroStats = {{0, 0}, {0, 0}, {0, 0}};
235 static int nStatsLastUpdateTime = 0;
236
237 unsigned NET_PlayerConnectionStatus[CONNECTIONSTATUS_NORMAL][MAX_PLAYERS];
238
239 static LobbyServerConnectionHandler lobbyConnectionHandler;
240
241 // ////////////////////////////////////////////////////////////////////////////
242 /************************************************************************************
243 ** NOTE (!) Increment NETCODE_VERSION_MINOR on each release.
244 **
245 ************************************************************************************
246 **/
247 static char const *versionString = version_getVersionString();
248
249 #include "lib/netplay/netplay_config.h"
250
NETPLAY()251 NETPLAY::NETPLAY()
252 {
253 players.resize(MAX_PLAYERS);
254 playerReferences.resize(MAX_PLAYERS);
255 for (auto i = 0; i < MAX_PLAYERS; i++)
256 {
257 playerReferences[i] = std::make_shared<PlayerReference>(i);
258 }
259 }
260
NETisCorrectVersion(uint32_t game_version_major,uint32_t game_version_minor)261 bool NETisCorrectVersion(uint32_t game_version_major, uint32_t game_version_minor)
262 {
263 return (uint32_t)NETCODE_VERSION_MAJOR == game_version_major && (uint32_t)NETCODE_VERSION_MINOR == game_version_minor;
264 }
265
NETisGreaterVersion(uint32_t game_version_major,uint32_t game_version_minor)266 bool NETisGreaterVersion(uint32_t game_version_major, uint32_t game_version_minor)
267 {
268 return (game_version_major > NETCODE_VERSION_MAJOR) || ((game_version_major == NETCODE_VERSION_MAJOR) && (game_version_minor > NETCODE_VERSION_MINOR));
269 }
270
NETGetMajorVersion()271 int NETGetMajorVersion()
272 {
273 return NETCODE_VERSION_MAJOR;
274 }
275
NETGetMinorVersion()276 int NETGetMinorVersion()
277 {
278 return NETCODE_VERSION_MINOR;
279 }
280
NETGameIsLocked()281 bool NETGameIsLocked()
282 {
283 return NetPlay.GamePassworded;
284 }
285
286 // Sets if the game is password protected or not
NETGameLocked(bool flag)287 void NETGameLocked(bool flag)
288 {
289 NetPlay.GamePassworded = flag;
290 bool flagChanged = gamestruct.privateGame != (uint32_t)flag;
291 gamestruct.privateGame = flag;
292 if (allow_joining && NetPlay.isHost && flagChanged)
293 {
294 debug(LOG_NET, "Updating game locked status.");
295 NETregisterServer(WZ_SERVER_UPDATE);
296 }
297 if (flagChanged)
298 {
299 ActivityManager::instance().updateMultiplayGameData(game, ingame, NETGameIsLocked());
300 }
301 NETlogEntry("Password is", SYNC_FLAG, NetPlay.GamePassworded);
302 debug(LOG_NET, "Passworded game is %s", NetPlay.GamePassworded ? "TRUE" : "FALSE");
303 }
304
NETsetLobbyOptField(const char * Value,const NET_LOBBY_OPT_FIELD Field)305 void NETsetLobbyOptField(const char *Value, const NET_LOBBY_OPT_FIELD Field)
306 {
307 switch (Field)
308 {
309 case NET_LOBBY_OPT_FIELD::GNAME:
310 sstrcpy(gamestruct.name, Value);
311 break;
312 case NET_LOBBY_OPT_FIELD::MAPNAME:
313 sstrcpy(gamestruct.mapname, formatGameName(Value).toUtf8().c_str());
314 break;
315 case NET_LOBBY_OPT_FIELD::HOSTNAME:
316 sstrcpy(gamestruct.hostname, Value);
317 break;
318 default:
319 debug(LOG_WARNING, "Invalid field specified for NETsetGameOptField()");
320 break;
321 }
322 }
323
324 // Sets the game password
NETsetGamePassword(const char * password)325 void NETsetGamePassword(const char *password)
326 {
327 sstrcpy(NetPlay.gamePassword, password);
328 debug(LOG_NET, "Password entered is: [%s]", NetPlay.gamePassword);
329 NETGameLocked(true);
330 }
331
332 // Resets the game password
NETresetGamePassword()333 void NETresetGamePassword()
334 {
335 sstrcpy(NetPlay.gamePassword, _("Enter password here"));
336 debug(LOG_NET, "password reset to 'Enter password here'");
337 NETGameLocked(false);
338 }
339
340 // *********** Socket with buffer that read NETMSGs ******************
341
NET_fillBuffer(Socket ** pSocket,SocketSet * pSocketSet,uint8_t * bufstart,int bufsize)342 static size_t NET_fillBuffer(Socket **pSocket, SocketSet *pSocketSet, uint8_t *bufstart, int bufsize)
343 {
344 Socket *socket = *pSocket;
345 ssize_t size;
346
347 if (!socketReadReady(socket))
348 {
349 return 0;
350 }
351
352 size_t rawBytes;
353 size = readNoInt(socket, bufstart, bufsize, &rawBytes);
354
355 if ((size != 0 || !socketReadDisconnected(socket)) && size != SOCKET_ERROR)
356 {
357 nStats.rawBytes.received += rawBytes;
358 nStats.uncompressedBytes.received += size;
359 nStats.packets.received += 1;
360
361 return size;
362 }
363 else
364 {
365 if (size == 0)
366 {
367 debug(LOG_NET, "Connection closed from the other side");
368 NETlogEntry("Connection closed from the other side..", SYNC_FLAG, selectedPlayer);
369 }
370 else
371 {
372 debug(LOG_NET, "%s tcp_socket %p is now invalid", strSockError(getSockErr()), static_cast<void *>(socket));
373 }
374
375 // an error occurred, or the remote host has closed the connection.
376 if (pSocketSet != nullptr)
377 {
378 SocketSet_DelSocket(pSocketSet, socket);
379 }
380
381 ASSERT(size <= bufsize, "Socket buffer is too small!");
382
383 if (size > bufsize)
384 {
385 debug(LOG_ERROR, "Fatal connection error: buffer size of (%d) was too small, current byte count was %ld", bufsize, (long)size);
386 NETlogEntry("Fatal connection error: buffer size was too small!", SYNC_FLAG, selectedPlayer);
387 }
388 if (bsocket == socket)
389 {
390 debug(LOG_NET, "Host connection was lost!");
391 NETlogEntry("Host connection was lost!", SYNC_FLAG, selectedPlayer);
392 bsocket = nullptr;
393 //Game is pretty much over --should just end everything when HOST dies.
394 NetPlay.isHostAlive = false;
395 ingame.localJoiningInProgress = false;
396 setLobbyError(ERROR_HOSTDROPPED);
397 NETclose();
398 return 0;
399 }
400 socketClose(socket);
401 *pSocket = nullptr;
402 }
403
404 return 0;
405 }
406
playersPerTeam()407 static int playersPerTeam()
408 {
409 for (int v = game.maxPlayers - 1; v > 1; --v)
410 {
411 if (game.maxPlayers%v == 0)
412 {
413 return v;
414 }
415 }
416 return 1;
417 }
418
419 /**
420 * Resets network properties of a player to safe defaults. Player slots should always be in this state
421 * before attemtping to assign a connectign player to it.
422 *
423 * Used to reset the player slot in NET_InitPlayer and to reset players slot without modifying ai/team/
424 * position configuration for the players.
425 */
initPlayerNetworkProps(int playerIndex)426 static void initPlayerNetworkProps(int playerIndex)
427 {
428 NetPlay.players[playerIndex].allocated = false;
429 NetPlay.players[playerIndex].autoGame = false;
430 NetPlay.players[playerIndex].heartattacktime = 0;
431 NetPlay.players[playerIndex].heartbeat = true; // we always start with a heartbeat
432 NetPlay.players[playerIndex].kick = false;
433 NetPlay.players[playerIndex].ready = false;
434
435 NetPlay.players[playerIndex].wzFiles.clear();
436 ingame.JoiningInProgress[playerIndex] = false;
437 }
438
NET_InitPlayer(int i,bool initPosition,bool initTeams)439 void NET_InitPlayer(int i, bool initPosition, bool initTeams)
440 {
441 initPlayerNetworkProps(i);
442
443 NetPlay.players[i].difficulty = AIDifficulty::DISABLED;
444 if (ingame.localJoiningInProgress)
445 {
446 // only clear name outside of games.
447 NetPlay.players[i].name[0] = '\0';
448 }
449 if (initPosition)
450 {
451 setPlayerColour(i, i);
452 NetPlay.players[i].position = i;
453 NetPlay.players[i].team = initTeams && i < game.maxPlayers? i/playersPerTeam() : i;
454 }
455 if (NetPlay.bComms)
456 {
457 NetPlay.players[i].ai = AI_OPEN;
458 }
459 else
460 {
461 NetPlay.players[i].ai = 0;
462 }
463 NetPlay.players[i].faction = FACTION_NORMAL;
464 }
465
NET_numHumanPlayers(void)466 uint8_t NET_numHumanPlayers(void)
467 {
468 uint8_t RetVal = 0;
469 for (uint8_t Inc = 0; Inc < MAX_PLAYERS; ++Inc)
470 {
471 if (NetPlay.players[Inc].allocated) ++RetVal;
472 }
473
474 return RetVal;
475 }
476
NET_getHumanPlayers(void)477 std::vector<uint8_t> NET_getHumanPlayers(void)
478 {
479 std::vector<uint8_t> RetVal;
480 RetVal.reserve(MAX_PLAYERS);
481
482 for (uint8_t Inc = 0; Inc < MAX_PLAYERS; ++Inc)
483 {
484 if (NetPlay.players[Inc].allocated) RetVal.push_back(Inc);
485 }
486
487 return RetVal;
488 }
489
NET_InitPlayers(bool initTeams)490 void NET_InitPlayers(bool initTeams)
491 {
492 for (unsigned i = 0; i < MAX_CONNECTED_PLAYERS; ++i)
493 {
494 NET_InitPlayer(i, true, initTeams);
495 NetPlay.players[i].name[0] = '\0';
496 NETinitQueue(NETnetQueue(i));
497 }
498 NETinitQueue(NETbroadcastQueue());
499
500 NetPlay.hostPlayer = NET_HOST_ONLY; // right now, host starts always at index zero
501 NetPlay.playercount = 0;
502 NetPlay.wzFiles.clear();
503 debug(LOG_NET, "Players initialized");
504 }
505
NETSendNPlayerInfoTo(uint32_t * index,uint32_t indexLen,unsigned to)506 static void NETSendNPlayerInfoTo(uint32_t *index, uint32_t indexLen, unsigned to)
507 {
508 NETbeginEncode(NETnetQueue(to), NET_PLAYER_INFO);
509 NETuint32_t(&indexLen);
510 for (unsigned n = 0; n < indexLen; ++n)
511 {
512 debug(LOG_NET, "sending player's (%u) info to all players", index[n]);
513 NETlogEntry(" sending player's info to all players", SYNC_FLAG, index[n]);
514 NETuint32_t(&index[n]);
515 NETbool(&NetPlay.players[index[n]].allocated);
516 NETbool(&NetPlay.players[index[n]].heartbeat);
517 NETbool(&NetPlay.players[index[n]].kick);
518 NETstring(NetPlay.players[index[n]].name, sizeof(NetPlay.players[index[n]].name));
519 NETuint32_t(&NetPlay.players[index[n]].heartattacktime);
520 NETint32_t(&NetPlay.players[index[n]].colour);
521 NETint32_t(&NetPlay.players[index[n]].position);
522 NETint32_t(&NetPlay.players[index[n]].team);
523 NETbool(&NetPlay.players[index[n]].ready);
524 NETint8_t(&NetPlay.players[index[n]].ai);
525 NETint8_t(reinterpret_cast<int8_t*>(&NetPlay.players[index[n]].difficulty));
526 NETuint8_t(reinterpret_cast<uint8_t *>(&NetPlay.players[index[n]].faction));
527 }
528 NETend();
529 ActivityManager::instance().updateMultiplayGameData(game, ingame, NETGameIsLocked());
530 }
531
NETSendPlayerInfoTo(uint32_t index,unsigned to)532 static void NETSendPlayerInfoTo(uint32_t index, unsigned to)
533 {
534 NETSendNPlayerInfoTo(&index, 1, to);
535 }
536
NETsendPlayerInfo(uint32_t index)537 static void NETsendPlayerInfo(uint32_t index)
538 {
539 NETSendPlayerInfoTo(index, NET_HOST_ONLY);
540 }
541
NETSendAllPlayerInfoTo(unsigned to)542 static void NETSendAllPlayerInfoTo(unsigned to)
543 {
544 static uint32_t indices[MAX_PLAYERS];
545 for (int i = 0; i < MAX_PLAYERS; ++i)
546 {
547 indices[i] = i;
548 }
549 ASSERT_OR_RETURN(, NetPlay.isHost == true, "Invalid call for non-host");
550
551 NETSendNPlayerInfoTo(indices, ARRAY_SIZE(indices), to);
552 }
553
NETBroadcastTwoPlayerInfo(uint32_t index1,uint32_t index2)554 void NETBroadcastTwoPlayerInfo(uint32_t index1, uint32_t index2)
555 {
556 uint32_t indices[2] = {index1, index2};
557 NETSendNPlayerInfoTo(indices, 2, NET_ALL_PLAYERS);
558 }
559
NETBroadcastPlayerInfo(uint32_t index)560 void NETBroadcastPlayerInfo(uint32_t index)
561 {
562 NETSendPlayerInfoTo(index, NET_ALL_PLAYERS);
563 }
564
NET_CreatePlayer(char const * name,bool forceTakeLowestAvailablePlayerNumber=false)565 static int NET_CreatePlayer(char const *name, bool forceTakeLowestAvailablePlayerNumber = false)
566 {
567 int index = -1;
568 int position = INT_MAX;
569 // Only look for spots up to the max players allowed on the map.
570 for (int i = 0; i < game.maxPlayers; ++i)
571 {
572 PLAYER const &p = NetPlay.players[i];
573 if (!p.allocated && p.ai == AI_OPEN && p.position < position)
574 {
575 index = i;
576 position = p.position;
577 if (forceTakeLowestAvailablePlayerNumber)
578 {
579 break;
580 }
581 }
582 }
583
584 if (index == -1)
585 {
586 debug(LOG_ERROR, "Could not find place for player %s", name);
587 NETlogEntry("Could not find a place for player!", SYNC_FLAG, index);
588 return -1;
589 }
590
591 char buf[250] = {'\0'};
592
593 ssprintf(buf, "A new player has been created. Player, %s, is set to slot %u", name, index);
594 debug(LOG_NET, "%s", buf);
595 NETlogEntry(buf, SYNC_FLAG, index);
596 NET_InitPlayer(index, false); // re-init everything
597 NetPlay.players[index].allocated = true;
598 sstrcpy(NetPlay.players[index].name, name);
599 ++NetPlay.playercount;
600 ++sync_counter.joins;
601 return index;
602 }
603
NET_DestroyPlayer(unsigned int index,bool suppressActivityUpdates=false)604 static void NET_DestroyPlayer(unsigned int index, bool suppressActivityUpdates = false)
605 {
606 debug(LOG_NET, "Freeing slot %u for a new player", index);
607 NETlogEntry("Freeing slot for a new player.", SYNC_FLAG, index);
608 bool wasAllocated = NetPlay.players[index].allocated;
609 if (NetPlay.players[index].allocated)
610 {
611 NetPlay.players[index].allocated = false;
612 NetPlay.playercount--;
613 gamestruct.desc.dwCurrentPlayers = NetPlay.playercount;
614 if (allow_joining && NetPlay.isHost)
615 {
616 // Update player count in the lobby by disconnecting
617 // and reconnecting
618 NETregisterServer(WZ_SERVER_UPDATE);
619 }
620 }
621 NET_InitPlayer(index, false); // reinitialize
622 if (wasAllocated && !suppressActivityUpdates)
623 {
624 ActivityManager::instance().updateMultiplayGameData(game, ingame, NETGameIsLocked());
625 }
626 }
627
628 /**
629 * @note Connection dropped. Handle it gracefully.
630 * \param index
631 */
NETplayerClientDisconnect(uint32_t index)632 static void NETplayerClientDisconnect(uint32_t index)
633 {
634 if (!NetPlay.isHost)
635 {
636 ASSERT(false, "Host only routine detected for client!");
637 return;
638 }
639
640 if (connected_bsocket[index])
641 {
642 debug(LOG_NET, "Player (%u) has left unexpectedly, closing socket %p", index, static_cast<void *>(connected_bsocket[index]));
643
644 NETplayerLeaving(index);
645
646 NETlogEntry("Player has left unexpectedly.", SYNC_FLAG, index);
647 // Announce to the world. This was really icky, because we may have been calling the send
648 // function recursively. We really ought to have had a send queue, and now we finally do...
649 if (ingame.localJoiningInProgress) // Only if game hasn't actually started yet.
650 {
651 NETbeginEncode(NETbroadcastQueue(), NET_PLAYER_DROPPED);
652 NETuint32_t(&index);
653 NETend();
654 }
655 }
656 else
657 {
658 debug(LOG_ERROR, "Player (%u) has left unexpectedly - but socket already closed?", index);
659 }
660 }
661
662 /**
663 * @note When a player leaves nicely (ie, we got a NET_PLAYER_LEAVING
664 * message), we clean up the socket that we used.
665 * \param index
666 */
NETplayerLeaving(UDWORD index)667 static void NETplayerLeaving(UDWORD index)
668 {
669 if (connected_bsocket[index])
670 {
671 debug(LOG_NET, "Player (%u) has left, closing socket %p", index, static_cast<void *>(connected_bsocket[index]));
672 NETlogEntry("Player has left nicely.", SYNC_FLAG, index);
673
674 // Although we can get a error result from DelSocket, it don't really matter here.
675 SocketSet_DelSocket(socket_set, connected_bsocket[index]);
676 socketClose(connected_bsocket[index]);
677 connected_bsocket[index] = nullptr;
678 }
679 else
680 {
681 debug(LOG_NET, "Player (%u) has left nicely, socket already closed?", index);
682 }
683 sync_counter.left++;
684 MultiPlayerLeave(index); // more cleanup
685 if (ingame.localJoiningInProgress) // Only if game hasn't actually started yet.
686 {
687 NET_DestroyPlayer(index); // sets index player's array to false
688 resetReadyStatus(false); // reset ready status for all players
689 }
690 }
691
692 /**
693 * @note When a player's connection is broken we broadcast the NET_PLAYER_DROPPED
694 * message.
695 * \param index
696 */
NETplayerDropped(UDWORD index)697 static void NETplayerDropped(UDWORD index)
698 {
699 uint32_t id = index;
700
701 if (!NetPlay.isHost)
702 {
703 ASSERT(false, "Host only routine detected for client!");
704 return;
705 }
706
707 sync_counter.drops++;
708 MultiPlayerLeave(id); // more cleanup
709 if (ingame.localJoiningInProgress) // Only if game hasn't actually started yet.
710 {
711 // Send message type specifically for dropped / disconnects
712 NETbeginEncode(NETbroadcastQueue(), NET_PLAYER_DROPPED);
713 NETuint32_t(&id);
714 NETend();
715 debug(LOG_INFO, "sending NET_PLAYER_DROPPED for player %d", id);
716 NET_DestroyPlayer(id); // just clears array
717 resetReadyStatus(false); // reset ready status for all players
718 }
719
720 NETsetPlayerConnectionStatus(CONNECTIONSTATUS_PLAYER_DROPPED, id);
721 }
722
723 /**
724 * @note Cleanup for when a player is kicked.
725 * \param index
726 */
NETplayerKicked(UDWORD index)727 void NETplayerKicked(UDWORD index)
728 {
729 ASSERT_OR_RETURN(, index < MAX_PLAYERS, "NETplayerKicked invalid player_id: (%" PRIu32")", index);
730
731 // kicking a player counts as "leaving nicely", since "nicely" in this case
732 // simply means "there wasn't a connection error."
733 debug(LOG_INFO, "Player %u was kicked.", index);
734 sync_counter.kicks++;
735 NETlogEntry("Player was kicked.", SYNC_FLAG, index);
736 if (NetPlay.isHost && NetPlay.players[index].allocated)
737 {
738 addToBanList(NetPlay.players[index].IPtextAddress, NetPlay.players[index].name);
739 }
740 NETplayerLeaving(index); // need to close socket for the player that left.
741 NETsetPlayerConnectionStatus(CONNECTIONSTATUS_PLAYER_LEAVING, index);
742 }
743
744 // ////////////////////////////////////////////////////////////////////////
745 // rename the local player
NETchangePlayerName(UDWORD index,char * newName)746 bool NETchangePlayerName(UDWORD index, char *newName)
747 {
748 if (!NetPlay.bComms)
749 {
750 sstrcpy(NetPlay.players[0].name, newName);
751 return true;
752 }
753 debug(LOG_NET, "Requesting a change of player name for pid=%u to %s", index, newName);
754 NETlogEntry("Player wants a name change.", SYNC_FLAG, index);
755
756 sstrcpy(NetPlay.players[index].name, newName);
757 if (NetPlay.isHost)
758 {
759 NETSendAllPlayerInfoTo(NET_ALL_PLAYERS);
760 }
761 else
762 {
763 NETsendPlayerInfo(index);
764 }
765
766 return true;
767 }
768
NETfixDuplicatePlayerNames()769 void NETfixDuplicatePlayerNames()
770 {
771 char name[StringSize];
772 unsigned i, j, pass;
773 for (i = 1; i != MAX_PLAYERS; ++i)
774 {
775 sstrcpy(name, NetPlay.players[i].name);
776 if (name[0] == '\0' || !NetPlay.players[i].allocated)
777 {
778 continue; // Ignore empty names.
779 }
780 for (pass = 0; pass != 101; ++pass)
781 {
782 if (pass != 0)
783 {
784 ssprintf(name, "%s_%X", NetPlay.players[i].name, pass + 1);
785 }
786
787 for (j = 0; j != i; ++j)
788 {
789 if (strcmp(name, NetPlay.players[j].name) == 0)
790 {
791 break; // Duplicate name.
792 }
793 }
794
795 if (i == j)
796 {
797 break; // Unique name.
798 }
799 }
800 if (pass != 0)
801 {
802 NETchangePlayerName(i, name);
803 }
804 }
805 }
806
807 // ////////////////////////////////////////////////////////////////////////
808 // return one of the four user flags in the current sessiondescription.
NETgetGameFlags(UDWORD flag)809 SDWORD NETgetGameFlags(UDWORD flag)
810 {
811 if (flag < 1 || flag > 4)
812 {
813 return 0;
814 }
815 else
816 {
817 return NetGameFlags[flag - 1];
818 }
819 }
820
NETsendGameFlags()821 static void NETsendGameFlags()
822 {
823 debug(LOG_NET, "sending game flags");
824 NETbeginEncode(NETbroadcastQueue(), NET_GAME_FLAGS);
825 {
826 // Send the amount of game flags we're about to send
827 uint8_t i, count = ARRAY_SIZE(NetGameFlags);
828 NETuint8_t(&count);
829
830 // Send over all game flags
831 for (i = 0; i < count; ++i)
832 {
833 NETint32_t(&NetGameFlags[i]);
834 }
835 }
836 NETend();
837 }
838
839 // ////////////////////////////////////////////////////////////////////////
840 // Set a game flag
NETsetGameFlags(UDWORD flag,SDWORD value)841 bool NETsetGameFlags(UDWORD flag, SDWORD value)
842 {
843 if (!NetPlay.bComms)
844 {
845 return true;
846 }
847
848 if (flag > 0 && flag < 5)
849 {
850 return (NetGameFlags[flag - 1] = value);
851 }
852
853 NETsendGameFlags();
854
855 return true;
856 }
857
858 /**
859 * @note \c game is being sent to the master server (if hosting)
860 * The implementation of NETsendGAMESTRUCT <em>must</em> guarantee to
861 * pack it in network byte order (big-endian).
862 *
863 * @return true on success, false when a socket error has occurred
864 *
865 * @see GAMESTRUCT,NETrecvGAMESTRUCT
866 */
NETsendGAMESTRUCT(Socket * sock,const GAMESTRUCT * ourgamestruct)867 static bool NETsendGAMESTRUCT(Socket *sock, const GAMESTRUCT *ourgamestruct)
868 {
869 // A buffer that's guaranteed to have the correct size (i.e. it
870 // circumvents struct padding, which could pose a problem). Initialise
871 // to zero so that we can be sure we're not sending any (undefined)
872 // memory content across the network.
873 char buf[sizeof(ourgamestruct->GAMESTRUCT_VERSION) + sizeof(ourgamestruct->name) + sizeof(ourgamestruct->desc.host) + (sizeof(int32_t) * 8) +
874 sizeof(ourgamestruct->secondaryHosts) + sizeof(ourgamestruct->extra) + sizeof(ourgamestruct->hostPort) + sizeof(ourgamestruct->mapname) + sizeof(ourgamestruct->hostname) + sizeof(ourgamestruct->versionstring) +
875 sizeof(ourgamestruct->modlist) + (sizeof(uint32_t) * 9) ] = { 0 };
876 char *buffer = buf;
877 unsigned int i;
878 ssize_t result;
879
880 auto push32 = [&](uint32_t value) {
881 uint32_t swapped = htonl(value);
882 memcpy(buffer, &swapped, sizeof(swapped));
883 buffer += sizeof(swapped);
884 };
885
886 auto push16 = [&](uint16_t value) {
887 uint16_t swapped = htons(value);
888 memcpy(buffer, &swapped, sizeof(swapped));
889 buffer += sizeof(swapped);
890 };
891
892 // Now dump the data into the buffer
893 // Copy 32bit large big endian numbers
894 push32(ourgamestruct->GAMESTRUCT_VERSION);
895
896 // Copy a string
897 strlcpy(buffer, ourgamestruct->name, sizeof(ourgamestruct->name));
898 buffer += sizeof(ourgamestruct->name);
899
900 // Copy 32bit large big endian numbers
901 push32(ourgamestruct->desc.dwSize);
902 push32(ourgamestruct->desc.dwFlags);
903
904 // Copy yet another string
905 strlcpy(buffer, ourgamestruct->desc.host, sizeof(ourgamestruct->desc.host));
906 buffer += sizeof(ourgamestruct->desc.host);
907
908 // Copy 32bit large big endian numbers
909 push32(ourgamestruct->desc.dwMaxPlayers);
910 push32(ourgamestruct->desc.dwCurrentPlayers);
911 for (i = 0; i < ARRAY_SIZE(ourgamestruct->desc.dwUserFlags); ++i)
912 {
913 push32(ourgamestruct->desc.dwUserFlags[i]);
914 }
915
916 // Copy a string
917 for (i = 0; i < ARRAY_SIZE(ourgamestruct->secondaryHosts); ++i)
918 {
919 strlcpy(buffer, ourgamestruct->secondaryHosts[i], sizeof(ourgamestruct->secondaryHosts[i]));
920 buffer += sizeof(ourgamestruct->secondaryHosts[i]);
921 }
922
923 // Copy a string
924 strlcpy(buffer, ourgamestruct->extra, sizeof(ourgamestruct->extra));
925 buffer += sizeof(ourgamestruct->extra);
926
927 // Copy 16bit large big endian number
928 push16(ourgamestruct->hostPort);
929
930 // Copy a string
931 strlcpy(buffer, ourgamestruct->mapname, sizeof(ourgamestruct->mapname));
932 buffer += sizeof(ourgamestruct->mapname);
933
934 // Copy a string
935 strlcpy(buffer, ourgamestruct->hostname, sizeof(ourgamestruct->hostname));
936 buffer += sizeof(ourgamestruct->hostname);
937
938 // Copy a string
939 strlcpy(buffer, ourgamestruct->versionstring, sizeof(ourgamestruct->versionstring));
940 buffer += sizeof(ourgamestruct->versionstring);
941
942 // Copy a string
943 strlcpy(buffer, ourgamestruct->modlist, sizeof(ourgamestruct->modlist));
944 buffer += sizeof(ourgamestruct->modlist);
945
946 // Copy 32bit large big endian numbers
947 push32(ourgamestruct->game_version_major);
948
949 // Copy 32bit large big endian numbers
950 push32(ourgamestruct->game_version_minor);
951
952 // Copy 32bit large big endian numbers
953 push32(ourgamestruct->privateGame);
954
955 // Copy 32bit large big endian numbers
956 push32(ourgamestruct->pureMap);
957
958 // Copy 32bit large big endian numbers
959 push32(ourgamestruct->Mods);
960
961 // Copy 32bit large big endian numbers
962 push32(ourgamestruct->gameId);
963
964 // Copy 32bit large big endian numbers
965 push32(ourgamestruct->limits);
966
967 // Copy 32bit large big endian numbers
968 push32(ourgamestruct->future3);
969
970 // Copy 32bit large big endian numbers
971 push32(ourgamestruct->future4);
972
973 debug(LOG_NET, "sending GAMESTRUCT, size: %u", (unsigned int)sizeof(buf));
974
975 // Send over the GAMESTRUCT
976 result = writeAll(sock, buf, sizeof(buf));
977 if (result == SOCKET_ERROR)
978 {
979 const int err = getSockErr();
980
981 // If packet could not be sent, we should inform user of the error.
982 debug(LOG_ERROR, "Failed to send GAMESTRUCT. Reason: %s", strSockError(getSockErr()));
983 debug(LOG_ERROR, "Please make sure TCP ports %u & %u are open!", masterserver_port, gameserver_port);
984
985 setSockErr(err);
986 return false;
987 }
988
989 return true;
990 }
991
992 /**
993 * @note \c game is being retrieved from the master server (if browsing the
994 * lobby). The implementation of NETrecvGAMESTRUCT should assume the data
995 * to be packed in network byte order (big-endian).
996 *
997 * @see GAMESTRUCT,NETsendGAMESTRUCT
998 */
NETrecvGAMESTRUCT(Socket * sock,GAMESTRUCT * ourgamestruct)999 static bool NETrecvGAMESTRUCT(Socket *sock, GAMESTRUCT *ourgamestruct)
1000 {
1001 // A buffer that's guaranteed to have the correct size (i.e. it
1002 // circumvents struct padding, which could pose a problem).
1003 char buf[sizeof(ourgamestruct->GAMESTRUCT_VERSION) + sizeof(ourgamestruct->name) + sizeof(ourgamestruct->desc.host) + (sizeof(int32_t) * 8) +
1004 sizeof(ourgamestruct->secondaryHosts) + sizeof(ourgamestruct->extra) + sizeof(ourgamestruct->hostPort) + sizeof(ourgamestruct->mapname) + sizeof(ourgamestruct->hostname) + sizeof(ourgamestruct->versionstring) +
1005 sizeof(ourgamestruct->modlist) + (sizeof(uint32_t) * 9) ] = { 0 };
1006 char *buffer = buf;
1007 unsigned int i;
1008 ssize_t result = 0;
1009
1010 auto pop32 = [&]() -> uint32_t {
1011 uint32_t value = 0;
1012 memcpy(&value, buffer, sizeof(value));
1013 value = ntohl(value);
1014 buffer += sizeof(value);
1015 return value;
1016 };
1017
1018 auto pop16 = [&]() -> uint16_t {
1019 uint16_t value = 0;
1020 memcpy(&value, buffer, sizeof(value));
1021 value = ntohs(value);
1022 buffer += sizeof(value);
1023 return value;
1024 };
1025
1026 // Read a GAMESTRUCT from the connection
1027 result = readAll(sock, buf, sizeof(buf), NET_TIMEOUT_DELAY);
1028 bool failed = false;
1029 if (result == SOCKET_ERROR)
1030 {
1031 debug(LOG_ERROR, "Lobby server connection error: %s", strSockError(getSockErr()));
1032 failed = true;
1033 }
1034 else if ((unsigned)result != sizeof(buf))
1035 {
1036 debug(LOG_ERROR, "GAMESTRUCT recv timed out; received %d bytes; expecting %d", (int)result, (int)sizeof(buf));
1037 failed = true;
1038 }
1039 if (failed)
1040 {
1041 // caller handles invalidating and closing tcp_socket
1042 return false;
1043 }
1044
1045 // Now dump the data into the game struct
1046 // Copy 32bit large big endian numbers
1047 ourgamestruct->GAMESTRUCT_VERSION = pop32();
1048 // Copy a string
1049 sstrcpy(ourgamestruct->name, buffer);
1050 buffer += sizeof(ourgamestruct->name);
1051
1052 // Copy 32bit large big endian numbers
1053 ourgamestruct->desc.dwSize = pop32();
1054 ourgamestruct->desc.dwFlags = pop32();
1055
1056 // Copy yet another string
1057 sstrcpy(ourgamestruct->desc.host, buffer);
1058 buffer += sizeof(ourgamestruct->desc.host);
1059
1060 // Copy 32bit large big endian numbers
1061 ourgamestruct->desc.dwMaxPlayers = pop32();
1062 ourgamestruct->desc.dwCurrentPlayers = pop32();
1063 for (i = 0; i < ARRAY_SIZE(ourgamestruct->desc.dwUserFlags); ++i)
1064 {
1065 ourgamestruct->desc.dwUserFlags[i] = pop32();
1066 }
1067
1068 // Copy a string
1069 for (i = 0; i < ARRAY_SIZE(ourgamestruct->secondaryHosts); ++i)
1070 {
1071 sstrcpy(ourgamestruct->secondaryHosts[i], buffer);
1072 buffer += sizeof(ourgamestruct->secondaryHosts[i]);
1073 }
1074
1075 // Copy a string
1076 sstrcpy(ourgamestruct->extra, buffer);
1077 buffer += sizeof(ourgamestruct->extra);
1078
1079 // Copy 16-bit host port
1080 ourgamestruct->hostPort = pop16();
1081
1082 // Copy a string
1083 sstrcpy(ourgamestruct->mapname, buffer);
1084 buffer += sizeof(ourgamestruct->mapname);
1085
1086 // Copy a string
1087 sstrcpy(ourgamestruct->hostname, buffer);
1088 buffer += sizeof(ourgamestruct->hostname);
1089
1090 // Copy a string
1091 sstrcpy(ourgamestruct->versionstring, buffer);
1092 buffer += sizeof(ourgamestruct->versionstring);
1093
1094 // Copy a string
1095 sstrcpy(ourgamestruct->modlist, buffer);
1096 buffer += sizeof(ourgamestruct->modlist);
1097
1098 // Copy 32bit large big endian numbers
1099 ourgamestruct->game_version_major = pop32();
1100 ourgamestruct->game_version_minor = pop32();
1101 ourgamestruct->privateGame = pop32();
1102 ourgamestruct->pureMap = pop32();
1103 ourgamestruct->Mods = pop32();
1104 ourgamestruct->gameId = pop32();
1105 ourgamestruct->limits = pop32();
1106 ourgamestruct->future3 = pop32();
1107 ourgamestruct->future4 = pop32();
1108
1109 debug(LOG_NET, "received GAMESTRUCT");
1110
1111 return true;
1112 }
1113
1114 // This function is run in its own thread! Do not call any non-threadsafe functions!
upnp_init(std::atomic_int & retval)1115 static void upnp_init(std::atomic_int &retval)
1116 {
1117 struct UPNPDev *devlist;
1118 struct UPNPDev *dev;
1119 char *descXML;
1120 int result = 0;
1121 int descXMLsize = 0;
1122 memset(&urls, 0, sizeof(struct UPNPUrls));
1123 memset(&data, 0, sizeof(struct IGDdatas));
1124
1125 debug(LOG_NET, "Searching for UPnP devices for automatic port forwarding...");
1126 #if defined(MINIUPNPC_API_VERSION) && (MINIUPNPC_API_VERSION >= 14)
1127 devlist = upnpDiscover(3000, nullptr, nullptr, 0, 0, 2, &result);
1128 #else
1129 devlist = upnpDiscover(3000, nullptr, nullptr, 0, 0, &result);
1130 #endif
1131 debug(LOG_NET, "UPnP device search finished.");
1132
1133 if (devlist)
1134 {
1135 dev = devlist;
1136 while (dev)
1137 {
1138 if (strstr(dev->st, "InternetGatewayDevice"))
1139 {
1140 break;
1141 }
1142 dev = dev->pNext;
1143 }
1144 if (!dev)
1145 {
1146 dev = devlist; /* defaulting to first device */
1147 }
1148
1149 debug(LOG_NET, "UPnP device found: %s %s\n", dev->descURL, dev->st);
1150
1151 #if defined(MINIUPNPC_API_VERSION) && (MINIUPNPC_API_VERSION >= 16)
1152 int status_code = -1;
1153 descXML = (char *)miniwget_getaddr(dev->descURL, &descXMLsize, lanaddr, sizeof(lanaddr), dev->scope_id, &status_code);
1154 if (status_code != 200)
1155 {
1156 if (descXML)
1157 {
1158 free(descXML);
1159 descXML = nullptr;
1160 }
1161 debug(LOG_NET, "HTTP error %d fetching: %s", status_code, (dev->descURL) ? dev->descURL : "");
1162 }
1163 #else
1164 descXML = (char *)miniwget_getaddr(dev->descURL, &descXMLsize, lanaddr, sizeof(lanaddr), dev->scope_id);
1165 #endif
1166 debug(LOG_NET, "LAN address: %s", lanaddr);
1167 if (descXML)
1168 {
1169 parserootdesc(descXML, descXMLsize, &data);
1170 free(descXML); descXML = nullptr;
1171 GetUPNPUrls(&urls, &data, dev->descURL, dev->scope_id);
1172 }
1173
1174 debug(LOG_NET, "UPnP device found: %s %s LAN address %s", dev->descURL, dev->st, lanaddr);
1175 freeUPNPDevlist(devlist);
1176
1177 if (!urls.controlURL || urls.controlURL[0] == '\0')
1178 {
1179 retval = UPNP_ERROR_CONTROL_NOT_AVAILABLE;
1180 }
1181 else
1182 {
1183 retval = UPNP_SUCCESS;
1184 }
1185 }
1186 else
1187 {
1188 retval = UPNP_ERROR_DEVICE_NOT_FOUND;
1189 }
1190 }
1191
upnp_add_redirect(int port)1192 static bool upnp_add_redirect(int port)
1193 {
1194 char port_str[16];
1195 char buf[512] = {'\0'};
1196 int r;
1197
1198 debug(LOG_NET, "upnp_add_redir(%d)", port);
1199 sprintf(port_str, "%d", port);
1200 r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype,
1201 port_str, port_str, lanaddr, "Warzone 2100", "TCP", nullptr, "0"); // "0" = lease time unlimited
1202 if (r != UPNPCOMMAND_SUCCESS)
1203 {
1204 ssprintf(buf, _("Could not open required port (%s) on (%s)"), port_str, lanaddr);
1205 debug(LOG_NET, "%s", buf);
1206 addConsoleMessage(buf, DEFAULT_JUSTIFY, NOTIFY_MESSAGE);
1207 // beware of writing a line too long, it screws up console line count. \n is safe for line split
1208 ssprintf(buf, "%s", _("You must manually configure your router & firewall to\n open port 2100 before you can host a game."));
1209 addConsoleMessage(buf, DEFAULT_JUSTIFY, NOTIFY_MESSAGE);
1210 return false;
1211 }
1212
1213 r = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, externalIPAddress);
1214 if (r != UPNPCOMMAND_SUCCESS)
1215 {
1216 ssprintf(externalIPAddress, "%s", "???");
1217 }
1218 ssprintf(buf, _("Game configured port (%s) correctly on (%s)\nYour external IP is %s"), port_str, lanaddr, externalIPAddress);
1219 addConsoleMessage(buf, DEFAULT_JUSTIFY, NOTIFY_MESSAGE);
1220
1221 return true;
1222 }
1223
upnp_rem_redirect(int port)1224 static int upnp_rem_redirect(int port)
1225 {
1226 char port_str[16];
1227 debug(LOG_NET, "upnp_rem_redir(%d)", port);
1228 sprintf(port_str, "%d", port);
1229 return UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port_str, "TCP", nullptr);
1230 }
1231
NETaddRedirects()1232 void NETaddRedirects()
1233 {
1234 if (upnp_add_redirect(gameserver_port))
1235 {
1236 debug(LOG_NET, "successful!");
1237 NetPlay.isUPNP_CONFIGURED = true;
1238 NetPlay.isUPNP_ERROR = false;
1239 }
1240 else
1241 {
1242 debug(LOG_NET, "failed!");
1243 NetPlay.isUPNP_CONFIGURED = false;
1244 NetPlay.isUPNP_ERROR = true;
1245 }
1246 }
1247
NETremRedirects()1248 void NETremRedirects()
1249 {
1250 debug(LOG_NET, "upnp is %d", NetPlay.isUPNP_CONFIGURED);
1251 if (NetPlay.isUPNP_CONFIGURED)
1252 {
1253 int result = upnp_rem_redirect(gameserver_port);
1254 if (!result)
1255 {
1256 debug(LOG_NET, "removed UPnP entry.");
1257 }
1258 else
1259 {
1260 debug(LOG_ERROR, "Failed to remove UPnP entry for the game. You must manually remove it from your router. (%d)", result);
1261 }
1262 }
1263 }
1264
NETdiscoverUPnPDevices()1265 void NETdiscoverUPnPDevices()
1266 {
1267 if (!NetPlay.isUPNP_CONFIGURED && NetPlay.isUPNP)
1268 {
1269 wz::thread t(upnp_init, std::ref(upnp_status));
1270 t.detach();
1271 }
1272 else if (!NetPlay.isUPNP)
1273 {
1274 debug(LOG_INFO, "UPnP detection disabled by user.");
1275 }
1276 }
1277
1278 // ////////////////////////////////////////////////////////////////////////
1279 // setup stuff
NETinit(bool bFirstCall)1280 int NETinit(bool bFirstCall)
1281 {
1282 debug(LOG_NET, "NETinit");
1283 upnp_status = 0;
1284 NETlogEntry("NETinit!", SYNC_FLAG, selectedPlayer);
1285 NET_InitPlayers(true);
1286
1287 SOCKETinit();
1288
1289 if (bFirstCall)
1290 {
1291 debug(LOG_NET, "NETPLAY: Init called, MORNIN'");
1292
1293 // NOTE NetPlay.isUPNP is already set in configuration.c!
1294 NetPlay.bComms = true;
1295 NetPlay.GamePassworded = false;
1296 NetPlay.ShowedMOTD = false;
1297 NetPlay.isHostAlive = false;
1298 NetPlay.HaveUpgrade = false;
1299 NetPlay.gamePassword[0] = '\0';
1300 NetPlay.MOTD = nullptr;
1301 sstrcpy(NetPlay.gamePassword, _("Enter password here"));
1302 NETstartLogging();
1303 }
1304
1305 if (NetPlay.MOTD)
1306 {
1307 free(NetPlay.MOTD);
1308 }
1309 NetPlay.MOTD = nullptr;
1310 NetPlay.ShowedMOTD = false;
1311 NetPlay.GamePassworded = false;
1312 memset(&sync_counter, 0x0, sizeof(sync_counter)); //clear counters
1313
1314 return 0;
1315 }
1316
1317 // ////////////////////////////////////////////////////////////////////////
1318 // SHUTDOWN THE CONNECTION.
NETshutdown()1319 int NETshutdown()
1320 {
1321 debug(LOG_NET, "NETshutdown");
1322 NETlogEntry("NETshutdown", SYNC_FLAG, selectedPlayer);
1323 if (NetPlay.bComms && NetPlay.isUPNP)
1324 {
1325 NETremRedirects();
1326 NetPlay.isUPNP_CONFIGURED = false;
1327 NetPlay.isUPNP_ERROR = false;
1328 }
1329 NETstopLogging();
1330 if (IPlist)
1331 {
1332 free(IPlist);
1333 }
1334 IPlist = nullptr;
1335 IPlistLast = 0;
1336 if (NetPlay.MOTD)
1337 {
1338 free(NetPlay.MOTD);
1339 }
1340 NetPlay.MOTD = nullptr;
1341 NETdeleteQueue();
1342 SOCKETshutdown();
1343
1344 // Reset net usage statistics.
1345 nStats = nZeroStats;
1346 nStatsLastSec = nZeroStats;
1347 nStatsSecondLastSec = nZeroStats;
1348
1349 return 0;
1350 }
1351
1352 // ////////////////////////////////////////////////////////////////////////
1353 //close the open game..
NETclose()1354 int NETclose()
1355 {
1356 unsigned int i;
1357
1358 // reset flag
1359 NetPlay.ShowedMOTD = false;
1360 NEThaltJoining();
1361
1362 debug(LOG_NET, "Terminating sockets.");
1363
1364 NetPlay.isHost = false;
1365 server_not_there = false;
1366 allow_joining = false;
1367
1368 for (i = 0; i < MAX_CONNECTED_PLAYERS; i++)
1369 {
1370 if (connected_bsocket[i])
1371 {
1372 debug(LOG_NET, "Closing connected_bsocket[%u], %p", i, static_cast<void *>(connected_bsocket[i]));
1373 socketClose(connected_bsocket[i]);
1374 connected_bsocket[i] = nullptr;
1375 }
1376 NET_DestroyPlayer(i, true);
1377 }
1378
1379 if (tmp_socket_set)
1380 {
1381 debug(LOG_NET, "Freeing tmp_socket_set %p", static_cast<void *>(tmp_socket_set));
1382 deleteSocketSet(tmp_socket_set);
1383 tmp_socket_set = nullptr;
1384 }
1385
1386 for (i = 0; i < MAX_TMP_SOCKETS; i++)
1387 {
1388 if (tmp_socket[i])
1389 {
1390 // FIXME: need SocketSet_DelSocket() as well, socket_set or tmp_socket_set?
1391 debug(LOG_NET, "Closing tmp_socket[%d] %p", i, static_cast<void *>(tmp_socket[i]));
1392 socketClose(tmp_socket[i]);
1393 tmp_socket[i] = nullptr;
1394 }
1395 }
1396
1397 if (socket_set)
1398 {
1399 // checking to make sure tcp_socket is still valid
1400 if (tcp_socket)
1401 {
1402 SocketSet_DelSocket(socket_set, tcp_socket);
1403 }
1404 if (bsocket)
1405 {
1406 SocketSet_DelSocket(socket_set, bsocket);
1407 }
1408 debug(LOG_NET, "Freeing socket_set %p", static_cast<void *>(socket_set));
1409 deleteSocketSet(socket_set);
1410 socket_set = nullptr;
1411 }
1412 if (tcp_socket)
1413 {
1414 debug(LOG_NET, "Closing tcp_socket %p", static_cast<void *>(tcp_socket));
1415 socketClose(tcp_socket);
1416 tcp_socket = nullptr;
1417 }
1418 if (bsocket)
1419 {
1420 debug(LOG_NET, "Closing bsocket %p", static_cast<void *>(bsocket));
1421 socketClose(bsocket);
1422 bsocket = nullptr;
1423 }
1424
1425 return 0;
1426 }
1427
1428
1429 // ////////////////////////////////////////////////////////////////////////
1430 // ////////////////////////////////////////////////////////////////////////
1431 // Send and Recv functions
1432
1433 // ////////////////////////////////////////////////////////////////////////
1434 // return bytes of data sent recently.
NETgetStatistic(NetStatisticType type,bool sent,bool isTotal)1435 size_t NETgetStatistic(NetStatisticType type, bool sent, bool isTotal)
1436 {
1437 size_t Statistic::*statisticType = sent ? &Statistic::sent : &Statistic::received;
1438 Statistic NETSTATS::*statsType;
1439 switch (type)
1440 {
1441 case NetStatisticRawBytes: statsType = &NETSTATS::rawBytes; break;
1442 case NetStatisticUncompressedBytes: statsType = &NETSTATS::uncompressedBytes; break;
1443 case NetStatisticPackets: statsType = &NETSTATS::packets; break;
1444 default: ASSERT(false, " "); return 0;
1445 }
1446
1447 int time = wzGetTicks();
1448 if ((unsigned)(time - nStatsLastUpdateTime) >= (unsigned)GAME_TICKS_PER_SEC)
1449 {
1450 nStatsLastUpdateTime = time;
1451 nStatsSecondLastSec = nStatsLastSec;
1452 nStatsLastSec = nStats;
1453 }
1454
1455 if (isTotal)
1456 {
1457 return nStats.*statsType.*statisticType;
1458 }
1459 return nStatsLastSec.*statsType.*statisticType - nStatsSecondLastSec.*statsType.*statisticType;
1460 }
1461
1462
1463 // ////////////////////////////////////////////////////////////////////////
1464 // Send a message to a player, option to guarantee message
NETsend(NETQUEUE queue,NetMessage const * message)1465 bool NETsend(NETQUEUE queue, NetMessage const *message)
1466 {
1467 uint8_t player = queue.index;
1468 ssize_t result = 0;
1469
1470 if (!NetPlay.bComms)
1471 {
1472 return true;
1473 }
1474
1475 Socket **sockets = connected_bsocket;
1476 bool isTmpQueue = false;
1477 switch (queue.queueType)
1478 {
1479 case QUEUE_BROADCAST:
1480 ASSERT_OR_RETURN(false, player == NET_ALL_PLAYERS, "Wrong queue index.");
1481 break;
1482 case QUEUE_NET:
1483 ASSERT_OR_RETURN(false, player < MAX_CONNECTED_PLAYERS, "Wrong queue index.");
1484 break;
1485 case QUEUE_TMP:
1486 sockets = tmp_socket;
1487 isTmpQueue = true;
1488
1489 ASSERT_OR_RETURN(false, player < MAX_TMP_SOCKETS && NetPlay.isHost, "Wrong queue index.");
1490 break;
1491 default:
1492 ASSERT_OR_RETURN(false, false, "Wrong queue type.");
1493 }
1494
1495 if (NetPlay.isHost)
1496 {
1497 int firstPlayer = player == NET_ALL_PLAYERS ? 0 : player;
1498 int lastPlayer = player == NET_ALL_PLAYERS ? MAX_CONNECTED_PLAYERS - 1 : player;
1499 for (player = firstPlayer; player <= lastPlayer; ++player)
1500 {
1501 // We are the host, send directly to player.
1502 if (sockets[player] != nullptr && player != queue.exclude)
1503 {
1504 uint8_t *rawData = message->rawDataDup();
1505 ssize_t rawLen = message->rawLen();
1506 size_t compressedRawLen;
1507 result = writeAll(sockets[player], rawData, rawLen, &compressedRawLen);
1508 delete[] rawData; // Done with the data.
1509
1510 if (result == rawLen)
1511 {
1512 nStats.rawBytes.sent += compressedRawLen;
1513 nStats.uncompressedBytes.sent += rawLen;
1514 nStats.packets.sent += 1;
1515 }
1516 else if (result == SOCKET_ERROR)
1517 {
1518 // Write error, most likely client disconnect.
1519 debug(LOG_ERROR, "Failed to send message: %s", strSockError(getSockErr()));
1520 if (!isTmpQueue)
1521 {
1522 NETlogEntry("client disconnect?", SYNC_FLAG, player);
1523 NETplayerClientDisconnect(player);
1524 }
1525 }
1526 }
1527 }
1528 return true;
1529 }
1530 else if (player == NetPlay.hostPlayer)
1531 {
1532 // We are a client, send directly to player, who happens to be the host.
1533 if (bsocket)
1534 {
1535 uint8_t *rawData = message->rawDataDup();
1536 ssize_t rawLen = message->rawLen();
1537 size_t compressedRawLen;
1538 result = writeAll(bsocket, rawData, rawLen, &compressedRawLen);
1539 delete[] rawData; // Done with the data.
1540
1541 if (result == rawLen)
1542 {
1543 nStats.rawBytes.sent += compressedRawLen;
1544 nStats.uncompressedBytes.sent += rawLen;
1545 nStats.packets.sent += 1;
1546 }
1547 else if (result == SOCKET_ERROR)
1548 {
1549 // Write error, most likely host disconnect.
1550 debug(LOG_ERROR, "Failed to send message: %s", strSockError(getSockErr()));
1551 debug(LOG_ERROR, "Host connection was broken, socket %p.", static_cast<void *>(bsocket));
1552 NETlogEntry("write error--client disconnect.", SYNC_FLAG, player);
1553 SocketSet_DelSocket(socket_set, bsocket); // mark it invalid
1554 socketClose(bsocket);
1555 bsocket = nullptr;
1556 NetPlay.players[NetPlay.hostPlayer].heartbeat = false; // mark host as dead
1557 //Game is pretty much over --should just end everything when HOST dies.
1558 NetPlay.isHostAlive = false;
1559 }
1560
1561 return result == rawLen;
1562 }
1563 }
1564 else
1565 {
1566 // We are a client and can't send the data directly, ask the host to send the data to the player.
1567 uint8_t sender = selectedPlayer;
1568 NETbeginEncode(NETnetQueue(NET_HOST_ONLY), NET_SEND_TO_PLAYER);
1569 NETuint8_t(&sender);
1570 NETuint8_t(&player);
1571 NETnetMessage(&message);
1572 NETend();
1573 }
1574
1575 return false;
1576 }
1577
NETflush()1578 void NETflush()
1579 {
1580 if (!NetPlay.bComms)
1581 {
1582 return;
1583 }
1584
1585 NETflushGameQueues();
1586
1587 size_t compressedRawLen;
1588 if (NetPlay.isHost)
1589 {
1590 for (int player = 0; player < MAX_CONNECTED_PLAYERS; ++player)
1591 {
1592 // We are the host, send directly to player.
1593 if (connected_bsocket[player] != nullptr)
1594 {
1595 socketFlush(connected_bsocket[player], &compressedRawLen);
1596 nStats.rawBytes.sent += compressedRawLen;
1597 }
1598 }
1599 for (int player = 0; player < MAX_TMP_SOCKETS; ++player)
1600 {
1601 // We are the host, send directly to player.
1602 if (tmp_socket[player] != nullptr)
1603 {
1604 socketFlush(tmp_socket[player], &compressedRawLen);
1605 nStats.rawBytes.sent += compressedRawLen;
1606 }
1607 }
1608 }
1609 else
1610 {
1611 if (bsocket != nullptr)
1612 {
1613 socketFlush(bsocket, &compressedRawLen);
1614 nStats.rawBytes.sent += compressedRawLen;
1615 }
1616 }
1617 }
1618
1619 ///////////////////////////////////////////////////////////////////////////
1620 // Check if a message is a system message
NETprocessSystemMessage(NETQUEUE playerQueue,uint8_t type)1621 static bool NETprocessSystemMessage(NETQUEUE playerQueue, uint8_t type)
1622 {
1623 switch (type)
1624 {
1625 case NET_SEND_TO_PLAYER:
1626 {
1627 uint8_t sender;
1628 uint8_t receiver;
1629 NetMessage const *message = nullptr;
1630 NETbeginDecode(playerQueue, NET_SEND_TO_PLAYER);
1631 NETuint8_t(&sender);
1632 NETuint8_t(&receiver);
1633 NETnetMessage(&message); // Must delete message later.
1634 std::unique_ptr<NetMessage const> deleteLater(message);
1635 if (!NETend())
1636 {
1637 debug(LOG_ERROR, "Incomplete NET_SEND_TO_PLAYER.");
1638 break;
1639 }
1640 if (sender >= MAX_PLAYERS || (receiver >= MAX_PLAYERS && receiver != NET_ALL_PLAYERS))
1641 {
1642 debug(LOG_ERROR, "Bad NET_SEND_TO_PLAYER.");
1643 break;
1644 }
1645 if ((receiver == selectedPlayer || receiver == NET_ALL_PLAYERS) && playerQueue.index == NetPlay.hostPlayer)
1646 {
1647 // Message was sent to us via the host.
1648 if (sender != selectedPlayer) // Make sure host didn't send us our own broadcast messages, which shouldn't happen anyway.
1649 {
1650 NETinsertMessageFromNet(NETnetQueue(sender), message);
1651 NETlogPacket(message->type, static_cast<uint32_t>(message->rawLen()), true);
1652 }
1653 }
1654 else if (NetPlay.isHost && sender == playerQueue.index)
1655 {
1656 if (((message->type == NET_FIREUP
1657 || message->type == NET_KICK
1658 || message->type == NET_PLAYER_LEAVING
1659 || message->type == NET_PLAYER_DROPPED
1660 || message->type == NET_REJECTED
1661 || message->type == NET_PLAYER_JOINED) && sender != NET_HOST_ONLY)
1662 ||
1663 ((message->type == NET_HOST_DROPPED
1664 || message->type == NET_OPTIONS
1665 || message->type == NET_FILE_REQUESTED
1666 || message->type == NET_READY_REQUEST
1667 || message->type == NET_TEAMREQUEST
1668 || message->type == NET_COLOURREQUEST
1669 || message->type == NET_POSITIONREQUEST
1670 || message->type == NET_FILE_CANCELLED
1671 || message->type == NET_JOIN
1672 || message->type == NET_PLAYER_INFO) && receiver != NET_HOST_ONLY))
1673 {
1674 char msg[256] = {'\0'};
1675
1676 ssprintf(msg, "Auto-kicking player %u, lacked the required access level for command(%d).", (unsigned int)sender, (int)message->type);
1677 sendRoomSystemMessage(msg);
1678 NETlogEntry(msg, SYNC_FLAG, sender);
1679 addToBanList(NetPlay.players[sender].IPtextAddress, NetPlay.players[sender].name);
1680 NETplayerDropped(sender);
1681 connected_bsocket[sender] = nullptr;
1682 debug(LOG_ERROR, "%s", msg);
1683 break;
1684 }
1685
1686 // We are the host, and player is asking us to send the message to receiver.
1687 NETbeginEncode(NETnetQueue(receiver, sender), NET_SEND_TO_PLAYER);
1688 NETuint8_t(&sender);
1689 NETuint8_t(&receiver);
1690 NETnetMessage(&message);
1691 NETend();
1692
1693 if (receiver == NET_ALL_PLAYERS)
1694 {
1695 NETinsertMessageFromNet(NETnetQueue(sender), message); // Message is also for the host.
1696 NETlogPacket(message->type, static_cast<uint32_t>(message->rawLen()), true);
1697 // Not sure if flushing here can make a difference, maybe it can:
1698 //NETflush(); // Send the message to everyone as fast as possible.
1699 }
1700 }
1701 else
1702 {
1703 debug(LOG_INFO, "Report this: Player %d sent us message type (%d) addressed to %d from %d. We are %d.", (int)playerQueue.index, (int)message->type, (int)receiver, (int)sender, selectedPlayer);
1704 }
1705
1706 break;
1707 }
1708 case NET_SHARE_GAME_QUEUE:
1709 {
1710 uint8_t player = 0;
1711 uint32_t num = 0, n;
1712 NetMessage const *message = nullptr;
1713
1714 // Encoded in NETprocessSystemMessage in nettypes.cpp.
1715 NETbeginDecode(playerQueue, NET_SHARE_GAME_QUEUE);
1716 NETuint8_t(&player);
1717 NETuint32_t(&num);
1718 bool isSentByCorrectClient = responsibleFor(playerQueue.index, player);
1719 isSentByCorrectClient = isSentByCorrectClient || (playerQueue.index == NET_HOST_ONLY && playerQueue.index != selectedPlayer); // Let host spoof other people's NET_SHARE_GAME_QUEUE messages, but not our own. This allows the host to spoof a GAME_PLAYER_LEFT message (but spoofing any message when the player is still there will fail with desynch).
1720 if (!isSentByCorrectClient || player >= MAX_PLAYERS)
1721 {
1722 break;
1723 }
1724 for (n = 0; n < num; ++n)
1725 {
1726 NETnetMessage(&message);
1727
1728 NETinsertMessageFromNet(NETgameQueue(player), message);
1729 NETlogPacket(message->type, static_cast<uint32_t>(message->rawLen()), true);
1730
1731 delete message;
1732 message = nullptr;
1733 }
1734 if (!NETend())
1735 {
1736 debug(LOG_ERROR, "Bad NET_SHARE_GAME_QUEUE message.");
1737 break;
1738 }
1739 break;
1740 }
1741 case NET_PLAYER_STATS:
1742 {
1743 recvMultiStats(playerQueue);
1744 netPlayersUpdated = true;
1745 break;
1746 }
1747 case NET_PLAYER_INFO:
1748 {
1749 uint32_t indexLen = 0, n;
1750 uint32_t index = MAX_PLAYERS;
1751 int32_t colour = 0;
1752 int32_t position = 0;
1753 int32_t team = 0;
1754 int8_t ai = 0;
1755 int8_t difficulty = 0;
1756 uint8_t faction = FACTION_NORMAL;
1757 bool error = false;
1758
1759 NETbeginDecode(playerQueue, NET_PLAYER_INFO);
1760 NETuint32_t(&indexLen);
1761 if (indexLen > MAX_PLAYERS || (playerQueue.index != NET_HOST_ONLY && indexLen > 1))
1762 {
1763 debug(LOG_ERROR, "MSG_PLAYER_INFO: Bad number of players updated: %u", indexLen);
1764 NETend();
1765 break;
1766 }
1767
1768 for (n = 0; n < indexLen; ++n)
1769 {
1770 bool wasAllocated = false;
1771 std::string oldName;
1772
1773 // Retrieve the player's ID
1774 NETuint32_t(&index);
1775
1776 // Bail out if the given ID number is out of range
1777 if (index >= MAX_CONNECTED_PLAYERS || (playerQueue.index != NetPlay.hostPlayer && (playerQueue.index != index || !NetPlay.players[index].allocated)))
1778 {
1779 debug(LOG_ERROR, "MSG_PLAYER_INFO from %u: Player ID (%u) out of range (max %u)", playerQueue.index, index, (unsigned int)MAX_CONNECTED_PLAYERS);
1780 error = true;
1781 break;
1782 }
1783
1784 // Retrieve the rest of the data
1785 wasAllocated = NetPlay.players[index].allocated;
1786 NETbool(&NetPlay.players[index].allocated);
1787 NETbool(&NetPlay.players[index].heartbeat);
1788 NETbool(&NetPlay.players[index].kick);
1789 oldName.clear();
1790 oldName = NetPlay.players[index].name;
1791 NETstring(NetPlay.players[index].name, sizeof(NetPlay.players[index].name));
1792 NETuint32_t(&NetPlay.players[index].heartattacktime);
1793 NETint32_t(&colour);
1794 NETint32_t(&position);
1795 NETint32_t(&team);
1796 NETbool(&NetPlay.players[index].ready);
1797 NETint8_t(&ai);
1798 NETint8_t(&difficulty);
1799 NETuint8_t(&faction);
1800
1801 auto newFactionId = uintToFactionID(faction);
1802 if (!newFactionId.has_value())
1803 {
1804 debug(LOG_ERROR, "MSG_PLAYER_INFO from %u: Faction ID (%u) out of range (max %u)", playerQueue.index, (unsigned int)faction, (unsigned int)MAX_FACTION_ID);
1805 error = true;
1806 break;
1807 }
1808
1809 // Don't let anyone except the host change these, otherwise it will end up inconsistent at some point, and the game gets really messed up.
1810 if (playerQueue.index == NetPlay.hostPlayer)
1811 {
1812 setPlayerColour(index, colour);
1813 NetPlay.players[index].position = position;
1814 NetPlay.players[index].team = team;
1815 NetPlay.players[index].ai = ai;
1816 NetPlay.players[index].difficulty = static_cast<AIDifficulty>(difficulty);
1817 NetPlay.players[index].faction = newFactionId.value();
1818 }
1819
1820 debug(LOG_NET, "%s for player %u (%s)", n == 0 ? "Receiving MSG_PLAYER_INFO" : " and", (unsigned int)index, NetPlay.players[index].allocated ? "human" : "AI");
1821
1822 if (wasAllocated && NetPlay.players[index].allocated && strncmp(oldName.c_str(), NetPlay.players[index].name, sizeof(NetPlay.players[index].name)) != 0)
1823 {
1824 printConsoleNameChange(oldName.c_str(), NetPlay.players[index].name);
1825 }
1826 }
1827 NETend();
1828 // If we're the game host make sure to send the updated
1829 // data to all other clients as well.
1830 if (NetPlay.isHost && !error)
1831 {
1832 NETBroadcastPlayerInfo(index); // ultimately triggers updateMultiplayGameData inside NETSendNPlayerInfoTo
1833 NETfixDuplicatePlayerNames();
1834 }
1835 else if (!error)
1836 {
1837 ActivityManager::instance().updateMultiplayGameData(game, ingame, NETGameIsLocked());
1838 }
1839 netPlayersUpdated = true;
1840 break;
1841 }
1842 case NET_PLAYER_JOINED:
1843 {
1844 uint8_t index;
1845
1846 NETbeginDecode(playerQueue, NET_PLAYER_JOINED);
1847 NETuint8_t(&index);
1848 NETend();
1849
1850 debug(LOG_NET, "Receiving NET_PLAYER_JOINED for player %u using socket %p",
1851 (unsigned int)index, static_cast<void *>(bsocket));
1852
1853 MultiPlayerJoin(index);
1854 netPlayersUpdated = true;
1855 break;
1856 }
1857 // This message type is when player is leaving 'nicely', and socket is still valid.
1858 case NET_PLAYER_LEAVING:
1859 {
1860 uint32_t index;
1861
1862 NETbeginDecode(playerQueue, NET_PLAYER_LEAVING);
1863 NETuint32_t(&index);
1864 NETend();
1865
1866 if (playerQueue.index != NetPlay.hostPlayer && index != playerQueue.index)
1867 {
1868 debug(LOG_ERROR, "Player %d left, but accidentally set player %d as leaving.", playerQueue.index, index);
1869 index = playerQueue.index;
1870 }
1871
1872 if (connected_bsocket[index])
1873 {
1874 debug(LOG_NET, "Receiving NET_PLAYER_LEAVING for player %u on socket %p", (unsigned int)index, static_cast<void *>(connected_bsocket[index]));
1875 }
1876 else
1877 {
1878 // dropped from join screen most likely
1879 debug(LOG_NET, "Receiving NET_PLAYER_LEAVING for player %u (no socket?)", (unsigned int)index);
1880 }
1881
1882 if (NetPlay.isHost)
1883 {
1884 debug(LOG_NET, "Broadcast leaving message to everyone else");
1885 NETbeginEncode(NETbroadcastQueue(), NET_PLAYER_LEAVING);
1886 NETuint32_t(&index);
1887 NETend();
1888 }
1889
1890 debug(LOG_INFO, "Player %u has left the game.", index);
1891 NETplayerLeaving(index); // need to close socket for the player that left.
1892 NETsetPlayerConnectionStatus(CONNECTIONSTATUS_PLAYER_LEAVING, index);
1893 break;
1894 }
1895 case NET_GAME_FLAGS:
1896 {
1897 debug(LOG_NET, "Receiving game flags");
1898
1899 NETbeginDecode(playerQueue, NET_GAME_FLAGS);
1900 {
1901 static unsigned int max_flags = ARRAY_SIZE(NetGameFlags);
1902 // Retrieve the amount of game flags that we should receive
1903 uint8_t i, count;
1904 NETuint8_t(&count);
1905
1906 // Make sure that we won't get buffer overflows by checking that we
1907 // have enough space to store the given amount of game flags.
1908 if (count > max_flags)
1909 {
1910 debug(LOG_NET, "NET_GAME_FLAGS: More game flags sent (%u) than our buffer can hold (%u)", (unsigned int)count, max_flags);
1911 count = max_flags;
1912 }
1913
1914 // Retrieve all game flags
1915 for (i = 0; i < count; ++i)
1916 {
1917 NETint32_t(&NetGameFlags[i]);
1918 }
1919 }
1920 NETend();
1921
1922 if (NetPlay.isHost)
1923 {
1924 NETsendGameFlags();
1925 }
1926 break;
1927 }
1928 case NET_DEBUG_SYNC:
1929 {
1930 recvDebugSync(playerQueue);
1931 break;
1932 }
1933
1934 default:
1935 return false;
1936 }
1937
1938 NETpop(playerQueue);
1939 return true;
1940 }
1941
1942 /*
1943 * Checks to see if a human player is still with us.
1944 * @note: resuscitation isn't possible with current code, so once we lose
1945 * the socket, then we have no way to connect with them again. Future
1946 * item to enhance.
1947 */
NETcheckPlayers()1948 static void NETcheckPlayers()
1949 {
1950 if (!NetPlay.isHost)
1951 {
1952 ASSERT(false, "Host only routine detected for client or not hosting yet!");
1953 return;
1954 }
1955
1956 for (int i = 0; i < MAX_PLAYERS ; i++)
1957 {
1958 if (NetPlay.players[i].allocated == 0)
1959 {
1960 continue; // not allocated means that it most likely it is a AI player
1961 }
1962 if (NetPlay.players[i].heartbeat == 0 && NetPlay.players[i].heartattacktime == 0) // looks like they are dead
1963 {
1964 NetPlay.players[i].heartattacktime = realTime; // mark when this occurred
1965 }
1966 else
1967 {
1968 if (NetPlay.players[i].heartattacktime)
1969 {
1970 if (NetPlay.players[i].heartattacktime + (15 * GAME_TICKS_PER_SEC) < realTime) // wait 15 secs
1971 {
1972 debug(LOG_NET, "Kicking due to client heart attack");
1973 NetPlay.players[i].kick = true; // if still dead, then kick em.
1974 }
1975 }
1976 }
1977 if (NetPlay.players[i].kick)
1978 {
1979 debug(LOG_NET, "Kicking player %d", i);
1980 kickPlayer(i, "you are unwanted by the host.", ERROR_KICKED);
1981 }
1982 }
1983 }
1984
1985 // ////////////////////////////////////////////////////////////////////////
1986 // Receive a message over the current connection. We return true if there
1987 // is a message for the higher level code to process, and false otherwise.
1988 // We should not block here.
NETrecvNet(NETQUEUE * queue,uint8_t * type)1989 bool NETrecvNet(NETQUEUE *queue, uint8_t *type)
1990 {
1991 const int status = upnp_status.load(); // hack fix for clang and c++11 - fixed in standard for c++14
1992 switch (status)
1993 {
1994 case UPNP_ERROR_CONTROL_NOT_AVAILABLE:
1995 case UPNP_ERROR_DEVICE_NOT_FOUND:
1996 if (upnp_status == UPNP_ERROR_DEVICE_NOT_FOUND)
1997 {
1998 debug(LOG_NET, "UPnP device not found");
1999 }
2000 else if (upnp_status == UPNP_ERROR_CONTROL_NOT_AVAILABLE)
2001 {
2002 debug(LOG_NET, "controlURL not available, UPnP disabled");
2003 }
2004 // beware of writing a line too long, it screws up console line count. \n is safe for line split
2005 addConsoleMessage(_("No UPnP device found. Configure your router/firewall to open port 2100!"), DEFAULT_JUSTIFY, NOTIFY_MESSAGE);
2006 NetPlay.isUPNP_CONFIGURED = false;
2007 NetPlay.isUPNP_ERROR = true;
2008 upnp_status = 0;
2009 break;
2010 case UPNP_SUCCESS:
2011 NETaddRedirects();
2012 upnp_status = 0;
2013 break;
2014 default:
2015 case 0:
2016 ASSERT(upnp_status == 0, "bad value");
2017 break;
2018 }
2019
2020 uint32_t current;
2021
2022 if (!NetPlay.bComms)
2023 {
2024 return false;
2025 }
2026
2027 if (NetPlay.isHost)
2028 {
2029 NETfixPlayerCount();
2030 NETallowJoining();
2031 lobbyConnectionHandler.run();
2032 NETcheckPlayers(); // make sure players are still alive & well
2033 }
2034
2035 if (socket_set == nullptr || checkSockets(socket_set, NET_READ_TIMEOUT) <= 0)
2036 {
2037 goto checkMessages;
2038 }
2039
2040 for (current = 0; current < MAX_CONNECTED_PLAYERS; ++current)
2041 {
2042 Socket **pSocket = NetPlay.isHost ? &connected_bsocket[current] : &bsocket;
2043 uint8_t buffer[NET_BUFFER_SIZE];
2044 size_t dataLen;
2045
2046 if (!NetPlay.isHost && current != NET_HOST_ONLY)
2047 {
2048 continue; // Don't have a socket open to this player.
2049 }
2050
2051 if (*pSocket == nullptr)
2052 {
2053 continue;
2054 }
2055
2056 dataLen = NET_fillBuffer(pSocket, socket_set, buffer, sizeof(buffer));
2057 if (dataLen > 0)
2058 {
2059 // we received some data, add to buffer
2060 NETinsertRawData(NETnetQueue(current), buffer, dataLen);
2061 }
2062 else if (*pSocket == nullptr)
2063 {
2064 // If there is a error in NET_fillBuffer() then socket is already invalid.
2065 // This means that the player dropped / disconnected for whatever reason.
2066 debug(LOG_INFO, "Player, (player %u) seems to have dropped/disconnected.", (unsigned)current);
2067
2068 if (NetPlay.isHost)
2069 {
2070 // Send message type specifically for dropped / disconnects
2071 NETplayerDropped(current);
2072 connected_bsocket[current] = nullptr; // clear their socket
2073 }
2074 else
2075 {
2076 // lobby errors were set in NET_fillBuffer()
2077 return false;
2078 }
2079 }
2080 }
2081
2082 checkMessages:
2083 for (current = 0; current < MAX_CONNECTED_PLAYERS; ++current)
2084 {
2085 *queue = NETnetQueue(current);
2086 while (NETisMessageReady(*queue))
2087 {
2088 *type = NETgetMessage(*queue)->type;
2089 if (!NETprocessSystemMessage(*queue, *type))
2090 {
2091 return true; // We couldn't process the message, let the caller deal with it..
2092 }
2093 }
2094 }
2095
2096 return false;
2097 }
2098
NETrecvGame(NETQUEUE * queue,uint8_t * type)2099 bool NETrecvGame(NETQUEUE *queue, uint8_t *type)
2100 {
2101 for (unsigned current = 0; current < MAX_PLAYERS; ++current)
2102 {
2103 *queue = NETgameQueue(current);
2104 while (!checkPlayerGameTime(current)) // Check for any messages that are scheduled to be read now.
2105 {
2106 if (!NETisMessageReady(*queue))
2107 {
2108 return false; // Still waiting for messages from this player, and all players should process messages in the same order. Will have to freeze the game while waiting.
2109 }
2110
2111 *type = NETgetMessage(*queue)->type;
2112
2113 if (*type == GAME_GAME_TIME)
2114 {
2115 recvPlayerGameTime(*queue);
2116 NETpop(*queue);
2117 continue;
2118 }
2119
2120 return true; // Have a message ready to read now.
2121 }
2122 }
2123
2124 return false; // No messages sceduled to be read yet. Game can continue.
2125 }
2126
2127 // ////////////////////////////////////////////////////////////////////////
2128 // File Transfer programs.
2129 /** Send file. It returns % of file sent when 100 it's complete. Call until it returns 100.
2130 * @TODO: more error checking (?) different file types (?)
2131 * Maybe should close file handle, and seek each time?
2132 *
2133 * @NOTE: MAX_FILE_TRANSFER_PACKET is set to 2k per packet since 7*2 = 14K which is pretty
2134 * much our limit. Don't screw with that without having a bigger buffer!
2135 * NET_BUFFER_SIZE is at 16k. (also remember text chat, plus all the other cruff)
2136 */
2137 #define MAX_FILE_TRANSFER_PACKET 2048
NETsendFile(WZFile & file,unsigned player)2138 int NETsendFile(WZFile &file, unsigned player)
2139 {
2140 ASSERT_OR_RETURN(100, NetPlay.isHost, "Trying to send a file and we are not the host!");
2141
2142 uint8_t inBuff[MAX_FILE_TRANSFER_PACKET];
2143 memset(inBuff, 0x0, sizeof(inBuff));
2144
2145 // read some bytes.
2146 uint32_t bytesToRead = WZ_PHYSFS_readBytes(file.handle, inBuff, MAX_FILE_TRANSFER_PACKET);
2147 ASSERT_OR_RETURN(100, (int32_t)bytesToRead >= 0, "Error reading file.");
2148
2149 NETbeginEncode(NETnetQueue(player), NET_FILE_PAYLOAD);
2150 NETbin(file.hash.bytes, file.hash.Bytes);
2151 NETuint32_t(&file.size); // total bytes in this file. (we don't support 64bit yet)
2152 NETuint32_t(&file.pos); // start byte
2153 NETuint32_t(&bytesToRead); // bytes in this packet
2154 NETbin(inBuff, bytesToRead);
2155 NETend();
2156
2157 file.pos += bytesToRead; // update position!
2158 if (file.pos == file.size)
2159 {
2160 PHYSFS_close(file.handle);
2161 file.handle = nullptr; // We are done sending to this client.
2162 }
2163
2164 return (uint64_t)file.pos * 100 / file.size;
2165 }
2166
validateReceivedFile(const WZFile & file)2167 bool validateReceivedFile(const WZFile& file)
2168 {
2169 PHYSFS_file *fileHandle = PHYSFS_openRead(file.filename.c_str());
2170 ASSERT_OR_RETURN(false, fileHandle != nullptr, "Could not open downloaded file %s for reading: %s", file.filename.c_str(), WZ_PHYSFS_getLastError());
2171
2172 PHYSFS_sint64 actualFileSize64 = PHYSFS_fileLength(fileHandle);
2173 if (actualFileSize64 < 0)
2174 {
2175 debug(LOG_ERROR, "Failed to determine file size of the downloaded file!");
2176 PHYSFS_close(fileHandle);
2177 return false;
2178 }
2179 if(actualFileSize64 > std::numeric_limits<int32_t>::max())
2180 {
2181 debug(LOG_ERROR, "Downloaded file is too large!");
2182 PHYSFS_close(fileHandle);
2183 return false;
2184 }
2185
2186 uint32_t actualFileSize = static_cast<uint32_t>(actualFileSize64);
2187 if (actualFileSize != file.size)
2188 {
2189 debug(LOG_ERROR, "Downloaded map unexpected size! Got %" PRIu32", expected %" PRIu32"!", actualFileSize, file.size);
2190 PHYSFS_close(fileHandle);
2191 return false;
2192 }
2193
2194 // verify actual downloaded file hash matches expected hash
2195
2196 Sha256 actualFileHash;
2197 crypto_hash_sha256_state state;
2198 crypto_hash_sha256_init(&state);
2199 size_t bufferSize = std::min<size_t>(actualFileSize, 4 * 1024 * 1024);
2200 std::vector<unsigned char> fileChunkBuffer(bufferSize, '\0');
2201 PHYSFS_sint64 length_read = 0;
2202 do {
2203 length_read = WZ_PHYSFS_readBytes(fileHandle, fileChunkBuffer.data(), static_cast<PHYSFS_uint32>(bufferSize));
2204 if (length_read != bufferSize)
2205 {
2206 if (length_read < 0 || !PHYSFS_eof(fileHandle))
2207 {
2208 // did not read expected amount, but did not reach end of file - some other error reading the file occurred
2209 debug(LOG_ERROR, "Failed to read downloaded file: %s", WZ_PHYSFS_getLastError());
2210 PHYSFS_close(fileHandle);
2211 return false;
2212 }
2213 }
2214 crypto_hash_sha256_update(&state, fileChunkBuffer.data(), static_cast<unsigned long long>(length_read));
2215 } while (length_read == bufferSize);
2216 crypto_hash_sha256_final(&state, actualFileHash.bytes);
2217 fileChunkBuffer.clear();
2218
2219 if (actualFileHash != file.hash)
2220 {
2221 debug(LOG_ERROR, "Downloaded file hash (%s) does not match requested file hash (%s)", actualFileHash.toString().c_str(), file.hash.toString().c_str());
2222 PHYSFS_close(fileHandle);
2223 return false;
2224 }
2225
2226 PHYSFS_close(fileHandle);
2227 return true;
2228 }
2229
markAsDownloadedFile(const std::string & filename)2230 bool markAsDownloadedFile(const std::string &filename)
2231 {
2232 // Files are written to the PhysFS writeDir
2233 const char * current_writeDir = PHYSFS_getWriteDir();
2234 ASSERT(current_writeDir != nullptr, "Failed to get PhysFS writeDir: %s", WZ_PHYSFS_getLastError());
2235 std::string fullFilePath = std::string(current_writeDir) + PHYSFS_getDirSeparator() + filename;
2236
2237 #if defined(WZ_OS_WIN)
2238 // On Windows:
2239 // - Create the Alternate Data Stream required to set the Internet Zone identifier
2240 const wchar_t kWindowsZoneIdentifierADSSuffix[] = L":Zone.Identifier";
2241
2242 // Convert fullFilePath to UTF-16 wchar_t
2243 int wstr_len = MultiByteToWideChar(CP_UTF8, 0, fullFilePath.c_str(), -1, NULL, 0);
2244 if (wstr_len <= 0)
2245 {
2246 debug(LOG_ERROR, "Could not convert string from UTF-8; MultiByteToWideChar failed with error %d: %s\n", GetLastError(), fullFilePath.c_str());
2247 return false;
2248 }
2249 std::vector<wchar_t> wstr_filename(wstr_len, 0);
2250 if (MultiByteToWideChar(CP_UTF8, 0, fullFilePath.c_str(), -1, &wstr_filename[0], wstr_len) == 0)
2251 {
2252 debug(LOG_ERROR, "Could not convert string from UTF-8; MultiByteToWideChar[2] failed with error %d: %s\n", GetLastError(), fullFilePath.c_str());
2253 return false;
2254 }
2255 std::wstring fullFilePathUTF16(wstr_filename.data());
2256 fullFilePathUTF16 += kWindowsZoneIdentifierADSSuffix;
2257
2258 HANDLE hStream = CreateFileW(fullFilePathUTF16.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
2259 if(hStream == INVALID_HANDLE_VALUE)
2260 {
2261 // Failed to open stream
2262 debug(LOG_ERROR, "Could not open stream; failed with error %d: %s\n", GetLastError(), fullFilePath.c_str());
2263 return false;
2264 }
2265
2266 // Set it to "downloaded from the Internet Zone" (ZoneId 3)
2267 const char kWindowsZoneIdentifierADSDataInternetZone[] = "[ZoneTransfer]\r\nZoneId=3\r\n";
2268 DWORD dwNumberOfBytesWritten;
2269 if (WriteFile(hStream, kWindowsZoneIdentifierADSDataInternetZone, static_cast<DWORD>(strlen(kWindowsZoneIdentifierADSDataInternetZone)), &dwNumberOfBytesWritten, NULL) == 0)
2270 {
2271 debug(LOG_ERROR, "Failed to write to stream with error %d: %s\n", GetLastError(), fullFilePath.c_str());
2272 CloseHandle(hStream);
2273 return false;
2274 }
2275
2276 FlushFileBuffers(hStream);
2277 CloseHandle(hStream);
2278
2279 return true;
2280 #elif defined (WZ_OS_MAC)
2281 // On macOS:
2282 // - Set the quarantine attribute on the file
2283 return cocoaSetFileQuarantineAttribute(fullFilePath.c_str());
2284 #else
2285 // Not currently implemented
2286 #endif
2287 return false;
2288 }
2289
2290 // recv file. it returns % of the file so far recvd.
NETrecvFile(NETQUEUE queue)2291 int NETrecvFile(NETQUEUE queue)
2292 {
2293 Sha256 hash;
2294 hash.setZero();
2295 uint32_t size = 0;
2296 uint32_t pos = 0;
2297 uint32_t bytesToRead = 0;
2298 uint8_t buf[MAX_FILE_TRANSFER_PACKET];
2299 memset(buf, 0x0, sizeof(buf));
2300
2301 //read incoming bytes.
2302 NETbeginDecode(queue, NET_FILE_PAYLOAD);
2303 NETbin(hash.bytes, hash.Bytes);
2304 NETuint32_t(&size); // total bytes in this file. (we don't support 64bit yet)
2305 NETuint32_t(&pos); // start byte
2306 NETuint32_t(&bytesToRead); // bytes in this packet
2307 ASSERT_OR_RETURN(100, bytesToRead <= sizeof(buf), "Bad value.");
2308 NETbin(buf, bytesToRead);
2309 NETend();
2310
2311 debug(LOG_NET, "New file position is %u", pos);
2312
2313 auto file = std::find_if(NetPlay.wzFiles.begin(), NetPlay.wzFiles.end(), [&](WZFile const &file) { return file.hash == hash; });
2314
2315 auto sendCancelFileDownload = [](Sha256 &hash) {
2316 NETbeginEncode(NETnetQueue(NET_HOST_ONLY), NET_FILE_CANCELLED);
2317 NETbin(hash.bytes, hash.Bytes);
2318 NETend();
2319 };
2320
2321 if (file == NetPlay.wzFiles.end())
2322 {
2323 debug(LOG_WARNING, "Receiving file data we didn't request.");
2324 sendCancelFileDownload(hash);
2325 return 100;
2326 }
2327
2328 auto terminateFileDownload = [sendCancelFileDownload](std::vector<WZFile>::iterator &file) {
2329 int noError = PHYSFS_close(file->handle);
2330 if (noError == 0)
2331 {
2332 debug(LOG_ERROR, "Could not close file handle after trying to terminate download: %s", WZ_PHYSFS_getLastError());
2333 }
2334 file->handle = nullptr;
2335 sendCancelFileDownload(file->hash);
2336 NetPlay.wzFiles.erase(file);
2337 };
2338
2339 //sanity checks
2340 if (file->size != size)
2341 {
2342 if (file->size == 0)
2343 {
2344 // host does not send the file size until the first recvFile packet
2345 file->size = size;
2346 }
2347 else
2348 {
2349 // host sent a different file size for this file with this chunk (vs the first chunk)
2350 // this should not happen!
2351 debug(LOG_ERROR, "Host sent a different file size for this file; (original size: %u, new size: %u)", file->size, size);
2352 terminateFileDownload(file); // 'file' is now an invalidated iterator.
2353 return 100;
2354 }
2355 }
2356
2357 if (size > MAX_NET_TRANSFERRABLE_FILE_SIZE)
2358 {
2359 // file size is too large
2360 debug(LOG_ERROR, "Downloaded filesize is too large; (size: %" PRIu32")", size);
2361 terminateFileDownload(file); // 'file' is now an invalidated iterator.
2362 return 100;
2363 }
2364
2365 if (PHYSFS_tell(file->handle) != static_cast<PHYSFS_sint64>(pos))
2366 {
2367 // actual position in file does not equal the expected position in the file (sent by the host)
2368 debug(LOG_ERROR, "Invalid file position in downloaded file; (desired: %" PRIu32")", pos);
2369 terminateFileDownload(file); // 'file' is now an invalidated iterator.
2370 return 100;
2371 }
2372
2373 // Write packet to the file.
2374 WZ_PHYSFS_writeBytes(file->handle, buf, bytesToRead);
2375
2376 uint32_t newPos = pos + bytesToRead;
2377 file->pos = newPos;
2378
2379 if (newPos >= size) // last packet
2380 {
2381 int noError = PHYSFS_close(file->handle);
2382 if (noError == 0)
2383 {
2384 debug(LOG_ERROR, "Could not close file handle after trying to save map: %s", WZ_PHYSFS_getLastError());
2385 }
2386 file->handle = nullptr;
2387
2388 if(!validateReceivedFile(*file))
2389 {
2390 // Delete the (invalid) downloaded file
2391 PHYSFS_delete(file->filename.c_str());
2392 }
2393 else
2394 {
2395 // Attach Quarantine / "downloaded" file attribute to file
2396 markAsDownloadedFile(file->filename.c_str());
2397 }
2398
2399 NetPlay.wzFiles.erase(file);
2400 }
2401 // 'file' may now be an invalidated iterator.
2402
2403 //return the percentage count
2404 if (size)
2405 {
2406 return (newPos * 100) / size;
2407 }
2408 debug(LOG_ERROR, "Received 0 byte file from host?");
2409 return 100; // file is nullbyte, so we are done.
2410 }
2411
NETgetDownloadProgress(unsigned player)2412 unsigned NETgetDownloadProgress(unsigned player)
2413 {
2414 std::vector<WZFile> const &files = player == selectedPlayer ?
2415 NetPlay.wzFiles : // Check our own download progress.
2416 NetPlay.players[player].wzFiles; // Check their download progress (currently only works if we are the host).
2417
2418 uint32_t progress = 100;
2419 for (WZFile const &file : files)
2420 {
2421 progress = std::min<uint32_t>(progress, (uint32_t)((uint64_t)file.pos * 100 / (uint64_t)std::max<uint32_t>(file.size, 1)));
2422 }
2423 return static_cast<unsigned>(progress);
2424 }
2425
readLobbyResponse(Socket * sock,unsigned int timeout)2426 static ssize_t readLobbyResponse(Socket *sock, unsigned int timeout)
2427 {
2428 uint32_t lobbyStatusCode;
2429 uint32_t MOTDLength;
2430 uint32_t buffer[2];
2431 ssize_t result, received = 0;
2432
2433 // Get status and message length
2434 result = readAll(sock, &buffer, sizeof(buffer), timeout);
2435 if (result != sizeof(buffer))
2436 {
2437 goto error;
2438 }
2439 received += result;
2440 lobbyStatusCode = ntohl(buffer[0]);
2441 MOTDLength = ntohl(buffer[1]);
2442
2443 // Get status message
2444 if (NetPlay.MOTD)
2445 {
2446 free(NetPlay.MOTD);
2447 }
2448 NetPlay.MOTD = (char *)malloc(MOTDLength + 1);
2449 result = readAll(sock, NetPlay.MOTD, MOTDLength, timeout);
2450 if (result != MOTDLength)
2451 {
2452 goto error;
2453 }
2454 received += result;
2455 // NUL terminate string
2456 NetPlay.MOTD[MOTDLength] = '\0';
2457
2458 switch (lobbyStatusCode)
2459 {
2460 case 200:
2461 debug(LOG_NET, "Lobby success (%u): %s", (unsigned int)lobbyStatusCode, NetPlay.MOTD);
2462 NetPlay.HaveUpgrade = false;
2463 break;
2464
2465 case 400:
2466 debug(LOG_NET, "**Upgrade available! Lobby success (%u): %s", (unsigned int)lobbyStatusCode, NetPlay.MOTD);
2467 NetPlay.HaveUpgrade = true;
2468 break;
2469
2470 default:
2471 debug(LOG_ERROR, "Lobby error (%u): %s", (unsigned int)lobbyStatusCode, NetPlay.MOTD);
2472 // ensure if the lobby returns an error, we are prepared to display it (once)
2473 NetPlay.ShowedMOTD = false;
2474 break;
2475 }
2476
2477 return received;
2478
2479 error:
2480 if (result == SOCKET_ERROR)
2481 {
2482 if (NetPlay.MOTD)
2483 {
2484 free(NetPlay.MOTD);
2485 }
2486 if (asprintf(&NetPlay.MOTD, "Error while connecting to the lobby server: %s\nMake sure port %d can receive incoming connections.", strSockError(getSockErr()), gameserver_port) == -1)
2487 {
2488 NetPlay.MOTD = nullptr;
2489 }
2490 else
2491 {
2492 NetPlay.ShowedMOTD = false;
2493 debug(LOG_ERROR, "%s", NetPlay.MOTD);
2494 }
2495 }
2496 else
2497 {
2498 if (NetPlay.MOTD)
2499 {
2500 free(NetPlay.MOTD);
2501 }
2502 if (asprintf(&NetPlay.MOTD, "Disconnected from lobby server. Failed to register game.") == -1)
2503 {
2504 NetPlay.MOTD = nullptr;
2505 }
2506 else
2507 {
2508 NetPlay.ShowedMOTD = false;
2509 debug(LOG_ERROR, "%s", NetPlay.MOTD);
2510 }
2511 }
2512
2513 return SOCKET_ERROR;
2514 }
2515
readGameStructsList(Socket * sock,unsigned int timeout,const std::function<bool (const GAMESTRUCT & game)> & handleEnumerateGameFunc)2516 bool readGameStructsList(Socket *sock, unsigned int timeout, const std::function<bool (const GAMESTRUCT& game)>& handleEnumerateGameFunc)
2517 {
2518 unsigned int gamecount = 0;
2519 uint32_t gamesavailable = 0;
2520 int result = 0;
2521
2522 if ((result = readAll(sock, &gamesavailable, sizeof(gamesavailable), NET_TIMEOUT_DELAY)) == sizeof(gamesavailable))
2523 {
2524 gamesavailable = ntohl(gamesavailable);
2525 }
2526 else
2527 {
2528 if (result == SOCKET_ERROR)
2529 {
2530 debug(LOG_NET, "Server socket encountered error: %s", strSockError(getSockErr()));
2531 }
2532 else
2533 {
2534 debug(LOG_NET, "Server didn't respond (timeout)");
2535 }
2536 return false;
2537 }
2538
2539 debug(LOG_NET, "receiving info on %u game(s)", (unsigned int)gamesavailable);
2540
2541 while (gamecount < gamesavailable)
2542 {
2543 // Attempt to receive a game description structure
2544 GAMESTRUCT tmpGame;
2545 memset(&tmpGame, 0x00, sizeof(tmpGame));
2546 if (!NETrecvGAMESTRUCT(sock, &tmpGame))
2547 {
2548 debug(LOG_NET, "only %u game(s) received", (unsigned int)gamecount);
2549 return false;
2550 }
2551
2552 if (tmpGame.desc.host[0] == '\0')
2553 {
2554 memset(tmpGame.desc.host, 0, sizeof(tmpGame.desc.host));
2555 strncpy(tmpGame.desc.host, getSocketTextAddress(sock), sizeof(tmpGame.desc.host) - 1);
2556 }
2557
2558 uint32_t Vmgr = (tmpGame.future4 & 0xFFFF0000) >> 16;
2559 uint32_t Vmnr = (tmpGame.future4 & 0x0000FFFF);
2560
2561 if (NETisGreaterVersion(Vmgr, Vmnr))
2562 {
2563 debug(LOG_NET, "Version update %d:%d", Vmgr, Vmnr);
2564 NetPlay.HaveUpgrade = true;
2565 }
2566
2567 if (tmpGame.desc.dwSize != 0)
2568 {
2569 if (!handleEnumerateGameFunc(tmpGame))
2570 {
2571 // stop enumerating
2572 break;
2573 }
2574 }
2575
2576 ++gamecount;
2577 }
2578
2579 return true;
2580 }
2581
connect()2582 bool LobbyServerConnectionHandler::connect()
2583 {
2584 if (server_not_there)
2585 {
2586 return false;
2587 }
2588 if (currentState == LobbyConnectionState::Connecting_WaitingForResponse || currentState == LobbyConnectionState::Connected)
2589 {
2590 return false; // already connecting or connected
2591 }
2592
2593 bool bProcessingConnectOrDisconnectThisCall = true;
2594 uint32_t gameId = 0;
2595 SocketAddress *const hosts = resolveHost(masterserver_name, masterserver_port);
2596
2597 if (hosts == nullptr)
2598 {
2599 debug(LOG_ERROR, "Cannot resolve masterserver \"%s\": %s", masterserver_name, strSockError(getSockErr()));
2600 free(NetPlay.MOTD);
2601 if (asprintf(&NetPlay.MOTD, _("Could not resolve masterserver name (%s)!"), masterserver_name) == -1)
2602 {
2603 NetPlay.MOTD = nullptr;
2604 }
2605 server_not_there = true;
2606 return bProcessingConnectOrDisconnectThisCall;
2607 }
2608
2609 // Close an existing socket.
2610 if (rs_socket != nullptr)
2611 {
2612 socketClose(rs_socket);
2613 rs_socket = nullptr;
2614 }
2615
2616 // try each address from resolveHost until we successfully connect.
2617 rs_socket = socketOpenAny(hosts, 1500);
2618 deleteSocketAddress(hosts);
2619
2620 // No address succeeded.
2621 if (rs_socket == nullptr)
2622 {
2623 debug(LOG_ERROR, "Cannot connect to masterserver \"%s:%d\": %s", masterserver_name, masterserver_port, strSockError(getSockErr()));
2624 free(NetPlay.MOTD);
2625 if (asprintf(&NetPlay.MOTD, _("Error connecting to the lobby server: %s.\nMake sure port %d can receive incoming connections.\nIf you're using a router configure it to use UPnP\n or to forward the port to your system."),
2626 strSockError(getSockErr()), masterserver_port) == -1)
2627 {
2628 NetPlay.MOTD = nullptr;
2629 }
2630 server_not_there = true;
2631 return bProcessingConnectOrDisconnectThisCall;
2632 }
2633
2634 // Get a game ID
2635 if (writeAll(rs_socket, "gaId", sizeof("gaId")) == SOCKET_ERROR
2636 || readAll(rs_socket, &gameId, sizeof(gameId), 10000) != sizeof(gameId))
2637 {
2638 free(NetPlay.MOTD);
2639 if (asprintf(&NetPlay.MOTD, "Failed to retrieve a game ID: %s", strSockError(getSockErr())) == -1)
2640 {
2641 NetPlay.MOTD = nullptr;
2642 }
2643 else
2644 {
2645 debug(LOG_ERROR, "%s", NetPlay.MOTD);
2646 }
2647
2648 // The socket has been invalidated, so get rid of it. (using them now may cause SIGPIPE).
2649 disconnect();
2650 return bProcessingConnectOrDisconnectThisCall;
2651 }
2652
2653 gamestruct.gameId = ntohl(gameId);
2654 debug(LOG_NET, "Using game ID: %u", (unsigned int)gamestruct.gameId);
2655
2656 // Register our game with the server
2657 if (writeAll(rs_socket, "addg", sizeof("addg")) == SOCKET_ERROR
2658 // and now send what the server wants
2659 || !NETsendGAMESTRUCT(rs_socket, &gamestruct))
2660 {
2661 debug(LOG_ERROR, "Failed to register game with server: %s", strSockError(getSockErr()));
2662 disconnect();
2663 return bProcessingConnectOrDisconnectThisCall;
2664 }
2665
2666 lastServerUpdate = realTime;
2667 queuedServerUpdate = false;
2668
2669 lastConnectionTime = realTime;
2670 waitingForConnectionFinalize = allocSocketSet();
2671 SocketSet_AddSocket(waitingForConnectionFinalize, rs_socket);
2672
2673 currentState = LobbyConnectionState::Connecting_WaitingForResponse;
2674 return bProcessingConnectOrDisconnectThisCall;
2675 }
2676
disconnect()2677 bool LobbyServerConnectionHandler::disconnect()
2678 {
2679 if (currentState == LobbyConnectionState::Disconnected)
2680 {
2681 return false; // already disconnected
2682 }
2683
2684 if (rs_socket != nullptr)
2685 {
2686 // we don't need this anymore, so clean up
2687 socketClose(rs_socket);
2688 rs_socket = nullptr;
2689 server_not_there = true;
2690 }
2691
2692 queuedServerUpdate = false;
2693
2694 currentState = LobbyConnectionState::Disconnected;
2695 return true;
2696 }
2697
sendUpdate()2698 void LobbyServerConnectionHandler::sendUpdate()
2699 {
2700 if (server_not_there)
2701 {
2702 return;
2703 }
2704
2705 if (canSendServerUpdateNow())
2706 {
2707 sendUpdateNow();
2708 }
2709 else
2710 {
2711 // queue future update
2712 debug(LOG_NET, "Queueing server update");
2713 queuedServerUpdate = true;
2714 }
2715 }
2716
sendUpdateNow()2717 void LobbyServerConnectionHandler::sendUpdateNow()
2718 {
2719 ASSERT_OR_RETURN(, rs_socket != nullptr, "Null socket");
2720 if (!NETsendGAMESTRUCT(rs_socket, &gamestruct))
2721 {
2722 disconnect();
2723 ActivityManager::instance().hostGameLobbyServerDisconnect();
2724 }
2725 lastServerUpdate = realTime;
2726 queuedServerUpdate = false;
2727 // newer lobby server will return a lobby response / status after each update call
2728 if (rs_socket && readLobbyResponse(rs_socket, NET_TIMEOUT_DELAY) == SOCKET_ERROR)
2729 {
2730 disconnect();
2731 ActivityManager::instance().hostGameLobbyServerDisconnect();
2732 }
2733 }
2734
sendKeepAlive()2735 void LobbyServerConnectionHandler::sendKeepAlive()
2736 {
2737 ASSERT_OR_RETURN(, rs_socket != nullptr, "Null socket");
2738 if (writeAll(rs_socket, "keep", sizeof("keep")) == SOCKET_ERROR)
2739 {
2740 // The socket has been invalidated, so get rid of it. (using them now may cause SIGPIPE).
2741 disconnect();
2742 ActivityManager::instance().hostGameLobbyServerDisconnect();
2743 }
2744 lastServerUpdate = realTime;
2745 }
2746
run()2747 void LobbyServerConnectionHandler::run()
2748 {
2749 switch (currentState)
2750 {
2751 case LobbyConnectionState::Disconnected:
2752 return;
2753 break;
2754 case LobbyConnectionState::Connecting_WaitingForResponse:
2755 {
2756 // check if response has been received
2757 ASSERT_OR_RETURN(, waitingForConnectionFinalize != nullptr, "Null socket set");
2758 ASSERT_OR_RETURN(, rs_socket != nullptr, "Null socket");
2759 bool exceededTimeout = (realTime - lastConnectionTime >= 10000);
2760 // We use readLobbyResponse to display error messages and handle state changes if there's no response
2761 // So if exceededTimeout, just call it with a low timeout
2762 int checkSocketRet = checkSockets(waitingForConnectionFinalize, NET_READ_TIMEOUT);
2763 if (checkSocketRet == SOCKET_ERROR)
2764 {
2765 debug(LOG_ERROR, "Lost connection to lobby server");
2766 disconnect();
2767 break;
2768 }
2769 if (exceededTimeout || (checkSocketRet > 0 && socketReadReady(rs_socket)))
2770 {
2771 if (readLobbyResponse(rs_socket, NET_TIMEOUT_DELAY) == SOCKET_ERROR)
2772 {
2773 disconnect();
2774 break;
2775 }
2776 deleteSocketSet(waitingForConnectionFinalize);
2777 waitingForConnectionFinalize = nullptr;
2778 currentState = LobbyConnectionState::Connected;
2779 }
2780 break;
2781 }
2782 case LobbyConnectionState::Connected:
2783 {
2784 // handle sending keep alive or queued updates
2785 if (!queuedServerUpdate)
2786 {
2787 if (allow_joining && shouldSendServerKeepAliveNow())
2788 {
2789 // ensure that the lobby server knows we're still alive by sending a no-op "keep-alive"
2790 sendKeepAlive();
2791 }
2792 break;
2793 }
2794 if (!canSendServerUpdateNow())
2795 {
2796 break;
2797 }
2798 queuedServerUpdate = false;
2799 sendUpdateNow();
2800 break;
2801 }
2802 }
2803 }
2804
NETregisterServer(int state)2805 bool NETregisterServer(int state)
2806 {
2807 switch (state)
2808 {
2809 case WZ_SERVER_UPDATE:
2810 lobbyConnectionHandler.sendUpdate();
2811 break;
2812 case WZ_SERVER_CONNECT:
2813 return lobbyConnectionHandler.connect();
2814 break;
2815 case WZ_SERVER_DISCONNECT:
2816 return lobbyConnectionHandler.disconnect();
2817 break;
2818 }
2819
2820 return false;
2821 }
2822
2823 // ////////////////////////////////////////////////////////////////////////
2824 // Check player "slots" & update player count if needed.
NETfixPlayerCount()2825 void NETfixPlayerCount()
2826 {
2827 int maxPlayers = game.maxPlayers;
2828 unsigned playercount = 0;
2829 for (int index = 0; index < game.maxPlayers; ++index)
2830 {
2831 if (NetPlay.players[index].ai == AI_CLOSED)
2832 {
2833 --maxPlayers;
2834 }
2835 else if (NetPlay.players[index].ai != AI_OPEN || NetPlay.players[index].allocated)
2836 {
2837 ++playercount;
2838 }
2839 }
2840
2841 if (allow_joining && NetPlay.isHost && (NetPlay.playercount != playercount || gamestruct.desc.dwMaxPlayers != maxPlayers))
2842 {
2843 debug(LOG_NET, "Updating player count from %d/%d to %d/%d", (int)NetPlay.playercount, gamestruct.desc.dwMaxPlayers, playercount, maxPlayers);
2844 gamestruct.desc.dwCurrentPlayers = NetPlay.playercount = playercount;
2845 gamestruct.desc.dwMaxPlayers = maxPlayers;
2846 NETregisterServer(WZ_SERVER_UPDATE);
2847 }
2848
2849 }
2850 // ////////////////////////////////////////////////////////////////////////
2851 // Host a game with a given name and player name. & 4 user game flags
NETallowJoining()2852 static void NETallowJoining()
2853 {
2854 unsigned int i;
2855 int32_t result;
2856 bool connectFailed = true;
2857 uint32_t major, minor;
2858 ssize_t recv_result = 0;
2859
2860 if (allow_joining == false)
2861 {
2862 return;
2863 }
2864 ASSERT(NetPlay.isHost, "Cannot receive joins if not host!");
2865
2866 bool bFirstTimeConnect = NETregisterServer(WZ_SERVER_CONNECT);
2867 if (bFirstTimeConnect)
2868 {
2869 ActivityManager::instance().updateMultiplayGameData(game, ingame, NETGameIsLocked());
2870 ActivitySink::ListeningInterfaces listeningInterfaces;
2871 if (tcp_socket != nullptr)
2872 {
2873 listeningInterfaces.IPv4 = socketHasIPv4(tcp_socket);
2874 if (listeningInterfaces.IPv4)
2875 {
2876 listeningInterfaces.ipv4_port = NETgetGameserverPort();
2877 }
2878 listeningInterfaces.IPv6 = socketHasIPv6(tcp_socket);
2879 if (listeningInterfaces.IPv6)
2880 {
2881 listeningInterfaces.ipv6_port = NETgetGameserverPort();
2882 }
2883 }
2884 ActivityManager::instance().hostGame(gamestruct.name, NetPlay.players[0].name, NETgetMasterserverName(), NETgetMasterserverPort(), listeningInterfaces, gamestruct.gameId);
2885 }
2886
2887 // This is here since we need to get the status, before we can show the info.
2888 // FIXME: find better location to stick this?
2889 if ((!NetPlay.ShowedMOTD) && (NetPlay.MOTD != nullptr))
2890 {
2891 ShowMOTD();
2892 free(NetPlay.MOTD);
2893 NetPlay.MOTD = nullptr;
2894 NetPlay.ShowedMOTD = true;
2895 }
2896
2897 if (tmp_socket_set == nullptr)
2898 {
2899 // initialize server socket set
2900 // FIXME: why is this not done in NETinit()?? - Per
2901 tmp_socket_set = allocSocketSet();
2902 if (tmp_socket_set == nullptr)
2903 {
2904 debug(LOG_ERROR, "Cannot create socket set: %s", strSockError(getSockErr()));
2905 return;
2906 }
2907 }
2908
2909 // Find the first empty socket slot
2910 for (i = 0; i < MAX_TMP_SOCKETS; ++i)
2911 {
2912 if (tmp_socket[i] == nullptr)
2913 {
2914 break;
2915 }
2916 }
2917 if (i == MAX_TMP_SOCKETS)
2918 {
2919 // this should *never* happen, it would mean we are going to reuse a socket already in use.
2920 debug(LOG_ERROR, "all temp sockets are used up!");
2921 return;
2922 }
2923
2924 // See if there's an incoming connection
2925 if (tmp_socket[i] == nullptr // Make sure that we're not out of sockets
2926 && (tmp_socket[i] = socketAccept(tcp_socket)) != nullptr)
2927 {
2928 NETinitQueue(NETnetTmpQueue(i));
2929 SocketSet_AddSocket(tmp_socket_set, tmp_socket[i]);
2930
2931 char buffer[10] = {'\0'};
2932 char *p_buffer = buffer;
2933
2934 // We check for socket activity (connection), and then we check if we got data, since it is possible to have a connection
2935 // and have no data waiting.
2936 if (checkSockets(tmp_socket_set, NET_TIMEOUT_DELAY) > 0
2937 && socketReadReady(tmp_socket[i])
2938 && (recv_result = readNoInt(tmp_socket[i], p_buffer, 8))
2939 && recv_result != SOCKET_ERROR)
2940 {
2941 std::string rIP = "Incoming connection from:";
2942 rIP.append(getSocketTextAddress(tmp_socket[i]));
2943 NETlogEntry(rIP.c_str(), SYNC_FLAG, i);
2944 // A 2.3.7 client sends a "list" command first, just drop the connection.
2945 if (strcmp(buffer, "list") == 0)
2946 {
2947 debug(LOG_INFO, "An old client tried to connect, closing the socket.");
2948 NETlogEntry("Dropping old client.", SYNC_FLAG, i);
2949 NETlogEntry("Invalid (old)game version", SYNC_FLAG, i);
2950 addToBanList(rIP.c_str(), "BAD_USER");
2951 connectFailed = true;
2952 }
2953 else
2954 {
2955 // New clients send NETCODE_VERSION_MAJOR and NETCODE_VERSION_MINOR
2956 // Check these numbers with our own.
2957
2958 memcpy(&major, p_buffer, sizeof(uint32_t));
2959 major = ntohl(major);
2960 p_buffer += sizeof(int32_t);
2961 memcpy(&minor, p_buffer, sizeof(uint32_t));
2962 minor = ntohl(minor);
2963
2964 if (major == 0 && minor == 0)
2965 {
2966 // special case for lobby server "alive" check
2967 // expects a special response that includes the gameId
2968 const char ResponseStart[] = "WZLR";
2969 char buf[(sizeof(char) * 4) + sizeof(uint32_t) + sizeof(uint32_t)] = { 0 };
2970 char *pLobbyRespBuffer = buf;
2971 auto push32 = [&pLobbyRespBuffer](uint32_t value) {
2972 uint32_t swapped = htonl(value);
2973 memcpy(pLobbyRespBuffer, &swapped, sizeof(swapped));
2974 pLobbyRespBuffer += sizeof(swapped);
2975 };
2976
2977 // Copy response prefix chars ("WZLR")
2978 memcpy(pLobbyRespBuffer, ResponseStart, sizeof(char) * strlen(ResponseStart));
2979 pLobbyRespBuffer += sizeof(char) * strlen(ResponseStart);
2980
2981 // Copy version of response
2982 const uint32_t response_version = 1;
2983 push32(response_version);
2984
2985 // Copy gameId (as 32bit large big endian number)
2986 push32(gamestruct.gameId);
2987
2988 writeAll(tmp_socket[i], buf, sizeof(buf));
2989 connectFailed = true;
2990 }
2991 else if (NETisCorrectVersion(major, minor))
2992 {
2993 result = htonl(ERROR_NOERROR);
2994 memcpy(&buffer, &result, sizeof(result));
2995 writeAll(tmp_socket[i], &buffer, sizeof(result));
2996 socketBeginCompression(tmp_socket[i]);
2997
2998 // Connection is successful.
2999 connectFailed = false;
3000 }
3001 else
3002 {
3003 debug(LOG_ERROR, "Received an invalid version \"%d.%d\".", major, minor);
3004 result = htonl(ERROR_WRONGVERSION);
3005 memcpy(&buffer, &result, sizeof(result));
3006 writeAll(tmp_socket[i], &buffer, sizeof(result));
3007 NETlogEntry("Invalid game version", SYNC_FLAG, i);
3008 addToBanList(rIP.c_str(), "BAD_USER");
3009 connectFailed = true;
3010 }
3011 if ((!connectFailed) && ((int)NetPlay.playercount == gamestruct.desc.dwMaxPlayers))
3012 {
3013 // early player count test, in case they happen to get in before updates.
3014 // Tell the player that we are full.
3015 uint8_t rejected = ERROR_FULL;
3016 NETbeginEncode(NETnetTmpQueue(i), NET_REJECTED);
3017 NETuint8_t(&rejected);
3018 NETend();
3019 NETflush();
3020 connectFailed = true;
3021 }
3022 }
3023 }
3024 else
3025 {
3026 debug(LOG_NET, "Failed to process joining, socket not ready or no data, recv_result is :%d", (int)recv_result);
3027 connectFailed = true;
3028 }
3029
3030 // Remove a failed connection.
3031 if (connectFailed)
3032 {
3033 debug(LOG_NET, "freeing temp socket %p (%d)", static_cast<void *>(tmp_socket[i]), __LINE__);
3034 SocketSet_DelSocket(tmp_socket_set, tmp_socket[i]);
3035 socketClose(tmp_socket[i]);
3036 tmp_socket[i] = nullptr;
3037 }
3038 }
3039
3040 if (checkSockets(tmp_socket_set, NET_READ_TIMEOUT) > 0)
3041 {
3042 for (i = 0; i < MAX_TMP_SOCKETS; ++i)
3043 {
3044 if (tmp_socket[i] != nullptr
3045 && socketReadReady(tmp_socket[i]))
3046 {
3047 uint8_t buffer[NET_BUFFER_SIZE];
3048 ssize_t size = readNoInt(tmp_socket[i], buffer, sizeof(buffer));
3049
3050 if ((size == 0 && socketReadDisconnected(tmp_socket[i])) || size == SOCKET_ERROR)
3051 {
3052 // disconnect or programmer error
3053 if (size == 0)
3054 {
3055 debug(LOG_NET, "Client socket disconnected.");
3056 }
3057 else
3058 {
3059 debug(LOG_NET, "Client socket encountered error: %s", strSockError(getSockErr()));
3060 }
3061 NETlogEntry("Client socket disconnected (allowJoining)", SYNC_FLAG, i);
3062 debug(LOG_NET, "freeing temp socket %p (%d)", static_cast<void *>(tmp_socket[i]), __LINE__);
3063 SocketSet_DelSocket(tmp_socket_set, tmp_socket[i]);
3064 socketClose(tmp_socket[i]);
3065 tmp_socket[i] = nullptr;
3066 continue;
3067 }
3068
3069 NETinsertRawData(NETnetTmpQueue(i), buffer, size);
3070
3071 if (NETisMessageReady(NETnetTmpQueue(i)) && NETgetMessage(NETnetTmpQueue(i))->type == NET_JOIN)
3072 {
3073 uint8_t j;
3074 uint8_t index;
3075 uint8_t rejected = 0;
3076 int tmp;
3077
3078 char name[64];
3079 char ModList[modlist_string_size] = { '\0' };
3080 char GamePassword[password_string_size] = { '\0' };
3081
3082 NETbeginDecode(NETnetTmpQueue(i), NET_JOIN);
3083 NETstring(name, sizeof(name));
3084 NETstring(ModList, sizeof(ModList));
3085 NETstring(GamePassword, sizeof(GamePassword));
3086 NETend();
3087
3088 tmp = NET_CreatePlayer(name);
3089
3090 if (tmp == -1)
3091 {
3092 debug(LOG_ERROR, "freeing temp socket %p, couldn't create player!", static_cast<void *>(tmp_socket[i]));
3093
3094 // Tell the player that we are full.
3095 rejected = ERROR_FULL;
3096 NETbeginEncode(NETnetTmpQueue(i), NET_REJECTED);
3097 NETuint8_t(&rejected);
3098 NETend();
3099 NETflush();
3100 NETpop(NETnetTmpQueue(i));
3101
3102 SocketSet_DelSocket(tmp_socket_set, tmp_socket[i]);
3103 socketClose(tmp_socket[i]);
3104 tmp_socket[i] = nullptr;
3105 sync_counter.cantjoin++;
3106 return;
3107 }
3108
3109 NETpop(NETnetTmpQueue(i));
3110 index = tmp;
3111
3112 debug(LOG_NET, "freeing temp socket %p (%d), creating permanent socket.", static_cast<void *>(tmp_socket[i]), __LINE__);
3113 SocketSet_DelSocket(tmp_socket_set, tmp_socket[i]);
3114 connected_bsocket[index] = tmp_socket[i];
3115 tmp_socket[i] = nullptr;
3116 SocketSet_AddSocket(socket_set, connected_bsocket[index]);
3117 NETmoveQueue(NETnetTmpQueue(i), NETnetQueue(index));
3118
3119 // Copy player's IP address.
3120 sstrcpy(NetPlay.players[index].IPtextAddress, getSocketTextAddress(connected_bsocket[index]));
3121
3122 if (onBanList(NetPlay.players[index].IPtextAddress))
3123 {
3124 char buf[256] = {'\0'};
3125 ssprintf(buf, "** A player that you have kicked tried to rejoin the game, and was rejected. IP: %s", NetPlay.players[index].IPtextAddress);
3126 debug(LOG_INFO, "%s", buf);
3127 NETlogEntry(buf, SYNC_FLAG, i);
3128
3129 // Player has been kicked before, kick again.
3130 rejected = (uint8_t)ERROR_KICKED;
3131 }
3132 else if (NetPlay.GamePassworded && strcmp(NetPlay.gamePassword, GamePassword) != 0)
3133 {
3134 // Wrong password. Reject.
3135 rejected = (uint8_t)ERROR_WRONGPASSWORD;
3136 }
3137 else if ((int)NetPlay.playercount > gamestruct.desc.dwMaxPlayers)
3138 {
3139 // Game full. Reject.
3140 rejected = (uint8_t)ERROR_FULL;
3141 }
3142
3143 if (rejected)
3144 {
3145 char buf[256] = {'\0'};
3146 ssprintf(buf, "**Rejecting player(%s), reason (%u). ", NetPlay.players[index].IPtextAddress, (unsigned int) rejected);
3147 debug(LOG_INFO, "%s", buf);
3148 NETlogEntry(buf, SYNC_FLAG, index);
3149 NETbeginEncode(NETnetQueue(index), NET_REJECTED);
3150 NETuint8_t(&rejected);
3151 NETend();
3152 NETflush();
3153
3154 allow_joining = false; // no need to inform master server
3155 NET_DestroyPlayer(index);
3156 allow_joining = true;
3157
3158 SocketSet_DelSocket(socket_set, connected_bsocket[index]);
3159 socketClose(connected_bsocket[index]);
3160 connected_bsocket[index] = nullptr;
3161 return;
3162 }
3163
3164 NETbeginEncode(NETnetQueue(index), NET_ACCEPTED);
3165 NETuint8_t(&index);
3166 NETend();
3167
3168 // First send info about players to newcomer.
3169 NETSendAllPlayerInfoTo(index);
3170 // then send info about newcomer to all players.
3171 NETBroadcastPlayerInfo(index);
3172
3173 char buf[250] = {'\0'};
3174 snprintf(buf, sizeof(buf), "Player %s has joined, IP is: %s", name, NetPlay.players[index].IPtextAddress);
3175 debug(LOG_INFO, "%s", buf);
3176 NETlogEntry(buf, SYNC_FLAG, index);
3177
3178 debug(LOG_NET, "Player, %s, with index of %u has joined using socket %p", name, (unsigned int)index, static_cast<void *>(connected_bsocket[index]));
3179
3180 // Increment player count
3181 gamestruct.desc.dwCurrentPlayers++;
3182
3183 MultiPlayerJoin(index);
3184
3185 // Narrowcast to new player that everyone has joined.
3186 for (j = 0; j < MAX_CONNECTED_PLAYERS; ++j)
3187 {
3188 if (index != j) // We will broadcast the index == j case.
3189 {
3190 if (NetPlay.players[j].allocated)
3191 {
3192 NETbeginEncode(NETnetQueue(index), NET_PLAYER_JOINED);
3193 NETuint8_t(&j);
3194 NETend();
3195 }
3196 }
3197 }
3198
3199 // Broadcast to everyone that a new player has joined
3200 NETbeginEncode(NETbroadcastQueue(), NET_PLAYER_JOINED);
3201 NETuint8_t(&index);
3202 NETend();
3203
3204 for (j = 0; j < MAX_CONNECTED_PLAYERS; ++j)
3205 {
3206 NETBroadcastPlayerInfo(j);
3207 }
3208 NETfixDuplicatePlayerNames();
3209
3210 // Send the updated GAMESTRUCT to the masterserver
3211 NETregisterServer(WZ_SERVER_UPDATE);
3212
3213 // reset flags for new players
3214 NetPlay.players[index].wzFiles.clear();
3215 }
3216 }
3217 }
3218 }
3219 }
3220
NETloadBanList()3221 void NETloadBanList() {
3222 char BanListPath[4096] = {0};
3223 strncpy(BanListPath, PHYSFS_getWriteDir(), 4095);
3224 size_t BanListAppendFname = strlen(BanListPath);
3225 strncpy(BanListPath+BanListAppendFname, "/banlist.txt", 4095-BanListAppendFname);
3226 FILE* f = fopen(BanListPath, "r");
3227 if(f == NULL) {
3228 return;
3229 }
3230 debug(LOG_INFO, "Reading banlist file: [%s]\n", BanListPath);
3231 char BanStringBuf[2048] = {0};
3232 char ToBanIP[256] = {0};
3233 char ToBanName[256] = {0};
3234 while(fgets(BanStringBuf, sizeof(BanStringBuf)-1, f)) {
3235 if(sscanf(BanStringBuf, "%255s %255[^\n]", ToBanIP, ToBanName) != 2) {
3236 if(strlen(BanStringBuf) > 2) {
3237 debug(LOG_ERROR, "Error reading banlist file!\n");
3238 }
3239 } else {
3240 addToBanList(ToBanIP, ToBanName);
3241 }
3242 }
3243 return;
3244 }
3245
NEThostGame(const char * SessionName,const char * PlayerName,SDWORD one,SDWORD two,SDWORD three,SDWORD four,UDWORD plyrs)3246 bool NEThostGame(const char *SessionName, const char *PlayerName,
3247 SDWORD one, SDWORD two, SDWORD three, SDWORD four,
3248 UDWORD plyrs) // # of players.
3249 {
3250 debug(LOG_NET, "NEThostGame(%s, %s, %d, %d, %d, %d, %u)", SessionName, PlayerName,
3251 one, two, three, four, plyrs);
3252
3253 netPlayersUpdated = true;
3254
3255 for (unsigned playerIndex = 0; playerIndex < MAX_PLAYERS; ++playerIndex)
3256 {
3257 initPlayerNetworkProps(playerIndex);
3258 }
3259 if (!NetPlay.bComms)
3260 {
3261 selectedPlayer = 0;
3262 NetPlay.isHost = true;
3263 NetPlay.players[0].allocated = true;
3264 NetPlay.players[0].connection = -1;
3265 NetPlay.playercount = 1;
3266 debug(LOG_NET, "Hosting but no comms");
3267 // Now switch player color of the host to what they normally use for MP games
3268 if (war_getMPcolour() >= 0)
3269 {
3270 changeColour(NET_HOST_ONLY, war_getMPcolour(), true);
3271 }
3272 return true;
3273 }
3274
3275 // tcp_socket is the connection to the lobby server (or machine)
3276 if (!tcp_socket)
3277 {
3278 tcp_socket = socketListen(gameserver_port);
3279 }
3280 if (tcp_socket == nullptr)
3281 {
3282 debug(LOG_ERROR, "Cannot connect to master self: %s", strSockError(getSockErr()));
3283 return false;
3284 }
3285 debug(LOG_NET, "New tcp_socket = %p", static_cast<void *>(tcp_socket));
3286 // Host needs to create a socket set for MAX_PLAYERS
3287 if (!socket_set)
3288 {
3289 socket_set = allocSocketSet();
3290 }
3291 if (socket_set == nullptr)
3292 {
3293 debug(LOG_ERROR, "Cannot create socket set: %s", strSockError(getSockErr()));
3294 return false;
3295 }
3296 // allocate socket storage for all possible players
3297 for (unsigned i = 0; i < MAX_CONNECTED_PLAYERS; ++i)
3298 {
3299 connected_bsocket[i] = nullptr;
3300 NETinitQueue(NETnetQueue(i));
3301 }
3302
3303 NetPlay.isHost = true;
3304 NETlogEntry("Hosting game, resetting ban list.", SYNC_FLAG, 0);
3305 if (IPlist)
3306 {
3307 free(IPlist);
3308 IPlist = nullptr;
3309 IPlistLast = 0;
3310 }
3311 NETloadBanList();
3312 sstrcpy(gamestruct.name, SessionName);
3313 memset(&gamestruct.desc, 0, sizeof(gamestruct.desc));
3314 gamestruct.desc.dwSize = sizeof(gamestruct.desc);
3315 //gamestruct.desc.guidApplication = GAME_GUID;
3316 memset(gamestruct.desc.host, 0, sizeof(gamestruct.desc.host));
3317 gamestruct.desc.dwCurrentPlayers = 1;
3318 gamestruct.desc.dwMaxPlayers = plyrs;
3319 gamestruct.desc.dwFlags = 0;
3320 gamestruct.desc.dwUserFlags[0] = one;
3321 gamestruct.desc.dwUserFlags[1] = two;
3322 gamestruct.desc.dwUserFlags[2] = three;
3323 gamestruct.desc.dwUserFlags[3] = four;
3324 memset(gamestruct.secondaryHosts, 0, sizeof(gamestruct.secondaryHosts));
3325 sstrcpy(gamestruct.extra, "Extra"); // extra string (future use)
3326 gamestruct.hostPort = gameserver_port;
3327 sstrcpy(gamestruct.mapname, game.map); // map we are hosting
3328 sstrcpy(gamestruct.hostname, PlayerName);
3329 sstrcpy(gamestruct.versionstring, versionString); // version (string)
3330 sstrcpy(gamestruct.modlist, getModList().c_str()); // List of mods
3331 gamestruct.GAMESTRUCT_VERSION = 4; // version of this structure
3332 gamestruct.game_version_major = NETCODE_VERSION_MAJOR; // Netcode Major version
3333 gamestruct.game_version_minor = NETCODE_VERSION_MINOR; // NetCode Minor version
3334 // gamestruct.privateGame = 0; // if true, it is a private game
3335 gamestruct.pureMap = game.isMapMod; // If map-mod...
3336 gamestruct.Mods = 0; // number of concatenated mods?
3337 gamestruct.gameId = 0;
3338 gamestruct.limits = 0x0; // used for limits
3339 #if defined(WZ_OS_WIN)
3340 gamestruct.future3 = 0x77696e; // for future use
3341 #elif defined (WZ_OS_MAC)
3342 gamestruct.future3 = 0x6d6163; // for future use
3343 #else
3344 gamestruct.future3 = 0x6c696e; // for future use
3345 #endif
3346 gamestruct.future4 = NETCODE_VERSION_MAJOR << 16 | NETCODE_VERSION_MINOR; // for future use
3347
3348 selectedPlayer = NET_CreatePlayer(PlayerName, (getHostLaunch() == HostLaunch::Autohost));
3349 ASSERT_OR_RETURN(false, selectedPlayer < MAX_PLAYERS, "Failed to create player");
3350 realSelectedPlayer = selectedPlayer;
3351 NetPlay.isHost = true;
3352 NetPlay.isHostAlive = true;
3353 NetPlay.HaveUpgrade = false;
3354 NetPlay.hostPlayer = NET_HOST_ONLY;
3355 ASSERT(selectedPlayer == NET_HOST_ONLY, "For now, host must start at player index zero, was %d", (int)selectedPlayer);
3356
3357 MultiPlayerJoin(selectedPlayer);
3358
3359 // Now switch player color of the host to what they normally use for SP games
3360 if (war_getMPcolour() >= 0)
3361 {
3362 changeColour(NET_HOST_ONLY, war_getMPcolour(), true);
3363 }
3364
3365 allow_joining = true;
3366
3367 NETregisterServer(WZ_SERVER_DISCONNECT);
3368
3369 debug(LOG_NET, "Hosting a server. We are player %d.", selectedPlayer);
3370
3371 return true;
3372 }
3373
3374 // ////////////////////////////////////////////////////////////////////////
3375 // Stop the dplay interface from accepting more players.
NEThaltJoining()3376 bool NEThaltJoining()
3377 {
3378 debug(LOG_NET, "temporarily locking game to prevent more players");
3379
3380 allow_joining = false;
3381 // disconnect from the master server
3382 NETregisterServer(WZ_SERVER_DISCONNECT);
3383 return true;
3384 }
3385
3386 // ////////////////////////////////////////////////////////////////////////
3387 // find games on open connection
NETenumerateGames(const std::function<bool (const GAMESTRUCT & game)> & handleEnumerateGameFunc)3388 bool NETenumerateGames(const std::function<bool (const GAMESTRUCT& game)>& handleEnumerateGameFunc)
3389 {
3390 SocketAddress *hosts;
3391 int result = 0;
3392 debug(LOG_NET, "Looking for games...");
3393
3394 if (getLobbyError() == ERROR_INVALID || getLobbyError() == ERROR_KICKED || getLobbyError() == ERROR_HOSTDROPPED)
3395 {
3396 return false;
3397 }
3398 setLobbyError(ERROR_NOERROR);
3399
3400 if (!NetPlay.bComms)
3401 {
3402 debug(LOG_ERROR, "Likely missing NETinit(true) - this won't return any results");
3403 return false;
3404 }
3405 if ((hosts = resolveHost(masterserver_name, masterserver_port)) == nullptr)
3406 {
3407 debug(LOG_ERROR, "Cannot resolve hostname \"%s\": %s", masterserver_name, strSockError(getSockErr()));
3408 setLobbyError(ERROR_CONNECTION);
3409 return false;
3410 }
3411
3412 if (tcp_socket != nullptr)
3413 {
3414 debug(LOG_NET, "Deleting tcp_socket %p", static_cast<void *>(tcp_socket));
3415 if (socket_set)
3416 {
3417 SocketSet_DelSocket(socket_set, tcp_socket);
3418 }
3419 socketClose(tcp_socket);
3420 tcp_socket = nullptr;
3421 }
3422
3423 tcp_socket = socketOpenAny(hosts, 15000);
3424
3425 deleteSocketAddress(hosts);
3426 hosts = nullptr;
3427
3428 if (tcp_socket == nullptr)
3429 {
3430 debug(LOG_ERROR, "Cannot connect to \"%s:%d\": %s", masterserver_name, masterserver_port, strSockError(getSockErr()));
3431 setLobbyError(ERROR_CONNECTION);
3432 return false;
3433 }
3434 debug(LOG_NET, "New tcp_socket = %p", static_cast<void *>(tcp_socket));
3435 // client machines only need 1 socket set
3436 socket_set = allocSocketSet();
3437 if (socket_set == nullptr)
3438 {
3439 debug(LOG_ERROR, "Cannot create socket set: %s", strSockError(getSockErr()));
3440 setLobbyError(ERROR_CONNECTION);
3441 return false;
3442 }
3443 debug(LOG_NET, "Created socket_set %p", static_cast<void *>(socket_set));
3444
3445 SocketSet_AddSocket(socket_set, tcp_socket);
3446
3447 debug(LOG_NET, "Sending list cmd");
3448
3449 if (writeAll(tcp_socket, "list", sizeof("list")) == SOCKET_ERROR)
3450 {
3451 debug(LOG_NET, "Server socket encountered error: %s", strSockError(getSockErr()));
3452 SocketSet_DelSocket(socket_set, tcp_socket); // mark it invalid
3453 socketClose(tcp_socket);
3454 tcp_socket = nullptr;
3455
3456 // when we fail to receive a game count, bail out
3457 setLobbyError(ERROR_CONNECTION);
3458 return false;
3459 }
3460
3461 // Retrieve the first batch of game structs
3462 // Earlier versions (and earlier lobby servers) restricted this to no more than 11
3463 std::vector<GAMESTRUCT> initialBatchOfGameStructs;
3464
3465 if (!readGameStructsList(tcp_socket, NET_TIMEOUT_DELAY, [&initialBatchOfGameStructs](const GAMESTRUCT &lobbyGame) -> bool {
3466 initialBatchOfGameStructs.push_back(lobbyGame);
3467 return true; // continue enumerating
3468 }))
3469 {
3470 SocketSet_DelSocket(socket_set, tcp_socket); // mark it invalid
3471 socketClose(tcp_socket);
3472 tcp_socket = nullptr;
3473
3474 setLobbyError(ERROR_CONNECTION);
3475 return false;
3476 }
3477
3478 // read the lobby response
3479 if (readLobbyResponse(tcp_socket, NET_TIMEOUT_DELAY) == SOCKET_ERROR)
3480 {
3481 socketClose(tcp_socket);
3482 tcp_socket = nullptr;
3483 addConsoleMessage(_("Failed to get a lobby response!"), DEFAULT_JUSTIFY, NOTIFY_MESSAGE);
3484
3485 // treat as fatal error
3486 setLobbyError(ERROR_CONNECTION);
3487 return false;
3488 }
3489
3490 // Backwards-compatible protocol enhancement, to raise game limit
3491 // Expects a uint32_t to determine whether to ignore the first batch of game structs
3492 // Earlier lobby servers will not provide anything, or may null-pad the lobby response / MOTD string
3493 // Hence as long as we don't treat "0" as signifying any change in behavior, this should be safe + backwards-compatible
3494 #define IGNORE_FIRST_BATCH 1
3495 uint32_t responseParameters = 0;
3496 if ((result = readAll(tcp_socket, &responseParameters, sizeof(responseParameters), NET_TIMEOUT_DELAY)) == sizeof(responseParameters))
3497 {
3498 responseParameters = ntohl(responseParameters);
3499
3500 bool requestSecondBatch = true;
3501 bool ignoreFirstBatch = ((responseParameters & IGNORE_FIRST_BATCH) == IGNORE_FIRST_BATCH);
3502 if (!ignoreFirstBatch)
3503 {
3504 // pass the first batch to the handleEnumerateGameFunc
3505 for (const auto& lobbyGame : initialBatchOfGameStructs)
3506 {
3507 if (!handleEnumerateGameFunc(lobbyGame))
3508 {
3509 // stop enumerating
3510 requestSecondBatch = false;
3511 break;
3512 }
3513 }
3514 }
3515
3516 if (requestSecondBatch)
3517 {
3518 if (!readGameStructsList(tcp_socket, NET_TIMEOUT_DELAY, handleEnumerateGameFunc))
3519 {
3520 // we failed to read a second list of game structs
3521
3522 if (!ignoreFirstBatch)
3523 {
3524 // just log and treat the error as non-fatal
3525 debug(LOG_NET, "Second readGameStructsList call failed - ignoring");
3526 }
3527 else
3528 {
3529 // if ignoring the first batch, treat this as a fatal error
3530 debug(LOG_NET, "Second readGameStructsList call failed");
3531
3532 SocketSet_DelSocket(socket_set, tcp_socket); // mark it invalid
3533 socketClose(tcp_socket);
3534 tcp_socket = nullptr;
3535
3536 // when we fail to receive a game count, bail out
3537 setLobbyError(ERROR_CONNECTION);
3538 return false;
3539 }
3540 }
3541 }
3542 }
3543 else
3544 {
3545 // to support earlier lobby servers that don't provide this additional second batch (and optional parameter), just process the first batch
3546 for (const auto& lobbyGame : initialBatchOfGameStructs)
3547 {
3548 if (!handleEnumerateGameFunc(lobbyGame))
3549 {
3550 // stop enumerating
3551 break;
3552 }
3553 }
3554 }
3555
3556 SocketSet_DelSocket(socket_set, tcp_socket); // mark it invalid (we are done with it)
3557 socketClose(tcp_socket);
3558 tcp_socket = nullptr;
3559
3560 return true;
3561 }
3562
NETfindGames(std::vector<GAMESTRUCT> & results,size_t startingIndex,size_t resultsLimit,bool onlyMatchingLocalVersion)3563 bool NETfindGames(std::vector<GAMESTRUCT>& results, size_t startingIndex, size_t resultsLimit, bool onlyMatchingLocalVersion /*= false*/)
3564 {
3565 size_t gamecount = 0;
3566 results.clear();
3567 bool success = NETenumerateGames([&results, &gamecount, startingIndex, resultsLimit, onlyMatchingLocalVersion](const GAMESTRUCT &lobbyGame) -> bool {
3568 if (gamecount++ < startingIndex)
3569 {
3570 // skip this item, continue
3571 return true;
3572 }
3573 if ((resultsLimit > 0) && (results.size() >= resultsLimit))
3574 {
3575 // stop processing games
3576 return false;
3577 }
3578 if ((onlyMatchingLocalVersion) && ((lobbyGame.game_version_major != (unsigned)NETGetMajorVersion()) || (lobbyGame.game_version_minor != (unsigned)NETGetMinorVersion())))
3579 {
3580 // skip this non-matching version, continue
3581 return true;
3582 }
3583 results.push_back(lobbyGame);
3584 return true;
3585 });
3586
3587 return success;
3588 }
3589
NETfindGame(uint32_t gameId,GAMESTRUCT & output)3590 bool NETfindGame(uint32_t gameId, GAMESTRUCT& output)
3591 {
3592 bool foundMatch = false;
3593 memset(&output, 0x00, sizeof(output));
3594 NETenumerateGames([&foundMatch, &output, gameId](const GAMESTRUCT &lobbyGame) -> bool {
3595 if (lobbyGame.gameId != gameId)
3596 {
3597 // not a match - continue enumerating
3598 return true;
3599 }
3600 output = lobbyGame;
3601 foundMatch = true;
3602 return false; // stop searching
3603 });
3604 return foundMatch;
3605 }
3606
3607 // ////////////////////////////////////////////////////////////////////////
3608 // ////////////////////////////////////////////////////////////////////////
3609 // Functions used to setup and join games.
NETjoinGame(const char * host,uint32_t port,const char * playername)3610 bool NETjoinGame(const char *host, uint32_t port, const char *playername)
3611 {
3612 SocketAddress *hosts = nullptr;
3613 unsigned int i;
3614 char buffer[sizeof(int32_t) * 2] = { 0 };
3615 char *p_buffer;
3616 uint32_t result;
3617
3618 if (port == 0)
3619 {
3620 port = gameserver_port;
3621 }
3622
3623 debug(LOG_NET, "resetting sockets.");
3624 NETclose(); // just to be sure :)
3625
3626 debug(LOG_NET, "Trying to join [%s]:%d ...", host, port);
3627
3628 netPlayersUpdated = true;
3629
3630 hosts = resolveHost(host, port);
3631 if (hosts == nullptr)
3632 {
3633 debug(LOG_ERROR, "Cannot resolve hostname \"%s\": %s", host, strSockError(getSockErr()));
3634 return false;
3635 }
3636
3637 if (tcp_socket != nullptr)
3638 {
3639 socketClose(tcp_socket);
3640 }
3641
3642 tcp_socket = socketOpenAny(hosts, 15000);
3643 deleteSocketAddress(hosts);
3644
3645 if (tcp_socket == nullptr)
3646 {
3647 debug(LOG_ERROR, "Cannot connect to [%s]:%d, %s", host, port, strSockError(getSockErr()));
3648 return false;
3649 }
3650
3651 // client machines only need 1 socket set
3652 socket_set = allocSocketSet();
3653 if (socket_set == nullptr)
3654 {
3655 debug(LOG_ERROR, "Cannot create socket set: %s", strSockError(getSockErr()));
3656 return false;
3657 }
3658 debug(LOG_NET, "Created socket_set %p", static_cast<void *>(socket_set));
3659
3660 // tcp_socket is used to talk to host machine
3661 SocketSet_AddSocket(socket_set, tcp_socket);
3662
3663 // Send NETCODE_VERSION_MAJOR and NETCODE_VERSION_MINOR
3664 p_buffer = buffer;
3665 auto pushu32 = [&](uint32_t value) {
3666 uint32_t swapped = htonl(value);
3667 memcpy(p_buffer, &swapped, sizeof(swapped));
3668 p_buffer += sizeof(swapped);
3669 };
3670 pushu32(NETCODE_VERSION_MAJOR);
3671 pushu32(NETCODE_VERSION_MINOR);
3672
3673 if (writeAll(tcp_socket, buffer, sizeof(buffer)) == SOCKET_ERROR
3674 || readAll(tcp_socket, &result, sizeof(result), 1500) != sizeof(result))
3675 {
3676 debug(LOG_ERROR, "Couldn't send my version.");
3677 return false;
3678 }
3679
3680 result = ntohl(result);
3681 if (result != ERROR_NOERROR)
3682 {
3683 debug(LOG_ERROR, "Received error %d", result);
3684
3685 SocketSet_DelSocket(socket_set, tcp_socket);
3686 socketClose(tcp_socket);
3687 tcp_socket = nullptr;
3688 deleteSocketSet(socket_set);
3689 socket_set = nullptr;
3690
3691 setLobbyError((LOBBY_ERROR_TYPES)result);
3692 return false;
3693 }
3694
3695 // Allocate memory for a new socket
3696 NETinitQueue(NETnetQueue(NET_HOST_ONLY));
3697 // NOTE: tcp_socket = bsocket now!
3698 bsocket = tcp_socket;
3699 tcp_socket = nullptr;
3700 socketBeginCompression(bsocket);
3701
3702 // Send a join message to the host
3703 NETbeginEncode(NETnetQueue(NET_HOST_ONLY), NET_JOIN);
3704 NETstring(playername, 64);
3705 NETstring(getModList().c_str(), modlist_string_size);
3706 NETstring(NetPlay.gamePassword, sizeof(NetPlay.gamePassword));
3707 NETend();
3708 if (bsocket == nullptr)
3709 {
3710 return false; // Connection dropped while sending NET_JOIN.
3711 }
3712 socketFlush(bsocket); // Make sure the message was completely sent.
3713
3714 i = wzGetTicks();
3715 // Loop until we've been accepted into the game
3716 for (;;)
3717 {
3718 NETQUEUE queue;
3719 uint8_t type;
3720
3721 // FIXME: shouldn't there be some sort of rejection message?
3722 if ((unsigned)wzGetTicks() > i + 5000)
3723 {
3724 // timeout
3725 return false;
3726 }
3727
3728 if (bsocket == nullptr)
3729 {
3730 return false; // Connection dropped.
3731 }
3732
3733 if (!NETrecvNet(&queue, &type))
3734 {
3735 continue;
3736 }
3737
3738 if (type == NET_ACCEPTED)
3739 {
3740 // :)
3741 uint8_t index;
3742
3743 NETbeginDecode(queue, NET_ACCEPTED);
3744 // Retrieve the player ID the game host arranged for us
3745 NETuint8_t(&index);
3746 NETend();
3747 NETpop(queue);
3748
3749 selectedPlayer = index;
3750 realSelectedPlayer = selectedPlayer;
3751 debug(LOG_NET, "NET_ACCEPTED received. Accepted into the game - I'm player %u using bsocket %p", (unsigned int)index, static_cast<void *>(bsocket));
3752 NetPlay.isHost = false;
3753 NetPlay.isHostAlive = true;
3754
3755 if (index >= MAX_CONNECTED_PLAYERS)
3756 {
3757 debug(LOG_ERROR, "Bad player number (%u) received from host!", index);
3758 return false;
3759 }
3760
3761 NetPlay.players[index].allocated = true;
3762 sstrcpy(NetPlay.players[index].name, playername);
3763 NetPlay.players[index].heartbeat = true;
3764
3765 return true;
3766 }
3767 else if (type == NET_REJECTED)
3768 {
3769 uint8_t rejection = 0;
3770
3771 NETbeginDecode(queue, NET_REJECTED);
3772 NETuint8_t(&rejection);
3773 NETend();
3774 NETpop(queue);
3775
3776 debug(LOG_NET, "NET_REJECTED received. Error code: %u", (unsigned int) rejection);
3777
3778 setLobbyError((LOBBY_ERROR_TYPES)rejection);
3779 NETclose();
3780 return false;
3781 }
3782 else
3783 {
3784 debug(LOG_ERROR, "Unexpected %s.", messageTypeToString(type));
3785 NETpop(queue);
3786 }
3787 }
3788 }
3789
3790 /*!
3791 * Set the masterserver name
3792 * \param hostname The hostname of the masterserver to connect to
3793 */
NETsetMasterserverName(const char * hostname)3794 void NETsetMasterserverName(const char *hostname)
3795 {
3796 sstrcpy(masterserver_name, hostname);
3797 }
3798
3799 /**
3800 * @return The hostname of the masterserver we will connect to.
3801 */
NETgetMasterserverName()3802 const char *NETgetMasterserverName()
3803 {
3804 return masterserver_name;
3805 }
3806
3807 /*!
3808 * Set the masterserver port
3809 * \param port The port of the masterserver to connect to
3810 */
NETsetMasterserverPort(unsigned int port)3811 void NETsetMasterserverPort(unsigned int port)
3812 {
3813 const unsigned int MAX_PORT = 65535;
3814 if (port > MAX_PORT || port == 0)
3815 {
3816 debug(LOG_ERROR, "Invalid port number: %u", port);
3817 return;
3818 }
3819 masterserver_port = port;
3820 }
3821
3822 /**
3823 * @return The port of the masterserver we will connect to.
3824 */
NETgetMasterserverPort()3825 unsigned int NETgetMasterserverPort()
3826 {
3827 return masterserver_port;
3828 }
3829
3830 /*!
3831 * Set the port we shall host games on
3832 * \param port The port to listen to
3833 */
NETsetGameserverPort(unsigned int port)3834 void NETsetGameserverPort(unsigned int port)
3835 {
3836 gameserver_port = port;
3837 }
3838
3839 /**
3840 * @return The port we will host games on.
3841 */
NETgetGameserverPort()3842 unsigned int NETgetGameserverPort()
3843 {
3844 return gameserver_port;
3845 }
3846
3847 /*!
3848 * Set the join preference for IPv6
3849 * \param bTryIPv6First Whether to attempt IPv6 first when joining, before IPv4.
3850 */
NETsetJoinPreferenceIPv6(bool bTryIPv6First)3851 void NETsetJoinPreferenceIPv6(bool bTryIPv6First)
3852 {
3853 bJoinPrefTryIPv6First = bTryIPv6First;
3854 }
3855
3856 /**
3857 * @return Whether joining a game that advertises both IPv6 and IPv4 should attempt IPv6 first.
3858 */
NETgetJoinPreferenceIPv6()3859 bool NETgetJoinPreferenceIPv6()
3860 {
3861 return bJoinPrefTryIPv6First;
3862 }
3863
3864
NETsetPlayerConnectionStatus(CONNECTION_STATUS status,unsigned player)3865 void NETsetPlayerConnectionStatus(CONNECTION_STATUS status, unsigned player)
3866 {
3867 unsigned n;
3868 const int timeouts[] = {GAME_TICKS_PER_SEC * 10, GAME_TICKS_PER_SEC * 10, GAME_TICKS_PER_SEC, GAME_TICKS_PER_SEC / 6};
3869 ASSERT(ARRAY_SIZE(timeouts) == CONNECTIONSTATUS_NORMAL, "Connection status timeout array too small.");
3870
3871 if (player == NET_ALL_PLAYERS)
3872 {
3873 for (n = 0; n < MAX_PLAYERS; ++n)
3874 {
3875 NETsetPlayerConnectionStatus(status, n);
3876 }
3877 return;
3878 }
3879 if (status == CONNECTIONSTATUS_NORMAL)
3880 {
3881 for (n = 0; n < CONNECTIONSTATUS_NORMAL; ++n)
3882 {
3883 NET_PlayerConnectionStatus[n][player] = 0;
3884 }
3885 return;
3886 }
3887
3888 NET_PlayerConnectionStatus[status][player] = realTime + timeouts[status];
3889 }
3890
NETcheckPlayerConnectionStatus(CONNECTION_STATUS status,unsigned player)3891 bool NETcheckPlayerConnectionStatus(CONNECTION_STATUS status, unsigned player)
3892 {
3893 unsigned n;
3894
3895 if (player == NET_ALL_PLAYERS)
3896 {
3897 for (n = 0; n < MAX_PLAYERS; ++n)
3898 {
3899 if (NETcheckPlayerConnectionStatus(status, n))
3900 {
3901 return true;
3902 }
3903 }
3904 return false;
3905 }
3906 if (status == CONNECTIONSTATUS_NORMAL)
3907 {
3908 for (n = 0; n < CONNECTIONSTATUS_NORMAL; ++n)
3909 {
3910 if (NETcheckPlayerConnectionStatus((CONNECTION_STATUS)n, player))
3911 {
3912 return true;
3913 }
3914 }
3915 return false;
3916 }
3917
3918 return realTime < NET_PlayerConnectionStatus[status][player];
3919 }
3920
3921 struct SyncDebugEntry
3922 {
3923 char const *function;
3924 };
3925
3926 struct SyncDebugString : public SyncDebugEntry
3927 {
setSyncDebugString3928 void set(uint32_t &crc, char const *f, char const *string)
3929 {
3930 function = f;
3931 crc = crcSum(crc, function, strlen(function) + 1);
3932 crc = crcSum(crc, string, strlen(string) + 1);
3933 }
snprintSyncDebugString3934 int snprint(char *buf, size_t bufSize, char const *&string) const
3935 {
3936 int ret = snprintf(buf, bufSize, "[%s] %s\n", function, string);
3937 string += strlen(string) + 1;
3938 return ret;
3939 }
3940 };
3941
3942 struct SyncDebugValueChange : public SyncDebugEntry
3943 {
setSyncDebugValueChange3944 void set(uint32_t &crc, char const *f, char const *vn, int nv, int i)
3945 {
3946 function = f;
3947 variableName = vn;
3948 newValue = nv;
3949 id = i;
3950 uint32_t valueBytes = htonl(newValue);
3951 crc = crcSum(crc, function, strlen(function) + 1);
3952 crc = crcSum(crc, variableName, strlen(variableName) + 1);
3953 crc = crcSum(crc, &valueBytes, 4);
3954 }
snprintSyncDebugValueChange3955 int snprint(char *buf, size_t bufSize) const
3956 {
3957 if (id != -1)
3958 {
3959 return snprintf(buf, bufSize, "[%s] %d %s = %d\n", function, id, variableName, newValue);
3960 }
3961 return snprintf(buf, bufSize, "[%s] %s = %d\n", function, variableName, newValue);
3962 }
3963
3964 int newValue;
3965 int id;
3966 char const *variableName;
3967 };
3968
3969 struct SyncDebugIntList : public SyncDebugEntry
3970 {
setSyncDebugIntList3971 void set(uint32_t &crc, char const *f, char const *s, int const *ints, size_t num)
3972 {
3973 function = f;
3974 string = s;
3975 uint32_t valueBytes[40];
3976 numInts = std::min(num, ARRAY_SIZE(valueBytes));
3977 for (unsigned n = 0; n < numInts; ++n)
3978 {
3979 valueBytes[n] = htonl(ints[n]);
3980 }
3981 crc = crcSum(crc, valueBytes, 4 * numInts);
3982 }
snprintSyncDebugIntList3983 int snprint(char *buf, size_t bufSize, int const *&ints) const
3984 {
3985 size_t index = 0;
3986 if (index < bufSize)
3987 {
3988 index += snprintf(buf + index, bufSize - index, "[%s] ", function);
3989 }
3990 if (index < bufSize)
3991 {
3992 switch (numInts)
3993 {
3994 case 0: index += snprintf(buf + index, bufSize - index, "%s", string); break;
3995 case 1: index += snprintf(buf + index, bufSize - index, string, ints[0]); break;
3996 case 2: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1]); break;
3997 case 3: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2]); break;
3998 case 4: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3]); break;
3999 case 5: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4]); break;
4000 case 6: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5]); break;
4001 case 7: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6]); break;
4002 case 8: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7]); break;
4003 case 9: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8]); break;
4004 case 10: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9]); break;
4005 case 11: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10]); break;
4006 case 12: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11]); break;
4007 case 13: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12]); break;
4008 case 14: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13]); break;
4009 case 15: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14]); break;
4010 case 16: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15]); break;
4011 case 17: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16]); break;
4012 case 18: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17]); break;
4013 case 19: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17], ints[18]); break;
4014 case 20: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17], ints[18], ints[19]); break;
4015 case 21: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17], ints[18], ints[19], ints[20]); break;
4016 case 22: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17], ints[18], ints[19], ints[20], ints[21]); break;
4017 case 23: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17], ints[18], ints[19], ints[20], ints[21], ints[22]); break;
4018 case 24: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17], ints[18], ints[19], ints[20], ints[21], ints[22], ints[23]); break;
4019 case 25: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17], ints[18], ints[19], ints[20], ints[21], ints[22], ints[23], ints[24]); break;
4020 case 26: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17], ints[18], ints[19], ints[20], ints[21], ints[22], ints[23], ints[24], ints[25]); break;
4021 case 27: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17], ints[18], ints[19], ints[20], ints[21], ints[22], ints[23], ints[24], ints[25], ints[26]); break;
4022 case 28: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17], ints[18], ints[19], ints[20], ints[21], ints[22], ints[23], ints[24], ints[25], ints[26], ints[27]); break;
4023 case 29: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17], ints[18], ints[19], ints[20], ints[21], ints[22], ints[23], ints[24], ints[25], ints[26], ints[27], ints[28]); break;
4024 case 30: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17], ints[18], ints[19], ints[20], ints[21], ints[22], ints[23], ints[24], ints[25], ints[26], ints[27], ints[28], ints[29]); break;
4025 case 31: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17], ints[18], ints[19], ints[20], ints[21], ints[22], ints[23], ints[24], ints[25], ints[26], ints[27], ints[28], ints[29], ints[30]); break;
4026 case 32: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17], ints[18], ints[19], ints[20], ints[21], ints[22], ints[23], ints[24], ints[25], ints[26], ints[27], ints[28], ints[29], ints[30], ints[31]); break;
4027 case 33: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17], ints[18], ints[19], ints[20], ints[21], ints[22], ints[23], ints[24], ints[25], ints[26], ints[27], ints[28], ints[29], ints[30], ints[31], ints[32]); break;
4028 case 34: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17], ints[18], ints[19], ints[20], ints[21], ints[22], ints[23], ints[24], ints[25], ints[26], ints[27], ints[28], ints[29], ints[30], ints[31], ints[32], ints[33]); break;
4029 case 35: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17], ints[18], ints[19], ints[20], ints[21], ints[22], ints[23], ints[24], ints[25], ints[26], ints[27], ints[28], ints[29], ints[30], ints[31], ints[32], ints[33], ints[34]); break;
4030 case 36: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17], ints[18], ints[19], ints[20], ints[21], ints[22], ints[23], ints[24], ints[25], ints[26], ints[27], ints[28], ints[29], ints[30], ints[31], ints[32], ints[33], ints[34], ints[35]); break;
4031 case 37: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17], ints[18], ints[19], ints[20], ints[21], ints[22], ints[23], ints[24], ints[25], ints[26], ints[27], ints[28], ints[29], ints[30], ints[31], ints[32], ints[33], ints[34], ints[35], ints[36]); break;
4032 case 38: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17], ints[18], ints[19], ints[20], ints[21], ints[22], ints[23], ints[24], ints[25], ints[26], ints[27], ints[28], ints[29], ints[30], ints[31], ints[32], ints[33], ints[34], ints[35], ints[36], ints[37]); break;
4033 case 39: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17], ints[18], ints[19], ints[20], ints[21], ints[22], ints[23], ints[24], ints[25], ints[26], ints[27], ints[28], ints[29], ints[30], ints[31], ints[32], ints[33], ints[34], ints[35], ints[36], ints[37], ints[38]); break;
4034 case 40: index += snprintf(buf + index, bufSize - index, string, ints[0], ints[1], ints[2], ints[3], ints[4], ints[5], ints[6], ints[7], ints[8], ints[9], ints[10], ints[11], ints[12], ints[13], ints[14], ints[15], ints[16], ints[17], ints[18], ints[19], ints[20], ints[21], ints[22], ints[23], ints[24], ints[25], ints[26], ints[27], ints[28], ints[29], ints[30], ints[31], ints[32], ints[33], ints[34], ints[35], ints[36], ints[37], ints[38], ints[39]); break;
4035 default: index += snprintf(buf + index, bufSize - index, "Too many ints in intlist."); break;
4036 }
4037 }
4038 if (index < bufSize)
4039 {
4040 index += snprintf(buf + index, bufSize - index, "\n");
4041 }
4042 ints += numInts;
4043 return index;
4044 }
4045
4046 char const *string;
4047 unsigned numInts;
4048 };
4049
4050 struct SyncDebugLog
4051 {
SyncDebugLogSyncDebugLog4052 SyncDebugLog() : time(0), crc(0x00000000) {}
clearSyncDebugLog4053 void clear()
4054 {
4055 log.clear();
4056 time = 0;
4057 crc = 0x00000000;
4058 //printf("Freeing %d strings, %d valueChanges, %d intLists, %d chars, %d ints\n", (int)strings.size(), (int)valueChanges.size(), (int)intLists.size(), (int)chars.size(), (int)ints.size());
4059 strings.clear();
4060 valueChanges.clear();
4061 intLists.clear();
4062 chars.clear();
4063 ints.clear();
4064 }
stringSyncDebugLog4065 void string(char const *f, char const *s)
4066 {
4067 size_t offset = chars.size();
4068 chars.resize(chars.size() + strlen(s) + 1);
4069 char *buf = &chars[offset];
4070 strcpy(buf, s);
4071
4072 strings.resize(strings.size() + 1);
4073 strings.back().set(crc, f, buf);
4074
4075 log.push_back('s');
4076 }
valueChangeSyncDebugLog4077 void valueChange(char const *f, char const *vn, int nv, int i)
4078 {
4079 valueChanges.resize(valueChanges.size() + 1);
4080 valueChanges.back().set(crc, f, vn, nv, i);
4081 log.push_back('v');
4082 }
intListSyncDebugLog4083 void intList(char const *f, char const *s, int *begin, size_t num)
4084 {
4085 size_t offset = ints.size();
4086 ints.resize(ints.size() + num);
4087 int *buf = &ints[offset];
4088 std::copy(begin, begin + num, buf);
4089
4090 intLists.resize(intLists.size() + 1);
4091 intLists.back().set(crc, f, s, buf, num);
4092 log.push_back('i');
4093 }
snprintSyncDebugLog4094 int snprint(char *buf, size_t bufSize)
4095 {
4096 SyncDebugString const *stringPtr = strings.empty() ? nullptr : &strings[0]; // .empty() check, since &strings[0] is undefined if strings is empty(), even if it's likely to work, anyway.
4097 SyncDebugValueChange const *valueChangePtr = valueChanges.empty() ? nullptr : &valueChanges[0];
4098 SyncDebugIntList const *intListPtr = intLists.empty() ? nullptr : &intLists[0];
4099 char const *charPtr = chars.empty() ? nullptr : &chars[0];
4100 int const *intPtr = ints.empty() ? nullptr : &ints[0];
4101
4102 int index = 0;
4103 for (size_t n = 0; n < log.size() && (size_t)index < bufSize; ++n)
4104 {
4105 char type = log[n];
4106 switch (type)
4107 {
4108 case 's':
4109 index += stringPtr++->snprint(buf + index, bufSize - index, charPtr);
4110 break;
4111 case 'v':
4112 index += valueChangePtr++->snprint(buf + index, bufSize - index);
4113 break;
4114 case 'i':
4115 index += intListPtr++->snprint(buf + index, bufSize - index, intPtr);
4116 break;
4117 default:
4118 abort();
4119 break;
4120 }
4121 }
4122 return index;
4123 }
getGameTimeSyncDebugLog4124 uint32_t getGameTime() const
4125 {
4126 return time;
4127 }
getCrcSyncDebugLog4128 uint32_t getCrc() const
4129 {
4130 return ~crc; // Invert bits, since everyone else seems to do that with CRCs...
4131 }
getNumEntriesSyncDebugLog4132 size_t getNumEntries() const
4133 {
4134 return log.size();
4135 }
setGameTimeSyncDebugLog4136 void setGameTime(uint32_t newTime)
4137 {
4138 time = newTime;
4139 }
setCrcSyncDebugLog4140 void setCrc(uint32_t newCrc)
4141 {
4142 crc = ~newCrc; // Invert bits, since everyone else seems to do that with CRCs...
4143 }
4144
4145 private:
4146 std::vector<char> log;
4147 uint32_t time;
4148 uint32_t crc;
4149
4150 std::vector<SyncDebugString> strings;
4151 std::vector<SyncDebugValueChange> valueChanges;
4152 std::vector<SyncDebugIntList> intLists;
4153
4154 std::vector<char> chars;
4155 std::vector<int> ints;
4156
4157 private:
4158 SyncDebugLog(SyncDebugLog const &)/* = delete*/;
4159 SyncDebugLog &operator =(SyncDebugLog const &)/* = delete*/;
4160 };
4161
4162 #define MAX_LEN_LOG_LINE 512 // From debug.c - no use printing something longer.
4163 #define MAX_SYNC_HISTORY 12
4164
4165 static unsigned syncDebugNext = 0;
4166 static SyncDebugLog syncDebugLog[MAX_SYNC_HISTORY];
4167 static uint32_t syncDebugExtraGameTime;
4168 static uint32_t syncDebugExtraCrc;
4169
4170 static uint32_t syncDebugNumDumps = 0;
4171
_syncDebug(const char * function,const char * str,...)4172 void _syncDebug(const char *function, const char *str, ...)
4173 {
4174 #ifdef WZ_CC_MSVC
4175 char const *f = function; while (*f != '\0') if (*f++ == ':')
4176 {
4177 function = f; // Strip "Class::" from "Class::myFunction".
4178 }
4179 #endif
4180
4181 va_list ap;
4182 char outputBuffer[MAX_LEN_LOG_LINE];
4183
4184 va_start(ap, str);
4185 vssprintf(outputBuffer, str, ap);
4186 va_end(ap);
4187
4188 syncDebugLog[syncDebugNext].string(function, outputBuffer);
4189 }
4190
_syncDebugIntList(const char * function,const char * str,int * ints,size_t numInts)4191 void _syncDebugIntList(const char *function, const char *str, int *ints, size_t numInts)
4192 {
4193 #ifdef WZ_CC_MSVC
4194 char const *f = function; while (*f != '\0') if (*f++ == ':')
4195 {
4196 function = f; // Strip "Class::" from "Class::myFunction".
4197 }
4198 #endif
4199
4200 syncDebugLog[syncDebugNext].intList(function, str, ints, numInts);
4201 }
4202
_syncDebugBacktrace(const char * function)4203 void _syncDebugBacktrace(const char *function)
4204 {
4205 #ifdef WZ_CC_MSVC
4206 char const *f = function; while (*f != '\0') if (*f++ == ':')
4207 {
4208 function = f; // Strip "Class::" from "Class::myFunction".
4209 }
4210 #endif
4211
4212 uint32_t backupCrc = syncDebugLog[syncDebugNext].getCrc(); // Ignore CRC changes from _syncDebug(), since identical backtraces can be printed differently.
4213
4214 #if defined(WZ_OS_LINUX) && defined(__GLIBC__)
4215 void *btv[20];
4216 unsigned num = backtrace(btv, sizeof(btv) / sizeof(*btv));
4217 char **btc = backtrace_symbols(btv, num);
4218 unsigned i;
4219 for (i = 1; i + 2 < num; ++i) // =1: Don't print "src/warzone2100(syncDebugBacktrace+0x16) [0x6312d1]". +2: Don't print last two lines of backtrace such as "/lib/libc.so.6(__libc_start_main+0xe6) [0x7f91e040ea26]", since the address varies (even with the same binary).
4220 {
4221 _syncDebug("BT", "%s", btc[i]);
4222 }
4223 free(btc);
4224 #else
4225 _syncDebug("BT", "Sorry, syncDebugBacktrace() not implemented on your system. Called from %s.", function);
4226 #endif
4227
4228 // Use CRC of something platform-independent, to avoid false positive desynchs.
4229 backupCrc = ~crcSum(~backupCrc, function, strlen(function) + 1);
4230 syncDebugLog[syncDebugNext].setCrc(backupCrc);
4231 }
4232
syncDebugGetCrc()4233 uint32_t syncDebugGetCrc()
4234 {
4235 return syncDebugLog[syncDebugNext].getCrc();
4236 }
4237
syncDebugSetCrc(uint32_t crc)4238 void syncDebugSetCrc(uint32_t crc)
4239 {
4240 syncDebugLog[syncDebugNext].setCrc(crc);
4241 }
4242
resetSyncDebug()4243 void resetSyncDebug()
4244 {
4245 for (unsigned i = 0; i < MAX_SYNC_HISTORY; ++i)
4246 {
4247 syncDebugLog[i].clear();
4248 }
4249
4250 syncDebugExtraGameTime = 0;
4251 syncDebugExtraCrc = 0xFFFFFFFF;
4252
4253 syncDebugNext = 0;
4254
4255 syncDebugNumDumps = 0;
4256 }
4257
nextDebugSync()4258 GameCrcType nextDebugSync()
4259 {
4260 uint32_t ret = syncDebugLog[syncDebugNext].getCrc();
4261
4262 // Save gameTime, so we know which CRC to compare with, later.
4263 syncDebugLog[syncDebugNext].setGameTime(gameTime);
4264
4265 // Go to next position, and free it ready for use.
4266 syncDebugNext = (syncDebugNext + 1) % MAX_SYNC_HISTORY;
4267 syncDebugLog[syncDebugNext].clear();
4268
4269 return (GameCrcType)ret;
4270 }
4271
dumpDebugSync(uint8_t * buf,size_t bufLen,uint32_t time,unsigned player)4272 static void dumpDebugSync(uint8_t *buf, size_t bufLen, uint32_t time, unsigned player)
4273 {
4274 char fname[100];
4275 PHYSFS_file *fp;
4276
4277 ssprintf(fname, "logs/desync%u_p%u.txt", time, player);
4278 fp = openSaveFile(fname);
4279 ASSERT(bufLen <= static_cast<size_t>(std::numeric_limits<PHYSFS_uint32>::max()), "bufLen (%zu) exceeds PHYSFS_uint32::max", bufLen);
4280 WZ_PHYSFS_writeBytes(fp, buf, static_cast<PHYSFS_uint32>(bufLen));
4281 PHYSFS_close(fp);
4282
4283 debug(LOG_ERROR, "Dumped player %u's sync error at gameTime %u to file: %s%s", player, time, WZ_PHYSFS_getRealDir_String(fname).c_str(), fname);
4284 }
4285
sendDebugSync(uint8_t * buf,uint32_t bufLen,uint32_t time)4286 static void sendDebugSync(uint8_t *buf, uint32_t bufLen, uint32_t time)
4287 {
4288 // Save our own, before sending, so that if we have 2 clients running on the same computer, to guarantee that it is done saving before the other client saves on top.
4289 dumpDebugSync(buf, bufLen, time, selectedPlayer);
4290
4291 NETbeginEncode(NETbroadcastQueue(), NET_DEBUG_SYNC);
4292 NETuint32_t(&time);
4293 NETuint32_t(&bufLen);
4294 NETbin(buf, bufLen);
4295 NETend();
4296 }
4297
4298 static uint8_t debugSyncTmpBuf[2000000];
recvDebugSync(NETQUEUE queue)4299 static void recvDebugSync(NETQUEUE queue)
4300 {
4301 uint32_t time = 0;
4302 uint32_t bufLen = 0;
4303
4304 NETbeginDecode(queue, NET_DEBUG_SYNC);
4305 NETuint32_t(&time);
4306 NETuint32_t(&bufLen);
4307 bufLen = MIN(bufLen, ARRAY_SIZE(debugSyncTmpBuf));
4308 NETbin(debugSyncTmpBuf, bufLen);
4309 NETend();
4310
4311 dumpDebugSync(debugSyncTmpBuf, bufLen, time, queue.index);
4312 }
4313
checkDebugSync(uint32_t checkGameTime,GameCrcType checkCrc)4314 bool checkDebugSync(uint32_t checkGameTime, GameCrcType checkCrc)
4315 {
4316 if (checkGameTime == syncDebugLog[syncDebugNext].getGameTime()) // Can't happen - and syncDebugGameTime[] == 0, until just before sending the CRC, anyway.
4317 {
4318 debug(LOG_ERROR, "Huh? We aren't done yet...");
4319 return true;
4320 }
4321
4322 unsigned logIndex;
4323 for (logIndex = 0; logIndex < MAX_SYNC_HISTORY; ++logIndex)
4324 {
4325 if (syncDebugLog[logIndex].getGameTime() == checkGameTime)
4326 {
4327 if ((GameCrcType)syncDebugLog[logIndex].getCrc() == checkCrc)
4328 {
4329 return true; // Check passed. (So far... There might still be more players to compare CRCs with.)
4330 }
4331
4332 break; // Check failed!
4333 }
4334 }
4335
4336 if (logIndex >= MAX_SYNC_HISTORY && syncDebugExtraGameTime == checkGameTime)
4337 {
4338 if ((GameCrcType)syncDebugExtraCrc == checkCrc)
4339 {
4340 return true;
4341 }
4342 }
4343
4344 if (logIndex >= MAX_SYNC_HISTORY)
4345 {
4346 return false; // Couldn't check. May have dumped already, or MAX_SYNC_HISTORY isn't big enough compared to the maximum latency.
4347 }
4348
4349 size_t bufIndex = 0;
4350 // Dump our version, and also erase it, so we only dump it at most once.
4351 debug(LOG_ERROR, "Inconsistent sync debug at gameTime %u. My version has %zu entries, CRC = 0x%08X.", syncDebugLog[logIndex].getGameTime(), syncDebugLog[logIndex].getNumEntries(), syncDebugLog[logIndex].getCrc());
4352 bufIndex += snprintf((char *)debugSyncTmpBuf + bufIndex, ARRAY_SIZE(debugSyncTmpBuf) - bufIndex, "===== BEGIN gameTime=%u, %zu entries, CRC 0x%08X =====\n", syncDebugLog[logIndex].getGameTime(), syncDebugLog[logIndex].getNumEntries(), syncDebugLog[logIndex].getCrc());
4353 bufIndex = MIN(bufIndex, ARRAY_SIZE(debugSyncTmpBuf)); // snprintf will not overflow debugSyncTmpBuf, but returns as much as it would have printed if possible.
4354 bufIndex += syncDebugLog[logIndex].snprint((char *)debugSyncTmpBuf + bufIndex, ARRAY_SIZE(debugSyncTmpBuf) - bufIndex);
4355 bufIndex = MIN(bufIndex, ARRAY_SIZE(debugSyncTmpBuf)); // snprintf will not overflow debugSyncTmpBuf, but returns as much as it would have printed if possible.
4356 bufIndex += snprintf((char *)debugSyncTmpBuf + bufIndex, ARRAY_SIZE(debugSyncTmpBuf) - bufIndex, "===== END gameTime=%u, %zu entries, CRC 0x%08X =====\n", syncDebugLog[logIndex].getGameTime(), syncDebugLog[logIndex].getNumEntries(), syncDebugLog[logIndex].getCrc());
4357 bufIndex = MIN(bufIndex, ARRAY_SIZE(debugSyncTmpBuf)); // snprintf will not overflow debugSyncTmpBuf, but returns as much as it would have printed if possible.
4358 if (syncDebugNumDumps < 2)
4359 {
4360 ++syncDebugNumDumps;
4361 sendDebugSync(debugSyncTmpBuf, static_cast<uint32_t>(bufIndex), syncDebugLog[logIndex].getGameTime());
4362 }
4363
4364 // Backup correct CRC for checking against remaining players, even though we erased the logs (which were dumped already).
4365 syncDebugExtraGameTime = syncDebugLog[logIndex].getGameTime();
4366 syncDebugExtraCrc = syncDebugLog[logIndex].getCrc();
4367
4368 // Finish erasing our version.
4369 syncDebugLog[logIndex].clear();
4370
4371 return false; // Ouch.
4372 }
4373
messageTypeToString(unsigned messageType_)4374 const char *messageTypeToString(unsigned messageType_)
4375 {
4376 MESSAGE_TYPES messageType = (MESSAGE_TYPES)messageType_; // Cast to enum, so switch gives a warning if new message types are added without updating the switch.
4377
4378 switch (messageType)
4379 {
4380 // Search: ^\s*([\w_]+).*
4381 // Replace: case \1: return "\1";
4382 // Search: (case ...............................) *(return "[\w_]+";)
4383 // Replace: \t\t\1\2
4384
4385 // Net-related messages.
4386 case NET_MIN_TYPE: return "NET_MIN_TYPE";
4387 case NET_PING: return "NET_PING";
4388 case NET_PLAYER_STATS: return "NET_PLAYER_STATS";
4389 case NET_TEXTMSG: return "NET_TEXTMSG";
4390 case NET_PLAYERRESPONDING: return "NET_PLAYERRESPONDING";
4391 case NET_OPTIONS: return "NET_OPTIONS";
4392 case NET_KICK: return "NET_KICK";
4393 case NET_FIREUP: return "NET_FIREUP";
4394 case NET_COLOURREQUEST: return "NET_COLOURREQUEST";
4395 case NET_FACTIONREQUEST: return "NET_FACTIONREQUEST";
4396 case NET_AITEXTMSG: return "NET_AITEXTMSG";
4397 case NET_BEACONMSG: return "NET_BEACONMSG";
4398 case NET_TEAMREQUEST: return "NET_TEAMREQUEST";
4399 case NET_JOIN: return "NET_JOIN";
4400 case NET_ACCEPTED: return "NET_ACCEPTED";
4401 case NET_PLAYER_INFO: return "NET_PLAYER_INFO";
4402 case NET_PLAYER_JOINED: return "NET_PLAYER_JOINED";
4403 case NET_PLAYER_LEAVING: return "NET_PLAYER_LEAVING";
4404 case NET_PLAYER_DROPPED: return "NET_PLAYER_DROPPED";
4405 case NET_GAME_FLAGS: return "NET_GAME_FLAGS";
4406 case NET_READY_REQUEST: return "NET_READY_REQUEST";
4407 case NET_REJECTED: return "NET_REJECTED";
4408 case NET_POSITIONREQUEST: return "NET_POSITIONREQUEST";
4409 case NET_DATA_CHECK: return "NET_DATA_CHECK";
4410 case NET_HOST_DROPPED: return "NET_HOST_DROPPED";
4411 case NET_SEND_TO_PLAYER: return "NET_SEND_TO_PLAYER";
4412 case NET_SHARE_GAME_QUEUE: return "NET_SHARE_GAME_QUEUE";
4413 case NET_FILE_REQUESTED: return "NET_FILE_REQUESTED";
4414 case NET_FILE_CANCELLED: return "NET_FILE_CANCELLED";
4415 case NET_FILE_PAYLOAD: return "NET_FILE_PAYLOAD";
4416 case NET_DEBUG_SYNC: return "NET_DEBUG_SYNC";
4417 case NET_VOTE: return "NET_VOTE";
4418 case NET_VOTE_REQUEST: return "NET_VOTE_REQUEST";
4419 case NET_MAX_TYPE: return "NET_MAX_TYPE";
4420
4421 // Game-state-related messages, must be processed by all clients at the same game time.
4422 case GAME_MIN_TYPE: return "GAME_MIN_TYPE";
4423 case GAME_DROIDINFO: return "GAME_DROIDINFO";
4424 case GAME_STRUCTUREINFO: return "GAME_STRUCTUREINFO";
4425 case GAME_RESEARCHSTATUS: return "GAME_RESEARCHSTATUS";
4426 case GAME_TEMPLATE: return "GAME_TEMPLATE";
4427 case GAME_TEMPLATEDEST: return "GAME_TEMPLATEDEST";
4428 case GAME_ALLIANCE: return "GAME_ALLIANCE";
4429 case GAME_GIFT: return "GAME_GIFT";
4430 case GAME_LASSAT: return "GAME_LASSAT";
4431 case GAME_GAME_TIME: return "GAME_GAME_TIME";
4432 case GAME_PLAYER_LEFT: return "GAME_PLAYER_LEFT";
4433 case GAME_DROIDDISEMBARK: return "GAME_DROIDDISEMBARK";
4434 case GAME_SYNC_REQUEST: return "GAME_SYNC_REQUEST";
4435
4436 // The following messages are used for debug mode.
4437 case GAME_DEBUG_MODE: return "GAME_DEBUG_MODE";
4438 case GAME_DEBUG_ADD_DROID: return "GAME_DEBUG_ADD_DROID";
4439 case GAME_DEBUG_ADD_STRUCTURE: return "GAME_DEBUG_ADD_STRUCTURE";
4440 case GAME_DEBUG_ADD_FEATURE: return "GAME_DEBUG_ADD_FEATURE";
4441 case GAME_DEBUG_REMOVE_DROID: return "GAME_DEBUG_REMOVE_DROID";
4442 case GAME_DEBUG_REMOVE_STRUCTURE: return "GAME_DEBUG_REMOVE_STRUCTURE";
4443 case GAME_DEBUG_REMOVE_FEATURE: return "GAME_DEBUG_REMOVE_FEATURE";
4444 case GAME_DEBUG_FINISH_RESEARCH: return "GAME_DEBUG_FINISH_RESEARCH";
4445 // End of redundant messages.
4446 case GAME_MAX_TYPE: return "GAME_MAX_TYPE";
4447 }
4448 return "(UNUSED)";
4449 }
4450
isLoopbackIP(const char * ip)4451 static bool isLoopbackIP(const char *ip)
4452 {
4453 if (!ip) return false;
4454 if (strncmp(ip, "127.", 4) == 0)
4455 {
4456 return true;
4457 }
4458 if (strcmp(ip, "::1") == 0)
4459 {
4460 return true;
4461 }
4462 if (strcmp(ip, "0000:0000:0000:0000:0000:0000:0000:0001") == 0)
4463 {
4464 return true;
4465 }
4466 return false;
4467 }
4468
4469 /**
4470 * Check if ip is on the banned list.
4471 * \param ip IP address converted to text
4472 */
onBanList(const char * ip)4473 static bool onBanList(const char *ip)
4474 {
4475 int i;
4476
4477 if (!IPlist)
4478 {
4479 return false; //if no bans are added, then don't check.
4480 }
4481 if (isLoopbackIP(ip))
4482 {
4483 return false; // ignore loopback IPs
4484 }
4485 for (i = 0; i < MAX_BANS ; i++)
4486 {
4487 if (RE2::FullMatch(ip, IPlist[i].IPAddress))
4488 {
4489 return true;
4490 }
4491 }
4492 return false;
4493 }
4494
4495 /**
4496 * Create the banned list.
4497 * \param ip IP address in text format
4498 * \param name Name of the player we are banning
4499 */
addToBanList(const char * ip,const char * name)4500 static void addToBanList(const char *ip, const char *name)
4501 {
4502 if (isLoopbackIP(ip))
4503 {
4504 return;
4505 }
4506 if (!IPlist)
4507 {
4508 IPlist = (PLAYER_IP *)malloc(sizeof(PLAYER_IP) * MAX_BANS + 1);
4509 if (!IPlist)
4510 {
4511 debug(LOG_FATAL, "Out of memory!");
4512 abort();
4513 }
4514 IPlistLast = 0;
4515 memset(IPlist, 0x0, sizeof(PLAYER_IP) * MAX_BANS);
4516 }
4517 sstrcpy(IPlist[IPlistLast].IPAddress, ip);
4518 sstrcpy(IPlist[IPlistLast].pname, name);
4519 IPlistLast++;
4520 sync_counter.banned++;
4521 if (IPlistLast > MAX_BANS)
4522 {
4523 debug(LOG_INFO, "We have exceeded %d bans, resetting to 0", MAX_BANS);
4524 IPlistLast = 0;
4525 }
4526 }
4527