/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2025 Electronic Arts Inc.
**
** 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 .
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: BuddyThread.cpp //////////////////////////////////////////////////////
// GameSpy Presence & Messaging (Buddy) thread
// This thread communicates with GameSpy's buddy server
// and talks through a message queue with the rest of
// the game.
// Author: Matthew D. Campbell, June 2002
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameNetwork/GameSpy/BuddyThread.h"
#include "GameNetwork/GameSpy/PeerThread.h"
#include "GameNetwork/GameSpy/PersistentStorageThread.h"
#include "GameNetwork/GameSpy/ThreadUtils.h"
#include "Common/StackDump.h"
#include "mutex.h"
#include "thread.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------
typedef std::queue RequestQueue;
typedef std::queue ResponseQueue;
class BuddyThreadClass;
class GameSpyBuddyMessageQueue : public GameSpyBuddyMessageQueueInterface
{
public:
virtual ~GameSpyBuddyMessageQueue();
GameSpyBuddyMessageQueue();
virtual void startThread( void );
virtual void endThread( void );
virtual Bool isThreadRunning( void );
virtual Bool isConnected( void );
virtual Bool isConnecting( void );
virtual void addRequest( const BuddyRequest& req );
virtual Bool getRequest( BuddyRequest& req );
virtual void addResponse( const BuddyResponse& resp );
virtual Bool getResponse( BuddyResponse& resp );
virtual GPProfile getLocalProfileID( void );
BuddyThreadClass* getThread( void );
private:
MutexClass m_requestMutex;
MutexClass m_responseMutex;
RequestQueue m_requests;
ResponseQueue m_responses;
BuddyThreadClass *m_thread;
};
GameSpyBuddyMessageQueueInterface* GameSpyBuddyMessageQueueInterface::createNewMessageQueue( void )
{
return NEW GameSpyBuddyMessageQueue;
}
GameSpyBuddyMessageQueueInterface *TheGameSpyBuddyMessageQueue;
#define MESSAGE_QUEUE ((GameSpyBuddyMessageQueue *)TheGameSpyBuddyMessageQueue)
//-------------------------------------------------------------------------
class BuddyThreadClass : public ThreadClass
{
public:
BuddyThreadClass() : ThreadClass() { m_isNewAccount = m_isdeleting = m_isConnecting = m_isConnected = false; m_profileID = 0; m_lastErrorCode = 0; }
void Thread_Function();
void errorCallback( GPConnection *con, GPErrorArg *arg );
void messageCallback( GPConnection *con, GPRecvBuddyMessageArg *arg );
void connectCallback( GPConnection *con, GPConnectResponseArg *arg );
void requestCallback( GPConnection *con, GPRecvBuddyRequestArg *arg );
void statusCallback( GPConnection *con, GPRecvBuddyStatusArg *arg );
Bool isConnecting( void ) { return m_isConnecting; }
Bool isConnected( void ) { return m_isConnected; }
GPProfile getLocalProfileID( void ) { return m_profileID; }
private:
Bool m_isNewAccount;
Bool m_isConnecting;
Bool m_isConnected;
GPProfile m_profileID;
Int m_lastErrorCode;
Bool m_isdeleting;
std::string m_nick, m_email, m_pass;
};
static enum CallbackType
{
CALLBACK_CONNECT,
CALLBACK_ERROR,
CALLBACK_RECVMESSAGE,
CALLBACK_RECVREQUEST,
CALLBACK_RECVSTATUS,
CALLBACK_MAX
};
void callbackWrapper( GPConnection *con, void *arg, void *param )
{
CallbackType info = (CallbackType)(Int)param;
BuddyThreadClass *thread = MESSAGE_QUEUE->getThread() ? MESSAGE_QUEUE->getThread() : NULL /*(TheGameSpyBuddyMessageQueue)?TheGameSpyBuddyMessageQueue->getThread():NULL*/;
if (!thread)
return;
switch (info)
{
case CALLBACK_CONNECT:
thread->connectCallback( con, (GPConnectResponseArg *)arg );
break;
case CALLBACK_ERROR:
thread->errorCallback( con, (GPErrorArg *)arg );
break;
case CALLBACK_RECVMESSAGE:
thread->messageCallback( con, (GPRecvBuddyMessageArg *)arg );
break;
case CALLBACK_RECVREQUEST:
thread->requestCallback( con, (GPRecvBuddyRequestArg *)arg );
break;
case CALLBACK_RECVSTATUS:
thread->statusCallback( con, (GPRecvBuddyStatusArg *)arg );
break;
}
}
//-------------------------------------------------------------------------
GameSpyBuddyMessageQueue::GameSpyBuddyMessageQueue()
{
m_thread = NULL;
}
GameSpyBuddyMessageQueue::~GameSpyBuddyMessageQueue()
{
endThread();
}
void GameSpyBuddyMessageQueue::startThread( void )
{
if (!m_thread)
{
m_thread = NEW BuddyThreadClass;
m_thread->Execute();
}
else
{
if (!m_thread->Is_Running())
{
m_thread->Execute();
}
}
}
void GameSpyBuddyMessageQueue::endThread( void )
{
if (m_thread)
delete m_thread;
m_thread = NULL;
}
Bool GameSpyBuddyMessageQueue::isThreadRunning( void )
{
return (m_thread) ? m_thread->Is_Running() : false;
}
Bool GameSpyBuddyMessageQueue::isConnected( void )
{
return (m_thread) ? m_thread->isConnected() : false;
}
Bool GameSpyBuddyMessageQueue::isConnecting( void )
{
return (m_thread) ? m_thread->isConnecting() : false;
}
void GameSpyBuddyMessageQueue::addRequest( const BuddyRequest& req )
{
MutexClass::LockClass m(m_requestMutex);
if (m.Failed())
return;
m_requests.push(req);
}
Bool GameSpyBuddyMessageQueue::getRequest( BuddyRequest& req )
{
MutexClass::LockClass m(m_requestMutex, 0);
if (m.Failed())
return false;
if (m_requests.empty())
return false;
req = m_requests.front();
m_requests.pop();
return true;
}
void GameSpyBuddyMessageQueue::addResponse( const BuddyResponse& resp )
{
MutexClass::LockClass m(m_responseMutex);
if (m.Failed())
return;
m_responses.push(resp);
}
Bool GameSpyBuddyMessageQueue::getResponse( BuddyResponse& resp )
{
MutexClass::LockClass m(m_responseMutex, 0);
if (m.Failed())
return false;
if (m_responses.empty())
return false;
resp = m_responses.front();
m_responses.pop();
return true;
}
BuddyThreadClass* GameSpyBuddyMessageQueue::getThread( void )
{
return m_thread;
}
GPProfile GameSpyBuddyMessageQueue::getLocalProfileID( void )
{
return (m_thread) ? m_thread->getLocalProfileID() : 0;
}
//-------------------------------------------------------------------------
void BuddyThreadClass::Thread_Function()
{
try {
_set_se_translator( DumpExceptionInfo ); // Hook that allows stack trace.
GPConnection gpCon;
GPConnection *con = &gpCon;
gpInitialize( con, 0 );
m_isConnected = m_isConnecting = false;
gpSetCallback( con, GP_ERROR, callbackWrapper, (void *)CALLBACK_ERROR );
gpSetCallback( con, GP_RECV_BUDDY_MESSAGE, callbackWrapper, (void *)CALLBACK_RECVMESSAGE );
gpSetCallback( con, GP_RECV_BUDDY_REQUEST, callbackWrapper, (void *)CALLBACK_RECVREQUEST );
gpSetCallback( con, GP_RECV_BUDDY_STATUS, callbackWrapper, (void *)CALLBACK_RECVSTATUS );
GPEnum lastStatus = GP_OFFLINE;
std::string lastStatusString;
BuddyRequest incomingRequest;
while ( running )
{
// deal with requests
if (TheGameSpyBuddyMessageQueue->getRequest(incomingRequest))
{
switch (incomingRequest.buddyRequestType)
{
case BuddyRequest::BUDDYREQUEST_LOGIN:
m_isConnecting = true;
m_nick = incomingRequest.arg.login.nick;
m_email = incomingRequest.arg.login.email;
m_pass = incomingRequest.arg.login.password;
m_isConnected = (gpConnect( con, incomingRequest.arg.login.nick, incomingRequest.arg.login.email,
incomingRequest.arg.login.password, (incomingRequest.arg.login.hasFirewall)?GP_FIREWALL:GP_NO_FIREWALL,
GP_BLOCKING, callbackWrapper, (void *)CALLBACK_CONNECT ) == GP_NO_ERROR);
m_isConnecting = false;
break;
case BuddyRequest::BUDDYREQUEST_RELOGIN:
m_isConnecting = true;
m_isConnected = (gpConnect( con, m_nick.c_str(), m_email.c_str(), m_pass.c_str(), GP_FIREWALL,
GP_BLOCKING, callbackWrapper, (void *)CALLBACK_CONNECT ) == GP_NO_ERROR);
m_isConnecting = false;
break;
case BuddyRequest::BUDDYREQUEST_DELETEACCT:
m_isdeleting = true;
gpDeleteProfile( con );
break;
case BuddyRequest::BUDDYREQUEST_LOGOUT:
m_isConnecting = m_isConnected = false;
gpDisconnect( con );
break;
case BuddyRequest::BUDDYREQUEST_MESSAGE:
{
std::string s = WideCharStringToMultiByte( incomingRequest.arg.message.text );
DEBUG_LOG(("Sending a buddy message to %d [%s]\n", incomingRequest.arg.message.recipient, s.c_str()));
gpSendBuddyMessage( con, incomingRequest.arg.message.recipient, s.c_str() );
}
break;
case BuddyRequest::BUDDYREQUEST_LOGINNEW:
{
m_isConnecting = true;
m_nick = incomingRequest.arg.login.nick;
m_email = incomingRequest.arg.login.email;
m_pass = incomingRequest.arg.login.password;
m_isNewAccount = TRUE;
m_isConnected = (gpConnectNewUser( con, incomingRequest.arg.login.nick, incomingRequest.arg.login.email,
incomingRequest.arg.login.password, (incomingRequest.arg.login.hasFirewall)?GP_FIREWALL:GP_NO_FIREWALL,
GP_BLOCKING, callbackWrapper, (void *)CALLBACK_CONNECT ) == GP_NO_ERROR);
if (m_isNewAccount) // if we didn't re-login
{
gpSetInfoMask( con, GP_MASK_NONE ); // don't share info
}
m_isConnecting = false;
}
break;
case BuddyRequest::BUDDYREQUEST_ADDBUDDY:
{
std::string s = WideCharStringToMultiByte( incomingRequest.arg.addbuddy.text );
gpSendBuddyRequest( con, incomingRequest.arg.addbuddy.id, s.c_str() );
}
break;
case BuddyRequest::BUDDYREQUEST_DELBUDDY:
{
gpDeleteBuddy( con, incomingRequest.arg.profile.id );
}
break;
case BuddyRequest::BUDDYREQUEST_OKADD:
{
gpAuthBuddyRequest( con, incomingRequest.arg.profile.id );
}
break;
case BuddyRequest::BUDDYREQUEST_DENYADD:
{
gpDenyBuddyRequest( con, incomingRequest.arg.profile.id );
}
case BuddyRequest::BUDDYREQUEST_SETSTATUS:
{
//don't blast our 'Loading' status with 'Online'.
if (lastStatus == GP_PLAYING && lastStatusString == "Loading" && incomingRequest.arg.status.status == GP_ONLINE)
break;
DEBUG_LOG(("BUDDYREQUEST_SETSTATUS: status is now %d:%s/%s\n",
incomingRequest.arg.status.status, incomingRequest.arg.status.statusString, incomingRequest.arg.status.locationString));
gpSetStatus( con, incomingRequest.arg.status.status, incomingRequest.arg.status.statusString,
incomingRequest.arg.status.locationString );
lastStatus = incomingRequest.arg.status.status;
lastStatusString = incomingRequest.arg.status.statusString;
}
break;
}
}
// update the network
GPEnum isConnected = GP_CONNECTED;
GPResult res = GP_NO_ERROR;
res = gpIsConnected( con, &isConnected );
if ( isConnected == GP_CONNECTED && res == GP_NO_ERROR )
gpProcess( con );
// end our timeslice
Switch_Thread();
}
gpDestroy( con );
} catch ( ... ) {
DEBUG_CRASH(("Exception in buddy thread!"));
}
}
void BuddyThreadClass::errorCallback( GPConnection *con, GPErrorArg *arg )
{
// log the error
DEBUG_LOG(("GPErrorCallback\n"));
m_lastErrorCode = arg->errorCode;
char errorCodeString[256];
char resultString[256];
#define RESULT(x) case x: strcpy(resultString, #x); break;
switch(arg->result)
{
RESULT(GP_NO_ERROR)
RESULT(GP_MEMORY_ERROR)
RESULT(GP_PARAMETER_ERROR)
RESULT(GP_NETWORK_ERROR)
RESULT(GP_SERVER_ERROR)
default:
strcpy(resultString, "Unknown result!");
}
#undef RESULT
#define ERRORCODE(x) case x: strcpy(errorCodeString, #x); break;
switch(arg->errorCode)
{
ERRORCODE(GP_GENERAL)
ERRORCODE(GP_PARSE)
ERRORCODE(GP_NOT_LOGGED_IN)
ERRORCODE(GP_BAD_SESSKEY)
ERRORCODE(GP_DATABASE)
ERRORCODE(GP_NETWORK)
ERRORCODE(GP_FORCED_DISCONNECT)
ERRORCODE(GP_CONNECTION_CLOSED)
ERRORCODE(GP_LOGIN)
ERRORCODE(GP_LOGIN_TIMEOUT)
ERRORCODE(GP_LOGIN_BAD_NICK)
ERRORCODE(GP_LOGIN_BAD_EMAIL)
ERRORCODE(GP_LOGIN_BAD_PASSWORD)
ERRORCODE(GP_LOGIN_BAD_PROFILE)
ERRORCODE(GP_LOGIN_PROFILE_DELETED)
ERRORCODE(GP_LOGIN_CONNECTION_FAILED)
ERRORCODE(GP_LOGIN_SERVER_AUTH_FAILED)
ERRORCODE(GP_NEWUSER)
ERRORCODE(GP_NEWUSER_BAD_NICK)
ERRORCODE(GP_NEWUSER_BAD_PASSWORD)
ERRORCODE(GP_UPDATEUI)
ERRORCODE(GP_UPDATEUI_BAD_EMAIL)
ERRORCODE(GP_NEWPROFILE)
ERRORCODE(GP_NEWPROFILE_BAD_NICK)
ERRORCODE(GP_NEWPROFILE_BAD_OLD_NICK)
ERRORCODE(GP_UPDATEPRO)
ERRORCODE(GP_UPDATEPRO_BAD_NICK)
ERRORCODE(GP_ADDBUDDY)
ERRORCODE(GP_ADDBUDDY_BAD_FROM)
ERRORCODE(GP_ADDBUDDY_BAD_NEW)
ERRORCODE(GP_ADDBUDDY_ALREADY_BUDDY)
ERRORCODE(GP_AUTHADD)
ERRORCODE(GP_AUTHADD_BAD_FROM)
ERRORCODE(GP_AUTHADD_BAD_SIG)
ERRORCODE(GP_STATUS)
ERRORCODE(GP_BM)
ERRORCODE(GP_BM_NOT_BUDDY)
ERRORCODE(GP_GETPROFILE)
ERRORCODE(GP_GETPROFILE_BAD_PROFILE)
ERRORCODE(GP_DELBUDDY)
ERRORCODE(GP_DELBUDDY_NOT_BUDDY)
ERRORCODE(GP_DELPROFILE)
ERRORCODE(GP_DELPROFILE_LAST_PROFILE)
ERRORCODE(GP_SEARCH)
ERRORCODE(GP_SEARCH_CONNECTION_FAILED)
default:
strcpy(errorCodeString, "Unknown error code!");
}
#undef ERRORCODE
if(arg->fatal)
{
DEBUG_LOG(( "-----------\n"));
DEBUG_LOG(( "GP FATAL ERROR\n"));
DEBUG_LOG(( "-----------\n"));
}
else
{
DEBUG_LOG(( "-----\n"));
DEBUG_LOG(( "GP ERROR\n"));
DEBUG_LOG(( "-----\n"));
}
DEBUG_LOG(( "RESULT: %s (%d)\n", resultString, arg->result));
DEBUG_LOG(( "ERROR CODE: %s (0x%X)\n", errorCodeString, arg->errorCode));
DEBUG_LOG(( "ERROR STRING: %s\n", arg->errorString));
if (arg->fatal == GP_FATAL)
{
BuddyResponse errorResponse;
errorResponse.buddyResponseType = BuddyResponse::BUDDYRESPONSE_DISCONNECT;
errorResponse.result = arg->result;
errorResponse.arg.error.errorCode = arg->errorCode;
errorResponse.arg.error.fatal = arg->fatal;
strncpy(errorResponse.arg.error.errorString, arg->errorString, MAX_BUDDY_CHAT_LEN);
errorResponse.arg.error.errorString[MAX_BUDDY_CHAT_LEN-1] = 0;
m_isConnecting = m_isConnected = false;
TheGameSpyBuddyMessageQueue->addResponse( errorResponse );
if (m_isdeleting)
{
PeerRequest req;
req.peerRequestType = PeerRequest::PEERREQUEST_LOGOUT;
TheGameSpyPeerMessageQueue->addRequest( req );
m_isdeleting = false;
}
}
}
static void getNickForMessage( GPConnection *con, GPGetInfoResponseArg *arg, void *param )
{
BuddyResponse *resp = (BuddyResponse *)param;
strcpy(resp->arg.message.nick, arg->nick);
}
void BuddyThreadClass::messageCallback( GPConnection *con, GPRecvBuddyMessageArg *arg )
{
BuddyResponse messageResponse;
messageResponse.buddyResponseType = BuddyResponse::BUDDYRESPONSE_MESSAGE;
messageResponse.profile = arg->profile;
// get info about the person asking to be our buddy
gpGetInfo( con, arg->profile, GP_CHECK_CACHE, GP_BLOCKING, (GPCallback)getNickForMessage, &messageResponse);
std::wstring s = MultiByteToWideCharSingleLine( arg->message );
wcsncpy(messageResponse.arg.message.text, s.c_str(), MAX_BUDDY_CHAT_LEN);
messageResponse.arg.message.text[MAX_BUDDY_CHAT_LEN-1] = 0;
messageResponse.arg.message.date = arg->date;
DEBUG_LOG(("Got a buddy message from %d [%ls]\n", arg->profile, s.c_str()));
TheGameSpyBuddyMessageQueue->addResponse( messageResponse );
}
void BuddyThreadClass::connectCallback( GPConnection *con, GPConnectResponseArg *arg )
{
BuddyResponse loginResponse;
if (arg->result == GP_NO_ERROR)
{
loginResponse.buddyResponseType = BuddyResponse::BUDDYRESPONSE_LOGIN;
loginResponse.result = arg->result;
loginResponse.profile = arg->profile;
TheGameSpyBuddyMessageQueue->addResponse( loginResponse );
m_profileID = arg->profile;
if (!TheGameSpyPeerMessageQueue->isConnected() && !TheGameSpyPeerMessageQueue->isConnecting())
{
DEBUG_LOG(("Buddy connect: trying chat connect\n"));
PeerRequest req;
req.peerRequestType = PeerRequest::PEERREQUEST_LOGIN;
req.nick = m_nick.c_str();
req.password = m_pass;
req.email = m_email;
req.login.profileID = arg->profile;
TheGameSpyPeerMessageQueue->addRequest(req);
}
}
else
{
if (!TheGameSpyPeerMessageQueue->isConnected() && !TheGameSpyPeerMessageQueue->isConnecting())
{
if (m_lastErrorCode == GP_NEWUSER_BAD_NICK)
{
m_isNewAccount = FALSE;
// they just hit 'create account' instead of 'log in'. Fix them.
DEBUG_LOG(("User Error: Create Account instead of Login. Fixing them...\n"));
BuddyRequest req;
req.buddyRequestType = BuddyRequest::BUDDYREQUEST_LOGIN;
strcpy(req.arg.login.nick, m_nick.c_str());
strcpy(req.arg.login.email, m_email.c_str());
strcpy(req.arg.login.password, m_pass.c_str());
req.arg.login.hasFirewall = true;
TheGameSpyBuddyMessageQueue->addRequest( req );
return;
}
DEBUG_LOG(("Buddy connect failed (%d/%d): posting a failed chat connect\n", arg->result, m_lastErrorCode));
PeerResponse resp;
resp.peerResponseType = PeerResponse::PEERRESPONSE_DISCONNECT;
resp.discon.reason = DISCONNECT_COULDNOTCONNECT;
switch (m_lastErrorCode)
{
case GP_LOGIN_TIMEOUT:
resp.discon.reason = DISCONNECT_GP_LOGIN_TIMEOUT;
break;
case GP_LOGIN_BAD_NICK:
resp.discon.reason = DISCONNECT_GP_LOGIN_BAD_NICK;
break;
case GP_LOGIN_BAD_EMAIL:
resp.discon.reason = DISCONNECT_GP_LOGIN_BAD_EMAIL;
break;
case GP_LOGIN_BAD_PASSWORD:
resp.discon.reason = DISCONNECT_GP_LOGIN_BAD_PASSWORD;
break;
case GP_LOGIN_BAD_PROFILE:
resp.discon.reason = DISCONNECT_GP_LOGIN_BAD_PROFILE;
break;
case GP_LOGIN_PROFILE_DELETED:
resp.discon.reason = DISCONNECT_GP_LOGIN_PROFILE_DELETED;
break;
case GP_LOGIN_CONNECTION_FAILED:
resp.discon.reason = DISCONNECT_GP_LOGIN_CONNECTION_FAILED;
break;
case GP_LOGIN_SERVER_AUTH_FAILED:
resp.discon.reason = DISCONNECT_GP_LOGIN_SERVER_AUTH_FAILED;
break;
case GP_NEWUSER_BAD_NICK:
resp.discon.reason = DISCONNECT_GP_NEWUSER_BAD_NICK;
break;
case GP_NEWUSER_BAD_PASSWORD:
resp.discon.reason = DISCONNECT_GP_NEWUSER_BAD_PASSWORD;
break;
case GP_NEWPROFILE_BAD_NICK:
resp.discon.reason = DISCONNECT_GP_NEWPROFILE_BAD_NICK;
break;
case GP_NEWPROFILE_BAD_OLD_NICK:
resp.discon.reason = DISCONNECT_GP_NEWPROFILE_BAD_OLD_NICK;
break;
}
TheGameSpyPeerMessageQueue->addResponse(resp);
}
}
}
// -----------------------
static void getInfoResponseForRequest( GPConnection *con, GPGetInfoResponseArg *arg, void *param )
{
BuddyResponse *resp = (BuddyResponse *)param;
resp->profile = arg->profile;
strcpy(resp->arg.request.nick, arg->nick);
strcpy(resp->arg.request.email, arg->email);
strcpy(resp->arg.request.countrycode, arg->countrycode);
}
void BuddyThreadClass::requestCallback( GPConnection *con, GPRecvBuddyRequestArg *arg )
{
BuddyResponse response;
response.buddyResponseType = BuddyResponse::BUDDYRESPONSE_REQUEST;
response.profile = arg->profile;
// get info about the person asking to be our buddy
gpGetInfo( con, arg->profile, GP_CHECK_CACHE, GP_BLOCKING, (GPCallback)getInfoResponseForRequest, &response);
std::wstring s = MultiByteToWideCharSingleLine( arg->reason );
wcsncpy(response.arg.request.text, s.c_str(), GP_REASON_LEN);
response.arg.request.text[GP_REASON_LEN-1] = 0;
TheGameSpyBuddyMessageQueue->addResponse( response );
}
// -----------------------
static void getInfoResponseForStatus(GPConnection * connection, GPGetInfoResponseArg * arg, void * param)
{
BuddyResponse *resp = (BuddyResponse *)param;
resp->profile = arg->profile;
strcpy(resp->arg.status.nick, arg->nick);
strcpy(resp->arg.status.email, arg->email);
strcpy(resp->arg.status.countrycode, arg->countrycode);
}
void BuddyThreadClass::statusCallback( GPConnection *con, GPRecvBuddyStatusArg *arg )
{
BuddyResponse response;
// get user's name
response.buddyResponseType = BuddyResponse::BUDDYRESPONSE_STATUS;
gpGetInfo( con, arg->profile, GP_CHECK_CACHE, GP_BLOCKING, (GPCallback)getInfoResponseForStatus, &response);
// get user's status
GPBuddyStatus status;
gpGetBuddyStatus( con, arg->index, &status );
strcpy(response.arg.status.location, status.locationString);
strcpy(response.arg.status.statusString, status.statusString);
response.arg.status.status = status.status;
DEBUG_LOG(("Got buddy status for %d(%s) - status %d\n", status.profile, response.arg.status.nick, status.status));
// relay to UI
TheGameSpyBuddyMessageQueue->addResponse( response );
}
//-------------------------------------------------------------------------