Files

743 lines
19 KiB
C++

/*
* 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.
*
*/
//Pilot tracker client code
#ifndef _WIN32
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cerrno>
#include <netdb.h>
#endif
#ifdef SCP_BSD
#include <sys/socket.h>
#endif
#include "globalincs/pstypes.h"
#include "io/timer.h"
#include "network/multi.h"
#include "network/ptrack.h"
#include "network/psnet2.h"
#include "network/multi_fstracker.h"
// check structs for size compatibility
SDL_COMPILE_TIME_ASSERT(udp_packet_header, sizeof(udp_packet_header) == 497);
SDL_COMPILE_TIME_ASSERT(vmt_fs2open_struct, sizeof(vmt_fs2open_struct) == 480);
SDL_COMPILE_TIME_ASSERT(validate_id_request, sizeof(validate_id_request) == 60);
SDL_COMPILE_TIME_ASSERT(squad_war_request, sizeof(squad_war_request) == 104);
SDL_COMPILE_TIME_ASSERT(squad_war_response, sizeof(squad_war_response) == 256);
SDL_COMPILE_TIME_ASSERT(squad_war_result, sizeof(squad_war_result) == 72);
SDL_COMPILE_TIME_ASSERT(pilot_request, sizeof(pilot_request) == 32);
//Variables
static SOCKADDR_STORAGE ptrackaddr;
static int FSWriteState;
static int FSReadState;
static unsigned int FSLastSentWrite;
static unsigned int FSFirstSentWrite;
static unsigned int FSLastSent;
static unsigned int FSFirstSent;
static int SWWriteState;
static unsigned int SWLastSentWrite;
static unsigned int SWFirstSentWrite;
static udp_packet_header fs_pilot_req, fs_pilot_write, sw_res_write;
static pilot_request *fs_pr;
static vmt_stats_struct *ReadFSPilot;
// squad war response
static squad_war_response SquadWarWriteResponse;
static int SerializePilotPacket(const udp_packet_header *uph, ubyte *data)
{
unsigned int packet_size = 0;
int i;
PXO_ADD_DATA(uph->type);
PXO_ADD_USHORT(uph->len);
PXO_ADD_UINT(uph->code);
PXO_ADD_USHORT(uph->xcode);
PXO_ADD_UINT(uph->sig);
PXO_ADD_UINT(uph->security);
switch (uph->type) {
// no extra data for this
case UNT_CONTROL:
break;
case UNT_PILOT_DATA_WRITE_NEW: {
auto fsos = reinterpret_cast<const vmt_stats_struct *>(&uph->data);
PXO_ADD_INT(fsos->tracker_id);
PXO_ADD_DATA(fsos->pilot_name);
PXO_ADD_INT(fsos->score);
PXO_ADD_INT(fsos->rank);
PXO_ADD_INT(fsos->assists);
PXO_ADD_INT(fsos->kill_count);
PXO_ADD_INT(fsos->kill_count_ok);
PXO_ADD_UINT(fsos->p_shots_fired);
PXO_ADD_UINT(fsos->s_shots_fired);
PXO_ADD_UINT(fsos->p_shots_hit);
PXO_ADD_UINT(fsos->s_shots_hit);
PXO_ADD_UINT(fsos->p_bonehead_hits);
PXO_ADD_UINT(fsos->s_bonehead_hits);
PXO_ADD_INT(fsos->bonehead_kills);
PXO_ADD_UINT(fsos->missions_flown);
PXO_ADD_UINT(fsos->flight_time);
PXO_ADD_UINT(fsos->last_flown);
PXO_ADD_INT(fsos->security);
PXO_ADD_UINT(fsos->checksum);
PXO_ADD_DATA(fsos->virgin_pilot);
// skip 1-byte pad
PXO_ADD_DATA(fsos->num_medals);
PXO_ADD_DATA(fsos->num_ships);
const int count = std::min(static_cast<unsigned short>(fsos->num_medals + fsos->num_ships), MAX_FS2OPEN_COUNTS);
for (i = 0; i < count; i++) {
PXO_ADD_USHORT(fsos->counts[i]);
}
break;
}
case UNT_SW_RESULT_WRITE: {
auto sw_result = reinterpret_cast<const squad_war_result *>(&uph->data);
PXO_ADD_DATA(sw_result->match_code);
PXO_ADD_DATA(sw_result->result);
PXO_ADD_DATA(sw_result->squad_count1);
PXO_ADD_DATA(sw_result->squad_count2);
PXO_ADD_DATA(sw_result->pad); // junk, for size/alignment
for (i = 0; i < MAX_SQUAD_PLAYERS; i++) {
PXO_ADD_INT(sw_result->squad_winners[i]);
}
for (i = 0; i < MAX_SQUAD_PLAYERS; i++) {
PXO_ADD_INT(sw_result->squad_losers[i]);
}
break;
}
case UNT_PILOT_DATA_READ_NEW:
case UNT_PILOT_DATA_READ: {
auto pr = reinterpret_cast<const pilot_request *>(&uph->data);
PXO_ADD_DATA(pr->pilot_name);
PXO_ADD_DATA(pr->tracker_id);
PXO_ADD_DATA(pr->pad); // junk, for size/alignment
break;
}
// we shouldn't be sending any other packet types
default:
Int3();
break;
}
Assert(packet_size >= PACKED_HEADER_ONLY_SIZE);
Assert(packet_size == uph->len);
return static_cast<int>(packet_size);
}
static void DeserializePilotPacket(const ubyte *data, const int data_size, udp_packet_header *uph)
{
int offset = 0;
int i;
memset(uph, 0, sizeof(udp_packet_header));
// make sure we received a complete base packet
if (data_size < static_cast<int>(PACKED_HEADER_ONLY_SIZE)) {
uph->len = 0;
uph->type = 0xff;
return;
}
PXO_GET_DATA(uph->type);
PXO_GET_USHORT(uph->len);
PXO_GET_UINT(uph->code);
PXO_GET_USHORT(uph->xcode);
PXO_GET_UINT(uph->sig);
PXO_GET_UINT(uph->security);
// 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<int>(uph->len)-1 > data_size) {
uph->len = 0;
uph->type = 0xff;
return;
}
switch (uph->type) {
// no extra data for these
case UNT_PILOT_READ_FAILED:
case UNT_PILOT_WRITE_SUCCESS:
case UNT_PILOT_WRITE_FAILED:
break;
case UNT_PILOT_DATA_RESPONSE: {
auto fsos = reinterpret_cast<vmt_stats_struct *>(&uph->data);
PXO_GET_INT(fsos->tracker_id);
PXO_GET_DATA(fsos->pilot_name);
PXO_GET_INT(fsos->score);
PXO_GET_INT(fsos->rank);
PXO_GET_INT(fsos->assists);
PXO_GET_INT(fsos->kill_count);
PXO_GET_INT(fsos->kill_count_ok);
PXO_GET_UINT(fsos->p_shots_fired);
PXO_GET_UINT(fsos->s_shots_fired);
PXO_GET_UINT(fsos->p_shots_hit);
PXO_GET_UINT(fsos->s_shots_hit);
PXO_GET_UINT(fsos->p_bonehead_hits);
PXO_GET_UINT(fsos->s_bonehead_hits);
PXO_GET_INT(fsos->bonehead_kills);
PXO_GET_UINT(fsos->missions_flown);
PXO_GET_UINT(fsos->flight_time);
PXO_GET_UINT(fsos->last_flown);
PXO_GET_INT(fsos->security);
PXO_GET_UINT(fsos->checksum);
PXO_GET_DATA(fsos->virgin_pilot);
// skip 1-byte pad
PXO_GET_DATA(fsos->num_medals);
PXO_GET_DATA(fsos->num_ships);
const int count = std::min(static_cast<unsigned short>(fsos->num_medals + fsos->num_ships), MAX_FS2OPEN_COUNTS);
for (i = 0; i < count; i++) {
PXO_GET_USHORT(fsos->counts[i]);
}
break;
}
case UNT_SW_RESULT_RESPONSE: {
auto sw_resp = reinterpret_cast<squad_war_response *>(&uph->data);
PXO_GET_DATA(sw_resp->reason);
PXO_GET_DATA(sw_resp->accepted);
break;
}
default:
break;
}
//Assert(offset == data_size);
}
int InitPilotTrackerClient()
{
FSWriteState = STATE_IDLE;
FSReadState = STATE_IDLE;
SWWriteState = STATE_IDLE;
ReadFSPilot = nullptr;
fs_pr = reinterpret_cast<pilot_request *>(&fs_pilot_req.data);
psnet_get_addr(Multi_options_g.user_tracker_ip, REGPORT, &ptrackaddr);
return 1;
}
// Returns:
// -3 Error -- Called with NULL, but no request is waiting
// -2 Error -- Already sending data (hasn't timed out yet)
// -1 Timeout trying to send pilot data
// 0 Sending
// 1 Data succesfully sent
// 2 Send Cancelled (data may still have been written already, we just haven't been ACK'd yet)
// 3 Pilot not written (for some reason)
// Call with NULL to poll
// Call with -1 to cancel send
// Call with valid pointer to a vmt_descent3_struct to initiate send
int SendFSPilotData(vmt_stats_struct *fs_pilot)
{
//First check the network
PollPTrackNet();
if(fs_pilot == nullptr)
{
if(FSWriteState == STATE_IDLE)
{
return -3;
}
if(FSWriteState == STATE_SENDING_PILOT)
{
return 0;
}
if(FSWriteState == STATE_WROTE_PILOT)
{
//We wrote this pilot, and now we are about to inform the app, so back to idle
FSWriteState = STATE_IDLE;
return 1;
}
if(FSWriteState == STATE_TIMED_OUT)
{
//We gave up on sending this pilot, and now we are about to inform the app, so back to idle
FSWriteState = STATE_IDLE;
return -1;
}
if(FSWriteState == STATE_WRITE_PILOT_FAILED)
{
//The tracker said this dude couldn't be written
FSWriteState = STATE_IDLE;
return 3;
}
}
else if(fs_pilot == reinterpret_cast<vmt_stats_struct *>(static_cast<uintptr_t>(0xffffffff)))
{
if(FSWriteState == STATE_IDLE)
{
return -3;
}
else
{
//Cancel this baby
FSWriteState = STATE_IDLE;
return 2;
}
}
else if(FSWriteState == STATE_IDLE)
{
//New request, send out the req, and go for it.
FSWriteState = STATE_SENDING_PILOT;
FSLastSentWrite = 0;
FSFirstSentWrite = timer_get_milliseconds();
// deal with variable length of vmt_fs2open_struct
const unsigned short size_offset = (MAX_FS2OPEN_COUNTS - (fs_pilot->num_ships + fs_pilot->num_medals)) * sizeof(unsigned short);
fs_pilot_write.type = UNT_PILOT_DATA_WRITE_NEW;
fs_pilot_write.code = CMD_GAME_FS2OPEN;
fs_pilot_write.xcode = static_cast<unsigned short>(Multi_fs_tracker_game_id);
fs_pilot_write.len = PACKED_HEADER_ONLY_SIZE+STATS_BLOCK_SIZE-size_offset;
memcpy(&fs_pilot_write.data,fs_pilot,sizeof(vmt_stats_struct));
return 0;
}
return -2;
}
// Returns:
// -3 Error -- Called with NULL, but no request is waiting
// -2 Error -- Already sending data (hasn't timed out yet)
// -1 Timeout trying to send pilot data
// 0 Sending
// 1 Data succesfully sent
// 2 Send Cancelled (data may still have been written already, we just haven't been ACK'd yet)
// 3 Pilot not written (for some reason)
// Call with NULL to poll
// Call with -1 to cancel send
// Call with valid pointer to a vmt_descent3_struct to initiate send
int SendSWData(squad_war_result *sw_res, squad_war_response *sw_resp)
{
//First check the network
PollPTrackNet();
if(sw_res == nullptr){
if(SWWriteState == STATE_IDLE){
return -3;
}
if(SWWriteState == STATE_SENDING_PILOT){
return 0;
}
// fill in the response
if(SWWriteState == STATE_WROTE_PILOT){
// We wrote this pilot, and now we are about to inform the app, so back to idle
SWWriteState = STATE_IDLE;
if(sw_resp != nullptr){
memcpy(sw_resp, &SquadWarWriteResponse, sizeof(squad_war_response));
}
return 1;
}
// fill in the response
if(SWWriteState == STATE_WRITE_PILOT_FAILED){
// The tracker said this dude couldn't be written
SWWriteState = STATE_IDLE;
if(sw_resp != nullptr){
memcpy(sw_resp, &SquadWarWriteResponse, sizeof(squad_war_response));
}
return 3;
}
if(SWWriteState == STATE_TIMED_OUT){
// We gave up on sending this pilot, and now we are about to inform the app, so back to idle
SWWriteState = STATE_IDLE;
return -1;
}
} else if(sw_res == reinterpret_cast<squad_war_result *>(static_cast<uintptr_t>(0xffffffff))){
if(SWWriteState == STATE_IDLE){
return -3;
} else {
// Cancel this baby
SWWriteState = STATE_IDLE;
return 2;
}
} else if(SWWriteState == STATE_IDLE) {
//New request, send out the req, and go for it.
SWWriteState = STATE_SENDING_PILOT;
SWLastSentWrite = 0;
SWFirstSentWrite = timer_get_milliseconds();
sw_res_write.len = PACKED_HEADER_ONLY_SIZE+sizeof(squad_war_result);
sw_res_write.type = UNT_SW_RESULT_WRITE;
sw_res_write.code = CMD_GAME_FS2OPEN;
memcpy(&sw_res_write.data, sw_res, sizeof(squad_war_result));
return 0;
}
return -2;
}
// Returns:
// -3 Error -- Called with NULL, but no request is waiting
// -2 Error -- Already waiting on data (hasn't timed out yet)
// -1 Timeout waiting for pilot data
// 0 Waiting for data
// 1 Data received
// 2 Get Cancelled
// 3 Pilot not found
// Call with NULL to poll
// Call with -1 to cancel wait
// Call with valid pointer to a vmt_descent3_struct to get a response
int GetFSPilotData(vmt_stats_struct *fs_pilot, const char *pilot_name, const char *tracker_id, int get_security)
{
//First check the network
PollPTrackNet();
if(fs_pilot == nullptr)
{
if(FSReadState == STATE_IDLE)
{
return -3;
}
if(FSReadState == STATE_READING_PILOT)
{
return 0;
}
if(FSReadState == STATE_RECEIVED_PILOT)
{
// We got this pilot, and now we are about to inform the app, so back to idle
FSReadState = STATE_IDLE;
ReadFSPilot = nullptr;
return 1;
}
if(FSReadState == STATE_TIMED_OUT)
{
// We gave up on this pilot, and now we are about to inform the app, so back to idle
FSReadState = STATE_IDLE;
ReadFSPilot = nullptr;
return -1;
}
if(FSReadState == STATE_PILOT_NOT_FOUND)
{
//The tracker said this dude is not found.
FSReadState = STATE_IDLE;
ReadFSPilot = nullptr;
return 3;
}
}
else if(fs_pilot == reinterpret_cast<vmt_stats_struct *>(static_cast<uintptr_t>(0xffffffff)))
{
if(FSReadState == STATE_IDLE)
{
return -3;
}
else
{
//Cancel this baby
FSReadState = STATE_IDLE;
ReadFSPilot = nullptr;
return 2;
}
}
else if(FSReadState == STATE_IDLE)
{
//New request, send out the req, and go for it.
FSReadState = STATE_READING_PILOT;
ReadFSPilot = fs_pilot;
FSLastSent = 0;
FSFirstSent = timer_get_milliseconds();
fs_pilot_req.xcode = static_cast<unsigned short>(Multi_fs_tracker_game_id);
fs_pilot_req.len = PACKED_HEADER_ONLY_SIZE+sizeof(pilot_request);
if(get_security){
fs_pilot_req.type = UNT_PILOT_DATA_READ_NEW;
} else {
fs_pilot_req.type = UNT_PILOT_DATA_READ;
}
fs_pilot_req.code = CMD_GAME_FS2OPEN;
SDL_strlcpy(fs_pr->pilot_name, pilot_name, SDL_arraysize(fs_pr->pilot_name));
SDL_strlcpy(fs_pr->tracker_id, tracker_id, SDL_arraysize(fs_pr->tracker_id));
return 0;
}
return -2;
}
// Send an ACK to the server
void AckServer(unsigned int sig)
{
udp_packet_header ack_pack;
ubyte packet_data[sizeof(udp_packet_header)];
int packet_length = 0;
ack_pack.type = UNT_CONTROL;
ack_pack.sig = sig;
ack_pack.code = CMD_CLIENT_RECEIVED;
ack_pack.len = PACKED_HEADER_ONLY_SIZE;
packet_length = SerializePilotPacket(&ack_pack, packet_data);
Assert(packet_length == PACKED_HEADER_ONLY_SIZE);
SENDTO(Psnet_socket, reinterpret_cast<char *>(&packet_data), packet_length, 0,
reinterpret_cast<LPSOCKADDR>(&ptrackaddr), sizeof(ptrackaddr), PSNET_TYPE_USER_TRACKER);
}
void IdlePTrack()
{
ubyte packet_data[sizeof(udp_packet_header)];
int packet_length = 0;
PSNET_TOP_LAYER_PROCESS();
// reading pilot data
if(FSReadState == STATE_READING_PILOT){
if((timer_get_milliseconds()-FSFirstSent)>=PILOT_REQ_TIMEOUT){
FSReadState = STATE_TIMED_OUT;
} else if((timer_get_milliseconds()-FSLastSent)>=PILOT_REQ_RESEND_TIME){
//Send 'da packet
packet_length = SerializePilotPacket(&fs_pilot_req, packet_data);
SENDTO(Psnet_socket, reinterpret_cast<char *>(&packet_data), packet_length, 0,
reinterpret_cast<LPSOCKADDR>(&ptrackaddr), sizeof(ptrackaddr), PSNET_TYPE_USER_TRACKER);
FSLastSent = timer_get_milliseconds();
}
}
// writing pilot data
if(FSWriteState == STATE_SENDING_PILOT){
if((timer_get_milliseconds()-FSFirstSentWrite)>=PILOT_REQ_TIMEOUT){
FSWriteState = STATE_TIMED_OUT;
} else if((timer_get_milliseconds()-FSLastSentWrite)>=PILOT_REQ_RESEND_TIME){
// Send 'da packet
packet_length = SerializePilotPacket(&fs_pilot_write, packet_data);
SENDTO(Psnet_socket, reinterpret_cast<char *>(&packet_data), packet_length, 0,
reinterpret_cast<LPSOCKADDR>(&ptrackaddr), sizeof(ptrackaddr), PSNET_TYPE_USER_TRACKER);
FSLastSentWrite = timer_get_milliseconds();
}
}
// writing squad war results
if(SWWriteState == STATE_SENDING_PILOT){
if((timer_get_milliseconds()-SWFirstSentWrite) >= PILOT_REQ_TIMEOUT){
SWWriteState = STATE_TIMED_OUT;
} else if((timer_get_milliseconds()-SWLastSentWrite) >= PILOT_REQ_RESEND_TIME){
// Send 'da packet
packet_length = SerializePilotPacket(&sw_res_write, packet_data);
SENDTO(Psnet_socket, reinterpret_cast<char *>(&packet_data), packet_length, 0,
reinterpret_cast<LPSOCKADDR>(&ptrackaddr), sizeof(ptrackaddr), PSNET_TYPE_USER_TRACKER);
SWLastSentWrite = timer_get_milliseconds();
}
}
}
void PollPTrackNet()
{
fd_set read_fds;
struct timeval timeout;
ubyte packet_data[sizeof(udp_packet_header)];
IdlePTrack();
timeout.tv_sec=0;
timeout.tv_usec=0;
FD_ZERO(&read_fds); // NOLINT
FD_SET(Psnet_socket, &read_fds);
if(SELECT(static_cast<int>(Psnet_socket+1), &read_fds,nullptr,nullptr,&timeout, PSNET_TYPE_USER_TRACKER)){
int bytesin;
int addrsize;
SOCKADDR_STORAGE fromaddr;
udp_packet_header inpacket;
SDL_zero(inpacket);
addrsize = sizeof(fromaddr);
bytesin = RECVFROM(Psnet_socket, reinterpret_cast<char *>(&packet_data), sizeof(udp_packet_header), 0,
reinterpret_cast<LPSOCKADDR>(&fromaddr), &addrsize, PSNET_TYPE_USER_TRACKER);
if (bytesin > 0) {
DeserializePilotPacket(packet_data, bytesin, &inpacket);
// decrease packet size by 1
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 == inpacket.len) ) {
switch(inpacket.type){
case UNT_PILOT_DATA_RESPONSE:
if(inpacket.code == CMD_GAME_FS2OPEN){
if(FSReadState == STATE_READING_PILOT){
vmt_stats_struct *stats;
// 9/17/98 MWA. Compare the tracker id of this packet with the tracker id of
// what we are expecting. Only set our state to something different when
// the tracker id's match. This fixes possible multiple packets for a single pilto
// accidentally getting set for the wrong pilot
stats = reinterpret_cast<vmt_stats_struct *>(&inpacket.data);
if ( stats->tracker_id == SDL_atoi(fs_pr->tracker_id) ) {
//Copy the data
memcpy(ReadFSPilot,&inpacket.data,sizeof(vmt_stats_struct));
//Set the state
FSReadState = STATE_RECEIVED_PILOT;
}
}
} else {
Int3();
}
break;
case UNT_PILOT_READ_FAILED:
if(inpacket.code == CMD_GAME_FS2OPEN){
if(FSReadState == STATE_READING_PILOT){
FSReadState = STATE_PILOT_NOT_FOUND;
}
} else {
Int3();
}
break;
case UNT_PILOT_WRITE_SUCCESS:
if(inpacket.code == CMD_GAME_FS2OPEN){
if(FSWriteState == STATE_SENDING_PILOT){
FSWriteState = STATE_WROTE_PILOT;
}
} else {
Int3();
}
break;
case UNT_PILOT_WRITE_FAILED:
if(inpacket.code == CMD_GAME_FS2OPEN){
if(FSWriteState == STATE_SENDING_PILOT){
FSWriteState = STATE_WRITE_PILOT_FAILED;
}
} else {
Int3();
}
break;
case UNT_SW_RESULT_RESPONSE:
if(SWWriteState == STATE_SENDING_PILOT){
// copy the data
Assert((bytesin - PACKED_HEADER_ONLY_SIZE) == sizeof(squad_war_response));
if((bytesin - PACKED_HEADER_ONLY_SIZE) == sizeof(squad_war_response)){
memset(&SquadWarWriteResponse, 0, sizeof(squad_war_response));
memcpy(&SquadWarWriteResponse, inpacket.data, sizeof(squad_war_response));
// now check to see if we're good
if(SquadWarWriteResponse.accepted){
SWWriteState = STATE_WROTE_PILOT;
} else {
SWWriteState = STATE_WRITE_PILOT_FAILED;
}
} else {
SWWriteState = STATE_WRITE_PILOT_FAILED;
}
}
break;
case UNT_CONTROL:
Int3();
break;
case UNT_CONTROL_VALIDATION:
Int3();
break;
default:
break;
}
AckServer(inpacket.sig);
}
}
}