BF2MC-Matchmaker
gamestats/client.cpp
1 #include <unistd.h>
2 #include <iostream>
3 #include <iomanip>
4 #include <thread>
5 
6 #include <settings.h>
7 #include <logger.h>
8 #include <server.h>
9 #include <globals.h>
10 #include <util.h>
11 #include <database.h>
12 #include <battlefield/player.h>
13 #include <battlefield/gamestat.h>
14 
15 #include <gamestats/client.h>
16 
17 typedef void (GameStats::Client::*RequestActionFunc)(const GameSpy::Parameter&);
18 
19 static std::map<std::string, RequestActionFunc> mRequestActions =
20 {
21  { "auth", &GameStats::Client::requestAuth },
22  { "newgame", &GameStats::Client::requestNewGame },
24  //{ "ka", &GameStats::Client::requestKa }
25 };
26 
27 GameStats::Client::Client(int socket, struct sockaddr_in address)
28 {
29  this->_socket = socket;
30  this->_address = address;
31  this->UpdateLastRecievedTime();
32 }
33 
35 {
36  this->Disconnect();
37 }
38 
40 {
41  bool isDisconnected = false;
42 
43  // Initialize connection send challenge
44  this->requestChallenge();
45 
46  while(!isDisconnected)
47  {
48  std::vector<unsigned char> combined_buffer;
49  std::string last_seven_chars = "";
50 
51  do
52  {
53  std::vector<unsigned char> buffer(16384, 0);
54 
55  int v = read(this->_socket, &(buffer[0]), 16384);
56 
57  // If error or no data is recieved we end the connection
58  if(v <= 0)
59  {
60  isDisconnected = true;
61  break;
62  }
63 
64  // Resize buffer
65  buffer.resize(v);
66 
67  this->UpdateLastRecievedTime();
68 
69  // Debug
70  //std::stringstream ss;
71  //for(int i = 0; i < buffer.size(); i++)
72  //{
73  // ss << std::hex << std::setfill('0') << std::setw(2) << (int)(buffer[i]);
74  //}
75  //Logger::info("buffer = " + ss.str());
76 
77  combined_buffer.insert(combined_buffer.end(), buffer.begin(), buffer.end());
78 
79  if(combined_buffer.size() > 7)
80  {
81  last_seven_chars.assign(combined_buffer.end() - 7, combined_buffer.end());
82  }
83  } while (last_seven_chars != "\\final\\" && combined_buffer.size() < 32768);
84 
85  if(combined_buffer.size() > 7)
86  {
87  std::string request = Decrypt(combined_buffer);
88 
89  this->_LogTransaction("-->", request);
90 
91  this->onRequest(request);
92  }
93  }
94 
95  this->Disconnect();
96 }
97 
99 {
100  this->Close();
101  g_gamestats_server->onClientDisconnect(*this);
102 }
103 
104 
105 // Events
106 
107 void GameStats::Client::onRequest(const std::string& request)
108 {
109  GameSpy::Parameter parameter = GameSpy::Request2Parameter(request);
110 
111  // Find function name
112  std::string action = parameter[0];
113 
114  auto it = mRequestActions.find(action);
115  if (it != mRequestActions.end())
116  {
117  // Get Function address
118  RequestActionFunc func = it->second;
119 
120  // Execute action function with class object.
121  (this->*(func))(parameter);
122  }
123  else
124  {
125  Logger::warning("action \"" + action + "\" not implemented!", Server::Type::GameStats);
126 
127  this->Disconnect();
128  }
129 }
130 
131 /*
132  Response:
133  \lc\1\challenge\GzlCt7q8sV\id\1\final\
134 */
135 
137 {
138  std::string challenge = Util::generateRandomChallenge();
139 
140  Logger::info(this->GetAddress() + " --> Challenge", Server::Type::GameStats);
141 
142  std::string response = GameSpy::Parameter2Response({
143  "lc", "1",
144  "challenge", challenge,
145  "id", "1",
146  "final"
147  });
148 
149  this->Send(Encrypt(response));
150 
151  this->_LogTransaction("<--", response);
152 }
153 
154 /*
155  Request:
156  \auth\\gamename\bfield1942ps2\response\f34cc66938c4b07c70ebff98d9d98561\port\0\id\1\final\
157  Response:
158  \lc\2\sesskey\1687554231\proof\0\id\1\final\
159 */
160 
161 void GameStats::Client::requestAuth(const GameSpy::Parameter& parameter)
162 {
163  Logger::info(this->GetAddress() + " --> Auth", Server::Type::GameStats);
164 
165  std::string response = GameSpy::Parameter2Response({
166  "lc", "2",
167  "sesskey", "1687554231",
168  "proof", "0",
169  "id", "1",
170  "final"
171  });
172 
173  this->Send(Encrypt(response));
174 
175  this->_LogTransaction("<--", response);
176 }
177 
178 /*
179  Request:
180  \newgame\\connid\1687554231\sesskey\197854479\final\
181 */
182 
183 void GameStats::Client::requestNewGame(const GameSpy::Parameter& parameter)
184 {
185  Logger::info(this->GetAddress() + " --> NewGame", Server::Type::GameStats);
186 }
187 
188 /*
189  \updgame\\sesskey\898654156\done\1\gamedata\
190  \gametype\2\gamver\V1.31a\hostname\[Server]IamLupo\mapid\2\numplayers\0\pplayers\0\tplayed\1141
191  \final\
192 
193  \updgame\\sesskey\1216337440\done\1\gamedata\
194  \gametype\1\gamver\V1.31a\hostname\[Server]IamLupo\mapid\2\numplayers\2\pplayers\2\tplayed\88
195  \clanid_t0\0\country_t0\2\victory_t0\2\clanid_t1\0\country_t1\1
196  \auth_0\1413c48006d6c3c7aca3cb558ca9ae07\bod_0\0\havd_0\0\hed_0\0\k1_0\0\k2_0\0\k3_0\0\k4_0\0\k5_0\0\kills_0\0
197  \lavd_0\0\mavd_0\0\medals_0\1069580287\mv_0\0\ngp_0\0\pid_0\10036819\pld_0\0\pph_0\5239\rank_0\13
198  \s1_0\0\s2_0\1\s3_0\0\s4_0\0\s5_0\0\score_0\3\suicides_0\0\time_0\71\tk_0\0\ttb_0\0
199  \auth_1\1413c48006d6c3c7aca3cb558ca9ae07\bod_1\0\havd_1\0\hed_1\0\k1_1\0\k2_1\0\k3_1\0\k4_1\0\k5_1\0\kills_1\0
200  \lavd_1\0\mavd_1\0\medals_1\1069580287\mv_1\0\ngp_1\1\pid_1\10037049\pld_1\0\pph_1\5175\rank_1\13
201  \s1_1\1\s2_1\0\s3_1\0\s4_1\0\s5_1\0\score_1\0\suicides_1\0\time_1\88\tk_1\0\ttb_1\1
202  \final\
203 */
204 
205 void GameStats::Client::requestUpdateGame(const GameSpy::Parameter& parameter)
206 {
207  int offset = 8;
208  std::string key, value;
209  Battlefield::GameStat game_stat;
210  Battlefield::GameServer game_server;
211 
212  // Set game server ip
213  game_server.SetIp(this->GetIP());
214 
215  // Check game server information in database
216  g_database->queryGameServerByIp(game_server);
217 
218  if(!game_server.isVerified())
219  {
220  Logger::warning("Server is not verified. Go to the database and verify the server.");
221  return;
222  }
223 
224  Logger::info(this->GetAddress() + " --> UpdateGame", Server::Type::GameStats);
225 
226  // Read Game stat information
227  while(parameter.size() > offset + 1)
228  {
229  key = parameter[offset];
230  value = parameter[offset + 1];
231 
232  if (key.find("auth_") != std::string::npos)
233  break;
234 
235  // Update game stat
236  auto it = Battlefield::GameStat::SetterMap.find(key);
237  if (it != Battlefield::GameStat::SetterMap.end()) {
238  (game_stat.*(it->second))(value);
239  }
240 
241  // Debug
242  //Logger::debug(key + " = " + value);
243 
244  offset += 2;
245  }
246 
247  // Debug
248  //game_stat.Debug();
249  //Logger::debug("=============================");
250 
251  // Read Game stat player information
252  while(parameter.size() > offset + 1)
253  {
255 
256  // Get key and player index out of parameter
257  int player_index;
258  this->_GetKeyAndPlayerIndex(parameter[offset], key, player_index);
259 
260  // Copy player index
261  int previous_player_index = player_index;
262 
263  // Debug
264  //Logger::debug("player_index = " + std::to_string(player_index));
265 
266  while(parameter.size() > offset + 1)
267  {
268  // Get key and player index
269  this->_GetKeyAndPlayerIndex(parameter[offset], key, player_index);
270 
271  // Get value
272  value = parameter[offset + 1];
273 
274  // If we found the new player_index then we create new GameStatPlayer
275  if (previous_player_index != player_index)
276  break;
277 
278  // Update game stat player
279  auto it = Battlefield::GameStatPlayer::SetterMap.find(key);
280  if (it != Battlefield::GameStatPlayer::SetterMap.end()) {
281  (gsplayer.*(it->second))(value);
282  }
283 
284  // Debug
285  //Logger::debug(key + " = " + value);
286 
287  offset += 2;
288  }
289 
290  game_stat.AddPlayer(gsplayer);
291 
292  // Save previous player index
293  previous_player_index = player_index;
294 
295  // Debug
296  //gsplayer.Debug();
297  //Logger::debug("=============================");
298  }
299 
300  // Update players stats
301  for(Battlefield::GameStatPlayer gsplayer : game_stat.GetPlayers())
302  {
303  gsplayer.UpdatePlayerStats(game_stat);
304  }
305 
306  // Insert GameStat in database
307  g_database->insertGameStat(game_stat);
308 
309  // Update Clan stats
310  if(game_stat.GetTeam1ClanId() != 0 && game_stat.GetTeam2ClanId() != 0)
311  {
312  game_stat.UpdateClanStats();
313  }
314 
315  g_database->createLeaderboards();
316 }
317 
318 // Private functions
319 
320 void GameStats::Client::_LogTransaction(const std::string& direction, const std::string& response) const
321 {
322  std::shared_lock<std::shared_mutex> guard2(g_settings_mutex); // settings lock (read)
323 
324  if ((g_logger_mode & Logger::Mode::Development) == 0)
325  {
326  return;
327  }
328 
329  bool show_console = (g_settings["gamestats"]["show_requests"].asBool() && direction == "-->") ||
330  (g_settings["gamestats"]["show_responses"].asBool() && direction == "<--");
331 
332  Logger::info(this->GetAddress() + " " + direction + " " + response,
333  Server::Type::GameStats, show_console);
334 }
335 
336 bool GameStats::Client::_GetKeyAndPlayerIndex(const std::string& input, std::string& key, int& player_index)
337 {
338  player_index = -1;
339  key = "";
340 
341  size_t pos = input.rfind('_');
342  if (pos == std::string::npos)
343  {
344  return false;
345  }
346 
347  try
348  {
349  key = input.substr(0, pos);
350  player_index = std::stoi(input.substr(pos + 1));
351  }
352  catch(...)
353  {
354  return false;
355  };
356 
357  return true;
358 }
359 
360 // Static functions
361 
363 {
364  Logger::info("Heartbeat started", Server::GameStats);
365 
366  while(true)
367  {
368  std::this_thread::sleep_for (std::chrono::seconds(60));
369 
370  for(std::shared_ptr<Net::Socket> client : g_gamestats_server->GetClients())
371  {
372  std::shared_ptr<GameStats::Client> gamestats_client = std::dynamic_pointer_cast<GameStats::Client>(client);
373 
374  std::string response = GameSpy::Parameter2Response({
375  "ka", "",
376  "final"
377  });
378 
379  gamestats_client.get()->Send(Encrypt(response));
380 
381  gamestats_client.get()->_LogTransaction("<--", response);
382  }
383  }
384 }
385 
386 std::string GameStats::Client::Decrypt(const std::vector<unsigned char>& request)
387 {
388  std::string msg;
389  const char key[10] = "GameSpy3D";
390 
391  for(int i = 0; i < static_cast<int>(request.size()) - 7; i++)
392  {
393  char v = key[(i % 9)] ^ request[i];
394 
395  if(v > 0x20 && v < 0x7F)
396  msg += v;
397  else
398  msg += "\\";
399  }
400 
401  msg += "\\final\\";
402 
403  return msg;
404 }
405 
406 std::vector<unsigned char> GameStats::Client::Encrypt(const std::string& response)
407 {
408  std::vector<unsigned char> enc_response;
409  const char key[10] = "GameSpy3D";
410 
411  for(int i = 0; i < response.size() - 7; i++)
412  {
413  enc_response.push_back(key[(i % 9)] ^ response[i]);
414  }
415 
416  // Add final at the end
417  enc_response.push_back(0x5c);
418  enc_response.push_back(0x66);
419  enc_response.push_back(0x69);
420  enc_response.push_back(0x6e);
421  enc_response.push_back(0x61);
422  enc_response.push_back(0x6c);
423  enc_response.push_back(0x5c);
424 
425  return enc_response;
426 }
427 
Class representing game server information.
Definition: gameserver.h:63
Represents a player's statistics in a game.
Definition: gamestat.h:146
Represents game statistics.
Definition: gamestat.h:37
bool queryGameServerByIp(Battlefield::GameServer &game_server)
Queries a game server by its IP address.
Definition: game_server.cpp:10
bool insertGameStat(Battlefield::GameStat &game_stat)
Inserts a game statistic into the database.
Definition: game_stat.cpp:299
bool createLeaderboards()
Creates leaderboard tables with ranked players.
Represents a client for game statistics.
~Client()
Destructor for Game Statistics Client.
void requestUpdateGame(const GameSpy::Parameter &parameter)
Request to update an existing game with provided parameters.
void onRequest(const std::string &request)
Handle incoming requests from the client.
void Listen()
Start listening for client requests.
static std::vector< unsigned char > Encrypt(const std::string &response)
Encrypts a response message.
void Disconnect()
Disconnect the client.
static void Heartbeat()
Heartbeat function to manage client connections.
void requestAuth(const GameSpy::Parameter &parameter)
Request authentication with provided parameters.
void _LogTransaction(const std::string &direction, const std::string &response) const
Log a transaction with direction and response.
void requestNewGame(const GameSpy::Parameter &parameter)
Request to create a new game with provided parameters.
static std::string Decrypt(const std::vector< unsigned char > &request)
Decrypts a request message.
void requestChallenge()
Request a challenge from the client.
Client(int socket, struct sockaddr_in address)
Constructor for Game Statistics Client.
bool _GetKeyAndPlayerIndex(const std::string &input, std::string &key, int &player_index)
Get the key and the player index from string input.
void UpdateLastRecievedTime()
Updates the last received time to the current system time.
Definition: socket.cpp:112
struct sockaddr_in _address
Definition: socket.h:18
int _socket
Definition: socket.h:17
std::vector< std::shared_ptr< Net::Socket > > GetClients()
Get the vector of client sockets connected to this server.
Definition: server.cpp:86
@ GameStats
Definition: server.h:22
void onClientDisconnect(const Net::Socket &client)
Called when a client disconnects from the server.
Definition: server.cpp:336