/*
 * Descent 3
 * Copyright (C) 2024 Parallax Software
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * $Logfile: /DescentIII/Main/mtclient/mtpilottracker.cpp $
 * $Revision: 1.1.1.1 $
 * $Date: 2003-08-26 03:58:40 $
 * $Author: kevinb $
 *
 * Pilot tracker client code
 *
 * $Log: not supported by cvs2svn $
 *
 * 15    10/28/99 3:13p Kevin
 * fixed major badness with byte ordering 'fix'
 *
 * 14    9/02/99 3:16p Kevin
 * Macintosh byte ordering fixes
 *
 * 13    9/01/99 5:24p Kevin
 * Macintosh byte ordering problems
 *
 * 12    7/21/99 9:11a Kevin
 * bandwidth reduction act #1
 *
 * 11    4/21/99 11:15a Kevin
 * sectret code to tell the differences between oem, demo and retail
 * builds on the tracker
 *
 * 10    4/19/99 8:36p Jeff
 * Compile under Linux
 *
 * 9     3/02/99 5:50p Kevin
 * Ouch. Duplicate structures existed and were conflicting.
 *
 * 8     1/11/99 6:02p Kevin
 * Fixed warning
 *
 * 7     12/23/98 6:38p Kevin
 * All UDP data (except gamespy) now uses one (registered) port number
 *
 * 6     11/03/98 9:27a Kevin
 * Added PXO message of the day
 *
 * 5     10/30/98 11:25a Kevin
 * Message of the day for PXO
 *
 * 4     8/31/98 10:14a Kevin
 * Misc. multi-UI fixes
 *
 * 3     8/25/98 6:33p Kevin
 * PXO screens
 *
 * 2     6/01/98 10:10a Kevin
 * Added DLL connection interface and auto update DLL
 *
 * 1     5/25/98 4:12p Kevin
 *
 * 10    5/14/98 6:08p Kevin
 * fixed encryption bug
 *
 * 9     5/14/98 5:19p Kevin
 * Encrypted pilot data
 *
 * 8     5/07/98 9:47a Kevin
 * Made pilot reading more robust
 *
 * 7     5/04/98 1:22p Kevin
 * Mastertracker
 *
 * 6     5/04/98 12:53p Kevin
 * Fixed mastertracker resend stuff
 *
 * 5     5/01/98 12:28p Kevin
 * Added new user registration button for MT
 *
 * 4     4/30/98 3:50p Kevin
 * Mastertracker pilot stats
 *
 * 3     4/28/98 12:51p Kevin
 * Master tracker login
 *
 * 2     4/27/98 7:12p Kevin
 * Mastertracker pilot tracker
 *
 * 1     4/27/98 5:29p Kevin
 * Initial Version
 *
 * $NoKeywords: $
 */

#ifdef WIN32
#include <windows.h>
#endif

#include <cstdlib>
#include <cstring>

#include "pstypes.h"
#include "networking.h"
#include "mt_net.h"
#include "byteswap.h"

#define LOGIN_LEN 33
#define REAL_NAME_LEN 66
#define PASSWORD_LEN 17
#define EMAIL_LEN 100
#define TRACKER_ID_LEN 10
#define PILOT_NAME_LEN 20

#ifdef WIN32
#pragma pack(push, pxo)
#endif
#pragma pack(1)
struct vmt_descent3_struct {
  char tracker_id[TRACKER_ID_LEN];
  char pilot_name[PILOT_NAME_LEN];
  int rank;

  int kills;
  int deaths;
  int suicides;
  int online_time;
  int games_played;
  uint32_t security;
  uint8_t virgin_pilot; // This pilot was just created if TRUE
  uint32_t lateral_thrust;
  uint32_t rotational_thrust;
  uint32_t sliding_pct; // Percentage of the time you were sliding
  uint32_t checksum;    // This value needs to be equal to whatever the checksum is once the packet is decoded
  uint32_t pad;         // just to provide room for out 4 byte encryption boundry only needed on the client side for now
};
#define DESCENT3_BLOCK_SIZE (sizeof(vmt_descent3_struct) - 4)
#ifdef WIN32
#pragma pack(pop, pxo)
#else
#pragma pack()
#endif

#include "mtpilottrack.h"

#define DLLmprintf(...) DLLDebug_ConsolePrintf(__VA_ARGS__)

typedef void (*Debug_ConsolePrintf_fp)(int n, const char *format, ...);

