1 /* Copyright (C) 2018 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/sysdep/sysdep.h"
29 #include "lobby/IXmppClient.h"
30 #include "ps/CConsole.h"
31 #include "ps/CLogger.h"
32 #include "ps/Compress.h"
33 #include "ps/CStr.h"
34 #include "ps/Game.h"
35 #include "ps/Loader.h"
36 #include "scriptinterface/ScriptInterface.h"
37 #include "simulation2/Simulation2.h"
38 
39 CNetClient *g_NetClient = NULL;
40 
41 /**
42  * Async task for receiving the initial game state when rejoining an
43  * in-progress network game.
44  */
45 class CNetFileReceiveTask_ClientRejoin : public CNetFileReceiveTask
46 {
47 	NONCOPYABLE(CNetFileReceiveTask_ClientRejoin);
48 public:
CNetFileReceiveTask_ClientRejoin(CNetClient & client)49 	CNetFileReceiveTask_ClientRejoin(CNetClient& client)
50 		: m_Client(client)
51 	{
52 	}
53 
OnComplete()54 	virtual void OnComplete()
55 	{
56 		// We've received the game state from the server
57 
58 		// Save it so we can use it after the map has finished loading
59 		m_Client.m_JoinSyncBuffer = m_Buffer;
60 
61 		// Pretend the server told us to start the game
62 		CGameStartMessage start;
63 		m_Client.HandleMessage(&start);
64 	}
65 
66 private:
67 	CNetClient& m_Client;
68 };
69 
CNetClient(CGame * game,bool isLocalClient)70 CNetClient::CNetClient(CGame* game, bool isLocalClient) :
71 	m_Session(NULL),
72 	m_UserName(L"anonymous"),
73 	m_HostID((u32)-1), m_ClientTurnManager(NULL), m_Game(game),
74 	m_GameAttributes(game->GetSimulation2()->GetScriptInterface().GetContext()),
75 	m_IsLocalClient(isLocalClient),
76 	m_LastConnectionCheck(0),
77 	m_Rejoin(false)
78 {
79 	m_Game->SetTurnManager(NULL); // delete the old local turn manager so we don't accidentally use it
80 
81 	void* context = this;
82 
83 	JS_AddExtraGCRootsTracer(GetScriptInterface().GetJSRuntime(), CNetClient::Trace, this);
84 
85 	// Set up transitions for session
86 	AddTransition(NCS_UNCONNECTED, (uint)NMT_CONNECT_COMPLETE, NCS_CONNECT, (void*)&OnConnect, context);
87 
88 	AddTransition(NCS_CONNECT, (uint)NMT_SERVER_HANDSHAKE, NCS_HANDSHAKE, (void*)&OnHandshake, context);
89 
90 	AddTransition(NCS_HANDSHAKE, (uint)NMT_SERVER_HANDSHAKE_RESPONSE, NCS_AUTHENTICATE, (void*)&OnHandshakeResponse, context);
91 
92 	AddTransition(NCS_AUTHENTICATE, (uint)NMT_AUTHENTICATE, NCS_AUTHENTICATE, (void*)&OnAuthenticateRequest, context);
93 	AddTransition(NCS_AUTHENTICATE, (uint)NMT_AUTHENTICATE_RESULT, NCS_INITIAL_GAMESETUP, (void*)&OnAuthenticate, context);
94 
95 	AddTransition(NCS_INITIAL_GAMESETUP, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context);
96 
97 	AddTransition(NCS_PREGAME, (uint)NMT_CHAT, NCS_PREGAME, (void*)&OnChat, context);
98 	AddTransition(NCS_PREGAME, (uint)NMT_READY, NCS_PREGAME, (void*)&OnReady, context);
99 	AddTransition(NCS_PREGAME, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context);
100 	AddTransition(NCS_PREGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_PREGAME, (void*)&OnPlayerAssignment, context);
101 	AddTransition(NCS_PREGAME, (uint)NMT_KICKED, NCS_PREGAME, (void*)&OnKicked, context);
102 	AddTransition(NCS_PREGAME, (uint)NMT_CLIENT_TIMEOUT, NCS_PREGAME, (void*)&OnClientTimeout, context);
103 	AddTransition(NCS_PREGAME, (uint)NMT_CLIENT_PERFORMANCE, NCS_PREGAME, (void*)&OnClientPerformance, context);
104 	AddTransition(NCS_PREGAME, (uint)NMT_GAME_START, NCS_LOADING, (void*)&OnGameStart, context);
105 	AddTransition(NCS_PREGAME, (uint)NMT_JOIN_SYNC_START, NCS_JOIN_SYNCING, (void*)&OnJoinSyncStart, context);
106 
107 	AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CHAT, NCS_JOIN_SYNCING, (void*)&OnChat, context);
108 	AddTransition(NCS_JOIN_SYNCING, (uint)NMT_GAME_SETUP, NCS_JOIN_SYNCING, (void*)&OnGameSetup, context);
109 	AddTransition(NCS_JOIN_SYNCING, (uint)NMT_PLAYER_ASSIGNMENT, NCS_JOIN_SYNCING, (void*)&OnPlayerAssignment, context);
110 	AddTransition(NCS_JOIN_SYNCING, (uint)NMT_KICKED, NCS_JOIN_SYNCING, (void*)&OnKicked, context);
111 	AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CLIENT_TIMEOUT, NCS_JOIN_SYNCING, (void*)&OnClientTimeout, context);
112 	AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CLIENT_PERFORMANCE, NCS_JOIN_SYNCING, (void*)&OnClientPerformance, context);
113 	AddTransition(NCS_JOIN_SYNCING, (uint)NMT_GAME_START, NCS_JOIN_SYNCING, (void*)&OnGameStart, context);
114 	AddTransition(NCS_JOIN_SYNCING, (uint)NMT_SIMULATION_COMMAND, NCS_JOIN_SYNCING, (void*)&OnInGame, context);
115 	AddTransition(NCS_JOIN_SYNCING, (uint)NMT_END_COMMAND_BATCH, NCS_JOIN_SYNCING, (void*)&OnJoinSyncEndCommandBatch, context);
116 	AddTransition(NCS_JOIN_SYNCING, (uint)NMT_LOADED_GAME, NCS_INGAME, (void*)&OnLoadedGame, context);
117 
118 	AddTransition(NCS_LOADING, (uint)NMT_CHAT, NCS_LOADING, (void*)&OnChat, context);
119 	AddTransition(NCS_LOADING, (uint)NMT_GAME_SETUP, NCS_LOADING, (void*)&OnGameSetup, context);
120 	AddTransition(NCS_LOADING, (uint)NMT_PLAYER_ASSIGNMENT, NCS_LOADING, (void*)&OnPlayerAssignment, context);
121 	AddTransition(NCS_LOADING, (uint)NMT_KICKED, NCS_LOADING, (void*)&OnKicked, context);
122 	AddTransition(NCS_LOADING, (uint)NMT_CLIENT_TIMEOUT, NCS_LOADING, (void*)&OnClientTimeout, context);
123 	AddTransition(NCS_LOADING, (uint)NMT_CLIENT_PERFORMANCE, NCS_LOADING, (void*)&OnClientPerformance, context);
124 	AddTransition(NCS_LOADING, (uint)NMT_CLIENTS_LOADING, NCS_LOADING, (void*)&OnClientsLoading, context);
125 	AddTransition(NCS_LOADING, (uint)NMT_LOADED_GAME, NCS_INGAME, (void*)&OnLoadedGame, context);
126 
127 	AddTransition(NCS_INGAME, (uint)NMT_REJOINED, NCS_INGAME, (void*)&OnRejoined, context);
128 	AddTransition(NCS_INGAME, (uint)NMT_KICKED, NCS_INGAME, (void*)&OnKicked, context);
129 	AddTransition(NCS_INGAME, (uint)NMT_CLIENT_TIMEOUT, NCS_INGAME, (void*)&OnClientTimeout, context);
130 	AddTransition(NCS_INGAME, (uint)NMT_CLIENT_PERFORMANCE, NCS_INGAME, (void*)&OnClientPerformance, context);
131 	AddTransition(NCS_INGAME, (uint)NMT_CLIENTS_LOADING, NCS_INGAME, (void*)&OnClientsLoading, context);
132 	AddTransition(NCS_INGAME, (uint)NMT_CLIENT_PAUSED, NCS_INGAME, (void*)&OnClientPaused, context);
133 	AddTransition(NCS_INGAME, (uint)NMT_CHAT, NCS_INGAME, (void*)&OnChat, context);
134 	AddTransition(NCS_INGAME, (uint)NMT_GAME_SETUP, NCS_INGAME, (void*)&OnGameSetup, context);
135 	AddTransition(NCS_INGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_INGAME, (void*)&OnPlayerAssignment, context);
136 	AddTransition(NCS_INGAME, (uint)NMT_SIMULATION_COMMAND, NCS_INGAME, (void*)&OnInGame, context);
137 	AddTransition(NCS_INGAME, (uint)NMT_SYNC_ERROR, NCS_INGAME, (void*)&OnInGame, context);
138 	AddTransition(NCS_INGAME, (uint)NMT_END_COMMAND_BATCH, NCS_INGAME, (void*)&OnInGame, context);
139 
140 	// Set first state
141 	SetFirstState(NCS_UNCONNECTED);
142 }
143 
~CNetClient()144 CNetClient::~CNetClient()
145 {
146 	DestroyConnection();
147 	JS_RemoveExtraGCRootsTracer(GetScriptInterface().GetJSRuntime(), CNetClient::Trace, this);
148 }
149 
TraceMember(JSTracer * trc)150 void CNetClient::TraceMember(JSTracer *trc)
151 {
152 	for (JS::Heap<JS::Value>& guiMessage : m_GuiMessageQueue)
153 		JS_CallValueTracer(trc, &guiMessage, "m_GuiMessageQueue");
154 }
155 
SetUserName(const CStrW & username)156 void CNetClient::SetUserName(const CStrW& username)
157 {
158 	ENSURE(!m_Session); // must be called before we start the connection
159 
160 	m_UserName = username;
161 }
162 
SetHostingPlayerName(const CStr & hostingPlayerName)163 void CNetClient::SetHostingPlayerName(const CStr& hostingPlayerName)
164 {
165 	m_HostingPlayerName = hostingPlayerName;
166 }
167 
SetupConnection(const CStr & server,const u16 port,ENetHost * enetClient)168 bool CNetClient::SetupConnection(const CStr& server, const u16 port, ENetHost* enetClient)
169 {
170 	CNetClientSession* session = new CNetClientSession(*this);
171 	bool ok = session->Connect(server, port, m_IsLocalClient, enetClient);
172 	SetAndOwnSession(session);
173 	return ok;
174 }
175 
SetAndOwnSession(CNetClientSession * session)176 void CNetClient::SetAndOwnSession(CNetClientSession* session)
177 {
178 	delete m_Session;
179 	m_Session = session;
180 }
181 
DestroyConnection()182 void CNetClient::DestroyConnection()
183 {
184 	// Attempt to send network messages from the current frame before connection is destroyed.
185 	if (m_ClientTurnManager)
186 	{
187 		m_ClientTurnManager->OnDestroyConnection();
188 		Flush();
189 	}
190 	SAFE_DELETE(m_Session);
191 }
192 
Poll()193 void CNetClient::Poll()
194 {
195 	if (!m_Session)
196 		return;
197 
198 	CheckServerConnection();
199 	m_Session->Poll();
200 }
201 
CheckServerConnection()202 void CNetClient::CheckServerConnection()
203 {
204 	// Trigger local warnings if the connection to the server is bad.
205 	// At most once per second.
206 	std::time_t now = std::time(nullptr);
207 	if (now <= m_LastConnectionCheck)
208 		return;
209 
210 	m_LastConnectionCheck = now;
211 
212 	JSContext* cx = GetScriptInterface().GetContext();
213 	JSAutoRequest rq(cx);
214 
215 	// Report if we are losing the connection to the server
216 	u32 lastReceived = m_Session->GetLastReceivedTime();
217 	if (lastReceived > NETWORK_WARNING_TIMEOUT)
218 	{
219 		JS::RootedValue msg(cx);
220 		GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'server-timeout' })", &msg);
221 		GetScriptInterface().SetProperty(msg, "lastReceivedTime", lastReceived);
222 		PushGuiMessage(msg);
223 		return;
224 	}
225 
226 	// Report if we have a bad ping to the server
227 	u32 meanRTT = m_Session->GetMeanRTT();
228 	if (meanRTT > DEFAULT_TURN_LENGTH_MP)
229 	{
230 		JS::RootedValue msg(cx);
231 		GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'server-latency' })", &msg);
232 		GetScriptInterface().SetProperty(msg, "meanRTT", meanRTT);
233 		PushGuiMessage(msg);
234 	}
235 }
236 
Flush()237 void CNetClient::Flush()
238 {
239 	if (m_Session)
240 		m_Session->Flush();
241 }
242 
GuiPoll(JS::MutableHandleValue ret)243 void CNetClient::GuiPoll(JS::MutableHandleValue ret)
244 {
245 	if (m_GuiMessageQueue.empty())
246 	{
247 		ret.setUndefined();
248 		return;
249 	}
250 
251 	ret.set(m_GuiMessageQueue.front());
252 	m_GuiMessageQueue.pop_front();
253 }
254 
PushGuiMessage(const JS::HandleValue message)255 void CNetClient::PushGuiMessage(const JS::HandleValue message)
256 {
257 	ENSURE(!message.isUndefined());
258 
259 	m_GuiMessageQueue.push_back(JS::Heap<JS::Value>(message));
260 }
261 
TestReadGuiMessages()262 std::string CNetClient::TestReadGuiMessages()
263 {
264 	JSContext* cx = GetScriptInterface().GetContext();
265 	JSAutoRequest rq(cx);
266 
267 	std::string r;
268 	JS::RootedValue msg(cx);
269 	while (true)
270 	{
271 		GuiPoll(&msg);
272 		if (msg.isUndefined())
273 			break;
274 		r += GetScriptInterface().ToString(&msg) + "\n";
275 	}
276 	return r;
277 }
278 
GetScriptInterface()279 const ScriptInterface& CNetClient::GetScriptInterface()
280 {
281 	return m_Game->GetSimulation2()->GetScriptInterface();
282 }
283 
PostPlayerAssignmentsToScript()284 void CNetClient::PostPlayerAssignmentsToScript()
285 {
286 	JSContext* cx = GetScriptInterface().GetContext();
287 	JSAutoRequest rq(cx);
288 
289 	JS::RootedValue msg(cx);
290 	GetScriptInterface().Eval("({'type':'players', 'newAssignments':{}})", &msg);
291 
292 	JS::RootedValue newAssignments(cx);
293 	GetScriptInterface().GetProperty(msg, "newAssignments", &newAssignments);
294 
295 	for (const std::pair<CStr, PlayerAssignment>& p : m_PlayerAssignments)
296 	{
297 		JS::RootedValue assignment(cx);
298 		GetScriptInterface().Eval("({})", &assignment);
299 		GetScriptInterface().SetProperty(assignment, "name", CStrW(p.second.m_Name), false);
300 		GetScriptInterface().SetProperty(assignment, "player", p.second.m_PlayerID, false);
301 		GetScriptInterface().SetProperty(assignment, "status", p.second.m_Status, false);
302 		GetScriptInterface().SetProperty(newAssignments, p.first.c_str(), assignment, false);
303 	}
304 
305 	PushGuiMessage(msg);
306 }
307 
SendMessage(const CNetMessage * message)308 bool CNetClient::SendMessage(const CNetMessage* message)
309 {
310 	if (!m_Session)
311 		return false;
312 
313 	return m_Session->SendMessage(message);
314 }
315 
HandleConnect()316 void CNetClient::HandleConnect()
317 {
318 	Update((uint)NMT_CONNECT_COMPLETE, NULL);
319 }
320 
HandleDisconnect(u32 reason)321 void CNetClient::HandleDisconnect(u32 reason)
322 {
323 	JSContext* cx = GetScriptInterface().GetContext();
324 	JSAutoRequest rq(cx);
325 
326 	JS::RootedValue msg(cx);
327 	GetScriptInterface().Eval("({'type':'netstatus','status':'disconnected'})", &msg);
328 	GetScriptInterface().SetProperty(msg, "reason", (int)reason, false);
329 	PushGuiMessage(msg);
330 
331 	SAFE_DELETE(m_Session);
332 
333 	// Update the state immediately to UNCONNECTED (don't bother with FSM transitions since
334 	// we'd need one for every single state, and we don't need to use per-state actions)
335 	SetCurrState(NCS_UNCONNECTED);
336 }
337 
SendGameSetupMessage(JS::MutableHandleValue attrs,const ScriptInterface & scriptInterface)338 void CNetClient::SendGameSetupMessage(JS::MutableHandleValue attrs, const ScriptInterface& scriptInterface)
339 {
340 	CGameSetupMessage gameSetup(scriptInterface);
341 	gameSetup.m_Data = attrs;
342 	SendMessage(&gameSetup);
343 }
344 
SendAssignPlayerMessage(const int playerID,const CStr & guid)345 void CNetClient::SendAssignPlayerMessage(const int playerID, const CStr& guid)
346 {
347 	CAssignPlayerMessage assignPlayer;
348 	assignPlayer.m_PlayerID = playerID;
349 	assignPlayer.m_GUID = guid;
350 	SendMessage(&assignPlayer);
351 }
352 
SendChatMessage(const std::wstring & text)353 void CNetClient::SendChatMessage(const std::wstring& text)
354 {
355 	CChatMessage chat;
356 	chat.m_Message = text;
357 	SendMessage(&chat);
358 }
359 
SendReadyMessage(const int status)360 void CNetClient::SendReadyMessage(const int status)
361 {
362 	CReadyMessage readyStatus;
363 	readyStatus.m_Status = status;
364 	SendMessage(&readyStatus);
365 }
366 
SendClearAllReadyMessage()367 void CNetClient::SendClearAllReadyMessage()
368 {
369 	CClearAllReadyMessage clearAllReady;
370 	SendMessage(&clearAllReady);
371 }
372 
SendStartGameMessage()373 void CNetClient::SendStartGameMessage()
374 {
375 	CGameStartMessage gameStart;
376 	SendMessage(&gameStart);
377 }
378 
SendRejoinedMessage()379 void CNetClient::SendRejoinedMessage()
380 {
381 	CRejoinedMessage rejoinedMessage;
382 	SendMessage(&rejoinedMessage);
383 }
384 
SendKickPlayerMessage(const CStrW & playerName,bool ban)385 void CNetClient::SendKickPlayerMessage(const CStrW& playerName, bool ban)
386 {
387 	CKickedMessage kickPlayer;
388 	kickPlayer.m_Name = playerName;
389 	kickPlayer.m_Ban = ban;
390 	SendMessage(&kickPlayer);
391 }
392 
SendPausedMessage(bool pause)393 void CNetClient::SendPausedMessage(bool pause)
394 {
395 	CClientPausedMessage pausedMessage;
396 	pausedMessage.m_Pause = pause;
397 	SendMessage(&pausedMessage);
398 }
399 
HandleMessage(CNetMessage * message)400 bool CNetClient::HandleMessage(CNetMessage* message)
401 {
402 	// Handle non-FSM messages first
403 
404 	Status status = m_Session->GetFileTransferer().HandleMessageReceive(message);
405 	if (status == INFO::OK)
406 		return true;
407 	if (status != INFO::SKIPPED)
408 		return false;
409 
410 	if (message->GetType() == NMT_FILE_TRANSFER_REQUEST)
411 	{
412 		CFileTransferRequestMessage* reqMessage = (CFileTransferRequestMessage*)message;
413 
414 		// TODO: we should support different transfer request types, instead of assuming
415 		// it's always requesting the simulation state
416 
417 		std::stringstream stream;
418 
419 		LOGMESSAGERENDER("Serializing game at turn %u for rejoining player", m_ClientTurnManager->GetCurrentTurn());
420 		u32 turn = to_le32(m_ClientTurnManager->GetCurrentTurn());
421 		stream.write((char*)&turn, sizeof(turn));
422 
423 		bool ok = m_Game->GetSimulation2()->SerializeState(stream);
424 		ENSURE(ok);
425 
426 		// Compress the content with zlib to save bandwidth
427 		// (TODO: if this is still too large, compressing with e.g. LZMA works much better)
428 		std::string compressed;
429 		CompressZLib(stream.str(), compressed, true);
430 
431 		m_Session->GetFileTransferer().StartResponse(reqMessage->m_RequestID, compressed);
432 
433 		return true;
434 	}
435 
436 	// Update FSM
437 	bool ok = Update(message->GetType(), message);
438 	if (!ok)
439 		LOGERROR("Net client: Error running FSM update (type=%d state=%d)", (int)message->GetType(), (int)GetCurrState());
440 	return ok;
441 }
442 
LoadFinished()443 void CNetClient::LoadFinished()
444 {
445 	JSContext* cx = GetScriptInterface().GetContext();
446 	JSAutoRequest rq(cx);
447 
448 	if (!m_JoinSyncBuffer.empty())
449 	{
450 		// We're rejoining a game, and just finished loading the initial map,
451 		// so deserialize the saved game state now
452 
453 		std::string state;
454 		DecompressZLib(m_JoinSyncBuffer, state, true);
455 
456 		std::stringstream stream(state);
457 
458 		u32 turn;
459 		stream.read((char*)&turn, sizeof(turn));
460 		turn = to_le32(turn);
461 
462 		LOGMESSAGE("Rejoining client deserializing state at turn %u\n", turn);
463 
464 		bool ok = m_Game->GetSimulation2()->DeserializeState(stream);
465 		ENSURE(ok);
466 
467 		m_ClientTurnManager->ResetState(turn, turn);
468 
469 		JS::RootedValue msg(cx);
470 		GetScriptInterface().Eval("({'type':'netstatus','status':'join_syncing'})", &msg);
471 		PushGuiMessage(msg);
472 	}
473 	else
474 	{
475 		// Connecting at the start of a game, so we'll wait for other players to finish loading
476 		JS::RootedValue msg(cx);
477 		GetScriptInterface().Eval("({'type':'netstatus','status':'waiting_for_players'})", &msg);
478 		PushGuiMessage(msg);
479 	}
480 
481 	CLoadedGameMessage loaded;
482 	loaded.m_CurrentTurn = m_ClientTurnManager->GetCurrentTurn();
483 	SendMessage(&loaded);
484 }
485 
SendAuthenticateMessage()486 void CNetClient::SendAuthenticateMessage()
487 {
488 	CAuthenticateMessage authenticate;
489 	authenticate.m_Name = m_UserName;
490 	authenticate.m_Password = L""; // TODO
491 	authenticate.m_IsLocalClient = m_IsLocalClient;
492 	SendMessage(&authenticate);
493 }
494 
OnConnect(void * context,CFsmEvent * event)495 bool CNetClient::OnConnect(void* context, CFsmEvent* event)
496 {
497 	ENSURE(event->GetType() == (uint)NMT_CONNECT_COMPLETE);
498 
499 	CNetClient* client = (CNetClient*)context;
500 
501 	JSContext* cx = client->GetScriptInterface().GetContext();
502 	JSAutoRequest rq(cx);
503 
504 	JS::RootedValue msg(cx);
505 	client->GetScriptInterface().Eval("({'type':'netstatus','status':'connected'})", &msg);
506 	client->PushGuiMessage(msg);
507 
508 	return true;
509 }
510 
OnHandshake(void * context,CFsmEvent * event)511 bool CNetClient::OnHandshake(void* context, CFsmEvent* event)
512 {
513 	ENSURE(event->GetType() == (uint)NMT_SERVER_HANDSHAKE);
514 
515 	CNetClient* client = (CNetClient*)context;
516 
517 	CCliHandshakeMessage handshake;
518 	handshake.m_MagicResponse = PS_PROTOCOL_MAGIC_RESPONSE;
519 	handshake.m_ProtocolVersion = PS_PROTOCOL_VERSION;
520 	handshake.m_SoftwareVersion = PS_PROTOCOL_VERSION;
521 	client->SendMessage(&handshake);
522 
523 	return true;
524 }
525 
OnHandshakeResponse(void * context,CFsmEvent * event)526 bool CNetClient::OnHandshakeResponse(void* context, CFsmEvent* event)
527 {
528 	ENSURE(event->GetType() == (uint)NMT_SERVER_HANDSHAKE_RESPONSE);
529 
530 	CNetClient* client = (CNetClient*)context;
531 
532 	CSrvHandshakeResponseMessage* message = (CSrvHandshakeResponseMessage*)event->GetParamRef();
533 	client->m_GUID = message->m_GUID;
534 
535 	if (message->m_Flags & PS_NETWORK_FLAG_REQUIRE_LOBBYAUTH)
536 	{
537 		if (g_XmppClient && !client->m_HostingPlayerName.empty())
538 			g_XmppClient->SendIqLobbyAuth(client->m_HostingPlayerName, client->m_GUID);
539 		else
540 		{
541 			JSContext* cx = client->GetScriptInterface().GetContext();
542 			JSAutoRequest rq(cx);
543 
544 			JS::RootedValue msg(cx);
545 			client->GetScriptInterface().Eval("({'type':'netstatus','status':'disconnected'})", &msg);
546 			client->GetScriptInterface().SetProperty(msg, "reason", (int)NDR_LOBBY_AUTH_FAILED, false);
547 			client->PushGuiMessage(msg);
548 			LOGMESSAGE("Net client: Couldn't send lobby auth xmpp message");
549 		}
550 		return true;
551 	}
552 
553 	client->SendAuthenticateMessage();
554 	return true;
555 }
556 
OnAuthenticateRequest(void * context,CFsmEvent * event)557 bool CNetClient::OnAuthenticateRequest(void* context, CFsmEvent* event)
558 {
559 	ENSURE(event->GetType() == (uint)NMT_AUTHENTICATE);
560 
561 	CNetClient* client = (CNetClient*)context;
562 	client->SendAuthenticateMessage();
563 	return true;
564 }
565 
OnAuthenticate(void * context,CFsmEvent * event)566 bool CNetClient::OnAuthenticate(void* context, CFsmEvent* event)
567 {
568 	ENSURE(event->GetType() == (uint)NMT_AUTHENTICATE_RESULT);
569 
570 	CNetClient* client = (CNetClient*)context;
571 
572 	JSContext* cx = client->GetScriptInterface().GetContext();
573 	JSAutoRequest rq(cx);
574 
575 	CAuthenticateResultMessage* message = (CAuthenticateResultMessage*)event->GetParamRef();
576 
577 	LOGMESSAGE("Net: Authentication result: host=%u, %s", message->m_HostID, utf8_from_wstring(message->m_Message));
578 
579 	client->m_HostID = message->m_HostID;
580 	client->m_Rejoin = message->m_Code == ARC_OK_REJOINING;
581 
582 	JS::RootedValue msg(cx);
583 	client->GetScriptInterface().Eval("({'type':'netstatus','status':'authenticated'})", &msg);
584 	client->GetScriptInterface().SetProperty(msg, "rejoining", client->m_Rejoin);
585 	client->PushGuiMessage(msg);
586 
587 	return true;
588 }
589 
OnChat(void * context,CFsmEvent * event)590 bool CNetClient::OnChat(void* context, CFsmEvent* event)
591 {
592 	ENSURE(event->GetType() == (uint)NMT_CHAT);
593 
594 	CNetClient* client = (CNetClient*)context;
595 	JSContext* cx = client->GetScriptInterface().GetContext();
596 	JSAutoRequest rq(cx);
597 
598 	CChatMessage* message = (CChatMessage*)event->GetParamRef();
599 
600 	JS::RootedValue msg(cx);
601 	client->GetScriptInterface().Eval("({'type':'chat'})", &msg);
602 	client->GetScriptInterface().SetProperty(msg, "guid", std::string(message->m_GUID), false);
603 	client->GetScriptInterface().SetProperty(msg, "text", std::wstring(message->m_Message), false);
604 	client->PushGuiMessage(msg);
605 
606 	return true;
607 }
608 
OnReady(void * context,CFsmEvent * event)609 bool CNetClient::OnReady(void* context, CFsmEvent* event)
610 {
611 	ENSURE(event->GetType() == (uint)NMT_READY);
612 
613 	CNetClient* client = (CNetClient*)context;
614 	JSContext* cx = client->GetScriptInterface().GetContext();
615 	JSAutoRequest rq(cx);
616 
617 	CReadyMessage* message = (CReadyMessage*)event->GetParamRef();
618 
619 	JS::RootedValue msg(cx);
620 	client->GetScriptInterface().Eval("({'type':'ready'})", &msg);
621 	client->GetScriptInterface().SetProperty(msg, "guid", std::string(message->m_GUID), false);
622 	client->GetScriptInterface().SetProperty(msg, "status", int (message->m_Status), false);
623 	client->PushGuiMessage(msg);
624 
625 	return true;
626 }
627 
OnGameSetup(void * context,CFsmEvent * event)628 bool CNetClient::OnGameSetup(void* context, CFsmEvent* event)
629 {
630 	ENSURE(event->GetType() == (uint)NMT_GAME_SETUP);
631 
632 	CNetClient* client = (CNetClient*)context;
633 	JSContext* cx = client->GetScriptInterface().GetContext();
634 	JSAutoRequest rq(cx);
635 
636 	CGameSetupMessage* message = (CGameSetupMessage*)event->GetParamRef();
637 
638 	client->m_GameAttributes = message->m_Data;
639 
640 	JS::RootedValue msg(cx);
641 	client->GetScriptInterface().Eval("({'type':'gamesetup'})", &msg);
642 	client->GetScriptInterface().SetProperty(msg, "data", message->m_Data, false);
643 	client->PushGuiMessage(msg);
644 
645 	return true;
646 }
647 
OnPlayerAssignment(void * context,CFsmEvent * event)648 bool CNetClient::OnPlayerAssignment(void* context, CFsmEvent* event)
649 {
650 	ENSURE(event->GetType() == (uint)NMT_PLAYER_ASSIGNMENT);
651 
652 	CNetClient* client = (CNetClient*)context;
653 
654 	CPlayerAssignmentMessage* message = (CPlayerAssignmentMessage*)event->GetParamRef();
655 
656 	// Unpack the message
657 	PlayerAssignmentMap newPlayerAssignments;
658 	for (size_t i = 0; i < message->m_Hosts.size(); ++i)
659 	{
660 		PlayerAssignment assignment;
661 		assignment.m_Enabled = true;
662 		assignment.m_Name = message->m_Hosts[i].m_Name;
663 		assignment.m_PlayerID = message->m_Hosts[i].m_PlayerID;
664 		assignment.m_Status = message->m_Hosts[i].m_Status;
665 		newPlayerAssignments[message->m_Hosts[i].m_GUID] = assignment;
666 	}
667 
668 	client->m_PlayerAssignments.swap(newPlayerAssignments);
669 
670 	client->PostPlayerAssignmentsToScript();
671 
672 	return true;
673 }
674 
675 // This is called either when the host clicks the StartGame button or
676 // if this client rejoins and finishes the download of the simstate.
OnGameStart(void * context,CFsmEvent * event)677 bool CNetClient::OnGameStart(void* context, CFsmEvent* event)
678 {
679 	ENSURE(event->GetType() == (uint)NMT_GAME_START);
680 
681 	CNetClient* client = (CNetClient*)context;
682 	JSContext* cx = client->GetScriptInterface().GetContext();
683 	JSAutoRequest rq(cx);
684 
685 	client->m_Session->SetLongTimeout(true);
686 
687 	// Find the player assigned to our GUID
688 	int player = -1;
689 	if (client->m_PlayerAssignments.find(client->m_GUID) != client->m_PlayerAssignments.end())
690 		player = client->m_PlayerAssignments[client->m_GUID].m_PlayerID;
691 
692 	client->m_ClientTurnManager = new CNetClientTurnManager(
693 			*client->m_Game->GetSimulation2(), *client, client->m_HostID, client->m_Game->GetReplayLogger());
694 
695 	client->m_Game->SetPlayerID(player);
696 	client->m_Game->StartGame(&client->m_GameAttributes, "");
697 
698 	JS::RootedValue msg(cx);
699 	client->GetScriptInterface().Eval("({'type':'start'})", &msg);
700 	client->PushGuiMessage(msg);
701 
702 	return true;
703 }
704 
OnJoinSyncStart(void * context,CFsmEvent * event)705 bool CNetClient::OnJoinSyncStart(void* context, CFsmEvent* event)
706 {
707 	ENSURE(event->GetType() == (uint)NMT_JOIN_SYNC_START);
708 
709 	CNetClient* client = (CNetClient*)context;
710 
711 	// The server wants us to start downloading the game state from it, so do so
712 	client->m_Session->GetFileTransferer().StartTask(
713 		shared_ptr<CNetFileReceiveTask>(new CNetFileReceiveTask_ClientRejoin(*client))
714 	);
715 
716 	return true;
717 }
718 
OnJoinSyncEndCommandBatch(void * context,CFsmEvent * event)719 bool CNetClient::OnJoinSyncEndCommandBatch(void* context, CFsmEvent* event)
720 {
721 	ENSURE(event->GetType() == (uint)NMT_END_COMMAND_BATCH);
722 
723 	CNetClient* client = (CNetClient*)context;
724 
725 	CEndCommandBatchMessage* endMessage = (CEndCommandBatchMessage*)event->GetParamRef();
726 
727 	client->m_ClientTurnManager->FinishedAllCommands(endMessage->m_Turn, endMessage->m_TurnLength);
728 
729 	// Execute all the received commands for the latest turn
730 	client->m_ClientTurnManager->UpdateFastForward();
731 
732 	return true;
733 }
734 
OnRejoined(void * context,CFsmEvent * event)735 bool CNetClient::OnRejoined(void* context, CFsmEvent* event)
736 {
737 	ENSURE(event->GetType() == (uint)NMT_REJOINED);
738 
739 	CNetClient* client = (CNetClient*)context;
740 	JSContext* cx = client->GetScriptInterface().GetContext();
741 	JSAutoRequest rq(cx);
742 
743 	CRejoinedMessage* message = (CRejoinedMessage*)event->GetParamRef();
744 	JS::RootedValue msg(cx);
745 	client->GetScriptInterface().Eval("({'type':'rejoined'})", &msg);
746 	client->GetScriptInterface().SetProperty(msg, "guid", std::string(message->m_GUID), false);
747 	client->PushGuiMessage(msg);
748 
749 	return true;
750 }
751 
OnKicked(void * context,CFsmEvent * event)752 bool CNetClient::OnKicked(void *context, CFsmEvent* event)
753 {
754 	ENSURE(event->GetType() == (uint)NMT_KICKED);
755 
756 	CNetClient* client = (CNetClient*)context;
757 	JSContext* cx = client->GetScriptInterface().GetContext();
758 	JSAutoRequest rq(cx);
759 
760 	CKickedMessage* message = (CKickedMessage*)event->GetParamRef();
761 	JS::RootedValue msg(cx);
762 
763 	client->GetScriptInterface().Eval("({})", &msg);
764 	client->GetScriptInterface().SetProperty(msg, "username", message->m_Name);
765 	client->GetScriptInterface().SetProperty(msg, "type", CStr("kicked"));
766 	client->GetScriptInterface().SetProperty(msg, "banned", message->m_Ban != 0);
767 	client->PushGuiMessage(msg);
768 
769 	return true;
770 }
771 
OnClientTimeout(void * context,CFsmEvent * event)772 bool CNetClient::OnClientTimeout(void *context, CFsmEvent* event)
773 {
774 	// Report the timeout of some other client
775 
776 	ENSURE(event->GetType() == (uint)NMT_CLIENT_TIMEOUT);
777 
778 	CNetClient* client = (CNetClient*)context;
779 	JSContext* cx = client->GetScriptInterface().GetContext();
780 	JSAutoRequest rq(cx);
781 
782 	CClientTimeoutMessage* message = (CClientTimeoutMessage*)event->GetParamRef();
783 	JS::RootedValue msg(cx);
784 
785 	client->GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'client-timeout' })", &msg);
786 	client->GetScriptInterface().SetProperty(msg, "guid", std::string(message->m_GUID));
787 	client->GetScriptInterface().SetProperty(msg, "lastReceivedTime", message->m_LastReceivedTime);
788 	client->PushGuiMessage(msg);
789 
790 	return true;
791 }
792 
OnClientPerformance(void * context,CFsmEvent * event)793 bool CNetClient::OnClientPerformance(void *context, CFsmEvent* event)
794 {
795 	// Performance statistics for one or multiple clients
796 
797 	ENSURE(event->GetType() == (uint)NMT_CLIENT_PERFORMANCE);
798 
799 	CNetClient* client = (CNetClient*)context;
800 	JSContext* cx = client->GetScriptInterface().GetContext();
801 	JSAutoRequest rq(cx);
802 
803 	CClientPerformanceMessage* message = (CClientPerformanceMessage*)event->GetParamRef();
804 
805 	// Display warnings for other clients with bad ping
806 	for (size_t i = 0; i < message->m_Clients.size(); ++i)
807 	{
808 		if (message->m_Clients[i].m_MeanRTT < DEFAULT_TURN_LENGTH_MP || message->m_Clients[i].m_GUID == client->m_GUID)
809 			continue;
810 
811 		JS::RootedValue msg(cx);
812 		client->GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'client-latency' })", &msg);
813 		client->GetScriptInterface().SetProperty(msg, "guid", message->m_Clients[i].m_GUID);
814 		client->GetScriptInterface().SetProperty(msg, "meanRTT", message->m_Clients[i].m_MeanRTT);
815 		client->PushGuiMessage(msg);
816 	}
817 
818 	return true;
819 }
820 
OnClientsLoading(void * context,CFsmEvent * event)821 bool CNetClient::OnClientsLoading(void *context, CFsmEvent *event)
822 {
823 	ENSURE(event->GetType() == (uint)NMT_CLIENTS_LOADING);
824 
825 	CClientsLoadingMessage* message = (CClientsLoadingMessage*)event->GetParamRef();
826 
827 	CNetClient* client = (CNetClient*)context;
828 	JSContext* cx = client->GetScriptInterface().GetContext();
829 	JSAutoRequest rq(cx);
830 
831 	bool finished = true;
832 	std::vector<CStr> guids;
833 	guids.reserve(message->m_Clients.size());
834 	for (const CClientsLoadingMessage::S_m_Clients& mClient : message->m_Clients)
835 	{
836 		if (client->m_GUID == mClient.m_GUID)
837 			finished = false;
838 
839 		guids.push_back(mClient.m_GUID);
840 	}
841 
842 	// Disable the timeout here after processing the enet message, so as to ensure that the connection isn't currently
843 	// timing out (as it is when just leaving the loading screen in LoadFinished).
844 	if (finished)
845 		client->m_Session->SetLongTimeout(false);
846 
847 	JS::RootedValue msg(cx);
848 	client->GetScriptInterface().Eval("({ 'type':'clients-loading' })", &msg);
849 	client->GetScriptInterface().SetProperty(msg, "guids", guids);
850 	client->PushGuiMessage(msg);
851 	return true;
852 }
853 
OnClientPaused(void * context,CFsmEvent * event)854 bool CNetClient::OnClientPaused(void *context, CFsmEvent *event)
855 {
856 	ENSURE(event->GetType() == (uint)NMT_CLIENT_PAUSED);
857 
858 	CNetClient* client = (CNetClient*)context;
859 	JSContext* cx = client->GetScriptInterface().GetContext();
860 	JSAutoRequest rq(cx);
861 
862 	CClientPausedMessage* message = (CClientPausedMessage*)event->GetParamRef();
863 
864 	JS::RootedValue msg(cx);
865 	client->GetScriptInterface().Eval("({ 'type':'paused' })", &msg);
866 	client->GetScriptInterface().SetProperty(msg, "pause", message->m_Pause != 0);
867 	client->GetScriptInterface().SetProperty(msg, "guid", message->m_GUID);
868 	client->PushGuiMessage(msg);
869 
870 	return true;
871 }
872 
OnLoadedGame(void * context,CFsmEvent * event)873 bool CNetClient::OnLoadedGame(void* context, CFsmEvent* event)
874 {
875 	ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);
876 
877 	CNetClient* client = (CNetClient*)context;
878 	JSContext* cx = client->GetScriptInterface().GetContext();
879 	JSAutoRequest rq(cx);
880 
881 	// All players have loaded the game - start running the turn manager
882 	// so that the game begins
883 	client->m_Game->SetTurnManager(client->m_ClientTurnManager);
884 
885 	JS::RootedValue msg(cx);
886 	client->GetScriptInterface().Eval("({'type':'netstatus','status':'active'})", &msg);
887 	client->PushGuiMessage(msg);
888 
889 	// If we have rejoined an in progress game, send the rejoined message to the server.
890 	if (client->m_Rejoin)
891 		client->SendRejoinedMessage();
892 
893 	// The last client to leave the loading screen didn't receive the CClientsLoadingMessage, so disable here.
894 	client->m_Session->SetLongTimeout(false);
895 
896 	return true;
897 }
898 
OnInGame(void * context,CFsmEvent * event)899 bool CNetClient::OnInGame(void *context, CFsmEvent* event)
900 {
901 	// TODO: should split each of these cases into a separate method
902 
903 	CNetClient* client = (CNetClient*)context;
904 
905 	CNetMessage* message = (CNetMessage*)event->GetParamRef();
906 	if (message)
907 	{
908 		if (message->GetType() == NMT_SIMULATION_COMMAND)
909 		{
910 			CSimulationMessage* simMessage = static_cast<CSimulationMessage*> (message);
911 			client->m_ClientTurnManager->OnSimulationMessage(simMessage);
912 		}
913 		else if (message->GetType() == NMT_SYNC_ERROR)
914 		{
915 			CSyncErrorMessage* syncMessage = static_cast<CSyncErrorMessage*> (message);
916 			client->m_ClientTurnManager->OnSyncError(syncMessage->m_Turn, syncMessage->m_HashExpected, syncMessage->m_PlayerNames);
917 		}
918 		else if (message->GetType() == NMT_END_COMMAND_BATCH)
919 		{
920 			CEndCommandBatchMessage* endMessage = static_cast<CEndCommandBatchMessage*> (message);
921 			client->m_ClientTurnManager->FinishedAllCommands(endMessage->m_Turn, endMessage->m_TurnLength);
922 		}
923 	}
924 
925 	return true;
926 }
927