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 : }
|