// ---------------------------------------------------------------------------80
//                ICQ plugin for Miranda Instant Messenger
//                ________________________________________
//
// Copyright � 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede
// Copyright � 2001-2002 Jon Keating, Richard Hughes
// Copyright � 2002-2004 Martin �berg, Sam Kothari, Robert Rainwater
// Copyright � 2004-2010 Joe Kucera
//
// 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 2
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// -----------------------------------------------------------------------------
//  DESCRIPTION:
//
//  Describe me here please...
//
// -----------------------------------------------------------------------------
#include "icqoscar.h"

extern const int moodXStatus[];

/*****************************************************************************
*
*   Some handy extra pack functions for basic message type headers
*
*/

// This is the part of the message header that is common for all message channels
static void packServMsgSendHeader(icq_packet *p, DWORD dwSequence, DWORD dwID1, DWORD dwID2, DWORD dwUin, const char *szUID, WORD wFmt, WORD wLen)
{
	serverPacketInit(p, (WORD)(21 + getUIDLen(dwUin, szUID) + wLen));
	packFNACHeader(p, ICQ_MSG_FAMILY, ICQ_MSG_SRV_SEND, 0, dwSequence | ICQ_MSG_SRV_SEND<<0x10);
	packLEDWord(p, dwID1);         // Msg ID part 1
	packLEDWord(p, dwID2);         // Msg ID part 2
	packWord(p, wFmt);             // Message channel
	packUID(p, dwUin, szUID);      // User ID
}


static void packServIcqExtensionHeader(icq_packet *p, CIcqProto *ppro, WORD wLen, WORD wType, WORD wSeq, WORD wCmd = ICQ_META_CLI_REQUEST)
{
	serverPacketInit(p, (WORD)(24 + wLen));
	packFNACHeader(p, ICQ_EXTENSIONS_FAMILY, ICQ_META_CLI_REQUEST, 0, wSeq | (wCmd<<0x10));
	packWord(p, 0x01);                // TLV type 1
	packWord(p, (WORD)(10 + wLen));   // TLV len
	packLEWord(p, (WORD)(8 + wLen));  // Data chunk size (TLV.Length-2)
	packLEDWord(p, ppro->m_dwLocalUIN); // My UIN
	packLEWord(p, wType);             // Request type
	packWord(p, wSeq);
}


static void packServIcqDirectoryHeader(icq_packet *p, CIcqProto *ppro, WORD wLen, WORD wType, WORD wCommand, WORD wSeq, WORD wSubCommand = ICQ_META_CLI_REQUEST)
{
	packServIcqExtensionHeader(p, ppro, wLen + 0x1E, CLI_META_INFO_REQ, wSeq, wSubCommand);
	packLEWord(p, wType);
	packLEWord(p, wLen + 0x1A);
	packFNACHeader(p, 0x5b9, wCommand, 0, 0, 2);
	packWord(p, 0);
	packWord(p, (WORD)GetACP());
	packDWord(p, 2);
}


static void packServTLV5HeaderBasic(icq_packet *p, WORD wLen, DWORD ID1, DWORD ID2, WORD wCommand, const plugin_guid pGuid)
{
	// TLV(5) header
	packWord(p, 0x05);                // Type
	packWord(p, (WORD)(26 + wLen));   // Len
	// TLV(5) data
	packWord(p, wCommand);            // Command
	packLEDWord(p, ID1);              // msgid1
	packLEDWord(p, ID2);              // msgid2
	packGUID(p, pGuid);               // capabilities (4 dwords)
}


static void packServTLV5HeaderMsg(icq_packet *p, WORD wLen, DWORD ID1, DWORD ID2, WORD wAckType)
{
	packServTLV5HeaderBasic(p, (WORD)(wLen + 10), ID1, ID2, 0, MCAP_SRV_RELAY_FMT);

	packTLVWord(p, 0x0A, wAckType);   // TLV: 0x0A Acktype: 1 for normal, 2 for ack
	packDWord(p, 0x000F0000);         // TLV: 0x0F empty
}


static void packServTLV2711Header(icq_packet *packet, WORD wCookie, WORD wVersion, BYTE bMsgType, BYTE bMsgFlags, WORD X1, WORD X2, int nLen)
{
	packWord(packet, 0x2711);            // Type
	packWord(packet, (WORD)(51 + nLen)); // Len
	// TLV(0x2711) data
	packLEWord(packet, 0x1B);            // Unknown
	packByte(packet, (BYTE)wVersion);    // Client (message) version
	packGUID(packet, PSIG_MESSAGE);
	packDWord(packet, CLIENTFEATURES);
	packDWord(packet, DC_TYPE);
	packLEWord(packet, wCookie);         // Reference cookie
	packLEWord(packet, 0x0E);            // Unknown
	packLEWord(packet, wCookie);         // Reference cookie again
	packDWord(packet, 0);                // Unknown (12 bytes)
	packDWord(packet, 0);                //  -
	packDWord(packet, 0);                //  -
	packByte(packet, bMsgType);          // Message type
	packByte(packet, bMsgFlags);         // Flags
	packLEWord(packet, X1);              // Accepted
	packWord(packet, X2);                // Unknown, priority?
}


static void packServDCInfo(icq_packet *p, CIcqProto* ppro, BOOL bEmpty)
{
	packTLVDWord(p, 0x03, bEmpty ? 0 : ppro->getSettingDword(NULL, "RealIP", 0)); // TLV: 0x03 DWORD IP
	packTLVWord(p, 0x05, (WORD)(bEmpty ? 0 : ppro->wListenPort));                 // TLV: 0x05 Listen port
}


static void packServChannel2Header(icq_packet *p, CIcqProto* ppro, DWORD dwUin, WORD wLen, DWORD dwID1, DWORD dwID2, DWORD dwCookie, WORD wVersion, BYTE bMsgType, BYTE bMsgFlags, WORD wPriority, int isAck, int includeDcInfo, BYTE bRequestServerAck)
{
	packServMsgSendHeader(p, dwCookie, dwID1, dwID2, dwUin, NULL, 0x0002, (WORD)(wLen + 95 + (bRequestServerAck?4:0) + (includeDcInfo?14:0)));

	packWord(p, 0x05);            // TLV type
	packWord(p, (WORD)(wLen + 91 + (includeDcInfo?14:0)));  /* TLV len */
	packWord(p, (WORD)(isAck ? 2: 0));     /* not aborting anything */
	packLEDWord(p, dwID1);        // Msg ID part 1
	packLEDWord(p, dwID2);        // Msg ID part 2
	packGUID(p, MCAP_SRV_RELAY_FMT); /* capability (4 dwords) */
	packDWord(p, 0x000A0002);     // TLV: 0x0A WORD: 1 for normal, 2 for ack
	packWord(p, (WORD)(isAck ? 2 : 1));

	if (includeDcInfo)
		packServDCInfo(p, ppro, FALSE);

	packDWord(p, 0x000F0000);     // TLV: 0x0F empty

	packServTLV2711Header(p, (WORD)dwCookie, wVersion, bMsgType, bMsgFlags, (WORD)MirandaStatusToIcq(ppro->m_iStatus), wPriority, wLen);
}


static void packServAdvancedReply(icq_packet *p, DWORD dwUin, const char *szUid, DWORD dwID1, DWORD dwID2, WORD wCookie, WORD wLen)
{
	serverPacketInit(p, (WORD)(getUIDLen(dwUin, szUid) + 23 + wLen));
	packFNACHeader(p, ICQ_MSG_FAMILY, ICQ_MSG_RESPONSE, 0, ICQ_MSG_RESPONSE<<0x10 | (wCookie & 0x7FFF));
	packLEDWord(p, dwID1);        // Msg ID part 1
	packLEDWord(p, dwID2);        // Msg ID part 2
	packWord(p, 0x02);            // Channel
	packUID(p, dwUin, szUid);     // Contact UID
	packWord(p, 0x03);            // Msg specific formating
}


static void packServAdvancedMsgReply(icq_packet *p, DWORD dwUin, const char *szUid, DWORD dwID1, DWORD dwID2, WORD wCookie, WORD wVersion, BYTE bMsgType, BYTE bMsgFlags, WORD wLen)
{
	packServAdvancedReply(p, dwUin, szUid, dwID1, dwID2, wCookie, (WORD)(wLen + 51));

	packLEWord(p, 0x1B);          // Unknown
	packByte(p, (BYTE)wVersion);  // Protocol version
	packGUID(p, PSIG_MESSAGE);
	packDWord(p, CLIENTFEATURES);
	packDWord(p, DC_TYPE);
	packLEWord(p, wCookie);  // Reference
	packLEWord(p, 0x0E);     // Unknown
	packLEWord(p, wCookie);  // Reference
	packDWord(p, 0);         // Unknown
	packDWord(p, 0);         // Unknown
	packDWord(p, 0);         // Unknown
	packByte(p, bMsgType);   // Message type
	packByte(p, bMsgFlags);  // Message flags
	packLEWord(p, 0);        // Ack status code ( 0 = accepted, this is hardcoded because
	//                   it is only used this way yet)
	packLEWord(p, 0);        // Unused priority field
}


void packMsgColorInfo(icq_packet *packet)
{ // TODO: make configurable
	packLEDWord(packet, 0x00000000);   // Foreground colour
	packLEDWord(packet, 0x00FFFFFF);   // Background colour
}


void packEmptyMsg(icq_packet *packet)
{
	packLEWord(packet, 1);
	packByte(packet, 0);
}

/*****************************************************************************
*
*   Functions to actually send the stuff
*
*/

void CIcqProto::icq_sendCloseConnection()
{
	icq_packet packet;

	packet.wLen = 0;
	write_flap(&packet, ICQ_CLOSE_CHAN);
	sendServPacket(&packet);
}