extern Debug_ConsolePrintf_fp DLLDebug_ConsolePrintf;

typedef float (*timer_GetTime_fp)();
extern timer_GetTime_fp DLLtimer_GetTime;

typedef void (*HandlePilotData_fp)(uint8_t *data, int len, network_address *from);

typedef int (*nw_RegisterCallback_fp)(HandlePilotData_fp, uint8_t id);
extern nw_RegisterCallback_fp DLLnw_RegisterCallback;

typedef int (*nw_DoReceiveCallbacks_fp)();
extern nw_DoReceiveCallbacks_fp DLLnw_DoReceiveCallbacks;

#define VALID_STATE_IDLE 1
#define VALID_STATE_WAITING 2
#define VALID_STATE_VALID 3
#define VALID_STATE_INVALID 4
#define VALID_STATE_TIMEOUT 5

#define VERSION_STATE_IDLE 1
#define VERSION_STATE_WAITING 2
#define VERSION_STATE_RECEIVED 3
#define VERSION_STATE_TIMEOUT 5

#define STATE_WAITING_MOTD 8
#define STATE_HAVE_MOTD 9

#define MAX_MOTDLEN 1000
// Variables

int D3WriteState;
int D3ReadState;
float D3LastSent;
float D3FirstSent;

float D3LastSentWrite;
float D3FirstSentWrite;

udp_packet_header d3_pilot_req, d3_pilot_write, motd_req;
pilot_request *d3_pr;
vmt_descent3_struct *ReadD3Pilot;
vmt_descent3_struct WriteD3Pilot;

udp_packet_header PacketHeader, VersionPacketHeader;
validate_id_request *ValidIDReq;

int MOTDState;

float MOTDFirstSent;
float MOTDLastSent;

char *Motdptr;
uint32_t Motd_maxlen;

int ValidState;

float ValidFirstSent;
float ValidLastSent;

int VersionState;

float VersionFirstSent;
float VersionLastSent;
char *VersionURL;

uint32_t ValidSecurity;

char *Psztracker_id;

char ProccesingPilot[PILOT_NAME_LEN];

typedef int (*PollUI_fp)();
extern PollUI_fp DLLPollUI;

