Exemple #1
0
/* process the packet when removed from a group */
void qq_process_recv_group_im_been_removed
    (guint8 *data, guint8 **cursor, gint len, guint32 internal_group_id, GaimConnection *gc)
{
	guint32 external_group_id, uid;
	guint8 group_type;
	gchar *msg;
	qq_group *group;

	g_return_if_fail(data != NULL && len > 0);

	if (*cursor >= (data + len - 1)) {
		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Received group msg been_removed is empty\n");
		return;
	}

	read_packet_dw(data, cursor, len, &external_group_id);
	read_packet_b(data, cursor, len, &group_type);
	read_packet_dw(data, cursor, len, &uid);

	g_return_if_fail(external_group_id > 0 && uid > 0);

	msg = g_strdup_printf(_("You [%d] has exit group \"%d\""), uid, external_group_id);
	gaim_notify_info(gc, _("QQ Qun Operation"), msg, NULL);

	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
	if (group != NULL) {
		group->my_status = QQ_GROUP_MEMBER_STATUS_NOT_MEMBER;
		qq_group_refresh(gc, group);
	}

	g_free(msg);
}
Exemple #2
0
/* receive an application to join the group */
void qq_process_recv_group_im_apply_join
    (guint8 *data, guint8 **cursor, gint len, guint32 internal_group_id, GaimConnection *gc)
{
	guint32 external_group_id, user_uid;
	guint8 group_type;
	gchar *reason_utf8, *msg, *reason;
	group_member_opt *g;

	g_return_if_fail(internal_group_id > 0 && data != NULL && len > 0);

	if (*cursor >= (data + len - 1)) {
		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Received group msg apply_join is empty\n");
		return;
	}

	read_packet_dw(data, cursor, len, &external_group_id);
	read_packet_b(data, cursor, len, &group_type);
	read_packet_dw(data, cursor, len, &user_uid);

	g_return_if_fail(external_group_id > 0 && user_uid > 0);

	convert_as_pascal_string(*cursor, &reason_utf8, QQ_CHARSET_DEFAULT);

	msg = g_strdup_printf(_("User %d applied to join group %d"), user_uid, external_group_id);
	reason = g_strdup_printf(_("Reason: %s"), reason_utf8);

	g = g_new0(group_member_opt, 1);
	g->gc = gc;
	g->internal_group_id = internal_group_id;
	g->member = user_uid;

	gaim_request_action(gc, _("QQ Qun Operation"),
			    msg, reason,
			    2, g, 3,
			    _("Approve"),
			    G_CALLBACK
			    (qq_group_approve_application_with_struct),
			    _("Reject"),
			    G_CALLBACK
			    (qq_group_reject_application_with_struct),
			    _("Search"), G_CALLBACK(qq_group_search_application_with_struct));

	g_free(reason);
	g_free(msg);
	g_free(reason_utf8);
}
Exemple #3
0
/* the request to join a group is rejected */
void qq_process_recv_group_im_been_rejected
    (guint8 *data, guint8 **cursor, gint len, guint32 internal_group_id, GaimConnection *gc)
{
	guint32 external_group_id, admin_uid;
	guint8 group_type;
	gchar *reason_utf8, *msg, *reason;
	qq_group *group;

	g_return_if_fail(data != NULL && len > 0);

	if (*cursor >= (data + len - 1)) {
		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Received group msg been_rejected is empty\n");
		return;
	}

	read_packet_dw(data, cursor, len, &external_group_id);
	read_packet_b(data, cursor, len, &group_type);
	read_packet_dw(data, cursor, len, &admin_uid);

	g_return_if_fail(external_group_id > 0 && admin_uid > 0);

	convert_as_pascal_string(*cursor, &reason_utf8, QQ_CHARSET_DEFAULT);

	msg = g_strdup_printf
	    (_("You request to join group %d has been rejected by admin %d"), external_group_id, admin_uid);
	reason = g_strdup_printf(_("Reason: %s"), reason_utf8);

	gaim_notify_warning(gc, _("QQ Qun Operation"), msg, reason);

	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
	if (group != NULL) {
		group->my_status = QQ_GROUP_MEMBER_STATUS_NOT_MEMBER;
		qq_group_refresh(gc, group);
	}

	g_free(reason);
	g_free(msg);
	g_free(reason_utf8);
}
Exemple #4
0
/* process the packet when added to a group */
void qq_process_recv_group_im_been_added
    (guint8 *data, guint8 **cursor, gint len, guint32 internal_group_id, GaimConnection *gc)
{
	guint32 external_group_id, uid;
	guint8 group_type;
	qq_group *group;
	gchar *msg;

	g_return_if_fail(data != NULL && len > 0);

	if (*cursor >= (data + len - 1)) {
		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Received group msg been_added is empty\n");
		return;
	}

	read_packet_dw(data, cursor, len, &external_group_id);
	read_packet_b(data, cursor, len, &group_type);
	read_packet_dw(data, cursor, len, &uid);

	g_return_if_fail(external_group_id > 0 && uid > 0);

	msg = g_strdup_printf(_("You [%d] has been added by group \"%d\""), uid, external_group_id);
	gaim_notify_info(gc, _("QQ Qun Operation"), msg, _("This group has been added to your buddy list"));

	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
	if (group != NULL) {
		group->my_status = QQ_GROUP_MEMBER_STATUS_IS_MEMBER;
		qq_group_refresh(gc, group);
	} else {		/* no such group, try to create a dummy first, and then update */
		group = qq_group_create_internal_record(gc, internal_group_id, external_group_id, NULL);
		group->my_status = QQ_GROUP_MEMBER_STATUS_IS_MEMBER;
		qq_group_refresh(gc, group);
		qq_send_cmd_group_get_group_info(gc, group);
		/* the return of this cmd will automatically update the group in blist */
	}

	g_free(msg);
}
Exemple #5
0
/* process login reply packet which includes redirected new server address */
static gint _qq_process_login_redirect(PurpleConnection *gc, guint8 *data, gint len)
{
	gint bytes, ret;
	guint8 *cursor;
	gchar *new_server_str;
	qq_data *qd;
	qq_login_reply_redirect_packet lrrp;

	qd = (qq_data *) gc->proto_data;
	cursor = data;
	bytes = 0;
	/* 000-000: reply code */
	bytes += read_packet_b(data, &cursor, len, &lrrp.result);
	/* 001-004: login uid */
	bytes += read_packet_dw(data, &cursor, len, &lrrp.uid);
	/* 005-008: redirected new server IP */
	bytes += read_packet_data(data, &cursor, len, lrrp.new_server_ip, 4);
	/* 009-010: redirected new server port */
	bytes += read_packet_w(data, &cursor, len, &lrrp.new_server_port);

	if (bytes != QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN) {
		purple_debug(PURPLE_DEBUG_ERROR, "QQ",
			   "Fail parsing login redirect packet, expect %d bytes, read %d bytes\n",
			   QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN, bytes);
		ret = QQ_LOGIN_REPLY_MISC_ERROR;
	} else {		/* start new connection */
		new_server_str = gen_ip_str(lrrp.new_server_ip);
		purple_debug(PURPLE_DEBUG_WARNING, "QQ",
			   "Redirected to new server: %s:%d\n", new_server_str, lrrp.new_server_port);
		qq_connect(gc->account, new_server_str, lrrp.new_server_port, qd->use_tcp, TRUE);
		g_free(new_server_str);
		ret = QQ_LOGIN_REPLY_REDIRECT;
	}

	return ret;
}
Exemple #6
0
void qq_process_get_all_list_with_group_reply(guint8 *buf, gint buf_len, GaimConnection *gc)
{
	qq_data *qd;
	gint len, i, j;
	guint8 *data, *cursor;
	guint8 sub_cmd, reply_code;
	guint32 unknown, position;
	guint32 uid;
	guint8 type, groupid;
	qq_group *group;

	g_return_if_fail(buf != NULL && buf_len != 0);

	qd = (qq_data *) gc->proto_data;
	len = buf_len;
	data = g_newa(guint8, len);
	cursor = data;

	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
		read_packet_b(data, &cursor, len, &sub_cmd);
		g_return_if_fail(sub_cmd == 0x01);
		read_packet_b(data, &cursor, len, &reply_code);
		if(0 != reply_code) {
			gaim_debug(GAIM_DEBUG_WARNING, "QQ", 
					"Get all list with group reply, reply_code(%d) is not zero", reply_code);
		}
		read_packet_dw(data, &cursor, len, &unknown);
		read_packet_dw(data, &cursor, len, &position);
		/* the following data is all list in this packet */
		i = 0;
		j = 0;
		while (cursor < (data + len)) {
			/* 00-03: uid */
			read_packet_dw(data, &cursor, len, &uid);
			/* 04: type 0x1:buddy 0x4:Qun */
			read_packet_b(data, &cursor, len, &type);
			/* 05: groupid*4 */ /* seems to always be 0 */
			read_packet_b(data, &cursor, len, &groupid);
			/*
			gaim_debug(GAIM_DEBUG_INFO, "QQ", "groupid: %i\n", groupid);
			groupid >>= 2;
			*/
			if (uid == 0 || (type != 0x1 && type != 0x4)) {
				gaim_debug(GAIM_DEBUG_INFO, "QQ",
					   "Buddy entry, uid=%d, type=%d", uid, type);
				continue;
			} 
			if(0x1 == type) { /* a buddy */
				/* don't do anything but count - buddies are handled by 
				 * qq_send_packet_get_buddies_list */
				++i;
			} else { /* a group */
				group = qq_group_find_by_id(gc, uid, QQ_INTERNAL_ID);
				if(group == NULL) {
					qq_set_pending_id(&qd->adding_groups_from_server, uid, TRUE);
					group = g_newa(qq_group, 1);
					group->internal_group_id = uid;
					qq_send_cmd_group_get_group_info(gc, group);
				} else {
					group->my_status = QQ_GROUP_MEMBER_STATUS_IS_MEMBER;
					qq_group_refresh(gc, group);
					qq_send_cmd_group_get_group_info(gc, group);
				}
				++j;
			}
		}
		if(cursor > (data + len)) {
			 gaim_debug(GAIM_DEBUG_ERROR, "QQ", 
					"qq_process_get_all_list_with_group_reply: Dangerous error! maybe protocol changed, notify developers!");
		}
		gaim_debug(GAIM_DEBUG_INFO, "QQ", "Get all list done, %d buddies and %d Quns\n", i, j);
	} else {
		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt all list with group");
	}
}
Exemple #7
0
/* process reply for get_buddies_list */
void qq_process_get_buddies_list_reply(guint8 *buf, gint buf_len, GaimConnection *gc)
{
	qq_data *qd;
	qq_buddy *q_bud;
	gint len, bytes, bytes_expected, i;
	guint16 position, unknown;
	guint8 *data, *cursor, pascal_len;
	gchar *name;
	GaimBuddy *b;

	g_return_if_fail(buf != NULL && buf_len != 0);

	qd = (qq_data *) gc->proto_data;
	len = buf_len;
	data = g_newa(guint8, len);
	cursor = data;

	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
		read_packet_w(data, &cursor, len, &position);
		/* the following data is buddy list in this packet */
		i = 0;
		while (cursor < (data + len)) {
			q_bud = g_new0(qq_buddy, 1);
			bytes = 0;
			/* 000-003: uid */
			bytes += read_packet_dw(data, &cursor, len, &q_bud->uid);
			/* 004-005: icon index (1-255) */
			bytes += read_packet_w(data, &cursor, len, &q_bud->face);
			/* 006-006: age */
			bytes += read_packet_b(data, &cursor, len, &q_bud->age);
			/* 007-007: gender */
			bytes += read_packet_b(data, &cursor, len, &q_bud->gender);
			pascal_len = convert_as_pascal_string(cursor, &q_bud->nickname, QQ_CHARSET_DEFAULT);
			cursor += pascal_len;
			bytes += pascal_len;
			bytes += read_packet_w(data, &cursor, len, &unknown);
			/* flag1: (0-7)
			 *        bit1 => qq show
			 * comm_flag: (0-7)
			 *        bit1 => member
			 *        bit4 => TCP mode
			 *        bit5 => open mobile QQ
			 *        bit6 => bind to mobile
			 *        bit7 => whether having a video
			 */
			bytes += read_packet_b(data, &cursor, len, &q_bud->flag1);
			bytes += read_packet_b(data, &cursor, len, &q_bud->comm_flag);

			bytes_expected = 12 + pascal_len;

			if (q_bud->uid == 0 || bytes != bytes_expected) {
				gaim_debug(GAIM_DEBUG_INFO, "QQ",
					   "Buddy entry, expect %d bytes, read %d bytes\n", bytes_expected, bytes);
				g_free(q_bud->nickname);
				g_free(q_bud);
				continue;
			} else {
				i++;
			}

			if (QQ_DEBUG) {
				gaim_debug(GAIM_DEBUG_INFO, "QQ",
					   "buddy [%09d]: flag1=0x%02x, comm_flag=0x%02x\n",
					   q_bud->uid, q_bud->flag1, q_bud->comm_flag);
			}

			name = uid_to_gaim_name(q_bud->uid);
			b = gaim_find_buddy(gc->account, name);
			g_free(name);

			if (b == NULL)
				b = qq_add_buddy_by_recv_packet(gc, q_bud->uid, TRUE, FALSE);

			b->proto_data = q_bud;
			qd->buddies = g_list_append(qd->buddies, q_bud);
			qq_update_buddy_contact(gc, q_bud);
		}

		if(cursor > (data + len)) {
			gaim_debug(GAIM_DEBUG_ERROR, "QQ", 
					"qq_process_get_buddies_list_reply: Dangerous error! maybe protocol changed, notify developers!");
                }
		if (position == QQ_FRIENDS_LIST_POSITION_END) {
			gaim_debug(GAIM_DEBUG_INFO, "QQ", "Get friends list done, %d buddies\n", i);
			qq_send_packet_get_buddies_online(gc, QQ_FRIENDS_ONLINE_POSITION_START);
		} else {
			qq_send_packet_get_buddies_list(gc, position);
		}
	} else {
		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt buddies list");
	}
}
Exemple #8
0
/* process the reply packet for get_buddies_online packet */
void qq_process_get_buddies_online_reply(guint8 *buf, gint buf_len, GaimConnection *gc)
{
	qq_data *qd;
	gint len, bytes;
	guint8 *data, *cursor, position;
	GaimBuddy *b;
	qq_buddy *q_bud;
	qq_friends_online_entry *fe;

	g_return_if_fail(buf != NULL && buf_len != 0);

	qd = (qq_data *) gc->proto_data;
	len = buf_len;
	data = g_newa(guint8, len);
	cursor = data;

	gaim_debug(GAIM_DEBUG_INFO, "QQ", "processing get_buddies_online_reply\n");
	
	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {

		_qq_show_packet("Get buddies online reply packet", data, len);

		read_packet_b(data, &cursor, len, &position);

		fe = g_newa(qq_friends_online_entry, 1);
		fe->s = g_newa(qq_buddy_status, 1);

		while (cursor < (data + len)) {
			/* based on one online buddy entry */
			bytes = 0;
			/* 000-030 qq_buddy_status */
			bytes += qq_buddy_status_read(data, &cursor, len, fe->s);
			/* 031-032: unknown4 */
			bytes += read_packet_w(data, &cursor, len, &fe->unknown1);
			/* 033-033: flag1 */
			bytes += read_packet_b(data, &cursor, len, &fe->flag1);
			/* 034-034: comm_flag */
			bytes += read_packet_b(data, &cursor, len, &fe->comm_flag);
			/* 035-036: */
			bytes += read_packet_w(data, &cursor, len, &fe->unknown2);
			/* 037-037: */
			bytes += read_packet_b(data, &cursor, len, &fe->ending);	/* 0x00 */

			if (fe->s->uid == 0 || bytes != QQ_ONLINE_BUDDY_ENTRY_LEN) {
				gaim_debug(GAIM_DEBUG_ERROR, "QQ", 
						"uid=0 or entry complete len(%d) != %d", 
						bytes, QQ_ONLINE_BUDDY_ENTRY_LEN);
				g_free(fe->s->ip);
				g_free(fe->s->unknown_key);
				continue;
			}	/* check if it is a valid entry */

			if (QQ_DEBUG)
				_qq_buddies_online_reply_dump_unclear(fe);

			/* update buddy information */
			b = gaim_find_buddy(gaim_connection_get_account(gc), uid_to_gaim_name(fe->s->uid));
			q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;

			if (q_bud != NULL) {	/* we find one and update qq_buddy */
				if(0 != fe->s->client_version)
					q_bud->client_version = fe->s->client_version;
				g_memmove(q_bud->ip, fe->s->ip, 4);
				q_bud->port = fe->s->port;
				q_bud->status = fe->s->status;
				q_bud->flag1 = fe->flag1;
				q_bud->comm_flag = fe->comm_flag;
				qq_update_buddy_contact(gc, q_bud);
			}
			else {
				gaim_debug(GAIM_DEBUG_ERROR, "QQ", 
						"Got an online buddy %d, but not in my buddy list\n", fe->s->uid);
			}

			g_free(fe->s->ip);
			g_free(fe->s->unknown_key);
		}
		
		if(cursor > (data + len)) {
			 gaim_debug(GAIM_DEBUG_ERROR, "QQ", 
					"qq_process_get_buddies_online_reply: Dangerous error! maybe protocol changed, notify developers!\n");
		}

		if (position != QQ_FRIENDS_ONLINE_POSITION_END) {
			gaim_debug(GAIM_DEBUG_INFO, "QQ", "Has more online buddies, position from %d\n", position);

			qq_send_packet_get_buddies_online(gc, position);
		}
		else {
			qq_send_packet_get_buddies_levels(gc);
			qq_refresh_all_buddy_status(gc);
		}

	} else {
		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt buddies online");
	}
}
Exemple #9
0
/* process login reply which says OK */
static gint _qq_process_login_ok(PurpleConnection *gc, guint8 *data, gint len)
{
	gint bytes;
	guint8 *cursor;
	qq_data *qd;
	qq_login_reply_ok_packet lrop;

	qd = (qq_data *) gc->proto_data;
	cursor = data;
	bytes = 0;

	/* 000-000: reply code */
	bytes += read_packet_b(data, &cursor, len, &lrop.result);
	/* 001-016: session key */
	lrop.session_key = g_memdup(cursor, QQ_KEY_LENGTH);
	cursor += QQ_KEY_LENGTH;
	bytes += QQ_KEY_LENGTH;
	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Get session_key done\n");
	/* 017-020: login uid */
	bytes += read_packet_dw(data, &cursor, len, &lrop.uid);
	/* 021-024: server detected user public IP */
	bytes += read_packet_data(data, &cursor, len, (guint8 *) &lrop.client_ip, 4);
	/* 025-026: server detected user port */
	bytes += read_packet_w(data, &cursor, len, &lrop.client_port);
	/* 027-030: server detected itself ip 127.0.0.1 ? */
	bytes += read_packet_data(data, &cursor, len, (guint8 *) &lrop.server_ip, 4);
	/* 031-032: server listening port */
	bytes += read_packet_w(data, &cursor, len, &lrop.server_port);
	/* 033-036: login time for current session */
	bytes += read_packet_time(data, &cursor, len, &lrop.login_time);
	/* 037-062: 26 bytes, unknown */
	bytes += read_packet_data(data, &cursor, len, (guint8 *) &lrop.unknown1, 26);
	/* 063-066: unknown server1 ip address */
	bytes += read_packet_data(data, &cursor, len, (guint8 *) &lrop.unknown_server1_ip, 4);
	/* 067-068: unknown server1 port */
	bytes += read_packet_w(data, &cursor, len, &lrop.unknown_server1_port);
	/* 069-072: unknown server2 ip address */
	bytes += read_packet_data(data, &cursor, len, (guint8 *) &lrop.unknown_server2_ip, 4);
	/* 073-074: unknown server2 port */
	bytes += read_packet_w(data, &cursor, len, &lrop.unknown_server2_port);
	/* 075-076: 2 bytes unknown */
	bytes += read_packet_w(data, &cursor, len, &lrop.unknown2);
	/* 077-078: 2 bytes unknown */
	bytes += read_packet_w(data, &cursor, len, &lrop.unknown3);
	/* 079-110: 32 bytes unknown */
	bytes += read_packet_data(data, &cursor, len, (guint8 *) &lrop.unknown4, 32);
	/* 111-122: 12 bytes unknown */
	bytes += read_packet_data(data, &cursor, len, (guint8 *) &lrop.unknown5, 12);
	/* 123-126: login IP of last session */
	bytes += read_packet_data(data, &cursor, len, (guint8 *) &lrop.last_client_ip, 4);
	/* 127-130: login time of last session */
	bytes += read_packet_time(data, &cursor, len, &lrop.last_login_time);
	/* 131-138: 8 bytes unknown */
	bytes += read_packet_data(data, &cursor, len, (guint8 *) &lrop.unknown6, 8);

	if (bytes != QQ_LOGIN_REPLY_OK_PACKET_LEN) {	/* fail parsing login info */
		purple_debug(PURPLE_DEBUG_WARNING, "QQ",
			   "Fail parsing login info, expect %d bytes, read %d bytes\n",
			   QQ_LOGIN_REPLY_OK_PACKET_LEN, bytes);
	}			/* but we still go on as login OK */

	qd->session_key = lrop.session_key;
	qd->session_md5 = _gen_session_md5(qd->uid, qd->session_key);
	qd->my_ip = gen_ip_str(lrop.client_ip);
	qd->my_port = lrop.client_port;
	qd->login_time = lrop.login_time;
	qd->last_login_time = lrop.last_login_time;
	qd->last_login_ip = gen_ip_str(lrop.last_client_ip);

	purple_connection_set_state(gc, PURPLE_CONNECTED);
	qd->logged_in = TRUE;	/* must be defined after sev_finish_login */

	/* now initiate QQ Qun, do it first as it may take longer to finish */
	qq_group_init(gc);

	/* Now goes on updating my icon/nickname, not showing info_window */
	qd->modifying_face = FALSE;
	qq_send_packet_get_info(gc, qd->uid, FALSE);
	/* grab my level */
	qq_send_packet_get_level(gc, qd->uid);

	qq_send_packet_change_status(gc);

	/* refresh buddies */
	qq_send_packet_get_buddies_list(gc, QQ_FRIENDS_LIST_POSITION_START);
	/* refresh groups */
	qq_send_packet_get_all_list_with_group(gc, QQ_FRIENDS_LIST_POSITION_START);

	return QQ_LOGIN_REPLY_OK;
}
Exemple #10
0
/* recv an IM from a group chat */
void qq_process_recv_group_im(guint8 *data, guint8 **cursor, gint data_len, 
		guint32 internal_group_id, GaimConnection *gc, guint16 im_type)
{
	gchar *msg_with_gaim_smiley, *msg_utf8_encoded, *im_src_name, *hex_dump;
	guint16 unknown;
	guint32 unknown4;
	GaimConversation *conv;
	qq_data *qd;
	qq_buddy *member;
	qq_group *group;
	qq_recv_group_im *im_group;
	gint skip_len;

	g_return_if_fail(data != NULL && data_len > 0);
	qd = (qq_data *) gc->proto_data;

	hex_dump = hex_dump_to_str(*cursor, data_len - (*cursor - data));
	gaim_debug(GAIM_DEBUG_INFO, "QQ", "group im hex dump\n%s\n", hex_dump);

	if (*cursor >= (data + data_len - 1)) {
		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Received group im_group is empty\n");
		return;
	}

	im_group = g_newa(qq_recv_group_im, 1);

	read_packet_dw(data, cursor, data_len, &(im_group->external_group_id));
	read_packet_b(data, cursor, data_len, &(im_group->group_type));

	if(QQ_RECV_IM_TEMP_QUN_IM == im_type) {
		read_packet_dw(data, cursor, data_len, &(internal_group_id));
	}

	read_packet_dw(data, cursor, data_len, &(im_group->member_uid));
	read_packet_w(data, cursor, data_len, &unknown);	/* 0x0001? */
	read_packet_w(data, cursor, data_len, &(im_group->msg_seq));
	read_packet_dw(data, cursor, data_len, (guint32 *) & (im_group->send_time));
	read_packet_dw(data, cursor, data_len, &unknown4);	/* versionID */
	/*
	 * length includes font_attr
	 * this msg_len includes msg and font_attr
	 **** the format is ****
	 * length of all
	 * 1. unknown 10 bytes
	 * 2. 0-ended string
	 * 3. font_attr
	 */

	read_packet_w(data, cursor, data_len, &(im_group->msg_len));
	g_return_if_fail(im_group->msg_len > 0);

	/*
	 * 10 bytes from lumaqq
	 *    contentType = buf.getChar();
	 *    totalFragments = buf.get() & 255;
	 *    fragmentSequence = buf.get() & 255;
	 *    messageId = buf.getChar();
	 *    buf.getInt();
	 */

	if(im_type != QQ_RECV_IM_UNKNOWN_QUN_IM)
		skip_len = 10;
	else
		skip_len = 0;
	*cursor += skip_len;

	im_group->msg = g_strdup((gchar *) *cursor);
	*cursor += strlen(im_group->msg) + 1;
	/* there might not be any font_attr, check it */
	im_group->font_attr_len = im_group->msg_len - strlen(im_group->msg) - 1 - skip_len;
	if (im_group->font_attr_len > 0)
		im_group->font_attr = g_memdup(*cursor, im_group->font_attr_len);
	else
		im_group->font_attr = NULL;

	/* group im_group has no flag to indicate whether it has font_attr or not */
	msg_with_gaim_smiley = qq_smiley_to_gaim(im_group->msg);
	if (im_group->font_attr_len > 0)
		msg_utf8_encoded = qq_encode_to_gaim(im_group->font_attr,
						     im_group->font_attr_len, msg_with_gaim_smiley);
	else
		msg_utf8_encoded = qq_to_utf8(msg_with_gaim_smiley, QQ_CHARSET_DEFAULT);

	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
	g_return_if_fail(group != NULL);

	conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, group->group_name_utf8, gaim_connection_get_account(gc));
	if (conv == NULL && gaim_prefs_get_bool("/plugins/prpl/qq/prompt_group_msg_on_recv")) {
		serv_got_joined_chat(gc, qd->channel++, group->group_name_utf8);
		conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, group->group_name_utf8, gaim_connection_get_account(gc));
	}

	if (conv != NULL) {
		member = qq_group_find_member_by_uid(group, im_group->member_uid);
		if (member == NULL || member->nickname == NULL)
			im_src_name = uid_to_gaim_name(im_group->member_uid);
		else
			im_src_name = g_strdup(member->nickname);
		serv_got_chat_in(gc,
				 gaim_conv_chat_get_id(GAIM_CONV_CHAT
						       (conv)), im_src_name, 0, msg_utf8_encoded, im_group->send_time);
		g_free(im_src_name);
	}
	g_free(hex_dump);
	g_free(msg_with_gaim_smiley);
	g_free(msg_utf8_encoded);
	g_free(im_group->msg);
	g_free(im_group->font_attr);
}