void CIcqProto::icq_requestnewfamily(WORD wFamily, void (CIcqProto::*familyhandler)(HANDLE hConn, char* cookie, WORD cookieLen))
{
	icq_packet packet;
	cookie_family_request *request;
	int bRequestSSL = m_bSecureConnection && (wFamily != ICQ_AVATAR_FAMILY); // Avatar servers does not support SSL

	request = (cookie_family_request*)SAFE_MALLOC(sizeof(cookie_family_request));
	request->wFamily = wFamily;
	request->familyHandler = familyhandler;

	DWORD dwCookie = AllocateCookie(CKT_SERVICEREQUEST, ICQ_CLIENT_NEW_SERVICE, 0, request); // generate and alloc cookie

	serverPacketInit(&packet, 12 + (bRequestSSL ? 4 : 0));
	packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_NEW_SERVICE, 0, dwCookie);
	packWord(&packet, wFamily);
	if (bRequestSSL)
		packDWord(&packet, 0x008C0000); // use SSL

	sendServPacket(&packet);
}


void CIcqProto::icq_setidle(int bAllow)
{
	icq_packet packet;

	if (bAllow != m_bIdleAllow)
	{
		/* SNAC 1,11 */
		serverPacketInit(&packet, 14);
		packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_SET_IDLE);
		if (bAllow==1)
			packDWord(&packet, 0x0000003C);
		else
			packDWord(&packet, 0x00000000);

		m_bIdleAllow = bAllow;
		sendServPacket(&packet);
	}
}


void CIcqProto::icq_setstatus(WORD wStatus, const char *szStatusNote)
{
	icq_packet packet;
	char *szCurrentStatusNote = szStatusNote ? getSettingStringUtf(NULL, DBSETTING_STATUS_NOTE, NULL) : NULL;
	WORD wStatusMoodLen = 0, wStatusNoteLen = 0, wSessionDataLen = 0;
	char *szMoodData = NULL;

	if (szStatusNote && strcmpnull(szCurrentStatusNote, szStatusNote))
	{ // status note was changed, update now
		DBVARIANT dbv = {DBVT_DELETED};

		if (m_bMoodsEnabled && !getSettingString(NULL, DBSETTING_STATUS_MOOD, &dbv))
			szMoodData = null_strdup(dbv.pszVal);

		db_free(&dbv);

		wStatusNoteLen = strlennull(szStatusNote);
		wStatusMoodLen = strlennull(szMoodData);

		wSessionDataLen = (wStatusNoteLen ? wStatusNoteLen + 4 : 0) + 4 + wStatusMoodLen + 4;
	}
	SAFE_FREE(&szCurrentStatusNote);

	// Pack data in packet
	serverPacketInit(&packet, (WORD)(18 + (wSessionDataLen ? wSessionDataLen + 4 : 0)));
	packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_SET_STATUS);
	packWord(&packet, 0x06);                // TLV 6
	packWord(&packet, 0x04);                // TLV length
	packWord(&packet, GetMyStatusFlags());  // Status flags
	packWord(&packet, wStatus);             // Status
	if (wSessionDataLen)
	{ // Pack session data
		packWord(&packet, 0x1D);              // TLV 1D
		packWord(&packet, wSessionDataLen);   // TLV length
		packWord(&packet, 0x02);              // Item Type
		if (wStatusNoteLen)
		{
			packWord(&packet, 0x400 | (WORD)(wStatusNoteLen + 4)); // Flags + Item Length
			packWord(&packet, wStatusNoteLen);  // Text Length
			packBuffer(&packet, (LPBYTE)szStatusNote, wStatusNoteLen);
			packWord(&packet, 0);               // Encoding not specified (utf-8 is default)
		}
		else
			packWord(&packet, 0);               // Flags + Item Length
		packWord(&packet, 0x0E);              // Item Type
		packWord(&packet, wStatusMoodLen);    // Flags + Item Length
		if (wStatusMoodLen)
			packBuffer(&packet, (LPBYTE)szMoodData, wStatusMoodLen); // Mood

		// Save current status note
		setSettingStringUtf(NULL, DBSETTING_STATUS_NOTE, szStatusNote);
	}
	// Release memory
	SAFE_FREE(&szMoodData);

	// Send packet
	sendServPacket(&packet);
}


DWORD CIcqProto::icq_SendChannel1Message(DWORD dwUin, char *szUID, HANDLE hContact, char *pszText, cookie_message_data *pCookieData)
{
	icq_packet packet;
	WORD wPacketLength;

	WORD wMessageLen = strlennull(pszText);
	DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData);

	if (pCookieData->nAckType == ACKTYPE_SERVER)
		wPacketLength = 25;
	else
		wPacketLength = 21;

	// Pack the standard header
	packServMsgSendHeader(&packet, dwCookie, pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwUin, szUID, 1, (WORD)(wPacketLength + wMessageLen));

	// Pack first TLV
	packWord(&packet, 0x0002); // TLV(2)
	packWord(&packet, (WORD)(wMessageLen + 13)); // TLV len

	// Pack client features
	packWord(&packet, 0x0501); // TLV(501)
	packWord(&packet, 0x0001); // TLV len
	packByte(&packet, 0x1);    // Features, meaning unknown, duplicated from ICQ Lite

	// Pack text TLV
	packWord(&packet, 0x0101); // TLV(2)
	packWord(&packet, (WORD)(wMessageLen + 4)); // TLV len
	packWord(&packet, 0x0003); // Message charset number, again copied from ICQ Lite
	packWord(&packet, 0x0000); // Message charset subset
	packBuffer(&packet, (LPBYTE)pszText, (WORD)(wMessageLen)); // Message text

	// Pack request server ack TLV
	if (pCookieData->nAckType == ACKTYPE_SERVER)
		packDWord(&packet, 0x00030000); // TLV(3)

	// Pack store on server TLV
	packDWord(&packet, 0x00060000); // TLV(6)

	sendServPacket(&packet);

	return dwCookie;
}


DWORD CIcqProto::icq_SendChannel1MessageW(DWORD dwUin, char *szUID, HANDLE hContact, WCHAR *pszText, cookie_message_data *pCookieData)
{
	icq_packet packet;
	WORD wMessageLen;
	DWORD dwCookie;
	WORD wPacketLength;
	WCHAR *ppText;
	int i;

	wMessageLen = strlennull(pszText) * (int)sizeof(WCHAR);
	dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData);

	if (pCookieData->nAckType == ACKTYPE_SERVER)
		wPacketLength = 26;
	else
		wPacketLength = 22;

	// Pack the standard header
	packServMsgSendHeader(&packet, dwCookie, pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwUin, szUID, 1, (WORD)(wPacketLength + wMessageLen));

	// Pack first TLV
	packWord(&packet, 0x0002); // TLV(2)
	packWord(&packet, (WORD)(wMessageLen + 14)); // TLV len

	// Pack client features
	packWord(&packet, 0x0501); // TLV(501)
	packWord(&packet, 0x0002); // TLV len
	packWord(&packet, 0x0106); // Features, meaning unknown, duplicated from ICQ 2003b

	// Pack text TLV
	packWord(&packet, 0x0101); // TLV(2)
	packWord(&packet, (WORD)(wMessageLen + 4)); // TLV len
	packWord(&packet, 0x0002); // Message charset number, again copied from ICQ 2003b
	packWord(&packet, 0x0000); // Message charset subset
	ppText = pszText;   // we must convert the widestring
	for (i = 0; i<wMessageLen; i+=2, ppText++)
		packWord(&packet, *ppText);

	// Pack request server ack TLV
	if (pCookieData->nAckType == ACKTYPE_SERVER)
		packDWord(&packet, 0x00030000); // TLV(3)

	// Pack store on server TLV
	packDWord(&packet, 0x00060000); // TLV(6)

	sendServPacket(&packet);
	return dwCookie;
}


DWORD CIcqProto::icq_SendChannel2Message(DWORD dwUin, HANDLE hContact, const char *szMessage, int nBodyLen, WORD wPriority, cookie_message_data *pCookieData, char *szCap)
{
	icq_packet packet;

	DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData);

	// Pack the standard header
	packServChannel2Header(&packet, this, dwUin, (WORD)(nBodyLen + (szCap ? 53:11)), pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwCookie, ICQ_VERSION, (BYTE)pCookieData->bMessageType, 0,
		wPriority, 0, 0, (BYTE)((pCookieData->nAckType == ACKTYPE_SERVER)?1:0));

	packLEWord(&packet, (WORD)(nBodyLen+1));            // Length of message
	packBuffer(&packet, (LPBYTE)szMessage, (WORD)(nBodyLen+1)); // Message
	packMsgColorInfo(&packet);

	if (szCap)
	{
		packLEDWord(&packet, 0x00000026);                 // length of GUID
		packBuffer(&packet, (LPBYTE)szCap, 0x26);                 // UTF-8 GUID
	}

	// Pack request server ack TLV
	if (pCookieData->nAckType == ACKTYPE_SERVER)
		packDWord(&packet, 0x00030000); // TLV(3)

	sendServPacket(&packet);
	return dwCookie;
}


DWORD CIcqProto::icq_SendChannel2Contacts(DWORD dwUin, char *szUid, HANDLE hContact, const char *pData, WORD wDataLen, const char *pNames, WORD wNamesLen, cookie_message_data *pCookieData)
{
	icq_packet packet;

	DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, pCookieData);

	WORD wPacketLength = wDataLen + wNamesLen + 0x12;

	// Pack the standard header
	packServMsgSendHeader(&packet, dwCookie, pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwUin, szUid, 2, (WORD)(wPacketLength + ((pCookieData->nAckType == ACKTYPE_SERVER)?0x22:0x1E)));

	packServTLV5HeaderBasic(&packet, wPacketLength, pCookieData->dwMsgID1, pCookieData->dwMsgID2, 0, MCAP_CONTACTS);

	packTLVWord(&packet, 0x0A, 1);              // TLV: 0x0A Acktype: 1 for normal, 2 for ack
	packDWord(&packet, 0x000F0000);             // TLV: 0x0F empty
	packTLV(&packet, 0x2711, wDataLen, (LPBYTE)pData);  // TLV: 0x2711 Content (Contact UIDs)
	packTLV(&packet, 0x2712, wNamesLen, (LPBYTE)pNames);// TLV: 0x2712 Extended Content (Contact NickNames)

	// Pack request ack TLV
	if (pCookieData->nAckType == ACKTYPE_SERVER)
	{
		packDWord(&packet, 0x00030000); // TLV(3)
	}

	sendServPacket(&packet);

	return dwCookie;
}