int InitPilotTrackerClient() {

  D3WriteState = STATE_IDLE;
  D3ReadState = STATE_IDLE;

  ReadD3Pilot = nullptr;
  ValidState = VALID_STATE_IDLE;

  VersionState = VERSION_STATE_IDLE;

  srand(DLLtimer_GetTime());
  ValidSecurity = (uint32_t)rand();
  // Build the request packet
  PacketHeader.type = UNT_LOGIN_AUTH_REQUEST; // UNT_VALIDAT_ID_REQUEST;
  PacketHeader.len = INTEL_SHORT(PACKED_HEADER_ONLY_SIZE + sizeof(validate_id_request));
  ValidIDReq = (validate_id_request *)&PacketHeader.data;

  d3_pr = (pilot_request *)&d3_pilot_req.data;

  DLLnw_RegisterCallback(HandlePilotData, PXO_NETID_USER_TRACKER);
  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 successfully sent

// Call with NULL to poll
// Call with valid pointer to a vmt_descent3_struct to initiate send
int SendD3PilotData(vmt_descent3_struct *d3_pilot) {
  // First check the network
  PollPTrackNet();

  if (d3_pilot == nullptr) {
    if (D3WriteState == STATE_IDLE) {
      return -3;
    }
    if (D3WriteState == STATE_SENDING_PILOT) {
      return 0;
    }
    if (D3WriteState == STATE_WROTE_PILOT) {
      // We wrote this pilot, and now we are about to inform the app, so back to idle
      D3WriteState = STATE_IDLE;
      return 1;
    }
    if (D3WriteState == STATE_TIMED_OUT) {
      // We gave up on sending this pilot, and now we are about to inform the app, so back to idle
      D3WriteState = STATE_IDLE;

      return -1;
    }
    if (D3WriteState == STATE_WRITE_PILOT_FAILED) {
      // The tracker said this dude couldn't be written
      D3WriteState = STATE_IDLE;

      return 3;
    }
  } else if (D3WriteState == STATE_IDLE) {
    // New request, send out the req, and go for it.

    D3WriteState = STATE_SENDING_PILOT;

    D3LastSentWrite = DLLtimer_GetTime();
    D3FirstSentWrite = DLLtimer_GetTime();

    d3_pilot_write.len = INTEL_SHORT(PACKED_HEADER_ONLY_SIZE + DESCENT3_BLOCK_SIZE);
    d3_pilot_write.type = UNT_PILOT_DATA_WRITE;
    d3_pilot_write.code = INTEL_INT(CMD_GAME_DESCENT3);
    d3_pilot_write.security = d3_pilot->security;

    d3_pilot->deaths = INTEL_INT(d3_pilot->deaths);
    d3_pilot->kills = INTEL_INT(d3_pilot->kills);
    d3_pilot->online_time = INTEL_INT(d3_pilot->online_time);
    d3_pilot->games_played = INTEL_INT(d3_pilot->games_played);
    d3_pilot->lateral_thrust = INTEL_INT(d3_pilot->lateral_thrust);
    d3_pilot->rank = INTEL_INT(d3_pilot->rank);
    d3_pilot->rotational_thrust = INTEL_INT(d3_pilot->rotational_thrust);
    d3_pilot->sliding_pct = INTEL_INT(d3_pilot->sliding_pct);
    d3_pilot->suicides = INTEL_INT(d3_pilot->suicides);

#if (!(defined(OEM) || defined(DEMO)))
    // This is the secret number we will use to determine if it's
    // a game where we want to update the player's rank or not.
    d3_pilot->lateral_thrust = INTEL_INT(0x142);
#endif
    memcpy(&d3_pilot_write.data, d3_pilot, DESCENT3_BLOCK_SIZE);

    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

// 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 GetD3PilotData(vmt_descent3_struct *d3_pilot, char *pilot_name, char *tracker_id) {
  // First check the network
  PollPTrackNet();

  if (d3_pilot == nullptr) {
    if (D3ReadState == STATE_IDLE) {
      return -3;
    }
    if (D3ReadState == STATE_READING_PILOT) {
      return 0;
    }
    if (D3ReadState == STATE_RECEIVED_PILOT) {
      // We got this pilot, and now we are about to inform the app, so back to idle
      D3ReadState = STATE_IDLE;
      ReadD3Pilot = nullptr;

      return 1;
    }
    if (D3ReadState == STATE_TIMED_OUT) {
      // We gave up on this pilot, and now we are about to inform the app, so back to idle
      D3ReadState = STATE_IDLE;
      ReadD3Pilot = nullptr;
      return -1;
    }
    if (D3ReadState == STATE_PILOT_NOT_FOUND) {
      // The tracker said this dude is not found.
      D3ReadState = STATE_IDLE;
      ReadD3Pilot = nullptr;
      return 3;
    }

  } else if (D3ReadState == STATE_IDLE) {
    // New request, send out the req, and go for it.

    D3ReadState = STATE_READING_PILOT;
    ReadD3Pilot = d3_pilot;
    D3LastSent = DLLtimer_GetTime();
    D3FirstSent = DLLtimer_GetTime(); // DLLtimer_GetTime();

    d3_pilot_req.len = INTEL_SHORT(PACKED_HEADER_ONLY_SIZE + sizeof(pilot_request));
    d3_pilot_req.type = UNT_PILOT_DATA_READ;
    d3_pilot_req.code = INTEL_INT(CMD_GAME_DESCENT3);
    strcpy(d3_pr->pilot_name, pilot_name);
    strcpy(d3_pr->tracker_id, tracker_id);
    strcpy(ProccesingPilot, pilot_name);
    return 0;
  }
  return -2;
}

int GetD3PilotDataCancel() {
  // First check the network
  PollPTrackNet();

  if (D3ReadState == STATE_IDLE) {
    return -3;
  } else {
    // Cancel this baby
    D3ReadState = STATE_IDLE;
    ReadD3Pilot = nullptr;
    return 2;
  }
}

// Send an ACK to the server
void AckServer(uint32_t sig) {
  udp_packet_header ack_pack;

  ack_pack.type = UNT_CONTROL;
  ack_pack.sig = sig;
  ack_pack.code = INTEL_INT(CMD_CLIENT_RECEIVED);
  ack_pack.len = INTEL_SHORT(PACKED_HEADER_ONLY_SIZE);

  SendPilotTrackerPacket(&ack_pack);
  // sendto(pilotsock,(char *)&ack_pack,PACKED_HEADER_ONLY_SIZE,0,(SOCKADDR *)&ptrackaddr,sizeof(SOCKADDR_IN));
}

void IdlePTrack() {
  // socket
  if (D3ReadState == STATE_READING_PILOT) {
    if ((DLLtimer_GetTime() - D3FirstSent) >= PILOT_REQ_TIMEOUT) {
      D3ReadState = STATE_TIMED_OUT;

    } else if ((DLLtimer_GetTime() - D3LastSent) >= PILOT_REQ_RESEND_TIME) {
      // Send 'da packet
      // sendto(pilotsock,(char *)&d3_pilot_req,d3_pilot_req.len,0,(SOCKADDR *)&ptrackaddr,sizeof(SOCKADDR_IN));
      SendPilotTrackerPacket(&d3_pilot_req);
      D3LastSent = DLLtimer_GetTime();
    }
  }

  if (D3WriteState == STATE_SENDING_PILOT) {
    if ((DLLtimer_GetTime() - D3FirstSentWrite) >= PILOT_REQ_TIMEOUT) {
      D3WriteState = STATE_TIMED_OUT;

    } else if ((DLLtimer_GetTime() - D3LastSentWrite) >= PILOT_REQ_RESEND_TIME) {
      // Send 'da packet
      SendPilotTrackerPacket(&d3_pilot_write);
      // sendto(pilotsock,(char *)&d3_pilot_write,d3_pilot_write.len,0,(SOCKADDR *)&ptrackaddr,sizeof(SOCKADDR_IN));
      D3LastSentWrite = DLLtimer_GetTime();
    }
  }
  if (MOTDState == STATE_WAITING_MOTD) {
    if ((DLLtimer_GetTime() - MOTDFirstSent) >= PILOT_REQ_TIMEOUT) {
      MOTDState = STATE_TIMED_OUT;

    } else if ((DLLtimer_GetTime() - MOTDLastSent) >= PILOT_REQ_RESEND_TIME) {
      // Send 'da packet
      SendPilotTrackerPacket(&motd_req);
      MOTDLastSent = DLLtimer_GetTime();
    }
  }
}

void PollPTrackNet() {
  IdlePTrack();
  DLLnw_DoReceiveCallbacks();
}

int Motd_version = 0;

void HandlePilotData(uint8_t *data, int len, network_address *from) {
  udp_packet_header inpacket;
  vmt_descent3_struct *d3_pilot;
  memcpy(&inpacket, data, sizeof(inpacket));
  inpacket.len = INTEL_SHORT(inpacket.len) - 1;
  if (len) {
    switch (inpacket.type) {
    case UNT_MOTD_RESPONSE:
      if (MOTDState == STATE_WAITING_MOTD) {
        if (Motdptr) {
          if ((inpacket.len - PACKED_HEADER_ONLY_SIZE) > Motd_maxlen)
            inpacket.len = PACKED_HEADER_ONLY_SIZE + Motd_maxlen - 1;
          strncpy(Motdptr, (char *)inpacket.data, inpacket.len - PACKED_HEADER_ONLY_SIZE);
          Motdptr[inpacket.len - PACKED_HEADER_ONLY_SIZE] = '\0';
        }
        MOTDState = STATE_HAVE_MOTD;
      }
      break;
    case UNT_PILOT_DATA_RESPONSE:
      if (inpacket.code == CMD_GAME_DESCENT3) {
        if (D3ReadState == STATE_READING_PILOT) {
          d3_pilot = (vmt_descent3_struct *)&inpacket.data;
          xorcode(d3_pilot, DESCENT3_BLOCK_SIZE, inpacket.security);
          if (stricmp(ProccesingPilot, d3_pilot->pilot_name) == 0) {
            // Copy the data
            memcpy(ReadD3Pilot, &inpacket.data, DESCENT3_BLOCK_SIZE);
            // Set the state
            D3ReadState = STATE_RECEIVED_PILOT;
          }
        }
      }
      break;
    case UNT_PILOT_READ_FAILED:
      if (inpacket.code == CMD_GAME_DESCENT3) {
        if (D3ReadState == STATE_READING_PILOT) {
          D3ReadState = STATE_PILOT_NOT_FOUND;
        }
      }
      break;
    case UNT_PILOT_WRITE_SUCCESS:
      if (inpacket.code == CMD_GAME_DESCENT3) {
        if (D3WriteState == STATE_SENDING_PILOT) {
          D3WriteState = STATE_WROTE_PILOT;
        }
      }
      break;
    case UNT_PILOT_WRITE_FAILED:
      if (inpacket.code == CMD_GAME_DESCENT3) {
        if (D3WriteState == STATE_SENDING_PILOT) {
          D3WriteState = STATE_WRITE_PILOT_FAILED;
        }
      }
      break;
    case UNT_CONTROL:
      switch (INTEL_INT(inpacket.code)) {
      case CMD_VALIDATED_USER_ACK:
        if (ValidState == VALID_STATE_WAITING) {
          ValidState = VALID_STATE_VALID;
        }
        break;
      case CMD_VALIDATED_USER_NAK:
        if (ValidState == VALID_STATE_WAITING) {
          ValidState = VALID_STATE_INVALID;
        }
        break;
      }
    case UNT_LOGIN_NO_AUTH:
      if (ValidState == VALID_STATE_WAITING) {
        ValidState = VALID_STATE_INVALID;
      }
      break;
    case UNT_LOGIN_AUTHENTICATED:
      if (ValidState == VALID_STATE_WAITING) {
        ValidState = VALID_STATE_VALID;
        strcpy(Psztracker_id, (const char *)&inpacket.data);
        // memcpy(&Motd_version,inpacket.data[strlen(Psztracker_id)+1],sizeof(int));
        Motd_version = inpacket.xcode;
      }
      break;
    case UNT_TRACKER_VERSION_RSP:
      if (VersionState == VERSION_STATE_WAITING) {
        // This is a version response possibly with the URL to download a file at the end.
        strcpy(VersionURL, (const char *)&inpacket.data);
        VersionState = VERSION_STATE_RECEIVED;
      }
      break;
    default:
      break;
    }
  }
  AckServer(inpacket.sig);
}

// Call with a valid struct to validate a user
// Call with NULL to poll

// Return codes:
//  -3	Still waiting (returned if we were waiting for a tracker response and ValidateUser was called with a non-NULL
//  value -2 Timeout waiting for tracker to respond -1	User invalid
//   0	Still waiting for response from tracker/Idle
//   1	User valid
int ValidateUser(validate_id_request *valid_id, char *trackerid) {
  ValidIdle();
  if (valid_id == NULL) {
    switch (ValidState) {
    case VALID_STATE_IDLE:
      return 0;
      break;
    case VALID_STATE_WAITING:
      return 0;
      break;
    case VALID_STATE_VALID:
      ValidState = VALID_STATE_IDLE;
      return 1;
      break;
    case VALID_STATE_INVALID:
      ValidState = VALID_STATE_IDLE;
      return -1;
    case VALID_STATE_TIMEOUT:
      ValidState = VALID_STATE_IDLE;
      return -2;
    }
    return 0;
  } else {
    // if(ValidState==VALID_STATE_IDLE)
    {
      /*
      //First, flush the input buffer for the socket
      uint32_t bytesin;
      udp_packet_header inpacket;
      do
      {
              bytesin = ReceivePilotTracker(&inpacket);
      }while(bytesin && (bytesin!=-1));
      */
      Psztracker_id = trackerid;
      ValidSecurity = (uint32_t)rand();
      PacketHeader.security = ValidSecurity;
      // strcpy(ValidIDReq->tracker_id,valid_id->tracker_id);
      strcpy(ValidIDReq->login, valid_id->login);
      strcpy(ValidIDReq->password, valid_id->password);
      SendPilotTrackerPacket(&PacketHeader);
      ValidState = VALID_STATE_WAITING;
      ValidFirstSent = DLLtimer_GetTime();
      ValidLastSent = DLLtimer_GetTime();
      return 0;
    }
    //	else
    //	{
    //		return -3;
    //	}
  }
}

void ValidIdle() {
  PollPTrackNet();
  if (ValidState == VALID_STATE_WAITING) {
    if ((DLLtimer_GetTime() - ValidFirstSent) >= PILOT_REQ_TIMEOUT) {
      ValidState = VALID_STATE_TIMEOUT;

    } else if ((DLLtimer_GetTime() - ValidLastSent) >= PILOT_REQ_RESEND_TIME) {
      // Send 'da packet
      SendPilotTrackerPacket(&PacketHeader);
      ValidLastSent = DLLtimer_GetTime();
    }
  }
}
// This code will modify 4 bytes at a time, so make sure to pad it!!!
void xorcode(void *data, uint32_t len, uint32_t hash) {
  return;
  uint32_t i = 0;
  uint32_t *src = (uint32_t *)&data;

  while (i < len) {
    *src = *src ^ hash;
    src++;
    i += sizeof(uint32_t);
  }
}

void VersionIdle() {
  PollPTrackNet();
  if (VersionState == VERSION_STATE_WAITING) {
    if ((DLLtimer_GetTime() - VersionFirstSent) >= PILOT_REQ_TIMEOUT) {
      VersionState = VERSION_STATE_TIMEOUT;

    } else if ((DLLtimer_GetTime() - VersionLastSent) >= PILOT_REQ_RESEND_TIME) {
      // Send 'da packet
      SendPilotTrackerPacket(&VersionPacketHeader);
      VersionLastSent = DLLtimer_GetTime();
    }
  }
}

// Return codes:
//  -3	Still waiting (returned if we were waiting for a tracker response and VersionCheck was called with a non-NULL
//  value -2 Timeout waiting for tracker to respond
//   0	Still waiting for response from tracker/Idle
//   1	Version received (URL[0] will not be NULL if we have the URL which means we need a newer version)
// Call once with the real version, then poll with the version set to 0 URL is a pointer to a char buff(max size == 200)
// that will be filled if/when we get a URL response
int MTAVersionCheck(uint32_t oldver, char *URL) {

  int res = DLLPollUI();
  if (res == 99) {
    VersionState = VERSION_STATE_TIMEOUT;
  }
  VersionIdle();
  if (oldver == 0) {
    switch (VersionState) {
    case VERSION_STATE_IDLE:
      return 0;
      break;
    case VERSION_STATE_WAITING:
      return 0;
      break;
    case VERSION_STATE_RECEIVED:
      VersionState = VERSION_STATE_IDLE;
      return 1;
    case VERSION_STATE_TIMEOUT:
      VersionState = VERSION_STATE_IDLE;
      return -2;
    }
    return 0;
  } else {
    if (VersionState == VERSION_STATE_IDLE) {
      /*
      //First, flush the input buffer for the socket
      uint32_t bytesin;
      udp_packet_header inpacket;
      do
      {
              bytesin = ReceivePilotTracker(&inpacket);
      }while(bytesin && (bytesin!=-1));
      */
      VersionURL = URL;
      VersionPacketHeader.len = INTEL_SHORT(PACKED_HEADER_ONLY_SIZE);
      VersionPacketHeader.type = UNT_TRACKER_VERSION_REQ;
      VersionPacketHeader.xcode = INTEL_SHORT(CMD_GAME_DESCENT3);
      VersionPacketHeader.code = INTEL_INT(oldver);

      SendPilotTrackerPacket(&VersionPacketHeader);
      VersionState = VERSION_STATE_WAITING;
      VersionFirstSent = DLLtimer_GetTime();
      VersionLastSent = DLLtimer_GetTime();
      return 0;
    } else {
      return -3;
    }
  }
}

int GetD3MOTD(char *szmotd, int maxlen) {
  // First check the network
  PollPTrackNet();

  if (szmotd == nullptr) {
    if (MOTDState == STATE_WAITING_MOTD) {
      return 0;
    }
    if (MOTDState == STATE_TIMED_OUT) {
      MOTDState = STATE_IDLE;
      return -1;
    }
    if (MOTDState == STATE_HAVE_MOTD) {
      MOTDState = STATE_IDLE;
      return 1;
    }
    return -3;
  } else if (MOTDState == STATE_IDLE) {
    // New request, send out the req, and go for it.

    MOTDState = STATE_WAITING_MOTD;
    Motdptr = szmotd;
    Motd_maxlen = maxlen;
    MOTDFirstSent = DLLtimer_GetTime();
    MOTDLastSent = DLLtimer_GetTime();

    motd_req.len = INTEL_SHORT(PACKED_HEADER_ONLY_SIZE);
    motd_req.type = UNT_MOTD_REQUEST;
    motd_req.code = INTEL_INT(CMD_GAME_DESCENT3);
    return 0;
  }
  return -2;
}

int GetD3MOTDCancel() {
  // First check the network
  PollPTrackNet();

  if (MOTDState == STATE_IDLE) {
    return -3;
  } else {
    // Cancel this baby
    MOTDState = STATE_IDLE;
    Motdptr = nullptr;
    ReadD3Pilot = nullptr;
    return 2;
  }
}
