/* * Copyright (C) Volition, Inc. 2005. All rights reserved. * * All source code herein is the property of Volition, Inc. You may not sell * or otherwise commercially exploit the source or things you created based on the * source. * */ #ifndef _WIN32 #include #include #include #include #endif #ifdef SCP_BSD #include #endif #include "globalincs/pstypes.h" #include "io/timer.h" #include "network/multi.h" #include "network/multi_pxo.h" #include "network/gtrack.h" #include "network/ptrack.h" #include "network/multi_fstracker.h" // check structs for size compatibility SDL_COMPILE_TIME_ASSERT(game_packet_header, sizeof(game_packet_header) == 529); SDL_COMPILE_TIME_ASSERT(freespace2_net_game_data, sizeof(freespace2_net_game_data) == 120); SDL_COMPILE_TIME_ASSERT(game_list_ip4, sizeof(game_list_ip4) == 384); SDL_COMPILE_TIME_ASSERT(game_list_ip6, sizeof(game_list_ip6) == 504); SDL_COMPILE_TIME_ASSERT(in6_addr, sizeof(in6_addr) == 16); SDL_COMPILE_TIME_ASSERT(filter_game_list_struct, sizeof(filter_game_list_struct) == 40); //Variables static SOCKADDR_STORAGE gtrackaddr; static game_list GameBuffer[MAX_GAME_BUFFERS]; static int GameType;//d3 or fs static time_t LastTrackerUpdate; static unsigned int LastSentToTracker; static unsigned int TrackerAckdUs; static unsigned int TrackerGameIsRunning; static game_packet_header TrackerGameData; static game_packet_header GameListReq; static game_packet_header TrackAckPacket; static game_packet_header GameOverPacket; static freespace2_net_game_data *FreeSpace2TrackerGameData; //Start New 7-9-98 static unsigned int LastGameOverPacket; static unsigned int FirstGameOverPacket; static int SendingGameOver; //End New 7-9-98 static int SerializeGamePacket(const game_packet_header *gph, ubyte *data) { unsigned int packet_size = 0; PXO_ADD_UINT(gph->len); PXO_ADD_DATA(gph->game_type); //PXO_ADD_SHORT(gph->game_id); // not used except with fs2open, part of 'junk' PXO_ADD_SHORT(Multi_fs_tracker_game_id); // just set here instead of 15 other places PXO_ADD_DATA(gph->junk); // not used, basically just padding for compatibility PXO_ADD_INT(gph->type); PXO_ADD_UINT(gph->sig); switch (gph->type) { // these have no other data case GNT_CLIENT_ACK: case GNT_GAMEOVER: break; // this one may or may not have extra data case GNT_GAMELIST_REQ: { if (gph->len > GAME_HEADER_ONLY_SIZE) { Assert(gph->len == (GAME_HEADER_ONLY_SIZE+sizeof(filter_game_list_struct))); auto filter = reinterpret_cast(&gph->data); PXO_ADD_INT(filter->rank); PXO_ADD_DATA(filter->channel); PXO_ADD_DATA(filter->pad); // for sizing, so gph->len will match } break; } case GNT_GAMEUPDATE: { auto game_data = reinterpret_cast(&gph->data); PXO_ADD_DATA(game_data->game_name); PXO_ADD_INT(game_data->difficulty); PXO_ADD_INT(game_data->type); PXO_ADD_INT(game_data->state); PXO_ADD_INT(game_data->max_players); PXO_ADD_INT(game_data->current_num_players); PXO_ADD_DATA(game_data->mission_name); PXO_ADD_DATA(game_data->channel); PXO_ADD_DATA(game_data->pad); // for sizing, so gph->len will match break; } case GNT_GAME_COUNT_REQ: { Assert(gph->len == (GAME_HEADER_ONLY_SIZE+sizeof(filter_game_list_struct))); filter_game_list_struct filter; SDL_zero(filter); memcpy(filter.channel, gph->data, sizeof(filter.channel)); PXO_ADD_DATA(filter.channel); // add in junk data (ignored on server) to make packet size match PXO_ADD_INT(filter.rank); PXO_ADD_DATA(filter.pad); break; } // we shouldn't be sending any other packet types default: Int3(); break; } Assert(packet_size >= GAME_HEADER_ONLY_SIZE); Assert(packet_size == gph->len); return static_cast(packet_size); } static void DeserializeGamePacket(const ubyte *data, const int data_size, game_packet_header *gph) { int offset = 0; int i; memset(gph, 0, sizeof(game_packet_header)); // make sure we received a complete base packet if (data_size < static_cast(GAME_HEADER_ONLY_SIZE)) { gph->len = 0; gph->type = 255; // invalid = 0xff return; } PXO_GET_UINT(gph->len); PXO_GET_DATA(gph->game_type); PXO_GET_SHORT(gph->game_id); // not used except with fs2open, part of 'junk' PXO_GET_DATA(gph->junk); // not used, basically just padding for compatibility PXO_GET_INT(gph->type); PXO_GET_UINT(gph->sig); // sanity check data size to make sure we reveived all of the expected packet // (the -1 is because psnet2 pops off one byte) if (static_cast(gph->len)-1 > data_size) { gph->len = 0; gph->type = -1; return; } switch (gph->type) { case GNT_SERVER_ACK: break; case GNT_GAMELIST_DATA: { auto games = reinterpret_cast(&gph->data); PXO_GET_DATA(games->game_type); for (i = 0; i < MAX_GAME_LISTS_PER_PACKET; i++) { PXO_GET_DATA(games->game_name[i]); } PXO_GET_DATA(games->pad); // padded bytes for alignment for (i = 0; i < MAX_GAME_LISTS_PER_PACKET; i++) { PXO_GET_UINT(games->game_server[i]); } for (i = 0; i < MAX_GAME_LISTS_PER_PACKET; i++) { PXO_GET_USHORT(games->port[i]); } break; } case GNT_GAMELIST_DATA_NEW: { auto games = reinterpret_cast(&gph->data); PXO_GET_DATA(games->game_type); for (i = 0; i < MAX_GAME_LISTS_PER_PACKET; i++) { PXO_GET_DATA(games->game_name[i]); } PXO_GET_DATA(games->pad); // padded bytes for alignment for (i = 0; i < MAX_GAME_LISTS_PER_PACKET; i++) { PXO_GET_DATA(games->game_server[i]); } for (i = 0; i < MAX_GAME_LISTS_PER_PACKET; i++) { PXO_GET_USHORT(games->port[i]); } break; } case GNT_GAME_COUNT_DATA: { int n_users = 0; char channel[512]; PXO_GET_INT(n_users); SDL_strlcpy(channel, reinterpret_cast(data+offset), SDL_arraysize(channel)); offset += static_cast(strlen(channel) + 1); memcpy(gph->data, &n_users, sizeof(int)); memcpy(gph->data+sizeof(int), channel, strlen(channel)+1); break; } default: break; } Assert(offset == data_size); } int InitGameTrackerClient(int gametype) { GameType = gametype; LastTrackerUpdate = 0; switch(gametype) { case GT_FS2OPEN: TrackerGameData.len = GAME_HEADER_ONLY_SIZE+sizeof(freespace2_net_game_data); break; default: Int3(); return 0; } TrackerGameData.game_type = static_cast(gametype); //1==freespace (GT_FREESPACE), 2==D3, 3==tuberacer, etc. TrackerGameData.type = GNT_GAMEUPDATE; //Used to specify what to do ie. Add a new net game (GNT_GAMESTARTED), remove a net game (game over), etc. FreeSpace2TrackerGameData = reinterpret_cast(&TrackerGameData.data); GameListReq.game_type = static_cast(gametype); GameListReq.type = GNT_GAMELIST_REQ; GameListReq.len = GAME_HEADER_ONLY_SIZE; TrackAckPacket.game_type = static_cast(gametype); TrackAckPacket.len = GAME_HEADER_ONLY_SIZE; TrackAckPacket.type = GNT_CLIENT_ACK; GameOverPacket.game_type = static_cast(gametype); GameOverPacket.len = GAME_HEADER_ONLY_SIZE; GameOverPacket.type = GNT_GAMEOVER; // This would be a good place to resolve the IP based on a domain name psnet_get_addr(Multi_options_g.game_tracker_ip, GAMEPORT, >rackaddr, ADDR_FLAG_PREFER_IPV4); SDL_zero(GameBuffer); //Start New 7-9-98 SendingGameOver = 0; //End New 7-9-98 return 1; } void IdleGameTracker() { fd_set read_fds; struct timeval timeout; ubyte packet_data[sizeof(game_packet_header)]; int packet_length = 0; PSNET_TOP_LAYER_PROCESS(); timeout.tv_sec=0; timeout.tv_usec=0; if((TrackerGameIsRunning) && ((time(nullptr)-LastTrackerUpdate)>TRACKER_UPDATE_INTERVAL) && !SendingGameOver) { //Time to update the tracker again packet_length = SerializeGamePacket(&TrackerGameData, packet_data); SENDTO(Psnet_socket, reinterpret_cast(&packet_data), packet_length, 0, reinterpret_cast(>rackaddr), sizeof(gtrackaddr), PSNET_TYPE_GAME_TRACKER); TrackerAckdUs = 0; LastTrackerUpdate = time(nullptr); } else if((TrackerGameIsRunning)&&(!TrackerAckdUs)&&((timer_get_milliseconds()-LastSentToTracker)>TRACKER_RESEND_TIME)) { //We still haven't been acked by the last packet and it's time to resend. packet_length = SerializeGamePacket(&TrackerGameData, packet_data); SENDTO(Psnet_socket, reinterpret_cast(&packet_data), packet_length, 0, reinterpret_cast(>rackaddr), sizeof(gtrackaddr), PSNET_TYPE_GAME_TRACKER); TrackerAckdUs = 0; LastTrackerUpdate = time(nullptr); LastSentToTracker = timer_get_milliseconds(); } //Start New 7-9-98 if(SendingGameOver){ if((timer_get_milliseconds()-LastGameOverPacket)>TRACKER_RESEND_TIME){ //resend packet_length = SerializeGamePacket(&GameOverPacket, packet_data); LastGameOverPacket = timer_get_milliseconds(); SENDTO(Psnet_socket, reinterpret_cast(&packet_data), packet_length, 0, reinterpret_cast(>rackaddr), sizeof(gtrackaddr), PSNET_TYPE_GAME_TRACKER); } /* else if((timer_get_milliseconds()-FirstGameOverPacket)>NET_ACK_TIMEOUT) { //Giving up, it timed out. SendingGameOver = 2; } */ } //End New 7-9-98 //Check for incoming FD_ZERO(&read_fds); // NOLINT FD_SET(Psnet_socket, &read_fds); if(SELECT(static_cast(Psnet_socket+1),&read_fds,nullptr,nullptr,&timeout, PSNET_TYPE_GAME_TRACKER)) { int bytesin; int addrsize; SOCKADDR_STORAGE fromaddr; game_packet_header inpacket; SDL_zero(inpacket); addrsize = sizeof(fromaddr); bytesin = RECVFROM(Psnet_socket, reinterpret_cast(&packet_data), sizeof(game_packet_header), 0, reinterpret_cast(&fromaddr), &addrsize, PSNET_TYPE_GAME_TRACKER); if (bytesin > 0) { DeserializeGamePacket(packet_data, bytesin, &inpacket); // subtract one from the header inpacket.len--; #ifndef NDEBUG } else { int wserr=WSAGetLastError(); mprintf(("RECVFROM() failure. WSAGetLastError() returned %d\n",wserr)); #endif } //Check to make sure the packets ok if ( (bytesin > 0) && (bytesin == (int)inpacket.len) ) { switch(inpacket.type) { case GNT_SERVER_ACK: //The server got our packet so we can stop sending now TrackerAckdUs = 1; // 7/13/98 -- because of the FreeSpace iterative frame process -- set this value to 0, instead // of to 2 (as it originally was) since we call SendGameOver() only once. Once we get the ack // from the server, we can assume that we are done. // need to mark this as 0 SendingGameOver = 0; break; case GNT_GAMELIST_DATA: case GNT_GAMELIST_DATA_NEW: int i; //Woohoo! Game data! put it in the buffer (if one's free) for(i=0;i(&inpacket.data); game_list_ip6 gl6; in_addr addr; gl6.game_type = gl4->game_type; memcpy(&gl6.game_name, &gl4->game_name, sizeof(gl6.game_name)); memcpy(&gl6.port, &gl4->port, sizeof(gl6.port)); for (int j = 0; j < MAX_GAME_LISTS_PER_PACKET; j++) { addr.s_addr = gl4->game_server[j]; psnet_map4to6(&addr, &gl6.game_server[j]); } memcpy(&GameBuffer[i], &gl6, sizeof(game_list)); } else { memcpy(&GameBuffer[i], &inpacket.data, sizeof(game_list)); } i=MAX_GAME_BUFFERS+1; } } break; case GNT_GAME_COUNT_DATA: //Here, inpacket.data contains the following structure //struct { // int numusers; // char channel[];//Null terminated // } //You can add whatever code, or callback, etc. you need to deal with this data // let the PXO screen know about this data int num_servers; char channel[512]; // get the user count memcpy(&num_servers,inpacket.data,sizeof(int)); // copy the channel name SDL_strlcpy(channel, inpacket.data+sizeof(int), SDL_arraysize(channel)); // send it to the PXO screen multi_pxo_channel_count_update(channel,num_servers); break; } AckPacket(inpacket.sig); } } } void UpdateGameData(void *buffer) { SendingGameOver = 0; switch(GameType){ case GT_FS2OPEN: memcpy(FreeSpace2TrackerGameData,buffer,sizeof(freespace2_net_game_data)); break; default: Int3(); break; } // forces update LastTrackerUpdate = 0; } game_list * GetGameList() { static game_list gl; for (auto &game : GameBuffer) { if (game.game_type != GT_UNUSED) { memcpy(&gl, &game, sizeof(game_list)); game.game_type = GT_UNUSED; return ≷ } } return nullptr; } void RequestGameList() { ubyte packet_data[sizeof(game_packet_header)]; int packet_length = 0; GameListReq.len = GAME_HEADER_ONLY_SIZE; packet_length = SerializeGamePacket(&GameListReq, packet_data); SENDTO(Psnet_socket, reinterpret_cast(&packet_data), packet_length, 0, reinterpret_cast(>rackaddr), sizeof(gtrackaddr), PSNET_TYPE_GAME_TRACKER); } void RequestGameListWithFilter(void *filter) { ubyte packet_data[sizeof(game_packet_header)]; int packet_length = 0; memcpy(&GameListReq.data,filter,sizeof(filter_game_list_struct)); GameListReq.len = GAME_HEADER_ONLY_SIZE+sizeof(filter_game_list_struct); packet_length = SerializeGamePacket(&GameListReq, packet_data); SENDTO(Psnet_socket, reinterpret_cast(&packet_data), packet_length, 0, reinterpret_cast(>rackaddr), sizeof(gtrackaddr), PSNET_TYPE_GAME_TRACKER); } /* REPLACED BELOW void SendGameOver() { TrackerGameIsRunning = 0; sendto(gamesock,(const char *)&GameOverPacket,GameOverPacket.len,0,(SOCKADDR *)>rackaddr,sizeof(SOCKADDR_IN)); } */ //Start New 7-9-98 int SendGameOver() { ubyte packet_data[sizeof(game_packet_header)]; int packet_length = 0; if(SendingGameOver==2) { SendingGameOver = 0; return 1; } if(SendingGameOver==1) { //Wait until it's sent. IdleGameTracker(); return 0; } if(SendingGameOver==0) { LastGameOverPacket = timer_get_milliseconds(); FirstGameOverPacket = timer_get_milliseconds(); SendingGameOver = 1; TrackerGameIsRunning = 0; packet_length = SerializeGamePacket(&GameOverPacket, packet_data); SENDTO(Psnet_socket, reinterpret_cast(&packet_data), packet_length, 0, reinterpret_cast(>rackaddr), sizeof(gtrackaddr), PSNET_TYPE_GAME_TRACKER); return 0; } return 0; } //End New 7-9-98 void AckPacket(int sig) { ubyte packet_data[sizeof(game_packet_header)]; int packet_length = 0; TrackAckPacket.sig = sig; packet_length = SerializeGamePacket(&TrackAckPacket, packet_data); SENDTO(Psnet_socket, reinterpret_cast(&packet_data), packet_length, 0, reinterpret_cast(>rackaddr), sizeof(gtrackaddr), PSNET_TYPE_GAME_TRACKER); } void StartTrackerGame(void *buffer) { SendingGameOver = 0; switch(GameType){ case GT_FS2OPEN: memcpy(FreeSpace2TrackerGameData,buffer,sizeof(freespace2_net_game_data)); break; default: Int3(); break; } TrackerGameIsRunning = 1; LastTrackerUpdate = 0; } //A new function void RequestGameCountWithFilter(void *filter) { game_packet_header GameCountReq; ubyte packet_data[sizeof(game_packet_header)]; int packet_length = 0; GameCountReq.game_type = GT_FS2OPEN; GameCountReq.type = GNT_GAME_COUNT_REQ; GameCountReq.len = GAME_HEADER_ONLY_SIZE+sizeof(filter_game_list_struct); memcpy(&GameCountReq.data, reinterpret_cast(filter)->channel, CHANNEL_LEN); packet_length = SerializeGamePacket(&GameCountReq, packet_data); SENDTO(Psnet_socket, reinterpret_cast(&packet_data), packet_length, 0, reinterpret_cast(>rackaddr), sizeof(gtrackaddr), PSNET_TYPE_GAME_TRACKER); }