DWORD CIcqProto::icq_SendChannel4Message(DWORD dwUin, HANDLE hContact, BYTE bMsgType, WORD wMsgLen, const char *szMsg, cookie_message_data *pCookieData)
{
	icq_packet packet;
	WORD wPacketLength;
	DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData);

	if (pCookieData->nAckType == ACKTYPE_SERVER)
		wPacketLength = 28;
	else
		wPacketLength = 24;

	// Pack the standard header
	packServMsgSendHeader(&packet, dwCookie, pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwUin, NULL, 4, (WORD)(wPacketLength + wMsgLen));

	// Pack first TLV
	packWord(&packet, 0x05);                 // TLV(5)
	packWord(&packet, (WORD)(wMsgLen + 16)); // TLV len
	packLEDWord(&packet, m_dwLocalUIN);        // My UIN
	packByte(&packet, bMsgType);             // Message type
	packByte(&packet, 0);                    // Message flags
	packLEWord(&packet, wMsgLen);            // Message length
	packBuffer(&packet, (LPBYTE)szMsg, wMsgLen);     // Message text
	packMsgColorInfo(&packet);

	// Pack request ack TLV
	if (pCookieData->nAckType == ACKTYPE_SERVER)
	{
		packDWord(&packet, 0x00030000); // TLV(3)
	}

	// Pack store on server TLV
	packDWord(&packet, 0x00060000); // TLV(6)

	sendServPacket(&packet);

	return dwCookie;
}


void CIcqProto::sendOwnerInfoRequest(void)
{
	icq_packet packet;

	cookie_directory_data *pCookieData = (cookie_directory_data*)SAFE_MALLOC(sizeof(cookie_directory_data));
	pCookieData->bRequestType = DIRECTORYREQUEST_INFOOWNER;

	DWORD dwCookie = AllocateCookie(CKT_DIRECTORY_QUERY, 0, NULL, (void*)pCookieData);
	WORD wDataLen = getUINLen(m_dwLocalUIN) + 4;

	packServIcqDirectoryHeader(&packet, this, wDataLen + 8, META_DIRECTORY_QUERY, DIRECTORY_QUERY_INFO, (WORD)dwCookie);
	packWord(&packet, 0x03); // with interests (ICQ6 uses 2 at login)
	packDWord(&packet, 0x01);
	packWord(&packet, wDataLen);

	packTLVUID(&packet, 0x32, m_dwLocalUIN, NULL);

	sendServPacket(&packet);
}


DWORD CIcqProto::sendUserInfoMultiRequest(BYTE *pRequestData, WORD wDataLen, int nItems)
{
	icq_packet packet;

	cookie_directory_data *pCookieData = (cookie_directory_data*)SAFE_MALLOC(sizeof(cookie_directory_data));
	if (!pCookieData) return 0; // Failure
	pCookieData->bRequestType = DIRECTORYREQUEST_INFOMULTI;

	DWORD dwCookie = AllocateCookie(CKT_DIRECTORY_QUERY, 0, NULL, (void*)pCookieData);

	packServIcqDirectoryHeader(&packet, this, wDataLen + 2, META_DIRECTORY_QUERY, DIRECTORY_QUERY_MULTI_INFO, (WORD)dwCookie);
	packWord(&packet, nItems);
	packBuffer(&packet, pRequestData, wDataLen);

	sendServPacket(&packet);

	return dwCookie;
}


DWORD CIcqProto::icq_sendGetInfoServ(HANDLE hContact, DWORD dwUin, int bManual)
{
	icq_packet packet;
	DWORD dwCookie = 0;

	if (IsServerOverRate(ICQ_EXTENSIONS_FAMILY, ICQ_META_CLI_REQUEST, bManual ? RML_IDLE_10 : RML_IDLE_50))
		return dwCookie;

	DBVARIANT infoToken = {DBVT_DELETED};
	BYTE *pToken = NULL;
	WORD cbToken = 0;

	if (!getSetting(hContact, DBSETTING_METAINFO_TOKEN, &infoToken))
	{ // retrieve user details using privacy token
		cbToken = infoToken.cpbVal;
		pToken = (BYTE*)_alloca(cbToken);
		memcpy(pToken, infoToken.pbVal, cbToken);

		db_free(&infoToken);
	}

	cookie_directory_data *pCookieData = (cookie_directory_data*)SAFE_MALLOC(sizeof(cookie_directory_data));
	pCookieData->bRequestType = DIRECTORYREQUEST_INFOUSER;

	dwCookie = AllocateCookie(CKT_DIRECTORY_QUERY, 0, hContact, (void*)pCookieData);
	WORD wDataLen = cbToken + getUINLen(dwUin) + (cbToken ? 8 : 4);

	packServIcqDirectoryHeader(&packet, this, wDataLen + 8, META_DIRECTORY_QUERY, DIRECTORY_QUERY_INFO, (WORD)dwCookie);
	packWord(&packet, 0x03);
	packDWord(&packet, 1);
	packWord(&packet, wDataLen);
	if (pToken)
		packTLV(&packet, 0x3C, cbToken, pToken);
	packTLVUID(&packet, 0x32, dwUin, NULL);

	sendServPacket(&packet);

	return dwCookie;
}


DWORD CIcqProto::icq_sendGetAimProfileServ(HANDLE hContact, char* szUid)
{
	icq_packet packet;
	BYTE bUIDlen = strlennull(szUid);

	if (IsServerOverRate(ICQ_LOCATION_FAMILY, ICQ_LOCATION_REQ_USER_INFO, RML_IDLE_10))
		return 0;

	cookie_fam15_data *pCookieData = (cookie_fam15_data*)SAFE_MALLOC(sizeof(cookie_fam15_data));
	pCookieData->bRequestType = REQUESTTYPE_PROFILE;

	DWORD dwCookie = AllocateCookie(CKT_FAMILYSPECIAL, ICQ_LOCATION_REQ_USER_INFO, hContact, (void*)pCookieData);

	serverPacketInit(&packet, (WORD)(13 + bUIDlen));
	packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_REQ_USER_INFO, 0, dwCookie);
	packWord(&packet, 0x01); // request profile info
	packByte(&packet, bUIDlen);
	packBuffer(&packet, (LPBYTE)szUid, bUIDlen);

	sendServPacket(&packet);

	return dwCookie;
}


DWORD CIcqProto::icq_sendGetAwayMsgServ(HANDLE hContact, DWORD dwUin, int type, WORD wVersion)
{
	icq_packet packet;

	if (IsServerOverRate(ICQ_MSG_FAMILY, ICQ_MSG_SRV_SEND, RML_IDLE_30))
		return 0;

	cookie_message_data *pCookieData = CreateMessageCookie(MTYPE_AUTOAWAY, (BYTE)type);
	DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData);

	packServChannel2Header(&packet, this, dwUin, 3, pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwCookie, wVersion, (BYTE)type, 3, 1, 0, 0, 0);
	packEmptyMsg(&packet);    // Message
	sendServPacket(&packet);

	return dwCookie;
}


DWORD CIcqProto::icq_sendGetAwayMsgServExt(HANDLE hContact, DWORD dwUin, char *szUID, int type, WORD wVersion)
{
	icq_packet packet;

	if (IsServerOverRate(ICQ_MSG_FAMILY, ICQ_MSG_SRV_SEND, RML_IDLE_30))
		return 0;

	cookie_message_data *pCookieData = CreateMessageCookie(MTYPE_AUTOAWAY, (BYTE)type);
	DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData);

	packServMsgSendHeader(&packet, dwCookie, pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwUin, szUID, 2, 122 + getPluginTypeIdLen(type));

	// TLV(5) header
	packServTLV5HeaderMsg(&packet, 82 + getPluginTypeIdLen(type), pCookieData->dwMsgID1, pCookieData->dwMsgID2, 1);

	// TLV(0x2711) header
	packServTLV2711Header(&packet, (WORD)dwCookie, wVersion, MTYPE_PLUGIN, 0, 0, 0x100, 27 + getPluginTypeIdLen(type));
	//
	packLEWord(&packet, 0); // Empty msg

	packPluginTypeId(&packet, type);

	packLEDWord(&packet, 0x15);
	packLEDWord(&packet, 0);
	packLEDWord(&packet, 0x0D);
	packBuffer(&packet, (LPBYTE)"text/x-aolrtf", 0x0D);

	// Send the monster
	sendServPacket(&packet);

	return dwCookie;
}


DWORD CIcqProto::icq_sendGetAimAwayMsgServ(HANDLE hContact, char *szUID, int type)
{
	icq_packet packet;
	BYTE bUIDlen = strlennull(szUID);

	cookie_message_data *pCookieData = CreateMessageCookie(MTYPE_AUTOAWAY, (byte)type);
	DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData);

	serverPacketInit(&packet, (WORD)(13 + bUIDlen));
	packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_REQ_USER_INFO, 0, dwCookie);
	packWord(&packet, 0x03);
	packUID(&packet, 0, szUID);

	sendServPacket(&packet);

	return dwCookie;
}


