1 /*****************************************************************************
2  * PokerTH - The open source texas holdem engine                             *
3  * Copyright (C) 2006-2012 Felix Hammer, Florian Thauer, Lothar May          *
4  *                                                                           *
5  * This program is free software: you can redistribute it and/or modify      *
6  * it under the terms of the GNU Affero General Public License as            *
7  * published by the Free Software Foundation, either version 3 of the        *
8  * License, or (at your option) any later version.                           *
9  *                                                                           *
10  * This program is distributed in the hope that it will be useful,           *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of            *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
13  * GNU Affero General Public License for more details.                       *
14  *                                                                           *
15  * You should have received a copy of the GNU Affero General Public License  *
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.     *
17  *                                                                           *
18  *                                                                           *
19  * Additional permission under GNU AGPL version 3 section 7                  *
20  *                                                                           *
21  * If you modify this program, or any covered work, by linking or            *
22  * combining it with the OpenSSL project's OpenSSL library (or a             *
23  * modified version of that library), containing parts covered by the        *
24  * terms of the OpenSSL or SSLeay licenses, the authors of PokerTH           *
25  * (Felix Hammer, Florian Thauer, Lothar May) grant you additional           *
26  * permission to convey the resulting work.                                  *
27  * Corresponding Source for a non-source form of such a combination          *
28  * shall include the source code for the parts of OpenSSL used as well       *
29  * as that of the covered work.                                              *
30  *****************************************************************************/
31 
32 #include <boost/asio.hpp>
33 #include <net/socket_helper.h>
34 #include <net/clientthread.h>
35 #include <net/clientstate.h>
36 #include <net/clientcontext.h>
37 #include <net/senderhelper.h>
38 #include <net/downloaderthread.h>
39 #include <net/clientexception.h>
40 #include <net/socket_msg.h>
41 #include <net/net_helper.h>
42 #include <net/asioreceivebuffer.h>
43 #include <core/avatarmanager.h>
44 #include <core/loghelper.h>
45 #include <clientenginefactory.h>
46 #include <game.h>
47 #include <log.h>
48 #include <qttoolsinterface.h>
49 
50 #include <boost/lambda/lambda.hpp>
51 #include <boost/foreach.hpp>
52 #include <boost/filesystem.hpp>
53 #include <sstream>
54 #include <fstream>
55 #include <memory>
56 #include <cassert>
57 #include <gsasl.h>
58 
59 #define TEMP_AVATAR_FILENAME	"avatar.tmp"
60 #define TEMP_GUID_FILENAME		"guid.tmp"
61 #define CLIENT_GUID_SIZE		16
62 #define CLIENT_AVATAR_LOOP_MSEC	100
63 #define CLIENT_SEND_LOOP_MSEC	50
64 
65 using namespace std;
66 using namespace boost::filesystem;
67 using boost::asio::ip::tcp;
68 
69 #ifdef BOOST_ASIO_HAS_STD_CHRONO
70 using namespace std::chrono;
71 #else
72 using namespace boost::chrono;
73 #endif
74 
ClientThread(GuiInterface & gui,AvatarManager & avatarManager,Log * myLog)75 ClientThread::ClientThread(GuiInterface &gui, AvatarManager &avatarManager, Log *myLog)
76 	: m_ioService(new boost::asio::io_service), m_clientLog(myLog), m_curState(NULL), m_gui(gui),
77 	  m_avatarManager(avatarManager), m_isServerSelected(false),
78 	  m_curGameId(0), m_curGameNum(1), m_guiPlayerId(0), m_sessionEstablished(false),
79 	  m_stateTimer(*m_ioService), m_avatarTimer(*m_ioService)
80 {
81 	m_context.reset(new ClientContext);
82 	myQtToolsInterface.reset(CreateQtToolsWrapper());
83 	m_senderHelper.reset(new SenderHelper(m_ioService));
84 }
85 
~ClientThread()86 ClientThread::~ClientThread()
87 {
88 }
89 
90 void
Init(const string & serverAddress,const string & serverListUrl,const string & serverPassword,bool useServerList,unsigned serverPort,bool ipv6,bool sctp,const string & avatarServerAddress,const string & playerName,const string & avatarFile,const string & cacheDir)91 ClientThread::Init(
92 	const string &serverAddress, const string &serverListUrl,
93 	const string &serverPassword,
94 	bool useServerList, unsigned serverPort, bool ipv6, bool sctp,
95 	const string &avatarServerAddress, const string &playerName,
96 	const string &avatarFile, const string &cacheDir)
97 {
98 	if (IsRunning()) {
99 		assert(false);
100 		return;
101 	}
102 
103 	ClientContext &context = GetContext();
104 
105 	context.SetSctp(sctp);
106 	context.SetAddrFamily(ipv6 ? AF_INET6 : AF_INET);
107 	context.SetServerAddr(serverAddress);
108 	context.SetServerListUrl(serverListUrl);
109 	context.SetServerPassword(serverPassword);
110 	context.SetUseServerList(useServerList);
111 	context.SetServerPort(serverPort);
112 	context.SetAvatarServerAddr(avatarServerAddress);
113 	context.SetPlayerName(playerName);
114 	context.SetAvatarFile(avatarFile);
115 	context.SetCacheDir(cacheDir);
116 
117 	ReadSessionGuidFromFile();
118 }
119 
120 void
SignalTermination()121 ClientThread::SignalTermination()
122 {
123 	Thread::SignalTermination();
124 	m_ioService->stop();
125 }
126 
127 void
SendKickPlayer(unsigned playerId)128 ClientThread::SendKickPlayer(unsigned playerId)
129 {
130 	boost::shared_ptr<NetPacket> packet(new NetPacket);
131 	packet->GetMsg()->set_messagetype(PokerTHMessage::Type_KickPlayerRequestMessage);
132 	KickPlayerRequestMessage *netKick = packet->GetMsg()->mutable_kickplayerrequestmessage();
133 	netKick->set_gameid(GetGameId());
134 	netKick->set_playerid(playerId);
135 	m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
136 }
137 
138 void
SendLeaveCurrentGame()139 ClientThread::SendLeaveCurrentGame()
140 {
141 	boost::shared_ptr<NetPacket> packet(new NetPacket);
142 	packet->GetMsg()->set_messagetype(PokerTHMessage::Type_LeaveGameRequestMessage);
143 	LeaveGameRequestMessage *netLeave = packet->GetMsg()->mutable_leavegamerequestmessage();
144 	netLeave->set_gameid(GetGameId());
145 	m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
146 }
147 
148 void
SendStartEvent(bool fillUpWithCpuPlayers)149 ClientThread::SendStartEvent(bool fillUpWithCpuPlayers)
150 {
151 	// Warning: This function is called in the context of the GUI thread.
152 	// Create a network packet for the server start event.
153 	boost::shared_ptr<NetPacket> packet(new NetPacket);
154 	packet->GetMsg()->set_messagetype(PokerTHMessage::Type_StartEventMessage);
155 	StartEventMessage *netStartEvent = packet->GetMsg()->mutable_starteventmessage();
156 	netStartEvent->set_starteventtype(StartEventMessage::startEvent);
157 	netStartEvent->set_gameid(GetGameId());
158 	netStartEvent->set_fillwithcomputerplayers(fillUpWithCpuPlayers);
159 	m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
160 }
161 
162 void
SendPlayerAction()163 ClientThread::SendPlayerAction()
164 {
165 	// Warning: This function is called in the context of the GUI thread.
166 	// Create a network packet containing the current player action.
167 	{
168 		boost::mutex::scoped_lock lock(m_pingDataMutex);
169 		m_pingData.StartPing();
170 	}
171 	boost::shared_ptr<NetPacket> packet(new NetPacket);
172 	packet->GetMsg()->set_messagetype(PokerTHMessage::Type_MyActionRequestMessage);
173 	MyActionRequestMessage *netMyAction = packet->GetMsg()->mutable_myactionrequestmessage();
174 	netMyAction->set_gameid(GetGameId());
175 	boost::shared_ptr<PlayerInterface> myPlayer = GetGame()->getSeatsList()->front();
176 	netMyAction->set_handnum(GetGame()->getCurrentHandID());
177 	netMyAction->set_gamestate(static_cast<NetGameState>(GetGame()->getCurrentHand()->getCurrentRound()));
178 	netMyAction->set_myaction(static_cast<NetPlayerAction>(myPlayer->getMyAction()));
179 	// Only send last bet if not fold/checked.
180 	if (myPlayer->getMyAction() != PLAYER_ACTION_FOLD && myPlayer->getMyAction() != PLAYER_ACTION_CHECK)
181 		netMyAction->set_myrelativebet(myPlayer->getMyLastRelativeSet());
182 	else
183 		netMyAction->set_myrelativebet(0);
184 	// Just dump the packet.
185 	m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
186 }
187 
188 void
SendGameChatMessage(const std::string & msg)189 ClientThread::SendGameChatMessage(const std::string &msg)
190 {
191 	// Warning: This function is called in the context of the GUI thread.
192 	// Create a network packet containing the chat message.
193 	boost::shared_ptr<NetPacket> packet(new NetPacket);
194 	packet->GetMsg()->set_messagetype(PokerTHMessage::Type_ChatRequestMessage);
195 	ChatRequestMessage *netChat = packet->GetMsg()->mutable_chatrequestmessage();
196 	netChat->set_targetgameid(GetGameId());
197 	netChat->set_chattext(msg);
198 
199 	// Just dump the packet.
200 	m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
201 }
202 
203 void
SendLobbyChatMessage(const std::string & msg)204 ClientThread::SendLobbyChatMessage(const std::string &msg)
205 {
206 	// Warning: This function is called in the context of the GUI thread.
207 	// Create a network packet containing the chat message.
208 	boost::shared_ptr<NetPacket> packet(new NetPacket);
209 	packet->GetMsg()->set_messagetype(PokerTHMessage::Type_ChatRequestMessage);
210 	ChatRequestMessage *netChat = packet->GetMsg()->mutable_chatrequestmessage();
211 	netChat->set_chattext(msg);
212 
213 	// Just dump the packet.
214 	m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
215 }
216 
217 void
SendPrivateChatMessage(unsigned targetPlayerId,const std::string & msg)218 ClientThread::SendPrivateChatMessage(unsigned targetPlayerId, const std::string &msg)
219 {
220 	// Warning: This function is called in the context of the GUI thread.
221 	// Create a network packet containing the chat message.
222 	boost::shared_ptr<NetPacket> packet(new NetPacket);
223 	packet->GetMsg()->set_messagetype(PokerTHMessage::Type_ChatRequestMessage);
224 	ChatRequestMessage *netChat = packet->GetMsg()->mutable_chatrequestmessage();
225 	netChat->set_targetplayerid(targetPlayerId);
226 	netChat->set_chattext(msg);
227 
228 	// Just dump the packet.
229 	m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
230 }
231 
232 void
SendJoinFirstGame(const std::string & password,bool autoLeave)233 ClientThread::SendJoinFirstGame(const std::string &password, bool autoLeave)
234 {
235 	// Warning: This function is called in the context of the GUI thread.
236 	// Create a network packet to request joining a game.
237 	boost::shared_ptr<NetPacket> packet(new NetPacket);
238 	packet->GetMsg()->set_messagetype(PokerTHMessage::Type_JoinExistingGameMessage);
239 	JoinExistingGameMessage *netJoinGame = packet->GetMsg()->mutable_joinexistinggamemessage();
240 	netJoinGame->set_gameid(1);
241 	netJoinGame->set_autoleave(autoLeave);
242 
243 	if (!password.empty()) {
244 		netJoinGame->set_password(password);
245 	}
246 	m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
247 }
248 
249 void
SendJoinGame(unsigned gameId,const std::string & password,bool autoLeave)250 ClientThread::SendJoinGame(unsigned gameId, const std::string &password, bool autoLeave)
251 {
252 	// Warning: This function is called in the context of the GUI thread.
253 	// Create a network packet to request joining a game.
254 	boost::shared_ptr<NetPacket> packet(new NetPacket);
255 	packet->GetMsg()->set_messagetype(PokerTHMessage::Type_JoinExistingGameMessage);
256 	JoinExistingGameMessage *netJoinGame = packet->GetMsg()->mutable_joinexistinggamemessage();
257 	netJoinGame->set_gameid(gameId);
258 	netJoinGame->set_autoleave(autoLeave);
259 
260 	if (!password.empty()) {
261 		netJoinGame->set_password(password);
262 	}
263 	m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
264 }
265 
266 void
SendRejoinGame(unsigned gameId,bool autoLeave)267 ClientThread::SendRejoinGame(unsigned gameId, bool autoLeave)
268 {
269 	// Warning: This function is called in the context of the GUI thread.
270 	// Create a network packet to request rejoining a running game.
271 	boost::shared_ptr<NetPacket> packet(new NetPacket);
272 	packet->GetMsg()->set_messagetype(PokerTHMessage::Type_RejoinExistingGameMessage);
273 	RejoinExistingGameMessage *netJoinGame = packet->GetMsg()->mutable_rejoinexistinggamemessage();
274 	netJoinGame->set_gameid(gameId);
275 	netJoinGame->set_autoleave(autoLeave);
276 
277 	m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
278 }
279 
280 void
SendCreateGame(const GameData & gameData,const std::string & name,const std::string & password,bool autoLeave)281 ClientThread::SendCreateGame(const GameData &gameData, const std::string &name, const std::string &password, bool autoLeave)
282 {
283 	// Warning: This function is called in the context of the GUI thread.
284 	// Create a network packet to request creating a new game.
285 	boost::shared_ptr<NetPacket> packet(new NetPacket);
286 	packet->GetMsg()->set_messagetype(PokerTHMessage::Type_JoinNewGameMessage);
287 	JoinNewGameMessage *netJoinGame = packet->GetMsg()->mutable_joinnewgamemessage();
288 	netJoinGame->set_autoleave(autoLeave);
289 	NetGameInfo *gameInfo = netJoinGame->mutable_gameinfo();
290 	NetPacket::SetGameData(gameData, *gameInfo);
291 	gameInfo->set_gamename(name);
292 
293 	if (!password.empty()) {
294 		netJoinGame->set_password(password);
295 	}
296 	m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
297 }
298 
299 void
SendResetTimeout()300 ClientThread::SendResetTimeout()
301 {
302 	boost::shared_ptr<NetPacket> packet(new NetPacket);
303 	packet->GetMsg()->set_messagetype(PokerTHMessage::Type_ResetTimeoutMessage);
304 	packet->GetMsg()->mutable_resettimeoutmessage();
305 	m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
306 }
307 
308 void
SendAskKickPlayer(unsigned playerId)309 ClientThread::SendAskKickPlayer(unsigned playerId)
310 {
311 	boost::shared_ptr<NetPacket> packet(new NetPacket);
312 	packet->GetMsg()->set_messagetype(PokerTHMessage::Type_AskKickPlayerMessage);
313 	AskKickPlayerMessage *netAsk = packet->GetMsg()->mutable_askkickplayermessage();
314 	netAsk->set_gameid(GetGameId());
315 	netAsk->set_playerid(playerId);
316 	m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
317 }
318 
319 void
SendVoteKick(bool doKick)320 ClientThread::SendVoteKick(bool doKick)
321 {
322 	boost::shared_ptr<NetPacket> packet(new NetPacket);
323 	packet->GetMsg()->set_messagetype(PokerTHMessage::Type_VoteKickRequestMessage);
324 	VoteKickRequestMessage *netVote = packet->GetMsg()->mutable_votekickrequestmessage();
325 	netVote->set_gameid(GetGameId());
326 	{
327 		boost::mutex::scoped_lock lock(m_curPetitionIdMutex);
328 		netVote->set_petitionid(m_curPetitionId);
329 	}
330 	netVote->set_votekick(doKick);
331 	m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
332 }
333 
334 void
SendShowMyCards()335 ClientThread::SendShowMyCards()
336 {
337 	boost::shared_ptr<NetPacket> packet(new NetPacket);
338 	packet->GetMsg()->set_messagetype(PokerTHMessage::Type_ShowMyCardsRequestMessage);
339 	packet->GetMsg()->mutable_showmycardsrequestmessage();
340 	m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
341 }
342 
343 void
SendInvitePlayerToCurrentGame(unsigned playerId)344 ClientThread::SendInvitePlayerToCurrentGame(unsigned playerId)
345 {
346 	boost::shared_ptr<NetPacket> packet(new NetPacket);
347 	packet->GetMsg()->set_messagetype(PokerTHMessage::Type_InvitePlayerToGameMessage);
348 	InvitePlayerToGameMessage *netInvite = packet->GetMsg()->mutable_inviteplayertogamemessage();
349 	netInvite->set_gameid(GetGameId());
350 	netInvite->set_playerid(playerId);
351 	m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
352 }
353 
354 void
SendRejectGameInvitation(unsigned gameId,DenyGameInvitationReason reason)355 ClientThread::SendRejectGameInvitation(unsigned gameId, DenyGameInvitationReason reason)
356 {
357 	boost::shared_ptr<NetPacket> packet(new NetPacket);
358 	packet->GetMsg()->set_messagetype(PokerTHMessage::Type_RejectGameInvitationMessage);
359 	RejectGameInvitationMessage *netReject = packet->GetMsg()->mutable_rejectgameinvitationmessage();
360 	netReject->set_gameid(gameId);
361 	netReject->set_myrejectreason(static_cast<RejectGameInvitationMessage::RejectGameInvReason>(reason));
362 	m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
363 }
364 
365 void
SendReportAvatar(unsigned reportedPlayerId,const std::string & avatarHash)366 ClientThread::SendReportAvatar(unsigned reportedPlayerId, const std::string &avatarHash)
367 {
368 	boost::shared_ptr<NetPacket> packet(new NetPacket);
369 	packet->GetMsg()->set_messagetype(PokerTHMessage::Type_ReportAvatarMessage);
370 	ReportAvatarMessage *netReport = packet->GetMsg()->mutable_reportavatarmessage();
371 	netReport->set_reportedplayerid(reportedPlayerId);
372 	MD5Buf tmpMD5;
373 	if (tmpMD5.FromString(avatarHash) && !tmpMD5.IsZero()) {
374 		netReport->set_reportedavatarhash(tmpMD5.GetData(), MD5_DATA_SIZE);
375 
376 		m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
377 	}
378 }
379 
380 void
SendReportGameName(unsigned reportedGameId)381 ClientThread::SendReportGameName(unsigned reportedGameId)
382 {
383 	boost::shared_ptr<NetPacket> packet(new NetPacket);
384 	packet->GetMsg()->set_messagetype(PokerTHMessage::Type_ReportGameMessage);
385 	ReportGameMessage *netReport = packet->GetMsg()->mutable_reportgamemessage();
386 	netReport->set_reportedgameid(reportedGameId);
387 	m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
388 }
389 
390 void
SendAdminRemoveGame(unsigned removeGameId)391 ClientThread::SendAdminRemoveGame(unsigned removeGameId)
392 {
393 	boost::shared_ptr<NetPacket> packet(new NetPacket);
394 	packet->GetMsg()->set_messagetype(PokerTHMessage::Type_AdminRemoveGameMessage);
395 	AdminRemoveGameMessage *netRemove = packet->GetMsg()->mutable_adminremovegamemessage();
396 	netRemove->set_removegameid(removeGameId);
397 	m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
398 }
399 
400 void
SendAdminBanPlayer(unsigned playerId)401 ClientThread::SendAdminBanPlayer(unsigned playerId)
402 {
403 	boost::shared_ptr<NetPacket> packet(new NetPacket);
404 	packet->GetMsg()->set_messagetype(PokerTHMessage::Type_AdminBanPlayerMessage);
405 	AdminBanPlayerMessage *netBan = packet->GetMsg()->mutable_adminbanplayermessage();
406 	netBan->set_banplayerid(playerId);
407 	m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
408 }
409 
410 void
StartAsyncRead()411 ClientThread::StartAsyncRead()
412 {
413 	GetContext().GetSessionData()->GetReceiveBuffer().StartAsyncRead(GetContext().GetSessionData());
414 }
415 
416 void
CloseSession(boost::shared_ptr<SessionData>)417 ClientThread::CloseSession(boost::shared_ptr<SessionData> /*session*/)
418 {
419 	throw NetException(__FILE__, __LINE__, ERR_SOCK_CONN_RESET, 0);
420 }
421 
422 void
HandlePacket(boost::shared_ptr<SessionData>,boost::shared_ptr<NetPacket> packet)423 ClientThread::HandlePacket(boost::shared_ptr<SessionData> /*session*/, boost::shared_ptr<NetPacket> packet)
424 {
425 	GetState().HandlePacket(shared_from_this(), packet);
426 }
427 
428 void
SelectServer(unsigned serverId)429 ClientThread::SelectServer(unsigned serverId)
430 {
431 	boost::mutex::scoped_lock lock(m_selectServerMutex);
432 	m_isServerSelected = true;
433 	m_selectedServerId = serverId;
434 }
435 
436 void
SetLogin(const std::string & userName,const std::string & password,bool isGuest)437 ClientThread::SetLogin(const std::string &userName, const std::string &password, bool isGuest)
438 {
439 	boost::mutex::scoped_lock lock(m_loginDataMutex);
440 	m_loginData.userName = userName;
441 	m_loginData.password = password;
442 	m_loginData.isGuest = isGuest;
443 }
444 
445 ServerInfo
GetServerInfo(unsigned serverId) const446 ClientThread::GetServerInfo(unsigned serverId) const
447 {
448 	ServerInfo tmpInfo;
449 	boost::mutex::scoped_lock lock(m_serverInfoMapMutex);
450 	ServerInfoMap::const_iterator pos = m_serverInfoMap.find(serverId);
451 	if (pos != m_serverInfoMap.end()) {
452 		tmpInfo = pos->second;
453 	}
454 	return tmpInfo;
455 }
456 
457 GameInfo
GetGameInfo(unsigned gameId) const458 ClientThread::GetGameInfo(unsigned gameId) const
459 {
460 	GameInfo tmpInfo;
461 	boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
462 	GameInfoMap::const_iterator pos = m_gameInfoMap.find(gameId);
463 	if (pos != m_gameInfoMap.end()) {
464 		tmpInfo = pos->second;
465 	}
466 	return tmpInfo;
467 }
468 
469 PlayerInfo
GetPlayerInfo(unsigned playerId) const470 ClientThread::GetPlayerInfo(unsigned playerId) const
471 {
472 	PlayerInfo info;
473 	if (!GetCachedPlayerInfo(playerId, info)) {
474 		ostringstream name;
475 		name << "#" << playerId;
476 
477 		info.playerName = name.str();
478 	}
479 	return info;
480 }
481 
482 bool
GetPlayerIdFromName(const string & playerName,unsigned & playerId) const483 ClientThread::GetPlayerIdFromName(const string &playerName, unsigned &playerId) const
484 {
485 	bool retVal = false;
486 
487 	boost::mutex::scoped_lock lock(m_playerInfoMapMutex);
488 	PlayerInfoMap::const_reverse_iterator i = m_playerInfoMap.rbegin();
489 	PlayerInfoMap::const_reverse_iterator end = m_playerInfoMap.rend();
490 
491 	while (i != end) {
492 		if (i->second.playerName == playerName) {
493 			playerId = i->first;
494 			retVal = true;
495 			break;
496 		}
497 		++i;
498 	}
499 	return retVal;
500 }
501 
502 unsigned
GetGameIdOfPlayer(unsigned playerId) const503 ClientThread::GetGameIdOfPlayer(unsigned playerId) const
504 {
505 	unsigned gameId = 0; // Default: no game (invalid id).
506 
507 	// Iterate through all games to find the player.
508 	boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
509 	GameInfoMap::const_iterator i = m_gameInfoMap.begin();
510 	GameInfoMap::const_iterator i_end = m_gameInfoMap.end();
511 	while (i != i_end) {
512 		PlayerIdList::const_iterator j = (*i).second.players.begin();
513 		PlayerIdList::const_iterator j_end = (*i).second.players.end();
514 		while (j != j_end) {
515 			if (playerId == *j) {
516 				gameId = (*i).first;
517 				break;
518 			}
519 			++j;
520 		}
521 		if (gameId)
522 			break;
523 		++i;
524 	}
525 	return gameId;
526 }
527 
528 ClientCallback &
GetCallback()529 ClientThread::GetCallback()
530 {
531 	return m_gui;
532 }
533 
534 GuiInterface &
GetGui()535 ClientThread::GetGui()
536 {
537 	return m_gui;
538 }
539 
540 boost::shared_ptr<Log>
GetClientLog()541 ClientThread::GetClientLog()
542 {
543 	return m_clientLog;
544 }
545 
546 AvatarManager &
GetAvatarManager()547 ClientThread::GetAvatarManager()
548 {
549 	return m_avatarManager;
550 }
551 
552 void
Main()553 ClientThread::Main()
554 {
555 	// Main loop.
556 	boost::asio::io_service::work ioWork(*m_ioService);
557 	try {
558 		InitAuthContext();
559 		// Start sub-threads.
560 		m_avatarDownloader.reset(new DownloaderThread);
561 		m_avatarDownloader->Run();
562 		SetState(CLIENT_INITIAL_STATE::Instance());
563 		RegisterTimers();
564 
565 		boost::asio::io_service::work ioWork(*m_ioService);
566 		m_ioService->run(); // Will only be aborted asynchronously.
567 
568 	} catch (const PokerTHException &e) {
569 		// Delete the cached server list, as it may be outdated.
570 		path tmpServerListPath(GetCacheServerListFileName());
571 		if (exists(tmpServerListPath)) {
572 			remove(tmpServerListPath);
573 		}
574 		GetCallback().SignalNetClientError(e.GetErrorId(), e.GetOsErrorCode());
575 	}
576 	// Close the socket.
577 	boost::system::error_code ec;
578 	GetContext().GetSessionData()->GetAsioSocket()->close(ec);
579 	// Set a state which does not do anything.
580 	SetState(CLIENT_FINAL_STATE::Instance());
581 	// Cancel timers.
582 	GetStateTimer().cancel();
583 	CancelTimers();
584 	// Terminate sub-threads.
585 	m_avatarDownloader->SignalTermination();
586 	m_avatarDownloader->Join(DOWNLOADER_THREAD_TERMINATE_TIMEOUT);
587 
588 	ClearAuthContext();
589 }
590 
591 void
RegisterTimers()592 ClientThread::RegisterTimers()
593 {
594 	m_avatarTimer.expires_from_now(
595 		milliseconds(CLIENT_AVATAR_LOOP_MSEC));
596 	m_avatarTimer.async_wait(
597 		boost::bind(
598 			&ClientThread::TimerCheckAvatarDownloads, shared_from_this(), boost::asio::placeholders::error));
599 }
600 
601 void
CancelTimers()602 ClientThread::CancelTimers()
603 {
604 	m_avatarTimer.cancel();
605 }
606 
607 void
InitAuthContext()608 ClientThread::InitAuthContext()
609 {
610 	int res = gsasl_init(&m_authContext);
611 	if (res != GSASL_OK)
612 		throw ClientException(__FILE__, __LINE__, ERR_NET_GSASL_INIT_FAILED, 0);
613 
614 	if (!gsasl_server_support_p(m_authContext, "SCRAM-SHA-1")) {
615 		gsasl_done(m_authContext);
616 		throw ClientException(__FILE__, __LINE__, ERR_NET_GSASL_NO_SCRAM, 0);
617 	}
618 }
619 
620 void
ClearAuthContext()621 ClientThread::ClearAuthContext()
622 {
623 	gsasl_done(m_authContext);
624 	m_authContext = NULL;
625 }
626 
627 void
InitGame()628 ClientThread::InitGame()
629 {
630 	// Store current session guid, in case we need to rejoin the game.
631 	WriteSessionGuidToFile();
632 
633 	// EngineFactory erstellen
634 	boost::shared_ptr<EngineFactory> factory(new ClientEngineFactory); // LocalEngine erstellen
635 
636 	MapPlayerDataList();
637 	// TODO
638 	//if (GetPlayerDataList().size() != (unsigned)GetStartData().numberOfPlayers)
639 	//	throw ClientException(__FILE__, __LINE__, ERR_NET_INVALID_PLAYER_COUNT, 0);
640 	m_startData.numberOfPlayers = (int)GetPlayerDataList().size();
641 	m_game.reset(new Game(&m_gui, factory, GetPlayerDataList(), GetGameData(), GetStartData(), m_curGameNum++, m_clientLog.get()));
642 	// Initialize Minimum GUI speed.
643 	int minimumGuiSpeed = 1;
644 	if(GetGameData().delayBetweenHandsSec < 11) {
645 		minimumGuiSpeed = 12-GetGameData().delayBetweenHandsSec;
646 	}
647 	GetGui().initGui(minimumGuiSpeed);
648 	// Signal start of game to GUI.
649 	GetCallback().SignalNetClientGameStart(m_game);
650 }
651 
652 void
SendSessionPacket(boost::shared_ptr<NetPacket> packet)653 ClientThread::SendSessionPacket(boost::shared_ptr<NetPacket> packet)
654 {
655 	// Put packets in a buffer until the session is established.
656 	if (IsSessionEstablished())
657 		GetSender().Send(GetContext().GetSessionData(), packet);
658 	else
659 		m_outPacketList.push_back(packet);
660 }
661 
662 void
SendQueuedPackets()663 ClientThread::SendQueuedPackets()
664 {
665 	if (!m_outPacketList.empty()) {
666 		NetPacketList::iterator i = m_outPacketList.begin();
667 		NetPacketList::iterator end = m_outPacketList.end();
668 
669 		while (i != end) {
670 			GetSender().Send(GetContext().GetSessionData(), *i);
671 			++i;
672 		}
673 		m_outPacketList.clear();
674 	}
675 }
676 
677 bool
GetCachedPlayerInfo(unsigned id,PlayerInfo & info) const678 ClientThread::GetCachedPlayerInfo(unsigned id, PlayerInfo &info) const
679 {
680 	bool retVal = false;
681 
682 	boost::mutex::scoped_lock lock(m_playerInfoMapMutex);
683 	PlayerInfoMap::const_iterator pos = m_playerInfoMap.find(id);
684 	if (pos != m_playerInfoMap.end()) {
685 		info = pos->second;
686 		retVal = true;
687 	}
688 	return retVal;
689 }
690 
691 void
RequestPlayerInfo(unsigned id,bool requestAvatar)692 ClientThread::RequestPlayerInfo(unsigned id, bool requestAvatar)
693 {
694 	list<unsigned> idList;
695 	idList.push_back(id);
696 	RequestPlayerInfo(idList, requestAvatar);
697 }
698 
699 void
RequestPlayerInfo(const list<unsigned> & idList,bool requestAvatar)700 ClientThread::RequestPlayerInfo(const list<unsigned> &idList, bool requestAvatar)
701 {
702 	boost::shared_ptr<NetPacket> packet(new NetPacket);
703 	packet->GetMsg()->set_messagetype(PokerTHMessage::Type_PlayerInfoRequestMessage);
704 	PlayerInfoRequestMessage *netPlayerInfo = packet->GetMsg()->mutable_playerinforequestmessage();
705 	BOOST_FOREACH(unsigned playerId, idList) {
706 		if (find(m_playerInfoRequestList.begin(), m_playerInfoRequestList.end(), playerId) == m_playerInfoRequestList.end()) {
707 			netPlayerInfo->add_playerid(playerId);
708 			m_playerInfoRequestList.push_back(playerId);
709 		}
710 		// Remember that we have to request an avatar.
711 		if (requestAvatar) {
712 			m_avatarShouldRequestList.push_back(playerId);
713 		}
714 	}
715 	if (netPlayerInfo->playerid_size() > 0) {
716 		GetSender().Send(GetContext().GetSessionData(), packet);
717 	}
718 }
719 
720 void
SetPlayerInfo(unsigned id,const PlayerInfo & info)721 ClientThread::SetPlayerInfo(unsigned id, const PlayerInfo &info)
722 {
723 	{
724 		boost::mutex::scoped_lock lock(m_playerInfoMapMutex);
725 		// Remove previous player entry with different id
726 		// for the same player name if it exists.
727 		// This can only be one entry, since every time a duplicate
728 		// name is added one is removed.
729 		// Only erase non computer player entries.
730 		if (info.playerName.substr(0, sizeof(SERVER_COMPUTER_PLAYER_NAME) - 1) != SERVER_COMPUTER_PLAYER_NAME) {
731 			PlayerInfoMap::iterator i = m_playerInfoMap.begin();
732 			PlayerInfoMap::iterator end = m_playerInfoMap.end();
733 			while (i != end) {
734 				if (i->first != id && i->second.playerName == info.playerName) {
735 					m_playerInfoMap.erase(i);
736 					break;
737 				}
738 				++i;
739 			}
740 		}
741 		m_playerInfoMap[id] = info;
742 	}
743 
744 	// Update player data for current game.
745 	boost::shared_ptr<PlayerData> playerData(GetPlayerDataByUniqueId(id));
746 	if (playerData) {
747 		playerData->SetName(info.playerName);
748 		playerData->SetType(info.ptype);
749 		if (info.hasAvatar) {
750 			string avatarFile;
751 			if (GetAvatarManager().GetAvatarFileName(info.avatar, avatarFile)) {
752 				playerData->SetAvatarFile(GetQtToolsInterface().stringToUtf8(avatarFile));
753 			}
754 		}
755 	}
756 	if (GetGame()) {
757 		boost::shared_ptr<PlayerInterface> clientPlayer(GetGame()->getPlayerByUniqueId(id));
758 		if (clientPlayer)
759 			clientPlayer->setMyName(info.playerName);
760 		// Skip avatar here, the game is already running.
761 	}
762 
763 	if (find(m_avatarShouldRequestList.begin(), m_avatarShouldRequestList.end(), id) != m_avatarShouldRequestList.end()) {
764 		m_avatarShouldRequestList.remove(id);
765 		// Retrieve avatar if needed.
766 		RetrieveAvatarIfNeeded(id, info);
767 	}
768 
769 	// Remove it from the request list.
770 	m_playerInfoRequestList.remove(id);
771 
772 	// Notify GUI
773 	GetCallback().SignalNetClientPlayerChanged(id, info.playerName);
774 
775 }
776 
777 void
SetUnknownPlayer(unsigned id)778 ClientThread::SetUnknownPlayer(unsigned id)
779 {
780 	// Just remove it from the request list.
781 	m_playerInfoRequestList.remove(id);
782 	m_avatarShouldRequestList.remove(id);
783 	LOG_ERROR("Server reported unknown player id: " << id);
784 }
785 
786 void
SetNewGameAdmin(unsigned id)787 ClientThread::SetNewGameAdmin(unsigned id)
788 {
789 	// Update player data for current game.
790 	boost::shared_ptr<PlayerData> playerData = GetPlayerDataByUniqueId(id);
791 	if (playerData.get()) {
792 		playerData->SetGameAdmin(true);
793 		GetCallback().SignalNetClientNewGameAdmin(id, playerData->GetName());
794 		if(m_game) {
795 			m_clientLog->logPlayerAction(playerData->GetName(),LOG_ACTION_ADMIN);
796 		}
797 	}
798 }
799 
800 void
RetrieveAvatarIfNeeded(unsigned id,const PlayerInfo & info)801 ClientThread::RetrieveAvatarIfNeeded(unsigned id, const PlayerInfo &info)
802 {
803 	if (find(m_avatarHasRequestedList.begin(), m_avatarHasRequestedList.end(), id) == m_avatarHasRequestedList.end()) {
804 		if (info.hasAvatar && !info.avatar.IsZero() && !GetAvatarManager().HasAvatar(info.avatar)) {
805 			m_avatarHasRequestedList.push_back(id); // Never remove from this list. Only request once.
806 
807 			// Download from avatar server if applicable.
808 			string avatarServerAddress(GetContext().GetAvatarServerAddr());
809 			if (!avatarServerAddress.empty() && m_avatarDownloader) {
810 				string serverFileName(info.avatar.ToString() + AvatarManager::GetAvatarFileExtension(info.avatarType));
811 				m_avatarDownloader->QueueDownload(
812 					id, avatarServerAddress + serverFileName, GetContext().GetCacheDir() + TEMP_AVATAR_FILENAME);
813 			} else {
814 				boost::shared_ptr<NetPacket> packet(new NetPacket);
815 				packet->GetMsg()->set_messagetype(PokerTHMessage::Type_AvatarRequestMessage);
816 				AvatarRequestMessage *netAvatar = packet->GetMsg()->mutable_avatarrequestmessage();
817 				netAvatar->set_requestid(id);
818 				netAvatar->set_avatarhash(info.avatar.GetData(), MD5_DATA_SIZE);
819 				GetSender().Send(GetContext().GetSessionData(), packet);
820 			}
821 		}
822 	}
823 }
824 
825 std::string
GetPlayerName(unsigned id)826 ClientThread::GetPlayerName(unsigned id)
827 {
828 	PlayerInfo info;
829 	if (!GetCachedPlayerInfo(id, info)) {
830 		// Request player info.
831 		ostringstream name;
832 		name << "#" << id;
833 		info.playerName = name.str();
834 		RequestPlayerInfo(id);
835 	}
836 	return info.playerName;
837 }
838 
839 void
AddTempAvatarFile(unsigned playerId,unsigned avatarSize,AvatarFileType type)840 ClientThread::AddTempAvatarFile(unsigned playerId, unsigned avatarSize, AvatarFileType type)
841 {
842 	boost::shared_ptr<AvatarFile> tmpAvatar(new AvatarFile);
843 	tmpAvatar->fileData.reserve(avatarSize);
844 	tmpAvatar->fileType = type;
845 	tmpAvatar->reportedSize = avatarSize;
846 
847 	m_tempAvatarMap[playerId] = tmpAvatar;
848 }
849 
850 void
StoreInTempAvatarFile(unsigned playerId,const vector<unsigned char> & data)851 ClientThread::StoreInTempAvatarFile(unsigned playerId, const vector<unsigned char> &data)
852 {
853 	AvatarFileMap::iterator pos = m_tempAvatarMap.find(playerId);
854 	if (pos == m_tempAvatarMap.end())
855 		throw ClientException(__FILE__, __LINE__, ERR_NET_INVALID_REQUEST_ID, 0);
856 	// We trust the server (concerning size of the data).
857 	std::copy(data.begin(), data.end(), back_inserter(pos->second->fileData));
858 }
859 
860 void
CompleteTempAvatarFile(unsigned playerId)861 ClientThread::CompleteTempAvatarFile(unsigned playerId)
862 {
863 	AvatarFileMap::iterator pos = m_tempAvatarMap.find(playerId);
864 	if (pos == m_tempAvatarMap.end())
865 		throw ClientException(__FILE__, __LINE__, ERR_NET_INVALID_REQUEST_ID, 0);
866 	boost::shared_ptr<AvatarFile> tmpAvatar = pos->second;
867 	unsigned avatarSize = (unsigned)tmpAvatar->fileData.size();
868 	if (avatarSize != tmpAvatar->reportedSize)
869 		LOG_ERROR("Client received invalid avatar file size!");
870 	else
871 		PassAvatarFileToManager(playerId, tmpAvatar);
872 
873 	// Free memory.
874 	m_tempAvatarMap.erase(pos);
875 }
876 
877 void
PassAvatarFileToManager(unsigned playerId,boost::shared_ptr<AvatarFile> AvatarFile)878 ClientThread::PassAvatarFileToManager(unsigned playerId, boost::shared_ptr<AvatarFile> AvatarFile)
879 {
880 	PlayerInfo tmpPlayerInfo;
881 	if (!GetCachedPlayerInfo(playerId, tmpPlayerInfo))
882 		LOG_ERROR("Client received invalid player id!");
883 	else {
884 		if (AvatarFile->fileType == AVATAR_FILE_TYPE_UNKNOWN)
885 			AvatarFile->fileType = tmpPlayerInfo.avatarType;
886 		if (!GetAvatarManager().StoreAvatarInCache(tmpPlayerInfo.avatar, AvatarFile->fileType, &AvatarFile->fileData[0], AvatarFile->reportedSize, false))
887 			LOG_ERROR("Failed to store avatar in cache directory.");
888 
889 		// Update player info, but never re-request avatar.
890 		SetPlayerInfo(playerId, tmpPlayerInfo);
891 
892 		string fileName;
893 		if (GetAvatarManager().GetAvatarFileName(tmpPlayerInfo.avatar, fileName)) {
894 			// Dynamically update avatar in GUI.
895 			GetGui().setPlayerAvatar(playerId, GetQtToolsInterface().stringToUtf8(fileName));
896 		}
897 	}
898 }
899 
900 void
SetUnknownAvatar(unsigned playerId)901 ClientThread::SetUnknownAvatar(unsigned playerId)
902 {
903 	m_tempAvatarMap.erase(playerId);
904 	LOG_ERROR("Server reported unknown avatar for player: " << playerId);
905 }
906 
907 void
TimerCheckAvatarDownloads(const boost::system::error_code & ec)908 ClientThread::TimerCheckAvatarDownloads(const boost::system::error_code& ec)
909 {
910 	if (!ec) {
911 		if (m_avatarDownloader && m_avatarDownloader->HasDownloadResult()) {
912 			unsigned playerId;
913 			boost::shared_ptr<AvatarFile> tmpAvatar(new AvatarFile);
914 			m_avatarDownloader->GetDownloadResult(playerId, tmpAvatar->fileData);
915 			tmpAvatar->reportedSize = tmpAvatar->fileData.size();
916 			PassAvatarFileToManager(playerId, tmpAvatar);
917 		}
918 		m_avatarTimer.expires_from_now(
919 			milliseconds(CLIENT_AVATAR_LOOP_MSEC));
920 		m_avatarTimer.async_wait(
921 			boost::bind(
922 				&ClientThread::TimerCheckAvatarDownloads, shared_from_this(), boost::asio::placeholders::error));
923 	}
924 }
925 
926 void
UnsubscribeLobbyMsg()927 ClientThread::UnsubscribeLobbyMsg()
928 {
929 	if (GetContext().GetSubscribeLobbyMsg()) {
930 		// Send unsubscribe request.
931 		boost::shared_ptr<NetPacket> packet(new NetPacket);
932 		packet->GetMsg()->set_messagetype(PokerTHMessage::Type_SubscriptionRequestMessage);
933 		SubscriptionRequestMessage *netRequest = packet->GetMsg()->mutable_subscriptionrequestmessage();
934 		netRequest->set_subscriptionaction(SubscriptionRequestMessage::unsubscribeGameList);
935 		GetSender().Send(GetContext().GetSessionData(), packet);
936 		GetContext().SetSubscribeLobbyMsg(false);
937 	}
938 }
939 
940 void
ResubscribeLobbyMsg()941 ClientThread::ResubscribeLobbyMsg()
942 {
943 	if (!GetContext().GetSubscribeLobbyMsg()) {
944 		// Clear game info map as it is outdated.
945 		ClearGameInfoMap();
946 		// Send resubscribe request.
947 		boost::shared_ptr<NetPacket> packet(new NetPacket);
948 		packet->GetMsg()->set_messagetype(PokerTHMessage::Type_SubscriptionRequestMessage);
949 		SubscriptionRequestMessage *netRequest = packet->GetMsg()->mutable_subscriptionrequestmessage();
950 		netRequest->set_subscriptionaction(SubscriptionRequestMessage::resubscribeGameList);
951 		GetSender().Send(GetContext().GetSessionData(), packet);
952 		GetContext().SetSubscribeLobbyMsg(true);
953 	}
954 }
955 
956 const ClientContext &
GetContext() const957 ClientThread::GetContext() const
958 {
959 	assert(m_context.get());
960 	return *m_context;
961 }
962 
963 ClientContext &
GetContext()964 ClientThread::GetContext()
965 {
966 	assert(m_context.get());
967 	return *m_context;
968 }
969 
970 string
GetCacheServerListFileName()971 ClientThread::GetCacheServerListFileName()
972 {
973 	string fileName;
974 	path tmpServerListPath(GetContext().GetCacheDir());
975 	string serverListUrl(GetContext().GetServerListUrl());
976 	// Retrieve the file name from the URL.
977 	size_t pos = serverListUrl.find_last_of('/');
978 	if (!GetContext().GetCacheDir().empty() && !serverListUrl.empty() && pos != string::npos && ++pos < serverListUrl.length()) {
979 		tmpServerListPath /= serverListUrl.substr(pos);
980 		fileName = tmpServerListPath.directory_string();
981 	}
982 	return fileName;
983 }
984 
985 void
CreateContextSession()986 ClientThread::CreateContextSession()
987 {
988 	bool validSocket = false;
989 	// TODO sctp
990 	try {
991 		boost::shared_ptr<tcp::socket> newSock;
992 		if (GetContext().GetAddrFamily() == AF_INET6)
993 			newSock.reset(new boost::asio::ip::tcp::socket(*m_ioService, tcp::v6()));
994 		else
995 			newSock.reset(new boost::asio::ip::tcp::socket(*m_ioService, tcp::v4()));
996 #if BOOST_VERSION < 106600
997 		boost::asio::socket_base::non_blocking_io command(true);
998 		newSock->io_control(command);
999 #else
1000 		newSock->non_blocking(true);
1001 #endif
1002 		newSock->set_option(tcp::no_delay(true));
1003 		newSock->set_option(boost::asio::socket_base::keep_alive(true));
1004 
1005 		GetContext().SetSessionData(boost::shared_ptr<SessionData>(new SessionData(
1006 										newSock,
1007 										SESSION_ID_GENERIC,
1008 										*this,
1009 										*m_ioService)));
1010 		GetContext().SetResolver(boost::shared_ptr<boost::asio::ip::tcp::resolver>(
1011 									 new boost::asio::ip::tcp::resolver(*m_ioService)));
1012 		validSocket = true;
1013 	} catch (...) {
1014 	}
1015 	if (!validSocket)
1016 		throw ClientException(__FILE__, __LINE__, ERR_SOCK_CREATION_FAILED, 0);
1017 }
1018 
1019 ClientState &
GetState()1020 ClientThread::GetState()
1021 {
1022 	assert(m_curState);
1023 	return *m_curState;
1024 }
1025 
1026 void
SetState(ClientState & newState)1027 ClientThread::SetState(ClientState &newState)
1028 {
1029 	if (m_curState)
1030 		m_curState->Exit(shared_from_this());
1031 	m_curState = &newState;
1032 	m_curState->Enter(shared_from_this());
1033 }
1034 
1035 boost::asio::steady_timer &
GetStateTimer()1036 ClientThread::GetStateTimer()
1037 {
1038 	return m_stateTimer;
1039 }
1040 
1041 SenderHelper &
GetSender()1042 ClientThread::GetSender()
1043 {
1044 	assert(m_senderHelper);
1045 	return *m_senderHelper;
1046 }
1047 
1048 unsigned
GetGameId() const1049 ClientThread::GetGameId() const
1050 {
1051 	boost::mutex::scoped_lock lock(m_curGameIdMutex);
1052 	return m_curGameId;
1053 }
1054 
1055 void
SetGameId(unsigned id)1056 ClientThread::SetGameId(unsigned id)
1057 {
1058 	boost::mutex::scoped_lock lock(m_curGameIdMutex);
1059 	m_curGameId = id;
1060 }
1061 
1062 Gsasl *
GetAuthContext()1063 ClientThread::GetAuthContext()
1064 {
1065 	assert(m_authContext);
1066 	return m_authContext;
1067 }
1068 
1069 const GameData &
GetGameData() const1070 ClientThread::GetGameData() const
1071 {
1072 	return m_gameData;
1073 }
1074 
1075 void
SetGameData(const GameData & gameData)1076 ClientThread::SetGameData(const GameData &gameData)
1077 {
1078 	m_gameData = gameData;
1079 }
1080 
1081 const StartData &
GetStartData() const1082 ClientThread::GetStartData() const
1083 {
1084 	return m_startData;
1085 }
1086 
1087 void
SetStartData(const StartData & startData)1088 ClientThread::SetStartData(const StartData &startData)
1089 {
1090 	m_startData = startData;
1091 }
1092 
1093 unsigned
GetGuiPlayerId() const1094 ClientThread::GetGuiPlayerId() const
1095 {
1096 	boost::mutex::scoped_lock lock(m_guiPlayerIdMutex);
1097 	return m_guiPlayerId;
1098 }
1099 
1100 int
GetOrigGuiPlayerNum() const1101 ClientThread::GetOrigGuiPlayerNum() const
1102 {
1103 	return m_origGuiPlayerNum;
1104 }
1105 
1106 void
SetGuiPlayerId(unsigned guiPlayerId)1107 ClientThread::SetGuiPlayerId(unsigned guiPlayerId)
1108 {
1109 	boost::mutex::scoped_lock lock(m_guiPlayerIdMutex);
1110 	m_guiPlayerId = guiPlayerId;
1111 }
1112 
1113 boost::shared_ptr<Game>
GetGame()1114 ClientThread::GetGame()
1115 {
1116 	return m_game;
1117 }
1118 
1119 QtToolsInterface &
GetQtToolsInterface()1120 ClientThread::GetQtToolsInterface()
1121 {
1122 	assert(myQtToolsInterface.get());
1123 	return *myQtToolsInterface;
1124 }
1125 
1126 boost::shared_ptr<PlayerData>
CreatePlayerData(unsigned playerId,bool isGameAdmin)1127 ClientThread::CreatePlayerData(unsigned playerId, bool isGameAdmin)
1128 {
1129 	boost::shared_ptr<PlayerData> playerData;
1130 	PlayerInfo info;
1131 	if (GetCachedPlayerInfo(playerId, info)) {
1132 		playerData.reset(
1133 			new PlayerData(playerId, 0, info.ptype,
1134 						   info.isGuest ? PLAYER_RIGHTS_GUEST : PLAYER_RIGHTS_NORMAL, isGameAdmin));
1135 		playerData->SetName(info.playerName);
1136 		if (info.hasAvatar) {
1137 			string avatarFile;
1138 			if (GetAvatarManager().GetAvatarFileName(info.avatar, avatarFile))
1139 				playerData->SetAvatarFile(GetQtToolsInterface().stringToUtf8(avatarFile));
1140 			else
1141 				RetrieveAvatarIfNeeded(playerId, info);
1142 		}
1143 	} else {
1144 		ostringstream name;
1145 		name << "#" << playerId;
1146 
1147 		// Request player info.
1148 		RequestPlayerInfo(playerId, true);
1149 		// Use temporary data until the PlayerInfo request is completed.
1150 		playerData.reset(
1151 			new PlayerData(playerId, 0, PLAYER_TYPE_HUMAN, PLAYER_RIGHTS_NORMAL, isGameAdmin));
1152 		playerData->SetName(name.str());
1153 	}
1154 	return playerData;
1155 }
1156 
1157 void
AddPlayerData(boost::shared_ptr<PlayerData> playerData)1158 ClientThread::AddPlayerData(boost::shared_ptr<PlayerData> playerData)
1159 {
1160 	if (playerData.get() && !playerData->GetName().empty()) {
1161 		m_playerDataList.push_back(playerData);
1162 		if (playerData->GetUniqueId() == GetGuiPlayerId())
1163 			GetCallback().SignalNetClientSelfJoined(playerData->GetUniqueId(), playerData->GetName(), playerData->IsGameAdmin());
1164 		else {
1165 			GetCallback().SignalNetClientPlayerJoined(playerData->GetUniqueId(), playerData->GetName(), playerData->IsGameAdmin());
1166 //			if(m_game) {
1167 //				m_clientLog->logPlayerAction(playerData->GetName(),LOG_ACTION_JOIN);
1168 //			}
1169 		}
1170 	}
1171 }
1172 
1173 void
RemovePlayerData(unsigned playerId,int removeReason)1174 ClientThread::RemovePlayerData(unsigned playerId, int removeReason)
1175 {
1176 	boost::shared_ptr<PlayerData> tmpData;
1177 
1178 	PlayerDataList::iterator i = m_playerDataList.begin();
1179 	PlayerDataList::iterator end = m_playerDataList.end();
1180 	while (i != end) {
1181 		if ((*i)->GetUniqueId() == playerId) {
1182 			tmpData = *i;
1183 			m_playerDataList.erase(i);
1184 			break;
1185 		}
1186 		++i;
1187 	}
1188 
1189 	if (tmpData.get()) {
1190 		// Remove player from gui.
1191 		if (GetGame()) {
1192 			boost::shared_ptr<PlayerInterface> tmpPlayer(GetGame()->getPlayerByUniqueId(tmpData->GetUniqueId()));
1193 			if (tmpPlayer) {
1194 				tmpPlayer->setMyStayOnTableStatus(false);
1195 			}
1196 		}
1197 		GetCallback().SignalNetClientPlayerLeft(tmpData->GetUniqueId(), tmpData->GetName(), removeReason);
1198 
1199 		if(m_game) {
1200 			if(removeReason == NTF_NET_REMOVED_KICKED) {
1201 				m_clientLog->logPlayerAction(tmpData->GetName(),LOG_ACTION_KICKED);
1202 			} else {
1203 				m_clientLog->logPlayerAction(tmpData->GetName(),LOG_ACTION_LEFT);
1204 			}
1205 		}
1206 
1207 	}
1208 }
1209 
1210 void
ClearPlayerDataList()1211 ClientThread::ClearPlayerDataList()
1212 {
1213 	m_playerDataList.clear();
1214 }
1215 
1216 void
MapPlayerDataList()1217 ClientThread::MapPlayerDataList()
1218 {
1219 	// Retrieve the GUI player.
1220 	boost::shared_ptr<PlayerData> guiPlayer = GetPlayerDataByUniqueId(GetGuiPlayerId());
1221 	assert(guiPlayer.get());
1222 	m_origGuiPlayerNum = guiPlayer->GetNumber();
1223 
1224 	// Create a copy of the player list so that the GUI player
1225 	// is player 0. This is mapped because the GUI depends on it.
1226 	PlayerDataList mappedList;
1227 
1228 	PlayerDataList::const_iterator i = m_playerDataList.begin();
1229 	PlayerDataList::const_iterator end = m_playerDataList.end();
1230 	int numPlayers = GetStartData().numberOfPlayers;
1231 
1232 	while (i != end) {
1233 		boost::shared_ptr<PlayerData> tmpData(new PlayerData(*(*i)));
1234 		int numberDiff = numPlayers - m_origGuiPlayerNum;
1235 		tmpData->SetNumber((tmpData->GetNumber() + numberDiff) % numPlayers);
1236 		mappedList.push_back(tmpData);
1237 		++i;
1238 	}
1239 
1240 	// Sort the list by player number.
1241 	mappedList.sort(*boost::lambda::_1 < *boost::lambda::_2);
1242 
1243 	m_playerDataList = mappedList;
1244 }
1245 
1246 const PlayerDataList &
GetPlayerDataList() const1247 ClientThread::GetPlayerDataList() const
1248 {
1249 	return m_playerDataList;
1250 }
1251 
1252 boost::shared_ptr<PlayerData>
GetPlayerDataByUniqueId(unsigned id)1253 ClientThread::GetPlayerDataByUniqueId(unsigned id)
1254 {
1255 	boost::shared_ptr<PlayerData> tmpPlayer;
1256 
1257 	PlayerDataList::const_iterator i = m_playerDataList.begin();
1258 	PlayerDataList::const_iterator end = m_playerDataList.end();
1259 
1260 	while (i != end) {
1261 		if ((*i)->GetUniqueId() == id) {
1262 			tmpPlayer = *i;
1263 			break;
1264 		}
1265 		++i;
1266 	}
1267 	return tmpPlayer;
1268 }
1269 
1270 boost::shared_ptr<PlayerData>
GetPlayerDataByName(const std::string & name)1271 ClientThread::GetPlayerDataByName(const std::string &name)
1272 {
1273 	boost::shared_ptr<PlayerData> tmpPlayer;
1274 
1275 	if (!name.empty()) {
1276 		PlayerDataList::const_iterator i = m_playerDataList.begin();
1277 		PlayerDataList::const_iterator end = m_playerDataList.end();
1278 
1279 		while (i != end) {
1280 			if ((*i)->GetName() == name) {
1281 				tmpPlayer = *i;
1282 				break;
1283 			}
1284 			++i;
1285 		}
1286 	}
1287 	return tmpPlayer;
1288 }
1289 
1290 void
AddServerInfo(unsigned serverId,const ServerInfo & info)1291 ClientThread::AddServerInfo(unsigned serverId, const ServerInfo &info)
1292 {
1293 	{
1294 		boost::mutex::scoped_lock lock(m_serverInfoMapMutex);
1295 		m_serverInfoMap.insert(ServerInfoMap::value_type(serverId, info));
1296 	}
1297 	GetCallback().SignalNetClientServerListAdd(serverId);
1298 }
1299 
1300 void
ClearServerInfoMap()1301 ClientThread::ClearServerInfoMap()
1302 {
1303 	{
1304 		boost::mutex::scoped_lock lock(m_serverInfoMapMutex);
1305 		m_serverInfoMap.clear();
1306 	}
1307 	GetCallback().SignalNetClientServerListClear();
1308 }
1309 
1310 bool
GetSelectedServer(unsigned & serverId) const1311 ClientThread::GetSelectedServer(unsigned &serverId) const
1312 {
1313 	bool retVal = false;
1314 	boost::mutex::scoped_lock lock(m_selectServerMutex);
1315 	if (m_isServerSelected) {
1316 		retVal = true;
1317 		serverId = m_selectedServerId;
1318 	}
1319 	return retVal;
1320 }
1321 
1322 void
UseServer(unsigned serverId)1323 ClientThread::UseServer(unsigned serverId)
1324 {
1325 	ClientContext &context = GetContext();
1326 	ServerInfo useInfo(GetServerInfo(serverId));
1327 
1328 	if (context.GetAddrFamily() == AF_INET6)
1329 		context.SetServerAddr(useInfo.ipv6addr);
1330 	else
1331 		context.SetServerAddr(useInfo.ipv4addr);
1332 
1333 	context.SetServerPort((unsigned)useInfo.port);
1334 	context.SetAvatarServerAddr(useInfo.avatarServerAddr);
1335 }
1336 
1337 bool
GetLoginData(LoginData & loginData) const1338 ClientThread::GetLoginData(LoginData &loginData) const
1339 {
1340 	bool retVal = false;
1341 	boost::mutex::scoped_lock lock(m_loginDataMutex);
1342 	if (!m_loginData.userName.empty()) {
1343 		loginData = m_loginData;
1344 		retVal = true;
1345 	}
1346 	return retVal;
1347 }
1348 
1349 void
AddGameInfo(unsigned gameId,const GameInfo & info)1350 ClientThread::AddGameInfo(unsigned gameId, const GameInfo &info)
1351 {
1352 	{
1353 		boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1354 		m_gameInfoMap.insert(GameInfoMap::value_type(gameId, info));
1355 	}
1356 	GetCallback().SignalNetClientGameListNew(gameId);
1357 }
1358 
1359 void
UpdateGameInfoMode(unsigned gameId,GameMode mode)1360 ClientThread::UpdateGameInfoMode(unsigned gameId, GameMode mode)
1361 {
1362 	bool found = false;
1363 	{
1364 		boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1365 		GameInfoMap::iterator pos = m_gameInfoMap.find(gameId);
1366 		if (pos != m_gameInfoMap.end()) {
1367 			found = true;
1368 			(*pos).second.mode = mode;
1369 		}
1370 	}
1371 	if (found)
1372 		GetCallback().SignalNetClientGameListUpdateMode(gameId, mode);
1373 }
1374 
1375 void
UpdateGameInfoAdmin(unsigned gameId,unsigned adminPlayerId)1376 ClientThread::UpdateGameInfoAdmin(unsigned gameId, unsigned adminPlayerId)
1377 {
1378 	bool found = false;
1379 	{
1380 		boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1381 		GameInfoMap::iterator pos = m_gameInfoMap.find(gameId);
1382 		if (pos != m_gameInfoMap.end()) {
1383 			found = true;
1384 			(*pos).second.adminPlayerId = adminPlayerId;
1385 		}
1386 	}
1387 	if (found)
1388 		GetCallback().SignalNetClientGameListUpdateAdmin(gameId, adminPlayerId);
1389 }
1390 
1391 void
RemoveGameInfo(unsigned gameId)1392 ClientThread::RemoveGameInfo(unsigned gameId)
1393 {
1394 	bool found = false;
1395 	{
1396 		boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1397 		GameInfoMap::iterator pos = m_gameInfoMap.find(gameId);
1398 		if (pos != m_gameInfoMap.end()) {
1399 			found = true;
1400 			m_gameInfoMap.erase(pos);
1401 		}
1402 	}
1403 	if (found)
1404 		GetCallback().SignalNetClientGameListRemove(gameId);
1405 }
1406 
1407 void
ModifyGameInfoAddPlayer(unsigned gameId,unsigned playerId)1408 ClientThread::ModifyGameInfoAddPlayer(unsigned gameId, unsigned playerId)
1409 {
1410 	bool playerAdded = false;
1411 	{
1412 		boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1413 		GameInfoMap::iterator pos = m_gameInfoMap.find(gameId);
1414 		if (pos != m_gameInfoMap.end()) {
1415 			pos->second.players.push_back(playerId);
1416 			playerAdded = true;
1417 		}
1418 	}
1419 	if (playerAdded)
1420 		GetCallback().SignalNetClientGameListPlayerJoined(gameId, playerId);
1421 }
1422 
1423 void
ModifyGameInfoRemovePlayer(unsigned gameId,unsigned playerId)1424 ClientThread::ModifyGameInfoRemovePlayer(unsigned gameId, unsigned playerId)
1425 {
1426 	bool playerRemoved = false;
1427 	{
1428 		boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1429 		GameInfoMap::iterator pos = m_gameInfoMap.find(gameId);
1430 		if (pos != m_gameInfoMap.end()) {
1431 			pos->second.players.remove(playerId);
1432 			playerRemoved = true;
1433 		}
1434 	}
1435 	if (playerRemoved)
1436 		GetCallback().SignalNetClientGameListPlayerLeft(gameId, playerId);
1437 }
1438 
1439 void
ModifyGameInfoAddSpectator(unsigned gameId,unsigned playerId)1440 ClientThread::ModifyGameInfoAddSpectator(unsigned gameId, unsigned playerId)
1441 {
1442 	bool playerAdded = false;
1443 	{
1444 		boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1445 		GameInfoMap::iterator pos = m_gameInfoMap.find(gameId);
1446 		if (pos != m_gameInfoMap.end()) {
1447 			pos->second.spectators.push_back(playerId);
1448 			playerAdded = true;
1449 		}
1450 	}
1451 	if (playerAdded)
1452 		GetCallback().SignalNetClientGameListSpectatorJoined(gameId, playerId);
1453 }
1454 
1455 void
ModifyGameInfoRemoveSpectator(unsigned gameId,unsigned playerId)1456 ClientThread::ModifyGameInfoRemoveSpectator(unsigned gameId, unsigned playerId)
1457 {
1458 	bool playerRemoved = false;
1459 	{
1460 		boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1461 		GameInfoMap::iterator pos = m_gameInfoMap.find(gameId);
1462 		if (pos != m_gameInfoMap.end()) {
1463 			pos->second.spectators.remove(playerId);
1464 			playerRemoved = true;
1465 		}
1466 	}
1467 	if (playerRemoved)
1468 		GetCallback().SignalNetClientGameListSpectatorLeft(gameId, playerId);
1469 }
1470 
1471 void
ModifyGameInfoClearSpectatorsDuringGame()1472 ClientThread::ModifyGameInfoClearSpectatorsDuringGame()
1473 {
1474 	boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1475 	GameInfoMap::iterator pos = m_gameInfoMap.find(GetGameId());
1476 	if (pos != m_gameInfoMap.end()) {
1477 		pos->second.spectatorsDuringGame.clear();
1478 	}
1479 }
1480 
1481 void
ModifyGameInfoAddSpectatorDuringGame(unsigned playerId)1482 ClientThread::ModifyGameInfoAddSpectatorDuringGame(unsigned playerId)
1483 {
1484 	bool spectatorAdded = false;
1485 	{
1486 		boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1487 		GameInfoMap::iterator pos = m_gameInfoMap.find(GetGameId());
1488 		if (pos != m_gameInfoMap.end()) {
1489 			pos->second.spectatorsDuringGame.push_back(playerId);
1490 			spectatorAdded = true;
1491 		}
1492 	}
1493 	if (spectatorAdded)
1494 		GetCallback().SignalNetClientSpectatorJoined(playerId, GetPlayerName(playerId));
1495 }
1496 
1497 void
ModifyGameInfoRemoveSpectatorDuringGame(unsigned playerId,int removeReason)1498 ClientThread::ModifyGameInfoRemoveSpectatorDuringGame(unsigned playerId, int removeReason)
1499 {
1500 	bool spectatorRemoved = false;
1501 	{
1502 		boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1503 		GameInfoMap::iterator pos = m_gameInfoMap.find(GetGameId());
1504 		if (pos != m_gameInfoMap.end()) {
1505 			pos->second.spectatorsDuringGame.remove(playerId);
1506 			spectatorRemoved = true;
1507 		}
1508 	}
1509 	if (spectatorRemoved)
1510 		GetCallback().SignalNetClientSpectatorLeft(playerId, GetPlayerName(playerId), removeReason);
1511 }
1512 
1513 void
ClearGameInfoMap()1514 ClientThread::ClearGameInfoMap()
1515 {
1516 	boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1517 	m_gameInfoMap.clear();
1518 }
1519 
1520 void
StartPetition(unsigned petitionId,unsigned proposingPlayerId,unsigned kickPlayerId,int timeoutSec,int numVotesToKick)1521 ClientThread::StartPetition(unsigned petitionId, unsigned proposingPlayerId, unsigned kickPlayerId, int timeoutSec, int numVotesToKick)
1522 {
1523 	{
1524 		boost::mutex::scoped_lock lock(m_curPetitionIdMutex);
1525 		m_curPetitionId = petitionId;
1526 	}
1527 	GetGui().startVoteOnKick(kickPlayerId, proposingPlayerId, timeoutSec, numVotesToKick);
1528 	if (GetGuiPlayerId() != kickPlayerId
1529 			&& GetGuiPlayerId() != proposingPlayerId) {
1530 		GetGui().changeVoteOnKickButtonsState(true);
1531 	}
1532 }
1533 
1534 void
UpdatePetition(unsigned petitionId,int,int numVotesInFavourOfKicking,int numVotesToKick)1535 ClientThread::UpdatePetition(unsigned petitionId, int /*numVotesAgainstKicking*/, int numVotesInFavourOfKicking, int numVotesToKick)
1536 {
1537 	bool isCurPetition;
1538 	{
1539 		boost::mutex::scoped_lock lock(m_curPetitionIdMutex);
1540 		isCurPetition = m_curPetitionId == petitionId;
1541 	}
1542 	if (isCurPetition) {
1543 		GetGui().refreshVotesMonitor(numVotesInFavourOfKicking, numVotesToKick);
1544 	}
1545 }
1546 
1547 void
EndPetition(unsigned petitionId)1548 ClientThread::EndPetition(unsigned petitionId)
1549 {
1550 	bool isCurPetition;
1551 	{
1552 		boost::mutex::scoped_lock lock(m_curPetitionIdMutex);
1553 		isCurPetition = m_curPetitionId == petitionId;
1554 	}
1555 	if (isCurPetition)
1556 		GetGui().endVoteOnKick();
1557 }
1558 
1559 void
UpdateStatData(const ServerStats & stats)1560 ClientThread::UpdateStatData(const ServerStats &stats)
1561 {
1562 	boost::mutex::scoped_lock lock(m_curStatsMutex);
1563 	if (stats.numberOfPlayersOnServer)
1564 		m_curStats.numberOfPlayersOnServer = stats.numberOfPlayersOnServer;
1565 
1566 	if (stats.totalPlayersEverLoggedIn)
1567 		m_curStats.totalPlayersEverLoggedIn = stats.totalPlayersEverLoggedIn;
1568 
1569 	if (stats.totalGamesEverCreated)
1570 		m_curStats.totalGamesEverCreated = stats.totalGamesEverCreated;
1571 
1572 	GetCallback().SignalNetClientStatsUpdate(m_curStats);
1573 }
1574 
1575 void
EndPing()1576 ClientThread::EndPing()
1577 {
1578 	boost::mutex::scoped_lock lock(m_pingDataMutex);
1579 	if (m_pingData.EndPing()) {
1580 		GetCallback().SignalNetClientPingUpdate(m_pingData.MinPing(), m_pingData.AveragePing(), m_pingData.MaxPing());
1581 	}
1582 }
1583 
1584 ServerStats
GetStatData() const1585 ClientThread::GetStatData() const
1586 {
1587 	boost::mutex::scoped_lock lock(m_curStatsMutex);
1588 	return m_curStats;
1589 }
1590 
1591 bool
IsSessionEstablished() const1592 ClientThread::IsSessionEstablished() const
1593 {
1594 	return m_sessionEstablished;
1595 }
1596 
1597 void
SetSessionEstablished(bool flag)1598 ClientThread::SetSessionEstablished(bool flag)
1599 {
1600 	if (m_sessionEstablished != flag) {
1601 		m_sessionEstablished = flag;
1602 		if (flag)
1603 			SendQueuedPackets();
1604 	}
1605 }
1606 
1607 bool
IsSynchronized() const1608 ClientThread::IsSynchronized() const
1609 {
1610 	return m_playerInfoRequestList.empty();
1611 }
1612 
1613 void
ReadSessionGuidFromFile()1614 ClientThread::ReadSessionGuidFromFile()
1615 {
1616 	string guidFileName(GetContext().GetCacheDir() + TEMP_GUID_FILENAME);
1617 	std::ifstream guidStream(guidFileName.c_str(), ios::in | ios::binary);
1618 	if (guidStream.good()) {
1619 		std::vector<char> tmpGuid(CLIENT_GUID_SIZE);
1620 		guidStream.read(&tmpGuid[0], CLIENT_GUID_SIZE);
1621 		GetContext().SetSessionGuid(string(tmpGuid.begin(), tmpGuid.end()));
1622 	}
1623 }
1624 
1625 void
WriteSessionGuidToFile() const1626 ClientThread::WriteSessionGuidToFile() const
1627 {
1628 	string guidFileName(GetContext().GetCacheDir() + TEMP_GUID_FILENAME);
1629 	std::ofstream guidStream(guidFileName.c_str(), ios::out | ios::trunc | ios::binary);
1630 	if (guidStream.good()) {
1631 		guidStream.write(GetContext().GetSessionGuid().c_str(), GetContext().GetSessionGuid().size());
1632 	}
1633 }
1634 
1635