text: /*
  gpiConnect.c
  GameSpy Presence SDK 
  Dan "Mr. Pants" Schoenblum
  
  Copyright 1999-2007 GameSpy Industries, Inc
  
  devsupport@gamespy.com
  
  ***********************************************************************
  Please see the GameSpy Presence SDK documentation for more information
  **********************************************************************/
  
  //INCLUDES
  //////////
  #include <stdio.h>
  #include <stdlib.h>
  #include "gpi.h"
  #include <string.h>
  
  //DEFINES
  /////////
  // Connection Manager Address.
  //////////////////////////////
  #define GPI_CONNECTION_MANAGER_NAME "gpcm." GSI_DOMAIN_NAME
  #define GPI_CONNECTION_MANAGER_PORT 29900
  
  #define GPI_UDP_HEADER "gamespygp"
  // Random String stuff.
  ///////////////////////
  #define RANDSTRING "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
  //this is off by one
  //#define RANDOMCHAR()    (RANDSTRING[(rand() * sizeof(RANDSTRING)) / (RAND_MAX + 1)])
  #define RANDOMCHAR() (RANDSTRING[rand() % (sizeof(RANDSTRING) - 1)])
  
  //GLOBALS
  /////////
  char GPConnectionManagerHostname[64] = GPI_CONNECTION_MANAGER_NAME;
  
  //FUNCTIONS
  ///////////
  static void randomString(char* buffer, int numChars)
  {
      int i;
  
      for (i = 0; i < numChars; i++)
          buffer[i] = RANDOMCHAR();
      buffer[i] = '\0';
  }
  
  static GPResult gpiStartConnect(GPConnection* connection, GPIOperation* operation)
  {
      struct sockaddr_in address;
      int rcode;
      //int len;
      GPIConnection* iconnection = (GPIConnection*)*connection;
      struct hostent* host;
  
      GSUdpErrorCode anError;
      strncpy(iconnection->mHeader, GPI_UDP_HEADER, GS_UDP_MSG_HEADER_LEN);
  
      if (!gsUdpEngineIsInitialized()) {
          unsigned short peerPort = GPI_PEER_PORT;
          gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_Notice, "Initializing UDP Layer\n");
          anError = gsUdpEngineInitialize(peerPort, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
          if (anError != GS_UDP_NO_ERROR) {
              while (anError != GS_UDP_NO_ERROR && peerPort < GPI_PEER_PORT + 100) {
                  gsDebugFormat(GSIDebugCat_GP,
                                GSIDebugType_Network,
                                GSIDebugLevel_Comment,
                                "Port %d failed, trying next port\n",
                                peerPort);
                  anError = gsUdpEngineInitialize(++peerPort, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
              }
              if (anError != GS_UDP_NO_ERROR) {
                  gsDebugFormat(GSIDebugCat_GP,
                                GSIDebugType_Network,
                                GSIDebugLevel_HotError,
                                "Tryed all 100 ports after default port, giving up.\n");
                  CallbackFatalError(
                      connection, GP_NETWORK_ERROR, GP_UDP_LAYER, "There was error starting the UDP layer.");
              }
          }
          if (!iconnection->firewall) {
              iconnection->peerPort = peerPort;
          }
      } else {
          gsDebugFormat(GSIDebugCat_GP,
                        GSIDebugType_Network,
                        GSIDebugLevel_Notice,
                        "UDP Layer already initialized, using existing port.\n");
          iconnection->peerPort = gsUdpEngineGetLocalPort();
      }
      anError = gsUdpEngineAddMsgHandler(iconnection->mHeader,
                                         iconnection->mHeader,
                                         NULL,
                                         gpiPeerAcceptedCallback,
                                         gpiPeerLeftCallback,
                                         gpiPeerPingReplyCallback,
                                         gpiPeerMessageCallback,
                                         connection);
      if (anError != GS_UDP_NO_ERROR) {
          CallbackFatalError(connection, GP_NETWORK_ERROR, GP_UDP_LAYER, "There was an error starting the UDP Layer.");
      }
      if (iconnection->firewall) {
  
          /*
  		// Create the peer listening socket.
  		////////////////////////////////////
  		iconnection->peerSocket = socket(AF_INET, SOCK_STREAM, 0);
  		if(iconnection->peerSocket == INVALID_SOCKET)
  			CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error creating a socket.");
  
  		// Make it non-blocking.
  		////////////////////////
  		rcode = SetSockBlocking(iconnection->peerSocket,0);
  		if (rcode == 0)
  			CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error making a socket non-blocking.");
  		// Bind the socket.
  		///////////////////
  		memset(&address, 0, sizeof(address));
  		address.sin_family = AF_INET;
  		rcode = bind(iconnection->peerSocket, (struct sockaddr *)&address, sizeof(struct sockaddr_in));
  		if(gsiSocketIsError(rcode))
  			CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error binding a socket.");
  
  		// Start listening on the socket.
  		/////////////////////////////////
  		rcode = listen(iconnection->peerSocket, SOMAXCONN);
  		if(gsiSocketIsError(rcode))
  			CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error listening on a socket.");
  
  		// Get the socket's port.
  		/////////////////////////
  		len = sizeof(struct sockaddr_in);
  		rcode = getsockname(iconnection->peerSocket, (struct sockaddr *)&address, &len);
  
  		if (gsiSocketIsError(rcode))
  			CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error getting a socket's addres.");
  		iconnection->peerPort = address.sin_port;
  		*/
  
          iconnection->peerPort = 0;
      }
      /*
  	else
  	{
  		// Deprecated TCP code; Replaced by UDP Layer
  		// No local port.
  		/////////////////
  		//iconnection->peerSocket = INVALID_SOCKET;
  		
  		// Set to nothing because NN will determine this
  		//////////////////////////
  		//iconnection->peerPort = 0;
  	}
  	*/
  
      // Create the cm socket.
      ////////////////////////
      iconnection->cmSocket = socket(AF_INET, SOCK_STREAM, 0);
      if (iconnection->cmSocket == INVALID_SOCKET)
          CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error creating a socket.");
  
      // Make it non-blocking.
      ////////////////////////
      rcode = SetSockBlocking(iconnection->cmSocket, 0);
      if (rcode == 0)
          CallbackFatalError(
              connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error making a socket non-blocking.");
      /* 
  	// Bind the socket.
  	///////////////////
  	memset(&address, 0, sizeof(address));
  	address.sin_family = AF_INET;
  	rcode = bind(iconnection->cmSocket, (struct sockaddr *)&address, sizeof(struct sockaddr_in));
  	if (gsiSocketIsError(rcode))
  		CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error binding a socket.");
  */
      memset(&address, 0, sizeof(address));
      address.sin_family = AF_INET;
      // Get the server host.
      ///////////////////////
      if (inet_addr(GPConnectionManagerHostname) == INADDR_NONE) {
          host = gethostbyname(GPConnectionManagerHostname);
          if (host == NULL)
              CallbackFatalError(
                  connection, GP_NETWORK_ERROR, GP_NETWORK, "Could not resolve connection mananger host name.");
          address.sin_addr.s_addr = *(unsigned int*)host->h_addr_list[0];
          //printf("Resolved Hostname and copied address:  %s\n", inet_ntoa(address.sin_addr));
      } else {
          address.sin_addr.s_addr = inet_addr(GPConnectionManagerHostname);
          //printf("Using hardcoded address: %s", GPConnectionManagerHostname);
      }
  
      // Connect the socket.
      //////////////////////
      assert(address.sin_addr.s_addr != 0);
      address.sin_port = htons(GPI_CONNECTION_MANAGER_PORT);
      rcode = connect(iconnection->cmSocket, (struct sockaddr*)&address, sizeof(struct sockaddr_in));
      if (gsiSocketIsError(rcode)) {
          int error = GOAGetLastError(iconnection->cmSocket);
          if ((error != WSAEWOULDBLOCK) && (error != WSAEINPROGRESS) && (error != WSAETIMEDOUT)) {
              CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an error connecting a socket.");
          }
      }
  
      // We're waiting for the connect to complete.
      /////////////////////////////////////////////
      operation->state = GPI_CONNECTING;
      iconnection->connectState = GPI_CONNECTING;
  
      return GP_NO_ERROR;
  }
  
  GPResult gpiConnect(GPConnection* connection,
                      const char nick[GP_NICK_LEN],
                      const char uniquenick[GP_UNIQUENICK_LEN],
                      const char email[GP_EMAIL_LEN],
                      const char password[GP_PASSWORD_LEN],
                      const char authtoken[GP_AUTHTOKEN_LEN],
                      const char partnerchallenge[GP_PARTNERCHALLENGE_LEN],
                      const char cdkey[GP_CDKEY_LEN],
                      GPEnum firewall,
                      GPIBool newuser,
                      GPEnum blocking,
                      GPCallback callback,
                      void* param)
  {
      GPIConnectData* data;
      GPIOperation* operation;
      GPIConnection* iconnection = (GPIConnection*)*connection;
      GPResult result;
  
      // Reset if this connection was already used.
      /////////////////////////////////////////////
      if (iconnection->connectState == GPI_DISCONNECTED)
          CHECK_RESULT(gpiReset(connection));
  
      // Error check.
      ///////////////
      if (iconnection->connectState != GPI_NOT_CONNECTED)
          Error(connection, GP_PARAMETER_ERROR, "Invalid connection.");
  
          // Get the firewall setting.
          ////////////////////////////
  #if defined(GS_WIRELESS_DEVICE)
      GSI_UNUSED(firewall);
      iconnection->firewall = GPITrue;
  #else
      switch (firewall) {
      case GP_FIREWALL:
          iconnection->firewall = GPITrue;
          break;
      case GP_NO_FIREWALL:
          iconnection->firewall = GPIFalse;
          break;
      default:
          Error(connection, GP_PARAMETER_ERROR, "Invalid firewall.");
      }
  #endif
  
      // Get the nick, uniquenick, email, and password.
      /////////////////////////////////////////////////
      strzcpy(iconnection->nick, nick, GP_NICK_LEN);
      strzcpy(iconnection->uniquenick, uniquenick, GP_UNIQUENICK_LEN);
      strzcpy(iconnection->email, email, GP_EMAIL_LEN);
      strzcpy(iconnection->password, password, GP_PASSWORD_LEN);
  
  #ifdef GSI_UNICODE
      // Create the _W version in addition
      UTF8ToUCS2StringLen(iconnection->nick, iconnection->nick_W, GP_NICK_LEN);
      UTF8ToUCS2StringLen(iconnection->uniquenick, iconnection->uniquenick_W, GP_UNIQUENICK_LEN);
      UTF8ToUCS2StringLen(iconnection->email, iconnection->email_W, GP_EMAIL_LEN);
      UTF8ToUCS2StringLen(iconnection->password, iconnection->password_W, GP_PASSWORD_LEN);
  #endif
  
      // Lowercase the email.
      ///////////////////////
      _strlwr(iconnection->email);
  
  #ifdef GSI_UNICODE
      // Update the UCS2 version (emails are ASCII anyhow so lowercasing didn't data)
      AsciiToUCS2String(iconnection->email, iconnection->email_W);
  #endif
  
      // Create a connect operation data struct.
      //////////////////////////////////////////
      data = (GPIConnectData*)gsimalloc(sizeof(GPIConnectData));
      if (data == NULL)
          Error(connection, GP_MEMORY_ERROR, "Out of memory.");
      memset(data, 0, sizeof(GPIConnectData));
  
      // Check for new user.
      //////////////////////
      data->newuser = newuser;
  
      // Store pre-auth data.
      ///////////////////////
      if (authtoken[0] && partnerchallenge[0]) {
          strzcpy(data->authtoken, authtoken, GP_AUTHTOKEN_LEN);
          strzcpy(data->partnerchallenge, partnerchallenge, GP_PARTNERCHALLENGE_LEN);
      }
  
      // Store cdkey if we have one.
      //////////////////////////////
      if (cdkey)
          strzcpy(data->cdkey, cdkey, GP_CDKEY_LEN);
  
      // Add the operation to the list.
      /////////////////////////////////
      CHECK_RESULT(gpiAddOperation(connection, GPI_CONNECT, data, &operation, blocking, callback, param));
  
      // Start it.
      ////////////
      result = gpiStartConnect(connection, operation);
      if (result != GP_NO_ERROR) {
          operation->result = result;
          gpiFailedOpCallback(connection, operation);
          gpiDisconnect(connection, GPIFalse);
          return result;
      }
  
      // Process it if blocking.
      //////////////////////////
      if (operation->blocking)
          CHECK_RESULT(gpiProcess(connection, operation->id));
  
      return GP_NO_ERROR;
  }
  
  static GPResult gpiSendLogin(GPConnection* connection, GPIConnectData* data)
  {
      char buffer[512];
      char response[33];
      GPIConnection* iconnection = (GPIConnection*)*connection;
      GPIProfile* profile;
      char* passphrase;
      char userBuffer[GP_NICK_LEN + GP_EMAIL_LEN];
      char partnerBuffer[11];
      char* user;
  
      // Construct the user challenge.
      ////////////////////////////////
      randomString(data->userChallenge, sizeof(data->userChallenge) - 1);
  
      // Hash the password.
      /////////////////////
      if (data->partnerchallenge[0])
          passphrase = data->partnerchallenge;
      else
          passphrase = iconnection->password;
      MD5Digest((unsigned char*)passphrase, (unsigned int)strlen(passphrase), data->passwordHash);
  
      // Construct the user.
      //////////////////////
      if (iconnection->partnerID != GP_PARTNERID_GAMESPY) {
          sprintf(partnerBuffer, "%d@", iconnection->partnerID);
      } else {
          // GS ID's do not stash the partner ID in the auth challenge to support legacy clients.
          strcpy(partnerBuffer, "");
      }
  
      if (data->authtoken[0])
          user = data->authtoken;
      else if (iconnection->uniquenick[0]) {
          sprintf(userBuffer, "%s%s", partnerBuffer, iconnection->uniquenick);
          user = userBuffer;
      } else {
          sprintf(userBuffer, "%s%s@%s", partnerBuffer, iconnection->nick, iconnection->email);
          user = userBuffer;
      }
  
      // Construct the response.
      //////////////////////////
      sprintf(buffer,
              "%s%s%s%s%s%s",
              data->passwordHash,
              "                                                ",
              user,
              data->userChallenge,
              data->serverChallenge,
              data->passwordHash);
      MD5Digest((unsigned char*)buffer, (unsigned int)strlen(buffer), response);
  
      // Check for an existing profile.
      /////////////////////////////////
      if (iconnection->infoCaching) {
          gpiFindProfileByUser(connection, iconnection->nick, iconnection->email, &profile);
          if (profile != NULL) {
              // Get the userid and profileid.
              ////////////////////////////////
              iconnection->userid = profile->userId;
              iconnection->profileid = profile->profileId;
          }
      }
  
      // Construct the outgoing message.
      //////////////////////////////////
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\login\\");
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\challenge\\");
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, data->userChallenge);
      if (data->authtoken[0]) {
          gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\authtoken\\");
          gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, data->authtoken);
      } else if (iconnection->uniquenick[0]) {
          gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\uniquenick\\");
          gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, iconnection->uniquenick);
      } else {
          gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\user\\");
          gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, iconnection->nick);
          gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "@");
          gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, iconnection->email);
      }
      if (iconnection->userid != 0) {
          gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\userid\\");
          gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->userid);
      }
      if (iconnection->profileid != 0) {
          gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\profileid\\");
          gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->profileid);
      }
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\partnerid\\");
      gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->partnerID);
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\response\\");
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, response);
      if (iconnection->firewall == GP_FIREWALL)
          gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\firewall\\1");
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\port\\");
      gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->peerPort);
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\productid\\");
      gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->productID);
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\gamename\\");
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, __GSIACGamename);
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\namespaceid\\");
      gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->namespaceID);
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sdkrevision\\");
      gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, GPI_SDKREV);
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\quiet\\");
      gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->quietModeFlags);
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\id\\1");
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\");
  
      return GP_NO_ERROR;
  }
  
  static GPResult gpiSendNewuser(GPConnection* connection, GPIConnectData* data)
  {
      GPIConnection* iconnection = (GPIConnection*)*connection;
      size_t i;
      const int useAlternateEncoding = 1;
  
      // Encrypt the password (xor with random values)
      char passwordenc[GP_PASSWORDENC_LEN];
      gpiEncodeString(iconnection->password, passwordenc);
  
      // Construct the outgoing message.
      //////////////////////////////////
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\newuser\\");
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\email\\");
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, iconnection->email);
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\nick\\");
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, iconnection->nick);
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\passwordenc\\");
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, passwordenc);
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\productid\\");
      gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->productID);
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\gamename\\");
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, __GSIACGamename);
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\namespaceid\\");
      gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->namespaceID);
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\uniquenick\\");
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, iconnection->uniquenick);
      if (data->cdkey[0]) {
          // Encrypt the cdkey (xor with random values)
          char cdkeyxor[GP_CDKEY_LEN];
          char cdkeyenc[GP_CDKEYENC_LEN];
          size_t cdkeylen = strlen(data->cdkey);
  
          Util_RandSeed((unsigned long)GP_XOR_SEED);
          for (i = 0; i < cdkeylen; i++) {
              // XOR each character with the next rand
              char aRand = (char)Util_RandInt(0, 0xFF);
              cdkeyxor[i] = (char)(data->cdkey[i] ^ aRand);
          }
          cdkeyxor[i] = '\0';
  
          // Base 64 it (printable chars only)
          B64Encode(cdkeyxor, cdkeyenc, (int)cdkeylen, useAlternateEncoding);
  
          //gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\cdkey\\");
          //gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, data->cdkey);
          gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\cdkeyenc\\");
          gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, cdkeyenc);
      }
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\partnerid\\");
      gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->partnerID);
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\id\\1");
      gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\");
  
      return GP_NO_ERROR;
  }
  
  GPResult gpiProcessConnect(GPConnection* connection, GPIOperation* operation, const char* input)
  {
      char buffer[512];
      char check[33];
      char uniquenick[GP_UNIQUENICK_LEN];
      GPIConnectData* data;
      GPIConnection* iconnection = (GPIConnection*)*connection;
      GPICallback callback;
      GPIProfile* profile;
      char userBuffer[GP_NICK_LEN + GP_EMAIL_LEN];
      char partnerBuffer[11];
      char* user;
  
      // Check for an error.
      //////////////////////
      if (gpiCheckForError(connection, input, GPIFalse)) {
          // Is this a deleted profile?
          /////////////////////////////
          if ((iconnection->errorCode == GP_LOGIN_PROFILE_DELETED) && iconnection->profileid) {
              // Remove this profile object.
              //////////////////////////////
              gpiRemoveProfileByID(connection, iconnection->profileid);
  
              // If we have the profileid/userid cached, lose them.
              /////////////////////////////////////////////////////
              iconnection->userid = 0;
              iconnection->profileid = 0;
          }
          // Check for creating an existing profile.
          //////////////////////////////////////////
          else if (iconnection->errorCode == GP_NEWUSER_BAD_NICK) {
              // Store the pid.
              /////////////////
              if (gpiValueForKey(input, "\\pid\\", buffer, sizeof(buffer)))
                  iconnection->profileid = atoi(buffer);
          }
  
          // Call the callbacks.
          //////////////////////
          CallbackFatalError(connection, GP_SERVER_ERROR, iconnection->errorCode, iconnection->errorString);
      }
  
      // Get a pointer to the data.
      /////////////////////////////
      data = (GPIConnectData*)operation->data;
  
      switch (operation->state) {
      case GPI_CONNECTING:
          // This should be \lc\1.
          ////////////////////////
          if (strncmp(input, "\\lc\\1", 5) != 0)
              CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
  
          // Get the server challenge.
          ////////////////////////////
          if (!gpiValueForKey(input, "\\challenge\\", data->serverChallenge, sizeof(data->serverChallenge)))
              CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
  
          // Check if this is a new user.
          ///////////////////////////////
          if (data->newuser) {
              // Send a new user message.
              ///////////////////////////
              CHECK_RESULT(gpiSendNewuser(connection, data));
  
              // Update the operation's state.
              ////////////////////////////////
              operation->state = GPI_REQUESTING;
          } else {
              // Send a login message.
              ////////////////////////
              CHECK_RESULT(gpiSendLogin(connection, data));
  
              // Update the operation's state.
              ////////////////////////////////
              operation->state = GPI_LOGIN;
          }
  
          break;
  
      case GPI_REQUESTING:
          // This should be \nur\.
          ////////////////////////
          if (strncmp(input, "\\nur\\", 5) != 0)
              CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
  
          // Get the userid.
          //////////////////
          if (!gpiValueForKey(input, "\\userid\\", buffer, sizeof(buffer)))
              CallbackFatalError(
                  connection, GP_NETWORK_ERROR, GP_PARSE, "Unexepected data was received from the server.");
          iconnection->userid = atoi(buffer);
  
          // Get the profileid.
          /////////////////////
          if (!gpiValueForKey(input, "\\profileid\\", buffer, sizeof(buffer)))
              CallbackFatalError(
                  connection, GP_NETWORK_ERROR, GP_PARSE, "Unexepected data was received from the server.");
          iconnection->profileid = atoi(buffer);
  
          // Send a login request.
          ////////////////////////
          CHECK_RESULT(gpiSendLogin(connection, data));
  
          // Update the operation's state.
          ////////////////////////////////
          operation->state = GPI_LOGIN;
  
          break;
  
      case GPI_LOGIN:
          // This should be \lc\2.
          ////////////////////////
          if (strncmp(input, "\\lc\\2", 5) != 0)
              CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
  
          // Get the sesskey.
          ///////////////////
          if (!gpiValueForKey(input, "\\sesskey\\", buffer, sizeof(buffer)))
              CallbackFatalError(
                  connection, GP_NETWORK_ERROR, GP_PARSE, "Unexepected data was received from the server.");
          iconnection->sessKey = atoi(buffer);
  
          // Get the userid.
          //////////////////
          if (!gpiValueForKey(input, "\\userid\\", buffer, sizeof(buffer)))
              CallbackFatalError(
                  connection, GP_NETWORK_ERROR, GP_PARSE, "Unexepected data was received from the server.");
          iconnection->userid = atoi(buffer);
  
          // Get the profileid.
          /////////////////////
          if (!gpiValueForKey(input, "\\profileid\\", buffer, sizeof(buffer)))
              CallbackFatalError(
                  connection, GP_NETWORK_ERROR, GP_PARSE, "Unexepected data was received from the server.");
          iconnection->profileid = atoi(buffer);
  
          // Get the uniquenick.
          //////////////////////
          if (!gpiValueForKey(input, "\\uniquenick\\", uniquenick, sizeof(uniquenick)))
              uniquenick[0] = '\0';
  
          // Get the loginticket.
          //////////////////////
          if (!gpiValueForKey(input, "\\lt\\", iconnection->loginTicket, sizeof(iconnection->loginTicket)))
              iconnection->loginTicket[0] = '\0';
  
          // Construct the user.
          //////////////////////
          if (iconnection->partnerID != GP_PARTNERID_GAMESPY) {
              sprintf(partnerBuffer, "%d@", iconnection->partnerID);
          } else {
              // GS ID's do not stash the partner ID in the auth challenge to support legacy clients.
              strcpy(partnerBuffer, "");
          }
  
          if (data->authtoken[0])
              user = data->authtoken;
          else if (iconnection->uniquenick[0]) {
              sprintf(userBuffer, "%s%s", partnerBuffer, iconnection->uniquenick);
              user = userBuffer;
          } else {
              sprintf(userBuffer, "%s%s@%s", partnerBuffer, iconnection->nick, iconnection->email);
              user = userBuffer;
          }
  
          // Construct the check.
          ///////////////////////
          sprintf(buffer,
                  "%s%s%s%s%s%s",
                  data->passwordHash,
                  "                                                ",
                  user,
                  data->serverChallenge,
                  data->userChallenge,
                  data->passwordHash);
          MD5Digest((unsigned char*)buffer, (unsigned int)strlen(buffer), check);
  
          // Get the proof.
          /////////////////
          if (!gpiValueForKey(input, "\\proof\\", buffer, sizeof(buffer)))
              CallbackFatalError(
                  connection, GP_NETWORK_ERROR, GP_PARSE, "Unexepected data was received from the server.");
  
          // Check the server authentication.
          ///////////////////////////////////
          if (memcmp(check, buffer, 32) != 0)
              CallbackFatalError(
                  connection, GP_NETWORK_ERROR, GP_LOGIN_SERVER_AUTH_FAILED, "Could not authenticate server.");
  
          // Add the local profile to the list.
          /////////////////////////////////////
          if (iconnection->infoCaching) {
              profile = gpiProfileListAdd(connection, iconnection->profileid);
              profile->profileId = iconnection->profileid;
              profile->userId = iconnection->userid;
          }
  
          // Set the connect state.
          /////////////////////////
          iconnection->connectState = GPI_CONNECTED;
  
          // Call the connect-response callback.
          //////////////////////////////////////
          callback = operation->callback;
          if (callback.callback != NULL) {
              GPConnectResponseArg* arg;
              arg = (GPConnectResponseArg*)gsimalloc(sizeof(GPConnectResponseArg));
              if (arg == NULL)
                  Error(connection, GP_MEMORY_ERROR, "Out of memory.");
              memset(arg, 0, sizeof(GPConnectResponseArg));
  
              arg->profile = (GPProfile)iconnection->profileid;
              arg->result = GP_NO_ERROR;
  #ifndef GSI_UNICODE
              strzcpy(arg->uniquenick, uniquenick, GP_UNIQUENICK_LEN);
  #else
              UTF8ToUCS2StringLen(uniquenick, arg->uniquenick, GP_UNIQUENICK_LEN);
  #endif
  
              CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0));
          }
  
          // This operation is complete.
          //////////////////////////////
          gpiRemoveOperation(connection, operation);
  
          // Get the local profile's info.
          ////////////////////////////////
  #if 0
  		gpiAddOperation(connection, GPI_GET_INFO, NULL, &operation, GP_NON_BLOCKING, NULL, NULL);
  		gpiSendGetInfo(connection, iconnection->profileid, operation->id);
  #endif
  
  #ifdef _PS3
          // We just connected, so setup buddy sync && start NP init
          // For future, we can limit syncs by setting flags to turn on/off here
          //////////////////////////////////////////////////////////////////////
          iconnection->npPerformBuddySync = gsi_true;
          iconnection->npPerformBlockSync = gsi_true;
          iconnection->loginTime = current_time();
  
          if (!iconnection->npInitialized)
              gpiInitializeNpBasic(connection);
  #endif
  
          break;
  
      default:
          break;
      }
  
      return GP_NO_ERROR;
  }
  
  GPResult gpiCheckConnect(GPConnection* connection)
  {
      GPIConnection* iconnection = (GPIConnection*)*connection;
      int state;
  
      // Check if the connection is completed.
      ////////////////////////////////////////
      CHECK_RESULT(gpiCheckSocketConnect(connection, iconnection->cmSocket, &state));
  
      // Check for a failed attempt.
      //////////////////////////////
      if (state == GPI_DISCONNECTED)
          CallbackFatalError(
              connection, GP_SERVER_ERROR, GP_LOGIN_CONNECTION_FAILED, "The server has refused the connection.");
  
      // Check if not finished connecting.
      ////////////////////////////////////
      if (state == GPI_NOT_CONNECTED)
          return GP_NO_ERROR;
  
      // We're now negotiating the connection.
      ////////////////////////////////////////
      assert(state == GPI_CONNECTED);
      iconnection->connectState = GPI_NEGOTIATING;
  
      return GP_NO_ERROR;
  }
  
  static GPIBool gpiDisconnectCleanupProfile(GPConnection* connection, GPIProfile* profile, void* data)
  {
      GPIConnection* iconnection = (GPIConnection*)*connection;
      GSI_UNUSED(data);
  
      // Even if we cache buddy/block info, free it up to get rid of mem
      // leaks, just don't remove the profile until we save the cache.
      //////////////////////////////////////////////////////////////////
      if (profile->buddyStatus) {
          profile->buddyOrBlockCache = gsi_true;
          freeclear(profile->buddyStatus->statusString);
          freeclear(profile->buddyStatus->locationString);
          freeclear(profile->buddyStatus);
      }
      if (profile->buddyStatusInfo) {
          profile->buddyOrBlockCache = gsi_true;
          freeclear(profile->buddyStatusInfo->richStatus);
          freeclear(profile->buddyStatusInfo->gameType);
          freeclear(profile->buddyStatusInfo->gameVariant);
          freeclear(profile->buddyStatusInfo->gameMapName);
          if (profile->buddyStatusInfo->extendedInfoKeys) {
              ArrayFree(profile->buddyStatusInfo->extendedInfoKeys);
              profile->buddyStatusInfo->extendedInfoKeys = NULL;
          }
          freeclear(profile->buddyStatusInfo);
      }
      if (profile->blocked)
          profile->buddyOrBlockCache = gsi_true;
  
      freeclear(profile->authSig);
      freeclear(profile->peerSig);
      profile->requestCount = 0;
  
      // Remove Profile if:
      //    (there is no info to cache) or
      //    (we only cache buddies/blocked and the user is not a buddy or a block)
      if ((!profile->cache) || (iconnection->infoCachingBuddyAndBlockOnly == GPITrue && !profile->buddyOrBlockCache)) {
          gpiRemoveProfile(connection, profile);
          return GPIFalse;
      }
  
      return GPITrue;
  }
  
  void gpiDisconnect(GPConnection* connection, GPIBool tellServer)
  {
      GPIConnection* iconnection = (GPIConnection*)*connection;
      GPIPeer* peer;
      GPIPeer* delPeer;
      GPIBool connClosed;
  
      // Check if we're already disconnected.
      // PANTS|05.15.00
      ///////////////////////////////////////
      if (iconnection->connectState == GPI_DISCONNECTED)
          return;
  
      // Skip most of this stuff if we never actually connected.
      // PANTS|05.16.00
      //////////////////////////////////////////////////////////
      if (iconnection->connectState != GPI_NOT_CONNECTED) {
          // Are we connected?
          ////////////////////
          if (tellServer && (iconnection->connectState == GPI_CONNECTED)) {
              // Send the disconnect.
              ///////////////////////
              gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\logout\\\\sesskey\\");
              gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey);
              gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\");
          }
  
          // Always flush remaining messages.
          // PANTS|05.16.00
          ///////////////////////////////////
          gpiSendFromBuffer(connection, iconnection->cmSocket, &iconnection->outputBuffer, &connClosed, GPITrue, "CM");
  
          // Cleanup the connection.
          //////////////////////////
          if (iconnection->cmSocket != INVALID_SOCKET) {
              shutdown(iconnection->cmSocket, 2);
              closesocket(iconnection->cmSocket);
              iconnection->cmSocket = INVALID_SOCKET;
          }
  
          if (/*iconnection->peerSocket != INVALID_SOCKET*/ gsUdpEngineIsInitialized()) {
              //shutdown(iconnection->peerSocket, 2);
              //closesocket(iconnection->peerSocket);
              //iconnection->peerSocket = INVALID_SOCKET;
              gsUdpEngineRemoveMsgHandler(iconnection->mHeader);
              if (gsUdpEngineNoMoreMsgHandlers() && gsUdpEngineNoApp())
                  gsUdpEngineShutdown();
          }
  
          // We're disconnected.
          //////////////////////
          iconnection->connectState = GPI_DISCONNECTED;
  
          // Don't keep the userid/profileid.
          ///////////////////////////////////
          iconnection->userid = 0;
          iconnection->profileid = 0;
      }
  
      // freeclear all the memory.
      ///////////////////////
      freeclear(iconnection->socketBuffer.buffer);
      freeclear(iconnection->inputBuffer);
      freeclear(iconnection->outputBuffer.buffer);
      freeclear(iconnection->updateproBuffer.buffer);
      freeclear(iconnection->updateuiBuffer.buffer);
      while (iconnection->operationList != NULL)
          gpiRemoveOperation(connection, iconnection->operationList);
      iconnection->operationList = NULL;
      for (peer = iconnection->peerList; peer != NULL;) {
          delPeer = peer;
          peer = peer->pnext;
          gpiDestroyPeer(connection, delPeer);
      }
      iconnection->peerList = NULL;
  
      // Cleanup buddies.
      // This is not optimal - because we can't continue the mapping
      // after freeing a profile, we need to start it all over again.
      ///////////////////////////////////////////////////////////////
      while (!gpiProfileMap(connection, gpiDisconnectCleanupProfile, NULL)) {
      };
  }
  