void CIcqProto::icq_sendSetAimAwayMsgServ(const char *szMsg)
{
	icq_packet packet;
	WORD wMsgLen = strlennull(szMsg);

	DWORD dwCookie = GenerateCookie(ICQ_LOCATION_SET_USER_INFO);

	if (wMsgLen)
	{
		if (wMsgLen > 0x1000) wMsgLen = 0x1000; // limit length

		if (IsUSASCII(szMsg, wMsgLen))
		{
			const char* fmt = "text/x-aolrtf; charset=\"us-ascii\"";
			const WORD fmtlen = (WORD)strlen(fmt);

			serverPacketInit(&packet, 23 + wMsgLen + fmtlen);
			packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_SET_USER_INFO, 0, dwCookie);
			packTLV(&packet, 0x0f, 1, (LPBYTE)"\x02");
			packTLV(&packet, 0x03, fmtlen, (LPBYTE)fmt);
			packTLV(&packet, 0x04, wMsgLen, (LPBYTE)szMsg);
		}
		else
		{
			const char* fmt = "text/x-aolrtf; charset=\"unicode-2-0\"";
			const WORD fmtlen = (WORD)strlen(fmt);

			WCHAR *szMsgW = make_unicode_string(szMsg);
			wMsgLen = (WORD)strlennull(szMsgW) * sizeof(WCHAR);

			WCHAR *szMsgW2 = (WCHAR*)alloca(wMsgLen), *szMsgW3 = szMsgW;
			unpackWideString((BYTE**)&szMsgW3, szMsgW2, wMsgLen);
			SAFE_FREE(&szMsgW);

			serverPacketInit(&packet, 23 + wMsgLen + fmtlen);
			packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_SET_USER_INFO, 0, dwCookie);
			packTLV(&packet, 0x0f, 1, (LPBYTE)"\x02");
			packTLV(&packet, 0x03, fmtlen, (LPBYTE)fmt);
			packTLV(&packet, 0x04, wMsgLen, (LPBYTE)szMsgW2);
		}
	}
	else
	{
		serverPacketInit(&packet, 19);
		packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_SET_USER_INFO, 0, dwCookie);
		packTLV(&packet, 0x0f, 1, (LPBYTE)"\x02");
		packTLV(&packet, 0x04, 0, NULL);
	}

	sendServPacket(&packet);
}


void CIcqProto::icq_sendFileSendServv7(filetransfer* ft, const char *szFiles)
{
	icq_packet packet;
	WORD wDescrLen = 0, wFilesLen = 0;
	char *szFilesAnsi = NULL, *szDescrAnsi = NULL;

	if (!utf8_decode(szFiles, &szFilesAnsi))
		szFilesAnsi = NULL;
	else
		wFilesLen = strlennull(szFilesAnsi);

	if (!utf8_decode(ft->szDescription, &szDescrAnsi))
		szDescrAnsi = NULL;
	else
		wDescrLen = strlennull(szDescrAnsi);

	packServChannel2Header(&packet, this, ft->dwUin, (WORD)(18 + wDescrLen + wFilesLen), ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, ft->dwCookie, ICQ_VERSION, MTYPE_FILEREQ, 0, 1, 0, 1, 1);

	packLEWord(&packet, (WORD)(wDescrLen + 1));
	packBuffer(&packet, (LPBYTE)szDescrAnsi, (WORD)(wDescrLen + 1));
	packLEDWord(&packet, 0);   // unknown
	packLEWord(&packet, (WORD)(wFilesLen + 1));
	packBuffer(&packet, (LPBYTE)szFilesAnsi, (WORD)(wFilesLen + 1));
	packLEDWord(&packet, ft->dwTotalSize);
	packLEDWord(&packet, 0);   // unknown

	SAFE_FREE(&szFilesAnsi);
	SAFE_FREE(&szDescrAnsi);

	sendServPacket(&packet);
}


void CIcqProto::icq_sendFileSendServv8(filetransfer* ft, const char *szFiles, int nAckType)
{
	icq_packet packet;
	WORD wDescrLen = 0, wFilesLen = 0;
	char *szFilesAnsi = NULL, *szDescrAnsi = NULL;

	if (!utf8_decode(szFiles, &szFilesAnsi))
		szFilesAnsi = NULL;
	else
		wFilesLen = strlennull(szFilesAnsi);

	if (!utf8_decode(ft->szDescription, &szDescrAnsi))
		szDescrAnsi = NULL;
	else
		wDescrLen = strlennull(szDescrAnsi);

	// 202 + UIN len + file description (no null) + file name (null included)
	// Packet size = Flap length + 4
	WORD wFlapLen = 178 + wDescrLen + wFilesLen + (nAckType == ACKTYPE_SERVER?4:0);
	packServMsgSendHeader(&packet, ft->dwCookie, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, ft->dwUin, NULL, 2, wFlapLen);

	// TLV(5) header
	packServTLV5HeaderMsg(&packet, (WORD)(138 + wDescrLen + wFilesLen), ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, 1);

	// Port & IP information
	packServDCInfo(&packet, this, FALSE);

	// TLV(0x2711) header
	packServTLV2711Header(&packet, (WORD)ft->dwCookie, ICQ_VERSION, MTYPE_PLUGIN, 0, (WORD)MirandaStatusToIcq(m_iStatus), 0x100, 69 + wDescrLen + wFilesLen);

	packEmptyMsg(&packet);  // Message (unused)

	packPluginTypeId(&packet, MTYPE_FILEREQ);

	packLEDWord(&packet, (WORD)(18 + wDescrLen + wFilesLen + 1)); // Remaining length
	packLEDWord(&packet, wDescrLen);          // Description
	packBuffer(&packet, (LPBYTE)szDescrAnsi, wDescrLen);
	packWord(&packet, 0x8c82); // Unknown (port?), seen 0x80F6
	packWord(&packet, 0x0222); // Unknown, seen 0x2e01
	packLEWord(&packet, (WORD)(wFilesLen + 1));
	packBuffer(&packet, (LPBYTE)szFilesAnsi, (WORD)(wFilesLen + 1));
	packLEDWord(&packet, ft->dwTotalSize);
	packLEDWord(&packet, 0x0008c82); // Unknown, (seen 0xf680 ~33000)

	SAFE_FREE(&szFilesAnsi);
	SAFE_FREE(&szDescrAnsi);

	// Pack request server ack TLV
	if (nAckType == ACKTYPE_SERVER)
		packDWord(&packet, 0x00030000); // TLV(3)

	// Send the monster
	sendServPacket(&packet);
}


/* also sends rejections */
void CIcqProto::icq_sendFileAcceptServv8(DWORD dwUin, DWORD TS1, DWORD TS2, DWORD dwCookie, const char *szFiles, const char *szDescr, DWORD dwTotalSize, WORD wPort, BOOL accepted, int nAckType)
{
	icq_packet packet;
	WORD wDescrLen, wFilesLen;
	char *szFilesAnsi = NULL, *szDescrAnsi = NULL;

	/* if !accepted, szDescr == szReason, szFiles = "" */

	if (!accepted) szFiles = "";

	if (!utf8_decode(szFiles, &szFilesAnsi))
		szFilesAnsi = NULL;

	if (!utf8_decode(szDescr, &szDescrAnsi))
		szDescrAnsi = NULL;

	wDescrLen = strlennull(szDescrAnsi);
	wFilesLen = strlennull(szFilesAnsi);

	// 202 + UIN len + file description (no null) + file name (null included)
	// Packet size = Flap length + 4
	WORD wFlapLen = 178 + wDescrLen + wFilesLen + (nAckType == ACKTYPE_SERVER?4:0);
	packServMsgSendHeader(&packet, dwCookie, TS1, TS2, dwUin, NULL, 2, wFlapLen);

	// TLV(5) header
	packServTLV5HeaderMsg(&packet, (WORD)(138 + wDescrLen + wFilesLen), TS1, TS2, 2);

	// Port & IP information
	packServDCInfo(&packet, this, !accepted);

	// TLV(0x2711) header
	packServTLV2711Header(&packet, (WORD)dwCookie, ICQ_VERSION, MTYPE_PLUGIN, 0, (WORD)(accepted ? 0:1), 0, 69 + wDescrLen + wFilesLen);
	//
	packEmptyMsg(&packet);    // Message (unused)

	packPluginTypeId(&packet, MTYPE_FILEREQ);

	packLEDWord(&packet, (WORD)(18 + wDescrLen + wFilesLen + 1)); // Remaining length
	packLEDWord(&packet, wDescrLen);          // Description
	packBuffer(&packet, (LPBYTE)szDescrAnsi, wDescrLen);
	packWord(&packet, wPort); // Port
	packWord(&packet, 0x00);  // Unknown
	packLEWord(&packet, (WORD)(wFilesLen + 1));
	packBuffer(&packet, (LPBYTE)szFilesAnsi, (WORD)(wFilesLen + 1));
	packLEDWord(&packet, dwTotalSize);
	packLEDWord(&packet, (DWORD)wPort); // Unknown

	SAFE_FREE(&szFilesAnsi);
	SAFE_FREE(&szDescrAnsi);

	// Pack request server ack TLV
	if (nAckType == ACKTYPE_SERVER)
	{
		packDWord(&packet, 0x00030000); // TLV(3)
	}

	// Send the monster
	sendServPacket(&packet);
}


