LCOV - code coverage report
Current view: top level - source/network - NetClient.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 1 475 0.2 %
Date: 2023-01-19 00:18:29 Functions: 0 60 0.0 %

          Line data    Source code
       1             : /* Copyright (C) 2022 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "NetClient.h"
      21             : 
      22             : #include "NetClientTurnManager.h"
      23             : #include "NetMessage.h"
      24             : #include "NetSession.h"
      25             : 
      26             : #include "lib/byte_order.h"
      27             : #include "lib/external_libraries/enet.h"
      28             : #include "lib/external_libraries/libsdl.h"
      29             : #include "lib/sysdep/sysdep.h"
      30             : #include "lobby/IXmppClient.h"
      31             : #include "ps/CConsole.h"
      32             : #include "ps/CLogger.h"
      33             : #include "ps/Compress.h"
      34             : #include "ps/CStr.h"
      35             : #include "ps/Game.h"
      36             : #include "ps/Hashing.h"
      37             : #include "ps/Loader.h"
      38             : #include "ps/Profile.h"
      39             : #include "ps/Threading.h"
      40             : #include "scriptinterface/ScriptInterface.h"
      41             : #include "scriptinterface/JSON.h"
      42             : #include "simulation2/Simulation2.h"
      43             : #include "network/StunClient.h"
      44             : 
      45             : /**
      46             :  * Once ping goes above turn length * command delay,
      47             :  * the game will start 'freezing' for other clients while we catch up.
      48             :  * Since commands are sent client -> server -> client, divide by 2.
      49             :  * (duplicated in NetServer.cpp to avoid having to fetch the constants in a header file)
      50             :  */
      51             : constexpr u32 NETWORK_BAD_PING = DEFAULT_TURN_LENGTH * COMMAND_DELAY_MP / 2;
      52             : 
      53             : CNetClient *g_NetClient = NULL;
      54             : 
      55             : /**
      56             :  * Async task for receiving the initial game state when rejoining an
      57             :  * in-progress network game.
      58             :  */
      59           0 : class CNetFileReceiveTask_ClientRejoin : public CNetFileReceiveTask
      60             : {
      61             :     NONCOPYABLE(CNetFileReceiveTask_ClientRejoin);
      62             : public:
      63           0 :     CNetFileReceiveTask_ClientRejoin(CNetClient& client, const CStr& initAttribs)
      64           0 :         : m_Client(client), m_InitAttributes(initAttribs)
      65             :     {
      66           0 :     }
      67             : 
      68           0 :     virtual void OnComplete()
      69             :     {
      70             :         // We've received the game state from the server
      71             : 
      72             :         // Save it so we can use it after the map has finished loading
      73           0 :         m_Client.m_JoinSyncBuffer = m_Buffer;
      74             : 
      75             :         // Pretend the server told us to start the game
      76           0 :         CGameStartMessage start;
      77           0 :         start.m_InitAttributes = m_InitAttributes;
      78           0 :         m_Client.HandleMessage(&start);
      79           0 :     }
      80             : 
      81             : private:
      82             :     CNetClient& m_Client;
      83             :     CStr m_InitAttributes;
      84             : };
      85             : 
      86           0 : CNetClient::CNetClient(CGame* game) :
      87             :     m_Session(NULL),
      88             :     m_UserName(L"anonymous"),
      89             :     m_HostID((u32)-1), m_ClientTurnManager(NULL), m_Game(game),
      90             :     m_LastConnectionCheck(0),
      91             :     m_ServerAddress(),
      92             :     m_ServerPort(0),
      93           0 :     m_Rejoin(false)
      94             : {
      95           0 :     m_Game->SetTurnManager(NULL); // delete the old local turn manager so we don't accidentally use it
      96             : 
      97           0 :     void* context = this;
      98             : 
      99           0 :     JS_AddExtraGCRootsTracer(GetScriptInterface().GetGeneralJSContext(), CNetClient::Trace, this);
     100             : 
     101             :     // Set up transitions for session
     102           0 :     AddTransition(NCS_UNCONNECTED, (uint)NMT_CONNECT_COMPLETE, NCS_CONNECT, (void*)&OnConnect, context);
     103             : 
     104           0 :     AddTransition(NCS_CONNECT, (uint)NMT_SERVER_HANDSHAKE, NCS_HANDSHAKE, (void*)&OnHandshake, context);
     105             : 
     106           0 :     AddTransition(NCS_HANDSHAKE, (uint)NMT_SERVER_HANDSHAKE_RESPONSE, NCS_AUTHENTICATE, (void*)&OnHandshakeResponse, context);
     107             : 
     108           0 :     AddTransition(NCS_AUTHENTICATE, (uint)NMT_AUTHENTICATE, NCS_AUTHENTICATE, (void*)&OnAuthenticateRequest, context);
     109           0 :     AddTransition(NCS_AUTHENTICATE, (uint)NMT_AUTHENTICATE_RESULT, NCS_PREGAME, (void*)&OnAuthenticate, context);
     110             : 
     111           0 :     AddTransition(NCS_PREGAME, (uint)NMT_CHAT, NCS_PREGAME, (void*)&OnChat, context);
     112           0 :     AddTransition(NCS_PREGAME, (uint)NMT_READY, NCS_PREGAME, (void*)&OnReady, context);
     113           0 :     AddTransition(NCS_PREGAME, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context);
     114           0 :     AddTransition(NCS_PREGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_PREGAME, (void*)&OnPlayerAssignment, context);
     115           0 :     AddTransition(NCS_PREGAME, (uint)NMT_KICKED, NCS_PREGAME, (void*)&OnKicked, context);
     116           0 :     AddTransition(NCS_PREGAME, (uint)NMT_CLIENT_TIMEOUT, NCS_PREGAME, (void*)&OnClientTimeout, context);
     117           0 :     AddTransition(NCS_PREGAME, (uint)NMT_CLIENT_PERFORMANCE, NCS_PREGAME, (void*)&OnClientPerformance, context);
     118           0 :     AddTransition(NCS_PREGAME, (uint)NMT_GAME_START, NCS_LOADING, (void*)&OnGameStart, context);
     119           0 :     AddTransition(NCS_PREGAME, (uint)NMT_JOIN_SYNC_START, NCS_JOIN_SYNCING, (void*)&OnJoinSyncStart, context);
     120             : 
     121           0 :     AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CHAT, NCS_JOIN_SYNCING, (void*)&OnChat, context);
     122           0 :     AddTransition(NCS_JOIN_SYNCING, (uint)NMT_GAME_SETUP, NCS_JOIN_SYNCING, (void*)&OnGameSetup, context);
     123           0 :     AddTransition(NCS_JOIN_SYNCING, (uint)NMT_PLAYER_ASSIGNMENT, NCS_JOIN_SYNCING, (void*)&OnPlayerAssignment, context);
     124           0 :     AddTransition(NCS_JOIN_SYNCING, (uint)NMT_KICKED, NCS_JOIN_SYNCING, (void*)&OnKicked, context);
     125           0 :     AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CLIENT_TIMEOUT, NCS_JOIN_SYNCING, (void*)&OnClientTimeout, context);
     126           0 :     AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CLIENT_PERFORMANCE, NCS_JOIN_SYNCING, (void*)&OnClientPerformance, context);
     127           0 :     AddTransition(NCS_JOIN_SYNCING, (uint)NMT_GAME_START, NCS_JOIN_SYNCING, (void*)&OnGameStart, context);
     128           0 :     AddTransition(NCS_JOIN_SYNCING, (uint)NMT_SIMULATION_COMMAND, NCS_JOIN_SYNCING, (void*)&OnInGame, context);
     129           0 :     AddTransition(NCS_JOIN_SYNCING, (uint)NMT_END_COMMAND_BATCH, NCS_JOIN_SYNCING, (void*)&OnJoinSyncEndCommandBatch, context);
     130           0 :     AddTransition(NCS_JOIN_SYNCING, (uint)NMT_LOADED_GAME, NCS_INGAME, (void*)&OnLoadedGame, context);
     131             : 
     132           0 :     AddTransition(NCS_LOADING, (uint)NMT_CHAT, NCS_LOADING, (void*)&OnChat, context);
     133           0 :     AddTransition(NCS_LOADING, (uint)NMT_GAME_SETUP, NCS_LOADING, (void*)&OnGameSetup, context);
     134           0 :     AddTransition(NCS_LOADING, (uint)NMT_PLAYER_ASSIGNMENT, NCS_LOADING, (void*)&OnPlayerAssignment, context);
     135           0 :     AddTransition(NCS_LOADING, (uint)NMT_KICKED, NCS_LOADING, (void*)&OnKicked, context);
     136           0 :     AddTransition(NCS_LOADING, (uint)NMT_CLIENT_TIMEOUT, NCS_LOADING, (void*)&OnClientTimeout, context);
     137           0 :     AddTransition(NCS_LOADING, (uint)NMT_CLIENT_PERFORMANCE, NCS_LOADING, (void*)&OnClientPerformance, context);
     138           0 :     AddTransition(NCS_LOADING, (uint)NMT_CLIENTS_LOADING, NCS_LOADING, (void*)&OnClientsLoading, context);
     139           0 :     AddTransition(NCS_LOADING, (uint)NMT_LOADED_GAME, NCS_INGAME, (void*)&OnLoadedGame, context);
     140             : 
     141           0 :     AddTransition(NCS_INGAME, (uint)NMT_REJOINED, NCS_INGAME, (void*)&OnRejoined, context);
     142           0 :     AddTransition(NCS_INGAME, (uint)NMT_KICKED, NCS_INGAME, (void*)&OnKicked, context);
     143           0 :     AddTransition(NCS_INGAME, (uint)NMT_CLIENT_TIMEOUT, NCS_INGAME, (void*)&OnClientTimeout, context);
     144           0 :     AddTransition(NCS_INGAME, (uint)NMT_CLIENT_PERFORMANCE, NCS_INGAME, (void*)&OnClientPerformance, context);
     145           0 :     AddTransition(NCS_INGAME, (uint)NMT_CLIENTS_LOADING, NCS_INGAME, (void*)&OnClientsLoading, context);
     146           0 :     AddTransition(NCS_INGAME, (uint)NMT_CLIENT_PAUSED, NCS_INGAME, (void*)&OnClientPaused, context);
     147           0 :     AddTransition(NCS_INGAME, (uint)NMT_CHAT, NCS_INGAME, (void*)&OnChat, context);
     148           0 :     AddTransition(NCS_INGAME, (uint)NMT_GAME_SETUP, NCS_INGAME, (void*)&OnGameSetup, context);
     149           0 :     AddTransition(NCS_INGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_INGAME, (void*)&OnPlayerAssignment, context);
     150           0 :     AddTransition(NCS_INGAME, (uint)NMT_SIMULATION_COMMAND, NCS_INGAME, (void*)&OnInGame, context);
     151           0 :     AddTransition(NCS_INGAME, (uint)NMT_SYNC_ERROR, NCS_INGAME, (void*)&OnInGame, context);
     152           0 :     AddTransition(NCS_INGAME, (uint)NMT_END_COMMAND_BATCH, NCS_INGAME, (void*)&OnInGame, context);
     153             : 
     154             :     // Set first state
     155           0 :     SetFirstState(NCS_UNCONNECTED);
     156           0 : }
     157             : 
     158           0 : CNetClient::~CNetClient()
     159             : {
     160             :     // Try to flush messages before dying (probably fails).
     161           0 :     if (m_ClientTurnManager)
     162           0 :         m_ClientTurnManager->OnDestroyConnection();
     163             : 
     164           0 :     DestroyConnection();
     165           0 :     JS_RemoveExtraGCRootsTracer(GetScriptInterface().GetGeneralJSContext(), CNetClient::Trace, this);
     166           0 : }
     167             : 
     168           0 : void CNetClient::TraceMember(JSTracer *trc)
     169             : {
     170           0 :     for (JS::Heap<JS::Value>& guiMessage : m_GuiMessageQueue)
     171           0 :         JS::TraceEdge(trc, &guiMessage, "m_GuiMessageQueue");
     172           0 : }
     173             : 
     174           0 : void CNetClient::SetUserName(const CStrW& username)
     175             : {
     176           0 :     ENSURE(!m_Session); // must be called before we start the connection
     177             : 
     178           0 :     m_UserName = username;
     179           0 : }
     180             : 
     181           0 : void CNetClient::SetHostJID(const CStr& jid)
     182             : {
     183           0 :     m_HostJID = jid;
     184           0 : }
     185             : 
     186           0 : void CNetClient::SetGamePassword(const CStr& hashedPassword)
     187             : {
     188             :     // Hash on top with the user's name, to make sure not all
     189             :     // hashing data is in control of the host.
     190           0 :     m_Password = HashCryptographically(hashedPassword, m_UserName.ToUTF8());
     191           0 : }
     192             : 
     193           0 : void CNetClient::SetControllerSecret(const std::string& secret)
     194             : {
     195           0 :     m_ControllerSecret = secret;
     196           0 : }
     197             : 
     198             : 
     199           0 : bool CNetClient::SetupConnection(ENetHost* enetClient)
     200             : {
     201           0 :     CNetClientSession* session = new CNetClientSession(*this);
     202           0 :     bool ok = session->Connect(m_ServerAddress, m_ServerPort, enetClient);
     203           0 :     SetAndOwnSession(session);
     204           0 :     if (ok)
     205           0 :         m_PollingThread = std::thread(Threading::HandleExceptions<CNetClientSession::RunNetLoop>::Wrapper, m_Session);
     206           0 :     return ok;
     207             : }
     208             : 
     209           0 : void CNetClient::SetupConnectionViaLobby()
     210             : {
     211           0 :     g_XmppClient->SendIqGetConnectionData(m_HostJID, m_Password, m_UserName.ToUTF8(), false);
     212           0 : }
     213             : 
     214           0 : void CNetClient::SetupServerData(CStr address, u16 port, bool stun)
     215             : {
     216           0 :     ENSURE(!m_Session);
     217             : 
     218           0 :     m_ServerAddress = address;
     219           0 :     m_ServerPort = port;
     220           0 :     m_UseSTUN = stun;
     221           0 : }
     222             : 
     223           0 : void CNetClient::HandleGetServerDataFailed(const CStr& error)
     224             : {
     225           0 :     if (m_Session)
     226           0 :         return;
     227             : 
     228           0 :     PushGuiMessage(
     229             :         "type", "serverdata",
     230             :         "status", "failed",
     231             :         "reason", error
     232             :     );
     233             : }
     234             : 
     235           0 : bool CNetClient::TryToConnect(const CStr& hostJID, bool localNetwork)
     236             : {
     237           0 :     if (m_Session)
     238           0 :         return false;
     239             : 
     240           0 :     if (m_ServerAddress.empty())
     241             :     {
     242           0 :         PushGuiMessage(
     243             :             "type", "netstatus",
     244             :             "status", "disconnected",
     245             :             "reason", static_cast<i32>(NDR_SERVER_REFUSED));
     246           0 :         return false;
     247             :     }
     248             : 
     249           0 :     ENetAddress hostAddr{ ENET_HOST_ANY, ENET_PORT_ANY };
     250           0 :     ENetHost* enetClient = enet_host_create(&hostAddr, 1, 1, 0, 0);
     251             : 
     252           0 :     if (!enetClient)
     253             :     {
     254           0 :         PushGuiMessage(
     255             :             "type", "netstatus",
     256             :             "status", "disconnected",
     257             :             "reason", static_cast<i32>(NDR_STUN_PORT_FAILED));
     258           0 :         return false;
     259             :     }
     260             : 
     261           0 :     CStr ip;
     262           0 :     u16 port = 0;
     263           0 :     if (g_XmppClient && m_UseSTUN)
     264             :     {
     265           0 :         if (!StunClient::FindPublicIP(*enetClient, ip, port))
     266             :         {
     267           0 :             PushGuiMessage(
     268             :                 "type", "netstatus",
     269             :                 "status", "disconnected",
     270             :                 "reason", static_cast<i32>(NDR_STUN_ENDPOINT_FAILED));
     271           0 :             return false;
     272             :         }
     273             : 
     274             :         // If the host is on the same network, we risk failing to connect
     275             :         // on routers that don't support NAT hairpinning/NAT loopback.
     276             :         // To work around that, send again a connection data request, but for internal IP this time.
     277           0 :         if (ip == m_ServerAddress)
     278             :         {
     279           0 :             g_XmppClient->SendIqGetConnectionData(m_HostJID, m_Password, m_UserName.ToUTF8(), true);
     280             :             // Return true anyways - we're on a success path here.
     281           0 :             return true;
     282             :         }
     283             :     }
     284           0 :     else if (g_XmppClient && localNetwork)
     285             :     {
     286             :         // We may need to punch a hole through the local firewall, so fetch our local IP.
     287             :         // NB: we'll ignore failures here, and hope that the firewall will be open to connection
     288             :         // if we fail to fetch the local IP (which is unlikely anyways).
     289           0 :         if (!StunClient::FindLocalIP(ip))
     290           0 :             ip = "";
     291             :         // Check if we're hosting on localhost, and if so, explicitly use that
     292             :         // (this circumvents, at least, the 'block all incoming connections' setting
     293             :         // on the MacOS firewall).
     294           0 :         if (ip == m_ServerAddress)
     295             :         {
     296           0 :             m_ServerAddress = "127.0.0.1";
     297           0 :             ip = "";
     298             :         }
     299           0 :         port = enetClient->address.port;
     300             :     }
     301             : 
     302           0 :     LOGMESSAGE("NetClient: connecting to server at %s:%i", m_ServerAddress, m_ServerPort);
     303             : 
     304           0 :     if (!ip.empty())
     305             :     {
     306             :         // UDP hole-punching
     307             :         // Step 0: send a message, via XMPP, to the server with our external IP & port.
     308           0 :         g_XmppClient->SendStunEndpointToHost(ip, port, hostJID);
     309             : 
     310             :         // Step 1b: Wait some time - we need the host to receive the stun endpoint and start punching a hole themselves before
     311             :         // we try to establish the connection below.
     312           0 :         SDL_Delay(1000);
     313             : 
     314             :         // Step 2: Send a message ourselves to the server so that the NAT, if any, routes incoming trafic correctly.
     315             :         // TODO: verify if this step is necessary, since we'll try and connect anyways below.
     316           0 :         StunClient::SendHolePunchingMessages(*enetClient, m_ServerAddress, m_ServerPort);
     317             :     }
     318             : 
     319           0 :     if (!g_NetClient->SetupConnection(enetClient))
     320             :     {
     321           0 :         PushGuiMessage(
     322             :             "type", "netstatus",
     323             :             "status", "disconnected",
     324             :             "reason", static_cast<i32>(NDR_UNKNOWN));
     325           0 :         return false;
     326             :     }
     327             : 
     328           0 :     return true;
     329             : }
     330             : 
     331             : 
     332           0 : void CNetClient::SetAndOwnSession(CNetClientSession* session)
     333             : {
     334           0 :     delete m_Session;
     335           0 :     m_Session = session;
     336           0 : }
     337             : 
     338           0 : void CNetClient::DestroyConnection()
     339             : {
     340           0 :     if (m_Session)
     341           0 :         m_Session->Shutdown();
     342             : 
     343           0 :     if (m_PollingThread.joinable())
     344             :         // Use detach() over join() because we don't want to wait for the session
     345             :         // (which may be polling or trying to send messages).
     346           0 :         m_PollingThread.detach();
     347             : 
     348             :     // The polling thread will cleanup the session on its own,
     349             :     // mark it as nullptr here so we know we're done using it.
     350           0 :     m_Session = nullptr;
     351           0 : }
     352             : 
     353           0 : void CNetClient::Poll()
     354             : {
     355           0 :     if (!m_Session)
     356           0 :         return;
     357             : 
     358           0 :     PROFILE3("NetClient::poll");
     359             : 
     360           0 :     CheckServerConnection();
     361           0 :     m_Session->ProcessPolledMessages();
     362             : }
     363             : 
     364           0 : void CNetClient::CheckServerConnection()
     365             : {
     366             :     // Trigger local warnings if the connection to the server is bad.
     367             :     // At most once per second.
     368           0 :     std::time_t now = std::time(nullptr);
     369           0 :     if (now <= m_LastConnectionCheck)
     370           0 :         return;
     371             : 
     372           0 :     m_LastConnectionCheck = now;
     373             : 
     374             :     // Report if we are losing the connection to the server
     375           0 :     u32 lastReceived = m_Session->GetLastReceivedTime();
     376           0 :     if (lastReceived > NETWORK_WARNING_TIMEOUT)
     377             :     {
     378           0 :         PushGuiMessage(
     379             :             "type", "netwarn",
     380             :             "warntype", "server-timeout",
     381             :             "lastReceivedTime", lastReceived);
     382           0 :         return;
     383             :     }
     384             : 
     385             :     // Report if we have a bad ping to the server.
     386           0 :     u32 meanRTT = m_Session->GetMeanRTT();
     387           0 :     if (meanRTT > NETWORK_BAD_PING)
     388             :     {
     389           0 :         PushGuiMessage(
     390             :             "type", "netwarn",
     391             :             "warntype", "server-latency",
     392             :             "meanRTT", meanRTT);
     393             :     }
     394             : }
     395             : 
     396           0 : void CNetClient::GuiPoll(JS::MutableHandleValue ret)
     397             : {
     398           0 :     if (m_GuiMessageQueue.empty())
     399             :     {
     400           0 :         ret.setUndefined();
     401           0 :         return;
     402             :     }
     403             : 
     404           0 :     ret.set(m_GuiMessageQueue.front());
     405           0 :     m_GuiMessageQueue.pop_front();
     406             : }
     407             : 
     408           0 : std::string CNetClient::TestReadGuiMessages()
     409             : {
     410           0 :     ScriptRequest rq(GetScriptInterface());
     411             : 
     412           0 :     std::string r;
     413           0 :     JS::RootedValue msg(rq.cx);
     414             :     while (true)
     415             :     {
     416           0 :         GuiPoll(&msg);
     417           0 :         if (msg.isUndefined())
     418           0 :             break;
     419           0 :         r += Script::ToString(rq, &msg) + "\n";
     420             :     }
     421           0 :     return r;
     422             : }
     423             : 
     424           0 : const ScriptInterface& CNetClient::GetScriptInterface()
     425             : {
     426           0 :     return m_Game->GetSimulation2()->GetScriptInterface();
     427             : }
     428             : 
     429           0 : void CNetClient::PostPlayerAssignmentsToScript()
     430             : {
     431           0 :     ScriptRequest rq(GetScriptInterface());
     432             : 
     433           0 :     JS::RootedValue newAssignments(rq.cx);
     434           0 :     Script::CreateObject(rq, &newAssignments);
     435             : 
     436           0 :     for (const std::pair<const CStr, PlayerAssignment>& p : m_PlayerAssignments)
     437             :     {
     438           0 :         JS::RootedValue assignment(rq.cx);
     439             : 
     440           0 :         Script::CreateObject(
     441             :             rq,
     442             :             &assignment,
     443             :             "name", p.second.m_Name,
     444             :             "player", p.second.m_PlayerID,
     445             :             "status", p.second.m_Status);
     446             : 
     447           0 :         Script::SetProperty(rq, newAssignments, p.first.c_str(), assignment);
     448             :     }
     449             : 
     450           0 :     PushGuiMessage(
     451             :         "type", "players",
     452             :         "newAssignments", newAssignments);
     453           0 : }
     454             : 
     455           0 : bool CNetClient::SendMessage(const CNetMessage* message)
     456             : {
     457           0 :     if (!m_Session)
     458           0 :         return false;
     459             : 
     460           0 :     return m_Session->SendMessage(message);
     461             : }
     462             : 
     463           0 : void CNetClient::HandleConnect()
     464             : {
     465           0 :     Update((uint)NMT_CONNECT_COMPLETE, NULL);
     466           0 : }
     467             : 
     468           0 : void CNetClient::HandleDisconnect(u32 reason)
     469             : {
     470           0 :     PushGuiMessage(
     471             :         "type", "netstatus",
     472             :         "status", "disconnected",
     473             :         "reason", reason);
     474             : 
     475           0 :     DestroyConnection();
     476             : 
     477             :     // Update the state immediately to UNCONNECTED (don't bother with FSM transitions since
     478             :     // we'd need one for every single state, and we don't need to use per-state actions)
     479           0 :     SetCurrState(NCS_UNCONNECTED);
     480           0 : }
     481             : 
     482           0 : void CNetClient::SendGameSetupMessage(JS::MutableHandleValue attrs, const ScriptInterface& scriptInterface)
     483             : {
     484           0 :     CGameSetupMessage gameSetup(scriptInterface);
     485           0 :     gameSetup.m_Data = attrs;
     486           0 :     SendMessage(&gameSetup);
     487           0 : }
     488             : 
     489           0 : void CNetClient::SendAssignPlayerMessage(const int playerID, const CStr& guid)
     490             : {
     491           0 :     CAssignPlayerMessage assignPlayer;
     492           0 :     assignPlayer.m_PlayerID = playerID;
     493           0 :     assignPlayer.m_GUID = guid;
     494           0 :     SendMessage(&assignPlayer);
     495           0 : }
     496             : 
     497           0 : void CNetClient::SendChatMessage(const std::wstring& text)
     498             : {
     499           0 :     CChatMessage chat;
     500           0 :     chat.m_Message = text;
     501           0 :     SendMessage(&chat);
     502           0 : }
     503             : 
     504           0 : void CNetClient::SendReadyMessage(const int status)
     505             : {
     506           0 :     CReadyMessage readyStatus;
     507           0 :     readyStatus.m_Status = status;
     508           0 :     SendMessage(&readyStatus);
     509           0 : }
     510             : 
     511           0 : void CNetClient::SendClearAllReadyMessage()
     512             : {
     513           0 :     CClearAllReadyMessage clearAllReady;
     514           0 :     SendMessage(&clearAllReady);
     515           0 : }
     516             : 
     517           0 : void CNetClient::SendStartGameMessage(const CStr& initAttribs)
     518             : {
     519           0 :     CGameStartMessage gameStart;
     520           0 :     gameStart.m_InitAttributes = initAttribs;
     521           0 :     SendMessage(&gameStart);
     522           0 : }
     523             : 
     524           0 : void CNetClient::SendRejoinedMessage()
     525             : {
     526           0 :     CRejoinedMessage rejoinedMessage;
     527           0 :     SendMessage(&rejoinedMessage);
     528           0 : }
     529             : 
     530           0 : void CNetClient::SendKickPlayerMessage(const CStrW& playerName, bool ban)
     531             : {
     532           0 :     CKickedMessage kickPlayer;
     533           0 :     kickPlayer.m_Name = playerName;
     534           0 :     kickPlayer.m_Ban = ban;
     535           0 :     SendMessage(&kickPlayer);
     536           0 : }
     537             : 
     538           0 : void CNetClient::SendPausedMessage(bool pause)
     539             : {
     540           0 :     CClientPausedMessage pausedMessage;
     541           0 :     pausedMessage.m_Pause = pause;
     542           0 :     SendMessage(&pausedMessage);
     543           0 : }
     544             : 
     545           0 : bool CNetClient::HandleMessage(CNetMessage* message)
     546             : {
     547             :     // Handle non-FSM messages first
     548             : 
     549           0 :     Status status = m_Session->GetFileTransferer().HandleMessageReceive(*message);
     550           0 :     if (status == INFO::OK)
     551           0 :         return true;
     552           0 :     if (status != INFO::SKIPPED)
     553           0 :         return false;
     554             : 
     555           0 :     if (message->GetType() == NMT_FILE_TRANSFER_REQUEST)
     556             :     {
     557           0 :         CFileTransferRequestMessage* reqMessage = static_cast<CFileTransferRequestMessage*>(message);
     558             : 
     559             :         // TODO: we should support different transfer request types, instead of assuming
     560             :         // it's always requesting the simulation state
     561             : 
     562           0 :         std::stringstream stream;
     563             : 
     564           0 :         LOGMESSAGERENDER("Serializing game at turn %u for rejoining player", m_ClientTurnManager->GetCurrentTurn());
     565           0 :         u32 turn = to_le32(m_ClientTurnManager->GetCurrentTurn());
     566           0 :         stream.write((char*)&turn, sizeof(turn));
     567             : 
     568           0 :         bool ok = m_Game->GetSimulation2()->SerializeState(stream);
     569           0 :         ENSURE(ok);
     570             : 
     571             :         // Compress the content with zlib to save bandwidth
     572             :         // (TODO: if this is still too large, compressing with e.g. LZMA works much better)
     573           0 :         std::string compressed;
     574           0 :         CompressZLib(stream.str(), compressed, true);
     575             : 
     576           0 :         m_Session->GetFileTransferer().StartResponse(reqMessage->m_RequestID, compressed);
     577             : 
     578           0 :         return true;
     579             :     }
     580             : 
     581             :     // Update FSM
     582           0 :     bool ok = Update(message->GetType(), message);
     583           0 :     if (!ok)
     584           0 :         LOGERROR("Net client: Error running FSM update (type=%d state=%d)", (int)message->GetType(), (int)GetCurrState());
     585           0 :     return ok;
     586             : }
     587             : 
     588           0 : void CNetClient::LoadFinished()
     589             : {
     590           0 :     if (!m_JoinSyncBuffer.empty())
     591             :     {
     592             :         // We're rejoining a game, and just finished loading the initial map,
     593             :         // so deserialize the saved game state now
     594             : 
     595           0 :         std::string state;
     596           0 :         DecompressZLib(m_JoinSyncBuffer, state, true);
     597             : 
     598           0 :         std::stringstream stream(state);
     599             : 
     600             :         u32 turn;
     601           0 :         stream.read((char*)&turn, sizeof(turn));
     602           0 :         turn = to_le32(turn);
     603             : 
     604           0 :         LOGMESSAGE("Rejoining client deserializing state at turn %u\n", turn);
     605             : 
     606           0 :         bool ok = m_Game->GetSimulation2()->DeserializeState(stream);
     607           0 :         ENSURE(ok);
     608             : 
     609           0 :         m_ClientTurnManager->ResetState(turn, turn);
     610             : 
     611           0 :         PushGuiMessage(
     612             :             "type", "netstatus",
     613             :             "status", "join_syncing");
     614             :     }
     615             :     else
     616             :     {
     617             :         // Connecting at the start of a game, so we'll wait for other players to finish loading
     618           0 :         PushGuiMessage(
     619             :             "type", "netstatus",
     620             :             "status", "waiting_for_players");
     621             :     }
     622             : 
     623           0 :     CLoadedGameMessage loaded;
     624           0 :     loaded.m_CurrentTurn = m_ClientTurnManager->GetCurrentTurn();
     625           0 :     SendMessage(&loaded);
     626           0 : }
     627             : 
     628           0 : void CNetClient::SendAuthenticateMessage()
     629             : {
     630           0 :     CAuthenticateMessage authenticate;
     631           0 :     authenticate.m_Name = m_UserName;
     632           0 :     authenticate.m_Password = m_Password;
     633           0 :     authenticate.m_ControllerSecret = m_ControllerSecret;
     634           0 :     SendMessage(&authenticate);
     635           0 : }
     636             : 
     637           0 : bool CNetClient::OnConnect(void* context, CFsmEvent* event)
     638             : {
     639           0 :     ENSURE(event->GetType() == (uint)NMT_CONNECT_COMPLETE);
     640             : 
     641           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     642             : 
     643           0 :     client->PushGuiMessage(
     644             :         "type", "netstatus",
     645             :         "status", "connected");
     646             : 
     647           0 :     return true;
     648             : }
     649             : 
     650           0 : bool CNetClient::OnHandshake(void* context, CFsmEvent* event)
     651             : {
     652           0 :     ENSURE(event->GetType() == (uint)NMT_SERVER_HANDSHAKE);
     653             : 
     654           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     655             : 
     656           0 :     CCliHandshakeMessage handshake;
     657           0 :     handshake.m_MagicResponse = PS_PROTOCOL_MAGIC_RESPONSE;
     658           0 :     handshake.m_ProtocolVersion = PS_PROTOCOL_VERSION;
     659           0 :     handshake.m_SoftwareVersion = PS_PROTOCOL_VERSION;
     660           0 :     client->SendMessage(&handshake);
     661             : 
     662           0 :     return true;
     663             : }
     664             : 
     665           0 : bool CNetClient::OnHandshakeResponse(void* context, CFsmEvent* event)
     666             : {
     667           0 :     ENSURE(event->GetType() == (uint)NMT_SERVER_HANDSHAKE_RESPONSE);
     668             : 
     669           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     670           0 :     CSrvHandshakeResponseMessage* message = static_cast<CSrvHandshakeResponseMessage*>(event->GetParamRef());
     671             : 
     672           0 :     client->m_GUID = message->m_GUID;
     673             : 
     674           0 :     if (message->m_Flags & PS_NETWORK_FLAG_REQUIRE_LOBBYAUTH)
     675             :     {
     676           0 :         if (g_XmppClient && !client->m_HostJID.empty())
     677           0 :             g_XmppClient->SendIqLobbyAuth(client->m_HostJID, client->m_GUID);
     678             :         else
     679             :         {
     680           0 :             client->PushGuiMessage(
     681             :                 "type", "netstatus",
     682             :                 "status", "disconnected",
     683             :                 "reason", static_cast<i32>(NDR_LOBBY_AUTH_FAILED));
     684             : 
     685           0 :             LOGMESSAGE("Net client: Couldn't send lobby auth xmpp message");
     686             :         }
     687           0 :         return true;
     688             :     }
     689             : 
     690           0 :     client->SendAuthenticateMessage();
     691           0 :     return true;
     692             : }
     693             : 
     694           0 : bool CNetClient::OnAuthenticateRequest(void* context, CFsmEvent* event)
     695             : {
     696           0 :     ENSURE(event->GetType() == (uint)NMT_AUTHENTICATE);
     697             : 
     698           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     699           0 :     client->SendAuthenticateMessage();
     700           0 :     return true;
     701             : }
     702             : 
     703           0 : bool CNetClient::OnAuthenticate(void* context, CFsmEvent* event)
     704             : {
     705           0 :     ENSURE(event->GetType() == (uint)NMT_AUTHENTICATE_RESULT);
     706             : 
     707           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     708           0 :     CAuthenticateResultMessage* message = static_cast<CAuthenticateResultMessage*>(event->GetParamRef());
     709             : 
     710           0 :     LOGMESSAGE("Net: Authentication result: host=%u, %s", message->m_HostID, utf8_from_wstring(message->m_Message));
     711             : 
     712           0 :     client->m_HostID = message->m_HostID;
     713           0 :     client->m_Rejoin = message->m_Code == ARC_OK_REJOINING;
     714           0 :     client->m_IsController = message->m_IsController;
     715             : 
     716           0 :     client->PushGuiMessage(
     717             :         "type", "netstatus",
     718             :         "status", "authenticated",
     719             :         "rejoining", client->m_Rejoin);
     720             : 
     721           0 :     return true;
     722             : }
     723             : 
     724           0 : bool CNetClient::OnChat(void* context, CFsmEvent* event)
     725             : {
     726           0 :     ENSURE(event->GetType() == (uint)NMT_CHAT);
     727             : 
     728           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     729           0 :     CChatMessage* message = static_cast<CChatMessage*>(event->GetParamRef());
     730             : 
     731           0 :     client->PushGuiMessage(
     732             :         "type", "chat",
     733             :         "guid", message->m_GUID,
     734             :         "text", message->m_Message);
     735             : 
     736           0 :     return true;
     737             : }
     738             : 
     739           0 : bool CNetClient::OnReady(void* context, CFsmEvent* event)
     740             : {
     741           0 :     ENSURE(event->GetType() == (uint)NMT_READY);
     742             : 
     743           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     744           0 :     CReadyMessage* message = static_cast<CReadyMessage*>(event->GetParamRef());
     745             : 
     746           0 :     client->PushGuiMessage(
     747             :         "type", "ready",
     748             :         "guid", message->m_GUID,
     749             :         "status", message->m_Status);
     750             : 
     751           0 :     return true;
     752             : }
     753             : 
     754           0 : bool CNetClient::OnGameSetup(void* context, CFsmEvent* event)
     755             : {
     756           0 :     ENSURE(event->GetType() == (uint)NMT_GAME_SETUP);
     757             : 
     758           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     759           0 :     CGameSetupMessage* message = static_cast<CGameSetupMessage*>(event->GetParamRef());
     760             : 
     761           0 :     client->PushGuiMessage(
     762             :         "type", "gamesetup",
     763             :         "data", message->m_Data);
     764             : 
     765           0 :     return true;
     766             : }
     767             : 
     768           0 : bool CNetClient::OnPlayerAssignment(void* context, CFsmEvent* event)
     769             : {
     770           0 :     ENSURE(event->GetType() == (uint)NMT_PLAYER_ASSIGNMENT);
     771             : 
     772           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     773           0 :     CPlayerAssignmentMessage* message = static_cast<CPlayerAssignmentMessage*>(event->GetParamRef());
     774             : 
     775             :     // Unpack the message
     776           0 :     PlayerAssignmentMap newPlayerAssignments;
     777           0 :     for (size_t i = 0; i < message->m_Hosts.size(); ++i)
     778             :     {
     779           0 :         PlayerAssignment assignment;
     780           0 :         assignment.m_Enabled = true;
     781           0 :         assignment.m_Name = message->m_Hosts[i].m_Name;
     782           0 :         assignment.m_PlayerID = message->m_Hosts[i].m_PlayerID;
     783           0 :         assignment.m_Status = message->m_Hosts[i].m_Status;
     784           0 :         newPlayerAssignments[message->m_Hosts[i].m_GUID] = assignment;
     785             :     }
     786             : 
     787           0 :     client->m_PlayerAssignments.swap(newPlayerAssignments);
     788             : 
     789           0 :     client->PostPlayerAssignmentsToScript();
     790             : 
     791           0 :     return true;
     792             : }
     793             : 
     794             : // This is called either when the host clicks the StartGame button or
     795             : // if this client rejoins and finishes the download of the simstate.
     796           0 : bool CNetClient::OnGameStart(void* context, CFsmEvent* event)
     797             : {
     798           0 :     ENSURE(event->GetType() == (uint)NMT_GAME_START);
     799             : 
     800           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     801           0 :     CGameStartMessage* message = static_cast<CGameStartMessage*>(event->GetParamRef());
     802             : 
     803             :     // Find the player assigned to our GUID
     804           0 :     int player = -1;
     805           0 :     if (client->m_PlayerAssignments.find(client->m_GUID) != client->m_PlayerAssignments.end())
     806           0 :         player = client->m_PlayerAssignments[client->m_GUID].m_PlayerID;
     807             : 
     808           0 :     client->m_ClientTurnManager = new CNetClientTurnManager(
     809           0 :             *client->m_Game->GetSimulation2(), *client, client->m_HostID, client->m_Game->GetReplayLogger());
     810             : 
     811             :     // Parse init attributes.
     812           0 :     const ScriptInterface& scriptInterface = client->m_Game->GetSimulation2()->GetScriptInterface();
     813           0 :     ScriptRequest rq(scriptInterface);
     814           0 :     JS::RootedValue initAttribs(rq.cx);
     815           0 :     Script::ParseJSON(rq, message->m_InitAttributes, &initAttribs);
     816             : 
     817           0 :     client->m_Game->SetPlayerID(player);
     818           0 :     client->m_Game->StartGame(&initAttribs, "");
     819             : 
     820           0 :     client->PushGuiMessage("type", "start",
     821             :                            "initAttributes", initAttribs);
     822             : 
     823           0 :     return true;
     824             : }
     825             : 
     826           0 : bool CNetClient::OnJoinSyncStart(void* context, CFsmEvent* event)
     827             : {
     828           0 :     ENSURE(event->GetType() == (uint)NMT_JOIN_SYNC_START);
     829             : 
     830           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     831             : 
     832           0 :     CJoinSyncStartMessage* joinSyncStartMessage = (CJoinSyncStartMessage*)event->GetParamRef();
     833             : 
     834             :     // The server wants us to start downloading the game state from it, so do so
     835           0 :     client->m_Session->GetFileTransferer().StartTask(
     836           0 :         std::shared_ptr<CNetFileReceiveTask>(new CNetFileReceiveTask_ClientRejoin(*client, joinSyncStartMessage->m_InitAttributes))
     837             :     );
     838             : 
     839           0 :     return true;
     840             : }
     841             : 
     842           0 : bool CNetClient::OnJoinSyncEndCommandBatch(void* context, CFsmEvent* event)
     843             : {
     844           0 :     ENSURE(event->GetType() == (uint)NMT_END_COMMAND_BATCH);
     845             : 
     846           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     847             : 
     848           0 :     CEndCommandBatchMessage* endMessage = (CEndCommandBatchMessage*)event->GetParamRef();
     849             : 
     850           0 :     client->m_ClientTurnManager->FinishedAllCommands(endMessage->m_Turn, endMessage->m_TurnLength);
     851             : 
     852             :     // Execute all the received commands for the latest turn
     853           0 :     client->m_ClientTurnManager->UpdateFastForward();
     854             : 
     855           0 :     return true;
     856             : }
     857             : 
     858           0 : bool CNetClient::OnRejoined(void* context, CFsmEvent* event)
     859             : {
     860           0 :     ENSURE(event->GetType() == (uint)NMT_REJOINED);
     861             : 
     862           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     863           0 :     CRejoinedMessage* message = static_cast<CRejoinedMessage*>(event->GetParamRef());
     864             : 
     865           0 :     client->PushGuiMessage(
     866             :         "type", "rejoined",
     867             :         "guid", message->m_GUID);
     868             : 
     869           0 :     return true;
     870             : }
     871             : 
     872           0 : bool CNetClient::OnKicked(void *context, CFsmEvent* event)
     873             : {
     874           0 :     ENSURE(event->GetType() == (uint)NMT_KICKED);
     875             : 
     876           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     877           0 :     CKickedMessage* message = static_cast<CKickedMessage*>(event->GetParamRef());
     878             : 
     879           0 :     client->PushGuiMessage(
     880             :         "username", message->m_Name,
     881             :         "type", "kicked",
     882           0 :         "banned", message->m_Ban != 0);
     883             : 
     884           0 :     return true;
     885             : }
     886             : 
     887           0 : bool CNetClient::OnClientTimeout(void *context, CFsmEvent* event)
     888             : {
     889             :     // Report the timeout of some other client
     890             : 
     891           0 :     ENSURE(event->GetType() == (uint)NMT_CLIENT_TIMEOUT);
     892             : 
     893           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     894           0 :     CClientTimeoutMessage* message = static_cast<CClientTimeoutMessage*>(event->GetParamRef());
     895             : 
     896           0 :     client->PushGuiMessage(
     897             :         "type", "netwarn",
     898             :         "warntype", "client-timeout",
     899             :         "guid", message->m_GUID,
     900             :         "lastReceivedTime", message->m_LastReceivedTime);
     901             : 
     902           0 :     return true;
     903             : }
     904             : 
     905           0 : bool CNetClient::OnClientPerformance(void *context, CFsmEvent* event)
     906             : {
     907             :     // Performance statistics for one or multiple clients
     908             : 
     909           0 :     ENSURE(event->GetType() == (uint)NMT_CLIENT_PERFORMANCE);
     910             : 
     911           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     912           0 :     CClientPerformanceMessage* message = static_cast<CClientPerformanceMessage*>(event->GetParamRef());
     913             : 
     914             :     // Display warnings for other clients with bad ping
     915           0 :     for (size_t i = 0; i < message->m_Clients.size(); ++i)
     916             :     {
     917           0 :         if (message->m_Clients[i].m_MeanRTT < NETWORK_BAD_PING || message->m_Clients[i].m_GUID == client->m_GUID)
     918           0 :             continue;
     919             : 
     920           0 :         client->PushGuiMessage(
     921             :             "type", "netwarn",
     922             :             "warntype", "client-latency",
     923           0 :             "guid", message->m_Clients[i].m_GUID,
     924           0 :             "meanRTT", message->m_Clients[i].m_MeanRTT);
     925             :     }
     926             : 
     927           0 :     return true;
     928             : }
     929             : 
     930           0 : bool CNetClient::OnClientsLoading(void *context, CFsmEvent *event)
     931             : {
     932           0 :     ENSURE(event->GetType() == (uint)NMT_CLIENTS_LOADING);
     933             : 
     934           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     935           0 :     CClientsLoadingMessage* message = static_cast<CClientsLoadingMessage*>(event->GetParamRef());
     936             : 
     937           0 :     std::vector<CStr> guids;
     938           0 :     guids.reserve(message->m_Clients.size());
     939           0 :     for (const CClientsLoadingMessage::S_m_Clients& mClient : message->m_Clients)
     940           0 :         guids.push_back(mClient.m_GUID);
     941             : 
     942           0 :     client->PushGuiMessage(
     943             :         "type", "clients-loading",
     944             :         "guids", guids);
     945           0 :     return true;
     946             : }
     947             : 
     948           0 : bool CNetClient::OnClientPaused(void *context, CFsmEvent *event)
     949             : {
     950           0 :     ENSURE(event->GetType() == (uint)NMT_CLIENT_PAUSED);
     951             : 
     952           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     953           0 :     CClientPausedMessage* message = static_cast<CClientPausedMessage*>(event->GetParamRef());
     954             : 
     955           0 :     client->PushGuiMessage(
     956             :         "type", "paused",
     957           0 :         "pause", message->m_Pause != 0,
     958             :         "guid", message->m_GUID);
     959             : 
     960           0 :     return true;
     961             : }
     962             : 
     963           0 : bool CNetClient::OnLoadedGame(void* context, CFsmEvent* event)
     964             : {
     965           0 :     ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);
     966             : 
     967           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     968             : 
     969             :     // All players have loaded the game - start running the turn manager
     970             :     // so that the game begins
     971           0 :     client->m_Game->SetTurnManager(client->m_ClientTurnManager);
     972             : 
     973           0 :     client->PushGuiMessage(
     974             :         "type", "netstatus",
     975             :         "status", "active");
     976             : 
     977             :     // If we have rejoined an in progress game, send the rejoined message to the server.
     978           0 :     if (client->m_Rejoin)
     979           0 :         client->SendRejoinedMessage();
     980             : 
     981           0 :     return true;
     982             : }
     983             : 
     984           0 : bool CNetClient::OnInGame(void *context, CFsmEvent* event)
     985             : {
     986             :     // TODO: should split each of these cases into a separate method
     987             : 
     988           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     989           0 :     CNetMessage* message = static_cast<CNetMessage*>(event->GetParamRef());
     990             : 
     991           0 :     if (message)
     992             :     {
     993           0 :         if (message->GetType() == NMT_SIMULATION_COMMAND)
     994             :         {
     995           0 :             CSimulationMessage* simMessage = static_cast<CSimulationMessage*> (message);
     996           0 :             client->m_ClientTurnManager->OnSimulationMessage(simMessage);
     997             :         }
     998           0 :         else if (message->GetType() == NMT_SYNC_ERROR)
     999             :         {
    1000           0 :             CSyncErrorMessage* syncMessage = static_cast<CSyncErrorMessage*> (message);
    1001           0 :             client->m_ClientTurnManager->OnSyncError(syncMessage->m_Turn, syncMessage->m_HashExpected, syncMessage->m_PlayerNames);
    1002             :         }
    1003           0 :         else if (message->GetType() == NMT_END_COMMAND_BATCH)
    1004             :         {
    1005           0 :             CEndCommandBatchMessage* endMessage = static_cast<CEndCommandBatchMessage*> (message);
    1006           0 :             client->m_ClientTurnManager->FinishedAllCommands(endMessage->m_Turn, endMessage->m_TurnLength);
    1007             :         }
    1008             :     }
    1009             : 
    1010           0 :     return true;
    1011        4358 : }

Generated by: LCOV version 1.13