void CIcqProto::icq_sendFileAcceptServv7(DWORD dwUin, DWORD TS1, DWORD TS2, DWORD dwCookie, const char* szFiles, const char* szDescr, DWORD dwTotalSize, WORD wPort, BOOL accepted, int nAckType)
{
	icq_packet packet;
	WORD wDescrLen, wFilesLen;
	char *szFilesAnsi = NULL, *szDescrAnsi = NULL;

	/* if !accepted, szDescr == szReason, szFiles = "" */

	if (!accepted) szFiles = "";

	if (!utf8_decode(szFiles, &szFilesAnsi))
		szFilesAnsi = NULL;

	if (!utf8_decode(szDescr, &szDescrAnsi))
		szDescrAnsi = NULL;

	wDescrLen = strlennull(szDescrAnsi);
	wFilesLen = strlennull(szFilesAnsi);

	// 150 + UIN len + file description (with null) + file name (2 nulls)
	// Packet size = Flap length + 4
	WORD wFlapLen = 127 + wDescrLen + 1 + wFilesLen + (nAckType == ACKTYPE_SERVER?4:0);
	packServMsgSendHeader(&packet, dwCookie, TS1, TS2, dwUin, NULL, 2, wFlapLen);

	// TLV(5) header
	packServTLV5HeaderMsg(&packet, (WORD)(88 + wDescrLen + wFilesLen), TS1, TS2, 2);

	// Port & IP information
	packServDCInfo(&packet, this, !accepted);

	// TLV(0x2711) header
	packServTLV2711Header(&packet, (WORD)dwCookie, ICQ_VERSION, MTYPE_FILEREQ, 0, (WORD)(accepted ? 0:1), 0, 19 + wDescrLen + wFilesLen);
	//
	packLEWord(&packet, (WORD)(wDescrLen + 1));     // Description
	packBuffer(&packet, (LPBYTE)szDescrAnsi, (WORD)(wDescrLen + 1));
	packWord(&packet, wPort);   // Port
	packWord(&packet, 0x00);    // Unknown
	packLEWord(&packet, (WORD)(wFilesLen + 2));
	packBuffer(&packet, (LPBYTE)szFilesAnsi, (WORD)(wFilesLen + 1));
	packByte(&packet, 0);
	packLEDWord(&packet, dwTotalSize);
	packLEDWord(&packet, (DWORD)wPort); // Unknown

	SAFE_FREE(&szFilesAnsi);
	SAFE_FREE(&szDescrAnsi);

	// Pack request server ack TLV
	if (nAckType == ACKTYPE_SERVER)
	{
		packDWord(&packet, 0x00030000); // TLV(3)
	}

	// Send the monster
	sendServPacket(&packet);
}


void CIcqProto::icq_sendFileAcceptServ(DWORD dwUin, filetransfer *ft, int nAckType)
{
	char *szDesc = ft->szDescription;

	if (ft->bEmptyDesc) szDesc = ""; // keep empty if it originally was (Trillian workaround)

	if (ft->nVersion >= 8)
	{
		icq_sendFileAcceptServv8(dwUin, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, ft->dwCookie, ft->szFilename, szDesc, ft->dwTotalSize, wListenPort, TRUE, nAckType);
		NetLog_Server("Sent file accept v%u through server, port %u", 8, wListenPort);
	}
	else
	{
		icq_sendFileAcceptServv7(dwUin, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, ft->dwCookie, ft->szFilename, szDesc, ft->dwTotalSize, wListenPort, TRUE, nAckType);
		NetLog_Server("Sent file accept v%u through server, port %u", 7, wListenPort);
	}
}


void CIcqProto::icq_sendFileDenyServ(DWORD dwUin, filetransfer *ft, const char *szReason, int nAckType)
{
	if (ft->nVersion >= 8)
	{
		icq_sendFileAcceptServv8(dwUin, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, ft->dwCookie, ft->szFilename, szReason, ft->dwTotalSize, wListenPort, FALSE, nAckType);
		NetLog_Server("Sent file deny v%u through server", 8);
	}
	else
	{
		icq_sendFileAcceptServv7(dwUin, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, ft->dwCookie, ft->szFilename, szReason, ft->dwTotalSize, wListenPort, FALSE, nAckType);
		NetLog_Server("Sent file deny v%u through server", 7);
	}
}


void CIcqProto::icq_sendAwayMsgReplyServ(DWORD dwUin, DWORD dwMsgID1, DWORD dwMsgID2, WORD wCookie, WORD wVersion, BYTE msgType, char** szMsg)
{
	HANDLE hContact = HContactFromUIN(dwUin, NULL);

	if (validateStatusMessageRequest(hContact, msgType))
	{
		NotifyEventHooks(m_modeMsgsEvent, (WPARAM)msgType, (LPARAM)dwUin);

		icq_lock l(m_modeMsgsMutex);

		if (szMsg && *szMsg)
		{
			char *pszMsg = NULL;
			WORD wReplyVersion = ICQ_VERSION;

			if (wVersion == 9)
			{
				pszMsg = *szMsg;
				wReplyVersion = 9;
			}
			else
			{ // only v9 protocol supports UTF-8 mode messagees
				WORD wMsgLen = strlennull(*szMsg) + 1;
				char *szAnsiMsg = (char*)_alloca(wMsgLen);

				utf8_decode_static(*szMsg, szAnsiMsg, wMsgLen);
				pszMsg = szAnsiMsg;
			}

			WORD wMsgLen = strlennull(pszMsg);

			// limit msg len to max snac size - we get disconnected if exceeded
			if (wMsgLen > MAX_MESSAGESNACSIZE)
				wMsgLen = MAX_MESSAGESNACSIZE;

			icq_packet packet;

			packServAdvancedMsgReply(&packet, dwUin, NULL, dwMsgID1, dwMsgID2, wCookie, wReplyVersion, msgType, 3, (WORD)(wMsgLen + 3));
			packLEWord(&packet, (WORD)(wMsgLen + 1));
			packBuffer(&packet, (LPBYTE)pszMsg, wMsgLen);
			packByte(&packet, 0);

			sendServPacket(&packet);
		}
	}
}


void CIcqProto::icq_sendAwayMsgReplyServExt(DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, WORD wCookie, WORD wVersion, BYTE msgType, char **szMsg)
{
	HANDLE hContact = HContactFromUID(dwUin, szUID, NULL);

	if (validateStatusMessageRequest(hContact, msgType))
	{
		NotifyEventHooks(m_modeMsgsEvent, (WPARAM)msgType, (LPARAM)dwUin);

		icq_lock l(m_modeMsgsMutex);

		if (szMsg && *szMsg)
		{
			char *pszMsg = NULL;
			WORD wReplyVersion = ICQ_VERSION;

			if (wVersion == 9)
			{
				pszMsg = *szMsg;
				wReplyVersion = 9;
			}
			else
			{ // only v9 protocol supports UTF-8 mode messagees
				WORD wMsgLen = strlennull(*szMsg) + 1;
				char *szAnsiMsg = (char*)_alloca(wMsgLen);

				utf8_decode_static(*szMsg, szAnsiMsg, wMsgLen);
				pszMsg = szAnsiMsg;
			}
			// convert to HTML 
			char *mng = MangleXml(pszMsg, strlennull(pszMsg));
			pszMsg = (char*)SAFE_MALLOC(strlennull(mng) + 28);
			strcpy(pszMsg, "<HTML><BODY>"); /// TODO: add support for RTL & user customizable font
			strcat(pszMsg, mng);
			SAFE_FREE(&mng);
			strcat(pszMsg, "</BODY></HTML>");

			WORD wMsgLen = strlennull(pszMsg);

			// limit msg len to max snac size - we get disconnected if exceeded /// FIXME: correct HTML cutting
			if (wMsgLen > MAX_MESSAGESNACSIZE)
				wMsgLen = MAX_MESSAGESNACSIZE;

			icq_packet packet;

			packServAdvancedMsgReply(&packet, dwUin, szUID, dwMsgID1, dwMsgID2, wCookie, wReplyVersion, MTYPE_PLUGIN, 0, wMsgLen + 27 + getPluginTypeIdLen(msgType));
			packLEWord(&packet, 0);   // Message size
			packPluginTypeId(&packet, msgType);

			packLEDWord(&packet, wMsgLen + 21);
			packLEDWord(&packet, wMsgLen);
			packBuffer(&packet, (LPBYTE)pszMsg, wMsgLen);

			packLEDWord(&packet, 0x0D);
			packBuffer(&packet, (LPBYTE)"text/x-aolrtf", 0x0D);

			sendServPacket(&packet);
			SAFE_FREE(&pszMsg);
		}
	}
}


void CIcqProto::icq_sendAdvancedMsgAck(DWORD dwUin, DWORD dwTimestamp, DWORD dwTimestamp2, WORD wCookie, BYTE bMsgType, BYTE bMsgFlags)
{
	icq_packet packet;

	packServAdvancedMsgReply(&packet, dwUin, NULL, dwTimestamp, dwTimestamp2, wCookie, ICQ_VERSION, bMsgType, bMsgFlags, 11);
	packEmptyMsg(&packet);       // Status message
	packMsgColorInfo(&packet);

	sendServPacket(&packet);
}


void CIcqProto::icq_sendContactsAck(DWORD dwUin, char *szUid, DWORD dwMsgID1, DWORD dwMsgID2)
{
	icq_packet packet;

	packServMsgSendHeader(&packet, 0, dwMsgID1, dwMsgID2, dwUin, szUid, 2, 0x1E);
	packServTLV5HeaderBasic(&packet, 0, dwMsgID1, dwMsgID2, 2, MCAP_CONTACTS);

	sendServPacket(&packet);
}


// Searches

DWORD CIcqProto::SearchByUin(DWORD dwUin)
{
	WORD wInfoLen;
	icq_packet pBuffer; // I reuse the ICQ packet type as a generic buffer
                       // I should be ashamed! ;)

	// Calculate data size
	wInfoLen = 4 + getUINLen(dwUin);

	// Initialize our handy data buffer
	pBuffer.wPlace = 0;
	pBuffer.pData = (BYTE *)_alloca(wInfoLen);
	pBuffer.wLen = wInfoLen;

	// Initialize our handy data buffer
	packTLVUID(&pBuffer, 0x32, dwUin, NULL);

	// Send it off for further packing
	return sendDirectorySearchPacket(pBuffer.pData, wInfoLen, 0, FALSE);
}


DWORD CIcqProto::SearchByNames(const char *pszNick, const char *pszFirstName, const char *pszLastName, WORD wPage)
{ // use directory search like ICQ6 does
	WORD wInfoLen = 0;
	WORD wNickLen,wFirstLen,wLastLen;
	icq_packet pBuffer; // I reuse the ICQ packet type as a generic buffer
                       // I should be ashamed! ;)

	wNickLen = strlennull(pszNick);
	wFirstLen = strlennull(pszFirstName);
	wLastLen = strlennull(pszLastName);

	_ASSERTE(wFirstLen || wLastLen || wNickLen);


	// Calculate data size
	if (wFirstLen)
		wInfoLen = wFirstLen + 4;
	if (wLastLen)
		wInfoLen += wLastLen + 4;
	if (wNickLen)
		wInfoLen += wNickLen + 4;

	// Initialize our handy data buffer
	pBuffer.wPlace = 0;
	pBuffer.pData = (BYTE *)_alloca(wInfoLen);
	pBuffer.wLen = wInfoLen;

	// Pack the search details
	if (wNickLen)
		packTLV(&pBuffer, 0x78, wNickLen, (PBYTE)pszNick);

	if (wLastLen)
		packTLV(&pBuffer, 0x6E, wLastLen, (PBYTE)pszLastName);

	if (wFirstLen)
		packTLV(&pBuffer, 0x64, wFirstLen, (PBYTE)pszFirstName);

	// Send it off for further packing
	if (wInfoLen)
		return sendDirectorySearchPacket(pBuffer.pData, wInfoLen, wPage, FALSE);
	else
		return 0; // Failure
}


DWORD CIcqProto::SearchByMail(const char* pszEmail)
{
	DWORD dwCookie = 0;
	WORD wInfoLen = 0;
	WORD wEmailLen;
	BYTE *pBuffer;
	int pBufferPos;

	wEmailLen = strlennull(pszEmail);

	_ASSERTE(wEmailLen);

	if (wEmailLen > 0)
	{
		// Calculate data size
		wInfoLen = wEmailLen + 7;

		// Initialize our handy data buffer
		pBuffer = (BYTE *)_alloca(wInfoLen);
		pBufferPos = 0;

		// Pack the search details
		packLETLVLNTS(&pBuffer, &pBufferPos, pszEmail, TLV_EMAIL);

		// Send it off for further packing
		dwCookie = sendTLVSearchPacket(SEARCHTYPE_EMAIL, (char*)pBuffer, META_SEARCH_EMAIL, wInfoLen, FALSE);
	}

	return dwCookie;
}


DWORD CIcqProto::sendDirectorySearchPacket(const BYTE *pSearchData, WORD wDataLen, WORD wPage, BOOL bOnlineUsersOnly)
{
	icq_packet packet;
	DWORD dwCookie;

	_ASSERTE(pSearchData);
	_ASSERTE(wDataLen >= 4);

	cookie_directory_data *pCookieData = (cookie_directory_data*)SAFE_MALLOC(sizeof(cookie_directory_data));
	if (pCookieData)
	{
		pCookieData->bRequestType = DIRECTORYREQUEST_SEARCH;
		dwCookie = AllocateCookie(CKT_DIRECTORY_QUERY, 0, NULL, (void*)pCookieData);
	}
	else
		return 0;

	// Pack headers
	packServIcqDirectoryHeader(&packet, this, wDataLen + (bOnlineUsersOnly ? 14 : 8), META_DIRECTORY_QUERY, DIRECTORY_QUERY_INFO, (WORD)dwCookie);
	packWord(&packet, 0x02);

	// Pack requested page number
	packWord(&packet, wPage);

	// Pack search data
	packWord(&packet, 0x0001);
	packWord(&packet, wDataLen + (bOnlineUsersOnly ? 6 : 0));
	packBuffer(&packet, pSearchData, wDataLen);

	if (bOnlineUsersOnly)
	{ // Pack "Online users only" flag
		packTLVWord(&packet, 0x136, 1);
	}

	// Go!
	sendServPacket(&packet);

	return dwCookie;
}


DWORD CIcqProto::sendTLVSearchPacket(BYTE bType, char* pSearchDataBuf, WORD wSearchType, WORD wInfoLen, BOOL bOnlineUsersOnly)
{
	icq_packet packet;
	cookie_search* pCookie;

	_ASSERTE(pSearchDataBuf);
	_ASSERTE(wInfoLen >= 4);

	pCookie = (cookie_search*)SAFE_MALLOC(sizeof(cookie_search));
	if (!pCookie)
		return 0;

	pCookie->bSearchType = bType;
	DWORD dwCookie = AllocateCookie(CKT_SEARCH, 0, 0, pCookie);

	// Pack headers
	packServIcqExtensionHeader(&packet, this, (WORD)(wInfoLen + (wSearchType==META_SEARCH_GENERIC?7:2)), CLI_META_INFO_REQ, (WORD)dwCookie);

	// Pack search type
	packLEWord(&packet, wSearchType);

	// Pack search data
	packBuffer(&packet, (LPBYTE)pSearchDataBuf, wInfoLen);

	if (wSearchType == META_SEARCH_GENERIC && bOnlineUsersOnly)
	{ // Pack "Online users only" flag - only for generic search
		BYTE bData = 1;
		packTLV(&packet, TLV_ONLINEONLY, 1, &bData);
	}

	// Go!
	sendServPacket(&packet);

	return dwCookie;
}


DWORD CIcqProto::icq_sendAdvancedSearchServ(BYTE* fieldsBuffer,int bufferLen)
{
	icq_packet packet;
	DWORD dwCookie;

	cookie_search *pCookie = (cookie_search*)SAFE_MALLOC(sizeof(cookie_search));
	if (pCookie)
	{
		pCookie->bSearchType = SEARCHTYPE_DETAILS;
		dwCookie = AllocateCookie(CKT_SEARCH, 0, 0, pCookie);
	}
	else
		return 0;

	packServIcqExtensionHeader(&packet, this, (WORD)bufferLen, CLI_META_INFO_REQ, (WORD)dwCookie);
	packBuffer(&packet, (LPBYTE)fieldsBuffer, (WORD)bufferLen);

	sendServPacket(&packet);

	return dwCookie;
}


DWORD CIcqProto::icq_searchAimByEmail(const char* pszEmail, DWORD dwSearchId)
{
	icq_packet packet;
	DWORD dwCookie;
	cookie_search* pCookie;
	WORD wEmailLen;

	if (!FindCookie(dwSearchId, NULL, (void**)&pCookie))
	{
		dwSearchId = 0;
		pCookie = (cookie_search*)SAFE_MALLOC(sizeof(cookie_search));
		pCookie->bSearchType = SEARCHTYPE_EMAIL;
	}

	if (pCookie)
	{
		pCookie->dwMainId = dwSearchId;
		pCookie->szObject = null_strdup(pszEmail);
		dwCookie = AllocateCookie(CKT_SEARCH, ICQ_LOOKUP_REQUEST, 0, pCookie);
	}
	else
		return 0;

	wEmailLen = strlennull(pszEmail);
	serverPacketInit(&packet, (WORD)(10 + wEmailLen));
	packFNACHeader(&packet, ICQ_LOOKUP_FAMILY, ICQ_LOOKUP_REQUEST, 0, dwCookie);
	packBuffer(&packet, (LPBYTE)pszEmail, wEmailLen);

	sendServPacket(&packet);

	return dwCookie;
}


DWORD CIcqProto::icq_changeUserPasswordServ(const char *szPassword)
{
	icq_packet packet;
	WORD wPasswordLen = strlennull(szPassword);
	DWORD dwCookie = GenerateCookie(0);

	packServIcqExtensionHeader(&packet, this, (WORD)(wPasswordLen + 4), CLI_META_INFO_REQ, (WORD)dwCookie, ICQ_META_SRV_UPDATE);
	packLEWord(&packet, META_SET_PASSWORD_REQ);
	packLEWord(&packet, wPasswordLen);
	packBuffer(&packet, (BYTE*)szPassword, wPasswordLen);

	sendServPacket(&packet);

	return dwCookie;
}


DWORD CIcqProto::icq_changeUserDirectoryInfoServ(const BYTE *pData, WORD wDataLen, BYTE bRequestType)
{
	icq_packet packet;
	cookie_directory_data *pCookieData = (cookie_directory_data*)SAFE_MALLOC(sizeof(cookie_directory_data));
	pCookieData->bRequestType = bRequestType;
	DWORD dwCookie = AllocateCookie(CKT_DIRECTORY_UPDATE, 0, NULL, pCookieData);

	packServIcqDirectoryHeader(&packet, this, wDataLen + 4, META_DIRECTORY_UPDATE, DIRECTORY_SET_INFO, (WORD)dwCookie, ICQ_META_SRV_UPDATE);
	packWord(&packet, 0x0003);
	packWord(&packet, wDataLen);
	packBuffer(&packet, pData, wDataLen);

	sendServPacket(&packet);

	return dwCookie;
}


DWORD CIcqProto::icq_sendSMSServ(const char *szPhoneNumber, const char *szMsg)
{
	icq_packet packet;
	DWORD dwCookie;
	WORD wBufferLen;
	char* szBuffer = NULL;
	char* szMyNick = NULL;
	char szTime[30];
	time_t now;
	int nBufferSize;

	now = time(NULL);
	strftime(szTime, sizeof(szTime), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now));
	/* Sun, 00 Jan 0000 00:00:00 GMT */

	szMyNick = null_strdup((char *)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)(HANDLE)NULL, 0));
	nBufferSize = 1 + strlennull(szMyNick) + strlennull(szPhoneNumber) + strlennull(szMsg) + sizeof("<icq_sms_message><destination></destination><text></text><codepage>1252</codepage><encoding>utf8</encoding><senders_UIN>0000000000</senders_UIN><senders_name></senders_name><delivery_receipt>Yes</delivery_receipt><time>Sun, 00 Jan 0000 00:00:00 GMT</time></icq_sms_message>");

	if (szBuffer = (char *)_alloca(nBufferSize))
	{

		wBufferLen = null_snprintf(szBuffer, nBufferSize,
			"<icq_sms_message>"
			"<destination>"
			"%s"   /* phone number */
			"</destination>"
			"<text>"
			"%s"   /* body */
			"</text>"
			"<codepage>"
			"1252"
			"</codepage>"
			"<encoding>"
			"utf8"
			"</encoding>"
			"<senders_UIN>"
			"%u"  /* my UIN */
			"</senders_UIN>"
			"<senders_name>"
			"%s"  /* my nick */
			"</senders_name>"
			"<delivery_receipt>"
			"Yes"
			"</delivery_receipt>"
			"<time>"
			"%s"  /* time */
			"</time>"
			"</icq_sms_message>",
			szPhoneNumber, szMsg, m_dwLocalUIN, szMyNick, szTime);

		dwCookie = GenerateCookie(0);

		packServIcqExtensionHeader(&packet, this, (WORD)(wBufferLen + 27), CLI_META_INFO_REQ, (WORD)dwCookie);
		packWord(&packet, 0x8214);     /* send sms */
		packWord(&packet, 1);
		packWord(&packet, 0x16);
		packDWord(&packet, 0);
		packDWord(&packet, 0);
		packDWord(&packet, 0);
		packDWord(&packet, 0);
		packWord(&packet, 0);
		packWord(&packet, (WORD)(wBufferLen + 1));
		packBuffer(&packet, (LPBYTE)szBuffer, (WORD)(1 + wBufferLen));

		sendServPacket(&packet);
	}
	else
	{
		dwCookie = 0;
	}

	SAFE_FREE((void**)&szMyNick);
	return dwCookie;
}

void CIcqProto::icq_sendGenericContact(DWORD dwUin, const char *szUid, WORD wFamily, WORD wSubType)
{
	icq_packet packet;
	int nUinLen;

	nUinLen = getUIDLen(dwUin, szUid);

	serverPacketInit(&packet, (WORD)(nUinLen + 11));
	packFNACHeader(&packet, wFamily, wSubType);
	packUID(&packet, dwUin, szUid);

	sendServPacket(&packet);
}

void CIcqProto::icq_sendNewContact(DWORD dwUin, const char *szUid)
{
	/* Try to add to temporary buddy list */
	icq_sendGenericContact(dwUin, szUid, ICQ_BUDDY_FAMILY, ICQ_USER_ADDTOTEMPLIST);
}


void CIcqProto::icq_sendRemoveContact(DWORD dwUin, const char *szUid)
{
	/* Remove from temporary buddy list */
	icq_sendGenericContact(dwUin, szUid, ICQ_BUDDY_FAMILY, ICQ_USER_REMOVEFROMTEMPLIST);
}


// list==0: visible list
// list==1: invisible list
void CIcqProto::icq_sendChangeVisInvis(HANDLE hContact, DWORD dwUin, char* szUID, int list, int add)
{ // TODO: This needs grouping & rate management
	// Tell server to change our server-side contact visbility list
	if (m_bSsiEnabled)
	{
		WORD wContactId;
		char* szSetting;
		WORD wType;

		if (list == 0)
		{
			wType = SSI_ITEM_PERMIT;
			szSetting = DBSETTING_SERVLIST_PERMIT;
		}
		else
		{
			wType = SSI_ITEM_DENY;
			szSetting = DBSETTING_SERVLIST_DENY;
		}

		if (add)
		{
			// check if we should make the changes, this is 2nd level check
			if (getSettingWord(hContact, szSetting, 0) != 0)
				return;

			// Add
			wContactId = GenerateServerID(SSIT_ITEM, 0);

			icq_addServerPrivacyItem(hContact, dwUin, szUID, wContactId, wType);

			setSettingWord(hContact, szSetting, wContactId);
		}
		else
		{
			// Remove
			wContactId = getSettingWord(hContact, szSetting, 0);

			if (wContactId)
			{
				icq_removeServerPrivacyItem(hContact, dwUin, szUID, wContactId, wType);

				deleteSetting(hContact, szSetting);
			}
		}
	}

	// Notify server that we have changed
	// our client side visibility list
	{
		int nUinLen;
		icq_packet packet;
		WORD wSnac = 0;

		if (list && m_iStatus == ID_STATUS_INVISIBLE)
			return;

		if (!list && m_iStatus != ID_STATUS_INVISIBLE)
			return;


		if (list && add)
			wSnac = ICQ_CLI_ADDINVISIBLE;
		else if (list && !add)
			wSnac = ICQ_CLI_REMOVEINVISIBLE;
		else if (!list && add)
			wSnac = ICQ_CLI_ADDVISIBLE;
		else if (!list && !add)
			wSnac = ICQ_CLI_REMOVEVISIBLE;

		nUinLen = getUIDLen(dwUin, szUID);

		serverPacketInit(&packet, (WORD)(nUinLen + 11));
		packFNACHeader(&packet, ICQ_BOS_FAMILY, wSnac);
		packUID(&packet, dwUin, szUID);

		sendServPacket(&packet);
	}
}

void CIcqProto::icq_sendEntireVisInvisList(int list)
{
	if (list)
		sendEntireListServ(ICQ_BOS_FAMILY, ICQ_CLI_ADDINVISIBLE, BUL_INVISIBLE);
	else
		sendEntireListServ(ICQ_BOS_FAMILY, ICQ_CLI_ADDVISIBLE, BUL_VISIBLE);
}

void CIcqProto::icq_sendRevokeAuthServ(DWORD dwUin, char *szUid)
{
	icq_sendGenericContact(dwUin, szUid, ICQ_LISTS_FAMILY, ICQ_LISTS_REVOKEAUTH);
}

void CIcqProto::icq_sendGrantAuthServ(DWORD dwUin, const char *szUid, const char *szMsg)
{
	icq_packet packet;
	BYTE nUinlen;
	char *szUtfMsg = NULL;
	WORD nMsglen;

	nUinlen = getUIDLen(dwUin, szUid);

	// Prepare custom utf-8 message
	szUtfMsg = ansi_to_utf8(szMsg);
	nMsglen = strlennull(szUtfMsg);

	serverPacketInit(&packet, (WORD)(15 + nUinlen + nMsglen));
	packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_GRANTAUTH);
	packUID(&packet, dwUin, szUid);
	packWord(&packet, nMsglen);
	packBuffer(&packet, (LPBYTE)szUtfMsg, nMsglen);
	packWord(&packet, 0);

	SAFE_FREE((void**)&szUtfMsg);

	sendServPacket(&packet);
}

void CIcqProto::icq_sendAuthReqServ(DWORD dwUin, char *szUid, const char *szMsg)
{
	icq_packet packet;
	BYTE nUinlen;
	WORD nMsglen;

	nUinlen = getUIDLen(dwUin, szUid);
	nMsglen = strlennull(szMsg);

	serverPacketInit(&packet, (WORD)(15 + nUinlen + nMsglen));
	packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_REQUESTAUTH);
	packUID(&packet, dwUin, szUid);
	packWord(&packet, nMsglen);
	packBuffer(&packet, (LPBYTE)szMsg, nMsglen);
	packWord(&packet, 0);

	sendServPacket(&packet);
}

void CIcqProto::icq_sendAuthResponseServ(DWORD dwUin, char* szUid, int auth, const TCHAR *szReason)
{
	icq_packet packet;
	BYTE nUinLen = getUIDLen(dwUin, szUid);

	// Prepare custom utf-8 reason
	char *szUtfReason = tchar_to_utf8(szReason);
	WORD nReasonLen = strlennull(szUtfReason);

	serverPacketInit(&packet, (WORD)(16 + nUinLen + nReasonLen));
	packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_CLI_AUTHRESPONSE);
	packUID(&packet, dwUin, szUid);
	packByte(&packet, (BYTE)auth);
	packWord(&packet, nReasonLen);
	packBuffer(&packet, (LPBYTE)szUtfReason, nReasonLen);
	packWord(&packet, 0);

	SAFE_FREE(&szUtfReason);

	sendServPacket(&packet);
}

void CIcqProto::icq_sendYouWereAddedServ(DWORD dwUin, DWORD dwMyUin)
{
	icq_packet packet;
	DWORD dwID1;
	DWORD dwID2;

	dwID1 = time(NULL);
	dwID2 = RandRange(0, 0x00FF);

	packServMsgSendHeader(&packet, 0, dwID1, dwID2, dwUin, NULL, 0x0004, 17);
	packWord(&packet, 0x0005);      // TLV(5)
	packWord(&packet, 0x0009);
	packLEDWord(&packet, dwMyUin);
	packByte(&packet, MTYPE_ADDED);
	packByte(&packet, 0);           // msg-flags
	packEmptyMsg(&packet);          // NTS
	packDWord(&packet, 0x00060000); // TLV(6)

	sendServPacket(&packet);
}

void CIcqProto::icq_sendXtrazRequestServ(DWORD dwUin, DWORD dwCookie, char* szBody, int nBodyLen, cookie_message_data *pCookieData)
{
	icq_packet packet;
	WORD wCoreLen;

	wCoreLen = 11 + getPluginTypeIdLen(pCookieData->bMessageType) + nBodyLen;
	packServMsgSendHeader(&packet, dwCookie, pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwUin, NULL, 2, (WORD)(99 + wCoreLen));

	// TLV(5) header
	packServTLV5HeaderMsg(&packet, (WORD)(55 + wCoreLen), pCookieData->dwMsgID1, pCookieData->dwMsgID2, 1);

	// TLV(0x2711) header
	packServTLV2711Header(&packet, (WORD)dwCookie, ICQ_VERSION, MTYPE_PLUGIN, 0, 0, 0x100, wCoreLen);
	//
	packEmptyMsg(&packet);

	packPluginTypeId(&packet, pCookieData->bMessageType);

	packLEDWord(&packet, nBodyLen + 4);
	packLEDWord(&packet, nBodyLen);
	packBuffer(&packet, (LPBYTE)szBody, (WORD)nBodyLen);

	// Pack request server ack TLV
	packDWord(&packet, 0x00030000); // TLV(3)

	// Send the monster
	sendServPacket(&packet);
}

void CIcqProto::icq_sendXtrazResponseServ(DWORD dwUin, DWORD dwMID, DWORD dwMID2, WORD wCookie, char* szBody, int nBodyLen, int nType)
{
	icq_packet packet;

	packServAdvancedMsgReply(&packet, dwUin, NULL, dwMID, dwMID2, wCookie, ICQ_VERSION, MTYPE_PLUGIN, 0, (WORD)(getPluginTypeIdLen(nType) + 11 + nBodyLen));
	//
	packEmptyMsg(&packet);

	packPluginTypeId(&packet, nType);

	packLEDWord(&packet, nBodyLen + 4);
	packLEDWord(&packet, nBodyLen);
	packBuffer(&packet, (LPBYTE)szBody, (WORD)nBodyLen);

	// Send the monster
	sendServPacket(&packet);
}

void CIcqProto::icq_sendReverseReq(directconnect *dc, DWORD dwCookie, cookie_message_data *pCookie)
{
	icq_packet packet;

	packServMsgSendHeader(&packet, dwCookie, pCookie->dwMsgID1, pCookie->dwMsgID2, dc->dwRemoteUin, NULL, 2, 0x47);

	packServTLV5HeaderBasic(&packet, 0x29, pCookie->dwMsgID1, pCookie->dwMsgID2, 0, MCAP_REVERSE_DC_REQ);

	packTLVWord(&packet, 0x0A, 1);            // TLV: 0x0A Acktype: 1 for normal, 2 for ack
	packDWord(&packet, 0x000F0000);           // TLV: 0x0F empty
	packDWord(&packet, 0x2711001B);           // TLV: 0x2711 Content
	// TLV(0x2711) data
	packLEDWord(&packet, m_dwLocalUIN);         // Our UIN
	packDWord(&packet, dc->dwLocalExternalIP);// IP to connect to
	packLEDWord(&packet, wListenPort);        // Port to connect to
	packByte(&packet, DC_NORMAL);             // generic DC type
	packDWord(&packet, dc->dwRemotePort);     // unknown
	packDWord(&packet, wListenPort);          // port again ?
	packLEWord(&packet, ICQ_VERSION);         // DC Version
	packLEDWord(&packet, dwCookie);           // Req Cookie

	// Send the monster
	sendServPacket(&packet);
}

void CIcqProto::icq_sendReverseFailed(directconnect* dc, DWORD dwMsgID1, DWORD dwMsgID2, DWORD dwCookie)
{
	icq_packet packet;
	int nUinLen = getUINLen(dc->dwRemoteUin);

	serverPacketInit(&packet, (WORD)(nUinLen + 74));
	packFNACHeader(&packet, ICQ_MSG_FAMILY, ICQ_MSG_RESPONSE, 0, ICQ_MSG_RESPONSE<<0x10 | (dwCookie & 0x7FFF));
	packLEDWord(&packet, dwMsgID1);   // Msg ID part 1
	packLEDWord(&packet, dwMsgID2);   // Msg ID part 2
	packWord(&packet, 0x02);
	packUIN(&packet, dc->dwRemoteUin);
	packWord(&packet, 0x03);
	packLEDWord(&packet, dc->dwRemoteUin);
	packLEDWord(&packet, dc->dwRemotePort);
	packLEDWord(&packet, wListenPort);
	packLEWord(&packet, ICQ_VERSION);
	packLEDWord(&packet, dwCookie);

	sendServPacket(&packet);
}


// OSCAR file-transfer packets starts here
//
void CIcqProto::oft_sendFileRequest(DWORD dwUin, char *szUid, oscar_filetransfer *ft, const char *pszFiles, DWORD dwLocalInternalIP)
{
	icq_packet packet;

	char *szCoolStr = (char*)_alloca(strlennull(ft->szDescription)+strlennull(pszFiles) + 160);
	sprintf(szCoolStr, "<ICQ_COOL_FT><FS>%s</FS><S>%I64u</S><SID>1</SID><DESC>%s</DESC></ICQ_COOL_FT>", pszFiles, ft->qwTotalSize, ft->szDescription);
	szCoolStr = MangleXml(szCoolStr, strlennull(szCoolStr));

	WORD wDataLen = 93 + strlennull(szCoolStr) + strlennull(pszFiles);
	if (ft->bUseProxy) wDataLen += 4;

	packServMsgSendHeader(&packet, ft->dwCookie, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, dwUin, szUid, 2, (WORD)(wDataLen + 0x1E));
	packServTLV5HeaderBasic(&packet, wDataLen, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, 0, MCAP_FILE_TRANSFER);

	packTLVWord(&packet, 0x0A, ++ft->wReqNum);        // Request sequence
	packDWord(&packet, 0x000F0000);                   // Unknown
	packTLV(&packet, 0x0D, 5, (LPBYTE)"utf-8");               // Charset
	packTLV(&packet, 0x0C, (WORD)strlennull(szCoolStr), (LPBYTE)szCoolStr); // User message (CoolData XML)
	SAFE_FREE(&szCoolStr);
	if (ft->bUseProxy)
	{
		packTLVDWord(&packet, 0x02, ft->dwProxyIP);     // Proxy IP
		packTLVDWord(&packet, 0x16, ft->dwProxyIP ^ 0x0FFFFFFFF);    // Proxy IP check
	}
	else
	{
		packTLVDWord(&packet, 0x02, dwLocalInternalIP);
		packTLVDWord(&packet, 0x16, dwLocalInternalIP ^ 0x0FFFFFFFF);
	}
	packTLVDWord(&packet, 0x03, dwLocalInternalIP);   // Client IP
	if (ft->bUseProxy)
	{
		packTLVWord(&packet, 0x05, ft->wRemotePort);
		packTLVWord(&packet, 0x17, (WORD)(ft->wRemotePort ^ 0x0FFFF));
		packDWord(&packet, 0x00100000);                 // Proxy flag
	}
	else
	{
		oscar_listener *pListener = (oscar_listener*)ft->listener;

		packTLVWord(&packet, 0x05, pListener->wPort);
		packTLVWord(&packet, 0x15, (WORD)((pListener->wPort) ^ 0x0FFFF));
	}
	{ // TLV(0x2711)
		packWord(&packet, 0x2711);
		packWord(&packet, (WORD)(9 + strlennull(pszFiles)));
		packWord(&packet, (WORD)(ft->wFilesCount == 1 ? 1 : 2));
		packWord(&packet, ft->wFilesCount);
		packDWord(&packet, (DWORD)ft->qwTotalSize);
		packBuffer(&packet, (LPBYTE)pszFiles, (WORD)(strlennull(pszFiles) + 1));
	}
	packTLV(&packet, 0x2712, 5, (LPBYTE)"utf-8");
	{ // TLV(0x2713)
		packWord(&packet, 0x2713);
		packWord(&packet, 8);
		packQWord(&packet, ft->qwTotalSize);
	}

	sendServPacket(&packet);                          // Send the monster
}


void CIcqProto::oft_sendFileReply(DWORD dwUin, char *szUid, oscar_filetransfer *ft, WORD wResult)
{
	icq_packet packet;

	packServMsgSendHeader(&packet, 0, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, dwUin, szUid, 2, 0x1E);
	packServTLV5HeaderBasic(&packet, 0, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, wResult, MCAP_FILE_TRANSFER);

	sendServPacket(&packet);
}


void CIcqProto::oft_sendFileAccept(DWORD dwUin, char *szUid, oscar_filetransfer *ft)
{
	oft_sendFileReply(dwUin, szUid, ft, 0x02);
}


void CIcqProto::oft_sendFileResponse(DWORD dwUin, char *szUid, oscar_filetransfer *ft, WORD wResponse)
{
	icq_packet packet;

	packServAdvancedReply(&packet, dwUin, szUid, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, 0, 4);
	packWord(&packet, 0x02);      // Length of following data
	packWord(&packet, wResponse); // Response code

	sendServPacket(&packet);
}


void CIcqProto::oft_sendFileDeny(DWORD dwUin, char *szUid, oscar_filetransfer *ft)
{
	if (dwUin)
	{ // ICQ clients uses special deny file transfer
		oft_sendFileResponse(dwUin, szUid, ft, 0x01);
	}
	else
		oft_sendFileReply(dwUin, szUid, ft, 0x01);
}


void CIcqProto::oft_sendFileCancel(DWORD dwUin, char *szUid, oscar_filetransfer *ft)
{
	oft_sendFileReply(dwUin, szUid, ft, 0x01);
}


void CIcqProto::oft_sendFileRedirect(DWORD dwUin, char *szUid, oscar_filetransfer *ft, DWORD dwIP, WORD wPort, int bProxy)
{
	icq_packet packet;

	packServMsgSendHeader(&packet, 0, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, dwUin, szUid, 2, (WORD)(bProxy ? 0x4a : 0x4e));
	packServTLV5HeaderBasic(&packet, (WORD)(bProxy ? 0x2C : 0x30), ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, 0, MCAP_FILE_TRANSFER);
	// Connection point data
	packTLVWord(&packet, 0x0A, ++ft->wReqNum);                // Ack Type
	packTLVWord(&packet, 0x14, 0x0A);                         // Unknown ?
	packTLVDWord(&packet, 0x02, dwIP);                        // Internal IP / Proxy IP
	packTLVDWord(&packet, 0x16, dwIP ^ 0x0FFFFFFFF);          // IP Check ?
	if (!bProxy)
		packTLVDWord(&packet, 0x03, dwIP);
	packTLVWord(&packet, 0x05, wPort);                        // Listening Port
	packTLVWord(&packet, 0x17, (WORD)(wPort ^ 0x0FFFF));      // Port Check ?
	if (bProxy)
		packDWord(&packet, 0x00100000);                         // Proxy Flag

	sendServPacket(&packet);
}