changeset 13870:983fd420e86b

[gaim-migrate @ 16340] Performed minor cleanup of the OpenQ codebase and patched it into the Gaim trunk as a prpl, providing basic QQ functionality. committer: Tailor Script <tailor@pidgin.im>
author Mark Huetsch <markhuetsch>
date Mon, 26 Jun 2006 02:58:54 +0000
parents 5642f4658b59
children 2be9dfa9569b
files configure.ac pixmaps/smileys/default/Makefile.am pixmaps/smileys/default/qq_smiley_0.gif pixmaps/smileys/default/qq_smiley_1.gif pixmaps/smileys/default/qq_smiley_10.gif pixmaps/smileys/default/qq_smiley_11.gif pixmaps/smileys/default/qq_smiley_12.gif pixmaps/smileys/default/qq_smiley_13.gif pixmaps/smileys/default/qq_smiley_14.gif pixmaps/smileys/default/qq_smiley_15.gif pixmaps/smileys/default/qq_smiley_16.gif pixmaps/smileys/default/qq_smiley_17.gif pixmaps/smileys/default/qq_smiley_18.gif pixmaps/smileys/default/qq_smiley_19.gif pixmaps/smileys/default/qq_smiley_2.gif pixmaps/smileys/default/qq_smiley_20.gif pixmaps/smileys/default/qq_smiley_21.gif pixmaps/smileys/default/qq_smiley_22.gif pixmaps/smileys/default/qq_smiley_23.gif pixmaps/smileys/default/qq_smiley_24.gif pixmaps/smileys/default/qq_smiley_25.gif pixmaps/smileys/default/qq_smiley_26.gif pixmaps/smileys/default/qq_smiley_27.gif pixmaps/smileys/default/qq_smiley_28.gif pixmaps/smileys/default/qq_smiley_29.gif pixmaps/smileys/default/qq_smiley_3.gif pixmaps/smileys/default/qq_smiley_30.gif pixmaps/smileys/default/qq_smiley_31.gif pixmaps/smileys/default/qq_smiley_32.gif pixmaps/smileys/default/qq_smiley_33.gif pixmaps/smileys/default/qq_smiley_34.gif pixmaps/smileys/default/qq_smiley_35.gif pixmaps/smileys/default/qq_smiley_36.gif pixmaps/smileys/default/qq_smiley_37.gif pixmaps/smileys/default/qq_smiley_38.gif pixmaps/smileys/default/qq_smiley_39.gif pixmaps/smileys/default/qq_smiley_4.gif pixmaps/smileys/default/qq_smiley_40.gif pixmaps/smileys/default/qq_smiley_41.gif pixmaps/smileys/default/qq_smiley_42.gif pixmaps/smileys/default/qq_smiley_43.gif pixmaps/smileys/default/qq_smiley_44.gif pixmaps/smileys/default/qq_smiley_45.gif pixmaps/smileys/default/qq_smiley_46.gif pixmaps/smileys/default/qq_smiley_47.gif pixmaps/smileys/default/qq_smiley_48.gif pixmaps/smileys/default/qq_smiley_49.gif pixmaps/smileys/default/qq_smiley_5.gif pixmaps/smileys/default/qq_smiley_50.gif pixmaps/smileys/default/qq_smiley_51.gif pixmaps/smileys/default/qq_smiley_52.gif pixmaps/smileys/default/qq_smiley_53.gif pixmaps/smileys/default/qq_smiley_54.gif pixmaps/smileys/default/qq_smiley_55.gif pixmaps/smileys/default/qq_smiley_56.gif pixmaps/smileys/default/qq_smiley_57.gif pixmaps/smileys/default/qq_smiley_58.gif pixmaps/smileys/default/qq_smiley_59.gif pixmaps/smileys/default/qq_smiley_6.gif pixmaps/smileys/default/qq_smiley_60.gif pixmaps/smileys/default/qq_smiley_61.gif pixmaps/smileys/default/qq_smiley_62.gif pixmaps/smileys/default/qq_smiley_63.gif pixmaps/smileys/default/qq_smiley_64.gif pixmaps/smileys/default/qq_smiley_65.gif pixmaps/smileys/default/qq_smiley_66.gif pixmaps/smileys/default/qq_smiley_67.gif pixmaps/smileys/default/qq_smiley_68.gif pixmaps/smileys/default/qq_smiley_69.gif pixmaps/smileys/default/qq_smiley_7.gif pixmaps/smileys/default/qq_smiley_70.gif pixmaps/smileys/default/qq_smiley_71.gif pixmaps/smileys/default/qq_smiley_72.gif pixmaps/smileys/default/qq_smiley_73.gif pixmaps/smileys/default/qq_smiley_74.gif pixmaps/smileys/default/qq_smiley_75.gif pixmaps/smileys/default/qq_smiley_76.gif pixmaps/smileys/default/qq_smiley_77.gif pixmaps/smileys/default/qq_smiley_78.gif pixmaps/smileys/default/qq_smiley_79.gif pixmaps/smileys/default/qq_smiley_8.gif pixmaps/smileys/default/qq_smiley_80.gif pixmaps/smileys/default/qq_smiley_81.gif pixmaps/smileys/default/qq_smiley_82.gif pixmaps/smileys/default/qq_smiley_83.gif pixmaps/smileys/default/qq_smiley_84.gif pixmaps/smileys/default/qq_smiley_85.gif pixmaps/smileys/default/qq_smiley_86.gif pixmaps/smileys/default/qq_smiley_87.gif pixmaps/smileys/default/qq_smiley_88.gif pixmaps/smileys/default/qq_smiley_89.gif pixmaps/smileys/default/qq_smiley_9.gif pixmaps/smileys/default/qq_smiley_90.gif pixmaps/smileys/default/qq_smiley_91.gif pixmaps/smileys/default/qq_smiley_92.gif pixmaps/smileys/default/qq_smiley_93.gif pixmaps/smileys/default/qq_smiley_94.gif pixmaps/smileys/default/qq_smiley_95.gif pixmaps/smileys/default/theme pixmaps/status/default/Makefile.am pixmaps/status/default/qq.png src/protocols/Makefile.am src/protocols/qq/Makefile.am src/protocols/qq/Makefile.in src/protocols/qq/Makefile.mingw src/protocols/qq/TODO src/protocols/qq/buddy_info.c src/protocols/qq/buddy_info.h src/protocols/qq/buddy_list.c src/protocols/qq/buddy_list.h src/protocols/qq/buddy_opt.c src/protocols/qq/buddy_opt.h src/protocols/qq/buddy_status.c src/protocols/qq/buddy_status.h src/protocols/qq/char_conv.c src/protocols/qq/char_conv.h src/protocols/qq/crypt.c src/protocols/qq/crypt.h src/protocols/qq/file_trans.c src/protocols/qq/file_trans.h src/protocols/qq/group.c src/protocols/qq/group.h src/protocols/qq/group_admindlg.c src/protocols/qq/group_admindlg.h src/protocols/qq/group_conv.c src/protocols/qq/group_conv.h src/protocols/qq/group_find.c src/protocols/qq/group_find.h src/protocols/qq/group_free.c src/protocols/qq/group_free.h src/protocols/qq/group_hash.c src/protocols/qq/group_hash.h src/protocols/qq/group_im.c src/protocols/qq/group_im.h src/protocols/qq/group_info.c src/protocols/qq/group_info.h src/protocols/qq/group_join.c src/protocols/qq/group_join.h src/protocols/qq/group_misc.c src/protocols/qq/group_misc.h src/protocols/qq/group_network.c src/protocols/qq/group_network.h src/protocols/qq/group_opt.c src/protocols/qq/group_opt.h src/protocols/qq/group_search.c src/protocols/qq/group_search.h src/protocols/qq/header_info.c src/protocols/qq/header_info.h src/protocols/qq/im.c src/protocols/qq/im.h src/protocols/qq/infodlg.c src/protocols/qq/infodlg.h src/protocols/qq/ip_location.c src/protocols/qq/ip_location.h src/protocols/qq/keep_alive.c src/protocols/qq/keep_alive.h src/protocols/qq/login_logout.c src/protocols/qq/login_logout.h src/protocols/qq/packet_parse.c src/protocols/qq/packet_parse.h src/protocols/qq/qq.c src/protocols/qq/qq.h src/protocols/qq/qq_proxy.c src/protocols/qq/qq_proxy.h src/protocols/qq/recv_core.c src/protocols/qq/recv_core.h src/protocols/qq/send_core.c src/protocols/qq/send_core.h src/protocols/qq/send_file.c src/protocols/qq/send_file.h src/protocols/qq/sendqueue.c src/protocols/qq/sendqueue.h src/protocols/qq/show.c src/protocols/qq/show.h src/protocols/qq/sys_msg.c src/protocols/qq/sys_msg.h src/protocols/qq/udp_proxy_s5.c src/protocols/qq/udp_proxy_s5.h src/protocols/qq/utils.c src/protocols/qq/utils.h
diffstat 180 files changed, 17202 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/configure.ac	Sun Jun 25 03:15:41 2006 +0000
+++ b/configure.ac	Mon Jun 26 02:58:54 2006 +0000
@@ -401,7 +401,7 @@
 fi
 
 if test "x$STATIC_PRPLS" = "xall" ; then
-	STATIC_PRPLS="bonjour gg irc jabber msn novell oscar sametime silc simple yahoo zephyr"
+	STATIC_PRPLS="bonjour gg irc jabber msn novell oscar qq sametime silc simple yahoo zephyr"
 fi
 if test "x$have_meanwhile" != "xyes" ; then
 	STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/sametime//'`
@@ -436,6 +436,7 @@
 		msn)		static_msn=yes ;;
 		novell)		static_novell=yes ;;
 		oscar)		static_oscar=yes ;;
+		qq)		static_qq=yes ;;
 		sametime)	static_sametime=yes ;;
 		silc)		static_silc=yes ;;
 		simple)		static_simple=yes ;;
@@ -452,6 +453,7 @@
 AM_CONDITIONAL(STATIC_MSN, test "x$static_msn" = "xyes")
 AM_CONDITIONAL(STATIC_NOVELL, test "x$static_novell" = "xyes")
 AM_CONDITIONAL(STATIC_OSCAR, test "x$static_oscar" = "xyes")
+AM_CONDITIONAL(STATIC_QQ, test "x$static_qq" = "xyes")
 AM_CONDITIONAL(STATIC_SAMETIME, test "x$static_sametime" = "xyes" -a "x$have_meanwhile" = "xyes")
 AM_CONDITIONAL(STATIC_SILC, test "x$static_silc" = "xyes" -a "x$silcincludes" = "xyes" -a "x$silcclient" = "xyes")
 AM_CONDITIONAL(STATIC_SIMPLE, test "x$static_simple" = "xyes")
@@ -464,7 +466,7 @@
 
 AC_ARG_WITH(dynamic_prpls, [AC_HELP_STRING([--with-dynamic-prpls], [specify which protocols to build dynamically])], [DYNAMIC_PRPLS=`echo $withval | $sedpath 's/,/ /g'`])
 if test "x$DYNAMIC_PRPLS" = "xall" ; then
-	DYNAMIC_PRPLS="bonjour gg irc jabber msn novell oscar sametime silc simple yahoo zephyr"
+	DYNAMIC_PRPLS="bonjour gg irc jabber msn novell oscar qq sametime silc simple yahoo zephyr"
 fi
 if test "x$have_meanwhile" != "xyes"; then
         DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/sametime//'`
@@ -488,6 +490,7 @@
 		msn)		dynamic_msn=yes ;;
 		novell)		dynamic_novell=yes ;;
 		oscar)		dynamic_oscar=yes ;;
+		qq)		dynamic_qq=yes ;;
 		sametime)	dynamic_sametime=yes ;;
 		silc)		dynamic_silc=yes ;;
 		simple)		dynamic_simple=yes ;;
@@ -504,6 +507,7 @@
 AM_CONDITIONAL(DYNAMIC_MSN, test "x$dynamic_msn" = "xyes")
 AM_CONDITIONAL(DYNAMIC_NOVELL, test "x$dynamic_novell" = "xyes")
 AM_CONDITIONAL(DYNAMIC_OSCAR, test "x$dynamic_oscar" = "xyes")
+AM_CONDITIONAL(DYNAMIC_QQ, test "x$dynamic_qq" = "xyes")
 AM_CONDITIONAL(DYNAMIC_SAMETIME, test "x$dynamic_sametime" = "xyes" -a "x$have_meanwhile" = "xyes")
 AM_CONDITIONAL(DYNAMIC_SILC, test "x$dynamic_silc" = "xyes" -a "x$silcincludes" = "xyes" -a "x$silcclient" = "xyes")
 AM_CONDITIONAL(DYNAMIC_SIMPLE, test "x$dynamic_simple" = "xyes")
@@ -1772,6 +1776,7 @@
 		   src/protocols/napster/Makefile
 		   src/protocols/novell/Makefile
 		   src/protocols/oscar/Makefile
+		   src/protocols/qq/Makefile
 		   src/protocols/sametime/Makefile
 		   src/protocols/silc/Makefile
 		   src/protocols/simple/Makefile
--- a/pixmaps/smileys/default/Makefile.am	Sun Jun 25 03:15:41 2006 +0000
+++ b/pixmaps/smileys/default/Makefile.am	Mon Jun 26 02:58:54 2006 +0000
@@ -107,6 +107,104 @@
 	msn_wink.gif		\
 	msn_xbox.png
 
+QQ_SMILEYS = \
+        qq_smiley_0.gif \
+        qq_smiley_10.gif \
+        qq_smiley_11.gif \
+        qq_smiley_12.gif \
+        qq_smiley_13.gif \
+        qq_smiley_14.gif \
+        qq_smiley_15.gif \
+        qq_smiley_16.gif \
+        qq_smiley_17.gif \
+        qq_smiley_18.gif \
+        qq_smiley_19.gif \
+        qq_smiley_1.gif \
+        qq_smiley_20.gif \
+        qq_smiley_21.gif \
+        qq_smiley_22.gif \
+        qq_smiley_23.gif \
+        qq_smiley_24.gif \
+        qq_smiley_25.gif \
+        qq_smiley_26.gif \
+        qq_smiley_27.gif \
+        qq_smiley_28.gif \
+        qq_smiley_29.gif \
+        qq_smiley_2.gif \
+        qq_smiley_30.gif \
+        qq_smiley_31.gif \
+        qq_smiley_32.gif \
+        qq_smiley_33.gif \
+        qq_smiley_34.gif \
+        qq_smiley_35.gif \
+        qq_smiley_36.gif \
+        qq_smiley_37.gif \
+        qq_smiley_38.gif \
+        qq_smiley_39.gif \
+        qq_smiley_3.gif \
+        qq_smiley_40.gif \
+        qq_smiley_41.gif \
+        qq_smiley_42.gif \
+        qq_smiley_43.gif \
+        qq_smiley_44.gif \
+        qq_smiley_45.gif \
+        qq_smiley_46.gif \
+        qq_smiley_47.gif \
+        qq_smiley_48.gif \
+        qq_smiley_49.gif \
+        qq_smiley_4.gif \
+        qq_smiley_50.gif \
+        qq_smiley_51.gif \
+        qq_smiley_52.gif \
+        qq_smiley_53.gif \
+        qq_smiley_54.gif \
+        qq_smiley_55.gif \
+        qq_smiley_56.gif \
+        qq_smiley_57.gif \
+        qq_smiley_58.gif \
+        qq_smiley_59.gif \
+        qq_smiley_5.gif \
+        qq_smiley_60.gif \
+        qq_smiley_61.gif \
+        qq_smiley_62.gif \
+        qq_smiley_63.gif \
+        qq_smiley_64.gif \
+        qq_smiley_65.gif \
+        qq_smiley_66.gif \
+        qq_smiley_67.gif \
+        qq_smiley_68.gif \
+        qq_smiley_69.gif \
+        qq_smiley_6.gif \
+        qq_smiley_70.gif \
+        qq_smiley_71.gif \
+        qq_smiley_72.gif \
+        qq_smiley_73.gif \
+        qq_smiley_74.gif \
+        qq_smiley_75.gif \
+        qq_smiley_76.gif \
+        qq_smiley_77.gif \
+        qq_smiley_78.gif \
+        qq_smiley_79.gif \
+        qq_smiley_7.gif \
+        qq_smiley_80.gif \
+        qq_smiley_81.gif \
+        qq_smiley_82.gif \
+        qq_smiley_83.gif \
+        qq_smiley_84.gif \
+        qq_smiley_85.gif \
+        qq_smiley_86.gif \
+        qq_smiley_87.gif \
+        qq_smiley_88.gif \
+        qq_smiley_89.gif \
+        qq_smiley_8.gif \
+        qq_smiley_90.gif \
+        qq_smiley_91.gif \
+        qq_smiley_92.gif \
+        qq_smiley_93.gif \
+        qq_smiley_94.gif \
+        qq_smiley_95.gif \
+        qq_smiley_9.gif
+
 YAHOO_SMILEYS = \
 	yahoo_alien.gif			\
 	yahoo_alien2.gif		\
@@ -194,6 +292,7 @@
 	$(AIM_SMILEYS) \
 	$(MSN_SMILEYS) \
 	$(YAHOO_SMILEYS) \
+	$(QQ_SMILEYS) \
 	theme
 
 EXTRA_DIST = $(gaimsmileypix_DATA)
Binary file pixmaps/smileys/default/qq_smiley_0.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_1.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_10.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_11.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_12.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_13.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_14.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_15.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_16.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_17.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_18.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_19.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_2.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_20.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_21.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_22.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_23.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_24.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_25.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_26.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_27.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_28.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_29.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_3.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_30.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_31.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_32.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_33.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_34.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_35.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_36.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_37.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_38.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_39.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_4.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_40.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_41.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_42.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_43.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_44.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_45.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_46.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_47.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_48.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_49.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_5.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_50.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_51.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_52.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_53.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_54.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_55.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_56.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_57.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_58.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_59.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_6.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_60.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_61.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_62.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_63.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_64.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_65.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_66.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_67.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_68.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_69.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_7.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_70.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_71.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_72.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_73.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_74.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_75.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_76.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_77.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_78.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_79.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_8.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_80.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_81.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_82.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_83.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_84.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_85.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_86.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_87.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_88.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_89.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_9.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_90.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_91.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_92.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_93.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_94.gif has changed
Binary file pixmaps/smileys/default/qq_smiley_95.gif has changed
--- a/pixmaps/smileys/default/theme	Sun Jun 25 03:15:41 2006 +0000
+++ b/pixmaps/smileys/default/theme	Mon Jun 26 02:58:54 2006 +0000
@@ -37,7 +37,7 @@
 msn_tongue.png			:p	:-P	:P	:-p	
 msn_hot.png				(h)	(H)
 msn_angry.png			:@	:-@
-msn_neutral.png			:-|	:|
+msn_neutral.png                 :-|     :|
 msn_weird.png			:s	:-S	:-s	:S
 msn_embarrassed.png		:$	:-$
 msn_sad.png				:(	:-(	:-<
@@ -110,6 +110,104 @@
 msn_xbox.png			(xx)
 msn_cigarette.gif		(ci)
 
+[QQ]
+qq_smiley_0.gif /jy
+qq_smiley_1.gif /se
+qq_smiley_2.gif /pz
+qq_smiley_3.gif /fd
+qq_smiley_4.gif	/dy
+qq_smiley_5.gif /ll
+qq_smiley_6.gif /hx
+qq_smiley_7.gif /bz
+qq_smiley_8.gif /shui
+qq_smiley_9.gif /dk	
+qq_smiley_10.gif /gg
+qq_smiley_11.gif /fn
+qq_smiley_12.gif /tp
+qq_smiley_13.gif /cy
+qq_smiley_14.gif /wx
+qq_smiley_15.gif /ng
+qq_smiley_16.gif /kuk
+qq_smiley_17.gif /feid
+qq_smiley_18.gif /zk
+qq_smiley_19.gif /tu
+qq_smiley_20.gif /tx
+qq_smiley_21.gif /ka
+qq_smiley_22.gif /by
+qq_smiley_23.gif /am
+qq_smiley_24.gif /jie
+qq_smiley_25.gif /kun
+qq_smiley_26.gif /jk
+qq_smiley_27.gif /lh
+qq_smiley_28.gif /hanx
+qq_smiley_29.gif /db
+qq_smiley_30.gif /fendou
+qq_smiley_31.gif /zhm
+qq_smiley_32.gif /yiw
+qq_smiley_33.gif /xu
+qq_smiley_34.gif /yun
+qq_smiley_35.gif /zhem
+qq_smiley_36.gif /shuai
+qq_smiley_37.gif /kl
+qq_smiley_38.gif /qiao
+qq_smiley_39.gif /zj
+qq_smiley_40.gif /shan
+qq_smiley_41.gif /fad
+qq_smiley_42.gif /aiq
+qq_smiley_43.gif /tiao
+qq_smiley_44.gif /zhao
+qq_smiley_45.gif /mm
+qq_smiley_46.gif /zt
+qq_smiley_47.gif /maom
+qq_smiley_48.gif /xg
+qq_smiley_49.gif /yb
+qq_smiley_50.gif /qianc
+qq_smiley_51.gif /dp
+qq_smiley_52.gif /bei
+qq_smiley_53.gif /dg
+qq_smiley_54.gif /shd
+qq_smiley_55.gif /zhd
+qq_smiley_56.gif /dao
+qq_smiley_57.gif /zq
+qq_smiley_58.gif /yy
+qq_smiley_59.gif /bb
+qq_smiley_60.gif /gf
+qq_smiley_61.gif /fan
+qq_smiley_62.gif /yw
+qq_smiley_63.gif /mg
+qq_smiley_64.gif /dx
+qq_smiley_65.gif /wen
+qq_smiley_66.gif /xin
+qq_smiley_67.gif /xs
+qq_smiley_68.gif /hy
+qq_smiley_69.gif /lw
+qq_smiley_70.gif /dh
+qq_smiley_71.gif /sj
+qq_smiley_72.gif /yj
+qq_smiley_73.gif /ds
+qq_smiley_74.gif /ty
+qq_smiley_75.gif /yl
+qq_smiley_76.gif /qiang
+qq_smiley_77.gif /ruo
+qq_smiley_78.gif /ws
+qq_smiley_79.gif /shl
+qq_smiley_80.gif /dd
+qq_smiley_81.gif /mn
+qq_smiley_82.gif /hl
+qq_smiley_83.gif /mamao
+qq_smiley_84.gif /qz
+qq_smiley_85.gif /fw
+qq_smiley_86.gif /oh
+qq_smiley_87.gif /bj
+qq_smiley_88.gif /qsh
+qq_smiley_89.gif /xig
+qq_smiley_90.gif /xy
+qq_smiley_91.gif /duoy
+qq_smiley_92.gif /xr
+qq_smiley_93.gif /xixing
+qq_smiley_94.gif /nv
+qq_smiley_95.gif /nan
+
 [Yahoo]
 yahoo_angel.gif		o:-) O:-) 0:-)
 yahoo_angry.gif		X-( x-( X( x(
--- a/pixmaps/status/default/Makefile.am	Sun Jun 25 03:15:41 2006 +0000
+++ b/pixmaps/status/default/Makefile.am	Mon Jun 26 02:58:54 2006 +0000
@@ -34,6 +34,7 @@
 	offline.png \
 	op.png \
 	pending.png \
+	qq.png \
 	secure.png \
 	silc.png \
 	simple.png \
Binary file pixmaps/status/default/qq.png has changed
--- a/src/protocols/Makefile.am	Sun Jun 25 03:15:41 2006 +0000
+++ b/src/protocols/Makefile.am	Mon Jun 26 02:58:54 2006 +0000
@@ -1,3 +1,3 @@
-DIST_SUBDIRS = bonjour gg irc jabber msn napster novell oscar sametime silc toc simple yahoo zephyr
+DIST_SUBDIRS = bonjour gg irc jabber msn napster novell oscar qq sametime silc toc simple yahoo zephyr
 
 SUBDIRS = $(DYNAMIC_PRPLS) $(STATIC_PRPLS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/Makefile.am	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,108 @@
+EXTRA_DIST = \
+		Makefile.mingw
+
+pkgdir = $(libdir)/gaim
+
+QQSOURCES = \
+	utils.c \
+	utils.h \
+	packet_parse.c \
+	packet_parse.h \
+	buddy_info.c \
+	buddy_info.h \
+	buddy_list.c \
+	buddy_list.h \
+	buddy_opt.c \
+	buddy_opt.h \
+	buddy_status.c \
+	buddy_status.h \
+	qq.c \
+	char_conv.c \
+	char_conv.h \
+	crypt.c \
+	crypt.h \
+	group_admindlg.c \
+	group_admindlg.h \
+	group.c \
+	group_conv.c \
+	group_conv.h \
+	group_find.c \
+	group_find.h \
+	group_free.c \
+	group_free.h \
+	group.h \
+	group_hash.c \
+	group_hash.h \
+	group_im.c \
+	group_im.h \
+	group_info.c \
+	group_info.h \
+	group_join.c \
+	group_join.h \
+	group_misc.c \
+	group_misc.h \
+	group_network.c \
+	group_network.h \
+	group_opt.c \
+	group_opt.h \
+	group_search.c \
+	group_search.h \
+	qq.h \
+	header_info.c \
+	header_info.h \
+	im.c \
+	im.h \
+	infodlg.c \
+	infodlg.h \
+	ip_location.c \
+	ip_location.h \
+	keep_alive.c \
+	keep_alive.h \
+	login_logout.c \
+	login_logout.h \
+	qq_proxy.c \
+	qq_proxy.h \
+	recv_core.c \
+	recv_core.h \
+	send_core.c \
+	send_core.h \
+	sendqueue.c \
+	sendqueue.h \
+	show.c \
+	show.h \
+	sys_msg.c \
+	sys_msg.h \
+	udp_proxy_s5.c \
+	udp_proxy_s5.h \
+	send_file.c \
+	send_file.h \
+	file_trans.c \
+	file_trans.h
+
+AM_CFLAGS = $(st)
+
+libqq_la_LDFLAGS = -module -avoid-version
+
+if STATIC_QQ
+
+st = -DGAIM_STATIC_PRPL
+noinst_LIBRARIES = libqq.a
+libqq_a_SOURCES  = $(QQSOURCES)
+libqq_a_CFLAGS   = $(AM_CFLAGS)
+
+else
+
+st =
+pkg_LTLIBRARIES  = libqq.la
+libqq_la_SOURCES = $(QQSOURCES)
+
+endif
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src \
+	-DDATADIR=\"${datadir}\" \
+        -DVERSION=\"$(VERSION)\" \
+        $(DEBUG_CFLAGS) \
+        $(GTK_CFLAGS) \
+        $(GLIB_CFLAGS) \
+	$(GAIM_CFLAGS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/Makefile.in	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,576 @@
+# Makefile.in generated automatically by automake 1.4-p6 from Makefile.am
+
+# Copyright (C) 1994, 1995-8, 1999, 2001 Free Software Foundation, Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+
+SHELL = @SHELL@
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = @srcdir@
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+
+bindir = @bindir@
+sbindir = @sbindir@
+libexecdir = @libexecdir@
+datadir = @datadir@
+sysconfdir = @sysconfdir@
+sharedstatedir = @sharedstatedir@
+localstatedir = @localstatedir@
+libdir = @libdir@
+infodir = @infodir@
+mandir = @mandir@
+includedir = @includedir@
+oldincludedir = /usr/include
+
+DESTDIR =
+
+pkgdatadir = $(datadir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+
+top_builddir = ../../..
+
+ACLOCAL = @ACLOCAL@
+AUTOCONF = @AUTOCONF@
+AUTOMAKE = @AUTOMAKE@
+AUTOHEADER = @AUTOHEADER@
+
+INSTALL = @INSTALL@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@ $(AM_INSTALL_PROGRAM_FLAGS)
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+transform = @program_transform_name@
+
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_alias = @build_alias@
+build_triplet = @build@
+host_alias = @host_alias@
+host_triplet = @host@
+target_alias = @target_alias@
+target_triplet = @target@
+ALL_LINGUAS = @ALL_LINGUAS@
+AO_CFLAGS = @AO_CFLAGS@
+AO_LIBS = @AO_LIBS@
+AR = @AR@
+AS = @AS@
+AUDIOFILE_CFLAGS = @AUDIOFILE_CFLAGS@
+AUDIOFILE_CONFIG = @AUDIOFILE_CONFIG@
+AUDIOFILE_LIBS = @AUDIOFILE_LIBS@
+BINRELOC_CFLAGS = @BINRELOC_CFLAGS@
+BINRELOC_LIBS = @BINRELOC_LIBS@
+BUILD_INCLUDED_LIBINTL = @BUILD_INCLUDED_LIBINTL@
+CATALOGS = @CATALOGS@
+CATOBJEXT = @CATOBJEXT@
+CC = @CC@
+CFLAGS = @CFLAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+DATADIRNAME = @DATADIRNAME@
+DBUS_CFLAGS = @DBUS_CFLAGS@
+DBUS_LIBS = @DBUS_LIBS@
+DBUS_SERVICES_DIR = @DBUS_SERVICES_DIR@
+DEBUG_CFLAGS = @DEBUG_CFLAGS@
+DLLTOOL = @DLLTOOL@
+DOT = @DOT@
+DOXYGEN = @DOXYGEN@
+DYNALOADER_A = @DYNALOADER_A@
+DYNAMIC_PRPLS = @DYNAMIC_PRPLS@
+ECHO = @ECHO@
+EGREP = @EGREP@
+EVOLUTION_ADDRESSBOOK_CFLAGS = @EVOLUTION_ADDRESSBOOK_CFLAGS@
+EVOLUTION_ADDRESSBOOK_LIBS = @EVOLUTION_ADDRESSBOOK_LIBS@
+EXEEXT = @EXEEXT@
+F77 = @F77@
+GADU_CFLAGS = @GADU_CFLAGS@
+GADU_LIBS = @GADU_LIBS@
+GCJ = @GCJ@
+GCJFLAGS = @GCJFLAGS@
+GENCAT = @GENCAT@
+GETTEXT_PACKAGE = @GETTEXT_PACKAGE@
+GLIBC2 = @GLIBC2@
+GLIBC21 = @GLIBC21@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_GENMARSHAL = @GLIB_GENMARSHAL@
+GLIB_LIBS = @GLIB_LIBS@
+GLIB_MKENUMS = @GLIB_MKENUMS@
+GMOFILES = @GMOFILES@
+GMSGFMT = @GMSGFMT@
+GNUTLS_CFLAGS = @GNUTLS_CFLAGS@
+GNUTLS_LIBS = @GNUTLS_LIBS@
+GOBJECT_QUERY = @GOBJECT_QUERY@
+GSTREAMER_CFLAGS = @GSTREAMER_CFLAGS@
+GSTREAMER_LIBS = @GSTREAMER_LIBS@
+GTKSPELL_CFLAGS = @GTKSPELL_CFLAGS@
+GTKSPELL_LIBS = @GTKSPELL_LIBS@
+GTK_CFLAGS = @GTK_CFLAGS@
+GTK_LIBS = @GTK_LIBS@
+HAVE_ASPRINTF = @HAVE_ASPRINTF@
+HAVE_LIB = @HAVE_LIB@
+HAVE_POSIX_PRINTF = @HAVE_POSIX_PRINTF@
+HAVE_SNPRINTF = @HAVE_SNPRINTF@
+HAVE_WPRINTF = @HAVE_WPRINTF@
+HOWL_CFLAGS = @HOWL_CFLAGS@
+HOWL_LIBS = @HOWL_LIBS@
+INSTOBJEXT = @INSTOBJEXT@
+INTLBISON = @INTLBISON@
+INTLLIBS = @INTLLIBS@
+INTLOBJS = @INTLOBJS@
+INTLTOOL_CAVES_RULE = @INTLTOOL_CAVES_RULE@
+INTLTOOL_DESKTOP_RULE = @INTLTOOL_DESKTOP_RULE@
+INTLTOOL_DIRECTORY_RULE = @INTLTOOL_DIRECTORY_RULE@
+INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@
+INTLTOOL_ICONV = @INTLTOOL_ICONV@
+INTLTOOL_KBD_RULE = @INTLTOOL_KBD_RULE@
+INTLTOOL_KEYS_RULE = @INTLTOOL_KEYS_RULE@
+INTLTOOL_MERGE = @INTLTOOL_MERGE@
+INTLTOOL_MSGFMT = @INTLTOOL_MSGFMT@
+INTLTOOL_MSGMERGE = @INTLTOOL_MSGMERGE@
+INTLTOOL_OAF_RULE = @INTLTOOL_OAF_RULE@
+INTLTOOL_PERL = @INTLTOOL_PERL@
+INTLTOOL_PONG_RULE = @INTLTOOL_PONG_RULE@
+INTLTOOL_PROP_RULE = @INTLTOOL_PROP_RULE@
+INTLTOOL_SCHEMAS_RULE = @INTLTOOL_SCHEMAS_RULE@
+INTLTOOL_SERVER_RULE = @INTLTOOL_SERVER_RULE@
+INTLTOOL_SERVICE_RULE = @INTLTOOL_SERVICE_RULE@
+INTLTOOL_SHEET_RULE = @INTLTOOL_SHEET_RULE@
+INTLTOOL_SOUNDLIST_RULE = @INTLTOOL_SOUNDLIST_RULE@
+INTLTOOL_THEME_RULE = @INTLTOOL_THEME_RULE@
+INTLTOOL_UI_RULE = @INTLTOOL_UI_RULE@
+INTLTOOL_UPDATE = @INTLTOOL_UPDATE@
+INTLTOOL_XAM_RULE = @INTLTOOL_XAM_RULE@
+INTLTOOL_XGETTEXT = @INTLTOOL_XGETTEXT@
+INTLTOOL_XML_NOMERGE_RULE = @INTLTOOL_XML_NOMERGE_RULE@
+INTLTOOL_XML_RULE = @INTLTOOL_XML_RULE@
+INTL_LIBTOOL_SUFFIX_PREFIX = @INTL_LIBTOOL_SUFFIX_PREFIX@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+KRB4_CFLAGS = @KRB4_CFLAGS@
+KRB4_LDFLAGS = @KRB4_LDFLAGS@
+KRB4_LIBS = @KRB4_LIBS@
+LDADD = @LDADD@
+LIB = @LIB@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBPERL_A = @LIBPERL_A@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBXML_CFLAGS = @LIBXML_CFLAGS@
+LIBXML_LIBS = @LIBXML_LIBS@
+LN_S = @LN_S@
+LTLIB = @LTLIB@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+MAKEINFO = @MAKEINFO@
+MEANWHILE_CFLAGS = @MEANWHILE_CFLAGS@
+MEANWHILE_LIBS = @MEANWHILE_LIBS@
+MKINSTALLDIRS = @MKINSTALLDIRS@
+MONO_CFLAGS = @MONO_CFLAGS@
+MONO_LIBS = @MONO_LIBS@
+NSS_CFLAGS = @NSS_CFLAGS@
+NSS_LIBS = @NSS_LIBS@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PERL = @PERL@
+PERL_CFLAGS = @PERL_CFLAGS@
+PERL_LIBS = @PERL_LIBS@
+PERL_MM_PARAMS = @PERL_MM_PARAMS@
+PKG_CONFIG = @PKG_CONFIG@
+POFILES = @POFILES@
+POSUB = @POSUB@
+PO_IN_DATADIR_FALSE = @PO_IN_DATADIR_FALSE@
+PO_IN_DATADIR_TRUE = @PO_IN_DATADIR_TRUE@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RC = @RC@
+SASL_LIBS = @SASL_LIBS@
+SILC_CFLAGS = @SILC_CFLAGS@
+SILC_LIBS = @SILC_LIBS@
+SM_LIBS = @SM_LIBS@
+STARTUP_NOTIFICATION_CFLAGS = @STARTUP_NOTIFICATION_CFLAGS@
+STARTUP_NOTIFICATION_LIBS = @STARTUP_NOTIFICATION_LIBS@
+STATIC_LINK_LIBS = @STATIC_LINK_LIBS@
+STATIC_PRPLS = @STATIC_PRPLS@
+STRIP = @STRIP@
+TCL_CFLAGS = @TCL_CFLAGS@
+TCL_LIBS = @TCL_LIBS@
+TK_LIBS = @TK_LIBS@
+USE_INCLUDED_LIBINTL = @USE_INCLUDED_LIBINTL@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+XSS_LIBS = @XSS_LIBS@
+ZEPHYR_CFLAGS = @ZEPHYR_CFLAGS@
+ZEPHYR_LDFLAGS = @ZEPHYR_LDFLAGS@
+ZEPHYR_LIBS = @ZEPHYR_LIBS@
+enable_dbus = @enable_dbus@
+enable_dot = @enable_dot@
+enable_doxygen = @enable_doxygen@
+gaimpath = @gaimpath@
+perlpath = @perlpath@
+sedpath = @sedpath@
+
+EXTRA_DIST =  		Makefile.mingw
+
+
+pkgdir = $(libdir)/gaim
+
+QQSOURCES =  	utils.c 	utils.h 	packet_parse.c 	packet_parse.h 	buddy_info.c 	buddy_info.h 	buddy_list.c 	buddy_list.h 	buddy_opt.c 	buddy_opt.h 	buddy_status.c 	buddy_status.h 	qq.c 	char_conv.c 	char_conv.h 	crypt.c 	crypt.h 	group_admindlg.c 	group_admindlg.h 	group.c 	group_conv.c 	group_conv.h 	group_find.c 	group_find.h 	group_free.c 	group_free.h 	group.h 	group_hash.c 	group_hash.h 	group_im.c 	group_im.h 	group_info.c 	group_info.h 	group_join.c 	group_join.h 	group_misc.c 	group_misc.h 	group_network.c 	group_network.h 	group_opt.c 	group_opt.h 	group_search.c 	group_search.h 	qq.h 	header_info.c 	header_info.h 	im.c 	im.h 	infodlg.c 	infodlg.h 	ip_location.c 	ip_location.h 	keep_alive.c 	keep_alive.h 	login_logout.c 	login_logout.h 	qq_proxy.c 	qq_proxy.h 	recv_core.c 	recv_core.h 	send_core.c 	send_core.h 	sendqueue.c 	sendqueue.h 	show.c 	show.h 	sys_msg.c 	sys_msg.h 	udp_proxy_s5.c 	udp_proxy_s5.h 	send_file.c 	send_file.h 	file_trans.c 	file_trans.h
+
+
+AM_CFLAGS = $(st)
+
+libqq_la_LDFLAGS = -module -avoid-version
+
+@STATIC_QQ_TRUE@st = -DGAIM_STATIC_PRPL
+@STATIC_QQ_FALSE@st = 
+@STATIC_QQ_TRUE@noinst_LIBRARIES = libqq.a
+@STATIC_QQ_TRUE@libqq_a_SOURCES = $(QQSOURCES)
+@STATIC_QQ_TRUE@libqq_a_CFLAGS = $(AM_CFLAGS)
+@STATIC_QQ_FALSE@pkg_LTLIBRARIES = libqq.la
+@STATIC_QQ_FALSE@libqq_la_SOURCES = $(QQSOURCES)
+
+AM_CPPFLAGS =  	-I$(top_srcdir)/src 	-DDATADIR=\"${datadir}\"         -DVERSION=\"$(VERSION)\"         $(DEBUG_CFLAGS)         $(GTK_CFLAGS)         $(GLIB_CFLAGS) 	$(GAIM_CFLAGS)
+
+mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
+CONFIG_HEADER = ../../../config.h
+CONFIG_CLEAN_FILES = 
+LIBRARIES =  $(noinst_LIBRARIES)
+
+
+DEFS = @DEFS@ -I. -I$(srcdir) -I../../..
+CPPFLAGS = @CPPFLAGS@
+LDFLAGS = @LDFLAGS@
+X_CFLAGS = @X_CFLAGS@
+X_LIBS = @X_LIBS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+libqq_a_LIBADD = 
+@STATIC_QQ_TRUE@libqq_a_OBJECTS =  utils.$(OBJEXT) \
+@STATIC_QQ_TRUE@packet_parse.$(OBJEXT) buddy_info.$(OBJEXT) \
+@STATIC_QQ_TRUE@buddy_list.$(OBJEXT) buddy_opt.$(OBJEXT) \
+@STATIC_QQ_TRUE@buddy_status.$(OBJEXT) qq.$(OBJEXT) char_conv.$(OBJEXT) \
+@STATIC_QQ_TRUE@crypt.$(OBJEXT) group_admindlg.$(OBJEXT) \
+@STATIC_QQ_TRUE@group.$(OBJEXT) group_conv.$(OBJEXT) \
+@STATIC_QQ_TRUE@group_find.$(OBJEXT) group_free.$(OBJEXT) \
+@STATIC_QQ_TRUE@group_hash.$(OBJEXT) group_im.$(OBJEXT) \
+@STATIC_QQ_TRUE@group_info.$(OBJEXT) group_join.$(OBJEXT) \
+@STATIC_QQ_TRUE@group_misc.$(OBJEXT) group_network.$(OBJEXT) \
+@STATIC_QQ_TRUE@group_opt.$(OBJEXT) group_search.$(OBJEXT) \
+@STATIC_QQ_TRUE@header_info.$(OBJEXT) im.$(OBJEXT) infodlg.$(OBJEXT) \
+@STATIC_QQ_TRUE@ip_location.$(OBJEXT) keep_alive.$(OBJEXT) \
+@STATIC_QQ_TRUE@login_logout.$(OBJEXT) qq_proxy.$(OBJEXT) \
+@STATIC_QQ_TRUE@recv_core.$(OBJEXT) send_core.$(OBJEXT) \
+@STATIC_QQ_TRUE@sendqueue.$(OBJEXT) show.$(OBJEXT) sys_msg.$(OBJEXT) \
+@STATIC_QQ_TRUE@udp_proxy_s5.$(OBJEXT) send_file.$(OBJEXT) \
+@STATIC_QQ_TRUE@file_trans.$(OBJEXT)
+LTLIBRARIES =  $(pkg_LTLIBRARIES)
+
+libqq_la_LIBADD = 
+@STATIC_QQ_FALSE@libqq_la_OBJECTS =  utils.lo packet_parse.lo \
+@STATIC_QQ_FALSE@buddy_info.lo buddy_list.lo buddy_opt.lo \
+@STATIC_QQ_FALSE@buddy_status.lo qq.lo char_conv.lo crypt.lo \
+@STATIC_QQ_FALSE@group_admindlg.lo group.lo group_conv.lo group_find.lo \
+@STATIC_QQ_FALSE@group_free.lo group_hash.lo group_im.lo group_info.lo \
+@STATIC_QQ_FALSE@group_join.lo group_misc.lo group_network.lo \
+@STATIC_QQ_FALSE@group_opt.lo group_search.lo header_info.lo im.lo \
+@STATIC_QQ_FALSE@infodlg.lo ip_location.lo keep_alive.lo \
+@STATIC_QQ_FALSE@login_logout.lo qq_proxy.lo recv_core.lo send_core.lo \
+@STATIC_QQ_FALSE@sendqueue.lo show.lo sys_msg.lo udp_proxy_s5.lo \
+@STATIC_QQ_FALSE@send_file.lo file_trans.lo
+COMPILE = $(CC) $(DEFS) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) --mode=compile $(CC) $(DEFS) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(LIBTOOL) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(LDFLAGS) -o $@
+DIST_COMMON =  Makefile.am Makefile.in
+
+
+DISTFILES = $(DIST_COMMON) $(SOURCES) $(HEADERS) $(TEXINFOS) $(EXTRA_DIST)
+
+TAR = tar
+GZIP_ENV = --best
+DEP_FILES =  .deps/buddy_info.P .deps/buddy_list.P .deps/buddy_opt.P \
+.deps/buddy_status.P .deps/char_conv.P .deps/crypt.P .deps/file_trans.P \
+.deps/group.P .deps/group_admindlg.P .deps/group_conv.P \
+.deps/group_find.P .deps/group_free.P .deps/group_hash.P \
+.deps/group_im.P .deps/group_info.P .deps/group_join.P \
+.deps/group_misc.P .deps/group_network.P .deps/group_opt.P \
+.deps/group_search.P .deps/header_info.P .deps/im.P .deps/infodlg.P \
+.deps/ip_location.P .deps/keep_alive.P .deps/login_logout.P \
+.deps/packet_parse.P .deps/qq.P .deps/qq_proxy.P .deps/recv_core.P \
+.deps/send_core.P .deps/send_file.P .deps/sendqueue.P .deps/show.P \
+.deps/sys_msg.P .deps/udp_proxy_s5.P .deps/utils.P
+SOURCES = $(libqq_a_SOURCES) $(libqq_la_SOURCES)
+OBJECTS = $(libqq_a_OBJECTS) $(libqq_la_OBJECTS)
+
+all: all-redirect
+.SUFFIXES:
+.SUFFIXES: .S .c .lo .o .obj .s
+$(srcdir)/Makefile.in: Makefile.am $(top_srcdir)/configure.ac $(ACLOCAL_M4) 
+	cd $(top_srcdir) && $(AUTOMAKE) --gnu src/protocols/qq/Makefile
+
+Makefile: $(srcdir)/Makefile.in  $(top_builddir)/config.status $(BUILT_SOURCES)
+	cd $(top_builddir) \
+	  && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= $(SHELL) ./config.status
+
+
+mostlyclean-noinstLIBRARIES:
+
+clean-noinstLIBRARIES:
+	-test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+
+distclean-noinstLIBRARIES:
+
+maintainer-clean-noinstLIBRARIES:
+
+# FIXME: We should only use cygpath when building on Windows,
+# and only if it is available.
+.c.obj:
+	$(COMPILE) -c `cygpath -w $<`
+
+.s.o:
+	$(COMPILE) -c $<
+
+.S.o:
+	$(COMPILE) -c $<
+
+mostlyclean-compile:
+	-rm -f *.o core *.core
+	-rm -f *.$(OBJEXT)
+
+clean-compile:
+
+distclean-compile:
+	-rm -f *.tab.c
+
+maintainer-clean-compile:
+
+.s.lo:
+	$(LIBTOOL) --mode=compile $(COMPILE) -c $<
+
+.S.lo:
+	$(LIBTOOL) --mode=compile $(COMPILE) -c $<
+
+mostlyclean-libtool:
+	-rm -f *.lo
+
+clean-libtool:
+	-rm -rf .libs _libs
+
+distclean-libtool:
+
+maintainer-clean-libtool:
+
+libqq.a: $(libqq_a_OBJECTS) $(libqq_a_DEPENDENCIES)
+	-rm -f libqq.a
+	$(AR) cru libqq.a $(libqq_a_OBJECTS) $(libqq_a_LIBADD)
+	$(RANLIB) libqq.a
+
+mostlyclean-pkgLTLIBRARIES:
+
+clean-pkgLTLIBRARIES:
+	-test -z "$(pkg_LTLIBRARIES)" || rm -f $(pkg_LTLIBRARIES)
+
+distclean-pkgLTLIBRARIES:
+
+maintainer-clean-pkgLTLIBRARIES:
+
+install-pkgLTLIBRARIES: $(pkg_LTLIBRARIES)
+	@$(NORMAL_INSTALL)
+	$(mkinstalldirs) $(DESTDIR)$(pkgdir)
+	@list='$(pkg_LTLIBRARIES)'; for p in $$list; do \
+	  if test -f $$p; then \
+	    echo "$(LIBTOOL)  --mode=install $(INSTALL) $$p $(DESTDIR)$(pkgdir)/$$p"; \
+	    $(LIBTOOL)  --mode=install $(INSTALL) $$p $(DESTDIR)$(pkgdir)/$$p; \
+	  else :; fi; \
+	done
+
+uninstall-pkgLTLIBRARIES:
+	@$(NORMAL_UNINSTALL)
+	list='$(pkg_LTLIBRARIES)'; for p in $$list; do \
+	  $(LIBTOOL)  --mode=uninstall rm -f $(DESTDIR)$(pkgdir)/$$p; \
+	done
+
+libqq.la: $(libqq_la_OBJECTS) $(libqq_la_DEPENDENCIES)
+	$(LINK) -rpath $(pkgdir) $(libqq_la_LDFLAGS) $(libqq_la_OBJECTS) $(libqq_la_LIBADD) $(LIBS)
+
+tags: TAGS
+
+ID: $(HEADERS) $(SOURCES) $(LISP)
+	list='$(SOURCES) $(HEADERS)'; \
+	unique=`for i in $$list; do echo $$i; done | \
+	  awk '    { files[$$0] = 1; } \
+	       END { for (i in files) print i; }'`; \
+	here=`pwd` && cd $(srcdir) \
+	  && mkid -f$$here/ID $$unique $(LISP)
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) $(LISP)
+	tags=; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)'; \
+	unique=`for i in $$list; do echo $$i; done | \
+	  awk '    { files[$$0] = 1; } \
+	       END { for (i in files) print i; }'`; \
+	test -z "$(ETAGS_ARGS)$$unique$(LISP)$$tags" \
+	  || (cd $(srcdir) && etags -o $$here/TAGS $(ETAGS_ARGS) $$tags  $$unique $(LISP))
+
+mostlyclean-tags:
+
+clean-tags:
+
+distclean-tags:
+	-rm -f TAGS ID
+
+maintainer-clean-tags:
+
+distdir = $(top_builddir)/$(PACKAGE)-$(VERSION)/$(subdir)
+
+subdir = src/protocols/qq
+
+distdir: $(DISTFILES)
+	here=`cd $(top_builddir) && pwd`; \
+	top_distdir=`cd $(top_distdir) && pwd`; \
+	distdir=`cd $(distdir) && pwd`; \
+	cd $(top_srcdir) \
+	  && $(AUTOMAKE) --include-deps --build-dir=$$here --srcdir-name=$(top_srcdir) --output-dir=$$top_distdir --gnu src/protocols/qq/Makefile
+	@for file in $(DISTFILES); do \
+	  d=$(srcdir); \
+	  if test -d $$d/$$file; then \
+	    cp -pr $$d/$$file $(distdir)/$$file; \
+	  else \
+	    test -f $(distdir)/$$file \
+	    || ln $$d/$$file $(distdir)/$$file 2> /dev/null \
+	    || cp -p $$d/$$file $(distdir)/$$file || :; \
+	  fi; \
+	done
+
+DEPS_MAGIC := $(shell mkdir .deps > /dev/null 2>&1 || :)
+
+-include $(DEP_FILES)
+
+mostlyclean-depend:
+
+clean-depend:
+
+distclean-depend:
+	-rm -rf .deps
+
+maintainer-clean-depend:
+
+%.o: %.c
+	@echo '$(COMPILE) -c $<'; \
+	$(COMPILE) -Wp,-MD,.deps/$(*F).pp -c $<
+	@-cp .deps/$(*F).pp .deps/$(*F).P; \
+	tr ' ' '\012' < .deps/$(*F).pp \
+	  | sed -e 's/^\\$$//' -e '/^$$/ d' -e '/:$$/ d' -e 's/$$/ :/' \
+	    >> .deps/$(*F).P; \
+	rm .deps/$(*F).pp
+
+%.lo: %.c
+	@echo '$(LTCOMPILE) -c $<'; \
+	$(LTCOMPILE) -Wp,-MD,.deps/$(*F).pp -c $<
+	@-sed -e 's/^\([^:]*\)\.o[ 	]*:/\1.lo \1.o :/' \
+	  < .deps/$(*F).pp > .deps/$(*F).P; \
+	tr ' ' '\012' < .deps/$(*F).pp \
+	  | sed -e 's/^\\$$//' -e '/^$$/ d' -e '/:$$/ d' -e 's/$$/ :/' \
+	    >> .deps/$(*F).P; \
+	rm -f .deps/$(*F).pp
+info-am:
+info: info-am
+dvi-am:
+dvi: dvi-am
+check-am: all-am
+check: check-am
+installcheck-am:
+installcheck: installcheck-am
+install-exec-am:
+install-exec: install-exec-am
+
+install-data-am: install-pkgLTLIBRARIES
+install-data: install-data-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+install: install-am
+uninstall-am: uninstall-pkgLTLIBRARIES
+uninstall: uninstall-am
+all-am: Makefile $(LIBRARIES) $(LTLIBRARIES)
+all-redirect: all-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) AM_INSTALL_PROGRAM_FLAGS=-s install
+installdirs:
+	$(mkinstalldirs)  $(DESTDIR)$(pkgdir)
+
+
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-rm -f Makefile $(CONFIG_CLEAN_FILES)
+	-rm -f config.cache config.log stamp-h stamp-h[0-9]*
+
+maintainer-clean-generic:
+mostlyclean-am:  mostlyclean-noinstLIBRARIES mostlyclean-compile \
+		mostlyclean-libtool mostlyclean-pkgLTLIBRARIES \
+		mostlyclean-tags mostlyclean-depend mostlyclean-generic
+
+mostlyclean: mostlyclean-am
+
+clean-am:  clean-noinstLIBRARIES clean-compile clean-libtool \
+		clean-pkgLTLIBRARIES clean-tags clean-depend \
+		clean-generic mostlyclean-am
+
+clean: clean-am
+
+distclean-am:  distclean-noinstLIBRARIES distclean-compile \
+		distclean-libtool distclean-pkgLTLIBRARIES \
+		distclean-tags distclean-depend distclean-generic \
+		clean-am
+	-rm -f libtool
+
+distclean: distclean-am
+
+maintainer-clean-am:  maintainer-clean-noinstLIBRARIES \
+		maintainer-clean-compile maintainer-clean-libtool \
+		maintainer-clean-pkgLTLIBRARIES maintainer-clean-tags \
+		maintainer-clean-depend maintainer-clean-generic \
+		distclean-am
+	@echo "This command is intended for maintainers to use;"
+	@echo "it deletes files that may require special tools to rebuild."
+
+maintainer-clean: maintainer-clean-am
+
+.PHONY: mostlyclean-noinstLIBRARIES distclean-noinstLIBRARIES \
+clean-noinstLIBRARIES maintainer-clean-noinstLIBRARIES \
+mostlyclean-compile distclean-compile clean-compile \
+maintainer-clean-compile mostlyclean-libtool distclean-libtool \
+clean-libtool maintainer-clean-libtool mostlyclean-pkgLTLIBRARIES \
+distclean-pkgLTLIBRARIES clean-pkgLTLIBRARIES \
+maintainer-clean-pkgLTLIBRARIES uninstall-pkgLTLIBRARIES \
+install-pkgLTLIBRARIES tags mostlyclean-tags distclean-tags clean-tags \
+maintainer-clean-tags distdir mostlyclean-depend distclean-depend \
+clean-depend maintainer-clean-depend info-am info dvi-am dvi check \
+check-am installcheck-am installcheck install-exec-am install-exec \
+install-data-am install-data install-am install uninstall-am uninstall \
+all-redirect all-am all installdirs mostlyclean-generic \
+distclean-generic clean-generic maintainer-clean-generic clean \
+mostlyclean distclean maintainer-clean
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/Makefile.mingw	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,176 @@
+#
+# Makefile.mingw
+#
+# Description: Makefile for win32 (mingw) version of OpenQ src
+#
+
+#
+# PATHS
+#
+
+INCLUDE_DIR :=		.
+OPENQ_TOP :=		../
+GTK_TOP :=		../../../../win32-dev/gtk_2_0
+GAIM_TOP :=		../../..
+OPENQ_ROOT :=		.
+GAIM_INSTALL_DIR :=	$(GAIM_TOP)/win32-install-dir
+
+##
+## VARIABLE DEFINITIONS
+##
+
+TARGET = openq
+
+TYPE = PLUGIN
+
+OPENQ_VERSION := $(shell cat $(OPENQ_TOP)/VERSION)
+
+# Compiler Options
+
+CFLAGS =
+
+DEFINES = -DOPENQ_VERSION=\"$(OPENQ_VERSION)\"
+
+# Static or Plugin... 
+ifeq ($(TYPE),STATIC)
+	DEFINES += -DSTATIC
+	DLL_INSTALL_DIR =	$(GAIM_INSTALL_DIR)
+else
+ifeq ($(TYPE),PLUGIN)
+	DLL_INSTALL_DIR =	$(GAIM_INSTALL_DIR)/plugins
+endif
+endif
+
+
+##
+## INCLUDE  MAKEFILES
+##
+
+include $(GAIM_TOP)/src/win32/global.mak
+
+##
+## INCLUDE PATHS
+##
+
+INCLUDE_PATHS += \
+	-I$(OPENQ_ROOT) \
+	-I$(GTK_TOP)/include \
+	-I$(GTK_TOP)/include/gtk-2.0 \
+	-I$(GTK_TOP)/include/glib-2.0 \
+	-I$(GTK_TOP)/include/pango-1.0 \
+	-I$(GTK_TOP)/include/atk-1.0 \
+	-I$(GTK_TOP)/lib/glib-2.0/include \
+	-I$(GTK_TOP)/lib/gtk-2.0/include \
+	-I$(GAIM_TOP)/src \
+	-I$(GAIM_TOP)/src/win32 \
+	-I$(GAIM_TOP)
+
+LIB_PATHS = \
+	-L$(GTK_TOP)/lib \
+	-L$(GAIM_TOP)/src
+
+##
+##  SOURCES, OBJECTS
+##
+
+C_SRC = \
+	buddy_info.c \
+	buddy_list.c \
+	buddy_opt.c \
+	buddy_status.c \
+	char_conv.c \
+	crypt.c \
+	file_trans.c \
+	group.c \
+	group_admindlg.c \
+	group_conv.c \
+	group_find.c \
+	group_free.c \
+	group_hash.c \
+	group_im.c \
+	group_info.c \
+	group_join.c \
+	group_misc.c \
+	group_network.c \
+	group_opt.c \
+	group_search.c \
+	header_info.c \
+	im.c \
+	infodlg.c \
+	ip_location.c \
+	keep_alive.c \
+	login_logout.c \
+	packet_parse.c \
+	qq.c \
+	qq_proxy.c \
+	recv_core.c \
+	send_core.c \
+	send_file.c \
+	sendqueue.c \
+	show.c \
+	sys_msg.c \
+	udp_proxy_s5.c \
+	utils.c
+
+OBJECTS = $(C_SRC:%.c=%.o)
+
+##
+## LIBRARIES
+##
+
+LIBS = \
+	-lgtk-win32-2.0 \
+	-lglib-2.0 \
+	-lgdk-win32-2.0 \
+	-lgdk_pixbuf-2.0 \
+	-lgmodule-2.0 \
+	-lgobject-2.0 \
+	-lws2_32 \
+	-lintl \
+	-lgaim
+
+##
+## RULES
+##
+
+# How to make a C file
+
+%.o: %.c
+	$(CC) $(CFLAGS) $(DEFINES) $(INCLUDE_PATHS) -o $@ -c $<
+
+##
+## TARGET DEFINITIONS
+##
+
+.PHONY: all clean
+
+all: $(TARGET).dll
+
+install:
+	mkdir -p $(DLL_INSTALL_DIR)
+	cp $(OPENQ_ROOT)/$(TARGET).dll $(DLL_INSTALL_DIR)
+
+
+##
+## BUILD Dependencies
+##
+
+$(GAIM_TOP)/src/gaim.lib:
+	$(MAKE) -C $(GAIM_TOP)/src -f Makefile.mingw gaim.lib
+
+##
+## BUILD DLL
+##
+
+$(TARGET).dll: $(OBJECTS) $(GAIM_TOP)/src/gaim.lib
+	$(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET).dll
+
+##
+## CLEAN RULES
+##
+
+clean:
+	rm -rf *.o
+	rm -rf $(TARGET).dll
+	rm -rf $(TARGET).lib
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/TODO	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,18 @@
+* Decide how to handle QQ status icons, which are customizable and legion.
+* Give "Online Offline" and "My Offline" statuses appropriate titles.
+* Handle emoticon at beginning of message via passthrough_unknown_commands.
+* Fix file transfer.
+* QQ protocol currently breaks Chinese localization of Gaim. Fix this.
+* Fix ability to insert images into a conversation via the menubar.
+* Fix _qq_menu_block_buddy in qq.c
+* Eliminate all references to QQWry.dat and the geolocation lookup feature previously present OpenQ.
+* Give smileys verbal instead of numerical titles.
+* Clean up signedness warnings.
+* Clean up mixed declaration and code warnings.
+* Better decomposition: 
+** some of these functions are _really_ long
+** buddy_status.c has a helper function which seems to belong in buddy_status.c
+** consider cleaning up qq_encrypt (nested function declarations)
+** eliminate group_misc.c
+** investigate whether all of these externs are appropriate
+* Check for memory leaks.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/buddy_info.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,274 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "internal.h"		// strlen, _("get_text)
+#include "debug.h"		// gaim_debug
+#include "notify.h"		// gaim_notify
+
+#include "utils.h"		// uid_to_gaim_name
+#include "packet_parse.h"	// MAX_PACKET_SIZE
+#include "buddy_info.h"		//
+#include "char_conv.h"		// qq_to_utf8
+#include "crypt.h"		// qq_crypt
+#include "header_info.h"	// cmd alias
+#include "infodlg.h"		// info_window
+#include "keep_alive.h"		// qq_update_buddy_contact
+#include "send_core.h"		// qq_send_cmd
+
+// amount of fiedls in user info
+#define QQ_CONTACT_FIELDS 				37
+
+// There is no user id stored in the reply packet for information query
+// we have to manually store the query, so that we know the query source
+typedef struct _qq_info_query {
+	guint32 uid;
+	gboolean show_window;
+	contact_info *ret_info;
+} qq_info_query;
+
+/*****************************************************************************/
+// send a packet to get detailed information of uid,
+// if show_window, a info window will be display upon receiving a reply
+void qq_send_packet_get_info(GaimConnection * gc, guint32 uid, gboolean show_window)
+{
+	qq_data *qd;
+	gchar *uid_str;
+	GList *list;
+	qq_info_query *query;
+	gboolean is_exist;
+	contact_info_window *info_window;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL && uid != 0);
+
+	qd = (qq_data *) gc->proto_data;
+	uid_str = g_strdup_printf("%d", uid);
+	qq_send_cmd(gc, QQ_CMD_GET_USER_INFO, TRUE, 0, TRUE, uid_str, strlen(uid_str));
+
+	if (show_window) {	// prepare the window
+		is_exist = FALSE;	// see if there is already a window for this uid
+		list = qd->contact_info_window;
+		while (list != NULL) {
+			info_window = (contact_info_window *) list->data;
+			if (uid == info_window->uid) {
+				is_exist = TRUE;
+				break;
+			} else
+				list = list->next;
+		}		// while list
+		if (!is_exist) {	// create a new one
+			info_window = g_new0(contact_info_window, 1);
+			info_window->uid = uid;
+			qd->contact_info_window = g_list_append(qd->contact_info_window, info_window);
+		}		// if !is_exist
+	}			// if show_window
+
+	query = g_new0(qq_info_query, 1);
+	query->uid = uid;
+	query->show_window = show_window;
+	qd->info_query = g_list_append(qd->info_query, query);
+
+	g_free(uid_str);
+}				// qq_send_packet_get_info
+
+/*****************************************************************************/
+// send packet to modify personal information, and/or change password
+void qq_send_packet_modify_info(GaimConnection * gc, contact_info * info, gchar * new_passwd)
+{
+	GaimAccount *a;
+	gchar *old_passwd, *info_field[QQ_CONTACT_FIELDS];
+	gint i;
+	guint8 *raw_data, *cursor, bar;
+
+	g_return_if_fail(gc != NULL && info != NULL);
+
+	a = gc->account;
+	old_passwd = a->password;
+	bar = 0x1f;
+	raw_data = g_newa(guint8, MAX_PACKET_SIZE - 128);
+	cursor = raw_data;
+
+	g_memmove(info_field, info, sizeof(gchar *) * QQ_CONTACT_FIELDS);
+
+	if (new_passwd == NULL || strlen(new_passwd) == 0)
+		create_packet_b(raw_data, &cursor, bar);
+	else {			// we gonna change passwd
+		create_packet_data(raw_data, &cursor, old_passwd, strlen(old_passwd));
+		create_packet_b(raw_data, &cursor, bar);
+		create_packet_data(raw_data, &cursor, new_passwd, strlen(new_passwd));
+	}
+
+	// important!, skip the first uid entry
+	for (i = 1; i < QQ_CONTACT_FIELDS; i++) {
+		create_packet_b(raw_data, &cursor, bar);
+		create_packet_data(raw_data, &cursor, info_field[i], strlen(info_field[i]));
+	}
+	create_packet_b(raw_data, &cursor, bar);
+
+	qq_send_cmd(gc, QQ_CMD_UPDATE_INFO, TRUE, 0, TRUE, raw_data, cursor - raw_data);
+
+}				// qq_send_packet_modify_info
+
+/*****************************************************************************/
+// process the reply of modidy_info packet
+void qq_process_modify_info_reply(guint8 * buf, gint buf_len, GaimConnection * gc)
+{
+	qq_data *qd;
+	gint len;
+	guint8 *data;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	g_return_if_fail(buf != NULL && buf_len != 0);
+
+	qd = (qq_data *) gc->proto_data;
+	len = buf_len;
+	data = g_newa(guint8, len);
+
+	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+		if (qd->uid == atoi(data)) {	// return should be my uid
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "Update info ACK OK\n");
+			gaim_notify_info(gc, NULL, _("You information have been updated"), NULL);
+		}
+	} else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt modify info reply\n");
+
+}				// qq_process_modify_info_reply
+
+/*****************************************************************************/
+// after getting info or modify myself, refresh the buddy list accordingly
+void qq_refresh_buddy_and_myself(contact_info * info, GaimConnection * gc)
+{
+	GaimBuddy *b;
+	qq_data *qd;
+	qq_buddy *q_bud;
+	gchar *alias_utf8;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	alias_utf8 = qq_to_utf8(info->nick, QQ_CHARSET_DEFAULT);
+	if (qd->uid == strtol(info->uid, NULL, 10)) {	// it is me
+		qd->my_icon = strtol(info->face, NULL, 10);
+		if (alias_utf8 != NULL)
+			gaim_account_set_alias(gc->account, alias_utf8);
+	}
+	// update buddy list (including myself, if myself is the buddy)
+	b = gaim_find_buddy(gc->account, uid_to_gaim_name(strtol(info->uid, NULL, 10)));
+	q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
+	if (q_bud != NULL) {	// I have this buddy
+		q_bud->age = strtol(info->age, NULL, 10);
+		q_bud->gender = strtol(info->gender, NULL, 10);
+		q_bud->icon = strtol(info->face, NULL, 10);
+		if (alias_utf8 != NULL)
+			q_bud->nickname = g_strdup(alias_utf8);
+		qq_update_buddy_contact(gc, q_bud);
+	}			// if q_bud
+	g_free(alias_utf8);
+}				// qq_refresh_buddy_and_myself
+
+/*****************************************************************************/
+// process reply to get_info packet
+void qq_process_get_info_reply(guint8 * buf, gint buf_len, GaimConnection * gc)
+{
+	gint len;
+	guint8 *data;
+	gchar **segments;
+	qq_info_query *query;
+	qq_data *qd;
+	contact_info *info;
+	contact_info_window *info_window;
+	gboolean show_window;
+	GList *list, *query_list;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	g_return_if_fail(buf != NULL && buf_len != 0);
+
+	qd = (qq_data *) gc->proto_data;
+	list = query_list = NULL;
+	len = buf_len;
+	data = g_newa(guint8, len);
+	info = NULL;
+
+	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+		if (NULL == (segments = split_data(data, len, "\x1e", QQ_CONTACT_FIELDS)))
+			return;
+
+		info = (contact_info *) segments;
+		qq_refresh_buddy_and_myself(info, gc);
+
+		query_list = qd->info_query;
+		show_window = FALSE;
+		while (query_list != NULL) {
+			query = (qq_info_query *) query_list->data;
+			if (query->uid == atoi(info->uid)) {
+				show_window = query->show_window;
+				qd->info_query = g_list_remove(qd->info_query, qd->info_query->data);
+				g_free(query);
+				break;
+			}
+			query_list = query_list->next;
+		}		// while query_list
+
+		if (!show_window) {
+			g_strfreev(segments);
+			return;
+		}
+		// if not show_window, we can not find the window here either
+		list = qd->contact_info_window;
+		while (list != NULL) {
+			info_window = (contact_info_window *) (list->data);
+			if (info_window->uid == atoi(info->uid)) {
+				if (info_window->window)
+					qq_refresh_contact_info_dialog(info, gc, info_window);
+				else
+					qq_show_contact_info_dialog(info, gc, info_window);
+				break;
+			} else
+				list = list->next;
+		}		// while list
+		g_strfreev(segments);
+	} else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt get info reply\n");
+
+}				// qq_process_get_info_reply
+
+/*****************************************************************************/
+void qq_info_query_free(qq_data * qd)
+{
+	gint i;
+	qq_info_query *p;
+
+	g_return_if_fail(qd != NULL);
+
+	i = 0;
+	while (qd->info_query != NULL) {
+		p = (qq_info_query *) (qd->info_query->data);
+		qd->info_query = g_list_remove(qd->info_query, p);
+		g_free(p);
+		i++;
+	}
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d info queries are freed!\n", i);
+}				// qq_add_buddy_request_free
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/buddy_info.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,94 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_BUDDY_INFO_H_
+#define _QQ_BUDDY_INFO_H_
+
+#include <glib.h>
+#include "connection.h"		// GaimConnection
+#include "buddy_opt.h"		// gc_and_uid
+#include "qq.h"			// qq_data
+
+#define QQ_COMM_FLAG_QQ_MEMBER      0x02
+#define QQ_COMM_FLAG_TCP_MODE       0x10
+#define QQ_COMM_FLAG_MOBILE         0x20
+#define QQ_COMM_FLAG_BIND_MOBILE    0x40
+#define QQ_COMM_FLAG_VIDEO          0x80
+
+#define QQ_BUDDY_GENDER_GG          0x00
+#define QQ_BUDDY_GENDER_MM          0x01
+#define QQ_BUDDY_GENDER_UNKNOWN     0xff
+
+typedef struct _contact_info contact_info;
+
+struct _contact_info {
+	gchar *uid;		//0
+	gchar *nick;		//1
+	gchar *country;		//2 
+	gchar *province;	//3
+	gchar *zipcode;		//4
+	gchar *address;		//5
+	gchar *tel;		//6
+	gchar *age;		//7
+	gchar *gender;		//8
+	gchar *name;		//9
+	gchar *email;		//10
+	gchar *pager_sn;	//11
+	gchar *pager_num;	//12
+	gchar *pager_sp;	//13
+	gchar *pager_base_num;	//14
+	gchar *pager_type;	//15
+	gchar *occupation;	//16
+	gchar *homepage;	//17
+	gchar *auth_type;	//18
+	gchar *unknown1;	//19
+	gchar *unknown2;	//20
+	gchar *face;		//21
+	gchar *hp_num;		//22
+	gchar *hp_type;		//23
+	gchar *intro;		//24
+	gchar *city;		//25
+	gchar *unknown3;	//26
+	gchar *unknown4;	//27
+	gchar *unknown5;	//28
+	gchar *is_open_hp;	//29
+	gchar *is_open_contact;	//30
+	gchar *college;		//31
+	gchar *horoscope;	//32
+	gchar *zodiac;		//33 sheng xiao
+	gchar *blood;		//34
+	gchar *qq_show;		//35
+	gchar *unknown6;	//36, always 0x2D
+};
+
+void qq_refresh_buddy_and_myself(contact_info * info, GaimConnection * gc);
+void qq_send_packet_get_info(GaimConnection * gc, guint32 uid, gboolean show_window);
+void qq_send_packet_modify_info(GaimConnection * gc, contact_info * info, gchar * new_passwd);
+void qq_process_modify_info_reply(guint8 * buf, gint buf_len, GaimConnection * gc);
+void qq_process_get_info_reply(guint8 * buf, gint buf_len, GaimConnection * gc);
+void qq_info_query_free(qq_data * qd);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/buddy_list.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,444 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include <string.h>		// g_memmove, memmove
+#include "debug.h"		// gaim_debug
+
+#include "notify.h"		// gaim_notify
+#include "utils.h"		// get_ip_str
+#include "packet_parse.h"	// create_packet, read_packet
+#include "buddy_list.h"
+#include "buddy_status.h"	// qq_buddy_status
+#include "buddy_opt.h"		// qq_add_buddy_by_recv_packet
+#include "char_conv.h"		// qq_to_utf8
+#include "crypt.h"		// qq_crypt
+#include "header_info.h"	// cmd alias
+#include "keep_alive.h"		// qq_refresh_all_buddy_status
+#include "send_core.h"		// qq_send_cmd
+#include "qq.h"			// qq_data
+#include "group.h"                // qq_group, by gfhuang
+#include "group_find.h"          // qq_group_find
+#include "group_hash.h"         //qq_group_create_by_id
+#include "group_info.h"         //qq_send_cmd_group_get_group_info
+
+#define QQ_GET_ONLINE_BUDDY_02          0x02
+#define QQ_GET_ONLINE_BUDDY_03          0x03	// unknown function
+
+#define QQ_ONLINE_BUDDY_ENTRY_LEN       38
+
+typedef struct _qq_friends_online_entry {
+	qq_buddy_status *s;
+	guint16 unknown1;
+	guint8 flag1;
+	guint8 comm_flag;
+	guint16 unknown2;
+	guint8 ending;		//0x00
+} qq_friends_online_entry;
+
+//TODO: defined in qq_buddy_status.c, but only used here. Check decomposition.
+extern void			// defined in qq_buddy_status.c
+ _qq_buddy_status_dump_unclear(qq_buddy_status * s);
+
+extern gint			// defined in qq_buddy_status.c
+ _qq_buddy_status_read(guint8 * data, guint8 ** cursor, gint len, qq_buddy_status * s);
+
+/*****************************************************************************/
+// get a list of online_buddies
+void qq_send_packet_get_buddies_online(GaimConnection * gc, guint8 position)
+{
+	qq_data *qd;
+	guint8 *raw_data, *cursor;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+	raw_data = g_newa(guint8, 5);
+	cursor = raw_data;
+
+	// 000-000 get online friends cmd
+	// only 0x02 and 0x03 returns info from server, other valuse all return 0xff
+	// I can also only send the first byte (0x02, or 0x03)
+	// and the result is the same
+	create_packet_b(raw_data, &cursor, QQ_GET_ONLINE_BUDDY_02);
+	// 001-001 seems it supports 255 online buddies at most
+	create_packet_b(raw_data, &cursor, position);
+	// 002-002
+	create_packet_b(raw_data, &cursor, 0x00);
+	// 003-004
+	create_packet_w(raw_data, &cursor, 0x0000);
+
+	qq_send_cmd(gc, QQ_CMD_GET_FRIENDS_ONLINE, TRUE, 0, TRUE, raw_data, 5);
+	qd->last_get_online = time(NULL);
+
+}				// qq_send_packet_get_buddies_online
+
+/*****************************************************************************/
+// position starts with 0x0000, 
+// server may return a position tag if list is too long for one packet
+void qq_send_packet_get_buddies_list(GaimConnection * gc, guint16 position)
+{
+	guint8 *raw_data, *cursor;
+	gint data_len;
+
+	g_return_if_fail(gc != NULL);
+
+	data_len = 3;
+	raw_data = g_newa(guint8, data_len);
+	cursor = raw_data;
+	// 000-001 starting position, can manually specify
+	create_packet_w(raw_data, &cursor, position);
+	// before Mar 18, 2004, any value can work, and we sent 00
+	// I do not know what data QQ server is expecting, as QQ2003iii 0304 itself
+	// even can sending packets 00 and get no response.
+	// Now I tested that 00,00,00,00,00,01 work perfectly
+	// March 22, fount the 00,00,00 starts to work as well
+	create_packet_b(raw_data, &cursor, 0x00);
+
+	qq_send_cmd(gc, QQ_CMD_GET_FRIENDS_LIST, TRUE, 0, TRUE, raw_data, data_len);
+
+}				// qq_send_packet_get_buddies_list
+
+// get all list, buddies & Quns with groupsid support, written by gfhuang
+void qq_send_packet_get_all_list_with_group(GaimConnection *gc, guint32 position)
+{
+	guint8 *raw_data, *cursor;
+	gint data_len;
+
+	g_return_if_fail(gc != NULL);
+
+	data_len = 10;
+	raw_data = g_newa(guint8, data_len);
+	cursor = raw_data;
+	// 0x01 download, 0x02, upload
+	create_packet_b(raw_data, &cursor, 0x01);
+	//unknown 0x02
+	create_packet_b(raw_data, &cursor, 0x02);
+	//unknown 00 00 00 00 
+	create_packet_dw(raw_data, &cursor, 0x00000000);
+	create_packet_dw(raw_data, &cursor, position);
+
+	qq_send_cmd(gc, QQ_CMD_GET_ALL_LIST_WITH_GROUP, TRUE, 0, TRUE, raw_data, data_len);
+}
+
+/*****************************************************************************/
+static void _qq_buddies_online_reply_dump_unclear(qq_friends_online_entry * fe)
+{
+	GString *dump;
+
+	g_return_if_fail(fe != NULL);
+
+	_qq_buddy_status_dump_unclear(fe->s);
+
+	dump = g_string_new("");
+	g_string_append_printf(dump, "unclear fields for [%d]:\n", fe->s->uid);
+	g_string_append_printf(dump, "031-032: %04x (unknown)\n", fe->unknown1);
+	g_string_append_printf(dump, "033:     %02x   (flag1)\n", fe->flag1);
+	g_string_append_printf(dump, "034:     %02x   (comm_flag)\n", fe->comm_flag);
+	g_string_append_printf(dump, "035-036: %04x (unknown)\n", fe->unknown2);
+
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Online buddy entry, %s", dump->str);
+	g_string_free(dump, TRUE);
+}				// _qq_buddies_online_reply_dump_unknown
+
+/*****************************************************************************/
+// 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(gc != NULL && gc->proto_data != NULL);
+	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, &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; //by gfhuang
+				if(0 != *((guint32 *)fe->s->ip)) { // by gfhuang
+					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);
+			}	// if q_bud
+			else {
+				gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Got an online buddy %d, but not in my buddy list", fe->s->uid);
+			}
+
+			g_free(fe->s->ip);
+			g_free(fe->s->unknown_key);
+		}		// while cursor
+		
+		if(cursor > (data + len)) {
+			 gaim_debug(GAIM_DEBUG_ERROR, "QQ", "qq_process_get_buddies_online_reply: Dangerous error! maybe protocal changed, notify me!");
+		}
+
+		if (position != QQ_FRIENDS_ONLINE_POSITION_END) {
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "Has more online buddies, position from %d", position);
+
+			qq_send_packet_get_buddies_online(gc, position);
+		}
+		else
+			qq_refresh_all_buddy_status(gc);
+
+	} else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt buddies online");
+
+}				// qq_process_get_buddies_online_reply
+
+/*****************************************************************************/
+// 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, bar, pascal_len;
+	gchar *name;
+	GaimBuddy *b;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	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-004: 0xff if buddy is self, 0x00 otherwise
+			bytes += read_packet_b(data, &cursor, len, &bar);
+			// 005-005: icon index (1-255)
+			bytes += read_packet_b(data, &cursor, len, &q_bud->icon);
+			// 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);
+		}		// while cursor
+
+		if(cursor > (data + len)) {
+                         gaim_debug(GAIM_DEBUG_ERROR, "QQ", "qq_process_get_buddies_list_reply: Dangerous error! maybe protocal changed, notify me!");
+                }
+		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");
+
+}				// qq_process_get_buddies_list_reply
+
+
+// written by gfhuang
+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_buddy *q_bud;
+	gchar *name;
+	GaimBuddy *b;
+	qq_group *group;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	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
+			read_packet_b(data, &cursor, len, &groupid);
+			groupid >>= 2;   // these 2 bits might not be 0, faint!
+			if (uid == 0 || (type != 0x1 && type != 0x4)) {
+				gaim_debug(GAIM_DEBUG_WARNING, "QQ",
+					   "Buddy entry, uid=%d, type=%d", uid, type);
+				continue;
+			} 
+			if(0x1 == type) { // a buddy
+				name = uid_to_gaim_name(uid);
+				b = gaim_find_buddy(gc->account, name);
+				g_free(name);
+
+				if (b == NULL) {
+					b = qq_add_buddy_by_recv_packet(gc, uid, TRUE, TRUE);
+					q_bud = b->proto_data;
+				}
+				else {
+					q_bud = NULL;
+					b->proto_data = q_bud;	//wrong !!!!
+				}
+				qd->buddies = g_list_append(qd->buddies, q_bud);
+				qq_update_buddy_contact(gc, q_bud);
+				++i;
+			}
+			else { //a group
+				 group = qq_group_find_by_internal_group_id(gc, uid);
+				if(group == NULL) {
+					/* not working, gfhuang
+					group = qq_group_create_by_id(gc, uid, 0);
+					qq_send_cmd_group_get_group_info(gc, group);
+					*/
+					gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Get a Qun with internel group %d\n", uid);
+					gaim_notify_info(gc, _("QQ Qun Operation"), _("Find one Qun in the server list, but i don't know its external id, please re-rejoin it manually"), NULL);
+				}
+				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 protocal changed, notify me!");
+		}
+		 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");
+
+}
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/buddy_list.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,47 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_BUDDY_LIST_H_
+#define _QQ_BUDDY_LIST_H_
+
+#include <glib.h>
+#include "connection.h"		// GaimConnection
+
+#define QQ_FRIENDS_LIST_POSITION_START 		0x0000
+#define QQ_FRIENDS_LIST_POSITION_END 		0xffff
+#define QQ_FRIENDS_ONLINE_POSITION_START 	0x00
+#define QQ_FRIENDS_ONLINE_POSITION_END 		0xff
+
+void qq_send_packet_get_buddies_online(GaimConnection * gc, guint8 position);
+void qq_process_get_buddies_online_reply(guint8 * buf, gint buf_len, GaimConnection * gc);
+void qq_send_packet_get_buddies_list(GaimConnection * gc, guint16 position);
+void qq_process_get_buddies_list_reply(guint8 * buf, gint buf_len, GaimConnection * gc);
+
+//added by gfhuang
+void qq_send_packet_get_all_list_with_group(GaimConnection * gc, guint32 position);
+void qq_process_get_all_list_with_group_reply(guint8 * buf, gint buf_len, GaimConnection * gc);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/buddy_opt.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,617 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "debug.h"		// gaim_debug
+#include "internal.h"		// strlen, _("get_text")
+#include "notify.h"		// gaim_notify
+#include "request.h"		// gaim_request_input
+
+#include "utils.h"		// uid_to_gaim_name, gaim_name_to_uid
+#include "packet_parse.h"	// create_packet, read_packet
+#include "buddy_info.h"		// qq_send_packet_get_info
+#include "buddy_list.h"		// qq_send_packet_get_buddies_online
+#include "buddy_opt.h"
+#include "char_conv.h"		// qq_to_utf8
+#include "crypt.h"		// qq_crypt
+#include "header_info.h"	// cmd alias
+#include "keep_alive.h"		// qq_update_buddy_contact
+#include "im.h"			// QQ_MSG_IM_MAX
+#include "send_core.h"		// qq_send_cmd
+
+#define GAIM_GROUP_QQ_FORMAT          "QQ (%s)"
+#define GAIM_GROUP_QQ_UNKNOWN         "QQ Unknown"
+#define GAIM_GROUP_QQ_BLOCKED         "QQ Blocked"
+
+#define QQ_REMOVE_BUDDY_REPLY_OK      0x00
+#define QQ_REMOVE_SELF_REPLY_OK       0x00
+#define QQ_ADD_BUDDY_AUTH_REPLY_OK    0x30	// ASCii value of "0"
+
+enum {
+	QQ_MY_AUTH_APPROVE = 0x30,	// ASCii value of "0"
+	QQ_MY_AUTH_REJECT = 0x31,	// ASCii value of "1"
+	QQ_MY_AUTH_REQUEST = 0x32,	// ASCii value of "2"
+};
+
+typedef struct _qq_add_buddy_request {
+	guint32 uid;
+	guint16 seq;
+} qq_add_buddy_request;
+
+/*****************************************************************************/
+// send packet to remove a buddy from my buddy list
+static void _qq_send_packet_remove_buddy(GaimConnection * gc, guint32 uid)
+{
+	gchar *uid_str;
+
+	g_return_if_fail(gc != NULL && uid > 0);
+
+	uid_str = g_strdup_printf("%d", uid);
+	qq_send_cmd(gc, QQ_CMD_DEL_FRIEND, TRUE, 0, TRUE, uid_str, strlen(uid_str));
+
+	g_free(uid_str);
+}				// _qq_send_packet_remove_buddy
+
+/*****************************************************************************/
+// try to remove myself from someone's buddy list
+static void _qq_send_packet_remove_self_from(GaimConnection * gc, guint32 uid)
+{
+	guint8 *raw_data, *cursor;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL && uid > 0);
+
+	raw_data = g_newa(guint8, 4);
+	cursor = raw_data;
+	create_packet_dw(raw_data, &cursor, uid);
+
+	qq_send_cmd(gc, QQ_CMD_REMOVE_SELF, TRUE, 0, TRUE, raw_data, 4);
+
+}				// _qq_send_packet_add_buddy
+
+/*****************************************************************************/
+// try to add a buddy without authentication
+static void _qq_send_packet_add_buddy(GaimConnection * gc, guint32 uid)
+{
+	qq_data *qd;
+	qq_add_buddy_request *req;
+	gchar *uid_str;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL && uid > 0);
+
+	// we need to send the ascii code of this uid to qq server
+	uid_str = g_strdup_printf("%d", uid);
+	qq_send_cmd(gc, QQ_CMD_ADD_FRIEND_WO_AUTH, TRUE, 0, TRUE, uid_str, strlen(uid_str));
+	g_free(uid_str);
+
+	// must be set after sending packet to get the correct send_seq
+	qd = (qq_data *) gc->proto_data;
+	req = g_new0(qq_add_buddy_request, 1);
+	req->seq = qd->send_seq;
+	req->uid = uid;
+	qd->add_buddy_request = g_list_append(qd->add_buddy_request, req);
+}				// _qq_send_packet_add_buddy
+
+/*****************************************************************************/
+// this buddy needs authentication, text conversion is done at lowest level
+static void _qq_send_packet_buddy_auth(GaimConnection * gc, guint32 uid, const gchar response, const gchar * text)
+{
+	gchar *text_qq, *uid_str;
+	guint8 bar, *cursor, *raw_data;
+
+	g_return_if_fail(gc != NULL && uid != 0);
+
+	uid_str = g_strdup_printf("%d", uid);
+	bar = 0x1f;
+	raw_data = g_newa(guint8, QQ_MSG_IM_MAX);
+	cursor = raw_data;
+
+	create_packet_data(raw_data, &cursor, uid_str, strlen(uid_str));
+	create_packet_b(raw_data, &cursor, bar);
+	create_packet_b(raw_data, &cursor, response);
+
+	if (text != NULL) {
+		text_qq = utf8_to_qq(text, QQ_CHARSET_DEFAULT);
+		create_packet_b(raw_data, &cursor, bar);
+		create_packet_data(raw_data, &cursor, text_qq, strlen(text_qq));
+		g_free(text_qq);
+	}
+
+	qq_send_cmd(gc, QQ_CMD_BUDDY_AUTH, TRUE, 0, TRUE, raw_data, cursor - raw_data);
+	g_free(uid_str);
+}				// _qq_send_packet_buddy_auth
+
+
+/*****************************************************************************/
+static void _qq_send_packet_add_buddy_auth_with_gc_and_uid(gc_and_uid * g, const gchar * text) {
+	GaimConnection *gc;
+	guint32 uid;
+	g_return_if_fail(g != NULL);
+
+	gc = g->gc;
+	uid = g->uid;
+	g_return_if_fail(gc != NULL && uid != 0);
+
+	_qq_send_packet_buddy_auth(gc, uid, QQ_MY_AUTH_REQUEST, text);
+	g_free(g);
+}				// qq_send_packet_add_buddy_auth
+
+/*****************************************************************************/
+// the real packet to reject and request is sent from here
+static void _qq_reject_add_request_real(gc_and_uid * g, const gchar * reason)
+{
+	gint uid;
+	GaimConnection *gc;
+
+	g_return_if_fail(g != NULL);
+
+	gc = g->gc;
+	uid = g->uid;
+	g_return_if_fail(gc != NULL && uid != 0);
+
+	_qq_send_packet_buddy_auth(gc, uid, QQ_MY_AUTH_REJECT, reason);
+	g_free(g);
+}				// _qq_reject_add_request_real
+
+/*****************************************************************************/
+// we approve other's request of adding me as friend
+void qq_approve_add_request_with_gc_and_uid(gc_and_uid * g)
+{
+	gint uid;
+	GaimConnection *gc;
+
+	g_return_if_fail(g != NULL);
+
+	gc = g->gc;
+	uid = g->uid;
+	g_return_if_fail(gc != NULL && uid != 0);
+
+	_qq_send_packet_buddy_auth(gc, uid, QQ_MY_AUTH_APPROVE, NULL);
+	g_free(g);
+}				// qq_approve_add_request_with_gc_and_uid
+
+/*****************************************************************************/
+void qq_do_nothing_with_gc_and_uid(gc_and_uid * g, const gchar * msg)
+{
+	g_free(g);
+}				// qq_do_nothing_with_gc_and_uid
+
+/*****************************************************************************/
+// we reject other's request of adding me as friend
+void qq_reject_add_request_with_gc_and_uid(gc_and_uid * g)
+{
+	gint uid;
+	gchar *msg1, *msg2;
+	GaimConnection *gc;
+	gc_and_uid *g2;
+
+	g_return_if_fail(g != NULL);
+
+	gc = g->gc;
+	uid = g->uid;
+	g_return_if_fail(gc != NULL && uid != 0);
+
+	g_free(g);
+
+	g2 = g_new0(gc_and_uid, 1);
+	g2->gc = gc;
+	g2->uid = uid;
+
+	msg1 = g_strdup_printf(_("You rejected %d's request"), uid);
+	msg2 = g_strdup(_("Input your reason:"));
+
+	gaim_request_input(gc, _("Reject request"), msg1, msg2,
+			   _("Sorry, you are not my type..."), TRUE, FALSE,
+			   NULL, _("Reject"), G_CALLBACK(_qq_reject_add_request_real), _("Cancel"), NULL, g2);
+
+}				// qq_reject_add_request_with_gc_and_uid
+
+/*****************************************************************************/
+void qq_add_buddy_with_gc_and_uid(gc_and_uid * g)
+{
+	gint uid;
+	GaimConnection *gc;
+
+	g_return_if_fail(g != NULL);
+
+	gc = g->gc;
+	uid = g->uid;
+	g_return_if_fail(gc != NULL && uid != 0);
+
+	_qq_send_packet_add_buddy(gc, uid);
+	g_free(g);
+}				// qq_add_buddy_with_gc_and_uid
+
+/*****************************************************************************/
+void qq_block_buddy_with_gc_and_uid(gc_and_uid * g)
+{
+	guint32 uid;
+	GaimConnection *gc;
+	GaimBuddy buddy;
+	GaimGroup group;
+
+	g_return_if_fail(g != NULL);
+
+	gc = g->gc;
+	uid = g->uid;
+	g_return_if_fail(gc != NULL && uid > 0);
+
+	buddy.name = uid_to_gaim_name(uid);
+	group.name = GAIM_GROUP_QQ_BLOCKED;
+
+	qq_remove_buddy(gc, &buddy, &group);
+	_qq_send_packet_remove_self_from(gc, uid);
+
+}				// qq_block_buddy_with_gc_and_uid
+
+/*****************************************************************************/
+//  process reply to add_buddy_auth request
+void qq_process_add_buddy_auth_reply(guint8 * buf, gint buf_len, GaimConnection * gc) {
+	qq_data *qd;
+	gint len;
+	guint8 *data, *cursor, reply;
+	gchar **segments, *msg_utf8;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	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, &reply);
+		if (reply != QQ_ADD_BUDDY_AUTH_REPLY_OK) {
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Add buddy with auth request fails\n");
+			if (NULL == (segments = split_data(data, len, "\x1f", 2)))
+				return;
+			msg_utf8 = qq_to_utf8(segments[1], QQ_CHARSET_DEFAULT);
+			gaim_notify_error(gc, NULL, _("Add buddy with auth request fails"), msg_utf8);
+			g_free(msg_utf8);
+		} else
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "Add buddy with auth request OK\n");
+	} else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt add buddy with auth reply\n");
+
+}				// qq_process_add_buddy_auth_reply
+
+/*****************************************************************************/
+// process the server reply for my request to remove a buddy
+void qq_process_remove_buddy_reply(guint8 * buf, gint buf_len, GaimConnection * gc) {
+	qq_data *qd;
+	gint len;
+	guint8 *data, *cursor, reply;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	g_return_if_fail(buf != NULL && buf_len != 0);
+
+	qd = (qq_data *) gc->proto_data;
+	len = buf_len;
+	data = g_newa(guint8, len);
+
+	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+		cursor = data;
+		read_packet_b(data, &cursor, len, &reply);
+		if (reply != QQ_REMOVE_BUDDY_REPLY_OK)
+			// there is no reason return from server
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Remove buddy fails\n");
+		else {		// if reply
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "Remove buddy OK\n");
+			gaim_notify_info(gc, NULL, _("You have successfully removed a buddy"), NULL);
+		}
+	} else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt remove buddy reply\n");
+
+}				// qq_process_remove_buddy_reply
+
+
+/*****************************************************************************/
+// process the server reply for my request to remove myself from a buddy
+void qq_process_remove_self_reply(guint8 * buf, gint buf_len, GaimConnection * gc) {
+	qq_data *qd;
+	gint len;
+	guint8 *data, *cursor, reply;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	g_return_if_fail(buf != NULL && buf_len != 0);
+
+	qd = (qq_data *) gc->proto_data;
+	len = buf_len;
+	data = g_newa(guint8, len);
+
+	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+		cursor = data;
+		read_packet_b(data, &cursor, len, &reply);
+		if (reply != QQ_REMOVE_SELF_REPLY_OK)
+			// there is no reason return from server
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Remove self fails\n");
+		else {		// if reply
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "Remove self from a buddy OK\n");
+			gaim_notify_info(gc, NULL, _("You have successfully removed yourself from a buddy"), NULL);
+		}
+	} else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt remove self reply\n");
+
+}				// qq_process_remove_buddy_reply
+
+/*****************************************************************************/
+void qq_process_add_buddy_reply(guint8 * buf, gint buf_len, guint16 seq, GaimConnection * gc) {
+	qq_data *qd;
+	gint len, for_uid;
+	gchar *msg, *data, **segments, *uid, *reply;
+	GList *list;
+	GaimBuddy *b;
+	gc_and_uid *g;
+	qq_add_buddy_request *req;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	g_return_if_fail(buf != NULL && buf_len != 0);
+
+	for_uid = 0;
+	qd = (qq_data *) gc->proto_data;
+	len = buf_len;
+
+	list = qd->add_buddy_request;
+	while (list != NULL) {
+		req = (qq_add_buddy_request *) list->data;
+		if (req->seq == seq) {	// reply to this
+			for_uid = req->uid;
+			qd->add_buddy_request = g_list_remove(qd->add_buddy_request, qd->add_buddy_request->data);
+			g_free(req);
+			break;
+		}
+		list = list->next;
+	}			// while list
+
+	if (for_uid == 0) {	// we have no record for this
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "We have no record for add buddy reply [%d], discard\n", seq);
+		return;
+	} else
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "Add buddy reply [%d] is for id [%d]\n", seq, for_uid);
+
+	data = g_newa(guint8, len);
+
+	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+		if (NULL == (segments = split_data(data, len, "\x1f", 2)))
+			return;
+		uid = segments[0];
+		reply = segments[1];
+		if (strtol(uid, NULL, 10) != qd->uid) {	// should not happen
+			gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Add buddy reply is to [%s], not me!", uid);
+			g_strfreev(segments);
+			return;
+		}		// if uid
+
+		if (strtol(reply, NULL, 10) > 0) {	// need auth
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Add buddy attempt fails, need authentication\n");
+			b = gaim_find_buddy(gc->account, uid_to_gaim_name(for_uid));
+			if (b != NULL)
+				gaim_blist_remove_buddy(b);
+			g = g_new0(gc_and_uid, 1);
+			g->gc = gc;
+			g->uid = for_uid;
+			msg = g_strdup_printf(_("User %d needs authentication"), for_uid);
+			gaim_request_input(gc, NULL, msg,
+					   _("Input request here"),
+					   _("Would you be my friend?"),
+					   TRUE, FALSE, NULL, _("Send"),
+					   G_CALLBACK
+					   (_qq_send_packet_add_buddy_auth_with_gc_and_uid),
+					   _("Cancel"), G_CALLBACK(qq_do_nothing_with_gc_and_uid), g);
+			g_free(msg);
+		} else {	// add OK
+			qq_add_buddy_by_recv_packet(gc, for_uid, TRUE, TRUE);
+			msg = g_strdup_printf(_("You have added %d in buddy list"), for_uid);
+			gaim_notify_info(gc, NULL, msg, NULL);
+			g_free(msg);
+		}		// if reply
+		g_strfreev(segments);
+	} else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt add buddy reply\n");
+
+}				// qq_process_add_buddy_reply
+
+/*****************************************************************************/
+GaimGroup *qq_get_gaim_group(const gchar * group_name)
+{
+	GaimGroup *g;
+
+	g_return_val_if_fail(group_name != NULL, NULL);
+
+	g = gaim_find_group(group_name);
+	if (g == NULL) {
+		g = gaim_group_new(group_name);
+		gaim_blist_add_group(g, NULL);
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Add new group: %s\n", group_name);
+	}			// if (g == NULL) 
+
+	return g;
+}				// qq_get_gaim_group
+
+/*****************************************************************************/
+// we add new buddy, if the received packet is from someone not in my list
+// return the GaimBuddy that is just created
+GaimBuddy *qq_add_buddy_by_recv_packet(GaimConnection * gc, guint32 uid, gboolean is_known, gboolean create) {
+	GaimAccount *a;
+	GaimBuddy *b;
+	GaimGroup *g;
+	qq_data *qd;
+	qq_buddy *q_bud;
+	gchar *name, *group_name;
+
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, NULL);
+
+	a = gc->account;
+	qd = (qq_data *) gc->proto_data;
+	g_return_val_if_fail(a != NULL && uid != 0, NULL);
+
+	group_name = is_known ?
+	    g_strdup_printf(GAIM_GROUP_QQ_FORMAT, gaim_account_get_username(a)) : g_strdup(GAIM_GROUP_QQ_UNKNOWN);
+
+	g = qq_get_gaim_group(group_name);
+
+	name = uid_to_gaim_name(uid);
+	b = gaim_find_buddy(gc->account, name);
+	// remove old, we can not simply return here
+	// because there might be old local copy of this buddy
+	if (b != NULL)
+		gaim_blist_remove_buddy(b);
+
+	b = gaim_buddy_new(a, name, NULL);
+
+	if (!create)
+		b->proto_data = NULL;
+	else {
+		q_bud = g_new0(qq_buddy, 1);
+		q_bud->uid = uid;
+		b->proto_data = q_bud;
+		qd->buddies = g_list_append(qd->buddies, q_bud);
+		qq_send_packet_get_info(gc, q_bud->uid, FALSE);
+		qq_send_packet_get_buddies_online(gc, QQ_FRIENDS_ONLINE_POSITION_START);
+	}			// if !create
+
+	gaim_blist_add_buddy(b, NULL, g, NULL);
+	gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Add new buddy: [%s]\n", name);
+
+	g_free(name);
+	g_free(group_name);
+
+	return b;
+}				// qq_add_buddy_by_recv_packet
+
+/*****************************************************************************/
+// add a buddy and send packet to QQ server
+// note that when gaim load local cached buddy list into its blist
+// it also calls this funtion, so we have to 
+// define qd->logged_in=TRUE AFTER serv_finish_login(gc)
+void qq_add_buddy(GaimConnection * gc, GaimBuddy * buddy, GaimGroup * group) {
+	qq_data *qd;
+	guint32 uid;
+	GaimBuddy *b;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+	if (!qd->logged_in)
+		return;		// IMPORTANT !
+
+	uid = gaim_name_to_uid(buddy->name);
+	if (uid > 0)
+		_qq_send_packet_add_buddy(gc, uid);
+	else {
+		b = gaim_find_buddy(gc->account, buddy->name);
+		if (b != NULL)
+			gaim_blist_remove_buddy(b);
+		gaim_notify_error(gc, NULL,
+				  _("QQid Error"),
+				  _("Invalid QQid, to add buddy 1234567, \nyou should input qq-1234567"));
+	}
+}				// _qq_add_buddy
+
+/*****************************************************************************/
+// remove a buddy and send packet to QQ server accordingly
+void qq_remove_buddy(GaimConnection * gc, GaimBuddy * buddy, GaimGroup * group) {
+	qq_data *qd;
+	GaimBuddy *b;
+	qq_buddy *q_bud;
+	guint32 uid;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+	uid = gaim_name_to_uid(buddy->name);
+
+	if (!qd->logged_in)
+		return;
+
+	if (uid > 0)
+		_qq_send_packet_remove_buddy(gc, uid);
+
+	b = gaim_find_buddy(gc->account, buddy->name);
+	if (b != NULL) {
+		q_bud = (qq_buddy *) b->proto_data;
+		if (q_bud != NULL)
+			qd->buddies = g_list_remove(qd->buddies, q_bud);
+		else
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "We have no qq_buddy record for %s\n", buddy->name);
+		// remove buddy on blist, this does not trigger qq_remove_buddy again
+		// do this only if the request comes from block request,
+		// otherwise gaim segmentation fault
+		if (g_ascii_strcasecmp(group->name, GAIM_GROUP_QQ_BLOCKED)
+		    == 0)
+			gaim_blist_remove_buddy(b);
+	}			// if b != NULL
+}				// _qq_remove_buddy
+
+/*****************************************************************************/
+// free add buddy request queue
+void qq_add_buddy_request_free(qq_data * qd)
+{
+	gint i;
+	qq_add_buddy_request *p;
+
+	g_return_if_fail(qd != NULL);
+
+	i = 0;
+	while (qd->add_buddy_request) {
+		p = (qq_add_buddy_request *) (qd->add_buddy_request->data);
+		qd->add_buddy_request = g_list_remove(qd->add_buddy_request, p);
+		g_free(p);
+		i++;
+	}
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d add buddy requests are freed!\n", i);
+}				// qq_add_buddy_request_free
+
+/*****************************************************************************/
+// free up all qq_buddy
+void qq_buddies_list_free(GaimAccount *account, qq_data * qd)
+{
+	gint i;
+	qq_buddy *p;
+	gchar *name;
+	GaimBuddy *b;
+
+	g_return_if_fail(qd != NULL);
+
+	i = 0;
+	while (qd->buddies) {
+		p = (qq_buddy *) (qd->buddies->data);
+		qd->buddies = g_list_remove(qd->buddies, p);
+		// added by gfhuang, for relogin crash bug
+		name = uid_to_gaim_name(p->uid);
+		b = gaim_find_buddy(account, name);   	
+		if(b != NULL) b->proto_data = NULL;
+		else {
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "qq_buddy %s not found in gaim proto_data\n", name);
+		}
+		g_free(name);
+
+		g_free(p);
+		i++;
+	}
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d qq_buddy structures are freed!\n", i);
+
+}				// qq_buddies_list_free
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/buddy_opt.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,65 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_BUDDY_OPT_H_
+#define _QQ_BUDDY_OPT_H_
+
+#include <glib.h>
+#include "connection.h"		// GaimConnection
+
+#include "qq.h"			// qq_buddy
+
+typedef struct _gc_and_uid gc_and_uid;
+
+struct _gc_and_uid {
+	guint32 uid;
+	GaimConnection *gc;
+};
+
+void qq_approve_add_request_with_gc_and_uid(gc_and_uid * g);
+
+void qq_reject_add_request_with_gc_and_uid(gc_and_uid * g);
+
+void qq_add_buddy_with_gc_and_uid(gc_and_uid * g);
+
+void qq_block_buddy_with_gc_and_uid(gc_and_uid * g);
+
+void qq_do_nothing_with_gc_and_uid(gc_and_uid * g, const gchar * msg);
+
+void qq_process_remove_buddy_reply(guint8 * buf, gint buf_len, GaimConnection * gc);
+void qq_process_remove_self_reply(guint8 * buf, gint buf_len, GaimConnection * gc);
+void qq_process_add_buddy_reply(guint8 * buf, gint buf_len, guint16 seq, GaimConnection * gc);
+void qq_process_add_buddy_auth_reply(guint8 * buf, gint buf_len, GaimConnection * gc);
+GaimBuddy *qq_add_buddy_by_recv_packet(GaimConnection * gc, guint32 uid, gboolean is_known, gboolean create);
+void qq_add_buddy(GaimConnection * gc, GaimBuddy * buddy, GaimGroup * group);
+GaimGroup *qq_get_gaim_group(const gchar * group_name);
+
+void qq_remove_buddy(GaimConnection * gc, GaimBuddy * buddy, GaimGroup * group);
+void qq_add_buddy_request_free(qq_data * qd);
+
+void qq_buddies_list_free(GaimAccount *account, qq_data * qd);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/buddy_status.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,272 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include <string.h>		// g_memmove
+#include "debug.h"		// gaim_debug
+#include "prefs.h"		// gaim_prefs_get_bool
+
+#include "utils.h"		// get_ip_str
+#include "packet_parse.h"	// create_packet,
+#include "buddy_status.h"
+#include "crypt.h"		// qq_crypt.h
+#include "header_info.h"	// cmd alias
+#include "keep_alive.h"		// qq_update_buddy_contact
+#include "send_core.h"		// qq_send_cmd
+
+#define QQ_MISC_STATUS_HAVING_VIIDEO      0x00000001
+
+#define QQ_ICON_SUFFIX_DEFAULT            QQ_ICON_SUFFIX_OFFLINE
+#define QQ_CHANGE_ONLINE_STATUS_REPLY_OK 	0x30	// ASCii value of "0"
+
+enum {
+	QQ_ICON_SUFFIX_NORMAL = 1,
+	QQ_ICON_SUFFIX_OFFLINE = 2,
+	QQ_ICON_SUFFIX_AWAY = 3,
+};
+
+/*****************************************************************************/
+static void _qq_buddy_status_dump_unclear(qq_buddy_status * s)
+{
+	GString *dump;
+
+	g_return_if_fail(s != NULL);
+
+	dump = g_string_new("");
+	g_string_append_printf(dump, "unclear fields for [%d]:\n", s->uid);
+	g_string_append_printf(dump, "004:     %02x   (unknown)\n", s->unknown1);
+	g_string_append_printf(dump, "011:     %02x   (unknown)\n", s->unknown2);
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Buddy status entry, %s", dump->str);
+	g_string_free(dump, TRUE);
+}				// _qq_buddy_status_dump_unclear
+
+/*****************************************************************************/
+// parse the data into qq_buddy_status
+static gint _qq_buddy_status_read(guint8 * data, guint8 ** cursor, gint len, qq_buddy_status * s) {
+	gint bytes;
+
+	g_return_val_if_fail(data != NULL && *cursor != NULL && s != NULL, -1);
+
+	bytes = 0;
+
+	// 000-003: uid
+	bytes += read_packet_dw(data, cursor, len, &s->uid);
+	// 004-004: 0x01
+	bytes += read_packet_b(data, cursor, len, &s->unknown1);
+	// 005-008: ip
+	s->ip = g_new0(guint8, 4);
+	bytes += read_packet_data(data, cursor, len, s->ip, 4);
+	// 009-010: port
+	bytes += read_packet_w(data, cursor, len, &s->port);
+	// 011-011: 0x00
+	bytes += read_packet_b(data, cursor, len, &s->unknown2);
+	// 012-012: status
+	bytes += read_packet_b(data, cursor, len, &s->status);
+	// 013-014: client_version 
+	bytes += read_packet_w(data, cursor, len, &s->client_version);
+	// 015-030: unknown key
+	s->unknown_key = g_new0(guint8, QQ_KEY_LENGTH);
+	bytes += read_packet_data(data, cursor, len, s->unknown_key, QQ_KEY_LENGTH);
+
+	if (s->uid == 0 || bytes != 31)
+		return -1;
+
+	return bytes;
+
+}				// _qq_buddy_status_read
+
+/*****************************************************************************/
+// check if status means online or offline
+gboolean is_online(guint8 status)
+{
+//	return (status == QQ_BUDDY_ONLINE_NORMAL ? TRUE : (status == QQ_BUDDY_ONLINE_AWAY ? TRUE : FALSE));
+	//rewritten by gfhuang
+	switch(status) {
+	case QQ_BUDDY_ONLINE_NORMAL:
+	case QQ_BUDDY_ONLINE_AWAY:
+	case QQ_BUDDY_ONLINE_INVISIBLE:
+		return TRUE;
+	case QQ_BUDDY_ONLINE_OFFLINE:
+		return FALSE;
+	}
+	return FALSE;
+}				// is_online
+
+/*****************************************************************************/
+// the icon suffix is detemined by status
+// although QQ server may return the right icon, I set it here myself
+gchar get_suffix_from_status(guint8 status)
+{
+	switch (status) {
+	case QQ_BUDDY_ONLINE_NORMAL:
+		return QQ_ICON_SUFFIX_NORMAL;
+	case QQ_BUDDY_ONLINE_AWAY:
+		return QQ_ICON_SUFFIX_AWAY;
+	case QQ_BUDDY_ONLINE_INVISIBLE:
+	case QQ_BUDDY_ONLINE_OFFLINE:
+		return QQ_ICON_SUFFIX_OFFLINE;
+	default:
+		return QQ_ICON_SUFFIX_DEFAULT;
+	}			// switch
+}				// get_suffix_from_status
+
+/*****************************************************************************/
+// send a packet to change my online status
+void qq_send_packet_change_status(GaimConnection * gc)
+{
+	qq_data *qd;
+	guint8 *raw_data, *cursor, away_cmd;
+	guint32 misc_status;
+	gboolean fake_video;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+	if (!qd->logged_in)
+		return;
+
+	switch (qd->status) {
+	case QQ_SELF_STATUS_AVAILABLE:
+		away_cmd = QQ_BUDDY_ONLINE_NORMAL;
+		break;
+	case QQ_SELF_STATUS_INVISIBLE:
+		away_cmd = QQ_BUDDY_ONLINE_INVISIBLE;
+		break;
+	case QQ_SELF_STATUS_AWAY:
+	case QQ_SELF_STATUS_IDLE:
+	case QQ_SELF_STATUS_CUSTOM:
+		away_cmd = QQ_BUDDY_ONLINE_AWAY;
+		break;
+	default:
+		away_cmd = QQ_BUDDY_ONLINE_NORMAL;
+	}			// switch
+
+	raw_data = g_new0(guint8, 5);
+	cursor = raw_data;
+	misc_status = 0x00000000;
+
+	fake_video = gaim_prefs_get_bool("/plugins/prpl/qq/show_fake_video");
+	if (fake_video)
+		misc_status |= QQ_MISC_STATUS_HAVING_VIIDEO;
+
+	create_packet_b(raw_data, &cursor, away_cmd);
+	create_packet_dw(raw_data, &cursor, misc_status);
+
+	qq_send_cmd(gc, QQ_CMD_CHANGE_ONLINE_STATUS, TRUE, 0, TRUE, raw_data, 5);
+
+	g_free(raw_data);
+}				// qq_send_packet_change_status
+
+/*****************************************************************************/
+// parse the reply packet for change_status
+void qq_process_change_status_reply(guint8 * buf, gint buf_len, GaimConnection * gc) {
+	qq_data *qd;
+	gint len;
+	guint8 *data, *cursor, reply;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	g_return_if_fail(buf != NULL && buf_len != 0);
+
+	qd = (qq_data *) gc->proto_data;
+	len = buf_len;
+	data = g_newa(guint8, len);
+
+	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+		cursor = data;
+		read_packet_b(data, &cursor, len, &reply);
+		if (reply != QQ_CHANGE_ONLINE_STATUS_REPLY_OK) {
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Change status fail\n");
+		} else
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "Change status OK\n");
+	} else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt chg status reply\n");
+
+}				// qq_process_change_status_reply
+
+/*****************************************************************************/
+// it is a server message 
+// indicating that one of my buddies changes its status
+void qq_process_friend_change_status(guint8 * buf, gint buf_len, GaimConnection * gc) {
+	qq_data *qd;
+	gint len, bytes;
+	guint32 my_uid;
+	guint8 *data, *cursor;
+	GaimBuddy *b;
+	qq_buddy *q_bud;
+	qq_buddy_status *s;
+	gchar *name;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	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)) {
+		s = g_new0(qq_buddy_status, 1);
+		bytes = 0;
+		// 000-030: qq_buddy_status;
+		bytes += _qq_buddy_status_read(data, &cursor, len, s);
+		// 031-034: my uid
+		bytes += read_packet_dw(data, &cursor, len, &my_uid);
+
+		if (my_uid == 0 || bytes != 35) {
+			gaim_debug(GAIM_DEBUG_ERROR, "QQ", "my_uid == 0 || bytes(%d) != 35\n", bytes);
+			g_free(s->ip);
+			g_free(s->unknown_key);
+			g_free(s);
+			return;
+		}
+//		if (QQ_DEBUG)						gfhuang
+//			_qq_buddy_status_dump_unclear(s);
+
+		name = uid_to_gaim_name(s->uid);	//by gfhuang
+		b = gaim_find_buddy(gc->account, name);
+		g_free(name);
+		q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
+		if (q_bud) {
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "s->uid = %d, q_bud->uid = %d\n", s->uid , q_bud->uid);
+			if(0 != *((guint32 *)s->ip)) { //by gfhuang
+				g_memmove(q_bud->ip, s->ip, 4);
+				q_bud->port = s->port;
+			}
+			q_bud->status = s->status;
+			if(0 != s->client_version) 
+				q_bud->client_version = s->client_version;  //gfhuang
+			qq_update_buddy_contact(gc, q_bud);
+		}
+		else 
+			gaim_debug(GAIM_DEBUG_ERROR, "QQ", "got information of unknown buddy by gfhuang %d\n", s->uid);
+
+		g_free(s->ip);
+		g_free(s->unknown_key);
+		g_free(s);
+	} else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt buddy status change packet\n");
+
+}				// qq_process_friend_change_status
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/buddy_status.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,70 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_BUDDY_STATUS_H_
+#define _QQ_BUDDY_STATUS_H_
+
+#include <glib.h>
+#include "connection.h"		// GaimConnection
+#include "qq.h"			// QQ_KEY_LENGTH
+
+typedef struct _qq_buddy_status {
+	guint32 uid;
+	guint8 unknown1;
+	guint8 *ip;
+	guint16 port;
+	guint8 unknown2;
+	guint8 status;
+	guint16 client_version;
+	guint8 *unknown_key;
+} qq_buddy_status;
+
+enum {
+	QQ_BUDDY_OFFLINE = 0x00,               // by gfhuang
+	QQ_BUDDY_ONLINE_NORMAL = 0x0a,		//10
+	QQ_BUDDY_ONLINE_OFFLINE = 0x14,		//20
+	QQ_BUDDY_ONLINE_AWAY = 0x1e,		//30
+	QQ_BUDDY_ONLINE_INVISIBLE = 0x28,	// 40 not 0x40!, bug by gfhuang
+};
+
+enum {
+	QQ_SELF_STATUS_AVAILABLE = 0x11,	// I determined value
+	QQ_SELF_STATUS_AWAY = 0x12,
+	QQ_SELF_STATUS_INVISIBLE = 0x13,
+	QQ_SELF_STATUS_CUSTOM = 0x14,
+	QQ_SELF_STATUS_IDLE = 0x15,
+};
+
+gboolean is_online(guint8 status);
+
+gchar get_suffix_from_status(guint8 status);
+
+void qq_send_packet_change_status(GaimConnection * gc);
+
+void qq_process_change_status_reply(guint8 * buf, gint buf_len, GaimConnection * gc);
+void qq_process_friend_change_status(guint8 * buf, gint buf_len, GaimConnection * gc);
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/char_conv.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,285 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "debug.h"		// gaim_debug
+#include "internal.h"		// strlen
+//	#include <regex.h>
+
+#include "utils.h"		// hex_dump_to_str
+#include "packet_parse.h"	// read_packet
+#include "char_conv.h"
+#include "qq.h"			// QQ_CHARSET_DEFAULT
+
+#define QQ_SMILEY_AMOUNT      96
+
+#define UTF8                  "UTF-8"
+#define QQ_CHARSET_ZH_CN      "GBK"
+#define QQ_CHARSET_ENG        "ISO-8859-1"
+
+#define QQ_NULL_MSG           "(NULL)"	// return this if conversion fail
+#define QQ_NULL_SMILEY        "(SM)"	// return this if smiley conversion fails
+
+// a debug function
+void _qq_show_packet(gchar * desc, gchar * buf, gint len);
+
+const gchar qq_smiley_map[QQ_SMILEY_AMOUNT] = {
+	0x41, 0x43, 0x42, 0x44, 0x45, 0x46, 0x47, 0x48,
+	0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x73,
+	0x74, 0x75, 0x76, 0x77, 0x8a, 0x8b, 0x8c, 0x8d,
+	0x8e, 0x8f, 0x78, 0x79, 0x7a, 0x7b, 0x90, 0x91,
+	0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
+	0x59, 0x5a, 0x5c, 0x58, 0x57, 0x55, 0x7c, 0x7d,
+	0x7e, 0x7f, 0x9a, 0x9b, 0x60, 0x67, 0x9c, 0x9d,
+	0x9e, 0x5e, 0x9f, 0x89, 0x80, 0x81, 0x82, 0x62,
+	0x63, 0x64, 0x65, 0x66, 0x83, 0x68, 0x84, 0x85,
+	0x86, 0x87, 0x6b, 0x6e, 0x6f, 0x70, 0x88, 0xa0,
+	0x50, 0x51, 0x52, 0x53, 0x54, 0x56, 0x5b, 0x5d,
+	0x5f, 0x61, 0x69, 0x6a, 0x6c, 0x6d, 0x71, 0x72,
+};
+
+
+// change from \\ to / by gfhuang, for gaim2beta2
+// change the pixmaps/smiley/theme file as well
+const gchar *gaim_smiley_map[QQ_SMILEY_AMOUNT] = {
+	"/jy", "/pz", "/se", "/fd", "/dy", "/ll", "/hx", "/bz",
+	"/shui", "/dk	", "/gg", "/fn", "/tp", "/cy", "/wx", "/ng",
+	"/kuk", "/feid", "/zk", "/tu", "/tx", "/ka", "/by", "/am",
+	"/jie", "/kun", "/jk", "/lh", "/hanx", "/db", "/fendou",
+	"/zhm",
+	"/yiw", "/xu", "/yun", "/zhem", "/shuai", "/kl", "/qiao",
+	"/zj",
+	"/shan", "/fad", "/aiq", "/tiao", "/zhao", "/mm", "/zt",
+	"/maom",
+	"/xg", "/yb", "/qianc", "/dp", "/bei", "/dg", "/shd",
+	"/zhd",
+	"/dao", "/zq", "/yy", "/bb", "/gf", "/fan", "/yw", "/mg",
+	"/dx", "/wen", "/xin", "/xs", "/hy", "/lw", "/dh", "/sj",
+	"/yj", "/ds", "/ty", "/yl", "/qiang", "/ruo", "/ws",
+	"/shl",
+	"/dd", "/mn", "/hl", "/mamao", "/qz", "/fw", "/oh", "/bj",
+	"/qsh", "/xig", "/xy", "/duoy", "/xr", "/xixing", "/nv",
+	"/nan"
+};
+
+/*****************************************************************************/
+// these functions parses font-attr
+static gchar _get_size(gchar font_attr)
+{
+	return font_attr & 0x1f;
+}
+
+static gboolean _check_bold(gchar font_attr)
+{
+	return (font_attr & 0x20) ? TRUE : FALSE;
+}
+
+static gboolean _check_italic(gchar font_attr)
+{
+	return (font_attr & 0x40) ? TRUE : FALSE;
+}
+
+static gboolean _check_underline(gchar font_attr)
+{
+	return (font_attr & 0x80) ? TRUE : FALSE;
+}
+
+/*****************************************************************************/
+// convert a string from from_charset to to_charset, using g_convert
+static gchar *_my_convert(const gchar * str, gssize len, const gchar * to_charset, const gchar * from_charset) {
+
+	GError *error = NULL;
+	gchar *ret;
+	gsize byte_read, byte_write;
+
+	g_return_val_if_fail(str != NULL && to_charset != NULL && from_charset != NULL, g_strdup(QQ_NULL_MSG));
+
+	ret = g_convert(str, len, to_charset, from_charset, &byte_read, &byte_write, &error);
+
+	if (error == NULL)
+		return ret;	// conversion is OK
+	else {			// conversion error
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "%s\n", error->message);
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ",
+			   "Dump failed text\n%s", hex_dump_to_str(str, (len == -1) ? strlen(str) : len));
+		g_error_free(error);
+		return g_strdup(QQ_NULL_MSG);
+	}			// if error
+}				// _my_convert
+
+/*****************************************************************************/
+// take the input as a pascal string and return a converted c-string in UTF-8
+// returns the number of bytes read, return -1 if fatal error
+// the converted UTF-8 will be save in ret, 
+gint convert_as_pascal_string(guint8 * data, gchar ** ret, const gchar * from_charset) {
+	guint8 len;
+
+	g_return_val_if_fail(data != NULL && from_charset != NULL, -1);
+
+	len = data[0];
+	*ret = _my_convert(data + 1, (gssize) len, UTF8, from_charset);
+
+	return len + 1;
+}				// convert_as_pascal_string
+
+/*****************************************************************************/
+// convert QQ formatted msg to GAIM formatted msg (and UTF-8)
+gchar *qq_encode_to_gaim(guint8 * data, gint len, const gchar * msg)
+{
+	GString *encoded;
+	guint8 font_attr, font_size, color[3], bar, *cursor;
+	gboolean is_bold, is_italic, is_underline;
+	guint16 charset_code;
+	gchar *font_name, *color_code, *msg_utf8, *ret;
+
+	cursor = data;
+	_qq_show_packet("QQ_MESG recv for font style", data, len);
+
+	read_packet_b(data, &cursor, len, &font_attr);
+	read_packet_data(data, &cursor, len, color, 3);	// red,green,blue
+	color_code = g_strdup_printf("#%02x%02x%02x", color[0], color[1], color[2]);
+
+	read_packet_b(data, &cursor, len, &bar);	// skip, not sure of its use
+	read_packet_w(data, &cursor, len, &charset_code);
+
+	font_name = g_strndup(cursor, data + len - cursor);
+
+	font_size = _get_size(font_attr);
+	is_bold = _check_bold(font_attr);
+	is_italic = _check_italic(font_attr);
+	is_underline = _check_underline(font_attr);
+
+	// although there is charset returned from QQ msg, it is can not be used
+	// for example, if a user send a Chinese message from English windows
+	// the charset_code in QQ msg is 0x0000, not 0x8602
+	// therefore, it is better to use uniform conversion.
+	// by default, we use GBK, which includes all character of SC, TC, and EN
+	msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT);
+	encoded = g_string_new("");
+
+	// Henry: The range QQ sends rounds from 8 to 22, where a font size
+	// of 10 is equal to 3 in html font tag
+	g_string_append_printf(encoded,
+			       "<font color=\"%s\"><font face=\"%s\"><font size=\"%d\">",
+			       color_code, font_name, font_size / 3);
+	gaim_debug(GAIM_DEBUG_INFO, "QQ_MESG",
+		   "recv <font color=\"%s\"><font face=\"%s\"><font size=\"%d\">\n",
+		   color_code, font_name, font_size / 3);
+	g_string_append(encoded, msg_utf8);
+
+	if (is_bold) {
+		g_string_prepend(encoded, "<b>");
+		g_string_append(encoded, "</b>");
+	}
+	if (is_italic) {
+		g_string_prepend(encoded, "<i>");
+		g_string_append(encoded, "</i>");
+	}
+	if (is_underline) {
+		g_string_prepend(encoded, "<u>");
+		g_string_append(encoded, "</u>");
+	}
+
+	g_string_append(encoded, "</font></font></font>");
+	ret = encoded->str;
+
+	g_free(msg_utf8);
+	g_free(font_name);
+	g_free(color_code);
+	g_string_free(encoded, FALSE);
+
+	return ret;
+}				// qq_encode_to_gaim
+
+/*****************************************************************************/
+// two convenient methods, using _my_convert
+gchar *utf8_to_qq(const gchar * str, const gchar * to_charset)
+{
+	return _my_convert(str, -1, to_charset, UTF8);
+}				// utf8_to_qq
+
+gchar *qq_to_utf8(const gchar * str, const gchar * from_charset)
+{
+	return _my_convert(str, -1, UTF8, from_charset);
+}				// qq_to_utf8
+
+/*****************************************************************************/
+// QQ uses binary code for smiley, while gaim uses strings. 
+// there is a mapping relations between these two.
+gchar *qq_smiley_to_gaim(gchar * text)
+{
+	gint index;
+	gchar qq_smiley, *cur_seg, **segments, *ret;
+	GString *converted;
+
+	converted = g_string_new("");
+	segments = split_data(text, strlen(text), "\x14", 0);
+	g_string_append(converted, segments[0]);
+
+	while ((*(++segments)) != NULL) {
+		cur_seg = *segments;
+		qq_smiley = cur_seg[0];
+		for (index = 0; index < QQ_SMILEY_AMOUNT; index++)
+			if (qq_smiley_map[index] == qq_smiley)
+				break;
+		if (index >= QQ_SMILEY_AMOUNT)
+			g_string_append(converted, QQ_NULL_SMILEY);
+		else {
+			g_string_append(converted, gaim_smiley_map[index]);
+			g_string_append(converted, (cur_seg + 1));
+		}		// if index
+	}			// while
+
+	ret = converted->str;
+	g_string_free(converted, FALSE);
+	return ret;
+}				// qq_smiley_to_gaim
+
+/*****************************************************************************/
+// convert smiley from gaim style to qq binary code
+gchar *gaim_smiley_to_qq(gchar * text)
+{
+	gchar *begin, *cursor, *ret;
+	gint index;
+	GString *converted;
+
+	converted = g_string_new(text);
+
+	for (index = 0; index < QQ_SMILEY_AMOUNT; index++) {
+		begin = cursor = converted->str;
+		while ((cursor = g_strstr_len(cursor, -1, gaim_smiley_map[index]))) {
+			g_string_erase(converted, (cursor - begin), strlen(gaim_smiley_map[index]));
+			g_string_insert_c(converted, (cursor - begin), 0x14);
+			g_string_insert_c(converted, (cursor - begin + 1), qq_smiley_map[index]);
+			cursor++;
+		}		// while
+	}			// for
+	g_string_append_c(converted, 0x20);	// important for last smiiley
+
+	ret = converted->str;
+	g_string_free(converted, FALSE);
+	return ret;
+}				// gaim_smiley_to_qq
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/char_conv.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,46 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_CHAR_CONV_H_
+#define _QQ_CHAR_CONV_H_
+
+#include <glib.h>
+
+#define QQ_CHARSET_DEFAULT      "GBK"
+
+gint convert_as_pascal_string(guint8 * data, gchar ** ret, const gchar * from_charset);
+
+gchar *qq_smiley_to_gaim(gchar * text);
+
+gchar *gaim_smiley_to_qq(gchar * text);
+
+gchar *utf8_to_qq(const gchar * str, const gchar * to_charset);
+gchar *qq_to_utf8(const gchar * str, const gchar * from_charset);
+gchar *qq_encode_to_gaim(guint8 * font_attr_data, gint len, const gchar * msg);
+
+gchar *qq_im_filter_html(const gchar * text);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/crypt.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,289 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *
+ * OICQ encryption algorithm
+ * Convert from ASM code provided by PerlOICQ
+ * 
+ * Puzzlebird, Nov-Dec 2002
+ */
+
+// START OF FILE
+/*****************************************************************************/
+/*Notes: (OICQ uses 0x10 iterations, and modified something...)
+
+IN : 64  bits of data in v[0] - v[1].
+OUT: 64  bits of data in w[0] - w[1].
+KEY: 128 bits of key  in k[0] - k[3].
+
+delta is chosen to be the real part of 
+the golden ratio: Sqrt(5/4) - 1/2 ~ 0.618034 multiplied by 2^32. 
+
+0x61C88647 is what we can track on the ASM codes.!!
+*/
+
+#ifndef _WIN32
+#include <arpa/inet.h>
+#else
+#include "win32dep.h"
+#endif
+
+#include <string.h>
+
+#include "crypt.h"
+#include "debug.h"              // gaim_debug, by gfhuang
+
+/*****************************************************************************/
+static void qq_encipher(unsigned long *const v, const unsigned long *const k, unsigned long *const w)
+{
+	register unsigned long y = ntohl(v[0]), z = ntohl(v[1]), a = ntohl(k[0]), b = ntohl(k[1]), c = ntohl(k[2]), d = ntohl(k[3]), n = 0x10, sum = 0, delta = 0x9E3779B9;	/*  0x9E3779B9 - 0x100000000 = -0x61C88647 */
+
+	while (n-- > 0) {
+		sum += delta;
+		y += ((z << 4) + a) ^ (z + sum) ^ ((z >> 5) + b);
+		z += ((y << 4) + c) ^ (y + sum) ^ ((y >> 5) + d);
+	}			// while
+
+	w[0] = htonl(y);
+	w[1] = htonl(z);
+}				// qq_enciper
+
+/*****************************************************************************/
+static void qq_decipher(unsigned long *const v, const unsigned long *const k, unsigned long *const w)
+{
+	register unsigned long y = ntohl(v[0]), z = ntohl(v[1]), a = ntohl(k[0]), b = ntohl(k[1]), c = ntohl(k[2]), d = ntohl(k[3]), n = 0x10, sum = 0xE3779B90,	// why this ? must be related with n value
+	    delta = 0x9E3779B9;
+
+	/* sum = delta<<5, in general sum = delta * n */
+	while (n-- > 0) {
+		z -= ((y << 4) + c) ^ (y + sum) ^ ((y >> 5) + d);
+		y -= ((z << 4) + a) ^ (z + sum) ^ ((z >> 5) + b);
+		sum -= delta;
+	}
+
+	w[0] = htonl(y);
+	w[1] = htonl(z);
+}				// qq_decipher
+
+/********************************************************************
+ * encrypt part
+ *******************************************************************/
+
+static void qq_encrypt(unsigned char *instr, int instrlen, unsigned char *key, unsigned char *outstr, int *outstrlen_prt)
+{
+	unsigned char plain[8],	// plain text buffer
+	 plain_pre_8[8],	// plain text buffer, previous 8 bytes
+	*crypted,		// crypted text
+	*crypted_pre_8,		// crypted test, previous 8 bytes
+	*inp;			// current position in instr
+	int pos_in_byte = 1,	// loop in the byte 
+	    is_header = 1,	// header is one byte
+	    count = 0,		// number of bytes being crypted
+	    padding = 0;	// number of padding stuff
+
+	int rand(void) {	// it can be the real random seed function
+		return 0xdead;
+	}			// override with number, convenient for debug
+
+  /*** we encrypt every eight byte ***/
+	void encrypt_every_8_byte(void) {
+		for (pos_in_byte = 0; pos_in_byte < 8; pos_in_byte++) {
+			if (is_header) {
+				plain[pos_in_byte] ^= plain_pre_8[pos_in_byte];
+			} else {
+				plain[pos_in_byte] ^= crypted_pre_8[pos_in_byte];
+			}
+		}		// prepare plain text
+		qq_encipher((unsigned long *) plain, (unsigned long *) key, (unsigned long *) crypted);	// encrypt it
+
+		for (pos_in_byte = 0; pos_in_byte < 8; pos_in_byte++) {
+			crypted[pos_in_byte] ^= plain_pre_8[pos_in_byte];
+		}
+		memcpy(plain_pre_8, plain, 8);	// prepare next
+
+		crypted_pre_8 = crypted;	// store position of previous 8 byte
+		crypted += 8;	// prepare next output
+		count += 8;	// outstrlen increase by 8
+		pos_in_byte = 0;	// back to start
+		is_header = 0;	// and exit header
+	}			// encrypt_every_8_byte
+
+	pos_in_byte = (instrlen + 0x0a) % 8;	// header padding decided by instrlen
+	if (pos_in_byte) {
+		pos_in_byte = 8 - pos_in_byte;
+	}
+	plain[0] = (rand() & 0xf8) | pos_in_byte;
+
+	memset(plain + 1, rand() & 0xff, pos_in_byte++);
+	memset(plain_pre_8, 0x00, sizeof(plain_pre_8));
+
+	crypted = crypted_pre_8 = outstr;
+
+	padding = 1;		// pad some stuff in header
+	while (padding <= 2) {	// at most two byte 
+		if (pos_in_byte < 8) {
+			plain[pos_in_byte++] = rand() & 0xff;
+			padding++;
+		}
+		if (pos_in_byte == 8) {
+			encrypt_every_8_byte();
+		}
+	}
+
+	inp = instr;
+	while (instrlen > 0) {
+		if (pos_in_byte < 8) {
+			plain[pos_in_byte++] = *(inp++);
+			instrlen--;
+		}
+		if (pos_in_byte == 8) {
+			encrypt_every_8_byte();
+		}
+	}
+
+	padding = 1;		// pad some stuff in tailer
+	while (padding <= 7) {	// at most sever byte
+		if (pos_in_byte < 8) {
+			plain[pos_in_byte++] = 0x00;
+			padding++;
+		}
+		if (pos_in_byte == 8) {
+			encrypt_every_8_byte();
+		}
+	}
+
+	*outstrlen_prt = count;
+}				// qq_encrypt
+
+
+/******************************************************************** 
+ * [decrypt part]
+ * return 0 if failed, otherwise return 1
+ ********************************************************************/
+
+static int qq_decrypt(unsigned char *instr, int instrlen, unsigned char *key, unsigned char *outstr, int *outstrlen_ptr)
+{
+	unsigned char decrypted[8], m[8], *crypt_buff, *crypt_buff_pre_8, *outp;
+	int count, context_start, pos_in_byte, padding;
+
+	int decrypt_every_8_byte(void) {
+		for (pos_in_byte = 0; pos_in_byte < 8; pos_in_byte++) {
+			if (context_start + pos_in_byte >= instrlen)
+				return 1;
+			decrypted[pos_in_byte] ^= crypt_buff[pos_in_byte];
+		}
+		qq_decipher((unsigned long *) decrypted, (unsigned long *) key, (unsigned long *) decrypted);
+
+		context_start += 8;
+		crypt_buff += 8;
+		pos_in_byte = 0;
+		return 1;
+	}			// decrypt_every_8_byte
+
+	// at least 16 bytes and %8 == 0
+	if ((instrlen % 8) || (instrlen < 16)) { 
+		//debug info by gfhuang
+		 gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Packet len is not times of 8 bytes, read %d bytes\n", instrlen);
+		return 0;
+	}
+	// get information from header
+	qq_decipher((unsigned long *) instr, (unsigned long *) key, (unsigned long *) decrypted);
+	pos_in_byte = decrypted[0] & 0x7;
+	count = instrlen - pos_in_byte - 10;	// this is the plaintext length
+	// return if outstr buffer is not large enought or error plaintext length
+	if (*outstrlen_ptr < count || count < 0) {
+		 gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Buffer len %d is less than real len %d", *outstrlen_ptr, count);
+		return 0;
+	}
+
+	memset(m, 0, 8);
+	crypt_buff_pre_8 = m;
+	*outstrlen_ptr = count;	// everything is ok! set return string length
+
+	crypt_buff = instr + 8;	// address of real data start 
+	context_start = 8;	// context is at the second 8 byte
+	pos_in_byte++;		// start of paddng stuff
+
+	padding = 1;		// at least one in header
+	while (padding <= 2) {	// there are 2 byte padding stuff in header
+		if (pos_in_byte < 8) {	// bypass the padding stuff, none sense data
+			pos_in_byte++;
+			padding++;
+		}
+		if (pos_in_byte == 8) {
+			crypt_buff_pre_8 = instr;
+			if (!decrypt_every_8_byte()) {
+				 gaim_debug(GAIM_DEBUG_ERROR, "QQ", "decrypt every 8 bytes error A");
+				return 0;
+			}
+		}
+	}			// while
+
+	outp = outstr;
+	while (count != 0) {
+		if (pos_in_byte < 8) {
+			*outp = crypt_buff_pre_8[pos_in_byte] ^ decrypted[pos_in_byte];
+			outp++;
+			count--;
+			pos_in_byte++;
+		}
+		if (pos_in_byte == 8) {
+			crypt_buff_pre_8 = crypt_buff - 8;
+			if (!decrypt_every_8_byte()) {
+				 gaim_debug(GAIM_DEBUG_ERROR, "QQ", "decrypt every 8 bytes error B");
+				return 0;
+			}
+		}
+	}			// while
+
+	for (padding = 1; padding < 8; padding++) {
+		if (pos_in_byte < 8) {
+			if (crypt_buff_pre_8[pos_in_byte] ^ decrypted[pos_in_byte])
+				return 0;
+			pos_in_byte++;
+		}
+		if (pos_in_byte == 8) {
+			crypt_buff_pre_8 = crypt_buff;
+			if (!decrypt_every_8_byte()) {
+				 gaim_debug(GAIM_DEBUG_ERROR, "QQ", "decrypt every 8 bytes error C");
+				return 0;
+			}
+		}
+	}			// for
+	return 1;
+}				// qq_decrypt
+
+/*****************************************************************************/
+/* This is the Public Function */
+// return 1 is succeed, otherwise return 0
+int qq_crypt(unsigned char flag,
+	     unsigned char *instr, int instrlen, unsigned char *key, unsigned char *outstr, int *outstrlen_ptr)
+{
+	if (flag == DECRYPT)
+		return qq_decrypt(instr, instrlen, key, outstr, outstrlen_ptr);
+	else if (flag == ENCRYPT)
+		qq_encrypt(instr, instrlen, key, outstr, outstrlen_ptr);
+
+	return 1;		// flag must be DECRYPT or ENCRYPT
+}				// qq_crypt
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/crypt.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,36 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_CRYPT_H_
+#define _QQ_CRYPT_H_
+
+#define DECRYPT 0x00
+#define ENCRYPT 0x01
+
+int qq_crypt(unsigned char flag,
+	     unsigned char *instr, int instrlen, unsigned char *key, unsigned char *outstr, int *outstrlen_ptr);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/file_trans.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,868 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ *	Author: Henry Ou <henry@linux.net>
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifdef _WIN32
+#define random rand
+#endif
+
+#include "debug.h"		// gaim_debug
+#include "ft.h"			// gaim_xfer
+//#include "md5.h"
+#include "cipher.h"         //by gfhuang
+
+#include "file_trans.h"
+#include "send_file.h"	// ft_info
+#include "packet_parse.h"	//read_packet
+#include "send_core.h"
+#include "header_info.h"
+#include "im.h"		//gen_session_md5
+#include "crypt.h"		//qq_crypt
+#include "proxy.h"		//qq_proxy_write
+
+extern gchar*
+hex_dump_to_str (const guint8 * buffer, gint bytes);
+
+struct _qq_file_header {
+	guint8 tag;
+	guint16 client_ver;
+	guint8 file_key;
+	guint32 sender_uid;
+	guint32 receiver_uid;
+};
+
+typedef struct _qq_file_header qq_file_header;
+
+static guint32 _get_file_key(guint8 seed)
+{
+	guint32 key;
+	key = seed | (seed << 8) | (seed << 16) | (seed << 24);
+	return key;
+}
+		
+static guint32 _gen_file_key()
+{
+	guint8 seed;
+	
+	seed = random();
+	return _get_file_key(seed);
+}
+
+static guint32 _decrypt_qq_uid(guint32 uid, guint32 key)
+{
+	return ~(uid ^ key);
+}
+
+static guint32 _encrypt_qq_uid(guint32 uid, guint32 key)
+{
+	return (~uid) ^ key;
+}
+
+static void _fill_filename_md5(const gchar *filename, gchar *md5)
+{
+//	md5_state_t ctx;   //gfhuang
+	GaimCipher *cipher;
+	GaimCipherContext *context;
+
+	g_return_if_fail(filename != NULL && md5 != NULL);
+
+	cipher = gaim_ciphers_find_cipher("md5");
+	context = gaim_cipher_context_new(cipher, NULL);
+	gaim_cipher_context_append(context, filename, strlen(filename));
+	gaim_cipher_context_digest(context, 16, md5, NULL);
+	gaim_cipher_context_destroy(context);
+/*    gfhuang
+	md5_init(&ctx);
+	md5_append(&ctx, filename, strlen(filename));
+	md5_finish(&ctx, md5);
+*/
+}
+
+static void _fill_file_md5(const gchar *filename, gint filelen, gchar *md5)
+{
+	FILE *fp;
+	gchar *buffer;
+//	md5_state_t ctx;    //gfhuang
+	GaimCipher *cipher;
+	GaimCipherContext *context;
+
+	const gint QQ_MAX_FILE_MD5_LENGTH = 10002432;
+
+	g_return_if_fail(filename != NULL && md5 != NULL);
+	if (filelen > QQ_MAX_FILE_MD5_LENGTH) 
+		filelen = QQ_MAX_FILE_MD5_LENGTH;
+
+	fp = fopen(filename, "rb");
+	g_return_if_fail(fp != NULL);
+
+	buffer = g_newa(gchar, filelen);
+	g_return_if_fail(buffer != NULL);
+	fread(buffer, filelen, 1, fp);
+
+	cipher = gaim_ciphers_find_cipher("md5");
+	context = gaim_cipher_context_new(cipher, NULL);
+	gaim_cipher_context_append(context, buffer, filelen);
+	gaim_cipher_context_digest(context, 16, md5, NULL);
+	gaim_cipher_context_destroy(context);
+/* by  gfhuang
+	md5_init(&ctx);
+	md5_append(&ctx, buffer, filelen);
+	md5_finish(&ctx, md5);
+*/
+	fclose(fp);
+}
+
+static void _qq_get_file_header(guint8 *buf, guint8 **cursor, gint buflen, qq_file_header *fh)
+{
+	read_packet_b(buf, cursor, buflen, &(fh->tag));
+	read_packet_w(buf, cursor, buflen, &(fh->client_ver));
+	read_packet_b(buf, cursor, buflen, &fh->file_key);
+	read_packet_dw(buf, cursor, buflen, &(fh->sender_uid));
+	read_packet_dw(buf, cursor, buflen, &(fh->receiver_uid));
+
+	fh->sender_uid = _decrypt_qq_uid(fh->sender_uid, _get_file_key(fh->file_key));
+	fh->receiver_uid = _decrypt_qq_uid(fh->receiver_uid, _get_file_key(fh->file_key));
+}
+
+static const gchar *qq_get_file_cmd_desc(gint type)
+{
+	switch (type) {
+		case QQ_FILE_CMD_SENDER_SAY_HELLO:
+			return "QQ_FILE_CMD_SENDER_SAY_HELLO";
+		case QQ_FILE_CMD_SENDER_SAY_HELLO_ACK:
+			return "QQ_FILE_CMD_SENDER_SAY_HELLO_ACK";
+		case QQ_FILE_CMD_RECEIVER_SAY_HELLO:
+			return "QQ_FILE_CMD_RECEIVER_SAY_HELLO";
+		case QQ_FILE_CMD_RECEIVER_SAY_HELLO_ACK:
+			return "QQ_FILE_CMD_RECEIVER_SAY_HELLO_ACK";
+		case QQ_FILE_CMD_NOTIFY_IP_ACK:
+			return "QQ_FILE_CMD_NOTIFY_IP_ACK";
+		case QQ_FILE_CMD_PING:
+			return "QQ_FILE_CMD_PING";
+		case QQ_FILE_CMD_PONG:
+			return "QQ_FILE_CMD_PONG";
+		case QQ_FILE_CMD_INITATIVE_CONNECT:
+			return "QQ_FILE_CMD_INITATIVE_CONNECT";
+		case QQ_FILE_CMD_FILE_OP:
+			return "QQ_FILE_CMD_FILE_OP";
+		case QQ_FILE_CMD_FILE_OP_ACK:
+			return "QQ_FILE_CMD_FILE_OP_ACK";
+		case QQ_FILE_BASIC_INFO:
+			return "QQ_FILE_BASIC_INFO";
+		case QQ_FILE_DATA_INFO:
+			return "QQ_FILE_DATA_INFO";
+		case QQ_FILE_EOF:
+			return "QQ_FILE_EOF";
+		default:
+			return "UNKNOWN_TYPE";
+	}
+}
+
+/* The memmap version has better performance for big files transfering
+ * but it will spend plenty of memory, so do not use it in a low-memory host
+ */
+#ifdef USE_MMAP
+#include <sys/mman.h>
+
+static int _qq_xfer_open_file(const gchar *filename, const gchar *method, GaimXfer *xfer)
+{
+	ft_info *info = xfer->data;
+	int fd;
+	if (method[0] == 'r') {
+		fd = open(gaim_xfer_get_local_filename(xfer), O_RDONLY);
+		info->buffer = mmap(0, gaim_xfer_get_size(xfer), PROT_READ, MAP_PRIVATE, fd, 0);
+	}
+	else 
+	{
+		fd = open(gaim_xfer_get_local_filename(xfer), O_RDWR|O_CREAT, 0644);
+		info->buffer = mmap(0, gaim_xfer_get_size(xfer), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FILE, fd, 0);
+	}
+		
+	if (info->buffer == NULL) {
+		return - 1;
+	}
+	return 0;
+}
+
+static gint 
+_qq_xfer_read_file(guint8 *buffer, guint index, guint len, GaimXfer *xfer)
+{
+	ft_info *info = xfer->data;
+	gint readbytes;
+
+	buffer = info->buffer + len * index;
+	readbytes = gaim_xfer_get_size(xfer) - (buffer - info->buffer);
+	if (readbytes > info->fragment_len) readbytes = info->fragment_len;
+	return readbytes;
+}
+
+static gint
+_qq_xfer_write_file(guint8 *buffer, guint index, guint len, GaimXfer *xfer)
+{
+	ft_info *info = xfer->data;
+
+	memcpy(info->buffer + index * len, buffer, len);
+	return 0;
+}
+
+void qq_xfer_close_file(GaimXfer *xfer)
+{
+	ft_info *info = xfer->data;
+
+	if (info->buffer) munmap(info->buffer, gaim_xfer_get_size(xfer));
+}
+#else
+static int 
+_qq_xfer_open_file(const gchar *filename, const gchar *method, GaimXfer *xfer)
+{
+	ft_info *info = xfer->data;
+	info->dest_fp = fopen(gaim_xfer_get_local_filename(xfer), method);
+	if (info->dest_fp == NULL) {
+		return -1;
+	}
+	return 0;
+}
+
+static gint
+_qq_xfer_read_file(guint8 *buffer, guint index, guint len, GaimXfer *xfer)
+{
+	ft_info *info = xfer->data;
+
+	fseek(info->dest_fp, index * len, SEEK_SET);
+	return fread(buffer, 1, len, info->dest_fp);
+}
+
+static gint
+_qq_xfer_write_file(guint8 *buffer, guint index, guint len, GaimXfer *xfer)
+{
+	ft_info *info = xfer->data;
+	fseek(info->dest_fp, index * len, SEEK_SET);
+	return fwrite(buffer, 1, len, info->dest_fp);
+}
+
+void qq_xfer_close_file(GaimXfer *xfer)
+{
+	ft_info *info = xfer->data;
+
+	if (info->dest_fp) fclose(info->dest_fp);
+}
+#endif
+
+static gint 
+_qq_send_file(GaimConnection *gc, guint8 *data, gint len, guint16 packet_type, guint32 to_uid)
+{
+	gint bytes;
+	guint8 *cursor, *buf;
+	guint32 file_key;
+	qq_data *qd;
+	ft_info *info;
+
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, -1);
+	qd = (qq_data *) gc->proto_data;
+	g_return_val_if_fail(qd != NULL && qd->session_key != NULL, -1);
+	info = (ft_info *) qd->xfer->data;
+	bytes = 0;
+
+	buf = g_newa(guint8, MAX_PACKET_SIZE);
+	cursor = buf;
+	file_key = _gen_file_key();
+
+	bytes += create_packet_b(buf, &cursor, packet_type);
+	bytes += create_packet_w(buf, &cursor, QQ_CLIENT);
+	bytes += create_packet_b(buf, &cursor, file_key & 0xff);
+	bytes += create_packet_dw(buf, &cursor, _encrypt_qq_uid(qd->uid, file_key));
+	bytes += create_packet_dw(buf, &cursor, _encrypt_qq_uid(to_uid, file_key));
+	bytes += create_packet_data(buf, &cursor, data, len);
+
+	ssize_t _qq_xfer_write(const char *buf, size_t len, GaimXfer *xfer);
+	if (bytes == len + 12) {
+		//gaim_xfer_write(qd->xfer, buf, bytes);
+		_qq_xfer_write(buf, bytes, qd->xfer);
+	} else
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "send_file: want %d but got %d\n", len + 12, bytes);
+	return bytes;
+}
+
+
+extern gchar *_gen_session_md5(gint uid, gchar *session_key);
+
+/********************************************************************************/
+// send a file to udp channel with QQ_FILE_CONTROL_PACKET_TAG
+void qq_send_file_ctl_packet(GaimConnection *gc, guint16 packet_type, guint32 to_uid, guint8 hellobyte)
+{
+	qq_data *qd;
+	gint bytes, bytes_expected, encrypted_len;
+	guint8 *raw_data, *cursor, *encrypted_data;
+	gchar *md5;
+	time_t now;
+	ft_info *info;
+	
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+	info = (ft_info *) qd->xfer->data;
+
+	raw_data = g_new0 (guint8, 61);
+	cursor = raw_data;
+	
+	bytes = 0;
+	now = time(NULL);
+	md5 = _gen_session_md5(qd->uid, qd->session_key);
+
+	bytes += create_packet_data(raw_data, &cursor, md5, 16);
+	bytes += create_packet_w(raw_data, &cursor, packet_type);
+	switch (packet_type) {
+		case QQ_FILE_CMD_SENDER_SAY_HELLO:
+		case QQ_FILE_CMD_SENDER_SAY_HELLO_ACK:
+		case QQ_FILE_CMD_RECEIVER_SAY_HELLO_ACK:
+		case QQ_FILE_CMD_NOTIFY_IP_ACK:
+		case QQ_FILE_CMD_RECEIVER_SAY_HELLO:
+			bytes += create_packet_w(raw_data, &cursor, info->send_seq);
+			break;
+		default:
+			bytes += create_packet_w(raw_data, &cursor, ++qd->send_seq);
+	}
+	bytes += create_packet_dw(raw_data, &cursor, (guint32) now);
+	bytes += create_packet_b(raw_data, &cursor, 0x00);
+	bytes += create_packet_b(raw_data, &cursor, qd->my_icon);
+	bytes += create_packet_dw(raw_data, &cursor, 0x00000000);
+	bytes += create_packet_dw(raw_data, &cursor, 0x00000000);
+	bytes += create_packet_dw(raw_data, &cursor, 0x00000000);
+	bytes += create_packet_dw(raw_data, &cursor, 0x00000000);
+	bytes += create_packet_w(raw_data, &cursor, 0x0000);
+	bytes += create_packet_b(raw_data, &cursor, 0x00);
+	// 0x65: send a file, 0x6b: send a custom face, by gfhuang
+	bytes += create_packet_b(raw_data, &cursor, QQ_FILE_TRANSFER_FILE); // FIXME temp by gfhuang
+	switch (packet_type)
+	{
+		case QQ_FILE_CMD_SENDER_SAY_HELLO:
+		case QQ_FILE_CMD_RECEIVER_SAY_HELLO:
+		case QQ_FILE_CMD_SENDER_SAY_HELLO_ACK:
+		case QQ_FILE_CMD_RECEIVER_SAY_HELLO_ACK:
+			bytes += create_packet_b(raw_data, &cursor, 0x00);
+			bytes += create_packet_b(raw_data, &cursor, hellobyte);
+			bytes_expected = 48;
+			break;
+		case QQ_FILE_CMD_PING:
+		case QQ_FILE_CMD_PONG:
+		case QQ_FILE_CMD_NOTIFY_IP_ACK:
+			bytes += qq_fill_conn_info(raw_data, &cursor, info);
+			bytes_expected = 61;
+			break;
+		default:
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "qq_send_file_ctl_packet: Unknown packet type[%d]\n",
+					packet_type);
+			bytes_expected = 0;
+	}
+	
+	if (bytes == bytes_expected) {
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "sending packet[%s]: \n%s", qq_get_file_cmd_desc(packet_type),
+				hex_dump_to_str(raw_data, bytes));
+		encrypted_len = bytes + 16;
+		encrypted_data = g_newa(guint8, encrypted_len);
+		qq_crypt(ENCRYPT, raw_data, bytes, info->file_session_key, encrypted_data, &encrypted_len);
+		//debug: try to decrypt it
+		/*
+		if (QQ_DEBUG) {
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "encrypted packet: \n%s",
+				hex_dump_to_str(encrypted_data, encrypted_len));
+			guint8 *buf;
+			int buflen;
+			buf = g_newa(guint8, MAX_PACKET_SIZE);
+			buflen = encrypted_len;
+			if (qq_crypt(DECRYPT, encrypted_data, encrypted_len, info->file_session_key, buf, &buflen)) {
+				gaim_debug(GAIM_DEBUG_INFO, "QQ", "decrypt success\n");
+				if (buflen == bytes && memcmp(raw_data, buf, buflen) == 0)
+					gaim_debug(GAIM_DEBUG_INFO, "QQ", "checksum ok\n");
+				gaim_debug(GAIM_DEBUG_INFO, "QQ", "decrypted packet: \n%s",
+					hex_dump_to_str(buf, buflen));
+			} else {
+				gaim_debug(GAIM_DEBUG_INFO, "QQ", "decrypt fail\n");
+			}
+		}
+		*/
+
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "<== send %s packet\n", qq_get_file_cmd_desc(packet_type));
+		_qq_send_file(gc, encrypted_data, encrypted_len, QQ_FILE_CONTROL_PACKET_TAG, info->to_uid);
+	}
+	else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "qq_send_file_ctl_packet: Expected to get %d bytes, but get %d",
+				bytes_expected, bytes);
+
+	g_free(md5);
+}
+
+/********************************************************************************/
+// send a file to udp channel with QQ_FILE_DATA_PACKET_TAG
+static void 
+_qq_send_file_data_packet(GaimConnection *gc, guint16 packet_type, guint8 sub_type, guint32 fragment_index, 
+		guint16 seq, guint8 *data, gint len)
+{
+	gint bytes;
+	guint8 *raw_data, *cursor;
+	guint32 fragment_size = 1000;
+	gchar file_md5[16], filename_md5[16], *filename;
+	gint filename_len, filesize;
+	qq_data *qd;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+	ft_info *info = (ft_info *) qd->xfer->data;
+
+	filename = (gchar *) gaim_xfer_get_filename(qd->xfer);
+	filesize = gaim_xfer_get_size(qd->xfer);
+
+	raw_data = g_newa(guint8, MAX_PACKET_SIZE);
+	cursor = raw_data;
+	bytes = 0;
+
+	bytes += create_packet_b(raw_data, &cursor, 0x00);
+	bytes += create_packet_w(raw_data, &cursor, packet_type);
+	switch (packet_type) {
+		case QQ_FILE_BASIC_INFO:
+		case QQ_FILE_DATA_INFO:
+		case QQ_FILE_EOF:
+			bytes += create_packet_w(raw_data, &cursor, 0x0000);
+			bytes += create_packet_b(raw_data, &cursor, 0x00);
+			break;
+		case QQ_FILE_CMD_FILE_OP:
+			switch(sub_type)
+			{
+				case QQ_FILE_BASIC_INFO:
+					filename_len = strlen(filename);
+					_fill_filename_md5(filename, filename_md5);
+					_fill_file_md5(gaim_xfer_get_local_filename(qd->xfer),
+							gaim_xfer_get_size(qd->xfer),
+							file_md5);
+
+					info->fragment_num = (filesize - 1) / QQ_FILE_FRAGMENT_MAXLEN + 1;
+					info->fragment_len = QQ_FILE_FRAGMENT_MAXLEN;
+
+					gaim_debug(GAIM_DEBUG_INFO, "QQ", "start transfering data, %d fragments with %d length each\n",
+							info->fragment_num, info->fragment_len);
+					/* Unknown */
+					bytes += create_packet_w(raw_data, &cursor, 0x0000);
+					/* Sub-operation type */
+					bytes += create_packet_b(raw_data, &cursor, sub_type);
+					/* Length of file */
+					bytes += create_packet_dw(raw_data, &cursor, filesize);
+					/* Number of fragments */
+					bytes += create_packet_dw(raw_data, &cursor, info->fragment_num);
+					/* Length of a single fragment */
+					bytes += create_packet_dw(raw_data, &cursor, info->fragment_len);
+					bytes += create_packet_data(raw_data, &cursor, file_md5, 16);
+					bytes += create_packet_data(raw_data, &cursor, filename_md5, 16);
+					/* Length of filename */
+					bytes += create_packet_w(raw_data, &cursor, filename_len);
+					/* 8 unknown bytes */
+					bytes += create_packet_dw(raw_data, &cursor, 0x00000000);
+					bytes += create_packet_dw(raw_data, &cursor, 0x00000000);
+					/* filename */
+					bytes += create_packet_data(raw_data, &cursor, (guint8 *) filename,
+							filename_len);
+					break;
+				case QQ_FILE_DATA_INFO:
+					gaim_debug(GAIM_DEBUG_INFO, "QQ", "sending %dth fragment with length %d, offset %d\n",
+							fragment_index, len, (fragment_index-1)*fragment_size);
+					//bytes += create_packet_w(raw_data, &cursor, ++(qd->send_seq));
+					bytes += create_packet_w(raw_data, &cursor, info->send_seq);
+					bytes += create_packet_b(raw_data, &cursor, sub_type);
+					//bytes += create_packet_dw(raw_data, &cursor, fragment_index);
+					bytes += create_packet_dw(raw_data, &cursor, fragment_index - 1);
+					bytes += create_packet_dw(raw_data, &cursor, (fragment_index - 1) * fragment_size);
+					bytes += create_packet_w(raw_data, &cursor, len);
+					bytes += create_packet_data(raw_data, &cursor, data, len);
+					break;
+				case QQ_FILE_EOF:
+					gaim_debug(GAIM_DEBUG_INFO, "QQ", "end of sending data\n");
+					//bytes += create_packet_w(raw_data, &cursor, info->fragment_num + 1);
+					bytes += create_packet_w(raw_data, &cursor, info->fragment_num);
+					bytes += create_packet_b(raw_data, &cursor, sub_type);
+					//gaim_xfer_set_completed(qd->xfer, TRUE);
+			}
+			break;
+		case QQ_FILE_CMD_FILE_OP_ACK:
+			switch (sub_type)
+			{
+				case QQ_FILE_BASIC_INFO:
+					bytes += create_packet_w(raw_data, &cursor, 0x0000);
+					bytes += create_packet_b(raw_data, &cursor, sub_type);
+					bytes += create_packet_dw(raw_data, &cursor, 0x00000000);
+					break;
+				case QQ_FILE_DATA_INFO:
+					bytes += create_packet_w(raw_data, &cursor, seq);
+					bytes += create_packet_b(raw_data, &cursor, sub_type);
+					bytes += create_packet_dw(raw_data, &cursor, fragment_index);
+					break;
+				case QQ_FILE_EOF:
+					bytes += create_packet_w(raw_data, &cursor, filesize / QQ_FILE_FRAGMENT_MAXLEN + 2);
+					bytes += create_packet_b(raw_data, &cursor, sub_type);
+					break;
+			}
+	}
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "<== send %s packet\n", qq_get_file_cmd_desc(packet_type));
+	_qq_send_file(gc, raw_data, bytes, QQ_FILE_DATA_PACKET_TAG, info->to_uid);
+}
+
+/* An conversation starts like this
+ * Sender ==> Receiver [QQ_FILE_CMD_PING]
+ * Sender <== Receiver [QQ_FILE_CMD_PONG]
+ * Sender ==> Receiver [QQ_FILE_CMD_SENDER_SAY_HELLO]
+ * Sender <== Receiver [QQ_FILE_CMD_SENDER_SAY_HELLO_ACK]
+ * Sender <== Receiver [QQ_FILE_CMD_RECEIVER_SAY_HELLO]
+ * Sender ==> Receiver [QQ_FILE_CMD_RECEIVER_SAY_HELLO_ACK]
+ * Sender ==> Receiver [QQ_FILE_CMD_FILE_OP, QQ_FILE_BASIC_INFO]
+ * Sender <== Receiver [QQ_FILE_CMD_FILE_OP_ACK, QQ_FILE_BASIC_INFO]
+ * Sender ==> Receiver [QQ_FILE_CMD_FILE_OP, QQ_FILE_DATA_INFO]
+ * Sender <== Receiver [QQ_FILE_CMD_FILE_OP_ACK, QQ_FILE_DATA_INFO]
+ * Sender ==> Receiver [QQ_FILE_CMD_FILE_OP, QQ_FILE_DATA_INFO]
+ * Sender <== Receiver [QQ_FILE_CMD_FILE_OP_ACK, QQ_FILE_DATA_INFO]
+ * ......
+ * Sender ==> Receiver [QQ_FILE_CMD_FILE_OP, QQ_FILE_EOF]
+ * Sender <== Receiver [QQ_FILE_CMD_FILE_OP_ACK, QQ_FILE_EOF]
+ */
+
+
+static void
+_qq_process_recv_file_ctl_packet(GaimConnection *gc, guint8 *data, guint8 *cursor,
+		gint len, qq_file_header *fh)
+{
+	guint8 *decrypted_data;
+	gint decrypted_len;
+	qq_data *qd = (qq_data *) gc->proto_data;
+	guint16 packet_type;
+	guint16 seq;
+	guint8 hellobyte;
+	gchar *md5;
+	ft_info *info = (ft_info *) qd->xfer->data;
+
+	decrypted_data = g_newa(guint8, len);
+	decrypted_len = len;
+
+	md5 = _gen_session_md5(qd->uid, qd->session_key);
+	if (qq_crypt(DECRYPT, cursor, len - (cursor - data), md5, decrypted_data, &decrypted_len)) {
+		cursor = decrypted_data + 16;	//skip md5 section
+		read_packet_w(decrypted_data, &cursor, decrypted_len, &packet_type);
+		read_packet_w(decrypted_data, &cursor, decrypted_len, &seq);
+		cursor += 4+1+1+19+1;
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "==> [%d] receive %s packet\n", seq, qq_get_file_cmd_desc(packet_type));
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "decrypted control packet received: \n%s",
+			hex_dump_to_str(decrypted_data, decrypted_len));
+		switch (packet_type) {
+			case QQ_FILE_CMD_NOTIFY_IP_ACK:
+				cursor = decrypted_data;
+				qq_get_conn_info(decrypted_data, &cursor, decrypted_len, info);
+//				qq_send_file_ctl_packet(gc, QQ_FILE_CMD_PING, fh->sender_uid, 0);
+				qq_send_file_ctl_packet(gc, QQ_FILE_CMD_SENDER_SAY_HELLO, fh->sender_uid, 0);	
+				break;
+			case QQ_FILE_CMD_SENDER_SAY_HELLO:
+				/* I'm receiver, if we receive SAY_HELLO from sender, we send back the ACK */
+				cursor += 47;
+				read_packet_b(decrypted_data, &cursor, 
+						decrypted_len, &hellobyte);
+
+				qq_send_file_ctl_packet(gc, QQ_FILE_CMD_SENDER_SAY_HELLO_ACK, fh->sender_uid, hellobyte);
+				qq_send_file_ctl_packet(gc, QQ_FILE_CMD_RECEIVER_SAY_HELLO, fh->sender_uid, 0);
+				break;
+			case QQ_FILE_CMD_SENDER_SAY_HELLO_ACK:
+				/* I'm sender, do nothing */
+				break;
+			case QQ_FILE_CMD_RECEIVER_SAY_HELLO:
+				/* I'm sender, ack the hello packet and send the first data */
+				cursor += 47;
+				read_packet_b(decrypted_data, &cursor, 
+						decrypted_len, &hellobyte);
+				qq_send_file_ctl_packet(gc, QQ_FILE_CMD_RECEIVER_SAY_HELLO_ACK, fh->sender_uid, hellobyte);
+				_qq_send_file_data_packet(gc, QQ_FILE_CMD_FILE_OP, QQ_FILE_BASIC_INFO, 0, 0, NULL, 0);
+				break;
+			case QQ_FILE_CMD_RECEIVER_SAY_HELLO_ACK:
+				/* I'm receiver, do nothing */
+				break;
+			case QQ_FILE_CMD_PING:
+				/* I'm receiver, ack the PING */
+				qq_send_file_ctl_packet(gc, QQ_FILE_CMD_PONG, fh->sender_uid, 0);
+				break;
+			case QQ_FILE_CMD_PONG:
+				qq_send_file_ctl_packet(gc, QQ_FILE_CMD_SENDER_SAY_HELLO, fh->sender_uid, 0);
+				break;
+			default:
+				gaim_debug(GAIM_DEBUG_INFO, "QQ", "unprocess file command %d\n", packet_type);
+		}
+	} 
+	g_free(md5);
+}
+
+static void
+_qq_recv_file_progess(GaimConnection *gc, guint8 *buffer, guint16 len, guint32 index, guint32 offset)
+{
+	qq_data *qd = (qq_data *) gc->proto_data;
+	GaimXfer *xfer = qd->xfer;
+	ft_info *info = (ft_info *) xfer->data;
+	guint32 mask;
+
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "receiving %dth fragment with length %d, slide window status %o, max_fragment_index %d\n", 
+			index, len, info->window, info->max_fragment_index);
+	if (info->window == 0 && info->max_fragment_index == 0)
+	{
+		if (_qq_xfer_open_file(gaim_xfer_get_local_filename(xfer), "wb", xfer) == -1) {
+			gaim_xfer_cancel_local(xfer);
+			return;
+		}
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "object file opened for writing\n");
+	}
+	mask = 0x1 << (index % sizeof(info->window));
+	if (index < info->max_fragment_index || (info->window & mask)) {
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "duplicate %dth fragment, drop it!\n", index+1);
+		return;
+	}
+		
+	info->window |= mask;
+
+	_qq_xfer_write_file(buffer, index, len, xfer);
+	
+	xfer->bytes_sent += len;
+	xfer->bytes_remaining -= len;
+	gaim_xfer_update_progress(xfer);
+	
+	mask = 0x1 << (info->max_fragment_index % sizeof(info->window));
+	while (info->window & mask)
+	{
+		info->window &= ~mask;
+		info->max_fragment_index ++;
+		if (mask & 0x8000) mask = 0x0001;
+		else mask = mask << 1;
+	}
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "procceed %dth fragment, slide window status %o, max_fragment_index %d\n", 
+			index, info->window, info->max_fragment_index);
+}
+
+static void
+_qq_send_file_progess(GaimConnection *gc)
+{
+	qq_data *qd = (qq_data *) gc->proto_data;
+	GaimXfer *xfer = qd->xfer;
+	ft_info *info = (ft_info *) xfer->data;
+	guint32 mask;
+	guint8 *buffer;
+	guint i;
+	gint readbytes;
+	
+	if (gaim_xfer_get_bytes_remaining(xfer) <= 0) return;
+	if (info->window == 0 && info->max_fragment_index == 0)
+	{
+		if (_qq_xfer_open_file(gaim_xfer_get_local_filename(xfer), "rb", xfer) == -1) {
+			gaim_xfer_cancel_local(xfer);
+			return;
+		}
+	}
+	buffer = g_newa(guint8, info->fragment_len);
+	mask = 0x1 << (info->max_fragment_index % sizeof(info->window));
+	for (i = 0; i < sizeof(info->window); i++) {
+		if ((info->window & mask) == 0) {
+			readbytes = _qq_xfer_read_file(buffer, info->max_fragment_index + i, info->fragment_len, xfer);
+			if (readbytes > 0)
+				_qq_send_file_data_packet(gc, QQ_FILE_CMD_FILE_OP, QQ_FILE_DATA_INFO,
+					info->max_fragment_index + i + 1, 0, buffer, readbytes);
+		}
+		if (mask & 0x8000) mask = 0x0001;
+		else mask = mask << 1;
+	}
+}
+
+static void
+_qq_update_send_progess(GaimConnection *gc, guint32 fragment_index)
+{
+	qq_data *qd = (qq_data *) gc->proto_data;
+	GaimXfer *xfer = qd->xfer;
+	ft_info *info = (ft_info *) xfer->data;
+	guint32 mask;
+
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "receiving %dth fragment ack, slide window status %o, max_fragment_index %d\n", 
+			fragment_index, info->window, info->max_fragment_index);
+	if (fragment_index < info->max_fragment_index || 
+			fragment_index >= info->max_fragment_index + sizeof(info->window)) {
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "duplicate %dth fragment, drop it!\n", fragment_index+1);
+		return;
+	}
+	mask = 0x1 << (fragment_index % sizeof(info->window));
+	if ((info->window & mask) == 0)
+	{
+		info->window |= mask;
+		if (fragment_index + 1 != info->fragment_num) {
+			xfer->bytes_sent += info->fragment_len;
+		} else {
+			xfer->bytes_sent += gaim_xfer_get_size(xfer) % info->fragment_len;
+		}
+		xfer->bytes_remaining = gaim_xfer_get_size(xfer) - gaim_xfer_get_bytes_sent(xfer);
+		gaim_xfer_update_progress(xfer);
+		if (gaim_xfer_get_bytes_remaining(xfer) <= 0) {
+			/* We have finished sending the file */
+			gaim_xfer_set_completed(xfer, TRUE);
+			return;
+		}
+		mask = 0x1 << (info->max_fragment_index % sizeof(info->window));
+		while (info->window & mask)
+		{
+			//move the slide window
+			info->window &= ~mask;
+			guint8 *buffer;
+			gint readbytes;
+
+			buffer = g_newa(guint8, info->fragment_len);
+			readbytes = _qq_xfer_read_file(buffer, info->max_fragment_index + sizeof(info->window), 
+					info->fragment_len, xfer);
+			if (readbytes > 0)
+				_qq_send_file_data_packet(gc, QQ_FILE_CMD_FILE_OP, QQ_FILE_DATA_INFO,
+					info->max_fragment_index + sizeof(info->window) + 1, 0, buffer, readbytes);
+			
+			info->max_fragment_index ++;
+			if (mask & 0x8000) mask = 0x0001;
+			else mask = mask << 1;
+		}
+	}
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "procceed %dth fragment ack, slide window status %o, max_fragment_index %d\n", 
+			fragment_index, info->window, info->max_fragment_index);
+}
+
+static void
+_qq_process_recv_file_data(GaimConnection *gc, guint8 *data, guint8 *cursor,
+		gint len, guint32 to_uid)
+{
+	guint16 packet_type;
+	guint16 packet_seq;
+	guint8 sub_type;
+	guint32 fragment_index;
+	guint16 fragment_len;
+	guint32 fragment_offset;
+	qq_data *qd = (qq_data *) gc->proto_data;
+	ft_info *info = (ft_info *) qd->xfer->data;
+	
+	cursor += 1;//skip an unknown byte
+	read_packet_w(data, &cursor, len, &packet_type);
+	switch(packet_type)
+	{
+		case QQ_FILE_CMD_FILE_OP:
+			read_packet_w(data, &cursor, len, &packet_seq);
+			read_packet_b(data, &cursor, len, &sub_type);
+			switch (sub_type)
+			{
+				case QQ_FILE_BASIC_INFO:
+					cursor += 4;	//file length, we have already known it from xfer
+					read_packet_dw(data, &cursor, len, &info->fragment_num);
+					read_packet_dw(data, &cursor, len, &info->fragment_len);
+
+					/* FIXME: We must check the md5 here, if md5 doesn't match
+					 * we will ignore the packet or send sth as error number
+					 */
+
+					info->max_fragment_index = 0;
+					info->window = 0;
+					gaim_debug(GAIM_DEBUG_INFO, "QQ", "start receiving data, %d fragments with %d length each\n",
+							info->fragment_num, info->fragment_len);
+					_qq_send_file_data_packet(gc, QQ_FILE_CMD_FILE_OP_ACK, sub_type,
+							0, 0, NULL, 0);
+					break;
+				case QQ_FILE_DATA_INFO:
+					read_packet_dw(data, &cursor, len, &fragment_index);
+					read_packet_dw(data, &cursor, len, &fragment_offset);
+					read_packet_w(data, &cursor, len, &fragment_len);
+					gaim_debug(GAIM_DEBUG_INFO, "QQ", "received %dth fragment with length %d, offset %d\n",
+							fragment_index, fragment_len, fragment_offset);
+					
+					_qq_send_file_data_packet(gc, QQ_FILE_CMD_FILE_OP_ACK, sub_type,
+							fragment_index, packet_seq, NULL, 0);
+					_qq_recv_file_progess(gc, cursor, fragment_len, fragment_index, fragment_offset);
+					break;
+				case QQ_FILE_EOF:
+					gaim_debug(GAIM_DEBUG_INFO, "QQ", "end of receiving\n");
+					_qq_send_file_data_packet(gc, QQ_FILE_CMD_FILE_OP_ACK, sub_type,
+						0, 0, NULL, 0);
+					break;
+			}
+			break;
+		case QQ_FILE_CMD_FILE_OP_ACK:
+			read_packet_w(data, &cursor, len, &packet_seq);
+			read_packet_b(data, &cursor, len, &sub_type);
+			switch (sub_type)
+			{
+				case QQ_FILE_BASIC_INFO:
+					info->max_fragment_index = 0;
+					info->window = 0;
+					/* It is ready to send file data */
+					_qq_send_file_progess(gc);
+					break;
+				case QQ_FILE_DATA_INFO:
+					read_packet_dw(data, &cursor, len, &fragment_index);
+					_qq_update_send_progess(gc, fragment_index);
+					if (gaim_xfer_is_completed(qd->xfer))
+						_qq_send_file_data_packet(gc, QQ_FILE_CMD_FILE_OP, QQ_FILE_EOF, 0, 0, NULL, 0);
+				//	else
+				//		_qq_send_file_progess(gc);
+					break;
+				case QQ_FILE_EOF:
+					/* FIXME: OK, we can end the connection successfully */
+					
+					_qq_send_file_data_packet(gc, QQ_FILE_EOF, 0, 0, 0, NULL, 0);
+					gaim_xfer_set_completed(qd->xfer, TRUE);
+					break;
+			}
+			break;
+		case QQ_FILE_EOF:
+			_qq_send_file_data_packet(gc, QQ_FILE_EOF, 0, 0, 0, NULL, 0);
+			gaim_xfer_set_completed(qd->xfer, TRUE);
+			gaim_xfer_end(qd->xfer);
+			break;
+		case QQ_FILE_BASIC_INFO:
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "here\n");
+			_qq_send_file_data_packet(gc, QQ_FILE_DATA_INFO, 0, 0, 0, NULL, 0);
+			break;
+		default:
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "_qq_process_recv_file_data: unknown packet type [%d]\n",
+					packet_type);
+			break;
+	}
+}
+
+void qq_process_recv_file(GaimConnection *gc, guint8 *data, gint len)
+{
+	guint8 *cursor;
+	qq_file_header fh;
+	qq_data *qd;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	cursor = data;
+	_qq_get_file_header(data, &cursor, len, &fh);
+
+	switch (fh.tag) {
+		case QQ_FILE_CONTROL_PACKET_TAG:
+			_qq_process_recv_file_ctl_packet(gc, data, cursor, len, &fh);
+			break;
+		case QQ_FILE_DATA_PACKET_TAG:
+			_qq_process_recv_file_data(gc, data, cursor, len, fh.sender_uid);
+			break;
+		default:
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "unknown packet tag");
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/file_trans.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,68 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ *	Author: Henry Ou <henry@linux.net>
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _QQ_QQ_FILE_TRANS_H_
+#define _QQ_QQ_FILE_TRANS_H_
+
+#include "server.h"
+
+enum {
+	QQ_FILE_CMD_SENDER_SAY_HELLO = 0x31,
+	QQ_FILE_CMD_SENDER_SAY_HELLO_ACK = 0x32,
+	QQ_FILE_CMD_RECEIVER_SAY_HELLO = 0x33,
+	QQ_FILE_CMD_RECEIVER_SAY_HELLO_ACK = 0x34,
+	QQ_FILE_CMD_NOTIFY_IP_ACK = 0x3c,
+	QQ_FILE_CMD_PING = 0x3d,
+	QQ_FILE_CMD_PONG = 0x3e,
+	QQ_FILE_CMD_INITATIVE_CONNECT = 0x40
+};
+
+enum {
+	QQ_FILE_TRANSFER_FILE = 0x65,
+	QQ_FILE_TRANSFER_FACE = 0x6b
+};
+
+enum {
+	QQ_FILE_BASIC_INFO = 0x01,
+	QQ_FILE_DATA_INFO = 0x02,
+	QQ_FILE_EOF = 0x03,
+	QQ_FILE_CMD_FILE_OP = 0x07,
+	QQ_FILE_CMD_FILE_OP_ACK = 0x08
+};
+
+#define QQ_FILE_FRAGMENT_MAXLEN 1000
+
+#define QQ_FILE_CONTROL_PACKET_TAG 0x00
+//#define QQ_PACKET_TAG           0x02    // all QQ text packets starts with it
+#define QQ_FILE_DATA_PACKET_TAG 0x03
+#define QQ_FILE_AGENT_PACKET_TAG 0x04
+//#define QQ_PACKET_TAIL          0x03    // all QQ text packets end with it
+
+
+void qq_send_file_ctl_packet(GaimConnection *gc, guint16 packet_type, guint32 to_uid, guint8 hellobyte);
+void qq_process_recv_file(GaimConnection *gc, guint8 *data, gint len);
+//void qq_send_file_data_packet(GaimConnection *gc, guint16 packet_type, guint8 sub_type, guint32 fragment_index, guint16 seq, 
+//		guint8 *data, gint len);
+void qq_xfer_close_file(GaimXfer *xfer);
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,179 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "internal.h"
+
+#include "debug.h"		// gaim_debug
+#include "prpl.h"		// struct proto_chat_entry
+#include "request.h"		// gaim_request_input
+
+#include "utils.h"		// qq_string_to_dec_value
+#include "group_hash.h"		// QQ_GROUP_KEY_EXTERNAL_ID
+#include "group_info.h"		// qq_send_cmd_group_get_group_info
+#include "group_search.h"	// qq_send_cmd_group_search_group
+
+#include "group.h"
+
+/*****************************************************************************/
+static void _qq_group_search_callback(GaimConnection * gc, const gchar * input)
+{
+	guint32 external_group_id;
+
+	g_return_if_fail(gc != NULL && input != NULL);
+	external_group_id = qq_string_to_dec_value(input);
+	// 0x00000000 means search for demo group
+	qq_send_cmd_group_search_group(gc, external_group_id);
+}				// _qq_group_search_callback
+
+/*****************************************************************************/
+// This is needed for GaimChat node to be valid
+GList *qq_chat_info(GaimConnection * gc)
+{
+	GList *m;
+	struct proto_chat_entry *pce;
+
+	m = NULL;
+
+	pce = g_new0(struct proto_chat_entry, 1);
+	pce->label = _("ID: ");
+	pce->identifier = QQ_GROUP_KEY_EXTERNAL_ID;
+	m = g_list_append(m, pce);
+
+	pce = g_new0(struct proto_chat_entry, 1);
+	pce->label = _("Admin: ");
+	pce->identifier = QQ_GROUP_KEY_CREATOR_UID;
+	m = g_list_append(m, pce);
+
+	pce = g_new0(struct proto_chat_entry, 1);
+	pce->label = _("Status: ");
+	pce->identifier = QQ_GROUP_KEY_MEMBER_STATUS_DESC;
+	m = g_list_append(m, pce);
+
+	return m;
+}				// qq_chat_info
+
+/*****************************************************************************/
+//  get a list of qq groups
+GaimRoomlist *qq_roomlist_get_list(GaimConnection * gc)
+{
+	GList *fields;
+	qq_data *qd;
+	GaimRoomlist *rl;
+	GaimRoomlistField *f;
+
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	fields = NULL;
+	rl = gaim_roomlist_new(gaim_connection_get_account(gc));
+	qd->roomlist = rl;
+
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, _("Group ID"), QQ_GROUP_KEY_EXTERNAL_ID, FALSE);
+	fields = g_list_append(fields, f);
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, _("Creator"), QQ_GROUP_KEY_CREATOR_UID, FALSE);
+	fields = g_list_append(fields, f);
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING,
+				    _("Group Description"), QQ_GROUP_KEY_GROUP_DESC_UTF8, FALSE);
+	fields = g_list_append(fields, f);
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, "", QQ_GROUP_KEY_INTERNAL_ID, TRUE);
+	fields = g_list_append(fields, f);
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, "", QQ_GROUP_KEY_GROUP_TYPE, TRUE);
+	fields = g_list_append(fields, f);
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, _("Auth"), QQ_GROUP_KEY_AUTH_TYPE, TRUE);
+	fields = g_list_append(fields, f);
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, "", QQ_GROUP_KEY_GROUP_CATEGORY, TRUE);
+	fields = g_list_append(fields, f);
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, "", QQ_GROUP_KEY_GROUP_NAME_UTF8, TRUE);
+
+	fields = g_list_append(fields, f);
+	gaim_roomlist_set_fields(rl, fields);
+	gaim_roomlist_set_in_progress(qd->roomlist, TRUE);
+
+	gaim_request_input(gc, _("QQ Qun"),
+			   _("Please input external group ID"),
+			   _
+			   ("You can only search for permanent QQ group\nInput 0 or leave it blank to search for demo groups"),
+			   NULL, FALSE, FALSE, NULL, _("Search"),
+			   G_CALLBACK(_qq_group_search_callback), _("Cancel"), NULL, gc);
+
+	return qd->roomlist;
+}				// qq_roomlist_get_list
+
+/*****************************************************************************/
+// free roomlist space, I have no idea when this one is called ...
+void qq_roomlist_cancel(GaimRoomlist * list)
+{
+	qq_data *qd;
+	GaimConnection *gc;
+
+	g_return_if_fail(list != NULL);
+	gc = gaim_account_get_connection(list->account);
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	gaim_roomlist_set_in_progress(list, FALSE);
+	gaim_roomlist_unref(list);
+}				// qq_roomlist_cancel
+
+/*****************************************************************************/
+// this should be called upon signin, even we did not open group chat window
+void qq_group_init(GaimConnection * gc)
+{
+	gint i;
+	GaimAccount *account;
+	GaimChat *chat;
+	GaimGroup *gaim_group;
+	GaimBlistNode *node;
+	qq_group *group;
+
+	g_return_if_fail(gc != NULL);
+	account = gaim_connection_get_account(gc);
+
+
+	gaim_group = gaim_find_group(GAIM_GROUP_QQ_QUN);
+	if (gaim_group == NULL) {
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "We have no QQ Qun\n");
+		return;
+	}			// if group
+
+	i = 0;
+	for (node = ((GaimBlistNode *) gaim_group)->child; node != NULL; node = node->next)
+		if (GAIM_BLIST_NODE_IS_CHAT(node)) {	// got one
+			chat = (GaimChat *) node;
+			if (account != chat->account)
+				continue;	// very important here !
+			group = qq_group_from_hashtable(gc, chat->components);
+			if (group != NULL) {
+				i++;
+				qq_send_cmd_group_get_group_info(gc, group);	// get group info and members
+			}	// if group
+		}		// if is chat
+
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Load %d QQ Qun configurations\n", i);
+
+}				// qq_group_init
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,70 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_GROUP_H_
+#define _QQ_GROUP_H_
+
+#include <glib.h>
+#include "account.h"
+#include "connection.h"		// GaimConnection
+#include "roomlist.h"		// GaimRoomlist
+#include "qq.h"			// qq_data
+
+#define GAIM_GROUP_QQ_QUN         "QQ 群"
+
+typedef enum {
+	QQ_GROUP_MEMBER_STATUS_NOT_MEMBER = 0x00,	// default 0x00 means not member
+	QQ_GROUP_MEMBER_STATUS_IS_MEMBER,
+	QQ_GROUP_MEMBER_STATUS_APPLYING,
+	QQ_GROUP_MEMBER_STATUS_IS_ADMIN,
+} qq_group_member_status;
+
+typedef struct _qq_group {
+	// all these will be saved when exit GAIM
+	qq_group_member_status my_status;	// my status for this group
+	gchar *my_status_desc;	// my status description
+	guint32 internal_group_id;
+	guint32 external_group_id;
+	guint8 group_type;	// permanent or temporory
+	guint32 creator_uid;
+	guint32 group_category;
+	guint8 auth_type;
+	gchar *group_name_utf8;
+	gchar *group_desc_utf8;
+	// all these will loaded from netowrk only
+	gchar *notice_utf8;	// group notice by admin
+	GList *members;		// those evert appear in the group
+} qq_group;
+
+GList *qq_chat_info(GaimConnection * gc);
+
+void qq_group_init(GaimConnection * gc);
+
+GaimRoomlist *qq_roomlist_get_list(GaimConnection * gc);
+
+void qq_roomlist_cancel(GaimRoomlist * list);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_admindlg.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,659 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "debug.h"		// gaim_debug
+#include "blist.h"		// GAIM_BLIST_NODE_IS_BUDDY
+#include "notify.h"		// gaim_notify_warning
+
+#include "utils.h"		// gaim_name_to_uid
+#include "group_admindlg.h"
+#include "group_find.h"		// qq_group_find_by_internal_group_id
+#include "group_join.h"		// auth_type
+#include "group_opt.h"		// QQ_GROUP_TYPE_PERMANENT
+
+enum {
+	COLUMN_SELECTED = 0,
+	COLUMN_UID,
+	COLUMN_NICKNAME,
+	NUM_COLUMNS
+};
+
+enum {
+	PAGE_INFO = 0,
+	PAGE_MEMBER,
+};
+
+typedef struct _qun_info_window {
+	guint32 internal_group_id;
+	GaimConnection *gc;
+	GtkWidget *window;
+	GtkWidget *notebook;
+	GtkWidget *lbl_external_group_id;
+	GtkWidget *lbl_admin_uid;
+	GtkWidget *ent_group_name;
+	GtkWidget *cmb_group_category;
+	GtkWidget *txt_group_desc;
+	GtkWidget *txt_group_notice;
+	GtkWidget *rad_auth[3];
+	GtkWidget *btn_mod;
+	GtkWidget *btn_close;
+	GtkWidget *tre_members;
+} qun_info_window;
+
+const gchar *qq_group_category[] = {
+	"åŒå­¦", "朋å‹", "åŒäº‹", "其他",
+};				// qq_group_category
+
+const gchar *qq_group_auth_type_desc[] = {
+	"无须认è¯", "需è¦è®¤è¯", "ä¸å¯æ·»åŠ ",
+};				// qq_group_auth_type_desc
+
+/*****************************************************************************/
+static void _qq_group_info_window_deleteevent(GtkWidget * widget, GdkEvent * event, gpointer data) {
+	gtk_widget_destroy(widget);	// this will call _window_destroy
+}				// _window_deleteevent
+
+/*****************************************************************************/
+static void _qq_group_info_window_close(GtkWidget * widget, gpointer data)
+{
+	// this will call _info_window_destroy if it is info-window
+	gtk_widget_destroy(GTK_WIDGET(data));
+}				// _window_close
+
+/*****************************************************************************/
+static void _qq_group_info_window_destroy(GtkWidget * widget, gpointer data)
+{
+	GaimConnection *gc;
+	GList *list;
+	qq_data *qd;
+	qun_info_window *info_window;
+
+	gc = (GaimConnection *) data;
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Group info is destoryed\n");
+
+	qd = (qq_data *) gc->proto_data;
+	list = qd->qun_info_window;
+
+	while (list) {
+		info_window = (qun_info_window *) (list->data);
+		if (info_window->window != widget)
+			list = list->next;
+		else {
+			qd->qun_info_window = g_list_remove(qd->qun_info_window, info_window);
+			g_free(info_window);
+			break;
+		}		// if info_window
+	}			// while
+}				// _window_destroy
+
+/*****************************************************************************/
+void qq_qun_info_window_free(qq_data * qd)
+{
+	gint i;
+	qun_info_window *info_window;
+
+	i = 0;
+	while (qd->qun_info_window) {
+		info_window = (qun_info_window *) qd->qun_info_window->data;
+		qd->qun_info_window = g_list_remove(qd->qun_info_window, info_window);
+		if (info_window->window)
+			gtk_widget_destroy(info_window->window);
+		g_free(info_window);
+		i++;
+	}			// while
+
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d Qun info windows are freed\n", i);
+}				// qq_qun_info_window_free
+
+/*****************************************************************************/
+static void _qq_group_info_window_modify(GtkWidget * widget, gpointer data)
+{
+	GaimConnection *gc;
+	qun_info_window *info_window;
+
+	g_return_if_fail(data != NULL);
+	info_window = (qun_info_window *) data;
+
+	gc = info_window->gc;
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	//henry: This function contains some codes only supported by gtk-2.4 or later
+//#if !GTK_CHECK_VERSION(2,4,0)
+//        gaim_notify_info(gc, _("QQ Qun Operation"),
+//                        _("This version of GTK-2 does not support this function"), NULL);
+//        return;
+//#else
+	gint page, group_category, i = 0;
+	qq_group *group;
+	qq_data *qd;
+	GtkTextIter start, end;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	GValue value = { 0, };
+	guint32 *new_members;
+	guint32 uid;
+	gboolean selected;
+
+	qd = (qq_data *) gc->proto_data;
+
+	// we assume the modification can succeed
+	// maybe it needs some tweak here
+	group = qq_group_find_by_internal_group_id(gc, info_window->internal_group_id);
+	g_return_if_fail(group != NULL);
+
+	new_members = g_newa(guint32, QQ_QUN_MEMBER_MAX);
+
+	page = gtk_notebook_get_current_page(GTK_NOTEBOOK(info_window->notebook));
+	switch (page) {
+	case PAGE_INFO:
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "Gonna change Qun detailed information\n");
+		// get the group_category
+#if GTK_CHECK_VERSION(2,4,0)
+		group_category = gtk_combo_box_get_active(GTK_COMBO_BOX(info_window->cmb_group_category));
+#else
+		group_category = gtk_option_menu_get_history(GTK_OPTION_MENU(info_window->cmb_group_category));
+#endif
+
+		if (group_category >= 0)
+			group->group_category = group_category;
+		else {
+			g_free(group);
+			gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Invalid group_category: %d\n", group_category);
+			return;
+		}		// if group_category
+		// get auth_type
+		if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(info_window->rad_auth[0])))
+			group->auth_type = QQ_GROUP_AUTH_TYPE_NO_AUTH;
+		else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(info_window->rad_auth[1])))
+			group->auth_type = QQ_GROUP_AUTH_TYPE_NEED_AUTH;
+		else
+			group->auth_type = QQ_GROUP_AUTH_TYPE_NO_ADD;
+		// MUST use g_strdup, otherwise core dump after info_window is closed
+		group->group_name_utf8 = g_strdup(gtk_entry_get_text(GTK_ENTRY(info_window->ent_group_name)));
+		gtk_text_buffer_get_bounds(gtk_text_view_get_buffer
+					   (GTK_TEXT_VIEW(info_window->txt_group_desc)), &start, &end);
+		group->group_desc_utf8 =
+		    g_strdup(gtk_text_buffer_get_text
+			     (gtk_text_view_get_buffer
+			      (GTK_TEXT_VIEW(info_window->txt_group_desc)), &start, &end, FALSE));
+		gtk_text_buffer_get_bounds(gtk_text_view_get_buffer
+					   (GTK_TEXT_VIEW(info_window->txt_group_notice)), &start, &end);
+		group->notice_utf8 =
+		    g_strdup(gtk_text_buffer_get_text
+			     (gtk_text_view_get_buffer
+			      (GTK_TEXT_VIEW(info_window->txt_group_notice)), &start, &end, FALSE));
+		// finally we can modify it with new information
+		qq_group_modify_info(gc, group);
+		break;
+	case PAGE_MEMBER:
+		if (info_window->tre_members == NULL) {
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "Member list is not ready, cannot modify!\n");
+		} else {
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "Gonna change Qun member list\n");
+			model = gtk_tree_view_get_model(GTK_TREE_VIEW(info_window->tre_members));
+			if (gtk_tree_model_get_iter_first(model, &iter)) {
+				gtk_tree_model_get_value(model, &iter, COLUMN_UID, &value);
+				uid = g_value_get_uint(&value);
+				g_value_unset(&value);
+				gtk_tree_model_get_value(model, &iter, COLUMN_SELECTED, &value);
+				selected = g_value_get_boolean(&value);
+				g_value_unset(&value);
+				if (!selected)
+					new_members[i++] = uid;
+				while (gtk_tree_model_iter_next(model, &iter)) {
+					gtk_tree_model_get_value(model, &iter, COLUMN_UID, &value);
+					uid = g_value_get_uint(&value);
+					g_value_unset(&value);
+					gtk_tree_model_get_value(model, &iter, COLUMN_SELECTED, &value);
+					selected = g_value_get_boolean(&value);
+					g_value_unset(&value);
+					if (!selected)
+						new_members[i++] = uid;
+				}	// while
+				new_members[i] = 0xffffffff;	// this labels the end
+			} else
+				new_members[0] = 0xffffffff;
+			qq_group_modify_members(gc, group, new_members);
+		}		// if info_window->tre_members
+		break;
+	default:
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "Invalid page number: %d\n", page);
+	}			// switch
+
+	_qq_group_info_window_close(NULL, info_window->window);
+
+//#endif /* GTK_CHECK_VERSION */
+}				// _qq_group_info_window_modify
+
+/*****************************************************************************/
+static void _qq_group_member_list_deleted_toggled(GtkCellRendererToggle * cell, gchar * path_str, gpointer data) {
+	qun_info_window *info_window;
+	GaimConnection *gc;
+	qq_group *group;
+
+	info_window = (qun_info_window *) data;
+	g_return_if_fail(info_window != NULL);
+
+	gc = info_window->gc;
+	g_return_if_fail(gc != NULL);
+
+	group = qq_group_find_by_internal_group_id(gc, info_window->internal_group_id);
+	g_return_if_fail(group != NULL);
+
+	GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(info_window->tre_members));
+	GtkTreeIter iter;
+	GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
+	gboolean selected;
+	guint32 uid;
+
+	gtk_tree_model_get_iter(model, &iter, path);
+	gtk_tree_model_get(model, &iter, COLUMN_SELECTED, &selected, -1);
+	gtk_tree_model_get(model, &iter, COLUMN_UID, &uid, -1);
+
+	if (uid != group->creator_uid) {	// do not allow delete admin
+		selected ^= 1;
+		gtk_list_store_set(GTK_LIST_STORE(model), &iter, COLUMN_SELECTED, selected, -1);
+		gtk_tree_path_free(path);
+	} else
+		gaim_notify_error(gc, NULL, _("Qun creator cannot be removed"), NULL);
+}				// _qq_group_member_list_deleted_toggled
+
+/*****************************************************************************/
+static void _qq_group_member_list_drag_data_rcv_cb
+    (GtkWidget * widget, GdkDragContext * dc, guint x, guint y,
+     GtkSelectionData * sd, guint info, guint t, gpointer data) {
+
+	GaimConnection *gc;
+	GaimAccount *account;
+	GaimBlistNode *n = NULL;
+	GaimContact *c = NULL;
+	GaimBuddy *b = NULL;
+	GtkWidget *treeview;
+	GtkTreeModel *model;
+	GtkListStore *store;
+	GtkTreeIter iter;
+	GValue value = { 0, };
+	guint32 uid, input_uid;
+
+	treeview = widget;
+	gc = (GaimConnection *) data;
+	g_return_if_fail(gc != NULL);
+	account = gaim_connection_get_account(gc);
+
+	if (sd->target != gdk_atom_intern("GAIM_BLIST_NODE", FALSE) || sd->data == NULL) {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Invalid drag data received, discard...\n");
+		return;
+	}			// if (sd->target
+
+	memcpy(&n, sd->data, sizeof(n));
+
+	// we expect GAIM_BLIST_CONTACT_NODE and GAIM_BLIST_BUDDY_NODE
+	if (GAIM_BLIST_NODE_IS_CONTACT(n)) {
+		c = (GaimContact *) n;
+		b = c->priority;	// we get the first buddy only
+	} else if (GAIM_BLIST_NODE_IS_BUDDY(n))
+		b = (GaimBuddy *) n;
+
+	if (b == NULL) {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "No valid GaimBuddy is passed from DnD\n");
+		return;
+	}			// if b == NULL
+
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "We get a GaimBuddy: %s\n", b->name);
+	input_uid = gaim_name_to_uid(b->name);
+	g_return_if_fail(input_uid > 0);
+
+	model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
+	// we need to check if the user id in the member list is unique
+	// possibly a tree transverse is necessary to achieve this
+	if (gtk_tree_model_get_iter_first(model, &iter)) {
+		gtk_tree_model_get_value(model, &iter, COLUMN_UID, &value);
+		uid = g_value_get_uint(&value);
+		g_value_unset(&value);
+		while (uid != input_uid && gtk_tree_model_iter_next(model, &iter)) {
+			gtk_tree_model_get_value(model, &iter, COLUMN_UID, &value);
+			uid = g_value_get_uint(&value);
+			g_value_unset(&value);
+		}		// while
+	} else
+		uid = 0;	// if gtk_tree_model_get_iter_first
+
+	if (uid == input_uid) {
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Qun already has this buddy %s\n", b->name);
+		return;
+	} else {		// we add it to list
+		store = GTK_LIST_STORE(model);
+		gtk_list_store_append(store, &iter);
+		gtk_list_store_set(store, &iter,
+				   COLUMN_SELECTED, FALSE, COLUMN_UID, input_uid, COLUMN_NICKNAME, b->alias, -1);
+		// re-sort the list
+		gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), COLUMN_UID, GTK_SORT_ASCENDING);
+	}			// if uid
+
+}				// _qq_group_member_list_drag_data_rcv_cb
+
+/*****************************************************************************/
+static GtkWidget *_create_page_info(GaimConnection * gc, qq_group * group, gboolean do_manage, qun_info_window * info_window) {
+	GtkWidget *vbox, *hbox;
+	GtkWidget *frame_info, *frame_auth;
+	GtkWidget *tbl_info;
+	GtkWidget *label, *entry, *combo, *text, *scrolled_window;
+	gint i;
+
+	g_return_val_if_fail(gc != NULL && group != NULL, NULL);
+
+	vbox = gtk_vbox_new(FALSE, 5);
+
+	frame_info = gtk_frame_new(NULL);
+	gtk_box_pack_start(GTK_BOX(vbox), frame_info, TRUE, TRUE, 0);
+
+	tbl_info = gtk_table_new(6, 4, FALSE);
+	gtk_table_set_row_spacings(GTK_TABLE(tbl_info), 4);
+	gtk_table_set_col_spacing(GTK_TABLE(tbl_info), 1, 10);
+	gtk_container_add(GTK_CONTAINER(frame_info), tbl_info);
+
+	label = gtk_label_new(_("Group ID: "));
+	gtk_table_attach(GTK_TABLE(tbl_info), label, 0, 1, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
+	label = gtk_label_new(g_strdup_printf("%d", group->external_group_id));
+	gtk_table_attach(GTK_TABLE(tbl_info), label, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
+	info_window->lbl_external_group_id = label;
+
+	label = gtk_label_new(_("Group Name"));
+	gtk_table_attach(GTK_TABLE(tbl_info), label, 2, 3, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
+	entry = gtk_entry_new();
+	gtk_widget_set_size_request(entry, 100, -1);
+	if (group->group_name_utf8 != NULL)
+		gtk_entry_set_text(GTK_ENTRY(entry), group->group_name_utf8);
+	gtk_table_attach(GTK_TABLE(tbl_info), entry, 3, 4, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
+	info_window->ent_group_name = entry;
+
+	label = gtk_label_new(_("Admin: "));
+	gtk_table_attach(GTK_TABLE(tbl_info), label, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
+	label = gtk_label_new(g_strdup_printf("%d", group->creator_uid));
+	gtk_table_attach(GTK_TABLE(tbl_info), label, 1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
+	info_window->lbl_admin_uid = label;
+
+	label = gtk_label_new(_("Category"));
+	gtk_table_attach(GTK_TABLE(tbl_info), label, 2, 3, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
+
+	//henry: these codes are supported only in GTK-2.4 or later
+#if GTK_CHECK_VERSION(2, 4, 0)
+	combo = gtk_combo_box_new_text();
+	for (i = 0; i < 4; i++)
+		gtk_combo_box_append_text(GTK_COMBO_BOX(combo), qq_group_category[i]);
+	gtk_combo_box_set_active(GTK_COMBO_BOX(combo), group->group_category);
+#else
+	GtkWidget *menu;
+	GtkWidget *item;
+
+	combo = gtk_option_menu_new();
+	menu = gtk_menu_new();
+	for (i = 0; i < 4; i++) {
+		item = gtk_menu_item_new_with_label(qq_group_category[i]);
+		gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+		gtk_widget_show(item);
+	}
+	gtk_option_menu_set_menu(GTK_OPTION_MENU(combo), menu);
+	gtk_option_menu_set_history(GTK_OPTION_MENU(combo), group->group_category);
+#endif				/* GTK_CHECK_VERSION */
+
+	gtk_table_attach(GTK_TABLE(tbl_info), combo, 3, 4, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
+	info_window->cmb_group_category = combo;
+
+	label = gtk_label_new(_("Description"));
+	gtk_table_attach(GTK_TABLE(tbl_info), label, 0, 1, 2, 3, GTK_FILL, GTK_FILL, 0, 0);
+	text = gtk_text_view_new();
+	gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
+	gtk_widget_set_size_request(text, -1, 50);
+	if (group->group_desc_utf8 != NULL)
+		gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)), group->group_desc_utf8, -1);
+	info_window->txt_group_desc = text;
+
+	scrolled_window = gtk_scrolled_window_new(NULL, NULL);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+	gtk_container_add(GTK_CONTAINER(scrolled_window), text);
+	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), GTK_SHADOW_IN);
+	gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 2);
+	gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 2);
+	gtk_table_attach(GTK_TABLE(tbl_info), scrolled_window, 0, 4, 3, 4, GTK_FILL, GTK_FILL, 0, 0);
+
+	label = gtk_label_new(_("Group Notice"));
+	gtk_table_attach(GTK_TABLE(tbl_info), label, 0, 1, 4, 5, GTK_FILL, GTK_FILL, 0, 0);
+	text = gtk_text_view_new();
+	gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
+	gtk_widget_set_size_request(text, -1, 50);
+	if (group->notice_utf8 != NULL)
+		gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)), group->notice_utf8, -1);
+	info_window->txt_group_notice = text;
+
+	scrolled_window = gtk_scrolled_window_new(NULL, NULL);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+	gtk_container_add(GTK_CONTAINER(scrolled_window), text);
+	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), GTK_SHADOW_IN);
+	gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 2);
+	gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 2);
+	gtk_table_attach(GTK_TABLE(tbl_info), scrolled_window, 0, 4, 5, 6, GTK_FILL, GTK_FILL, 0, 0);
+
+	frame_auth = gtk_frame_new(_("Authentication"));
+	hbox = gtk_hbox_new(FALSE, 5);
+	gtk_container_add(GTK_CONTAINER(frame_auth), hbox);
+	info_window->rad_auth[0] = gtk_radio_button_new_with_label(NULL, qq_group_auth_type_desc[0]);
+	info_window->rad_auth[1] =
+	    gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON
+							(info_window->rad_auth[0]), qq_group_auth_type_desc[1]);
+	info_window->rad_auth[2] =
+	    gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON
+							(info_window->rad_auth[0]), qq_group_auth_type_desc[2]);
+	for (i = 0; i < 3; i++)
+		gtk_box_pack_start(GTK_BOX(hbox), info_window->rad_auth[i], FALSE, FALSE, 0);
+	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(info_window->rad_auth[group->auth_type - 1]), TRUE);
+	gtk_box_pack_start(GTK_BOX(vbox), frame_auth, FALSE, FALSE, 0);
+
+	if (!do_manage) {
+		gtk_widget_set_sensitive(frame_info, FALSE);
+		gtk_widget_set_sensitive(frame_auth, FALSE);
+	}			// if ! do_manage
+
+	return vbox;
+}				// _create_info_page
+
+/*****************************************************************************/
+static GtkWidget *_create_page_members
+    (GaimConnection * gc, qq_group * group, gboolean do_manage, qun_info_window * info_window) {
+	GtkWidget *vbox, *sw, *treeview;
+	GtkTreeModel *model;
+	GtkListStore *store;
+	GtkTreeIter iter;
+	GtkCellRenderer *renderer;
+	GtkTreeViewColumn *column;
+	GList *list;
+	qq_buddy *q_bud;
+	GtkTargetEntry gte = { "GAIM_BLIST_NODE", GTK_TARGET_SAME_APP, 0 };
+
+	g_return_val_if_fail(gc != NULL && group != NULL, NULL);
+
+	vbox = gtk_vbox_new(FALSE, 0);
+
+	if (group->members == NULL) {	// if NULL, not ready
+		sw = gtk_label_new(_
+				   ("OpenQ is collecting member information.\nPlease close this window and open again"));
+		gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
+		return vbox;
+	}			// if group->members
+
+	sw = gtk_scrolled_window_new(NULL, NULL);
+	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+	gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
+
+	store = gtk_list_store_new(NUM_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_UINT, G_TYPE_STRING);
+
+	list = group->members;
+	while (list != NULL) {
+		q_bud = (qq_buddy *) list->data;
+		gtk_list_store_append(store, &iter);
+		gtk_list_store_set(store, &iter,
+				   COLUMN_SELECTED, FALSE,
+				   COLUMN_UID, q_bud->uid, COLUMN_NICKNAME, q_bud->nickname, -1);
+		list = list->next;
+	}			// for
+
+	model = GTK_TREE_MODEL(store);
+	treeview = gtk_tree_view_new_with_model(model);
+	info_window->tre_members = treeview;
+
+	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
+	gtk_tree_view_set_search_column(GTK_TREE_VIEW(treeview), COLUMN_UID);
+	g_object_unref(model);
+
+	// set up drag & drop ONLY for managable Qun
+	if (do_manage) {
+		gtk_tree_view_enable_model_drag_dest(GTK_TREE_VIEW(treeview), &gte, 1, GDK_ACTION_COPY);
+		g_signal_connect(G_OBJECT(treeview), "drag-data-received",
+				 G_CALLBACK(_qq_group_member_list_drag_data_rcv_cb), gc);
+	}			// if do manage
+
+	gtk_container_add(GTK_CONTAINER(sw), treeview);
+
+	model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
+	renderer = gtk_cell_renderer_toggle_new();
+
+	// it seems this signal has to be handled
+	// otherwise, the checkbox in the column does not reponse to user action
+	if (do_manage)
+		g_signal_connect(renderer, "toggled", G_CALLBACK(_qq_group_member_list_deleted_toggled), info_window);
+
+	column = gtk_tree_view_column_new_with_attributes(_("Del"), renderer, "active", COLUMN_SELECTED, NULL);
+
+	gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column), GTK_TREE_VIEW_COLUMN_FIXED);
+	gtk_tree_view_column_set_fixed_width(GTK_TREE_VIEW_COLUMN(column), 30);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
+
+	renderer = gtk_cell_renderer_text_new();
+	column = gtk_tree_view_column_new_with_attributes(_("UID"), renderer, "text", COLUMN_UID, NULL);
+	gtk_tree_view_column_set_sort_column_id(column, COLUMN_UID);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
+	// default sort by UID
+	gtk_tree_view_column_set_sort_order(column, GTK_SORT_ASCENDING);
+	gtk_tree_view_column_set_sort_indicator(column, TRUE);
+
+	renderer = gtk_cell_renderer_text_new();
+	column = gtk_tree_view_column_new_with_attributes(_("Nickname"), renderer, "text", COLUMN_NICKNAME, NULL);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
+
+	return vbox;
+}				// _create_page_members
+
+/*****************************************************************************/
+void qq_group_detail_window_show(GaimConnection * gc, qq_group * group)
+{
+	GtkWidget *vbox, *notebook;
+	GtkWidget *label, *bbox;
+	GList *list;
+	qq_data *qd;
+	qun_info_window *info_window = NULL;
+	gboolean do_manage, do_show, do_exist;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL && group != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	do_manage = group->my_status == QQ_GROUP_MEMBER_STATUS_IS_ADMIN;
+	do_show = do_manage || (group->my_status == QQ_GROUP_MEMBER_STATUS_IS_MEMBER);
+
+	if (!do_show) {
+		gaim_notify_error(gc, _("QQ Qun Operation"),
+				  _("You can not view Qun details"),
+				  _("Only Qun admin or Qun member can view details"));
+		return;
+	}			// if ! do_show
+
+	list = qd->qun_info_window;
+	do_exist = FALSE;
+	while (list != NULL) {
+		info_window = (qun_info_window *) list->data;
+		if (info_window->internal_group_id == group->internal_group_id) {
+			break;
+			do_exist = TRUE;
+		} else
+			list = list->next;
+	}			// while list
+
+	if (!do_exist) {
+		info_window = g_new0(qun_info_window, 1);
+		info_window->gc = gc;
+		info_window->internal_group_id = group->internal_group_id;
+		g_list_append(qd->qun_info_window, info_window);
+
+		info_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+		g_signal_connect(GTK_WINDOW(info_window->window),
+				 "delete_event", G_CALLBACK(_qq_group_info_window_deleteevent), NULL);
+		g_signal_connect(G_OBJECT(info_window->window), "destroy",
+				 G_CALLBACK(_qq_group_info_window_destroy), gc);
+
+		gtk_window_set_title(GTK_WINDOW(info_window->window), _("Manage Qun"));
+		gtk_window_set_resizable(GTK_WINDOW(info_window->window), FALSE);
+		gtk_container_set_border_width(GTK_CONTAINER(info_window->window), 5);
+
+		vbox = gtk_vbox_new(FALSE, 0);
+		gtk_container_add(GTK_CONTAINER(info_window->window), vbox);
+
+		notebook = gtk_notebook_new();
+		info_window->notebook = notebook;
+		gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook), GTK_POS_TOP);
+		gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0);
+
+		label = gtk_label_new(_("Qun Information"));
+		gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+					 _create_page_info(gc, group, do_manage, info_window), label);
+		label = gtk_label_new(_("Members"));
+		gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+					 _create_page_members(gc, group, do_manage, info_window), label);
+
+		bbox = gtk_hbutton_box_new();
+		gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_SPREAD);
+		gtk_box_set_spacing(GTK_BOX(bbox), 10);
+
+		info_window->btn_mod = gtk_button_new_with_label(_("Modify"));
+		gtk_container_add(GTK_CONTAINER(bbox), info_window->btn_mod);
+		g_signal_connect(G_OBJECT(info_window->btn_mod), "clicked",
+				 G_CALLBACK(_qq_group_info_window_modify), info_window);
+
+		info_window->btn_close = gtk_button_new_with_label(_("Close"));
+		gtk_container_add(GTK_CONTAINER(bbox), info_window->btn_close);
+		g_signal_connect(G_OBJECT(info_window->btn_close),
+				 "clicked", G_CALLBACK(_qq_group_info_window_close), info_window->window);
+
+		if (!do_manage)
+			gtk_widget_set_sensitive(info_window->btn_mod, FALSE);
+		gtk_box_pack_start(GTK_BOX(vbox), bbox, TRUE, TRUE, 5);
+		gtk_widget_show_all(info_window->window);
+
+	} else			// we already have this
+		gtk_widget_grab_focus(info_window->window);
+
+}				// qq_group_manage_window_show
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_admindlg.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,39 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_GROUP_ADMINDLG_H_
+#define _QQ_GROUP_ADMINDLG_H_
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include "connection.h"
+#include "group.h"
+#include "qq.h"
+
+void qq_group_detail_window_show(GaimConnection * gc, qq_group * group);
+
+void qq_qun_info_window_free(qq_data * qd);
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_conv.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,102 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include <glib.h>		// GList
+#include "conversation.h"	// GaimConversation
+
+#include "utils.h"		// uid_to_gaim_name
+#include "buddy_status.h"	// is_online
+#include "group_conv.h"
+#include "qq.h"			// qq_buddy
+
+/*****************************************************************************/
+// show group conversation window
+void qq_group_conv_show_window(GaimConnection * gc, qq_group * group)
+{
+	GaimConversation *conv;
+	qq_data *qd;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL && group != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, /* add by gfhuang */group->group_name_utf8, gaim_connection_get_account(gc));
+	if (conv == NULL)	// show only one window per group
+		serv_got_joined_chat(gc, qd->channel++, group->group_name_utf8);
+}				// qq_group_conv_show_window
+
+/*****************************************************************************/
+// refresh online member in group conversation window
+void qq_group_conv_refresh_online_member(GaimConnection * gc, qq_group * group)
+{
+	GList *names, *list, *flags;
+	qq_buddy *member;
+	gchar *member_name;
+	GaimConversation *conv;
+	gint flag;
+	g_return_if_fail(gc != NULL && group != NULL);
+
+	names = NULL;
+	flags = NULL;
+	conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT /*gfhuang*/, group->group_name_utf8, gaim_connection_get_account(gc));
+	if (conv != NULL && group->members != NULL) {
+		list = group->members;
+		while (list != NULL) {
+			member = (qq_buddy *) list->data;
+			//always put it even offline, by gfhuang
+			names = g_list_append(names,
+					      (member->nickname !=
+					      NULL) ?
+					      g_strdup(member->nickname) : uid_to_gaim_name(member->uid));
+		
+			flag = 0;
+			if (is_online(member->status)) flag |= (GAIM_CBFLAGS_TYPING | GAIM_CBFLAGS_VOICE);		// TYPING to put online above OP and FOUNDER
+			if(1 == (member->role & 1)) flag |= GAIM_CBFLAGS_OP;
+			//if(4 == (member->role & 4)) flag |= GAIM_CBFLAGS_VOICE;    //active, no use
+			if(member->uid == group->creator_uid) flag |= GAIM_CBFLAGS_FOUNDER;
+			flags = g_list_append(flags, GINT_TO_POINTER(flag));
+			list = list->next;
+		}		// while list
+		
+		gaim_conv_chat_clear_users(GAIM_CONV_CHAT(conv));
+		gaim_conv_chat_add_users(GAIM_CONV_CHAT(conv), names, NULL /*gfhuang*/, flags, FALSE /*gfhuang*/);
+	}			// if conv
+	// clean up names
+	while (names != NULL) {
+		member_name = (gchar *) names->data;
+		names = g_list_remove(names, member_name);
+		g_free(member_name);
+	}			// while name
+	// clean up flags, NOOOOOOOOOOOOOOOOOOOOOOO!!!!!! bugs, flags is not name! got by gfhuang
+	/*
+	while (flags != NULL) {
+		member_name = (gchar *) flags->data;
+		flags = g_list_remove(flags, member_name);
+		g_free(member_name);
+	}
+	*/
+	g_list_free(flags);	
+}				// qq_group_conv_show_window
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_conv.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,36 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_GROUP_CONV_H_
+#define _QQ_GROUP_CONV_H_
+
+#include "connection.h"		// GaimConnection
+#include "group.h"		// qq_group
+
+void qq_group_conv_show_window(GaimConnection * gc, qq_group * group);
+void qq_group_conv_refresh_online_member(GaimConnection * gc, qq_group * group);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_find.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,207 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "debug.h"		// gaim_debug
+#include "conversation.h"	// GaimConversation
+
+#include "utils.h"		// QQ_NAME_PREFIX, gaim_name_to_uid
+#include "group_find.h"
+#include "group_network.h"	// group_packet
+#include "qq.h"			// qq_data
+
+/*****************************************************************************/
+// find a chat member's valid gaim_name of its nickname and chat room channel
+gchar *qq_group_find_member_by_channel_and_nickname(GaimConnection * gc, gint channel, const gchar * who) {
+	qq_group *group;
+	qq_buddy *member;
+	GList *list;
+
+	g_return_val_if_fail(gc != NULL && who != NULL, NULL);
+
+	// if it starts with QQ_NAME_PREFIX, we think it is valid name already
+	// otherwise we think it is nickname and try to find the matching gaim_name
+	if (g_str_has_prefix(who, QQ_NAME_PREFIX) && gaim_name_to_uid(who) > 0)
+		return (gchar *) who;
+
+	group = qq_group_find_by_channel(gc, channel);
+	g_return_val_if_fail(group != NULL, NULL);
+
+	list = group->members;
+	member = NULL;
+	while (list != NULL) {
+		member = (qq_buddy *) list->data;
+		if (member->nickname != NULL && !g_ascii_strcasecmp(member->nickname, who))
+			break;
+		list = list->next;
+	}			// while list
+
+	return (member == NULL) ? NULL : uid_to_gaim_name(member->uid);
+
+}				// qq_group_find_member_by_channel_and_nickname
+
+/*****************************************************************************/
+// find the internal_group_id by the reply packet sequence
+// return TRUE if we have a record of it, return FALSE if not
+gboolean qq_group_find_internal_group_id_by_seq(GaimConnection * gc, guint16 seq, guint32 * internal_group_id) {
+	GList *list;
+	qq_data *qd;
+	group_packet *p;
+
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL && internal_group_id != NULL, FALSE);
+	qd = (qq_data *) gc->proto_data;
+
+	list = qd->group_packets;
+	while (list != NULL) {
+		p = (group_packet *) (list->data);
+		if (p->send_seq == seq) {	// found and remove
+			*internal_group_id = p->internal_group_id;
+			qd->group_packets = g_list_remove(qd->group_packets, p);
+			g_free(p);
+			return TRUE;
+		}		// if
+		list = list->next;
+	}			// while
+
+	return FALSE;
+}				// qq_group_find_internal_group_id_by_seq
+
+/*****************************************************************************/
+// find a qq_buddy by uid, called by qq_im.c
+qq_buddy *qq_group_find_member_by_uid(qq_group * group, guint32 uid)
+{
+	GList *list;
+	qq_buddy *member;
+	g_return_val_if_fail(group != NULL && uid > 0, NULL);
+
+	list = group->members;
+	while (list != NULL) {
+		member = (qq_buddy *) list->data;
+		if (member->uid == uid)
+			return member;
+		else
+			list = list->next;
+	}			// while list
+
+	return NULL;
+}				// qq_group_find_member_by_uid
+
+/*****************************************************************************/
+// remove a qq_buddy by uid, called by qq_group_opt.c
+void qq_group_remove_member_by_uid(qq_group * group, guint32 uid)
+{
+	GList *list;
+	qq_buddy *member;
+	g_return_if_fail(group != NULL && uid > 0);
+
+	list = group->members;
+	while (list != NULL) {
+		member = (qq_buddy *) list->data;
+		if (member->uid == uid) {
+			group->members = g_list_remove(group->members, member);
+			return;
+		} else
+			list = list->next;
+	}			// while list
+
+}				// qq_group_remove_member_by_uid
+
+/*****************************************************************************/
+qq_buddy *qq_group_find_or_add_member(GaimConnection * gc, qq_group * group, guint32 member_uid) {
+	qq_buddy *member, *q_bud;
+	GaimBuddy *buddy;
+	g_return_val_if_fail(gc != NULL && group != NULL && member_uid > 0, NULL);
+
+	member = qq_group_find_member_by_uid(group, member_uid);
+	if (member == NULL) {	// first appear during my session
+		member = g_new0(qq_buddy, 1);
+		member->uid = member_uid;
+		buddy = gaim_find_buddy(gaim_connection_get_account(gc), uid_to_gaim_name(member_uid));
+		if (buddy != NULL) {
+			q_bud = (qq_buddy *) buddy->proto_data;
+			if (q_bud != NULL)
+				member->nickname = g_strdup(q_bud->nickname);
+			else if (buddy->alias != NULL)
+				member->nickname = g_strdup(buddy->alias);
+		}		// if buddy != NULL
+		group->members = g_list_append(group->members, member);
+	}			// if member
+
+	return member;
+}				// qq_group_find_or_add_member
+
+/*****************************************************************************/
+// find a qq_group by chatroom channel
+qq_group *qq_group_find_by_channel(GaimConnection * gc, gint channel)
+{
+	GaimConversation *conv;
+	qq_data *qd;
+	qq_group *group;
+	GList *list;
+
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	conv = gaim_find_chat(gc, channel);
+	g_return_val_if_fail(conv != NULL, NULL);
+
+	list = qd->groups;
+	group = NULL;
+	while (list != NULL) {
+		group = (qq_group *) list->data;
+		if (!g_ascii_strcasecmp(gaim_conversation_get_name(conv), group->group_name_utf8))
+			break;
+		list = list->next;
+	}			// while list
+
+	return group;
+
+}				// qq_group_find_by_name
+
+/*****************************************************************************/
+// find a qq_group by internal_group_id
+qq_group *qq_group_find_by_internal_group_id(GaimConnection * gc, guint32 internal_group_id) {
+
+	GList *list;
+	qq_group *group;
+	qq_data *qd;
+
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL && internal_group_id > 0, NULL);
+
+	qd = (qq_data *) gc->proto_data;
+	if (qd->groups == NULL)
+		return NULL;
+
+	list = qd->groups;
+	while (list != NULL) {
+		group = (qq_group *) list->data;
+		if (group->internal_group_id == internal_group_id)
+			return group;
+		list = list->next;
+	}			// while
+
+	return NULL;
+}				// qq_group_find_by_internal_group_id
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_find.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,42 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_GROUP_FIND_H_
+#define _QQ_GROUP_FIND_H_
+
+#include <glib.h>
+#include "connection.h"		// GaimConnection
+#include "group.h"		// qq_group
+
+gchar *qq_group_find_member_by_channel_and_nickname(GaimConnection * gc, gint channel, const gchar * who);
+qq_buddy *qq_group_find_member_by_uid(qq_group * group, guint32 uid);
+void qq_group_remove_member_by_uid(qq_group * group, guint32 uid);
+qq_buddy *qq_group_find_or_add_member(GaimConnection * gc, qq_group * group, guint32 member_uid);
+gboolean qq_group_find_internal_group_id_by_seq(GaimConnection * gc, guint16 seq, guint32 * internal_group_id);
+qq_group *qq_group_find_by_channel(GaimConnection * gc, gint channel);
+qq_group *qq_group_find_by_internal_group_id(GaimConnection * gc, guint32 internal_group_id);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_free.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,120 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "debug.h"		// gaim_debug
+
+#include "buddy_status.h"	// QQ_BUDDY_ONLINE_OFFLINE
+#include "group_free.h"
+#include "group_network.h"	// group_packet
+#include "group.h"		// qq_group
+
+/*****************************************************************************/
+// gracefully free all members in a group
+static void _qq_group_free_member(qq_group * group)
+{
+	gint i;
+	GList *list;
+	qq_buddy *member;
+
+	g_return_if_fail(group != NULL);
+	i = 0;
+	while (NULL != (list = group->members)) {
+		member = (qq_buddy *) list->data;
+		i++;
+		group->members = g_list_remove(group->members, member);
+		g_free(member->nickname);
+		g_free(member);
+	}			// while
+
+	group->members = NULL;
+
+}				// _qq_group_free_member
+
+/*****************************************************************************/
+// gracefully free the memory for one qq_group
+static void _qq_group_free(qq_group * group)
+{
+	g_return_if_fail(group != NULL);
+	_qq_group_free_member(group);
+	g_free(group->group_name_utf8);
+	g_free(group->group_desc_utf8);
+	g_free(group);
+}				// _qq_group_free
+
+/*****************************************************************************/
+// clean up group_packets and free all contents
+void qq_group_packets_free(qq_data * qd)
+{
+	group_packet *p;
+	gint i;
+
+	i = 0;
+	while (qd->group_packets != NULL) {
+		p = (group_packet *) (qd->group_packets->data);
+		qd->group_packets = g_list_remove(qd->group_packets, p);
+		g_free(p);
+		i++;
+	}			// while
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d group packets are freed!\n", i);
+}				// qq_group_packets_free
+
+/*****************************************************************************/
+void qq_group_remove_by_internal_group_id(qq_data * qd, guint32 internal_group_id) {
+	qq_group *group;
+	GList *list;
+	g_return_if_fail(qd != NULL);
+
+	list = qd->groups;
+	while (list != NULL) {
+		group = (qq_group *) qd->groups->data;
+		if (internal_group_id == group->internal_group_id) {
+			qd->groups = g_list_remove(qd->groups, group);
+			_qq_group_free(group);
+			break;
+		} else
+			list = list->next;
+	}			// while
+
+}				// qq_group_free_all
+
+/*****************************************************************************/
+void qq_group_free_all(qq_data * qd)
+{
+	qq_group *group;
+	gint i;
+	g_return_if_fail(qd != NULL);
+
+	i = 0;
+	while (qd->groups != NULL) {
+		i++;
+		group = (qq_group *) qd->groups->data;
+		qd->groups = g_list_remove(qd->groups, group);
+		_qq_group_free(group);
+	}			// while
+
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d groups are freed\n", i);
+}				// qq_group_free_all
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_free.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,39 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_GROUP_FREE_H_
+#define _QQ_GROUP_FREE_H_
+
+#include <glib.h>
+#include "qq.h"			// qq_data
+
+void qq_group_packets_free(qq_data * qd);
+
+void qq_group_free_all(qq_data * qd);
+
+void qq_group_remove_by_internal_group_id(qq_data * qd, guint32 internal_group_id);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_hash.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,205 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "blist.h"		// gaim_blist_find_chat
+#include "debug.h"		// gaim_debug
+
+#include "utils.h"		// qq_string_to_dec_value
+#include "buddy_opt.h"		// qq_get_gaim_group
+#include "group_hash.h"
+#include "group_misc.h"		// qq_group_get_auth_index_str
+
+/*****************************************************************************/
+static gchar *_qq_group_set_my_status_desc(qq_group * group)
+{
+	const char *status_desc;
+	g_return_val_if_fail(group != NULL, g_strdup(""));
+
+	switch (group->my_status) {
+	case QQ_GROUP_MEMBER_STATUS_NOT_MEMBER:
+		status_desc = _("I am not member");
+		break;
+	case QQ_GROUP_MEMBER_STATUS_IS_MEMBER:
+		status_desc = _("I am a member");
+		break;
+	case QQ_GROUP_MEMBER_STATUS_APPLYING:
+		status_desc = _("I am applying to join");
+		break;
+	case QQ_GROUP_MEMBER_STATUS_IS_ADMIN:
+		status_desc = _("I am the admin");
+		break;
+	default:
+		status_desc = _("Unknown status");
+	}			// switch
+
+	return g_strdup(status_desc);
+}				// _qq_group_set_my_status_desc
+
+/*****************************************************************************/
+static void _qq_group_add_to_blist(GaimConnection * gc, qq_group * group)
+{
+	GHashTable *components;
+	GaimGroup *g;
+	GaimChat *chat;
+	components = qq_group_to_hashtable(group);
+	chat = gaim_chat_new(gaim_connection_get_account(gc), group->group_name_utf8, components);
+	g = qq_get_gaim_group(GAIM_GROUP_QQ_QUN);
+	gaim_blist_add_chat(chat, g, NULL);
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "You have add group \"%s\" to blist locally\n", group->group_name_utf8);
+}				// _qq_group_add_to_blist
+
+/*****************************************************************************/
+// create a dummy qq_group, which includes only internal_id and external_id
+// all other attributes should be set to empty.
+// and we need to send a get_group_info to QQ server to update it right away
+qq_group *qq_group_create_by_id(GaimConnection * gc, guint32 internal_id, guint32 external_id) {
+	qq_group *group;
+	qq_data *qd;
+
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, NULL);
+	g_return_val_if_fail(internal_id > 0, NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	group = g_new0(qq_group, 1);
+	group->my_status = QQ_GROUP_MEMBER_STATUS_NOT_MEMBER;
+	group->my_status_desc = _qq_group_set_my_status_desc(group);
+	group->internal_group_id = internal_id;
+	group->external_group_id = external_id;
+	group->group_type = 0x01;	// assume permanent Qun
+	group->creator_uid = 10000;	// assume by QQ admin
+	group->group_category = 0x01;
+	group->auth_type = 0x02;	// assume need auth
+	group->group_name_utf8 = g_strdup("");
+	group->group_desc_utf8 = g_strdup("");
+	group->notice_utf8 = g_strdup("");
+	group->members = NULL;
+
+	qd->groups = g_list_append(qd->groups, group);
+	_qq_group_add_to_blist(gc, group);
+
+	return group;
+}				// qq_group_create_by_id
+
+/*****************************************************************************/
+// convert a qq_group to hash-table, which could be component of GaimChat
+GHashTable *qq_group_to_hashtable(qq_group * group)
+{
+	GHashTable *components;
+	components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+	g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_MEMBER_STATUS), g_strdup_printf("%d", group->my_status));
+	group->my_status_desc = _qq_group_set_my_status_desc(group);
+
+	g_hash_table_insert(components,
+			    g_strdup(QQ_GROUP_KEY_INTERNAL_ID), g_strdup_printf("%d", group->internal_group_id));
+	g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_EXTERNAL_ID),
+			    g_strdup_printf("%d", group->external_group_id));
+	g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_GROUP_TYPE), g_strdup_printf("%d", group->group_type));
+	g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_CREATOR_UID), g_strdup_printf("%d", group->creator_uid));
+	g_hash_table_insert(components,
+			    g_strdup(QQ_GROUP_KEY_GROUP_CATEGORY), g_strdup_printf("%d", group->group_category));
+	g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_AUTH_TYPE), g_strdup_printf("%d", group->auth_type));
+	g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_MEMBER_STATUS_DESC), g_strdup(group->my_status_desc));
+	g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_GROUP_NAME_UTF8), g_strdup(group->group_name_utf8));
+	g_hash_table_insert(components, g_strdup(QQ_GROUP_KEY_GROUP_DESC_UTF8), g_strdup(group->group_desc_utf8));
+	return components;
+}				// qq_group_to_hashtable
+
+/*****************************************************************************/
+// create a qq_group from hashtable
+qq_group *qq_group_from_hashtable(GaimConnection * gc, GHashTable * data)
+{
+	qq_data *qd;
+	qq_group *group;
+
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, NULL);
+	g_return_val_if_fail(data != NULL, NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	group = g_new0(qq_group, 1);
+	group->my_status =
+	    qq_string_to_dec_value
+	    (NULL ==
+	     g_hash_table_lookup(data,
+				 QQ_GROUP_KEY_MEMBER_STATUS) ?
+	     g_strdup_printf("%d",
+			     QQ_GROUP_MEMBER_STATUS_NOT_MEMBER) :
+	     g_hash_table_lookup(data, QQ_GROUP_KEY_MEMBER_STATUS));
+	group->internal_group_id = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_INTERNAL_ID));
+	group->external_group_id = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_EXTERNAL_ID));
+	group->group_type = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_GROUP_TYPE));
+	group->creator_uid = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_CREATOR_UID));
+	group->group_category = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_GROUP_CATEGORY));
+	group->auth_type = qq_string_to_dec_value(g_hash_table_lookup(data, QQ_GROUP_KEY_AUTH_TYPE));
+	group->group_name_utf8 = g_strdup(g_hash_table_lookup(data, QQ_GROUP_KEY_GROUP_NAME_UTF8));
+	group->group_desc_utf8 = g_strdup(g_hash_table_lookup(data, QQ_GROUP_KEY_GROUP_DESC_UTF8));
+	group->my_status_desc = _qq_group_set_my_status_desc(group);
+
+	qd->groups = g_list_append(qd->groups, group);
+
+	return group;
+}				// qq_group_from_hashtable
+
+/*****************************************************************************/
+// refresh group local subscription
+void qq_group_refresh(GaimConnection * gc, qq_group * group)
+{
+	GaimChat *chat;
+	g_return_if_fail(gc != NULL && group != NULL);
+
+	chat = gaim_blist_find_chat(gaim_connection_get_account(gc), g_strdup_printf("%d", group->external_group_id));
+	if (chat == NULL && group->my_status != QQ_GROUP_MEMBER_STATUS_NOT_MEMBER)
+		_qq_group_add_to_blist(gc, group);
+	else if (chat != NULL) {	// we have a local record, update its info
+		// if there is group_name_utf8, we update the group name
+		if (group->group_name_utf8 != NULL && strlen(group->group_name_utf8) > 0)
+			gaim_blist_alias_chat(chat, group->group_name_utf8);
+		g_hash_table_replace(chat->components,
+				     g_strdup(QQ_GROUP_KEY_MEMBER_STATUS), g_strdup_printf("%d", group->my_status));
+		group->my_status_desc = _qq_group_set_my_status_desc(group);
+		g_hash_table_replace(chat->components,
+				     g_strdup(QQ_GROUP_KEY_MEMBER_STATUS_DESC), g_strdup(group->my_status_desc));
+		g_hash_table_replace(chat->components,
+				     g_strdup(QQ_GROUP_KEY_INTERNAL_ID),
+				     g_strdup_printf("%d", group->internal_group_id));
+		g_hash_table_replace(chat->components,
+				     g_strdup(QQ_GROUP_KEY_EXTERNAL_ID),
+				     g_strdup_printf("%d", group->external_group_id));
+		g_hash_table_replace(chat->components,
+				     g_strdup(QQ_GROUP_KEY_GROUP_TYPE), g_strdup_printf("%d", group->group_type));
+		g_hash_table_replace(chat->components,
+				     g_strdup(QQ_GROUP_KEY_CREATOR_UID), g_strdup_printf("%d", group->creator_uid));
+		g_hash_table_replace(chat->components,
+				     g_strdup(QQ_GROUP_KEY_GROUP_CATEGORY),
+				     g_strdup_printf("%d", group->group_category));
+		g_hash_table_replace(chat->components,
+				     g_strdup(QQ_GROUP_KEY_AUTH_TYPE), g_strdup_printf("%d", group->auth_type));
+		g_hash_table_replace(chat->components,
+				     g_strdup(QQ_GROUP_KEY_GROUP_NAME_UTF8), g_strdup(group->group_name_utf8));
+		g_hash_table_replace(chat->components,
+				     g_strdup(QQ_GROUP_KEY_GROUP_DESC_UTF8), g_strdup(group->group_desc_utf8));
+	}			// if chat
+}				// qq_group_refresh
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_hash.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,50 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_GROUP_HASH_H_
+#define _QQ_GROUP_HASH_H_
+
+#include <glib.h>		// GHashTable
+#include "group.h"		// qq_group
+
+#define QQ_GROUP_KEY_MEMBER_STATUS      "my_status_code"
+#define QQ_GROUP_KEY_MEMBER_STATUS_DESC "my_status_desc"
+#define QQ_GROUP_KEY_INTERNAL_ID        "internal_group_id"
+#define QQ_GROUP_KEY_EXTERNAL_ID        "external_group_id"
+#define QQ_GROUP_KEY_GROUP_TYPE         "group_type"
+#define QQ_GROUP_KEY_CREATOR_UID        "creator_uid"
+#define QQ_GROUP_KEY_GROUP_CATEGORY     "group_category"
+#define QQ_GROUP_KEY_AUTH_TYPE          "auth_type"
+#define QQ_GROUP_KEY_GROUP_NAME_UTF8    "group_name_utf8"
+#define QQ_GROUP_KEY_GROUP_DESC_UTF8    "group_desc_utf8"
+
+qq_group *qq_group_create_by_id(GaimConnection * gc, guint32 internal_id, guint32 external_id);
+GHashTable *qq_group_to_hashtable(qq_group * group);
+
+qq_group *qq_group_from_hashtable(GaimConnection * gc, GHashTable * data);
+void qq_group_refresh(GaimConnection * gc, qq_group * group);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_im.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,413 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "debug.h"
+#include "conversation.h"	// GaimConversation
+#include "notify.h"		// gaim_notify_warning
+#include "prefs.h"		// gaim_prefs_get_bool
+#include "request.h"		// gaim_request_action
+#include "util.h"
+
+#include "utils.h"		// uid_to_gaim_name
+#include "packet_parse.h"	// create_packet_xx
+#include "char_conv.h"		// qq_smiley_to_gaim
+#include "group_find.h"		// qq_group_find_by_external_group_id
+#include "group_hash.h"		// qq_group_refresh
+#include "group_info.h"		// qq_send_cmd_group_get_group_info
+#include "group_im.h"
+#include "group_network.h"	// qq_send_group_cmd
+#include "group_opt.h"		// add_group_member
+#include "im.h"			// QQ_SEND_IM_AFTER_MSG_LEN
+
+typedef struct _qq_recv_group_im {
+	guint32 external_group_id;
+	guint8 group_type;
+	guint32 member_uid;
+	guint16 msg_seq;
+	time_t send_time;
+	guint16 msg_len;
+	guint8 *msg;
+	guint8 *font_attr;
+	gint font_attr_len;
+} qq_recv_group_im;
+
+/*****************************************************************************/
+// send IM to a group
+void qq_send_packet_group_im(GaimConnection * gc, qq_group * group, const gchar * msg) {
+	gint data_len, bytes;
+	guint8 *raw_data, *cursor;
+	guint16 msg_len;
+	gchar *msg_filtered;
+
+	g_return_if_fail(gc != NULL && group != NULL && msg != NULL);
+
+	msg_filtered = gaim_markup_strip_html(msg);
+	msg_len = strlen(msg_filtered);
+	data_len = 7 + msg_len + QQ_SEND_IM_AFTER_MSG_LEN;
+	raw_data = g_newa(guint8, data_len);
+	cursor = raw_data;
+
+	bytes = 0;
+	bytes += create_packet_b(raw_data, &cursor, QQ_GROUP_CMD_SEND_MSG);
+	bytes += create_packet_dw(raw_data, &cursor, group->internal_group_id);
+	bytes += create_packet_w(raw_data, &cursor, msg_len + QQ_SEND_IM_AFTER_MSG_LEN);
+	bytes += create_packet_data(raw_data, &cursor, (gchar *) msg_filtered, msg_len);
+	guint8 *send_im_tail = qq_get_send_im_tail(NULL, NULL, NULL,
+						   FALSE, FALSE, FALSE,
+						   QQ_SEND_IM_AFTER_MSG_LEN);
+	bytes += create_packet_data(raw_data, &cursor, (gchar *) send_im_tail, QQ_SEND_IM_AFTER_MSG_LEN);
+	g_free(send_im_tail);
+	g_free(msg_filtered);
+
+	if (bytes == data_len)	// create OK
+		qq_send_group_cmd(gc, group, raw_data, data_len);
+	else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+			   "Fail creating group_im packet, expect %d bytes, build %d bytes\n", data_len, bytes);
+
+}				// qq_send_packet_group_im
+
+/*****************************************************************************/
+// this is the ACK
+void qq_process_group_cmd_im(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) {
+	// return should be the internal group id
+	// but we have nothing to do with it
+	return;
+}				// qq_process_group_cmd_im
+
+/*****************************************************************************/
+// 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(gc != NULL && 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;
+	}			// if
+
+	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);
+
+}				// qq_process_recv_group_im_apply_join
+
+/*****************************************************************************/
+// 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(gc != NULL && data != NULL && len > 0);
+
+	if (*cursor >= (data + len - 1)) {
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Received group msg been_rejected is empty\n");
+		return;
+	}			// if
+
+	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_internal_group_id(gc, internal_group_id);
+	if (group != NULL) {
+		group->my_status = QQ_GROUP_MEMBER_STATUS_NOT_MEMBER;
+		qq_group_refresh(gc, group);
+	}			// if group
+
+	g_free(reason);
+	g_free(msg);
+	g_free(reason_utf8);
+
+}				// qq_process_group_im_being_rejected
+
+/*****************************************************************************/
+// the request to join a group is approved
+void qq_process_recv_group_im_been_approved
+    (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;
+	qq_group *group;
+
+	g_return_if_fail(gc != NULL && data != NULL && len > 0);
+
+	if (*cursor >= (data + len - 1)) {
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Received group msg been_approved is empty\n");
+		return;
+	}			// if
+
+	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);
+	// it is also a "æ— " here, so do not display
+	convert_as_pascal_string(*cursor, &reason_utf8, QQ_CHARSET_DEFAULT);
+
+	msg = g_strdup_printf
+	    (_("You request to join group %d has been approved by admin %d"), external_group_id, admin_uid);
+
+	gaim_notify_warning(gc, _("QQ Qun Operation"), msg, NULL);
+
+	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	if (group != NULL) {
+		group->my_status = QQ_GROUP_MEMBER_STATUS_IS_MEMBER;
+		qq_group_refresh(gc, group);
+	}			// if group
+
+	g_free(msg);
+	g_free(reason_utf8);
+}				// qq_process_group_im_being_approved
+
+/*****************************************************************************/
+// process the packet when reomved 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(gc != NULL && data != NULL && len > 0);
+
+	if (*cursor >= (data + len - 1)) {
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Received group msg been_removed is empty\n");
+		return;
+	}			// if
+
+	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_internal_group_id(gc, internal_group_id);
+	if (group != NULL) {
+		group->my_status = QQ_GROUP_MEMBER_STATUS_NOT_MEMBER;
+		qq_group_refresh(gc, group);
+	}			// if group
+
+	g_free(msg);
+}				// qq_process_recv_group_im_been_removed
+
+/*****************************************************************************/
+// 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(gc != NULL && data != NULL && len > 0);
+
+	if (*cursor >= (data + len - 1)) {
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Received group msg been_added is empty\n");
+		return;
+	}			// if
+
+	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, _("OpenQ has added this group to your buddy list"));
+
+	group = qq_group_find_by_internal_group_id(gc, internal_group_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_by_id(gc, internal_group_id, external_group_id);
+		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
+	}			// if group;
+
+	g_free(msg);
+
+}				// qq_process_recv_group_im_been_added
+
+/*****************************************************************************/
+// 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 /* gfhuang */)
+{
+	gchar *msg_with_gaim_smiley, *msg_utf8_encoded, *im_src_name;
+	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(gc != NULL && gc->proto_data != NULL && data != NULL && data_len > 0);
+	qd = (qq_data *) gc->proto_data;
+
+	gaim_debug(GAIM_DEBUG_INFO, "QQ",	//by gfhuang
+			   "group im hex dump\n%s\n", hex_dump_to_str(*cursor, data_len - (*cursor - data)));
+
+	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) {	//by gfhuang, protocal changed
+		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, gfhuang
+	// 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)  // gfhuang, protocal changed
+		skip_len = 10;
+	else
+		skip_len = 0;
+	*cursor += skip_len;
+
+	im_group->msg = g_strdup(*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 /* gfhuang */;
+	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_internal_group_id(gc, internal_group_id);
+	g_return_if_fail(group != NULL);
+
+	conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, /*gfhuang*/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, /*gfhuang*/group->group_name_utf8, gaim_connection_get_account(gc));
+	}			// if conv
+
+	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);
+	}			// if conv
+	g_free(msg_with_gaim_smiley);
+	g_free(msg_utf8_encoded);
+	g_free(im_group->msg);
+	g_free(im_group->font_attr);
+}				// _qq_process_recv_group_im
+
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_im.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,54 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_GROUP_IM_H_
+#define _QQ_GROUP_IM_H_
+
+#include <glib.h>
+#include "connection.h"		// GaimConnection
+#include "group.h"		// qq_group
+
+void qq_send_packet_group_im(GaimConnection * gc, qq_group * group, const gchar * msg);
+void qq_process_group_cmd_im(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc);
+void
+qq_process_recv_group_im(guint8 * data,
+			 guint8 ** cursor, gint data_len, guint32 internal_group_id, GaimConnection * gc, guint16 im_type /* gfhuang */);
+void
+qq_process_recv_group_im_apply_join(guint8 * data,
+				    guint8 ** cursor, gint len, guint32 internal_group_id, GaimConnection * gc);
+void
+qq_process_recv_group_im_been_rejected(guint8 * data,
+				       guint8 ** cursor, gint len, guint32 internal_group_id, GaimConnection * gc);
+void
+qq_process_recv_group_im_been_approved(guint8 * data,
+				       guint8 ** cursor, gint len, guint32 internal_group_id, GaimConnection * gc);
+void
+qq_process_recv_group_im_been_removed(guint8 * data,
+				      guint8 ** cursor, gint len, guint32 internal_group_id, GaimConnection * gc);
+void
+qq_process_recv_group_im_been_added(guint8 * data,
+				    guint8 ** cursor, gint len, guint32 internal_group_id, GaimConnection * gc);
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_info.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,330 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "debug.h"		// gaim_debug
+#include "conversation.h"	// gaim_find_conversation_with_account
+
+#include "buddy_status.h"	// QQ_BUDDY_ONLINE_NORMAL
+#include "char_conv.h"		// convert_as_pascal_string
+#include "group_find.h"		// qq_group_find_by_internal_group_id
+#include "group_hash.h"		// qq_group_refresh
+#include "group_info.h"
+#include "buddy_status.h"	// is_online
+#include "group_network.h"	// qq_send_group_cmd
+
+// we check who needs to update member info every minutes
+// this interval determines if their member info is outdated
+#define QQ_GROUP_CHAT_REFRESH_NICKNAME_INTERNAL  180
+
+/*****************************************************************************/
+static gboolean _is_group_member_need_update_info(qq_buddy * member)
+{
+	g_return_val_if_fail(member != NULL, FALSE);
+	return (member->nickname == NULL) ||
+	    (time(NULL) - member->last_refresh) > QQ_GROUP_CHAT_REFRESH_NICKNAME_INTERNAL;
+}				// _is_group_member_need_update_info
+
+/*****************************************************************************/
+// this is done when we receive the reply to get_online_member sub_cmd
+// all member are set offline, and then only those in reply packets are online
+static void _qq_group_set_members_all_offline(qq_group * group)
+{
+	GList *list;
+	qq_buddy *member;
+	g_return_if_fail(group != NULL);
+
+	list = group->members;
+	while (list != NULL) {
+		member = (qq_buddy *) list->data;
+		member->status = QQ_BUDDY_ONLINE_OFFLINE;
+		list = list->next;
+	}			// while list
+}				// _qq_group_set_members_all_offline
+
+/*****************************************************************************/
+// send packet to get detailed information of one group
+void qq_send_cmd_group_get_group_info(GaimConnection * gc, qq_group * group)
+{
+	guint8 *raw_data, *cursor;
+	gint bytes, data_len;
+
+	g_return_if_fail(gc != NULL && group != NULL);
+
+	data_len = 5;
+	raw_data = g_newa(guint8, data_len);
+	cursor = raw_data;
+
+	bytes = 0;
+	bytes += create_packet_b(raw_data, &cursor, QQ_GROUP_CMD_GET_GROUP_INFO);
+	bytes += create_packet_dw(raw_data, &cursor, group->internal_group_id);
+
+	if (bytes != data_len)
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+			   "Fail create packet for %s\n", qq_group_cmd_get_desc(QQ_GROUP_CMD_GET_GROUP_INFO));
+	else
+		qq_send_group_cmd(gc, group, raw_data, data_len);
+}				// qq_send_cmd_group_get_group_info
+
+/*****************************************************************************/
+// send packet to get online group member, called by keep_alive
+void qq_send_cmd_group_get_online_member(GaimConnection * gc, qq_group * group) {
+	guint8 *raw_data, *cursor;
+	gint bytes, data_len;
+
+	g_return_if_fail(gc != NULL && group != NULL);
+
+	// only get online members when conversation window is on
+	if (NULL == gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT/*gfhuang*/,group->group_name_utf8, gaim_connection_get_account(gc))) {
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ",
+			   "Conv windows for \"%s\" is not on, do not get online members\n", group->group_name_utf8);
+		return;
+	}			// if gaim_find_conversation_with_account
+
+	data_len = 5;
+	raw_data = g_newa(guint8, data_len);
+	cursor = raw_data;
+
+	bytes = 0;
+	bytes += create_packet_b(raw_data, &cursor, QQ_GROUP_CMD_GET_ONLINE_MEMBER);
+	bytes += create_packet_dw(raw_data, &cursor, group->internal_group_id);
+
+	if (bytes != data_len)
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+			   "Fail create packet for %s\n", qq_group_cmd_get_desc(QQ_GROUP_CMD_GET_ONLINE_MEMBER));
+	else
+		qq_send_group_cmd(gc, group, raw_data, data_len);
+}				// qq_send_cmd_group_search_group
+
+/*****************************************************************************/
+// send packet to get group member info
+void qq_send_cmd_group_get_member_info(GaimConnection * gc, qq_group * group) {
+	guint8 *raw_data, *cursor;
+	gint bytes, data_len, i;
+	GList *list;
+	qq_buddy *member;
+
+	g_return_if_fail(gc != NULL && group != NULL);
+	for (i = 0, list = group->members; list != NULL; list = list->next) {
+		member = (qq_buddy *) list->data;
+		if (_is_group_member_need_update_info(member))
+			i++;
+	}			// for i
+
+	if (i <= 0) {
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "No group member needs to to update info now.\n");
+		return;
+	}			// if i
+
+	data_len = 5 + 4 * i;
+	raw_data = g_newa(guint8, data_len);
+	cursor = raw_data;
+
+	bytes = 0;
+	bytes += create_packet_b(raw_data, &cursor, QQ_GROUP_CMD_GET_MEMBER_INFO);
+	bytes += create_packet_dw(raw_data, &cursor, group->internal_group_id);
+
+	list = group->members;
+	while (list != NULL) {
+		member = (qq_buddy *) list->data;
+		if (_is_group_member_need_update_info(member))
+			bytes += create_packet_dw(raw_data, &cursor, member->uid);
+		list = list->next;
+	}			// while list
+
+	if (bytes != data_len)
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+			   "Fail create packet for %s\n", qq_group_cmd_get_desc(QQ_GROUP_CMD_GET_MEMBER_INFO));
+	else
+		qq_send_group_cmd(gc, group, raw_data, data_len);
+}				// qq_send_cmd_group_get_member_info
+
+/*****************************************************************************/
+void qq_process_group_cmd_get_group_info(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) {
+	qq_group *group;
+	qq_data *qd;
+	guint8 orgnization, role; //gfhuang
+	guint16 unknown;
+	guint32 member_uid, internal_group_id;
+	gint pascal_len, i;
+	guint32 unknown4;
+	guint8 unknown1;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	g_return_if_fail(data != NULL && len > 0);
+	qd = (qq_data *) gc->proto_data;
+
+	read_packet_dw(data, cursor, len, &(internal_group_id));
+	g_return_if_fail(internal_group_id > 0);
+
+	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	g_return_if_fail(group != NULL);
+
+	read_packet_dw(data, cursor, len, &(group->external_group_id));
+	read_packet_b(data, cursor, len, &(group->group_type));
+	read_packet_dw(data, cursor, len, &unknown4);	//unknown 4 bytes, protocal changed by gfhuang
+	read_packet_dw(data, cursor, len, &(group->creator_uid));
+	read_packet_b(data, cursor, len, &(group->auth_type));
+	read_packet_dw(data, cursor, len, &unknown4);	// oldCategory, by gfhuang
+	read_packet_w(data, cursor, len, &unknown);	
+	read_packet_dw(data, cursor, len, &(group->group_category));
+	read_packet_w(data, cursor, len, &(unknown));	// 0x0000
+	read_packet_b(data, cursor, len, &unknown1);
+	read_packet_dw(data, cursor, len, &(unknown4));	// versionID, by gfhuang
+
+	pascal_len = convert_as_pascal_string(*cursor, &(group->group_name_utf8), QQ_CHARSET_DEFAULT);
+	*cursor += pascal_len;
+	read_packet_w(data, cursor, len, &(unknown));	// 0x0000  
+	pascal_len = convert_as_pascal_string(*cursor, &(group->notice_utf8), QQ_CHARSET_DEFAULT);
+	*cursor += pascal_len;
+	pascal_len = convert_as_pascal_string(*cursor, &(group->group_desc_utf8), QQ_CHARSET_DEFAULT);
+	*cursor += pascal_len;
+
+	i = 0;
+	// now comes the member list separated by 0x00
+	while (*cursor < data + len) {
+		read_packet_dw(data, cursor, len, &member_uid);
+		i++;
+		read_packet_b(data, cursor, len, &orgnization);	// protocal changed, gfhuang
+		read_packet_b(data, cursor, len, &role);// gfhuang
+
+		if(orgnization != 0 || role != 0) {
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "group member %d: orgnizatio=%d, role=%d\n", member_uid, orgnization, role);
+		}
+		qq_buddy *member = qq_group_find_or_add_member(gc, group, member_uid);
+		member->role = role;
+	}			// while *cursor
+        if(*cursor > (data + len)) {
+                         gaim_debug(GAIM_DEBUG_ERROR, "QQ", "group_cmd_get_group_info: Dangerous error! maybe protocal changed, notify me!");
+        }
+
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "group \"%s\" has %d members\n", group->group_name_utf8, i);
+
+	if (group->creator_uid == qd->uid)
+		group->my_status = QQ_GROUP_MEMBER_STATUS_IS_ADMIN;
+
+	qq_group_refresh(gc, group);
+
+	//added topic by gfhuang
+	GaimConversation *gaim_conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT,group->group_name_utf8, gaim_connection_get_account(gc));
+	if(NULL == gaim_conv) {
+                gaim_debug(GAIM_DEBUG_WARNING, "QQ",
+                           "Conv windows for \"%s\" is not on, do not set topic\n", group->group_name_utf8);
+	}
+	else {
+		gaim_conv_chat_set_topic(GAIM_CONV_CHAT(gaim_conv), NULL, group->notice_utf8);
+	}
+}				// qq_process_group_cmd_get_group_info
+
+/*****************************************************************************/
+void qq_process_group_cmd_get_online_member(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) {
+	guint32 internal_group_id, member_uid;
+	guint8 unknown;
+	gint bytes, i;
+	qq_group *group;
+	qq_buddy *member;
+
+	g_return_if_fail(gc != NULL && data != NULL && len > 0);
+
+	if (data + len - *cursor < 4) {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Invalid group online member reply, discard it!\n");
+		return;
+	}			// if data_len-*cursor
+
+	bytes = 0;
+	i = 0;
+	bytes += read_packet_dw(data, cursor, len, &internal_group_id);
+	bytes += read_packet_b(data, cursor, len, &unknown);	// 0x3c ??
+	g_return_if_fail(internal_group_id > 0);
+
+	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	if (group == NULL) {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "We have no group info for internal id [%d]\n", internal_group_id);
+		return;
+	}			// if group == NULL
+
+	// set all offline first, then update those online
+	_qq_group_set_members_all_offline(group);
+	while (*cursor < data + len) {
+		bytes += read_packet_dw(data, cursor, len, &member_uid);
+		i++;
+		member = qq_group_find_or_add_member(gc, group, member_uid);
+		if (member != NULL)
+			member->status = QQ_BUDDY_ONLINE_NORMAL;
+	}			// while
+        if(*cursor > (data + len)) {
+                         gaim_debug(GAIM_DEBUG_ERROR, "QQ", "group_cmd_get_online_member: Dangerous error! maybe protocal changed, notify me!");
+        }
+
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Group \"%s\" has %d online members\n", group->group_name_utf8, i);
+
+}				// qq_process_group_cmd_get_online_member
+
+/*****************************************************************************/
+// process the reply to get_member_info packet
+void qq_process_group_cmd_get_member_info(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) {
+	guint32 internal_group_id, member_uid;
+	guint16 unknown;
+	guint8 bar;
+	gint pascal_len, i;
+	qq_group *group;
+	qq_buddy *member;
+
+	g_return_if_fail(gc != NULL && data != NULL && len > 0);
+
+	read_packet_dw(data, cursor, len, &internal_group_id);
+	g_return_if_fail(internal_group_id > 0);
+
+	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	g_return_if_fail(group != NULL);
+
+	i = 0;
+	// now starts the member info, as get buddy list reply
+	while (*cursor < data + len) {
+		read_packet_dw(data, cursor, len, &member_uid);
+		g_return_if_fail(member_uid > 0);
+		member = qq_group_find_member_by_uid(group, member_uid);
+		g_return_if_fail(member != NULL);
+
+		i++;
+		read_packet_b(data, cursor, len, &bar);
+		read_packet_b(data, cursor, len, &(member->icon));
+		read_packet_b(data, cursor, len, &(member->age));
+		read_packet_b(data, cursor, len, &(member->gender));
+		pascal_len = convert_as_pascal_string(*cursor, &(member->nickname), QQ_CHARSET_DEFAULT);
+		*cursor += pascal_len;
+		read_packet_w(data, cursor, len, &unknown);
+		read_packet_b(data, cursor, len, &(member->flag1));
+		read_packet_b(data, cursor, len, &(member->comm_flag));
+
+		member->last_refresh = time(NULL);
+	}			// while
+        if(*cursor > (data + len)) {
+                         gaim_debug(GAIM_DEBUG_ERROR, "QQ", "group_cmd_get_member_info: Dangerous error! maybe protocal changed, notify me!");
+        }
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Group \"%s\" obtained %d member info\n", group->group_name_utf8, i);
+
+}				// qq_process_group_cmd_get_member_info
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_info.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,41 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_GROUP_INFO_H_
+#define _QQ_GROUP_INFO_H_
+
+#include <glib.h>
+#include "connection.h"		// GaimConnection
+#include "group.h"		// qq_group
+
+void qq_send_cmd_group_get_group_info(GaimConnection * gc, qq_group * group);
+void qq_send_cmd_group_get_online_member(GaimConnection * gc, qq_group * group);
+void qq_send_cmd_group_get_member_info(GaimConnection * gc, qq_group * group);
+void qq_process_group_cmd_get_group_info(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc);
+void qq_process_group_cmd_get_online_member(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc);
+void qq_process_group_cmd_get_member_info(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_join.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,375 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "debug.h"		// gaim_debug
+#include "notify.h"		// gaim_notify_xxx
+#include "request.h"		// gaim_request_input
+#include "server.h"		// serv_got_joined_chat
+
+#include "buddy_opt.h"		// gc_and_uid
+#include "char_conv.h"		// QQ_CHARSET_DEFAULT
+#include "group_conv.h"		// qq_group_conv_show_window
+#include "group_find.h"		// qq_group_find_by_internal_group_id
+#include "group_free.h"		// qq_group_remove_by_internal_group_id
+#include "group_hash.h"		// qq_group_refresh
+#include "group_info.h"		// qq_send_cmd_group_get_group_info
+#include "group_join.h"
+#include "group_opt.h"		// qq_send_cmd_group_auth
+#include "group_network.h"	// qq_send_group_cmd
+
+enum {
+	QQ_GROUP_JOIN_OK = 0x01,
+	QQ_GROUP_JOIN_NEED_AUTH = 0x02,
+};
+
+/*****************************************************************************/
+static void _qq_group_exit_with_gc_and_id(gc_and_uid * g)
+{
+	GaimConnection *gc;
+	guint32 internal_group_id;
+	qq_group *group;
+
+	g_return_if_fail(g != NULL && g->gc != NULL && g->uid > 0);
+	gc = g->gc;
+	internal_group_id = g->uid;
+
+	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	g_return_if_fail(group != NULL);
+
+	qq_send_cmd_group_exit_group(gc, group);
+}				// _qq_group_exist_with_gc_and_id
+
+/*****************************************************************************/
+// send packet to join a group without auth
+static void _qq_send_cmd_group_join_group(GaimConnection * gc, qq_group * group)
+{
+	guint8 *raw_data, *cursor;
+	gint bytes, data_len;
+
+	g_return_if_fail(gc != NULL && group != NULL);
+	if (group->my_status == QQ_GROUP_MEMBER_STATUS_NOT_MEMBER) {
+		group->my_status = QQ_GROUP_MEMBER_STATUS_APPLYING;
+		qq_group_refresh(gc, group);
+	}			// if group->my_status
+
+	data_len = 5;
+	raw_data = g_newa(guint8, data_len);
+	cursor = raw_data;
+
+	bytes = 0;
+	bytes += create_packet_b(raw_data, &cursor, QQ_GROUP_CMD_JOIN_GROUP);
+	bytes += create_packet_dw(raw_data, &cursor, group->internal_group_id);
+
+	if (bytes != data_len)
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+			   "Fail create packet for %s\n", qq_group_cmd_get_desc(QQ_GROUP_CMD_JOIN_GROUP));
+	else
+		qq_send_group_cmd(gc, group, raw_data, data_len);
+}				// _qq_send_cmd_group_join_group
+
+/*****************************************************************************/
+static void _qq_group_join_auth_with_gc_and_id(gc_and_uid * g, const gchar * reason_utf8)
+{
+	GaimConnection *gc;
+	qq_group *group;
+	guint32 internal_group_id;
+
+	g_return_if_fail(g != NULL && g->gc != NULL && g->uid > 0);
+	gc = g->gc;
+	internal_group_id = g->uid;
+
+	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	if (group == NULL) {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Can not find qq_group by internal_id: %d\n", internal_group_id);
+		return;
+	} else			// everything is OK
+		qq_send_cmd_group_auth(gc, group, QQ_GROUP_AUTH_REQUEST_APPLY, 0, reason_utf8);
+
+}				// _qq_group_join_auth_with_gc_and_id
+
+/*****************************************************************************/
+static void _qq_group_join_auth(GaimConnection * gc, qq_group * group)
+{
+	gchar *msg;
+	gc_and_uid *g;
+	g_return_if_fail(gc != NULL && group != NULL);
+
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Group (internal id: %d) needs authentication\n", group->internal_group_id);
+
+	msg = g_strdup_printf("Group \"%s\" needs authentication\n", group->group_name_utf8);
+	g = g_new0(gc_and_uid, 1);
+	g->gc = gc;
+	g->uid = group->internal_group_id;
+	gaim_request_input(gc, NULL, msg,
+			   _("Input request here"),
+			   _("Would you be my friend?"), TRUE, FALSE, NULL,
+			   _("Send"),
+			   G_CALLBACK(_qq_group_join_auth_with_gc_and_id),
+			   _("Cancel"), G_CALLBACK(qq_do_nothing_with_gc_and_uid), g);
+	g_free(msg);
+}				// _qq_group_join_auth
+
+/*****************************************************************************/
+void qq_send_cmd_group_auth(GaimConnection * gc, qq_group * group, guint8 opt, guint32 uid, const gchar * reason_utf8) {
+	guint8 *raw_data, *cursor;
+	gchar *reason_qq;
+	gint bytes, data_len;
+
+	g_return_if_fail(gc != NULL && group != NULL);
+
+	if (reason_utf8 == NULL || strlen(reason_utf8) == 0)
+		reason_qq = g_strdup("");
+	else
+		reason_qq = utf8_to_qq(reason_utf8, QQ_CHARSET_DEFAULT);
+
+	if (opt == QQ_GROUP_AUTH_REQUEST_APPLY) {
+		group->my_status = QQ_GROUP_MEMBER_STATUS_APPLYING;
+		qq_group_refresh(gc, group);
+		uid = 0;
+	}			// if (opt == QQ_GROUP_AUTH_REQUEST_APPLY)
+
+	data_len = 10 + strlen(reason_qq) + 1;
+	raw_data = g_newa(guint8, data_len);
+	cursor = raw_data;
+
+	bytes = 0;
+	bytes += create_packet_b(raw_data, &cursor, QQ_GROUP_CMD_JOIN_GROUP_AUTH);
+	bytes += create_packet_dw(raw_data, &cursor, group->internal_group_id);
+	bytes += create_packet_b(raw_data, &cursor, opt);
+	bytes += create_packet_dw(raw_data, &cursor, uid);
+	bytes += create_packet_b(raw_data, &cursor, strlen(reason_qq));
+	bytes += create_packet_data(raw_data, &cursor, reason_qq, strlen(reason_qq));
+
+	if (bytes != data_len)
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+			   "Fail create packet for %s\n", qq_group_cmd_get_desc(QQ_GROUP_CMD_JOIN_GROUP_AUTH));
+	else
+		qq_send_group_cmd(gc, group, raw_data, data_len);
+}				// qq_send_packet_group_auth
+
+/*****************************************************************************/
+// send packet to exit one group
+// In fact, this will never be used for GAIM
+// when we remove a GaimChat node, there is no user controlable callback
+// so we only remove the GaimChat node,
+// but we never use this cmd to update the server side
+// anyway, it is function, as when we remove the GaimChat node,
+// user has no way to start up the chat conversation window
+// therefore even we are still in it, 
+// the group IM will not show up to bother us. (Limited by GAIM)
+void qq_send_cmd_group_exit_group(GaimConnection * gc, qq_group * group)
+{
+	guint8 *raw_data, *cursor;
+	gint bytes, data_len;
+
+	g_return_if_fail(gc != NULL && group != NULL);
+
+	data_len = 5;
+	raw_data = g_newa(guint8, data_len);
+	cursor = raw_data;
+
+	bytes = 0;
+	bytes += create_packet_b(raw_data, &cursor, QQ_GROUP_CMD_EXIT_GROUP);
+	bytes += create_packet_dw(raw_data, &cursor, group->internal_group_id);
+
+	if (bytes != data_len)
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+			   "Fail create packet for %s\n", qq_group_cmd_get_desc(QQ_GROUP_CMD_EXIT_GROUP));
+	else
+		qq_send_group_cmd(gc, group, raw_data, data_len);
+}				// qq_send_cmd_group_get_group_info
+
+/*****************************************************************************/
+// If comes here, cmd is OK already
+void qq_process_group_cmd_exit_group(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) {
+	gint bytes, expected_bytes;
+	guint32 internal_group_id;
+	GaimChat *chat;
+	qq_group *group;
+	qq_data *qd;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	g_return_if_fail(data != NULL && len > 0);
+	qd = (qq_data *) gc->proto_data;
+
+	bytes = 0;
+	expected_bytes = 4;
+	bytes += read_packet_dw(data, cursor, len, &internal_group_id);
+
+	if (bytes == expected_bytes) {
+		group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+		if (group != NULL) {
+			chat =
+			    gaim_blist_find_chat
+			    (gaim_connection_get_account(gc), g_strdup_printf("%d", group->external_group_id));
+			if (chat != NULL)
+				gaim_blist_remove_chat(chat);
+			qq_group_remove_by_internal_group_id(qd, internal_group_id);
+		}		// if group
+		gaim_notify_info(gc, _("QQ Qun Operation"), _("You have successfully exit group"), NULL);
+	} else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+			   "Invalid exit group reply, expect %d bytes, read %d bytes\n", expected_bytes, bytes);
+
+}				// qq_process_group_cmd_exit_group
+
+/*****************************************************************************/
+// Process the reply to group_auth subcmd
+void qq_process_group_cmd_join_group_auth(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) {
+	gint bytes, expected_bytes;
+	guint32 internal_group_id;
+	qq_data *qd;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	g_return_if_fail(data != NULL && len > 0);
+	qd = (qq_data *) gc->proto_data;
+
+	bytes = 0;
+	expected_bytes = 4;
+	bytes += read_packet_dw(data, cursor, len, &internal_group_id);
+	g_return_if_fail(internal_group_id > 0);
+
+	if (bytes == expected_bytes)
+		gaim_notify_info
+		    (gc, _("QQ Group Auth"), _("You authorization operation has been accepted by QQ server"), NULL);
+	else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+			   "Invalid join group reply, expect %d bytes, read %d bytes\n", expected_bytes, bytes);
+
+}				// qq_process_group_cmd_group_auth
+
+/*****************************************************************************/
+// process group cmd reply "join group"
+void qq_process_group_cmd_join_group(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) {
+	gint bytes, expected_bytes;
+	guint32 internal_group_id;
+	guint8 reply;
+	qq_group *group;
+
+	g_return_if_fail(gc != NULL && data != NULL && len > 0);
+
+	bytes = 0;
+	expected_bytes = 5;
+	bytes += read_packet_dw(data, cursor, len, &internal_group_id);
+	bytes += read_packet_b(data, cursor, len, &reply);
+
+	if (bytes != expected_bytes) {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+			   "Invalid join group reply, expect %d bytes, read %d bytes\n", expected_bytes, bytes);
+		return;
+	} else {		// join group OK
+		group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+		// need to check if group is NULL or not.
+		g_return_if_fail(group != NULL);
+		switch (reply) {
+		case QQ_GROUP_JOIN_OK:
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "Succeed joining group \"%s\"\n", group->group_name_utf8);
+			group->my_status = QQ_GROUP_MEMBER_STATUS_IS_MEMBER;
+			qq_group_refresh(gc, group);
+			// this must be show before getting online member
+			qq_group_conv_show_window(gc, group);
+			qq_send_cmd_group_get_group_info(gc, group);
+			break;
+		case QQ_GROUP_JOIN_NEED_AUTH:
+			gaim_debug(GAIM_DEBUG_INFO, "QQ",
+				   "Fail joining group [%d] %s, needs authentication\n",
+				   group->external_group_id, group->group_name_utf8);
+			group->my_status = QQ_GROUP_MEMBER_STATUS_NOT_MEMBER;
+			qq_group_refresh(gc, group);
+			_qq_group_join_auth(gc, group);
+			break;
+		default:
+			gaim_debug(GAIM_DEBUG_INFO, "QQ",
+				   "Error joining group [%d] %s, unknown reply: 0x%02x\n",
+				   group->external_group_id, group->group_name_utf8, reply);
+		}		// switch reply
+	}			// if bytes != expected_bytes
+}				// qq_process_group_cmd_join_group
+
+/*****************************************************************************/
+// Apply to join one group without auth
+void qq_group_join(GaimConnection * gc, GHashTable * data)
+{
+	gchar *internal_group_id_ptr;
+	guint32 internal_group_id;
+	qq_group *group;
+
+	g_return_if_fail(gc != NULL && data != NULL);
+
+	internal_group_id_ptr = g_hash_table_lookup(data, "internal_group_id");
+	internal_group_id = strtol(internal_group_id_ptr, NULL, 10);
+
+	g_return_if_fail(internal_group_id > 0);
+
+	// for those we have subscribed, they should have been put into
+	// qd->groups in qq_group_init subroutine
+	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	if (group == NULL)
+		group = qq_group_from_hashtable(gc, data);
+
+	g_return_if_fail(group != NULL);
+
+	switch (group->auth_type) {
+	case QQ_GROUP_AUTH_TYPE_NO_AUTH:
+	case QQ_GROUP_AUTH_TYPE_NEED_AUTH:
+		_qq_send_cmd_group_join_group(gc, group);
+		break;
+	case QQ_GROUP_AUTH_TYPE_NO_ADD:
+		gaim_notify_warning(gc, NULL, _("This group does not allow others to join"), NULL);
+		break;
+	default:
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Unknown group auth type: %d\n", group->auth_type);
+	}			// switch auth_type
+}				// qq_group_join
+
+/*****************************************************************************/
+void qq_group_exit(GaimConnection * gc, GHashTable * data)
+{
+	gchar *internal_group_id_ptr;
+	guint32 internal_group_id;
+	gc_and_uid *g;
+
+	g_return_if_fail(gc != NULL && data != NULL);
+
+	internal_group_id_ptr = g_hash_table_lookup(data, "internal_group_id");
+	internal_group_id = strtol(internal_group_id_ptr, NULL, 10);
+
+	g_return_if_fail(internal_group_id > 0);
+
+	g = g_new0(gc_and_uid, 1);
+	g->gc = gc;
+	g->uid = internal_group_id;
+
+	gaim_request_action(gc, _("QQ Qun Operation"),
+			    _("Are you sure to exit this Qun?"),
+			    _
+			    ("Note, if you are the creator, \nthis operation will eventually remove this Qun."),
+			    1, g, 2, _("Cancel"),
+			    G_CALLBACK(qq_do_nothing_with_gc_and_uid),
+			    _("Go ahead"), G_CALLBACK(_qq_group_exit_with_gc_and_id));
+
+}				// qq_group_exit
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_join.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,54 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_GROUP_JOIN_H_
+#define _QQ_GROUP_JOIN_H_
+
+#include <glib.h>
+#include "connection.h"
+#include "group.h"
+
+enum {
+	QQ_GROUP_AUTH_TYPE_NO_AUTH = 0x01,
+	QQ_GROUP_AUTH_TYPE_NEED_AUTH = 0x02,
+	QQ_GROUP_AUTH_TYPE_NO_ADD = 0x03,
+};
+
+enum {
+	QQ_GROUP_AUTH_REQUEST_APPLY = 0x01,
+	QQ_GROUP_AUTH_REQUEST_APPROVE = 0x02,
+	QQ_GROUP_AUTH_REQUEST_REJECT = 0x03,
+};
+
+void qq_send_cmd_group_auth(GaimConnection * gc, qq_group * group, guint8 opt, guint32 uid, const gchar * reason_utf8);
+void qq_group_join(GaimConnection * gc, GHashTable * data);
+void qq_group_exit(GaimConnection * gc, GHashTable * data);
+void qq_send_cmd_group_exit_group(GaimConnection * gc, qq_group * group);
+void qq_process_group_cmd_exit_group(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc);
+void qq_process_group_cmd_join_group_auth(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc);
+void qq_process_group_cmd_join_group(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_misc.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,34 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "debug.h"
+
+#include "utils.h"
+#include "buddy_status.h"
+#include "group_misc.h"
+
+
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_misc.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,34 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_GROUP_MISC_H_
+#define _QQ_GROUP_MISC_H_
+
+#include <glib.h>
+#include "group.h"		// qq_group
+
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_network.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,241 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "debug.h"		// gaim_debug
+#include "notify.h"		// gaim_notify_xxx
+
+#include "utils.h"		// hex_dump_to_str
+#include "char_conv.h"		// qq_to_utf8
+#include "crypt.h"		// qq_crypt
+#include "group_conv.h"		// qq_group_conv_refresh_online_member
+#include "group_find.h"		// qq_group_find_internal_group_id_by_seq
+#include "group_hash.h"		// qq_group_refresh
+#include "group_im.h"		// qq_process_group_cmd_im
+#include "group_info.h"		// qq_process_group_cmd_get_online_member
+#include "group_join.h"		// qq_process_group_cmd_join_group
+#include "group_network.h"
+#include "group_opt.h"		// qq_group_process_modify_info_reply
+#include "group_search.h"	// qq_process_group_cmd_search_group
+#include "header_info.h"	// QQ_CMD_GROUP_CMD
+#include "send_core.h"		// qq_send_cmd
+
+enum {
+	QQ_GROUP_CMD_REPLY_OK = 0x00,
+	QQ_GROUP_CMD_REPLY_NOT_MEMBER = 0x0a,
+};
+
+/*****************************************************************************/
+const gchar *qq_group_cmd_get_desc(qq_group_cmd cmd)
+{
+	switch (cmd) {
+	case QQ_GROUP_CMD_CREATE_GROUP:
+		return "QQ_GROUP_CMD_CREATE_GROUP";
+	case QQ_GROUP_CMD_MEMBER_OPT:
+		return "QQ_GROUP_CMD_MEMBER_OPT";
+	case QQ_GROUP_CMD_MODIFY_GROUP_INFO:
+		return "QQ_GROUP_CMD_MODIFY_GROUP_INFO";
+	case QQ_GROUP_CMD_GET_GROUP_INFO:
+		return "QQ_GROUP_CMD_GET_GROUP_INFO";
+	case QQ_GROUP_CMD_ACTIVATE_GROUP:
+		return "QQ_GROUP_CMD_ACTIVATE_GROUP";
+	case QQ_GROUP_CMD_SEARCH_GROUP:
+		return "QQ_GROUP_CMD_SEARCH_GROUP";
+	case QQ_GROUP_CMD_JOIN_GROUP:
+		return "QQ_GROUP_CMD_JOIN_GROUP";
+	case QQ_GROUP_CMD_JOIN_GROUP_AUTH:
+		return "QQ_GROUP_CMD_JOIN_GROUP_AUTH";
+	case QQ_GROUP_CMD_EXIT_GROUP:
+		return "QQ_GROUP_CMD_EXIT_GROUP";
+	case QQ_GROUP_CMD_SEND_MSG:
+		return "QQ_GROUP_CMD_SEND_MSG";
+	case QQ_GROUP_CMD_GET_ONLINE_MEMBER:
+		return "QQ_GROUP_CMD_GET_ONLINE_MEMBER";
+	case QQ_GROUP_CMD_GET_MEMBER_INFO:
+		return "QQ_GROUP_CMD_GET_MEMBER_INFO";
+	default:
+		return "Unknown QQ Group Command";
+	}			// switch
+}				// qq_group_cmd_get_desc
+
+/*****************************************************************************/
+// default process of reply error
+static void _qq_process_group_cmd_reply_error_default(guint8 reply, guint8 * cursor, gint len, GaimConnection * gc) {
+	gchar *msg, *msg_utf8;
+	g_return_if_fail(cursor != NULL && len > 0 && gc != NULL);
+
+	msg = g_strndup(cursor, len);	// it will append 0x00
+	msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT);
+	g_free(msg);
+	msg = g_strdup_printf(_("Code [0x%02X]: %s"), reply, msg_utf8);
+	gaim_notify_error(gc, NULL, _("Group Operation Error"), msg);
+	g_free(msg);
+	g_free(msg_utf8);
+
+}				// _qq_process_group_cmd_reply_error_default
+
+/*****************************************************************************/
+// default process, dump only
+static void _qq_process_group_cmd_reply_default(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) {
+	g_return_if_fail(gc != NULL && data != NULL && len > 0);
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Dump unprocessed group cmd reply:\n%s", hex_dump_to_str(data, len));
+}				// _qq_process_group_cmd_reply_default
+
+/*****************************************************************************/
+// The lower layer command of send group cmd
+void qq_send_group_cmd(GaimConnection * gc, qq_group * group, guint8 * raw_data, gint data_len) {
+	qq_data *qd;
+	group_packet *p;
+
+	g_return_if_fail(gc != NULL);
+	g_return_if_fail(raw_data != NULL && data_len > 0);
+
+	qd = (qq_data *) gc->proto_data;
+	g_return_if_fail(qd != NULL);
+
+	qq_send_cmd(gc, QQ_CMD_GROUP_CMD, TRUE, 0, TRUE, raw_data, data_len);
+
+	p = g_new0(group_packet, 1);
+
+	p->send_seq = qd->send_seq;
+	if (group == NULL)
+		p->internal_group_id = 0;
+	else
+		p->internal_group_id = group->internal_group_id;
+
+	qd->group_packets = g_list_append(qd->group_packets, p);
+
+}				// qq_send_group_cmd
+
+/*****************************************************************************/
+// the main entry of group cmd processing, called by qq_recv_core.c
+void qq_process_group_cmd_reply(guint8 * buf, gint buf_len, guint16 seq, GaimConnection * gc) {
+	qq_group *group;
+	qq_data *qd;
+	gint len, bytes;
+	guint32 internal_group_id;
+	guint8 *data, *cursor, sub_cmd, reply;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	g_return_if_fail(buf != NULL && buf_len != 0);
+
+	qd = (qq_data *) gc->proto_data;
+	len = buf_len;
+	data = g_newa(guint8, len);
+
+	if (!qq_group_find_internal_group_id_by_seq(gc, seq, &internal_group_id)) {
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "We have no record of group cmd, seq [%d]\n", seq);
+		return;
+	}			// if ! qq_group_find_internal_group_id_by_seq
+
+	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+		if (len <= 2) {
+			gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Group cmd reply is too short, only %d bytes\n", len);
+			return;
+		}		// if len
+
+		bytes = 0;
+		cursor = data;
+		bytes += read_packet_b(data, &cursor, len, &sub_cmd);
+		bytes += read_packet_b(data, &cursor, len, &reply);
+
+		group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+
+		if (reply != QQ_GROUP_CMD_REPLY_OK) {
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ",
+				   "Group cmd reply says cmd %s fails\n", qq_group_cmd_get_desc(sub_cmd));
+			switch (reply) {	// this should be all errors
+			case QQ_GROUP_CMD_REPLY_NOT_MEMBER:
+				if (group != NULL) {
+					gaim_debug(GAIM_DEBUG_WARNING,
+						   "QQ",
+						   "You are not a member of group \"%s\"\n", group->group_name_utf8);
+					group->my_status = QQ_GROUP_MEMBER_STATUS_NOT_MEMBER;
+					qq_group_refresh(gc, group);
+				}	// if group
+				break;
+			default:
+				_qq_process_group_cmd_reply_error_default(reply, cursor, len - bytes, gc);
+			}	// switch reply
+			return;
+		}		// if reply != QQ_GROUP_CMD_REPLY_OK
+
+		// seems to ok so far, so we process the reply according to sub_cmd
+		switch (sub_cmd) {
+		case QQ_GROUP_CMD_GET_GROUP_INFO:
+			qq_process_group_cmd_get_group_info(data, &cursor, len, gc);
+			if (group != NULL) {
+				qq_send_cmd_group_get_member_info(gc, group);
+				qq_send_cmd_group_get_online_member(gc, group);
+			}
+			break;
+		case QQ_GROUP_CMD_CREATE_GROUP:
+			qq_group_process_create_group_reply(data, &cursor, len, gc);
+			break;
+		case QQ_GROUP_CMD_MODIFY_GROUP_INFO:
+			qq_group_process_modify_info_reply(data, &cursor, len, gc);
+			break;
+		case QQ_GROUP_CMD_MEMBER_OPT:
+			qq_group_process_modify_members_reply(data, &cursor, len, gc);
+			break;
+		case QQ_GROUP_CMD_ACTIVATE_GROUP:
+			qq_group_process_activate_group_reply(data, &cursor, len, gc);
+			break;
+		case QQ_GROUP_CMD_SEARCH_GROUP:
+			qq_process_group_cmd_search_group(data, &cursor, len, gc);
+			break;
+		case QQ_GROUP_CMD_JOIN_GROUP:
+			qq_process_group_cmd_join_group(data, &cursor, len, gc);
+			break;
+		case QQ_GROUP_CMD_JOIN_GROUP_AUTH:
+			qq_process_group_cmd_join_group_auth(data, &cursor, len, gc);
+			break;
+		case QQ_GROUP_CMD_EXIT_GROUP:
+			qq_process_group_cmd_exit_group(data, &cursor, len, gc);
+			break;
+		case QQ_GROUP_CMD_SEND_MSG:
+			qq_process_group_cmd_im(data, &cursor, len, gc);
+			break;
+		case QQ_GROUP_CMD_GET_ONLINE_MEMBER:
+			qq_process_group_cmd_get_online_member(data, &cursor, len, gc);
+			if (group != NULL)
+				qq_group_conv_refresh_online_member(gc, group);
+			break;
+		case QQ_GROUP_CMD_GET_MEMBER_INFO:
+			qq_process_group_cmd_get_member_info(data, &cursor, len, gc);
+			if (group != NULL)
+				qq_group_conv_refresh_online_member(gc, group);
+			break;
+		default:
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ",
+				   "Group cmd %s is processed by default\n", qq_group_cmd_get_desc(sub_cmd));
+			_qq_process_group_cmd_reply_default(data, &cursor, len, gc);
+		}		// switch sub_cmd
+
+	} else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt group cmd reply\n");
+
+}				// qq_process_group_cmd_reply
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_network.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,60 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_GROUP_NETWORK_H_
+#define _QQ_GROUP_NETWORK_H_
+
+#include <glib.h>
+#include "connection.h"		// GaimConnection
+#include "packet_parse.h"	// create_packet
+#include "group.h"		// qq_group
+
+typedef enum {
+	QQ_GROUP_CMD_CREATE_GROUP = 0x01,
+	QQ_GROUP_CMD_MEMBER_OPT = 0x02,
+	QQ_GROUP_CMD_MODIFY_GROUP_INFO = 0x03,
+	QQ_GROUP_CMD_GET_GROUP_INFO = 0x04,
+	QQ_GROUP_CMD_ACTIVATE_GROUP = 0x05,
+	QQ_GROUP_CMD_SEARCH_GROUP = 0x06,
+	QQ_GROUP_CMD_JOIN_GROUP = 0x07,
+	QQ_GROUP_CMD_JOIN_GROUP_AUTH = 0x08,
+	QQ_GROUP_CMD_EXIT_GROUP = 0x09,
+	QQ_GROUP_CMD_SEND_MSG = 0x0a,
+	QQ_GROUP_CMD_GET_ONLINE_MEMBER = 0x0b,
+	QQ_GROUP_CMD_GET_MEMBER_INFO = 0x0c,
+} qq_group_cmd;
+
+typedef struct _group_packet {
+	guint16 send_seq;
+	guint32 internal_group_id;
+} group_packet;
+
+const gchar *qq_group_cmd_get_desc(qq_group_cmd cmd);
+
+void qq_send_group_cmd(GaimConnection * gc, qq_group * group, guint8 * raw_data, gint data_len);
+void qq_process_group_cmd_reply(guint8 * buf, gint buf_len, guint16 seq, GaimConnection * gc);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_opt.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,479 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "debug.h"		// gaim_debug
+#include "notify.h"		// gaim_notify_info
+#include "request.h"		// gaim_request_input
+
+#include "utils.h"		// uid_to_gaim_name
+#include "packet_parse.h"	// create_packet_b
+#include "buddy_info.h"		// qq_send_packet_get_info
+#include "char_conv.h"		// utf8_to_qq
+#include "group_admindlg.h"	// qq_group_manage_window_show
+#include "group_find.h"		// qq_group_find_by_internal_group_id
+#include "group_hash.h"		// qq_group_refresh
+#include "group_info.h"		// qq_send_cmd_group_get_group_info
+#include "group_join.h"		// qq_send_cmd_group_auth
+#include "group_network.h"	// qq_send_group_cmd
+#include "group_opt.h"
+#include "qq.h"
+
+/*****************************************************************************/
+// This implement quick sort algorithm (low->high)
+static void _quick_sort(gint * numbers, gint left, gint right)
+{
+	gint pivot, l_hold, r_hold;
+
+	l_hold = left;
+	r_hold = right;
+	pivot = numbers[left];
+	while (left < right) {
+		while ((numbers[right] >= pivot) && (left < right))
+			right--;
+		if (left != right) {
+			numbers[left] = numbers[right];
+			left++;
+		}		// if
+		while ((numbers[left] <= pivot) && (left < right))
+			left++;
+		if (left != right) {
+			numbers[right] = numbers[left];
+			right--;
+		}		// if
+	}			// while
+	numbers[left] = pivot;
+	pivot = left;
+	left = l_hold;
+	right = r_hold;
+	if (left < pivot)
+		_quick_sort(numbers, left, pivot - 1);
+	if (right > pivot)
+		_quick_sort(numbers, pivot + 1, right);
+}				// _quick_sort
+
+/*****************************************************************************/
+static void _sort(guint32 * list)
+{
+	gint i;
+	for (i = 0; list[i] < 0xffffffff; i++) {;
+	}
+	_quick_sort(list, 0, i - 1);
+}				// _sort
+
+/*****************************************************************************/
+static void _qq_group_member_opt(GaimConnection * gc, qq_group * group, gint operation, guint32 * members) {
+	guint8 *data, *cursor;
+	gint i, count, data_len;
+	g_return_if_fail(gc != NULL && group != NULL && members != NULL);
+
+	for (i = 0; members[i] != 0xffffffff; i++) {;
+	}
+	count = i;
+	data_len = 6 + count * 4;
+	data = g_newa(guint8, data_len);
+	cursor = data;
+	create_packet_b(data, &cursor, QQ_GROUP_CMD_MEMBER_OPT);
+	create_packet_dw(data, &cursor, group->internal_group_id);
+	create_packet_b(data, &cursor, operation);
+	for (i = 0; i < count; i++)
+		create_packet_dw(data, &cursor, members[i]);
+	qq_send_group_cmd(gc, group, data, data_len);
+}				// _qq_group_member_opt
+
+/*****************************************************************************/
+static void _qq_group_do_nothing_with_struct(group_member_opt * g)
+{
+	if (g != NULL)
+		g_free(g);
+}				// _qq_group_do_nothing_with_struct
+
+/*****************************************************************************/
+static void _qq_group_reject_application_real(group_member_opt * g, gchar * msg_utf8)
+{
+	qq_group *group;
+	g_return_if_fail(g != NULL && g->gc != NULL && g->internal_group_id > 0 && g->member > 0);
+	group = qq_group_find_by_internal_group_id(g->gc, g->internal_group_id);
+	g_return_if_fail(group != NULL);
+	qq_send_cmd_group_auth(g->gc, group, QQ_GROUP_AUTH_REQUEST_REJECT, g->member, msg_utf8);
+	g_free(g);
+}				// _qq_group_reject_application_real
+
+/*****************************************************************************/
+void qq_group_search_application_with_struct(group_member_opt * g)
+{
+	g_return_if_fail(g != NULL && g->gc != NULL && g->member > 0);
+
+	qq_send_packet_get_info(g->gc, g->member, TRUE);	// we wanna see window
+	gaim_request_action
+	    (g->gc, NULL, _("Do you wanna approve the request?"), "", 2, g,
+	     2, _("Reject"),
+	     G_CALLBACK(qq_group_reject_application_with_struct),
+	     _("Approve"), G_CALLBACK(qq_group_approve_application_with_struct));
+
+}				// qq_group_search_application_with_struct
+
+/*****************************************************************************/
+void qq_group_reject_application_with_struct(group_member_opt * g)
+{
+	gchar *msg1, *msg2;
+	g_return_if_fail(g != NULL && g->gc != NULL && g->member > 0);
+
+	msg1 = g_strdup_printf(_("You rejected %d's request"), g->member);
+	msg2 = g_strdup(_("Input your reason:"));
+
+	gaim_request_input(g->gc, NULL, msg1, msg2,
+			   _("Sorry, you are not my type..."), TRUE, FALSE,
+			   NULL, _("Send"),
+			   G_CALLBACK(_qq_group_reject_application_real),
+			   _("Cancel"), G_CALLBACK(_qq_group_do_nothing_with_struct), g);
+
+	g_free(msg1);
+	g_free(msg2);
+
+}				// qq_group_do_nothing_with_struct
+
+/*****************************************************************************/
+void qq_group_approve_application_with_struct(group_member_opt * g)
+{
+	qq_group *group;
+	g_return_if_fail(g != NULL && g->gc != NULL && g->internal_group_id > 0 && g->member > 0);
+	group = qq_group_find_by_internal_group_id(g->gc, g->internal_group_id);
+	g_return_if_fail(group != NULL);
+	qq_send_cmd_group_auth(g->gc, group, QQ_GROUP_AUTH_REQUEST_APPROVE, g->member, "");
+	qq_group_find_or_add_member(g->gc, group, g->member);
+	g_free(g);
+}				// qq_group_add_member_with_gc_and_uid
+
+/*****************************************************************************/
+void qq_group_modify_members(GaimConnection * gc, qq_group * group, guint32 * new_members) {
+	guint32 *old_members, *del_members, *add_members;
+	qq_buddy *q_bud;
+	qq_data *qd;
+	gint i = 0, old = 0, new = 0, del = 0, add = 0;
+	GList *list;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL && group != NULL);
+	qd = (qq_data *) gc->proto_data;
+	if (new_members[0] == 0xffffffff)
+		return;
+
+	old_members = g_newa(guint32, QQ_QUN_MEMBER_MAX);
+	del_members = g_newa(guint32, QQ_QUN_MEMBER_MAX);
+	add_members = g_newa(guint32, QQ_QUN_MEMBER_MAX);
+
+	// construct the old member list
+	list = group->members;
+	while (list != NULL) {
+		q_bud = (qq_buddy *) list->data;
+		if (q_bud != NULL)
+			old_members[i++] = q_bud->uid;
+		list = list->next;
+	}			// while
+	old_members[i] = 0xffffffff;	// this is the end
+
+	// sort to speed up making del_members and add_members list
+	_sort(old_members);
+	_sort(new_members);
+
+	for (old = 0, new = 0; old_members[old] < 0xffffffff || new_members[new] < 0xffffffff;) {
+		if (old_members[old] > new_members[new])
+			add_members[add++] = new_members[new++];
+		else if (old_members[old] < new_members[new])
+			del_members[del++] = old_members[old++];
+		else {
+			if (old_members[old] < 0xffffffff)
+				old++;
+			if (new_members[new] < 0xffffffff)
+				new++;
+		}		// if old_members, new_members
+	}			// for old,new
+	del_members[del] = add_members[add] = 0xffffffff;
+
+	for (i = 0; i < del; i++)
+		qq_group_remove_member_by_uid(group, del_members[i]);
+	for (i = 0; i < add; i++)
+		qq_group_find_or_add_member(gc, group, add_members[i]);
+
+	if (del > 0)
+		_qq_group_member_opt(gc, group, QQ_GROUP_MEMBER_DEL, del_members);
+	if (add > 0)
+		_qq_group_member_opt(gc, group, QQ_GROUP_MEMBER_ADD, add_members);
+
+}				// qq_group_modify_members
+
+/*****************************************************************************/
+void qq_group_process_modify_members_reply(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) {
+	guint32 internal_group_id;
+	qq_group *group;
+	g_return_if_fail(data != NULL && gc != NULL);
+
+	read_packet_dw(data, cursor, len, &internal_group_id);
+	g_return_if_fail(internal_group_id > 0);
+
+	// we should have its info locally
+	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	g_return_if_fail(group != NULL);
+
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Succeed in modify members for Qun %d\n", group->external_group_id);
+
+	gaim_notify_info(gc, _("QQ Qun Operation"), _("You have successfully modify Qun member"), NULL);
+
+}				// qq_group_process_modify_members_reply
+
+/*****************************************************************************/
+void qq_group_modify_info(GaimConnection * gc, qq_group * group)
+{
+	gint data_len, data_written;
+	guint8 *data, *cursor;
+	gchar *group_name, *group_desc, *notice;
+
+	g_return_if_fail(gc != NULL && group != NULL);
+
+	group_name = group->group_name_utf8 == NULL ? "" : utf8_to_qq(group->group_name_utf8, QQ_CHARSET_DEFAULT);
+	group_desc = group->group_desc_utf8 == NULL ? "" : utf8_to_qq(group->group_desc_utf8, QQ_CHARSET_DEFAULT);
+	notice = group->notice_utf8 == NULL ? "" : utf8_to_qq(group->notice_utf8, QQ_CHARSET_DEFAULT);
+
+	data_len = 13 + 1 + strlen(group_name)
+	    + 1 + strlen(group_desc)
+	    + 1 + strlen(notice);
+
+	data = g_newa(guint8, data_len);
+	cursor = data;
+	data_written = 0;
+	// 000-000
+	data_written += create_packet_b(data, &cursor, QQ_GROUP_CMD_MODIFY_GROUP_INFO);
+	// 001-004
+	data_written += create_packet_dw(data, &cursor, group->internal_group_id);
+	// 005-005
+	data_written += create_packet_b(data, &cursor, 0x01);
+	// 006-006
+	data_written += create_packet_b(data, &cursor, group->auth_type);
+	// 007-008
+	data_written += create_packet_w(data, &cursor, 0x0000);
+	// 009-010
+	data_written += create_packet_w(data, &cursor, group->group_category);
+
+	data_written += create_packet_b(data, &cursor, strlen(group_name));
+	data_written += create_packet_data(data, &cursor, group_name, strlen(group_name));
+
+	data_written += create_packet_w(data, &cursor, 0x0000);
+
+	data_written += create_packet_b(data, &cursor, strlen(notice));
+	data_written += create_packet_data(data, &cursor, notice, strlen(notice));
+
+	data_written += create_packet_b(data, &cursor, strlen(group_desc));
+	data_written += create_packet_data(data, &cursor, group_desc, strlen(group_desc));
+
+	if (data_written != data_len)
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+			   "Fail to create group_modify_info packet, expect %d bytes, wrote %d bytes\n",
+			   data_len, data_written);
+	else
+		qq_send_group_cmd(gc, group, data, data_len);
+
+}				// qq_group_modify_info
+
+/*****************************************************************************/
+void qq_group_process_modify_info_reply(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) {
+	guint32 internal_group_id;
+	qq_group *group;
+	g_return_if_fail(data != NULL && gc != NULL);
+
+	read_packet_dw(data, cursor, len, &internal_group_id);
+	g_return_if_fail(internal_group_id > 0);
+
+	// we should have its info locally
+	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	g_return_if_fail(group != NULL);
+
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Succeed in modify info for Qun %d\n", group->external_group_id);
+	qq_group_refresh(gc, group);
+
+	gaim_notify_info(gc, _("QQ Qun Operation"), _("You have successfully modify Qun information"), NULL);
+
+}				// qq_group_process_modify_info_reply
+
+/*****************************************************************************/
+// we create a very simple group first, and then let the user to modify
+void qq_group_create_with_name(GaimConnection * gc, const gchar * name)
+{
+	gint data_len, data_written;
+	guint8 *data, *cursor;
+	qq_data *qd;
+	g_return_if_fail(gc != NULL && name != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+	data_len = 7 + 1 + strlen(name) + 2 + 1 + 1 + 4;
+	data = g_newa(guint8, data_len);
+	cursor = data;
+
+	data_written = 0;
+	// we create the simpleset group, only group name is given
+	// 000
+	data_written += create_packet_b(data, &cursor, QQ_GROUP_CMD_CREATE_GROUP);
+	// 001
+	data_written += create_packet_b(data, &cursor, QQ_GROUP_TYPE_PERMANENT);
+	// 002
+	data_written += create_packet_b(data, &cursor, QQ_GROUP_AUTH_TYPE_NEED_AUTH);
+	// 003-004
+	data_written += create_packet_w(data, &cursor, 0x0000);
+	// 005-006
+	data_written += create_packet_w(data, &cursor, 0x0003);
+	// 007
+	data_written += create_packet_b(data, &cursor, strlen(name));
+	data_written += create_packet_data(data, &cursor, (gchar *) name, strlen(name));
+	data_written += create_packet_w(data, &cursor, 0x0000);
+	data_written += create_packet_b(data, &cursor, 0x00);	// no group notice
+	data_written += create_packet_b(data, &cursor, 0x00);	// no group desc
+	data_written += create_packet_dw(data, &cursor, qd->uid);	// I am member of coz
+
+	if (data_written != data_len)
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+			   "Fail create create_group packet, expect %d bytes, written %d bytes\n",
+			   data_len, data_written);
+	else
+		qq_send_group_cmd(gc, NULL, data, data_len);
+
+}				// qq_group_create_with_name
+
+/*****************************************************************************/
+static void qq_group_setup_with_gc_and_uid(gc_and_uid * g)
+{
+	qq_group *group;
+	g_return_if_fail(g != NULL && g->gc != NULL && g->uid > 0);
+
+	group = qq_group_find_by_internal_group_id(g->gc, g->uid);
+	g_return_if_fail(group != NULL);
+
+	qq_group_detail_window_show(g->gc, group);
+	g_free(g);
+}				// qq_group_setup_with_gc_and_uid
+
+/*****************************************************************************/
+void qq_group_process_create_group_reply(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) {
+	guint32 internal_group_id, external_group_id;
+	qq_group *group;
+	gc_and_uid *g;
+	qq_data *qd;
+
+	g_return_if_fail(data != NULL && gc != NULL);
+	g_return_if_fail(gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	read_packet_dw(data, cursor, len, &internal_group_id);
+	read_packet_dw(data, cursor, len, &external_group_id);
+	g_return_if_fail(internal_group_id > 0 && external_group_id);
+
+	group = qq_group_create_by_id(gc, internal_group_id, external_group_id);
+	group->my_status = QQ_GROUP_MEMBER_STATUS_IS_ADMIN;
+	group->creator_uid = qd->uid;
+	qq_group_refresh(gc, group);
+
+	qq_group_activate_group(gc, internal_group_id);
+	qq_send_cmd_group_get_group_info(gc, group);
+
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Succeed in create Qun, external ID %d\n", group->external_group_id);
+
+	g = g_new0(gc_and_uid, 1);
+	g->gc = gc;
+	g->uid = internal_group_id;
+
+	gaim_request_action(gc, _("QQ Qun Operation"),
+			    _("You have successfully created a Qun"),
+			    _
+			    ("Would you like to set up the Qun details now?"),
+			    1, g, 2, _("Setup"),
+			    G_CALLBACK(qq_group_setup_with_gc_and_uid),
+			    _("Cancel"), G_CALLBACK(qq_do_nothing_with_gc_and_uid));
+
+}				// qq_group_process_modify_info_reply
+
+/*****************************************************************************/
+// we have to activate group after creation, otherwise the group can not be searched
+void qq_group_activate_group(GaimConnection * gc, guint32 internal_group_id)
+{
+	gint data_len, data_written;
+	guint8 *data, *cursor;
+	g_return_if_fail(gc != NULL && internal_group_id > 0);
+
+	data_len = 5;
+	data = g_newa(guint8, data_len);
+	cursor = data;
+
+	data_written = 0;
+	// we create the simpleset group, only group name is given
+	// 000
+	data_written += create_packet_b(data, &cursor, QQ_GROUP_CMD_ACTIVATE_GROUP);
+	// 001-005
+	data_written += create_packet_dw(data, &cursor, internal_group_id);
+
+	if (data_written != data_len)
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+			   "Fail create activate_group packet, expect %d bytes, written %d bytes\n",
+			   data_len, data_written);
+	else
+		qq_send_group_cmd(gc, NULL, data, data_len);
+
+}				// qq_group_activate_group
+
+/*****************************************************************************/
+void qq_group_process_activate_group_reply(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) {
+	guint32 internal_group_id;
+	qq_group *group;
+	g_return_if_fail(data != NULL && gc != NULL);
+
+	read_packet_dw(data, cursor, len, &internal_group_id);
+	g_return_if_fail(internal_group_id > 0);
+
+	// we should have its info locally
+	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	g_return_if_fail(group != NULL);
+
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Succeed in activate Qun %d\n", group->external_group_id);
+
+}				// qq_group_process_activate_group_reply
+
+/*****************************************************************************/
+void qq_group_manage_group(GaimConnection * gc, GHashTable * data)
+{
+	gchar *internal_group_id_ptr;
+	guint32 internal_group_id;
+	qq_group *group;
+
+	g_return_if_fail(gc != NULL && data != NULL);
+
+	internal_group_id_ptr = g_hash_table_lookup(data, "internal_group_id");
+	internal_group_id = strtol(internal_group_id_ptr, NULL, 10);
+	g_return_if_fail(internal_group_id > 0);
+
+	group = qq_group_find_by_internal_group_id(gc, internal_group_id);
+	g_return_if_fail(group != NULL);
+
+	qq_group_detail_window_show(gc, group);
+
+}				// qq_group_manage_members
+
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_opt.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,67 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_GROUP_OPT_H_
+#define _QQ_GROUP_OPT_H_
+
+#include <glib.h>
+#include "connection.h"		// GaimConnection
+#include "group.h"		// qq_group
+
+#define QQ_QUN_MEMBER_MAX       80	// max number of the group
+
+typedef struct _group_member_opt {
+	GaimConnection *gc;
+	guint32 internal_group_id;
+	guint32 member;
+} group_member_opt;
+
+enum {
+	QQ_GROUP_TYPE_PERMANENT = 0x01,
+	QQ_GROUP_TYPE_TEMPORARY,
+};
+
+enum {
+	QQ_GROUP_MEMBER_ADD = 0x01,
+	QQ_GROUP_MEMBER_DEL,
+};
+
+void qq_group_modify_members(GaimConnection * gc, qq_group * group, guint32 * new_members);
+void qq_group_modify_info(GaimConnection * gc, qq_group * group);
+
+void qq_group_approve_application_with_struct(group_member_opt * g);
+void qq_group_reject_application_with_struct(group_member_opt * g);
+void qq_group_search_application_with_struct(group_member_opt * g);
+
+void qq_group_process_modify_info_reply(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc);
+void qq_group_process_modify_members_reply(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc);
+void qq_group_manage_group(GaimConnection * gc, GHashTable * data);
+void qq_group_create_with_name(GaimConnection * gc, const gchar * name);
+void qq_group_activate_group(GaimConnection * gc, guint32 internal_group_id);
+void qq_group_process_activate_group_reply(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc);
+void qq_group_process_create_group_reply(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_search.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,125 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "debug.h"		// gaim_debug
+
+#include "char_conv.h"		// convert_as_pascal_string
+#include "group_free.h"		// qq_group_free_not_subscribed
+#include "group_network.h"	// qq_send_group_cmd
+#include "group_search.h"
+#include "utils.h"
+
+enum {
+	QQ_GROUP_SEARCH_TYPE_BY_ID = 0x01,
+	QQ_GROUP_SEARCH_TYPE_DEMO = 0x02,
+};
+
+/*****************************************************************************/
+// send packet to search for qq_group
+void qq_send_cmd_group_search_group(GaimConnection * gc, guint32 external_group_id) {
+	guint8 *raw_data, *cursor, type;
+	gint bytes, data_len;
+
+	g_return_if_fail(gc != NULL);
+
+	data_len = 6;
+	raw_data = g_newa(guint8, data_len);
+	cursor = raw_data;
+	type = (external_group_id == 0x00000000) ? QQ_GROUP_SEARCH_TYPE_DEMO : QQ_GROUP_SEARCH_TYPE_BY_ID;
+
+	bytes = 0;
+	bytes += create_packet_b(raw_data, &cursor, QQ_GROUP_CMD_SEARCH_GROUP);
+	bytes += create_packet_b(raw_data, &cursor, type);
+	bytes += create_packet_dw(raw_data, &cursor, external_group_id);
+
+	if (bytes != data_len)
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+			   "Fail create packet for %s\n", qq_group_cmd_get_desc(QQ_GROUP_CMD_SEARCH_GROUP));
+	else
+		qq_send_group_cmd(gc, NULL, raw_data, data_len);
+}				// qq_send_cmd_group_search_group
+
+/*****************************************************************************/
+// process group cmd reply "search group"
+void qq_process_group_cmd_search_group(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) {
+	guint8 search_type;
+	guint16 unknown;
+	gint bytes, pascal_len, i;
+	qq_data *qd;
+	GaimRoomlistRoom *room;
+	qq_group *group;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	g_return_if_fail(data != NULL && len > 0);
+	qd = (qq_data *) gc->proto_data;
+
+	i = 0;
+	bytes += read_packet_b(data, cursor, len, &search_type);
+	group = g_newa(qq_group, 1);
+
+	// now it starts with group_info_entry
+	while (*cursor < (data + len)) {	// still have data to read
+		// begin of one qq_group
+		bytes = 0;
+		i++;
+		bytes += read_packet_dw(data, cursor, len, &(group->internal_group_id));
+		bytes += read_packet_dw(data, cursor, len, &(group->external_group_id));
+		bytes += read_packet_b(data, cursor, len, &(group->group_type));
+		bytes += read_packet_w(data, cursor, len, &(unknown)); // protocal changed, gfhuang
+		bytes += read_packet_w(data, cursor, len, &(unknown)); // protocal changed, gfhuang
+		bytes += read_packet_dw(data, cursor, len, &(group->creator_uid));
+		bytes += read_packet_w(data, cursor, len, &(unknown)); // protocal changed, gfhuang
+		bytes += read_packet_w(data, cursor, len, &(unknown)); // protocal changed, gfhuang
+		bytes += read_packet_w(data, cursor, len, &(unknown)); // protocal changed, gfhuang
+		bytes += read_packet_dw(data, cursor, len, &(group->group_category));
+		pascal_len = convert_as_pascal_string(*cursor, &(group->group_name_utf8), QQ_CHARSET_DEFAULT);
+		bytes += pascal_len;
+		*cursor += pascal_len;
+		bytes += read_packet_w(data, cursor, len, &(unknown));
+		bytes += read_packet_b(data, cursor, len, &(group->auth_type));
+		pascal_len = convert_as_pascal_string(*cursor, &(group->group_desc_utf8), QQ_CHARSET_DEFAULT);
+		bytes += pascal_len;
+		*cursor += pascal_len;
+		// end of one qq_group
+		room = gaim_roomlist_room_new(GAIM_ROOMLIST_ROOMTYPE_ROOM, group->group_name_utf8, NULL);
+		gaim_roomlist_room_add_field(qd->roomlist, room, g_strdup_printf("%d", group->external_group_id));
+		gaim_roomlist_room_add_field(qd->roomlist, room, g_strdup_printf("%d", group->creator_uid));
+		gaim_roomlist_room_add_field(qd->roomlist, room, group->group_desc_utf8);
+		gaim_roomlist_room_add_field(qd->roomlist, room, g_strdup_printf("%d", group->internal_group_id));
+		gaim_roomlist_room_add_field(qd->roomlist, room, g_strdup_printf("%d", group->group_type));
+		gaim_roomlist_room_add_field(qd->roomlist, room, g_strdup_printf("%d", group->auth_type));
+		gaim_roomlist_room_add_field(qd->roomlist, room, g_strdup_printf("%d", group->group_category));
+		gaim_roomlist_room_add_field(qd->roomlist, room, group->group_name_utf8);
+		gaim_roomlist_room_add(qd->roomlist, room);
+	}			// while
+        if(*cursor > (data + len)) {
+                         gaim_debug(GAIM_DEBUG_ERROR, "QQ", "group_cmd_search_group: Dangerous error! maybe protocal changed, notify me!");
+        }
+	gaim_roomlist_set_in_progress(qd->roomlist, FALSE);
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Search group reply: %d groups\n", i);
+
+}				// qq_process_group_cmd_search_group
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/group_search.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,36 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_GROUP_SEARCH_H_
+#define _QQ_GROUP_SEARCH_H_
+
+#include <glib.h>
+#include "connection.h"		// GaimConnection
+
+void qq_send_cmd_group_search_group(GaimConnection * gc, guint32 external_group_id);
+void qq_process_group_cmd_search_group(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/header_info.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,118 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "header_info.h"
+
+#define QQ_CLIENT_062E 0x062e	// GB QQ2000c build 0630
+#define QQ_CLIENT_072E 0x072e	// En QQ2000c build 0305
+#define QQ_CLIENT_0801 0x0801	// En QQ2000c build 0630
+#define QQ_CLIENT_0A1D 0x0a1d	// Gb QQ2003c build 0808
+#define QQ_CLIENT_0B07 0x0b07	// Gb QQ2003c build 0925
+#define QQ_CLIENT_0B2F 0x0b2f	// GB QQ2003iii build 0117
+#define QQ_CLIENT_0B35 0x0b35	// GB QQ2003iii build 0304 (offical release)
+#define QQ_CLIENT_0B37 0x0b37	// GB QQ2003iii build 0304 (April 05 updates)
+#define QQ_SERVER_0100 0x0100	// server
+
+/*****************************************************************************/
+// given command alias, return the command name accordingly
+const gchar *qq_get_cmd_desc(gint type)
+{
+	switch (type) {
+	case QQ_CMD_LOGOUT:
+		return "QQ_CMD_LOGOUT";
+	case QQ_CMD_KEEP_ALIVE:
+		return "QQ_CMD_KEEP_ALIVE";
+	case QQ_CMD_UPDATE_INFO:
+		return "QQ_CMD_UPDATE_INFO";
+	case QQ_CMD_SEARCH_USER:
+		return "QQ_CMD_SEARCH_USER";
+	case QQ_CMD_GET_USER_INFO:
+		return "QQ_CMD_GET_USER_INFO";
+	case QQ_CMD_ADD_FRIEND_WO_AUTH:
+		return "QQ_CMD_ADD_FRIEND_WO_AUTH";
+	case QQ_CMD_DEL_FRIEND:
+		return "QQ_CMD_DEL_FRIEND";
+	case QQ_CMD_BUDDY_AUTH:
+		return "QQ_CMD_BUDDY_AUTH";
+	case QQ_CMD_CHANGE_ONLINE_STATUS:
+		return "QQ_CMD_CHANGE_ONLINE_STATUS";
+	case QQ_CMD_ACK_SYS_MSG:
+		return "QQ_CMD_ACK_SYS_MSG";
+	case QQ_CMD_SEND_IM:
+		return "QQ_CMD_SEND_IM";
+	case QQ_CMD_RECV_IM:
+		return "QQ_CMD_RECV_IM";
+	case QQ_CMD_REMOVE_SELF:
+		return "QQ_CMD_REMOVE_SELF";
+	case QQ_CMD_LOGIN:
+		return "QQ_CMD_LOGIN";
+	case QQ_CMD_GET_FRIENDS_LIST:
+		return "QQ_CMD_GET_FRIENDS_LIST";
+	case QQ_CMD_GET_FRIENDS_ONLINE:
+		return "QQ_CMD_GET_FRIENDS_ONLINE";
+	case QQ_CMD_GROUP_CMD:
+		return "QQ_CMD_GROUP_CMD";
+	case QQ_CMD_GET_ALL_LIST_WITH_GROUP:		// by gfhuang
+		return "QQ_CMD_GET_ALL_LIST_WITH_GROUP";
+	case QQ_CMD_REQUEST_LOGIN_TOKEN:
+		return "QQ_CMD_REQUEST_LOGIN_TOKEN";	// by gfhuang
+	case QQ_CMD_RECV_MSG_SYS:
+		return "QQ_CMD_RECV_MSG_SYS";
+	case QQ_CMD_RECV_MSG_FRIEND_CHANGE_STATUS:
+		return "QQ_CMD_RECV_MSG_FRIEND_CHANGE_STATUS";
+	default:
+		return "UNKNOWN_TYPE";
+	}			// switch (type)
+}				// qq_get_cmd_desc
+
+/*****************************************************************************/
+// given source tag, return its description accordingly
+const gchar *qq_get_source_str(gint source)
+{
+	switch (source) {
+	case QQ_CLIENT_062E:
+		return "GB QQ2000c build 0630";
+	case QQ_CLIENT_072E:
+		return "En QQ2000c build 0305";
+	case QQ_CLIENT_0801:
+		return "En QQ2000c build 0630";
+	case QQ_CLIENT_0A1D:
+		return "GB QQ2003ii build 0808";
+	case QQ_CLIENT_0B07:
+		return "GB QQ2003ii build 0925";
+	case QQ_CLIENT_0B2F:
+		return "GB QQ2003iii build 0117";
+	case QQ_CLIENT_0B35:
+		return "GB QQ2003iii build 0304";
+	case QQ_CLIENT_0B37:
+		return "GB QQ2003iii build 0304 (April 5 update)";
+	case QQ_SERVER_0100:
+		return "QQ Server 0100";
+	default:
+		return "QQ unknown version";
+	}
+}				// qq_get_source_str
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/header_info.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,72 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_HEADER_INFO_H_
+#define _QQ_HEADER_INFO_H_
+
+#include <glib.h>
+
+#define QQ_UDP_HEADER_LENGTH    7
+#define QQ_TCP_HEADER_LENGTH    9
+
+#define QQ_PACKET_TAG           0x02	// all QQ text packets starts with it
+#define QQ_PACKET_TAIL          0x03	// all QQ text packets end with it
+
+//#define QQ_CLIENT               0x0b37	// QQ2003iii build 0304, Aprili 05 update
+
+// list of known QQ commands
+enum {
+	QQ_CMD_LOGOUT = 0x0001,	// log out
+	QQ_CMD_KEEP_ALIVE = 0x0002,	// get onlines from tencent
+	QQ_CMD_UPDATE_INFO = 0x0004,	// update information
+	QQ_CMD_SEARCH_USER = 0x0005,	// serach for user
+	QQ_CMD_GET_USER_INFO = 0x0006,	// get user information
+	QQ_CMD_ADD_FRIEND_WO_AUTH = 0x0009,	// add friend without auth
+	QQ_CMD_DEL_FRIEND = 0x000a,	// delete a friend 
+	QQ_CMD_BUDDY_AUTH = 0x000b,	// buddy authentication
+	QQ_CMD_CHANGE_ONLINE_STATUS = 0x000d,	// change my online status
+	QQ_CMD_ACK_SYS_MSG = 0x0012,	// ack system message
+	QQ_CMD_SEND_IM = 0x0016,	// send message
+	QQ_CMD_RECV_IM = 0x0017,	// receive message
+	QQ_CMD_REMOVE_SELF = 0x001c,	// remove self 
+	QQ_CMD_REQUEST_KEY = 0x001d,	// request key for file transfer
+	QQ_CMD_CELL_PHONE_1 = 0x0021,	// cell phone 1
+	QQ_CMD_LOGIN = 0x0022,	// login
+	QQ_CMD_GET_FRIENDS_LIST = 0x0026,	// retrieve my freinds list
+	QQ_CMD_GET_FRIENDS_ONLINE = 0x0027,	// get my online friends list
+	QQ_CMD_CELL_PHONE_2 = 0x0029,	// cell phone 2
+	QQ_CMD_GROUP_CMD = 0x0030,	// group command
+	QQ_CMD_GET_ALL_LIST_WITH_GROUP = 0x58, //by gfhuang
+	QQ_CMD_REQUEST_LOGIN_TOKEN  = 0x62, //by gfhuang
+	QQ_CMD_RECV_MSG_SYS = 0x0080,	// receive a system message
+	QQ_CMD_RECV_MSG_FRIEND_CHANGE_STATUS = 0x0081,	// friends change status
+};
+
+const gchar *qq_get_cmd_desc(gint type);
+
+const gchar *qq_get_source_str(gint source);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/im.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,795 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "conversation.h"	// GaimConeversation
+#include "debug.h"		// gaim_debug
+#include "internal.h"		// strlen, _("get_text")
+#include "cipher.h"		// md5 functions    //md5.h gfhuang
+#include "notify.h"		// gaim_notify_warning
+#include "server.h"		// serv_got_im
+#include "util.h"		// gaim_markup_find_tag, gaim_base16_decode
+
+#include "utils.h"		// gen_ip_str
+#include "packet_parse.h"	// create_packet
+#include "buddy_info.h"		// qq_send_packet_get_info
+#include "buddy_list.h"		// qq_send_packet_get_buddies_online
+#include "buddy_opt.h"		// qq_add_buddy_by_recv_packet
+#include "char_conv.h"		// qq_to_utf8, qq_encode_to_gaim
+#include "crypt.h"		// qq_crypt
+#include "group_im.h"		// qq_send_packet_group_im
+#include "header_info.h"	// cmd alias
+#include "im.h"
+#include "send_core.h"		// qq_send_cmd
+#include "send_core.h"			// qq_data
+#include "send_file.h"
+
+#define QQ_SEND_IM_REPLY_OK       0x00
+#define DEFAULT_FONT_NAME_LEN 	  4
+
+// a debug function
+static void _qq_show_packet(gchar * desc, gchar * buf, gint len);
+
+enum
+{
+        QQ_NORMAL_IM_TEXT = 0x000b,
+	QQ_NORMAL_IM_FILE_REQUEST_TCP = 0x0001,			//gfhuang
+	QQ_NORMAL_IM_FILE_APPROVE_TCP = 0x0003,
+	QQ_NORMAL_IM_FILE_REJECT_TCP = 0x0005,
+	QQ_NORMAL_IM_FILE_REQUEST_UDP = 0x0035,
+	QQ_NORMAL_IM_FILE_APPROVE_UDP = 0x0037,
+	QQ_NORMAL_IM_FILE_REJECT_UDP = 0x0039,
+	QQ_NORMAL_IM_FILE_NOTIFY = 0x003b,
+	QQ_NORMAL_IM_FILE_PASV = 0x003f,			// are you behind a firewall, gfhuang
+	QQ_NORMAL_IM_FILE_CANCEL = 0x0049,
+	QQ_NORMAL_IM_FILE_EX_REQUEST_UDP = 0x81,		//gfhuang	
+	QQ_NORMAL_IM_FILE_EX_REQUEST_ACCEPT = 0x83,		//gfhuang
+	QQ_NORMAL_IM_FILE_EX_REQUEST_CANCEL = 0x85,		//gfhuang
+	QQ_NORMAL_IM_FILE_EX_NOTIFY_IP = 0x87			//gfhuang
+};
+
+
+enum {
+	QQ_RECV_SYS_IM_KICK_OUT = 0x01,
+};
+
+/*
+guint8 send_im_tail[QQ_SEND_IM_AFTER_MSG_LEN] = {
+  0x00,       // end of msg
+  0x0a,       // font attr, size=10, no bold, no italic, no underline
+  0x00,       // font color red 00-ff
+  0x00,       // font color green
+  0x00,       // font color blue
+  0x00,       // unknown
+  0x86, 0x22, // encoding, 0x8622=GB, 0x0000=EN, define BIG5 support here
+  0xcb, 0xce, 0xcc, 0xe5, 0x0d // font name, related, not fix length
+};
+*/
+
+typedef struct _qq_recv_im_header qq_recv_im_header;
+typedef struct _qq_recv_normal_im_text qq_recv_normal_im_text;
+typedef struct _qq_recv_normal_im_common qq_recv_normal_im_common;
+typedef struct _qq_recv_normal_im_unprocessed qq_recv_normal_im_unprocessed;
+
+struct _qq_recv_normal_im_common {
+	// this is the common part of normal_text
+	guint16 sender_ver;
+	guint32 sender_uid;
+	guint32 receiver_uid;
+	guint8 *session_md5;
+	guint16 normal_im_type;
+};
+
+struct _qq_recv_normal_im_text {
+	qq_recv_normal_im_common *common;
+	// now comes the part fo text only
+	guint16 msg_seq;
+	guint32 send_time;
+	guint8 unknown1;
+	guint8 sender_icon;
+	guint8 unknown2[3];
+	guint8 is_there_font_attr;
+	guint8 unknown3[4];
+	guint8 msg_type;
+	guint8 *msg;		// no fixed length, ends with 0x00
+	guint8 *font_attr;
+	gint font_attr_len;
+};
+
+struct _qq_recv_normal_im_unprocessed {
+	qq_recv_normal_im_common *common;
+	// now comes the part of unprocessed
+	guint8 *unknown;	// no fixed length
+	gint length;
+};
+
+struct _qq_recv_im_header {
+	guint32 sender_uid;
+	guint32 receiver_uid;
+	guint32 server_im_seq;
+	guint8 sender_ip[4];
+	guint16 sender_port;
+	guint16 im_type;
+};
+
+#define QQ_SEND_IM_AFTER_MSG_HEADER_LEN 8
+#define DEFAULT_FONT_NAME "\0xcb\0xce\0xcc\0xe5"
+
+guint8 *qq_get_send_im_tail(const gchar * font_color,
+			    const gchar * font_size,
+			    const gchar * font_name,
+			    gboolean is_bold, gboolean is_italic, gboolean is_underline, guint tail_len)
+{
+	gchar *s1, *s2;
+	unsigned char *rgb;
+	guint font_name_len;
+	guint8 *send_im_tail;
+	const guint8 simsun[] = { 0xcb, 0xce, 0xcc, 0xe5 };
+
+	if (font_name) {
+		font_name_len = strlen(font_name);
+	} else {
+		font_name_len = DEFAULT_FONT_NAME_LEN;
+		font_name = simsun;
+	}
+
+	send_im_tail = g_new0(guint8, tail_len);
+
+	g_strlcpy(send_im_tail + QQ_SEND_IM_AFTER_MSG_HEADER_LEN,
+		  font_name, tail_len - QQ_SEND_IM_AFTER_MSG_HEADER_LEN);
+	send_im_tail[tail_len - 1] = tail_len;
+
+	send_im_tail[0] = 0x00;
+	if (font_size) {
+		send_im_tail[1] = (guint8) (atoi(font_size) * 3 + 1);
+	} else {
+		send_im_tail[1] = 10;
+	}
+	if (is_bold)
+		send_im_tail[1] |= 0x20;
+	if (is_italic)
+		send_im_tail[1] |= 0x40;
+	if (is_underline)
+		send_im_tail[1] |= 0x80;
+
+	if (font_color) {
+		s1 = g_strndup(font_color + 1, 6);
+		/* Henry: maybe this is a bug of gaim, the string should have
+		 * the length of odd number @_@
+		 */
+		s2 = g_strdup_printf("%sH", s1);
+//		gaim_base16_decode(s2, &rgb);  by gfhuang
+		rgb = gaim_base16_decode(s2, NULL);
+		g_free(s1);
+		g_free(s2);
+		memcpy(send_im_tail + 2, rgb, 3);
+		g_free(rgb);
+	} else {
+		send_im_tail[2] = send_im_tail[3] = send_im_tail[4] = 0;
+	}
+
+	send_im_tail[5] = 0x00;
+	send_im_tail[6] = 0x86;
+	send_im_tail[7] = 0x22;	// encoding, 0x8622=GB, 0x0000=EN, define BIG5 support here
+	_qq_show_packet("QQ_MESG", send_im_tail, tail_len);
+	return (guint8 *) send_im_tail;
+}				// qq_get_send_im_tail
+
+/*****************************************************************************/
+static const gchar *qq_get_recv_im_type_str(gint type)
+{
+	switch (type) {
+	case QQ_RECV_IM_TO_BUDDY:
+		return "QQ_RECV_IM_TO_BUDDY";
+	case QQ_RECV_IM_TO_UNKNOWN:
+		return "QQ_RECV_IM_TO_UNKNOWN";
+	case QQ_RECV_IM_UNKNOWN_QUN_IM:
+		return "QQ_RECV_IM_UNKNOWN_QUN_IM";
+	case QQ_RECV_IM_ADD_TO_QUN:
+		return "QQ_RECV_IM_ADD_TO_QUN";
+	case QQ_RECV_IM_DEL_FROM_QUN:
+		return "QQ_RECV_IM_DEL_FROM_QUN";
+	case QQ_RECV_IM_APPLY_ADD_TO_QUN:
+		return "QQ_RECV_IM_APPLY_ADD_TO_QUN";
+	case QQ_RECV_IM_CREATE_QUN:
+		return "QQ_RECV_IM_CREATE_QUN";
+	case QQ_RECV_IM_SYS_NOTIFICATION:
+		return "QQ_RECV_IM_SYS_NOTIFICATION";
+	case QQ_RECV_IM_APPROVE_APPLY_ADD_TO_QUN:
+		return "QQ_RECV_IM_APPROVE_APPLY_ADD_TO_QUN";
+	case QQ_RECV_IM_REJCT_APPLY_ADD_TO_QUN:
+		return "QQ_RECV_IM_REJCT_APPLY_ADD_TO_QUN";
+	case QQ_RECV_IM_TEMP_QUN_IM:
+		return "QQ_RECV_IM_TEMP_QUN_IM";	//gfhuang
+	case QQ_RECV_IM_QUN_IM:
+		return "QQ_RECV_IM_QUN_IM";		//gfhuang
+	default:
+		return "QQ_RECV_IM_UNKNOWN";
+	}			// switch type
+}				// qq_get_recv_im_type_str
+
+/*****************************************************************************/
+// generate a md5 key using uid and session_key
+static gchar *_gen_session_md5(gint uid, gchar * session_key)
+{
+	gchar *src, md5_str[QQ_KEY_LENGTH];
+	guint8 *cursor;
+//	md5_state_t ctx;             //gfhuang
+	GaimCipher *cipher;
+        GaimCipherContext *context;
+
+
+	src = g_newa(gchar, 20);
+	cursor = src;
+	create_packet_dw(src, &cursor, uid);
+	create_packet_data(src, &cursor, session_key, QQ_KEY_LENGTH);
+
+/*         gfhuang
+	md5_init(&ctx);
+	md5_append(&ctx, src, 20);
+	md5_finish(&ctx, (md5_byte_t *) md5_str);
+*/
+	cipher = gaim_ciphers_find_cipher("md5");
+	context = gaim_cipher_context_new(cipher, NULL);
+	gaim_cipher_context_append(context, src, 20);
+	gaim_cipher_context_digest(context, sizeof(md5_str), md5_str, NULL);
+	gaim_cipher_context_destroy(context);
+
+	return g_memdup(md5_str, QQ_KEY_LENGTH);
+}				// _gen_session_md5
+
+/*****************************************************************************/
+// when we receive a message,
+// we send an ACK which is the first 16 bytes of incoming packet
+static void _qq_send_packet_recv_im_ack(GaimConnection * gc, guint16 seq, guint8 * data) {
+	qq_send_cmd(gc, QQ_CMD_RECV_IM, FALSE, seq, FALSE, data, 16);
+}				// _qq_send_packet_recv_im_ack
+
+/*****************************************************************************/
+// read the common parts of the normal_im,
+// returns the bytes read if succeed, or -1 if there is any error
+static gint _qq_normal_im_common_read(guint8 * data, guint8 ** cursor, gint len, qq_recv_normal_im_common * common) {
+
+	gint bytes;
+	g_return_val_if_fail(data != NULL && len != 0 && common != NULL, -1);
+
+	bytes = 0;
+	// now push data into common header
+	bytes += read_packet_w(data, cursor, len, &(common->sender_ver));
+	bytes += read_packet_dw(data, cursor, len, &(common->sender_uid));
+	bytes += read_packet_dw(data, cursor, len, &(common->receiver_uid));
+
+	common->session_md5 = g_memdup(*cursor, QQ_KEY_LENGTH);
+	bytes += QQ_KEY_LENGTH;
+	*cursor += QQ_KEY_LENGTH;
+
+	bytes += read_packet_w(data, cursor, len, &(common->normal_im_type));
+
+	if (bytes != 28) {	// read common place fail
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Expect 28 bytes, read %d bytes\n", bytes);
+		return -1;
+	}			// if bytes
+
+	return bytes;
+}				// _qq_normal_im_common_read
+
+/*****************************************************************************/
+// process received normal text IM
+static void _qq_process_recv_normal_im_text
+    (guint8 * data, guint8 ** cursor, gint len, qq_recv_normal_im_common * common, GaimConnection * gc) {
+	guint16 gaim_msg_type;
+	gchar *name;
+	gchar *msg_with_gaim_smiley;
+	gchar *msg_utf8_encoded;
+	qq_data *qd;
+	qq_recv_normal_im_text *im_text;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL && common != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	// now it is QQ_NORMAL_IM_TEXT
+	if (*cursor >= (data + len - 1)) {
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Received normal IM text is empty\n");
+		return;
+	} else
+		im_text = g_newa(qq_recv_normal_im_text, 1);
+
+	im_text->common = common;
+
+	// push data into im_text
+	read_packet_w(data, cursor, len, &(im_text->msg_seq));
+	read_packet_dw(data, cursor, len, &(im_text->send_time));
+	read_packet_b(data, cursor, len, &(im_text->unknown1));
+	read_packet_b(data, cursor, len, &(im_text->sender_icon));
+	read_packet_data(data, cursor, len, (guint8 *) & (im_text->unknown2), 3);
+	read_packet_b(data, cursor, len, &(im_text->is_there_font_attr));
+	//////////////////////
+	// from lumaqq	for unknown3, gfhuang
+	//	totalFragments = buf.get() & 255;
+        //	fragmentSequence = buf.get() & 255;
+        //	messageId = buf.getChar();
+	read_packet_data(data, cursor, len, (guint8 *) & (im_text->unknown3), 4);
+	read_packet_b(data, cursor, len, &(im_text->msg_type));
+
+	// we need to check if this is auto-reply
+	// QQ2003iii build 0304, returns the msg without font_attr
+	// even the is_there_font_attr shows 0x01, and msg does not ends with 0x00
+	if (im_text->msg_type == QQ_IM_AUTO_REPLY) {
+		im_text->is_there_font_attr = 0x00;	// indeed there is no this flag
+		im_text->msg = g_strndup(*cursor, data + len - *cursor);
+	} else {		// it is normal mesasge
+		if (im_text->is_there_font_attr) {
+			im_text->msg = g_strdup(*cursor);
+			*cursor += strlen(im_text->msg) + 1;
+			im_text->font_attr_len = data + len - *cursor;
+			im_text->font_attr = g_memdup(*cursor, im_text->font_attr_len);
+		} else		// not im_text->is_there_font_attr
+			im_text->msg = g_strndup(*cursor, data + len - *cursor);
+	}			// if im_text->msg_type
+	_qq_show_packet("QQ_MESG recv", data, *cursor - data);
+
+	name = uid_to_gaim_name(common->sender_uid);
+	if (gaim_find_buddy(gc->account, name) == NULL)
+		qq_add_buddy_by_recv_packet(gc, common->sender_uid, FALSE, TRUE);
+
+	gaim_msg_type = (im_text->msg_type == QQ_IM_AUTO_REPLY) ? GAIM_MESSAGE_AUTO_RESP : 0;
+
+	msg_with_gaim_smiley = qq_smiley_to_gaim(im_text->msg);
+	msg_utf8_encoded = im_text->is_there_font_attr ?
+	    qq_encode_to_gaim(im_text->font_attr,
+			      im_text->font_attr_len,
+			      msg_with_gaim_smiley) : qq_to_utf8(msg_with_gaim_smiley, QQ_CHARSET_DEFAULT);
+
+	// send encoded to gaim, note that we use im_text->send_time,
+	// not the time we receive the message
+	// as it may have been dealyed when I am not online.
+	serv_got_im(gc, name, msg_utf8_encoded, gaim_msg_type, (time_t) im_text->send_time);
+
+	g_free(msg_utf8_encoded);
+	g_free(msg_with_gaim_smiley);
+	g_free(name);
+	g_free(im_text->msg);
+	if (im_text->is_there_font_attr)
+		g_free(im_text->font_attr);
+}				// _qq_process_recv_normal_im_text
+
+/*****************************************************************************/
+// it is a normal IM, maybe text or video request
+static void _qq_process_recv_normal_im(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc)
+{
+	gint bytes;
+	qq_recv_normal_im_common *common;
+	qq_recv_normal_im_unprocessed *im_unprocessed;
+
+	g_return_if_fail (data != NULL && len != 0);
+
+	if (*cursor >= (data + len - 1)) {
+		gaim_debug (GAIM_DEBUG_WARNING, "QQ",
+			    "Received normal IM is empty\n");
+		return;
+	}
+	else
+		common = g_newa (qq_recv_normal_im_common, 1);
+
+	bytes = _qq_normal_im_common_read (data, cursor, len, common);
+	if (bytes < 0) {
+		gaim_debug (GAIM_DEBUG_ERROR, "QQ",
+			    "Fail read the common part of normal IM\n");
+		return;
+	}			// if bytes
+
+	switch (common->normal_im_type) {
+	case QQ_NORMAL_IM_TEXT:
+		gaim_debug (GAIM_DEBUG_INFO,
+			    "QQ",
+			    "Normal IM, text type:\n [%d] => [%d], src: %s\n",
+			    common->sender_uid, common->receiver_uid,
+			    qq_get_source_str (common->sender_ver));
+		_qq_process_recv_normal_im_text (data, cursor, len, common,
+						 gc);
+		break;
+	case QQ_NORMAL_IM_FILE_REJECT_UDP:
+		qq_process_recv_file_reject (data, cursor, len,
+					     common->sender_uid, gc);
+		break;
+	case QQ_NORMAL_IM_FILE_APPROVE_UDP:
+		qq_process_recv_file_accept (data, cursor, len,
+					     common->sender_uid, gc);
+		break;
+	case QQ_NORMAL_IM_FILE_REQUEST_UDP:
+		qq_process_recv_file_request (data, cursor, len,
+					      common->sender_uid, gc);
+		break;
+	case QQ_NORMAL_IM_FILE_CANCEL:
+		qq_process_recv_file_cancel (data, cursor, len,
+					     common->sender_uid, gc);
+		break;
+	case QQ_NORMAL_IM_FILE_NOTIFY:
+		qq_process_recv_file_notify (data, cursor, len,
+				common->sender_uid, gc);
+		break;
+	default:
+		im_unprocessed = g_newa (qq_recv_normal_im_unprocessed, 1);
+		im_unprocessed->common = common;
+		im_unprocessed->unknown = *cursor;
+		im_unprocessed->length = data + len - *cursor;
+		//  a simple process here, maybe more later
+		gaim_debug (GAIM_DEBUG_WARNING, "QQ",
+			    "Normal IM, unprocessed type [0x%04x]\n",
+			    common->normal_im_type);
+		gaim_debug (GAIM_DEBUG_WARNING, "QQ",
+			    "Dump unknown part.\n%s",
+			    hex_dump_to_str (im_unprocessed->unknown,
+					     im_unprocessed->length));
+		g_free (common->session_md5);
+		return;
+	}			// normal_im_type
+
+	g_free (common->session_md5);
+}				// qq_process_recv_normal_im
+
+
+/*
+static void _qq_process_recv_normal_im(guint8 * data, guint8 ** cursor, gint len, GaimConnection * gc) {
+	gint bytes;
+	qq_recv_normal_im_common *common;
+	qq_recv_normal_im_unprocessed *im_unprocessed;
+
+	g_return_if_fail(data != NULL && len != 0);
+
+	if (*cursor >= (data + len - 1)) {
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Received normal IM is empty\n");
+		return;
+	} else
+		common = g_newa(qq_recv_normal_im_common, 1);
+
+	bytes = _qq_normal_im_common_read(data, cursor, len, common);
+	if (bytes < 0) {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail read the common part of normal IM\n");
+		return;
+	}			// if bytes
+
+	if (common->normal_im_type != QQ_NORMAL_IM_TEXT) {
+		im_unprocessed = g_newa(qq_recv_normal_im_unprocessed, 1);
+		im_unprocessed->common = common;
+		im_unprocessed->unknown = *cursor;
+		im_unprocessed->length = data + len - *cursor;
+		// a simple process here, maybe more later
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Normal IM, unprocessed type [0x%04x]\n", common->normal_im_type);
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ",
+			   "Dump unknown part.\n%s", hex_dump_to_str(im_unprocessed->unknown, im_unprocessed->length));
+		g_free(common->session_md5);
+		return;
+	} else {		// normal_im_type ==  QQ_NORMAL_IM_TEXT
+		gaim_debug(GAIM_DEBUG_INFO,
+			   "QQ",
+			   "Normal IM, text type:\n [%d] => [%d], src: %s\n",
+			   common->sender_uid, common->receiver_uid, qq_get_source_str(common->sender_ver));
+		_qq_process_recv_normal_im_text(data, cursor, len, common, gc);
+	}			// normal_im_type
+
+	g_free(common->session_md5);
+}				// qq_process_recv_normal_im
+*/
+
+/*****************************************************************************/
+// process im from system administrator
+static void _qq_process_recv_sys_im(guint8 * data, guint8 ** cursor, gint data_len, GaimConnection * gc) {
+	gint len;
+	guint8 reply;
+	gchar **segments, *msg_utf8;
+
+	g_return_if_fail(gc != NULL && data != NULL && data_len != 0);
+
+	if (*cursor >= (data + data_len - 1)) {
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Received sys IM is empty\n");
+		return;
+	}
+
+	len = data + data_len - *cursor;
+
+	if (NULL == (segments = split_data(*cursor, len, "\x2f", 2)))
+		return;
+
+	reply = strtol(segments[0], NULL, 10);
+	if (reply == QQ_RECV_SYS_IM_KICK_OUT)
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "We are kicked out by QQ server\n");
+	msg_utf8 = qq_to_utf8(segments[1], QQ_CHARSET_DEFAULT);
+	gaim_notify_warning(gc, NULL, _("System Message"), msg_utf8);
+
+}				// _qq_process_recv_sys_im
+
+/*****************************************************************************/
+// send an IM to to_uid
+void qq_send_packet_im(GaimConnection * gc, guint32 to_uid, gchar * msg, gint type) {
+	qq_data *qd;
+	guint8 *cursor, *raw_data;
+	guint16 client_tag, normal_im_type;
+	gint msg_len, raw_len, bytes;
+	time_t now;
+	gchar *md5, *msg_filtered;
+	GData *attribs;
+	gchar *font_size = NULL, *font_color = NULL, *font_name = NULL, *tmp;
+	gboolean is_bold = FALSE, is_italic = FALSE, is_underline = FALSE;
+	const gchar *start, *end, *last;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+	client_tag = QQ_CLIENT;
+	normal_im_type = QQ_NORMAL_IM_TEXT;
+
+	last = msg;
+	while (gaim_markup_find_tag("font", last, &start, &end, &attribs)) {
+		tmp = g_datalist_get_data(&attribs, "size");
+		if (tmp) {
+			if (font_size)
+				g_free(font_size);
+			font_size = g_strdup(tmp);
+		}
+		tmp = g_datalist_get_data(&attribs, "color");
+		if (tmp) {
+			if (font_color)
+				g_free(font_color);
+			font_color = g_strdup(tmp);
+		}
+		tmp = g_datalist_get_data(&attribs, "face");
+		if (tmp) {
+			if (font_name)
+				g_free(font_name);
+			font_name = g_strdup(tmp);
+		}
+
+		g_datalist_clear(&attribs);
+		last = end + 1;
+	}
+
+	if (gaim_markup_find_tag("b", msg, &start, &end, &attribs)) {
+		is_bold = TRUE;
+		g_datalist_clear(&attribs);
+	}
+
+	if (gaim_markup_find_tag("i", msg, &start, &end, &attribs)) {
+		is_italic = TRUE;
+		g_datalist_clear(&attribs);
+	}
+
+	if (gaim_markup_find_tag("u", msg, &start, &end, &attribs)) {
+		is_underline = TRUE;
+		g_datalist_clear(&attribs);
+	}
+
+	gaim_debug(GAIM_DEBUG_INFO, "QQ_MESG", "send mesg: %s\n", msg);
+	msg_filtered = gaim_markup_strip_html(msg);
+	msg_len = strlen(msg_filtered);
+	now = time(NULL);
+	md5 = _gen_session_md5(qd->uid, qd->session_key);
+
+	guint font_name_len, tail_len;
+	font_name_len = (font_name) ? strlen(font_name) : DEFAULT_FONT_NAME_LEN;
+	tail_len = font_name_len + QQ_SEND_IM_AFTER_MSG_HEADER_LEN + 1;
+
+	raw_len = QQ_SEND_IM_BEFORE_MSG_LEN + msg_len + tail_len;
+	raw_data = g_newa(guint8, raw_len);
+	cursor = raw_data;
+	bytes = 0;
+
+	//000-003: receiver uid
+	bytes += create_packet_dw(raw_data, &cursor, qd->uid);
+	//004-007: sender uid
+	bytes += create_packet_dw(raw_data, &cursor, to_uid);
+	//008-009: sender client version
+	bytes += create_packet_w(raw_data, &cursor, client_tag);
+	//010-013: receiver uid
+	bytes += create_packet_dw(raw_data, &cursor, qd->uid);
+	//014-017: sender uid
+	bytes += create_packet_dw(raw_data, &cursor, to_uid);
+	//018-033: md5 of (uid+session_key)
+	bytes += create_packet_data(raw_data, &cursor, md5, 16);
+	//034-035: message type
+	bytes += create_packet_w(raw_data, &cursor, normal_im_type);
+	//036-037: sequence number
+	bytes += create_packet_w(raw_data, &cursor, qd->send_seq);
+	//038-041: send time
+	bytes += create_packet_dw(raw_data, &cursor, (guint32) now);
+	//042-042: always 0x00
+	bytes += create_packet_b(raw_data, &cursor, 0x00);
+	//043-043: sender icon
+	bytes += create_packet_b(raw_data, &cursor, qd->my_icon);
+	//044-046: always 0x00
+	bytes += create_packet_w(raw_data, &cursor, 0x0000);
+	bytes += create_packet_b(raw_data, &cursor, 0x00);
+	//047-047: we use font attr
+	bytes += create_packet_b(raw_data, &cursor, 0x01);
+	//048-051: always 0x00
+	bytes += create_packet_dw(raw_data, &cursor, 0x00000000);
+	//052-052: text message type (normal/auto-reply)
+	bytes += create_packet_b(raw_data, &cursor, type);
+	//053-   : msg ends with 0x00
+	bytes += create_packet_data(raw_data, &cursor, msg_filtered, msg_len);
+	guint8 *send_im_tail = qq_get_send_im_tail(font_color, font_size, font_name, is_bold,
+						   is_italic, is_underline, tail_len);
+	_qq_show_packet("QQ_MESG debug", send_im_tail, tail_len);
+	bytes += create_packet_data(raw_data, &cursor, (gchar *) send_im_tail, tail_len);
+
+	_qq_show_packet("QQ_MESG raw", raw_data, cursor - raw_data);
+
+	if (bytes == raw_len)	// create packet OK
+		qq_send_cmd(gc, QQ_CMD_SEND_IM, TRUE, 0, TRUE, raw_data, cursor - raw_data);
+	else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+			   "Fail creating send_im packet, expect %d bytes, build %d bytes\n", raw_len, bytes);
+
+	if (font_color)
+		g_free(font_color);
+	if (font_size)
+		g_free(font_size);
+	g_free(send_im_tail);
+	g_free(msg_filtered);
+}				// qq_send_packet_im
+
+/*****************************************************************************/
+// parse the reply to send_im
+void qq_process_send_im_reply(guint8 * buf, gint buf_len, GaimConnection * gc)
+{
+	qq_data *qd;
+	gint len;
+	guint8 *data, *cursor, reply;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	g_return_if_fail(buf != NULL && buf_len != 0);
+
+	qd = gc->proto_data;
+	len = buf_len;
+	data = g_newa(guint8, len);
+
+	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+		cursor = data;
+		read_packet_b(data, &cursor, len, &reply);
+		if (reply != QQ_SEND_IM_REPLY_OK) {
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Send IM fail\n");
+			gaim_notify_error(gc, _("Server ACK"), _("Send IM fail\n"), NULL);
+//			serv_got_im(gc, name, "Server ACK: Send IM fail\n", GAIM_MESSAGE_ERROR, time(NULL));
+		}
+		else
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "IM ACK OK\n");
+	} else {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt send im reply\n");
+	}
+
+}				// qq_process_send_im_reply
+
+/*****************************************************************************/
+// I receive a message, mainly it is text msg,
+// but we need to proess other types (group etc)
+void qq_process_recv_im(guint8 * buf, gint buf_len, guint16 seq, GaimConnection * gc) {
+	qq_data *qd;
+	gint len, bytes;
+	guint8 *data, *cursor;
+	qq_recv_im_header *im_header;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	g_return_if_fail(buf != NULL && buf_len != 0);
+
+	qd = (qq_data *) gc->proto_data;
+	len = buf_len;
+	data = g_newa(guint8, len);
+
+	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+		if (len < 16) {	// we need to ack with the first 16 bytes
+			gaim_debug(GAIM_DEBUG_ERROR, "QQ", "IM is too short\n");
+			return;
+		} else
+			_qq_send_packet_recv_im_ack(gc, seq, data);
+
+		cursor = data;
+		bytes = 0;
+		im_header = g_newa(qq_recv_im_header, 1);
+		bytes += read_packet_dw(data, &cursor, len, &(im_header->sender_uid));
+		bytes += read_packet_dw(data, &cursor, len, &(im_header->receiver_uid));
+		bytes += read_packet_dw(data, &cursor, len, &(im_header->server_im_seq));
+		// if the message is delivered via server, it is server IP/port
+		bytes += read_packet_data(data, &cursor, len, (guint8 *) & (im_header->sender_ip), 4);
+		bytes += read_packet_w(data, &cursor, len, &(im_header->sender_port));
+		bytes += read_packet_w(data, &cursor, len, &(im_header->im_type));
+
+		if (bytes != 20) {	// length of im_header
+			gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+				   "Fail read recv IM header, expect 20 bytes, read %d bytes\n", bytes);
+			return;
+		}		// if bytes
+
+		if (im_header->receiver_uid != qd->uid) {	// should not happen
+			gaim_debug(GAIM_DEBUG_ERROR, "QQ", "IM to [%d], NOT me\n", im_header->receiver_uid);
+			return;
+		}		// if im_header->receiver_uid
+
+		switch (im_header->im_type) {
+		case QQ_RECV_IM_TO_BUDDY:
+			gaim_debug(GAIM_DEBUG_INFO, "QQ",
+				   "IM from buddy [%d], I am in his/her buddy list\n", im_header->sender_uid);
+			_qq_process_recv_normal_im(data, &cursor, len, gc);
+			break;
+		case QQ_RECV_IM_TO_UNKNOWN:
+			gaim_debug(GAIM_DEBUG_INFO, "QQ",
+				   "IM from buddy [%d], I am a stranger to him/her\n", im_header->sender_uid);
+			_qq_process_recv_normal_im(data, &cursor, len, gc);
+			break;
+		case QQ_RECV_IM_UNKNOWN_QUN_IM:
+		case QQ_RECV_IM_TEMP_QUN_IM:
+		case QQ_RECV_IM_QUN_IM:
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "IM from group, internal_id [%d]\n", im_header->sender_uid);
+			// sender_uid is in fact internal_group_id
+			qq_process_recv_group_im(data, &cursor, len, im_header->sender_uid, gc, im_header->im_type);
+			break;
+		case QQ_RECV_IM_ADD_TO_QUN:
+			gaim_debug(GAIM_DEBUG_INFO, "QQ",
+				   "IM from group, added by group internal_id [%d]\n", im_header->sender_uid);
+			// sender_uid is in fact internal_group_id
+			// we need this to create a dummy group and add to blist
+			qq_process_recv_group_im_been_added(data, &cursor, len, im_header->sender_uid, gc);
+			break;
+		case QQ_RECV_IM_DEL_FROM_QUN:
+			gaim_debug(GAIM_DEBUG_INFO, "QQ",
+				   "IM from group, removed by group internal_ID [%d]\n", im_header->sender_uid);
+			// sender_uid is in fact internal_group_id
+			qq_process_recv_group_im_been_removed(data, &cursor, len, im_header->sender_uid, gc);
+			break;
+		case QQ_RECV_IM_APPLY_ADD_TO_QUN:
+			gaim_debug(GAIM_DEBUG_INFO, "QQ",
+				   "IM from group, apply to join group internal_ID [%d]\n", im_header->sender_uid);
+			// sender_uid is in fact internal_group_id
+			qq_process_recv_group_im_apply_join(data, &cursor, len, im_header->sender_uid, gc);
+			break;
+		case QQ_RECV_IM_APPROVE_APPLY_ADD_TO_QUN:
+			gaim_debug(GAIM_DEBUG_INFO, "QQ",
+				   "IM for group system info, approved by group internal_id [%d]\n",
+				   im_header->sender_uid);
+			// sender_uid is in fact internal_group_id
+			qq_process_recv_group_im_been_approved(data, &cursor, len, im_header->sender_uid, gc);
+			break;
+		case QQ_RECV_IM_REJCT_APPLY_ADD_TO_QUN:
+			gaim_debug(GAIM_DEBUG_INFO, "QQ",
+				   "IM for group system info, rejected by group internal_id [%d]\n",
+				   im_header->sender_uid);
+			// sender_uid is in fact internal_group_id
+			qq_process_recv_group_im_been_rejected(data, &cursor, len, im_header->sender_uid, gc);
+			break;
+		case QQ_RECV_IM_SYS_NOTIFICATION:
+			gaim_debug(GAIM_DEBUG_INFO, "QQ",
+				   "IM from [%d], should be a system administrator\n", im_header->sender_uid);
+			_qq_process_recv_sys_im(data, &cursor, len, gc);
+			break;
+		default:
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ",
+				   "IM from [%d], [0x%02x] %s is not processed\n",
+				   im_header->sender_uid,
+				   im_header->im_type, qq_get_recv_im_type_str(im_header->im_type));
+		}		// switch
+	} else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt rev im\n");
+
+}				// qq_process_recv_im
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/im.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,68 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_IM_H_
+#define _QQ_IM_H_
+
+#include <glib.h>
+#include "connection.h"		// GaimConnection
+#include "group.h"		// qq_group
+
+#define QQ_MSG_IM_MAX               500	// max length of IM
+#define QQ_SEND_IM_BEFORE_MSG_LEN   53
+#define QQ_SEND_IM_AFTER_MSG_LEN    13	// there is one 0x00 at the end
+
+enum {
+	QQ_IM_TEXT = 0x01,
+	QQ_IM_AUTO_REPLY = 0x02,
+};
+
+
+enum {
+	QQ_RECV_IM_TO_BUDDY = 0x0009,
+	QQ_RECV_IM_TO_UNKNOWN = 0x000a,
+	QQ_RECV_IM_UNKNOWN_QUN_IM = 0x0020,     //gfhuang
+	QQ_RECV_IM_ADD_TO_QUN = 0x0021,
+	QQ_RECV_IM_DEL_FROM_QUN = 0x0022,
+	QQ_RECV_IM_APPLY_ADD_TO_QUN = 0x0023,
+	QQ_RECV_IM_APPROVE_APPLY_ADD_TO_QUN = 0x0024,
+	QQ_RECV_IM_REJCT_APPLY_ADD_TO_QUN = 0x0025,
+	QQ_RECV_IM_CREATE_QUN = 0x0026,
+	QQ_RECV_IM_TEMP_QUN_IM = 0x002A,        //gfhuang
+	QQ_RECV_IM_QUN_IM = 0x002B,             //gfhuang
+	QQ_RECV_IM_SYS_NOTIFICATION = 0x0030,
+};
+
+guint8 *qq_get_send_im_tail(const gchar * font_color,
+			    const gchar * font_size,
+			    const gchar * font_name,
+			    gboolean is_bold, gboolean is_italic, gboolean is_underline, guint len);
+
+void qq_send_packet_im(GaimConnection * gc, guint32 to_uid, gchar * msg, gint type);
+void qq_process_recv_im(guint8 * buf, gint buf_len, guint16 seq, GaimConnection * gc);
+void qq_process_send_im_reply(guint8 * buf, gint buf_len, GaimConnection * gc);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/infodlg.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,1127 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "debug.h"		// gaim_debug
+#include "internal.h"		// strlen, _("get_text")
+#include "notify.h"		// gaim_notify_error
+
+#include "utils.h"		// get_face_gdkpixbuf
+#include "char_conv.h"		// qq_to_utf8
+#include "show.h"		// qq_show_get_image
+#include "infodlg.h"
+
+// must be defined after qq_infodlg.h to have <gtk/gtk.h>
+#include "gtkdialogs.h"		// GAIM_DIALOG, mainwindow
+
+
+// the follwoing definition is in UTF-8
+const gint blood_types_count = 5;
+const gchar *blood_types[] = {
+	"其它", "A", "B", "O", "AB",
+};
+
+const gint country_names_count = 6;
+const gchar *country_names[] = {
+	"中国", "中国香港", "中国澳门", "中国å°æ¹¾",
+	"新加å¡", "马æ¥è¥¿äºš", "美国",
+};
+
+const gint province_names_count = 34;
+const gchar *province_names[] = {
+	"北京", "天津", "上海", "é‡åº†", "香港",
+	"河北", "山西", "内蒙å¤", "è¾½å®", "å‰æž—",
+	"黑龙江", "江西", "浙江", "江è‹", "安徽",
+	"ç¦å»º", "山东", "æ²³å—", "湖北", "æ¹–å—",
+	"广东", "广西", "æµ·å—", "å››å·", "贵州",
+	"云å—", "西è—", "陕西", "甘肃", "å®å¤",
+	"é’æµ·", "æ–°ç–†", "å°æ¹¾", "澳门",
+};
+
+const gint zodiac_names_count = 13;
+const gchar *zodiac_names[] = {
+	"-", "鼠", "牛", "虎", "兔",
+	"龙", "蛇", "马", "羊", "猴",
+	"鸡", "狗", "猪",
+};
+
+const gint horoscope_names_count = 13;
+const gchar *horoscope_names[] = {
+	"-", "水瓶座", "åŒé±¼åº§", "牡羊座", "金牛座",
+	"åŒå­åº§", "巨蟹座", "ç‹®å­åº§", "处女座", "天秤座",
+	"天èŽåº§", "射手座", "魔羯座",
+};
+
+const gint occupation_names_count = 15;
+const gchar *occupation_names[] = {
+	"å…¨èŒ", "å…¼èŒ", "制造业", "商业", "失业中",
+	"学生", "工程师", "政府部门", "教育业", "æœåŠ¡è¡Œä¸š",
+	"è€æ¿", "计算机业", "退休", "金èžä¸š",
+	"销售/广告/市场",
+};
+
+enum {
+	QQ_CONTACT_OPEN = 0x00,
+	QQ_CONTACT_ONLY_FRIENDS = 0x01,
+	QQ_CONTACT_CLOSE = 0x02,
+};
+
+
+enum {
+	QQ_AUTH_NO_AUTH = 0x00,
+	QQ_AUTH_NEED_AUTH = 0x01,
+	QQ_AUTH_NO_ADD = 0x02,
+};
+
+typedef struct _change_icon_widgets change_icon_widgets;
+
+struct _change_icon_widgets {
+	GtkWidget *dialog;	// dialog that shows all icons
+	GtkWidget *face;	// the image widget we are going to change
+};
+
+/*****************************************************************************/
+static void _window_deleteevent(GtkWidget * widget, GdkEvent * event, gpointer data) {
+	gtk_widget_destroy(widget);	// this will call _window_destroy
+}				// _window_deleteevent
+
+/*****************************************************************************/
+static void _window_close(GtkWidget * widget, gpointer data)
+{
+	// this will call _info_window_destroy if it is info-window
+	gtk_widget_destroy(GTK_WIDGET(data));
+}				// _window_close
+
+/*****************************************************************************/
+static void _no_edit(GtkWidget * w)
+{
+	gtk_editable_set_editable(GTK_EDITABLE(w), FALSE);
+}				// _no_edit
+
+/*****************************************************************************/
+static GtkWidget *_qq_entry_new()
+{
+	GtkWidget *entry;
+	entry = gtk_entry_new();
+	gtk_entry_set_max_length(GTK_ENTRY(entry), 255);	// set the max length
+	return entry;
+}				// _qq_entry_new
+
+/*****************************************************************************/
+static void _qq_set_entry(GtkWidget * entry, gchar * text)
+{
+	gchar *text_utf8;
+
+	text_utf8 = qq_to_utf8(text, QQ_CHARSET_DEFAULT);
+	gtk_entry_set_text(GTK_ENTRY(entry), text_utf8);
+
+	g_free(text_utf8);
+}				// _qq_set_entry
+
+/*****************************************************************************/
+static gchar *_qq_get_entry(GtkWidget * entry)
+{
+	return utf8_to_qq(gtk_entry_get_text(GTK_ENTRY(entry)), QQ_CHARSET_DEFAULT);
+}				// _qq_get_entry
+
+/*****************************************************************************/
+static void _qq_set_text(GtkWidget * entry, gchar * text)
+{
+	gchar *text_utf8;
+
+	text_utf8 = qq_to_utf8(text, QQ_CHARSET_DEFAULT);
+	gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)), text_utf8, -1);
+
+	g_free(text_utf8);
+}				// _qq_set_text
+
+/*****************************************************************************/
+static gchar *_qq_get_text(GtkWidget * entry)
+{
+	gchar *str, *ret;
+	GtkTextIter start, end;
+
+	gtk_text_buffer_get_bounds(gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)), &start, &end);
+	str = gtk_text_buffer_get_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)), &start, &end, FALSE);
+	ret = utf8_to_qq(str, QQ_CHARSET_DEFAULT);
+
+	return ret;
+}				// _qq_get_text
+
+/*****************************************************************************/
+static void _qq_set_image(GtkWidget * entry, gint index)
+{
+	GdkPixbuf *pixbuf;
+
+	g_return_if_fail(entry != NULL && index >= 0);
+
+	pixbuf = get_face_gdkpixbuf(index);
+	gtk_image_set_from_pixbuf(GTK_IMAGE(entry), pixbuf);
+	g_object_unref(pixbuf);
+	g_object_set_data(G_OBJECT(entry), "user_data", GINT_TO_POINTER(index));
+}				// _qq_set_image
+
+
+/*****************************************************************************/
+static void _qq_change_face(GtkWidget * w, gpointer * user_data)
+{
+	gint index;
+	change_icon_widgets *change_icon;
+
+	change_icon = (change_icon_widgets *) user_data;
+
+	index = (gint) g_object_get_data(G_OBJECT(w), "user_data");
+
+	_qq_set_image(change_icon->face, index);
+	_window_close(NULL, change_icon->dialog);
+
+	g_free(change_icon);
+
+}				// _qq_change_face
+
+/*****************************************************************************/
+static GList *_get_list_by_array(gchar ** array, gint size)
+{
+	gint i;
+	GList *cbitems;
+
+	cbitems = NULL;
+	for (i = 0; i < size; i++)
+		cbitems = g_list_append(cbitems, array[i]);
+	return cbitems;
+}				// _get_list_by_array
+
+/*****************************************************************************/
+static void _qq_set_open_contact_radio(contact_info_window * info_window, gchar * is_open_contact) {
+	gint open;
+	GtkWidget **radio;
+
+	open = atoi(is_open_contact);
+	radio = info_window->open_contact_radio;
+
+	if (open == QQ_CONTACT_OPEN)
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio[0]), TRUE);
+	else if (open == QQ_CONTACT_ONLY_FRIENDS)
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio[1]), TRUE);
+	else
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio[2]), TRUE);
+}				// _qq_set_open_contact_radio
+
+/*****************************************************************************/
+static void _qq_set_auth_type_radio(contact_info_window * info_window, gchar * auth_type_str) {
+	gint auth_type;
+	GtkWidget **radio;
+
+	auth_type = atoi(auth_type_str);
+	radio = info_window->auth_radio;
+
+	if (auth_type == QQ_AUTH_NO_AUTH)
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio[0]), TRUE);
+	else if (auth_type == QQ_AUTH_NEED_AUTH)
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio[1]), TRUE);
+	else
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio[2]), TRUE);
+}				// _qq_set_auth_type_radio
+
+/*****************************************************************************/
+static void _info_window_destroy(GtkWidget * widget, gpointer data)
+{
+	GList *list;
+	qq_data *qd;
+	GaimConnection *gc;
+	contact_info_window *info_window;
+
+	gc = (GaimConnection *) data;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	if (QQ_DEBUG)
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "Destroy info window.\n");
+	qd = (qq_data *) gc->proto_data;
+	list = qd->contact_info_window;
+
+	while (list) {
+		info_window = (contact_info_window *) (list->data);
+		if (info_window->window != widget)
+			list = list->next;
+		else {
+			if (info_window->old_info)
+				g_strfreev((gchar **) info_window->old_info);
+			qd->contact_info_window = g_list_remove(qd->contact_info_window, info_window);
+			g_free(info_window);
+			break;
+		}
+	}			// while
+}				// _info_window_destory
+
+/*****************************************************************************/
+void qq_contact_info_window_free(qq_data * qd)
+{
+	gint i;
+	contact_info_window *info;
+
+	g_return_if_fail(qd != NULL);
+
+	i = 0;
+	while (qd->contact_info_window) {
+		info = (contact_info_window *) qd->contact_info_window->data;
+		// remove the entry before free it
+		// in some rare cases, system might automatically (stupidly) 
+		// remove the entry from the list when user free the entry, 
+		// then, qd->contact_info_window becomes NULL and it causes crash
+		qd->contact_info_window = g_list_remove(qd->contact_info_window, info);
+		if (info->window)
+			gtk_widget_destroy(info->window);
+		g_free(info);
+		i++;
+	}
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d conatct_info_window are freed\n", i);
+}				// qq_contact_info_window_free
+
+/*****************************************************************************/
+static void _info_window_refresh(GtkWidget * widget, gpointer data)
+{
+	GaimConnection *gc;
+	contact_info_window *info_window;
+
+	gc = (GaimConnection *) data;
+	info_window = (contact_info_window *)
+	    g_object_get_data(G_OBJECT(widget), "user_data");
+
+	qq_send_packet_get_info(gc, info_window->uid, TRUE);
+	gtk_widget_set_sensitive(info_window->refresh_button, FALSE);
+}				// _info_window_refresh
+
+/*****************************************************************************/
+static void _info_window_change(GtkWidget * widget, gpointer data)
+{
+	gchar *passwd[3];
+	gboolean is_modify_passwd;
+	guint8 icon, auth_type, open_contact;
+	qq_data *qd;
+	GaimAccount *a;
+	GaimConnection *gc;
+	contact_info *info;
+	contact_info_window *info_window;
+
+	gc = (GaimConnection *) data;
+	a = gc->account;
+	qd = gc->proto_data;
+	info_window = (contact_info_window *)
+	    g_object_get_data(G_OBJECT(widget), "user_data");
+	info = info_window->old_info;
+
+	is_modify_passwd = FALSE;
+	passwd[0] = passwd[1] = passwd[2] = NULL;
+
+	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(info_window->is_modify_passwd)))
+		is_modify_passwd = TRUE;
+
+	if (is_modify_passwd) {
+		passwd[0] = _qq_get_entry(info_window->old_password);
+		passwd[1] = _qq_get_entry(info_window->password[0]);
+		passwd[2] = _qq_get_entry(info_window->password[1]);
+
+		if (g_ascii_strcasecmp(a->password, passwd[0])) {
+			gaim_notify_error(gc, _("Error"), _("Old password is wrong"), NULL);
+			g_free(passwd[0]);
+			g_free(passwd[1]);
+			g_free(passwd[2]);
+			return;
+		}
+		if (passwd[1][0] == '\0') {
+			gaim_notify_error(gc, _("Error"), _("Password can not be empty"), NULL);
+			g_free(passwd[0]);
+			g_free(passwd[1]);
+			g_free(passwd[2]);
+			return;
+		}
+		if (g_ascii_strcasecmp(passwd[1], passwd[2])) {
+			gaim_notify_error(gc, _("Error"),
+					  _("Confirmed password is not the same as new password"), NULL);
+			g_free(passwd[0]);
+			g_free(passwd[1]);
+			g_free(passwd[2]);
+			return;
+		}
+		if (strlen(passwd[1]) > 16) {
+			gaim_notify_error(gc, _("Error"), _("Password can not longer than 16 characters"), NULL);
+			g_free(passwd[0]);
+			g_free(passwd[1]);
+			g_free(passwd[2]);
+			return;
+		}
+	}			// if (is_modify_passwd)
+
+	icon = (gint) g_object_get_data(G_OBJECT(info_window->face), "user_data");
+
+	info->face = g_strdup_printf("%d", icon);
+	info->nick = _qq_get_entry(info_window->nick);
+	info->age = _qq_get_entry(info_window->age);
+	info->gender = _qq_get_entry(info_window->gender);
+	info->country = _qq_get_entry(info_window->country);
+	info->province = _qq_get_entry(info_window->province);
+	info->city = _qq_get_entry(info_window->city);
+	info->email = _qq_get_entry(info_window->email);
+	info->address = _qq_get_entry(info_window->address);
+	info->zipcode = _qq_get_entry(info_window->zipcode);
+	info->tel = _qq_get_entry(info_window->tel);
+	info->name = _qq_get_entry(info_window->name);
+	info->college = _qq_get_entry(info_window->college);
+	info->occupation = _qq_get_entry(info_window->occupation);
+	info->homepage = _qq_get_entry(info_window->homepage);
+	info->intro = _qq_get_text(info_window->intro);
+
+	info->blood =
+	    get_index_str_by_name((gchar **) blood_types,
+				  gtk_entry_get_text(GTK_ENTRY(info_window->blood)), blood_types_count);
+	info->zodiac =
+	    get_index_str_by_name((gchar **) zodiac_names,
+				  gtk_entry_get_text(GTK_ENTRY(info_window->zodiac)), zodiac_names_count);
+	info->horoscope =
+	    get_index_str_by_name((gchar **) horoscope_names,
+				  gtk_entry_get_text(GTK_ENTRY(info_window->horoscope)), horoscope_names_count);
+
+	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(info_window->auth_radio[0])))
+		auth_type = QQ_AUTH_NO_AUTH;
+	else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(info_window->auth_radio[1])))
+		auth_type = QQ_AUTH_NEED_AUTH;
+	else
+		auth_type = QQ_AUTH_NO_ADD;
+
+	info->auth_type = g_strdup_printf("%d", auth_type);
+
+	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(info_window->open_contact_radio[0])))
+		open_contact = QQ_CONTACT_OPEN;
+	else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(info_window->open_contact_radio[1])))
+		open_contact = QQ_CONTACT_ONLY_FRIENDS;
+	else
+		open_contact = QQ_CONTACT_CLOSE;
+
+	info->is_open_contact = g_strdup_printf("%d", open_contact);
+
+	qq_send_packet_modify_info(gc, info, passwd[1]);
+	qq_refresh_buddy_and_myself(info, gc);
+
+	gtk_widget_destroy(info_window->window);	//close window
+}
+
+/*****************************************************************************/
+static void _info_window_change_face(GtkWidget * widget, GdkEvent * event, contact_info_window * info_window) {
+	gint i;
+	GdkPixbuf *pixbuf;
+	GtkWidget *dialog, *image, *button, *vbox, *smiley_box;
+	change_icon_widgets *change_icon;
+
+	change_icon = g_new0(change_icon_widgets, 1);
+
+	smiley_box = NULL;
+	GAIM_DIALOG(dialog);	// learned from gtkimhtmltoolbar.c
+	gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
+	gtk_window_set_modal(GTK_WINDOW(dialog), FALSE);
+
+	g_signal_connect(G_OBJECT(dialog), "delete_event", G_CALLBACK(_window_deleteevent), NULL);
+
+	gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
+	gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
+
+	change_icon->dialog = dialog;
+	change_icon->face = info_window->face;
+
+	vbox = gtk_vbox_new(TRUE, 5);
+
+	for (i = 0; i < 85; i++) {
+		if (i % 8 == 0) {
+			smiley_box = gtk_toolbar_new();
+			gtk_box_pack_start(GTK_BOX(vbox), smiley_box, TRUE, TRUE, 0);
+		}
+		pixbuf = get_face_gdkpixbuf(i * 3);
+		image = gtk_image_new_from_pixbuf(pixbuf);
+		g_object_unref(pixbuf);
+		button = gtk_toolbar_append_item(GTK_TOOLBAR(smiley_box),
+						 NULL, NULL, NULL, image, G_CALLBACK(_qq_change_face), change_icon);
+		g_object_set_data(G_OBJECT(button), "user_data", GINT_TO_POINTER(i * 3));
+		gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
+	}			// for i
+
+	gtk_container_add(GTK_CONTAINER(dialog), vbox);
+	gtk_container_set_border_width(GTK_CONTAINER(dialog), 5);
+	gtk_window_set_title(GTK_WINDOW(dialog), _("Choose my head icon"));
+	gtk_widget_show_all(dialog);
+}				// _info_window_change_face
+
+/*****************************************************************************/
+static void _change_passwd_checkbutton_callback(GtkWidget * widget, contact_info_window * info_window) {
+	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
+		gtk_widget_set_sensitive(info_window->old_password, TRUE);
+		gtk_widget_set_sensitive(info_window->password[0], TRUE);
+		gtk_widget_set_sensitive(info_window->password[1], TRUE);
+	} else {
+		gtk_widget_set_sensitive(info_window->old_password, FALSE);
+		gtk_widget_set_sensitive(info_window->password[0], FALSE);
+		gtk_widget_set_sensitive(info_window->password[1], FALSE);
+	}
+}				// _change_passwd_checkbutton_callback
+
+/*****************************************************************************/
+static GtkWidget *_create_page_basic
+    (gint is_myself, contact_info * info, GaimConnection * gc, contact_info_window * info_window) {
+	GdkPixbuf *pixbuf;
+	GList *cbitems;
+	GtkWidget *hbox, *pixmap, *frame, *label, *table;
+	GtkWidget *entry, *combo, *alignment;
+	GtkWidget *event_box, *qq_show;
+	GtkTooltips *tooltips;
+
+	tooltips = gtk_tooltips_new();
+
+	cbitems = NULL;
+
+	hbox = gtk_hbox_new(FALSE, 0);
+	frame = gtk_frame_new("");
+	gtk_container_set_border_width(GTK_CONTAINER(frame), 5);
+	gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0);
+
+	table = gtk_table_new(7, 3, FALSE);
+	gtk_table_set_row_spacings(GTK_TABLE(table), 2);
+	gtk_table_set_col_spacings(GTK_TABLE(table), 5);
+	gtk_container_set_border_width(GTK_CONTAINER(table), 5);
+	gtk_container_add(GTK_CONTAINER(frame), table);
+
+	pixbuf = get_face_gdkpixbuf((guint8) strtol(info->face, NULL, 10));
+	pixmap = gtk_image_new_from_pixbuf(pixbuf);
+	info_window->face = pixmap;
+
+	alignment = gtk_alignment_new(0.25, 0, 0, 0);
+
+	// set up icon
+	if (is_myself) {
+		g_object_set_data(G_OBJECT(info_window->face), "user_data",
+				  GINT_TO_POINTER(strtol(info->face, NULL, 10)));
+		event_box = gtk_event_box_new();
+		g_signal_connect(G_OBJECT(event_box), "button_press_event",
+				 G_CALLBACK(_info_window_change_face), info_window);
+		gtk_container_add(GTK_CONTAINER(event_box), pixmap);
+		gtk_container_add(GTK_CONTAINER(alignment), event_box);
+		gtk_tooltips_set_tip(GTK_TOOLTIPS(tooltips), event_box, _("Click to change icon"), NULL);
+	} else
+		gtk_container_add(GTK_CONTAINER(alignment), pixmap);
+
+	gtk_table_attach(GTK_TABLE(table), alignment, 2, 3, 0, 2, GTK_FILL, 0, 0, 0);
+
+	// set up qq_show image and event
+	qq_show = qq_show_default(info);
+	g_object_set_data(G_OBJECT(qq_show), "user_data", GINT_TO_POINTER((gint) atoi(info->uid)));
+
+	frame = gtk_frame_new("");
+	gtk_container_set_border_width(GTK_CONTAINER(frame), 0);
+
+	if (strtol(info->qq_show, NULL, 10) != 0) {	// buddy has qq_show
+		event_box = gtk_event_box_new();
+		gtk_container_add(GTK_CONTAINER(frame), event_box);
+		gtk_container_add(GTK_CONTAINER(event_box), qq_show);
+		g_signal_connect(G_OBJECT(event_box), "button_press_event", G_CALLBACK(qq_show_get_image), qq_show);
+		gtk_tooltips_set_tip(GTK_TOOLTIPS(tooltips), event_box, _("Click to refresh"), NULL);
+	} else			// no event_box
+		gtk_container_add(GTK_CONTAINER(frame), qq_show);
+
+	gtk_table_attach(GTK_TABLE(table), frame, 2, 3, 2, 7, GTK_EXPAND, 0, 0, 0);
+
+	label = gtk_label_new(_("QQid"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
+	gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+
+	label = gtk_label_new(_("Nickname"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
+	gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+
+	entry = _qq_entry_new();
+	info_window->uid_entry = entry;
+	_qq_set_entry(info_window->uid_entry, info->uid);
+	gtk_widget_set_size_request(entry, 120, -1);
+	gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 0, 1, GTK_FILL, 0, 0, 0);
+
+	entry = _qq_entry_new();
+	info_window->nick = entry;
+	_qq_set_entry(info_window->nick, info->nick);
+	gtk_widget_set_size_request(entry, 120, -1);
+	gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 1, 2, GTK_FILL, 0, 0, 0);
+
+	label = gtk_label_new(_("Age"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
+	gtk_table_attach(GTK_TABLE(table), label, 0, 1, 2, 3, GTK_FILL, 0, 0, 0);
+
+	label = gtk_label_new(_("Gender"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
+	gtk_table_attach(GTK_TABLE(table), label, 0, 1, 3, 4, GTK_FILL, 0, 0, 0);
+
+	label = gtk_label_new(_("Country/Region"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
+	gtk_table_attach(GTK_TABLE(table), label, 0, 1, 4, 5, GTK_FILL, 0, 0, 0);
+
+	combo = gtk_combo_new();
+	cbitems = _get_list_by_array((gchar **) country_names, country_names_count);
+	gtk_combo_set_popdown_strings(GTK_COMBO(combo), cbitems);
+	g_list_free(cbitems);
+	cbitems = NULL;
+	info_window->country = GTK_COMBO(combo)->entry;
+	_qq_set_entry(info_window->country, info->country);
+	gtk_widget_set_size_request(combo, 150, -1);
+	gtk_table_attach(GTK_TABLE(table), combo, 1, 2, 4, 5, GTK_FILL, 0, 0, 0);
+	if (!is_myself)
+		gtk_widget_set_sensitive(combo, FALSE);
+
+	entry = _qq_entry_new();
+	info_window->age = entry;
+	_qq_set_entry(info_window->age, info->age);
+	gtk_widget_set_size_request(entry, 40, -1);
+	gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 2, 3, GTK_FILL, 0, 0, 0);
+
+	combo = gtk_combo_new();
+	cbitems = g_list_append(cbitems, (char *)_("Male"));
+	cbitems = g_list_append(cbitems, (char *)_("Female"));
+	gtk_combo_set_popdown_strings(GTK_COMBO(combo), cbitems);
+	g_list_free(cbitems);
+	cbitems = NULL;
+	info_window->gender = GTK_COMBO(combo)->entry;
+	gtk_widget_set_size_request(combo, 60, -1);
+	_qq_set_entry(info_window->gender, info->gender);
+	gtk_editable_set_editable(GTK_EDITABLE(GTK_COMBO(combo)->entry), FALSE);
+	gtk_table_attach(GTK_TABLE(table), combo, 1, 2, 3, 4, GTK_FILL, 0, 0, 0);
+	if (!is_myself)
+		gtk_widget_set_sensitive(combo, FALSE);
+
+	label = gtk_label_new(_("Province"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
+	gtk_table_attach(GTK_TABLE(table), label, 0, 1, 5, 6, GTK_FILL, 0, 0, 0);
+
+	label = gtk_label_new(_("City"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
+	gtk_table_attach(GTK_TABLE(table), label, 0, 1, 6, 7, GTK_FILL, 0, 0, 0);
+
+	combo = gtk_combo_new();
+	cbitems = _get_list_by_array((gchar **) province_names, province_names_count);
+	gtk_combo_set_popdown_strings(GTK_COMBO(combo), cbitems);
+	g_list_free(cbitems);
+	cbitems = NULL;
+	info_window->province = GTK_COMBO(combo)->entry;
+	_qq_set_entry(info_window->province, info->province);
+	gtk_widget_set_size_request(combo, 150, -1);
+	gtk_table_attach(GTK_TABLE(table), combo, 1, 2, 5, 6, GTK_FILL, 0, 0, 0);
+	if (!is_myself)
+		gtk_widget_set_sensitive(combo, FALSE);
+
+	entry = _qq_entry_new();
+	info_window->city = entry;
+	gtk_widget_set_size_request(entry, 120, -1);
+	_qq_set_entry(info_window->city, info->city);
+	gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 6, 7, GTK_FILL, 0, 0, 0);
+
+	return hbox;
+}				// _create_page_basic
+
+/*****************************************************************************/
+static GtkWidget *_create_page_contact
+    (int is_myself, contact_info * info, GaimConnection * gc, contact_info_window * info_window) {
+	GtkWidget *vbox1, *frame;
+	GtkWidget *hbox1, *entry, *label;
+	GtkWidget *hbox, *radio, *table;
+
+	hbox = gtk_hbox_new(FALSE, 0);
+	vbox1 = gtk_vbox_new(FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(hbox), vbox1, TRUE, TRUE, 0);
+
+	frame = gtk_frame_new("");
+	gtk_container_set_border_width(GTK_CONTAINER(frame), 5);
+	gtk_box_pack_start(GTK_BOX(vbox1), frame, TRUE, TRUE, 0);
+
+	table = gtk_table_new(2, 5, FALSE);
+	gtk_table_set_row_spacings(GTK_TABLE(table), 2);
+	gtk_table_set_col_spacings(GTK_TABLE(table), 5);
+	gtk_container_set_border_width(GTK_CONTAINER(table), 5);
+	gtk_container_add(GTK_CONTAINER(frame), table);
+
+	label = gtk_label_new(_("Email"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
+	gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+
+	label = gtk_label_new(_("Address"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
+	gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+
+	label = gtk_label_new(_("Zipcode"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
+	gtk_table_attach(GTK_TABLE(table), label, 0, 1, 2, 3, GTK_FILL, 0, 0, 0);
+
+	label = gtk_label_new(_("Tel"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
+	gtk_table_attach(GTK_TABLE(table), label, 0, 1, 3, 4, GTK_FILL, 0, 0, 0);
+
+	label = gtk_label_new(_("Homepage"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
+	gtk_table_attach(GTK_TABLE(table), label, 0, 1, 4, 5, GTK_FILL, 0, 0, 0);
+
+	entry = _qq_entry_new();
+	info_window->email = entry;
+	gtk_widget_set_size_request(entry, 240, -1);
+	_qq_set_entry(info_window->email, info->email);
+	gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 0, 1, GTK_FILL, 0, 0, 0);
+
+	entry = _qq_entry_new();
+	info_window->address = entry;
+	_qq_set_entry(info_window->address, info->address);
+	gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 1, 2, GTK_FILL, 0, 0, 0);
+
+	entry = _qq_entry_new();
+	info_window->zipcode = entry;
+	_qq_set_entry(info_window->zipcode, info->zipcode);
+	gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 2, 3, GTK_FILL, 0, 0, 0);
+
+	entry = _qq_entry_new();
+	info_window->tel = entry;
+	_qq_set_entry(info_window->tel, info->tel);
+	gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 3, 4, GTK_FILL, 0, 0, 0);
+
+	entry = _qq_entry_new();
+	info_window->homepage = entry;
+	_qq_set_entry(info_window->homepage, info->homepage);
+	gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 4, 5, GTK_FILL, 0, 0, 0);
+
+	frame = gtk_frame_new(_("Above infomation"));
+	gtk_container_set_border_width(GTK_CONTAINER(frame), 5);
+	if (!is_myself)
+		gtk_widget_set_sensitive(frame, FALSE);
+	gtk_box_pack_start(GTK_BOX(vbox1), frame, FALSE, FALSE, 0);
+
+	hbox1 = gtk_hbox_new(FALSE, 5);
+	gtk_container_add(GTK_CONTAINER(frame), hbox1);
+
+	radio = gtk_radio_button_new_with_label(NULL, _("Open to all"));
+	info_window->open_contact_radio[0] = radio;
+	gtk_box_pack_start(GTK_BOX(hbox1), radio, FALSE, FALSE, 0);
+
+	radio = gtk_radio_button_new_with_label_from_widget
+	    (GTK_RADIO_BUTTON(radio), _("Only accessible by my frineds"));
+	info_window->open_contact_radio[1] = radio;
+	gtk_box_pack_start(GTK_BOX(hbox1), radio, FALSE, FALSE, 0);
+
+	radio = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(radio), _("Close to all"));
+	info_window->open_contact_radio[2] = radio;
+	gtk_box_pack_start(GTK_BOX(hbox1), radio, FALSE, FALSE, 0);
+
+	_qq_set_open_contact_radio(info_window, info->is_open_contact);
+
+	return hbox;
+}				// _create_page_contact
+
+/*****************************************************************************/
+static GtkWidget *_create_page_details
+    (int is_myself, contact_info * info, GaimConnection * gc, contact_info_window * info_window) {
+	GList *cbitems;
+	GtkWidget *text, *hbox, *frame;
+	GtkWidget *combo, *table, *label, *entry, *scrolled_window;
+
+	cbitems = NULL;
+
+	hbox = gtk_hbox_new(FALSE, 0);
+	frame = gtk_frame_new("");
+	gtk_container_set_border_width(GTK_CONTAINER(frame), 5);
+	gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0);
+
+	table = gtk_table_new(4, 4, FALSE);
+	gtk_table_set_row_spacings(GTK_TABLE(table), 2);
+	gtk_table_set_col_spacings(GTK_TABLE(table), 5);
+	gtk_container_set_border_width(GTK_CONTAINER(table), 5);
+	gtk_container_add(GTK_CONTAINER(frame), table);
+
+	label = gtk_label_new(_("Real Name"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
+	gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+
+	label = gtk_label_new(_(" Zodiac"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
+	gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0);
+
+	label = gtk_label_new(_(" Blood Type"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
+	gtk_table_attach(GTK_TABLE(table), label, 2, 3, 1, 2, GTK_FILL, 0, 0, 0);
+
+	label = gtk_label_new(_(" Horoscope"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
+	gtk_table_attach(GTK_TABLE(table), label, 2, 3, 2, 3, GTK_FILL, 0, 0, 0);
+
+	label = gtk_label_new(_("College"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
+	gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+
+	label = gtk_label_new(_("Career"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
+	gtk_table_attach(GTK_TABLE(table), label, 0, 1, 2, 3, GTK_FILL, 0, 0, 0);
+
+	label = gtk_label_new(_("Intro"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+	gtk_table_attach(GTK_TABLE(table), label, 0, 1, 3, 4, GTK_FILL, 0, 0, 0);
+
+	entry = _qq_entry_new();
+	info_window->name = entry;
+	gtk_widget_set_size_request(entry, 120, -1);
+	_qq_set_entry(info_window->name, info->name);
+	gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 0, 1, GTK_FILL, 0, 0, 0);
+
+	entry = _qq_entry_new();
+	info_window->college = entry;
+	gtk_widget_set_size_request(entry, 120, -1);
+	_qq_set_entry(info_window->college, info->college);
+	gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 1, 2, GTK_FILL, 0, 0, 0);
+
+	combo = gtk_combo_new();
+	cbitems = _get_list_by_array((gchar **) zodiac_names, zodiac_names_count);
+	gtk_combo_set_popdown_strings(GTK_COMBO(combo), cbitems);
+	g_list_free(cbitems);
+	cbitems = NULL;
+	info_window->zodiac = GTK_COMBO(combo)->entry;
+	gtk_entry_set_text(GTK_ENTRY(info_window->zodiac),
+			   get_name_by_index_str((gchar **) zodiac_names, info->zodiac, zodiac_names_count));
+	gtk_widget_set_size_request(combo, 70, -1);
+	gtk_editable_set_editable(GTK_EDITABLE(GTK_COMBO(combo)->entry), FALSE);
+	gtk_table_attach(GTK_TABLE(table), combo, 3, 4, 0, 1, GTK_FILL, 0, 0, 0);
+	if (!is_myself)
+		gtk_widget_set_sensitive(combo, FALSE);
+
+	combo = gtk_combo_new();
+	cbitems = _get_list_by_array((gchar **) blood_types, blood_types_count);
+	gtk_combo_set_popdown_strings(GTK_COMBO(combo), cbitems);
+	g_list_free(cbitems);
+	cbitems = NULL;
+	info_window->blood = GTK_COMBO(combo)->entry;
+	gtk_entry_set_text(GTK_ENTRY(info_window->blood),
+			   get_name_by_index_str((gchar **) blood_types, info->blood, blood_types_count));
+	gtk_widget_set_size_request(combo, 70, -1);
+	gtk_editable_set_editable(GTK_EDITABLE(GTK_COMBO(combo)->entry), FALSE);
+	gtk_table_attach(GTK_TABLE(table), combo, 3, 4, 1, 2, GTK_FILL, 0, 0, 0);
+	if (!is_myself)
+		gtk_widget_set_sensitive(combo, FALSE);
+
+	combo = gtk_combo_new();
+	cbitems = _get_list_by_array((gchar **) horoscope_names, horoscope_names_count);
+	gtk_combo_set_popdown_strings(GTK_COMBO(combo), cbitems);
+	g_list_free(cbitems);
+	cbitems = NULL;
+	info_window->horoscope = GTK_COMBO(combo)->entry;
+	gtk_entry_set_text(GTK_ENTRY(info_window->horoscope), get_name_by_index_str((gchar **)
+										    horoscope_names,
+										    info->horoscope,
+										    horoscope_names_count));
+	gtk_widget_set_size_request(combo, 70, -1);
+	gtk_editable_set_editable(GTK_EDITABLE(GTK_COMBO(combo)->entry), FALSE);
+	gtk_table_attach(GTK_TABLE(table), combo, 3, 4, 2, 3, GTK_FILL, 0, 0, 0);
+	if (!is_myself)
+		gtk_widget_set_sensitive(combo, FALSE);
+
+	combo = gtk_combo_new();
+	cbitems = _get_list_by_array((gchar **) occupation_names, occupation_names_count);
+	gtk_combo_set_popdown_strings(GTK_COMBO(combo), cbitems);
+	g_list_free(cbitems);
+	cbitems = NULL;
+	info_window->occupation = GTK_COMBO(combo)->entry;
+	_qq_set_entry(info_window->occupation, info->occupation);
+	gtk_widget_set_size_request(combo, 120, -1);
+	gtk_table_attach(GTK_TABLE(table), combo, 1, 2, 2, 3, GTK_FILL, 0, 0, 0);
+	if (!is_myself)
+		gtk_widget_set_sensitive(combo, FALSE);
+
+	text = gtk_text_view_new();
+	gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD);
+	info_window->intro = text;
+	gtk_widget_set_size_request(text, -1, 90);
+	_qq_set_text(info_window->intro, info->intro);
+
+	scrolled_window = gtk_scrolled_window_new(NULL, NULL);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+	gtk_container_add(GTK_CONTAINER(scrolled_window), text);
+	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), GTK_SHADOW_IN);
+	gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 2);
+	gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 2);
+
+	gtk_table_attach(GTK_TABLE(table), scrolled_window, 1, 4, 3, 4, GTK_FILL, 0, 0, 0);
+	return hbox;
+}				// _create_page_details
+
+/*****************************************************************************/
+static GtkWidget *_create_page_security
+    (int is_myself, contact_info * info, GaimConnection * gc, contact_info_window * info_window) {
+	GtkWidget *hbox, *frame, *vbox1, *vbox2;
+	GtkWidget *entry, *table, *label, *check_button;
+	GtkWidget *radio, *alignment;
+
+	hbox = gtk_hbox_new(FALSE, 0);
+	vbox1 = gtk_vbox_new(FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(hbox), vbox1, TRUE, TRUE, 0);
+
+	frame = gtk_frame_new(_("Change Password"));
+	gtk_container_set_border_width(GTK_CONTAINER(frame), 5);
+	gtk_box_pack_start(GTK_BOX(vbox1), frame, FALSE, FALSE, 0);
+
+	table = gtk_table_new(3, 3, FALSE);
+	gtk_table_set_row_spacings(GTK_TABLE(table), 2);
+	gtk_table_set_col_spacings(GTK_TABLE(table), 5);
+	gtk_container_set_border_width(GTK_CONTAINER(table), 5);
+	gtk_container_add(GTK_CONTAINER(frame), table);
+
+	label = gtk_label_new(_("Old Passwd"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
+	gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+
+	check_button = gtk_check_button_new_with_label(_("I wanna change"));
+	info_window->is_modify_passwd = check_button;
+	g_signal_connect(G_OBJECT(check_button), "toggled",
+			 G_CALLBACK(_change_passwd_checkbutton_callback), info_window);
+	gtk_table_attach(GTK_TABLE(table), check_button, 2, 3, 0, 1, GTK_FILL, 0, 0, 0);
+
+	label = gtk_label_new(_("New Passwd"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
+	gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+
+	label = gtk_label_new(_("(less than 16 char)"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
+	gtk_table_attach(GTK_TABLE(table), label, 2, 3, 1, 2, GTK_FILL, 0, 0, 0);
+
+	label = gtk_label_new(_("Confirm"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
+	gtk_table_attach(GTK_TABLE(table), label, 0, 1, 2, 3, GTK_FILL, 0, 0, 0);
+
+	entry = _qq_entry_new();
+	gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
+	gtk_widget_set_sensitive(entry, FALSE);
+	info_window->old_password = entry;
+	gtk_widget_set_size_request(entry, 160, -1);
+	gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 0, 1, GTK_FILL, 0, 0, 0);
+
+	entry = _qq_entry_new();
+	gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
+	gtk_widget_set_sensitive(entry, FALSE);
+	info_window->password[0] = entry;
+	gtk_widget_set_size_request(entry, 160, -1);
+	gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 1, 2, GTK_FILL, 0, 0, 0);
+
+	entry = _qq_entry_new();
+	gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
+	gtk_widget_set_sensitive(entry, FALSE);
+	info_window->password[1] = entry;
+	gtk_widget_set_size_request(entry, 160, -1);
+	gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 2, 3, GTK_FILL, 0, 0, 0);
+
+	alignment = gtk_alignment_new(0, 0, 0, 0);
+	gtk_widget_set_size_request(alignment, -1, 5);
+	gtk_box_pack_start(GTK_BOX(vbox1), alignment, FALSE, FALSE, 0);
+
+	frame = gtk_frame_new(_("Authentication"));
+	gtk_container_set_border_width(GTK_CONTAINER(frame), 5);
+	gtk_box_pack_start(GTK_BOX(vbox1), frame, FALSE, FALSE, 0);
+
+	vbox2 = gtk_vbox_new(FALSE, 1);
+	gtk_container_add(GTK_CONTAINER(frame), vbox2);
+
+	radio = gtk_radio_button_new_with_label(NULL, _("Anyone can add me"));
+	info_window->auth_radio[0] = radio;
+	gtk_box_pack_start(GTK_BOX(vbox2), radio, FALSE, FALSE, 0);
+
+	radio = gtk_radio_button_new_with_label_from_widget
+	    (GTK_RADIO_BUTTON(radio), _("User needs to be authenticated to add me"));
+	info_window->auth_radio[1] = radio;
+	gtk_box_pack_start(GTK_BOX(vbox2), radio, FALSE, FALSE, 0);
+
+	radio = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(radio), _("Nobody can add me"));
+	info_window->auth_radio[2] = radio;
+	gtk_box_pack_start(GTK_BOX(vbox2), radio, FALSE, FALSE, 0);
+
+	_qq_set_auth_type_radio(info_window, info->auth_type);
+
+	return hbox;
+}				// _create_page_security 
+
+/*****************************************************************************/
+void qq_show_contact_info_dialog(contact_info * info, GaimConnection * gc, contact_info_window * info_window) {
+
+	GaimAccount *a = gc->account;
+	gboolean is_myself;
+	GtkWidget *label, *window, *notebook, *vbox, *bbox, *button;
+
+	is_myself = (!strcmp(info->uid, a->username));
+	window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+//	gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(mainwindow));  //by gfhuang
+	info_window->window = window;
+
+	// we need to duplicate here, 
+	// as the passed-in info structure will be freed in his function
+	info_window->old_info = (contact_info *) g_strdupv((gchar **) info);
+
+	/* When the window is given the "delete_event" signal (this is given
+	 * by the window manager, usually by the "close" option, or on the
+	 * titlebar), we ask it to call the delete_event () function
+	 * as defined above. The data passed to the callback  
+	 * function is NULL and is ignored in the callback function. 
+	 */
+	g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(_window_deleteevent), NULL);
+
+	/* Here we connect the "destroy" event to a signal handler.  
+	 * This event occurs when we call gtk_widget_destroy() on the window,
+	 * or if we return FALSE in the "delete_event" callback. 
+	 */
+	g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(_info_window_destroy), gc);
+
+	gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
+
+	if (is_myself)
+		gtk_window_set_title(GTK_WINDOW(window), _("My Information"));
+	else
+		gtk_window_set_title(GTK_WINDOW(window), _("My Buddy's Information"));
+	gtk_container_set_border_width(GTK_CONTAINER(window), 5);
+
+	vbox = gtk_vbox_new(FALSE, 0);
+	gtk_container_add(GTK_CONTAINER(window), vbox);
+
+	notebook = gtk_notebook_new();
+	gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook), GTK_POS_LEFT);
+
+	label = gtk_label_new(_("Basic"));
+	gtk_notebook_append_page(GTK_NOTEBOOK(notebook), _create_page_basic(is_myself, info, gc, info_window), label);
+
+	label = gtk_label_new(_("Contact"));
+	gtk_notebook_append_page(GTK_NOTEBOOK(notebook), _create_page_contact(is_myself, info, gc, info_window), label);
+
+	label = gtk_label_new(_("Details"));
+	gtk_notebook_append_page(GTK_NOTEBOOK(notebook), _create_page_details(is_myself, info, gc, info_window), label);
+
+	if (is_myself) {
+		label = gtk_label_new(_("Security"));
+		gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+					 _create_page_security(is_myself, info, gc, info_window), label);
+	}
+
+	gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0);
+
+	bbox = gtk_hbutton_box_new();
+	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
+	gtk_box_set_spacing(GTK_BOX(bbox), 10);
+
+	button = gtk_button_new_with_label(_("Modify"));
+	g_object_set_data(G_OBJECT(button), "user_data", (gpointer) info_window);
+	g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(_info_window_change), gc);
+	gtk_container_add(GTK_CONTAINER(bbox), button);
+
+	if (is_myself)
+		gtk_widget_set_sensitive(button, TRUE);
+	else
+		gtk_widget_set_sensitive(button, FALSE);
+
+	button = gtk_button_new_with_label(_("Refresh"));
+	info_window->refresh_button = button;
+	g_object_set_data(G_OBJECT(button), "user_data", (gpointer) info_window);
+	g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(_info_window_refresh), gc);
+	gtk_container_add(GTK_CONTAINER(bbox), button);
+
+	button = gtk_button_new_with_label(_("Close"));
+	g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(_window_close), info_window->window);
+	gtk_container_add(GTK_CONTAINER(bbox), button);
+
+	gtk_box_pack_start(GTK_BOX(vbox), bbox, TRUE, TRUE, 5);
+
+	_no_edit(info_window->uid_entry);	// uid entry cannot be changed
+	if (!is_myself) {
+		_no_edit(info_window->nick);
+		_no_edit(info_window->country);
+		_no_edit(info_window->age);
+		_no_edit(info_window->gender);
+		_no_edit(info_window->province);
+		_no_edit(info_window->city);
+		_no_edit(info_window->email);
+		_no_edit(info_window->address);
+		_no_edit(info_window->zipcode);
+		_no_edit(info_window->tel);
+		_no_edit(info_window->name);
+		_no_edit(info_window->blood);
+		_no_edit(info_window->college);
+		_no_edit(info_window->occupation);
+		_no_edit(info_window->zodiac);
+		_no_edit(info_window->horoscope);
+		_no_edit(info_window->homepage);
+		gtk_text_view_set_editable(GTK_TEXT_VIEW(info_window->intro), FALSE);
+	}
+	gtk_widget_show_all(window);
+}				// qq_show_contact_info_dialog
+
+/*****************************************************************************/
+void qq_refresh_contact_info_dialog(contact_info * new_info, GaimConnection * gc, contact_info_window * info_window) {
+	GaimAccount *a;
+	gboolean is_myself;
+	contact_info *info;
+	a = gc->account;
+
+	if (info_window->old_info)
+		g_strfreev((gchar **) info_window->old_info);
+
+	// we need to duplicate here, 
+	// as the passed-in info structure will be freed in his function
+	info = (contact_info *) g_strdupv((gchar **) new_info);
+	info_window->old_info = info;
+
+	is_myself = !(g_ascii_strcasecmp(info->uid, a->username));
+	gtk_widget_set_sensitive(info_window->refresh_button, TRUE);
+
+	if (is_myself) {
+		_qq_set_auth_type_radio(info_window, info->auth_type);
+		_qq_set_open_contact_radio(info_window, info->is_open_contact);
+	}
+
+	_qq_set_entry(info_window->uid_entry, info->uid);
+	_qq_set_entry(info_window->nick, info->nick);
+	_qq_set_entry(info_window->country, info->country);
+	_qq_set_entry(info_window->age, info->age);
+	_qq_set_entry(info_window->gender, info->gender);
+	_qq_set_entry(info_window->province, info->province);
+	_qq_set_entry(info_window->city, info->city);
+	_qq_set_entry(info_window->email, info->email);
+	_qq_set_entry(info_window->address, info->address);
+	_qq_set_entry(info_window->zipcode, info->zipcode);
+	_qq_set_entry(info_window->tel, info->tel);
+	_qq_set_entry(info_window->name, info->name);
+	gtk_entry_set_text(GTK_ENTRY(info_window->zodiac),
+			   get_name_by_index_str((gchar **) zodiac_names, info->zodiac, zodiac_names_count));
+	gtk_entry_set_text(GTK_ENTRY(info_window->horoscope), get_name_by_index_str((gchar **)
+										    horoscope_names,
+										    info->horoscope,
+										    horoscope_names_count));
+	gtk_entry_set_text(GTK_ENTRY(info_window->blood),
+			   get_name_by_index_str((gchar **) blood_types, info->blood, blood_types_count));
+	_qq_set_entry(info_window->college, info->college);
+	_qq_set_entry(info_window->occupation, info->occupation);
+	_qq_set_entry(info_window->homepage, info->homepage);
+
+	_qq_set_image(info_window->face, (guint8) atoi(info->face));
+
+	_qq_set_text(info_window->intro, info->intro);
+}				// qq_refresh_contact_info_dialog
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/infodlg.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,63 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_INFODLG_H_
+#define _QQ_INFODLG_H_
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include "connection.h"		// GaimConnection
+
+#include "buddy_info.h"		// contact_info
+#include "qq.h"			// qq_data
+
+typedef struct _contact_info_window contact_info_window;
+
+struct _contact_info_window {
+	guint32 uid;
+	GtkWidget *window;
+	GtkWidget *refresh_button;
+	GtkWidget *uid_entry,
+	    *face,
+	    *nick,
+	    *country,
+	    *age,
+	    *gender,
+	    *province,
+	    *city,
+	    *email,
+	    *address, *zipcode, *tel, *name, *blood, *college, *occupation, *zodiac, *horoscope, *homepage, *intro;
+	GtkWidget *open_contact_radio[3];
+	GtkWidget *auth_radio[3], *is_modify_passwd, *old_password, *password[2];
+	contact_info *old_info;
+};
+
+void qq_contact_info_window_free(qq_data * qd);
+
+void qq_show_contact_info_dialog(contact_info * info, GaimConnection * gc, contact_info_window * info_window);
+void qq_refresh_contact_info_dialog(contact_info * info, GaimConnection * gc, contact_info_window * info_window);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/ip_location.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,253 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * This code is based on
+ *  * qqwry.c, by lvxiang <srm2003@163.com>
+ *  * IpChecker.m, by Jeff_Ye
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "internal.h"
+#include <string.h>		// memset
+#include "debug.h"
+#include "prefs.h"		// gaim_prefs_get_string
+
+#include "utils.h"
+#include "ip_location.h"
+
+#define DEFAULT_IP_LOCATION_FILE  "gaim/QQWry.dat"
+
+typedef struct _ip_finder ip_finder;
+
+// all offset is based the begining of the file
+struct _ip_finder {
+	guint32 offset_first_start_ip;	// first abs offset of start ip
+	guint32 offset_last_start_ip;	// last abs offset of start ip
+	guint32 cur_start_ip;	// start ip of current search range
+	guint32 cur_end_ip;	// end ip of current search range
+	guint32 offset_cur_end_ip;	// where is the current end ip saved
+	GIOChannel *io;		// IO Channel to read file
+};				// struct _ip_finder
+
+/*****************************************************************************/
+// convert 1-4 bytes array to integer.
+// Small endian (higher bytes in lower place)
+static guint32 _byte_array_to_int(guint8 * ip, gint count)
+{
+	guint32 ret, i;
+	g_return_val_if_fail(count >= 1 && count <= 4, 0);
+	ret = ip[0];
+	for (i = 0; i < count; i++)
+		ret |= ((guint32) ip[i]) << (8 * i);
+	return ret;
+}				// _byte_array_to_int
+
+/*****************************************************************************/
+// read len of bytes to buf, from io at offset
+static void _read_from(GIOChannel * io, guint32 offset, guint8 * buf, gint len)
+{
+	GError *err;
+	GIOStatus status;
+
+	err = NULL;
+	status = g_io_channel_seek_position(io, offset, G_SEEK_SET, &err);
+	if (err != NULL) {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail seek file @offset[%d]: %s", offset, err->message);
+		g_error_free(err);
+		memset(buf, 0, len);
+		return;
+	}			// if err
+
+	status = g_io_channel_read_chars(io, buf, len, NULL, &err);
+	if (err != NULL) {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail read %d bytes from file: %s", len, err->message);
+		g_error_free(err);
+		memset(buf, 0, len);
+		return;
+	}			// if err
+}				// _read_from
+
+/*****************************************************************************/
+// read len of bytes to buf, from io at offset
+static gsize _read_line_from(GIOChannel * io, guint32 offset, gchar ** ret_str)
+{
+	gsize bytes_read;
+	GError *err;
+	GIOStatus status;
+
+	err = NULL;
+	status = g_io_channel_seek_position(io, offset, G_SEEK_SET, &err);
+	if (err != NULL) {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail seek file @offset[%d]: %s", offset, err->message);
+		g_error_free(err);
+		ret_str = NULL;
+		return -1;
+	}			// if err
+
+	status = g_io_channel_read_line(io, ret_str, &bytes_read, NULL, &err);
+	if (err != NULL) {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail read from file: %s", err->message);
+		g_error_free(err);
+		ret_str = NULL;
+		return -1;
+	}			// if err
+
+	return bytes_read;
+}				// _read_from
+
+/*****************************************************************************/
+// read the string from io, at offset, it may jump several times
+// returns the offset of next readable string for area
+static guint32 _get_string(GIOChannel * io, guint32 offset, gchar ** ret)
+{
+	guint8 *buf;
+	g_return_val_if_fail(io != NULL, 0);
+
+	buf = g_new0(guint8, 3);
+	_read_from(io, offset, buf, 1);
+
+	switch (buf[0]) {	/* fixed by bluestar11 at gmail dot com, 04/12/20 */
+	case 0x01:		/* jump together */
+	  _read_from(io, offset + 1, buf, 3);
+	  return _get_string(io, _byte_array_to_int(buf, 3), ret);
+	case 0x02:		/* jump separately */
+	   _read_from(io, offset + 1, buf, 3);
+	  _get_string(io, _byte_array_to_int(buf, 3), ret);
+	  return offset + 4;
+	default:
+	  _read_line_from(io, offset, ret);
+	  return offset + strlen(*ret) + 1;
+	} /* switch */
+}				// _get_string
+
+/*****************************************************************************/
+// extract country and area starting from offset
+static void _get_country_city(GIOChannel * io, guint32 offset, gchar ** country, gchar ** area)
+{
+	guint32 next_offset;
+	g_return_if_fail(io != NULL);
+
+	next_offset = _get_string(io, offset, country);
+	if (next_offset == 0)
+		*area = g_strdup("");
+	else
+		_get_string(io, next_offset, area);
+}				// _get_country_city
+
+/*****************************************************************************/
+// set start_ip and end_ip of current range
+static void _set_ip_range(gint rec_no, ip_finder * f)
+{
+	guint8 *buf;
+	guint32 offset;
+
+	g_return_if_fail(f != NULL);
+
+	buf = g_newa(guint8, 7);
+	offset = f->offset_first_start_ip + rec_no * 7;
+
+	_read_from(f->io, offset, buf, 7);
+	f->cur_start_ip = _byte_array_to_int(buf, 4);
+	f->offset_cur_end_ip = _byte_array_to_int(buf + 4, 3);
+
+	_read_from(f->io, f->offset_cur_end_ip, buf, 4);
+	f->cur_end_ip = _byte_array_to_int(buf, 4);
+
+}				// _set_ip_range
+
+/*****************************************************************************/
+// set the country and area for given IP.
+// country and area needs to be freed later
+gboolean qq_ip_get_location(guint32 ip, gchar ** country, gchar ** area)
+{
+	gint rec, record_count, B, E;
+	guint8 *buf;
+	gchar *addr_file;
+	ip_finder *f;
+	GError *err;
+	const char *ip_fn;
+
+	if (ip == 0)
+		return FALSE;
+
+	f = g_new0(ip_finder, 1);
+	err = NULL;
+	ip_fn = gaim_prefs_get_string("/plugins/prpl/qq/ipfile");
+	if (ip_fn == NULL || strlen(ip_fn) == 0 || strncmp(ip_fn, "(null)", strlen("(null)")) == 0) {
+		addr_file = g_build_filename(DATADIR, DEFAULT_IP_LOCATION_FILE, NULL);
+	} else {
+		addr_file = g_build_filename(ip_fn, NULL);
+	}
+
+	f->io = g_io_channel_new_file(addr_file, "r", &err);
+	g_free(addr_file);
+	if (err != NULL) {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Unable to open (%s): %s\n", addr_file, err->message);
+		g_error_free(err);
+		return FALSE;
+	} else
+		g_io_channel_set_encoding(f->io, NULL, NULL);	// set binary
+
+	buf = g_newa(guint8, 4);
+
+	_read_from(f->io, 0, buf, 4);
+	f->offset_first_start_ip = _byte_array_to_int(buf, 4);
+	_read_from(f->io, 4, buf, 4);
+	f->offset_last_start_ip = _byte_array_to_int(buf, 4);
+
+	record_count = (f->offset_last_start_ip - f->offset_first_start_ip) / 7;
+	if (record_count <= 1) {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "File data error, no records found\n");
+		g_io_channel_shutdown(f->io, FALSE, NULL);
+		return FALSE;;
+	}
+	// search for right range
+	B = 0;
+	E = record_count;
+	while (B < E - 1) {
+		rec = (B + E) / 2;
+		_set_ip_range(rec, f);
+		if (ip == f->cur_start_ip) {
+			B = rec;
+			break;
+		}
+		if (ip > f->cur_start_ip)
+			B = rec;
+		else
+			E = rec;
+	}			// while
+	_set_ip_range(B, f);
+
+	if (f->cur_start_ip <= ip && ip <= f->cur_end_ip) {
+		_get_country_city(f->io, f->offset_cur_end_ip + 4, country, area);
+	} else {		// not in this range... miss
+		*country = g_strdup("unkown");
+		*area = g_strdup(" ");
+	}			// if ip_start<=ip<=ip_end
+
+	g_io_channel_shutdown(f->io, FALSE, NULL);
+	return TRUE;
+
+}				// qq_ip_get_location
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/ip_location.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,34 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_IP_LOCATION_H_
+#define _QQ_IP_LOCATION_H_
+
+#include "glib.h"
+
+gboolean qq_ip_get_location(guint32 ip, gchar ** country, gchar ** city);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/keep_alive.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,182 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *
+ * OICQ encryption algorithm
+ * Convert from ASM code provided by PerlOICQ
+ * 
+ * Puzzlebird, Nov-Dec 2002
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "debug.h"		// gaim_debug
+#include "server.h"		// serv_got_update
+
+#include "utils.h"		// uid_to_gaim_name
+#include "packet_parse.h"	// create_packet
+#include "buddy_list.h"		// qq_send_packet_get_buddies_online
+#include "buddy_status.h"	// QQ_BUDDY_ONLINE_NORMAL
+#include "crypt.h"		// qq_crypt
+#include "header_info.h"	// cmd alias
+#include "keep_alive.h"
+#include "send_core.h"		// qq_send_cmd
+
+#define QQ_UPDATE_ONLINE_INTERVAL   300	// in sec
+
+/*****************************************************************************/
+// send keep-alive packet to QQ server (it is a heart-beat)
+void qq_send_packet_keep_alive(GaimConnection * gc)
+{
+	qq_data *qd;
+	guint8 *raw_data, *cursor;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+	raw_data = g_newa(guint8, 4);
+	cursor = raw_data;
+
+	// In fact, we can send whatever we like to server
+	// with this command, server return the same result including
+	// the amount of online QQ users, my ip and port
+	create_packet_dw(raw_data, &cursor, qd->uid);
+
+	qq_send_cmd(gc, QQ_CMD_KEEP_ALIVE, TRUE, 0, TRUE, raw_data, 4);
+
+}				// qq_send_packet_keep_alive
+
+/*****************************************************************************/
+// parse the return of keep-alive packet, it includes some system information
+void qq_process_keep_alive_reply(guint8 * buf, gint buf_len, GaimConnection * gc) {
+	qq_data *qd;
+	gint len;
+	gchar *data, **segments;	// the returns are gchar, no need guint8
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	g_return_if_fail(buf != NULL && buf_len != 0);
+
+	qd = (qq_data *) gc->proto_data;
+	len = buf_len;
+	data = g_newa(guint8, len);
+
+	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+		if (NULL == (segments = split_data(data, len, "\x1f", 6 /* 5->6, protocal changed by gfhuang*, the last one is 60, don't know what it is */)))
+			return;
+		// segments[0] and segment[1] are all 0x30 ("0")
+		qd->all_online = strtol(segments[2], NULL, 10);
+		if(0 == qd->all_online) 	//added by gfhuang
+			gaim_connection_error(gc, _("Keep alive error, seems connection lost!"));
+		g_free(qd->my_ip);
+		qd->my_ip = g_strdup(segments[3]);
+		qd->my_port = strtol(segments[4], NULL, 10);
+		g_strfreev(segments);
+	} else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt keep alive reply\n");
+
+	// we refresh buddies's online status periodically 
+	// qd->lasat_get_online is updated when setting get_buddies_online packet
+	if ((time(NULL) - qd->last_get_online) >= QQ_UPDATE_ONLINE_INTERVAL)
+		qq_send_packet_get_buddies_online(gc, QQ_FRIENDS_ONLINE_POSITION_START);
+
+}				// qq_process_keep_alive_reply
+
+/*****************************************************************************/
+// refresh all buddies online/offline,
+// after receiving reply for get_buddies_online packet 
+void qq_refresh_all_buddy_status(GaimConnection * gc)
+{
+	time_t now;
+	GList *list;
+	qq_data *qd;
+	qq_buddy *q_bud;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	qd = (qq_data *) (gc->proto_data);
+	now = time(NULL);
+	list = qd->buddies;
+	g_return_if_fail(qd != NULL);
+
+	while (list != NULL) {
+		q_bud = (qq_buddy *) list->data;
+		if (q_bud != NULL && now > q_bud->last_refresh + QQ_UPDATE_ONLINE_INTERVAL
+				&& q_bud->status != QQ_BUDDY_ONLINE_INVISIBLE) { // by gfhuang
+			q_bud->status = QQ_BUDDY_ONLINE_OFFLINE;
+			qq_update_buddy_contact(gc, q_bud);
+		}
+		list = list->next;
+	}			// while
+}				// qq_refresh_all_buddy_status
+
+/*****************************************************************************/
+void qq_update_buddy_contact(GaimConnection * gc, qq_buddy * q_bud)
+{
+	gchar *name;
+//	gboolean online;
+	GaimBuddy *bud;
+	g_return_if_fail(gc != NULL && q_bud != NULL);
+
+	name = uid_to_gaim_name(q_bud->uid);
+	bud = gaim_find_buddy(gc->account, name);
+	g_return_if_fail(bud != NULL);
+
+	if (bud != NULL) {
+		gaim_blist_server_alias_buddy(bud, q_bud->nickname); //server by gfhuang
+		q_bud->last_refresh = time(NULL);
+
+		// gaim support signon and idle time
+		// but it is not much useful for QQ, I do not use them it
+//		serv_got_update(gc, name, online, 0, q_bud->signon, q_bud->idle, bud->uc);          //disable by gfhuang
+		char *status_id = "available";
+		switch(q_bud->status) {
+		case QQ_BUDDY_OFFLINE:
+			status_id = "offline";
+			break;
+		case QQ_BUDDY_ONLINE_NORMAL:
+			status_id = "available";
+			break;
+		case QQ_BUDDY_ONLINE_OFFLINE:
+			status_id = "offline";
+			break;
+	        case QQ_BUDDY_ONLINE_AWAY:
+			status_id = "away";
+			break;
+	       	case QQ_BUDDY_ONLINE_INVISIBLE:
+			status_id = "invisible";
+			break;
+		default:
+			status_id = "invisible";
+			gaim_debug(GAIM_DEBUG_ERROR, "QQ", "unknown status by gfhuang: %x\n", q_bud->status);
+			break;
+		}
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "set buddy %d to %s\n", q_bud->uid, status_id);
+		gaim_prpl_got_user_status(gc->account, name, status_id, NULL);
+	}			// if bud
+	else 
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "unknown buddy by gfhuang: %d\n", q_bud->uid);
+
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "qq_update_buddy_contact, client=%04x\n", q_bud->client_version);
+	g_free(name);
+}				// qq_update_buddy_contact
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/keep_alive.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,42 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_KEEP_ALIVE_H_
+#define _QQ_KEEP_ALIVE_H_
+
+#include <glib.h>
+#include "connection.h"		// GaimConnection
+#include "qq.h"			// qq_buddy
+
+void qq_send_packet_keep_alive(GaimConnection * gc);
+
+void qq_process_keep_alive_reply(guint8 * buf, gint buf_len, GaimConnection * gc);
+void qq_refresh_all_buddy_status(GaimConnection * gc);
+
+void qq_update_buddy_contact(GaimConnection * gc, qq_buddy * q_bud);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/login_logout.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,511 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "debug.h"		// gaim_debug
+#include "internal.h"		// memcpy, _("get_text")
+#include "server.h"		// serv_finish_login
+
+#include "utils.h"		// gen_ip_str
+#include "packet_parse.h"	// create_packet
+#include "buddy_info.h"		// qq_send_packet_get_info
+#include "buddy_list.h"		// qq_send_packet_get_buddies_list
+#include "buddy_status.h"	// QQ_SELF_STATUS_AVAILABLE
+#include "char_conv.h"		// qq_to_utf8
+#include "crypt.h"		// qq_crypt
+#include "group.h"		// qq_group_init
+#include "header_info.h"	// QQ_CMD_LOGIN
+#include "login_logout.h"
+#include "qq_proxy.h"		// qq_connect
+#include "send_core.h"		// qq_send_cmd
+#include "qq.h"			// qq_data
+
+//#define QQ_LOGIN_DATA_LENGTH                69	//length of plain login packet
+#define QQ_LOGIN_DATA_LENGTH			416	//new length from eva, by gfhuang
+#define QQ_LOGIN_REPLY_OK_PACKET_LEN        139
+#define QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN  11
+
+#define QQ_REQUEST_LOGIN_TOKEN_REPLY_OK 	0x00		//added by gfhuang
+
+#define QQ_LOGIN_REPLY_OK                   0x00
+#define QQ_LOGIN_REPLY_REDIRECT             0x01
+//#define QQ_LOGIN_REPLY_PWD_ERROR            0x02
+#define QQ_LOGIN_REPLY_PWD_ERROR            0x05
+#define QQ_LOGIN_REPLY_MISC_ERROR           0xff	// defined by myself
+
+// for QQ 2003iii 0117, fixed value
+/* static const guint8 login_23_51[29] = {
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0xbf, 0x14, 0x11, 0x20,
+	0x03, 0x9d, 0xb2, 0xe6, 0xb3, 0x11, 0xb7, 0x13,
+	0x95, 0x67, 0xda, 0x2c, 0x01 
+}; */
+
+// for QQ 2003iii 0304, fixed value
+/*
+static const guint8 login_23_51[29] = {
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x9a, 0x93, 0xfe, 0x85,
+	0xd3, 0xd9, 0x2a, 0x41, 0xc8, 0x0d, 0xff, 0xb6,
+	0x40, 0xb8, 0xac, 0x32, 0x01
+};
+*/
+
+//for QQ 2005? copy from lumqq
+static const gint8 login_23_51[29] = {
+	0, 0, 0, 
+	0, 0, 0, 0, 0, 0, 0, 0, 0, -122, 
+	-52, 76, 53, 44, -45, 115, 108, 20, -10, -10, 
+	-81, -61, -6, 51, -92, 1
+};
+
+static const gint8 login_53_68[16] = {
+	-115, -117, -6, -20, -43, 82, 23, 74, -122, -7, 
+	-89, 117, -26, 50, -47, 109
+};
+
+static const gint8 login_100_bytes[100] = {
+	64, 
+	11, 4, 2, 0, 1, 0, 0, 0, 0, 0, 
+	3, 9, 0, 0, 0, 0, 0, 0, 0, 1, 
+	-23, 3, 1, 0, 0, 0, 0, 0, 1, -13, 
+	3, 0, 0, 0, 0, 0, 0, 1, -19, 3, 
+	0, 0, 0, 0, 0, 0, 1, -20, 3, 0, 
+	0, 0, 0, 0, 0, 3, 5, 0, 0, 0, 
+	0, 0, 0, 0, 3, 7, 0, 0, 0, 0, 
+	0, 0, 0, 1, -18, 3, 0, 0, 0, 0, 
+	0, 0, 1, -17, 3, 0, 0, 0, 0, 0, 
+	0, 1, -21, 3, 0, 0, 0, 0, 0
+};
+
+// fixed value, not affected by version, or mac address
+/*
+static const guint8 login_53_68[16] = {
+	0x82, 0x2a, 0x91, 0xfd, 0xa5, 0xca, 0x67, 0x4c,
+	0xac, 0x81, 0x1f, 0x6f, 0x52, 0x05, 0xa7, 0xbf
+};
+*/
+
+
+typedef struct _qq_login_reply_ok qq_login_reply_ok_packet;
+typedef struct _qq_login_reply_redirect qq_login_reply_redirect_packet;
+
+struct _qq_login_reply_ok {
+	guint8 result;
+	guint8 *session_key;
+	guint32 uid;
+	guint8 client_ip[4];	// those detected by server
+	guint16 client_port;
+	guint8 server_ip[4];
+	guint16 server_port;
+	time_t login_time;
+	guint8 unknown1[26];
+	guint8 unknown_server1_ip[4];
+	guint16 unknown_server1_port;
+	guint8 unknown_server2_ip[4];
+	guint16 unknown_server2_port;
+	guint16 unknown2;	// 0x0001
+	guint16 unknown3;	// 0x0000
+	guint8 unknown4[32];
+	guint8 unknown5[12];
+	guint8 last_client_ip[4];
+	time_t last_login_time;
+	guint8 unknown6[8];
+};
+
+struct _qq_login_reply_redirect {
+	guint8 result;
+	guint32 uid;
+	guint8 new_server_ip[4];
+	guint16 new_server_port;
+};
+
+extern gint			// defined in send_core.c
+ _create_packet_head_seq(guint8 * buf,
+			 guint8 ** cursor, GaimConnection * gc, guint16 cmd, gboolean is_auto_seq, guint16 * seq);
+extern gint			// defined in send_core.c
+ _qq_send_packet(GaimConnection * gc, guint8 * buf, gint len, guint16 cmd);
+
+/*****************************************************************************/
+// It is fixed to 16 bytes 0x01 for QQ2003, 
+// Any value works (or a random 16 bytes string)
+static gchar *_gen_login_key(void)
+{
+	return g_strnfill(QQ_KEY_LENGTH, 0x01);
+}				// _gen_login_key
+
+/*****************************************************************************/
+// process login reply which says OK
+static gint _qq_process_login_ok(GaimConnection * gc, guint8 * data, gint len)
+{
+	gint bytes;
+	guint8 *cursor;
+	qq_data *qd;
+	qq_login_reply_ok_packet lrop;
+
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, QQ_LOGIN_REPLY_MISC_ERROR);
+
+	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;
+	gaim_debug(GAIM_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_dw(data, &cursor, len, (guint32 *) & 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_dw(data, &cursor, len, (guint32 *) & 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       
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ",
+			   "Fail parsing login info, expect %d bytes, read %d bytes\n",
+			   QQ_LOGIN_REPLY_OK_PACKET_LEN, bytes);
+	}			// but we still goes on as login OK
+
+	qd->session_key = g_memdup(lrop.session_key, QQ_KEY_LENGTH);
+	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);
+
+	g_free(lrop.session_key);
+
+	gaim_connection_set_state(gc, GAIM_CONNECTED);
+//	serv_finish_login(gc);         //by gfhuang
+	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
+	qq_send_packet_get_info(gc, qd->uid, FALSE);
+	// change my status manually, even server may broadcast my online
+	qd->status = (qd->login_mode == QQ_LOGIN_MODE_HIDDEN) ? QQ_SELF_STATUS_INVISIBLE : QQ_SELF_STATUS_AVAILABLE;
+	qq_send_packet_change_status(gc);
+	// now refresh buddy list
+
+	//changed by gfhuang, using With Qun version, error, not working still
+	qq_send_packet_get_buddies_list(gc, QQ_FRIENDS_LIST_POSITION_START);
+	//qq_send_packet_get_all_list_with_group(gc, QQ_FRIENDS_LIST_POSITION_START);
+
+	return QQ_LOGIN_REPLY_OK;
+}				// _qq_process_login_ok
+
+/*****************************************************************************/
+// process login reply packet which includes redirected new server address
+static gint _qq_process_login_redirect(GaimConnection * gc, guint8 * data, gint len)
+{
+	gint bytes, ret;
+	guint8 *cursor;
+	gchar *new_server_str;
+	qq_data *qd;
+	qq_login_reply_redirect_packet lrrp;
+
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, QQ_LOGIN_REPLY_MISC_ERROR);
+
+	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) {
+		gaim_debug(GAIM_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);
+		gaim_debug(GAIM_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;
+	}			// if bytes != QQ_LOGIN_REPLY_MISC_ERROR 
+
+	return ret;
+}				// _qq_process_login_redirect
+
+/*****************************************************************************/
+// process login reply which says wrong password
+static gint _qq_process_login_wrong_pwd(GaimConnection * gc, guint8 * data, gint len)
+{
+	gchar *server_reply, *server_reply_utf8;
+	server_reply = g_new0(gchar, len);
+	g_memmove(server_reply, data + 1, len - 1);
+	server_reply_utf8 = qq_to_utf8(server_reply, QQ_CHARSET_DEFAULT);
+	gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Wrong password, server msg in UTF8: %s\n", server_reply_utf8);
+	g_free(server_reply);
+	g_free(server_reply_utf8);
+
+	return QQ_LOGIN_REPLY_PWD_ERROR;
+}				// _qq_process_login_wrong_pwd
+
+
+// request before login, new protocal, by gfhuang
+void qq_send_packet_request_login_token(GaimConnection *gc)
+{
+	qq_data *qd;
+	guint8 *buf, *cursor;
+	guint16 seq_ret;
+	gint bytes;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+	buf = g_newa(guint8, MAX_PACKET_SIZE);
+
+	cursor = buf;
+	bytes = 0;
+	bytes += _create_packet_head_seq(buf, &cursor, gc, QQ_CMD_REQUEST_LOGIN_TOKEN, TRUE, &seq_ret);
+	bytes += create_packet_dw(buf, &cursor, qd->uid);
+	bytes += create_packet_b(buf, &cursor, 0);
+	bytes += create_packet_b(buf, &cursor, QQ_PACKET_TAIL);
+
+	if (bytes == (cursor - buf))	// packet creation OK
+		_qq_send_packet(gc, buf, bytes, QQ_CMD_REQUEST_LOGIN_TOKEN);
+	else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail create request login token packet\n");
+}
+
+/*****************************************************************************/
+// send login packet to QQ server
+void qq_send_packet_login(GaimConnection * gc, guint8 token_length, guint8 *token)
+{
+	qq_data *qd;
+	guint8 *buf, *cursor, *raw_data, *encrypted_data;
+	guint16 seq_ret;
+	gint encrypted_len, bytes;
+	gint pos;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+	buf = g_newa(guint8, MAX_PACKET_SIZE);
+	raw_data = g_newa(guint8, QQ_LOGIN_DATA_LENGTH);
+	encrypted_data = g_newa(guint8, QQ_LOGIN_DATA_LENGTH + 16);	// 16 bytes more
+	qd->inikey = _gen_login_key();
+
+	// now generate the encrypted data
+	// 000-015 use pwkey as key to encrypt empty string
+	qq_crypt(ENCRYPT, "", 0, qd->pwkey, raw_data, &encrypted_len);
+	// 016-016 
+	raw_data[16] = 0x00;
+	// 017-020, used to be IP, now zero
+	*((guint32 *) (raw_data + 17)) = 0x00000000;
+	// 021-022, used to be port, now zero
+	*((guint16 *) (raw_data + 21)) = 0x0000;
+	// 023-051, fixed value, unknown
+	g_memmove(raw_data + 23, login_23_51, 29);
+	// 052-052, login mode
+	raw_data[52] = qd->login_mode;
+	// 053-068, fixed value, maybe related to per machine
+	g_memmove(raw_data + 53, login_53_68, 16);
+
+	// 069    , login token length, by gfhuang
+	raw_data[69] = token_length;
+	pos = 70;
+	// 070-093, login token		//normally 24 bytes
+	g_memmove(raw_data + pos, token, token_length);
+	pos += token_length;
+	// 100 bytes unknown
+	g_memmove(raw_data + pos, login_100_bytes, 100);
+	pos += 100;
+	// all zero left
+	memset(raw_data+pos, 0, QQ_LOGIN_DATA_LENGTH - pos);
+
+	qq_crypt(ENCRYPT, raw_data, QQ_LOGIN_DATA_LENGTH, qd->inikey, encrypted_data, &encrypted_len);
+
+	cursor = buf;
+	bytes = 0;
+	bytes += _create_packet_head_seq(buf, &cursor, gc, QQ_CMD_LOGIN, TRUE, &seq_ret);
+	bytes += create_packet_dw(buf, &cursor, qd->uid);
+	bytes += create_packet_data(buf, &cursor, qd->inikey, QQ_KEY_LENGTH);
+	bytes += create_packet_data(buf, &cursor, encrypted_data, encrypted_len);
+	bytes += create_packet_b(buf, &cursor, QQ_PACKET_TAIL);
+
+	if (bytes == (cursor - buf))	// packet creation OK
+		_qq_send_packet(gc, buf, bytes, QQ_CMD_LOGIN);
+	else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail create login packet\n");
+}				// qq_send_packet_login
+
+
+//added by gfhuang
+void qq_process_request_login_token_reply(guint8 * buf, gint buf_len, GaimConnection * gc)
+{
+        qq_data *qd;
+
+        g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+        g_return_if_fail(buf != NULL && buf_len != 0);
+
+        qd = (qq_data *) gc->proto_data;
+
+	if (buf[0] == QQ_REQUEST_LOGIN_TOKEN_REPLY_OK) {
+		gaim_debug(GAIM_DEBUG_INFO, "QQ",
+                                   "<<< got a token with %d bytes -> [default] decrypt and dump\n%s",buf[1], hex_dump_to_str(buf+2, buf[1]));
+		qq_send_packet_login(gc, buf[1], buf + 2);
+	} else {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Unknown request login token reply code : %d\n", buf[0]);
+                gaim_debug(GAIM_DEBUG_WARNING, "QQ",
+           		           ">>> %d bytes -> [default] decrypt and dump\n%s",
+	                           buf_len, hex_dump_to_str(buf, buf_len));
+               		try_dump_as_gbk(buf, buf_len);
+		gaim_connection_error(gc, _("Request login token error!"));
+	}		
+}
+
+/*****************************************************************************/
+// send logout packets to QQ server
+void qq_send_packet_logout(GaimConnection * gc)
+{
+	gint i;
+	qq_data *qd;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+	for (i = 0; i < 4; i++)
+		qq_send_cmd(gc, QQ_CMD_LOGOUT, FALSE, 0xffff, FALSE, qd->pwkey, QQ_KEY_LENGTH);
+
+	qd->logged_in = FALSE;	// update login status AFTER sending logout packets
+}				// qq_send_packet_logout
+
+/*****************************************************************************/
+// process the login reply packet
+void qq_process_login_reply(guint8 * buf, gint buf_len, GaimConnection * gc)
+{
+	gint len, ret, bytes;
+	guint8 *data;
+	qq_data *qd;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	g_return_if_fail(buf != NULL && buf_len != 0);
+
+	qd = (qq_data *) gc->proto_data;
+	len = buf_len;
+	data = g_newa(guint8, len);	// no need to be freed in the future
+
+	if (qq_crypt(DECRYPT, buf, buf_len, qd->pwkey, data, &len)) {
+		// should be able to decrypt with pwkey
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "Decrypt login reply packet with pwkey, %d bytes\n", len);
+		if (data[0] == QQ_LOGIN_REPLY_OK) {
+			ret = _qq_process_login_ok(gc, data, len);
+		} else {
+			gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Unknown login reply code : %d\n", data[0]);
+			ret = QQ_LOGIN_REPLY_MISC_ERROR;
+		}		// if QQ_LOGIN_REPLY_OK
+	} else {		// decrypt with pwkey error
+		len = buf_len;	// reset len, decrypt will fail if len is too short              
+		if (qq_crypt(DECRYPT, buf, buf_len, qd->inikey, data, &len)) {
+			// decrypt ok with inipwd, it might be password error
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Decrypt login reply packet with inikey, %d bytes\n", len);
+			bytes = 0;
+			switch (data[0]) {
+			case QQ_LOGIN_REPLY_REDIRECT:
+				ret = _qq_process_login_redirect(gc, data, len);
+				break;
+			case QQ_LOGIN_REPLY_PWD_ERROR:
+				ret = _qq_process_login_wrong_pwd(gc, data, len);
+				break;
+			default:
+				gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Unknown reply code: %d\n", data[0]);
+				// dump by gfhuang
+		                gaim_debug(GAIM_DEBUG_WARNING, "QQ",
+                		           ">>> %d bytes -> [default] decrypt and dump\n%s",
+		                           buf_len, hex_dump_to_str(data, len));
+                		try_dump_as_gbk(data, len);
+
+				ret = QQ_LOGIN_REPLY_MISC_ERROR;
+			}	// switch data[0]
+		} else {	// no idea how to decrypt
+			gaim_debug(GAIM_DEBUG_ERROR, "QQ", "No idea how to decrypt login reply\n");
+			ret = QQ_LOGIN_REPLY_MISC_ERROR;
+		}		// if qq_crypt with qd->inikey
+	}			// if qq_crypt with qd->pwkey
+
+	switch (ret) {
+	case QQ_LOGIN_REPLY_PWD_ERROR:
+		gc->wants_to_die = TRUE;
+		gaim_connection_error(gc, _("Wrong password!"));
+		break;
+	case QQ_LOGIN_REPLY_MISC_ERROR:
+		gaim_connection_error(gc, _("Unable to login, check debug log"));
+		break;
+	case QQ_LOGIN_REPLY_OK:
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "Login replys OK, everything is fine\n");
+		break;
+	case QQ_LOGIN_REPLY_REDIRECT:
+		// the redirect has been done in _qq_process_login_reply
+		break;
+	default:{;
+		}
+	}			// switch ret
+}				// qq_process_login_reply
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/login_logout.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,44 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_LOGIN_LOGOUT_H_
+#define _QQ_LOGIN_LOGOUT_H_
+
+#include <glib.h>
+#include "connection.h"		// GaimConnection
+
+#define QQ_LOGIN_MODE_NORMAL        0x0a
+#define QQ_LOGIN_MODE_HIDDEN        0x28
+
+//void qq_send_packet_login(GaimConnection * gc);	//for internal usage, by gfhuang
+void qq_send_packet_request_login_token(GaimConnection *gc);	//by gfhuang
+
+void qq_send_packet_logout(GaimConnection * gc);
+
+void qq_process_login_reply(guint8 * buf, gint buf_len, GaimConnection * gc);
+void qq_process_request_login_token_reply(guint8 *buf, gint buf_len, GaimConnection *gc); // by gfhuang
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/packet_parse.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,138 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _WIN32
+#include <arpa/inet.h>
+#else
+#include "win32dep.h"
+#endif
+
+#include <string.h>
+
+#include "packet_parse.h"
+
+/*****************************************************************************/
+// read one byte from buf, 
+// return the number of bytes read if succeeds, otherwise return -1
+gint read_packet_b(guint8 * buf, guint8 ** cursor, gint buflen, guint8 * b)
+{
+	if (*cursor <= buf + buflen - sizeof(*b)) {
+		*b = **(guint8 **) cursor;
+		*cursor += sizeof(*b);
+		return sizeof(*b);
+	} else
+		return -1;
+}				// read_packet_b
+
+/*****************************************************************************/
+// read two bytes as "guint16" from buf, 
+// return the number of bytes read if succeeds, otherwise return -1
+gint read_packet_w(guint8 * buf, guint8 ** cursor, gint buflen, guint16 * w)
+{
+	if (*cursor <= buf + buflen - sizeof(*w)) {
+		*w = ntohs(**(guint16 **) cursor);
+		*cursor += sizeof(*w);
+		return sizeof(*w);
+	} else
+		return -1;
+}				// read_packet_w
+
+/*****************************************************************************/
+// read four bytes as "guint32" from buf, 
+// return the number of bytes read if succeeds, otherwise return -1
+gint read_packet_dw(guint8 * buf, guint8 ** cursor, gint buflen, guint32 * dw)
+{
+	if (*cursor <= buf + buflen - sizeof(*dw)) {
+		*dw = ntohl(**(guint32 **) cursor);
+		*cursor += sizeof(*dw);
+		return sizeof(*dw);
+	} else
+		return -1;
+}				// read_packet_dw
+
+/*****************************************************************************/
+// read datalen bytes from buf, 
+// return the number of bytes read if succeeds, otherwise return -1
+gint read_packet_data(guint8 * buf, guint8 ** cursor, gint buflen, guint8 * data, gint datalen) {
+	if (*cursor <= buf + buflen - datalen) {
+		g_memmove(data, *cursor, datalen);
+		*cursor += datalen;
+		return datalen;
+	} else
+		return -1;
+}				// read_packet_data
+
+/*****************************************************************************/
+// pack one byte into buf
+// return the number of bytes packed, otherwise return -1
+gint create_packet_b(guint8 * buf, guint8 ** cursor, guint8 b)
+{
+	if (*cursor <= buf + MAX_PACKET_SIZE - sizeof(guint8)) {
+		**(guint8 **) cursor = b;
+		*cursor += sizeof(guint8);
+		return sizeof(guint8);
+	} else
+		return -1;
+}				// create_packet_b
+
+/*****************************************************************************/
+// pack two bytes as "guint16" into buf
+// return the number of bytes packed, otherwise return -1
+gint create_packet_w(guint8 * buf, guint8 ** cursor, guint16 w)
+{
+	if (*cursor <= buf + MAX_PACKET_SIZE - sizeof(guint16)) {
+		**(guint16 **) cursor = htons(w);
+		*cursor += sizeof(guint16);
+		return sizeof(guint16);
+	} else
+		return -1;
+}				// create_packet_w
+
+/*****************************************************************************/
+// pack four bytes as "guint32" into buf
+// return the number of bytes packed, otherwise return -1
+gint create_packet_dw(guint8 * buf, guint8 ** cursor, guint32 dw)
+{
+	if (*cursor <= buf + MAX_PACKET_SIZE - sizeof(guint32)) {
+		**(guint32 **) cursor = htonl(dw);
+		*cursor += sizeof(guint32);
+		return sizeof(guint32);
+	} else
+		return -1;
+}				// create_packet_dw
+
+/*****************************************************************************/
+// pack datalen bytes into buf
+// return the number of bytes packed, otherwise return -1
+gint create_packet_data(guint8 * buf, guint8 ** cursor, guint8 * data, gint datalen) {
+	if (*cursor <= buf + MAX_PACKET_SIZE - datalen) {
+		g_memmove(*cursor, data, datalen);
+		*cursor += datalen;
+		return datalen;
+	} else
+		return -1;
+}				// create_packet_data
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/packet_parse.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,50 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_PACKET_PARSE_H_
+#define _QQ_PACKED_PARSE_H_
+
+#include <glib.h>
+
+/* According to "UNIX Network Programming", all TCP/IP implementations
+ * must support a minimum IP datagram size of 576 bytes, regardless of the MTU.
+ * Assuming a 20 byte IP header and 8 byte UDP header, this leaves 548 bytes 
+ * as a safe maximum size for UDP messages.
+ *
+ * TCP allows packet 64K
+ */
+#define MAX_PACKET_SIZE 65535
+
+gint read_packet_b(guint8 * buf, guint8 ** cursor, gint buflen, guint8 * b);
+gint read_packet_w(guint8 * buf, guint8 ** cursor, gint buflen, guint16 * w);
+gint read_packet_dw(guint8 * buf, guint8 ** cursor, gint buflen, guint32 * dw);
+gint read_packet_data(guint8 * buf, guint8 ** cursor, gint buflen, guint8 * data, gint datalen);
+gint create_packet_b(guint8 * buf, guint8 ** cursor, guint8 b);
+gint create_packet_w(guint8 * buf, guint8 ** cursor, guint16 w);
+gint create_packet_dw(guint8 * buf, guint8 ** cursor, guint32 dw);
+gint create_packet_data(guint8 * buf, guint8 ** cursor, guint8 * data, gint datalen);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/qq.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,1195 @@
+/**
+ * @file qq.c The QQ2003C protocol plugin
+ *
+ * gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "internal.h"
+
+#ifdef _WIN32
+#define random rand
+#endif
+
+#include "debug.h"
+#include "notify.h"
+#include "prefs.h"
+#include "request.h"
+#include "accountopt.h"
+#include "prpl.h"
+#include "gtkroomlist.h"
+#include "gtklog.h"
+#include "server.h"
+#include "util.h" 		/* GaimMenuAction, gaim_menu_action_new, gaim2beta2, gfhuang*/
+
+#include "utils.h"
+#include "buddy_info.h"
+#include "buddy_opt.h"
+#include "buddy_status.h"
+#include "char_conv.h"
+#include "group_find.h"		/* qq_group_find_member_by_channel_and_nickname */
+#include "group_im.h"		/* qq_send_packet_group_im */
+#include "group_info.h"		/* qq_send_cmd_group_get_group_info */
+#include "group_join.h"		/* qq_group_join */
+#include "group_opt.h"		/* qq_group_manage_members */
+#include "group.h"		/* chat_info, etc */
+#include "header_info.h"	/* qq_get_cmd_desc */
+#include "im.h"
+#include "infodlg.h"
+#include "keep_alive.h"
+#include "ip_location.h"	/* qq_ip_get_location */
+#include "login_logout.h"
+#include "qq_proxy.h"		/* qq_connect, qq_disconnect */
+#include "send_core.h"
+#include "qq.h"
+#include "send_file.h"
+#include "version.h"
+
+#define OPENQ_AUTHOR            "Puzzlebird"
+#define OPENQ_WEBSITE            "http://openq.sourceforge.net"
+#define QQ_TCP_QUERY_PORT       "8000"
+#define QQ_UDP_PORT             "8000"
+
+const gchar *udp_server_list[] = {
+	"sz.tencent.com",	// 61.144.238.145
+	"sz2.tencent.com",	// 61.144.238.146
+	"sz3.tencent.com",	// 202.104.129.251
+	"sz4.tencent.com",	// 202.104.129.254
+	"sz5.tencent.com",	// 61.141.194.203
+	"sz6.tencent.com",	// 202.104.129.252
+	"sz7.tencent.com",	// 202.104.129.253
+	"202.96.170.64",
+	"64.144.238.155",
+	"202.104.129.254"
+};
+const gint udp_server_amount = (sizeof(udp_server_list) / sizeof(udp_server_list[0]));
+
+
+const gchar *tcp_server_list[] = {
+	"tcpconn.tencent.com",	// 218.17.209.23
+	"tcpconn2.tencent.com",	// 218.18.95.153
+	"tcpconn3.tencent.com",	// 218.17.209.23
+	"tcpconn4.tencent.com",	// 218.18.95.153
+};
+const gint tcp_server_amount = (sizeof(tcp_server_list) / sizeof(tcp_server_list[0]));
+
+/*********** Prototypes ***********/
+
+static void _qq_login(GaimAccount * account);
+static void _qq_close(GaimConnection * gc);
+static const gchar *_qq_list_icon(GaimAccount * a, GaimBuddy * b);
+static gchar *_qq_status_text(GaimBuddy *b);
+static void _qq_tooltip_text(GaimBuddy *b, GString *tooltip, gboolean full);
+static void _qq_list_emblems(GaimBuddy * b, const char **se, const char **sw, const char **nw, const char **ne);
+static GList *_qq_away_states(GaimAccount *ga);
+static void _qq_set_away(GaimAccount *account, GaimStatus *status);
+static gint _qq_send_im(GaimConnection * gc, const gchar * who, const gchar * message, GaimMessageFlags flags); 
+static int _qq_chat_send(GaimConnection *gc, int channel, const char *message, GaimMessageFlags flags);
+static void _qq_get_info(GaimConnection * gc, const gchar * who);
+static void _qq_menu_get_my_info(GaimPluginAction * action);
+//static void _qq_menu_block_buddy(GaimBlistNode * node);
+static void _qq_menu_show_login_info(GaimPluginAction * action);
+static void _qq_menu_show_about(GaimPluginAction * action);
+static void _qq_menu_any_cmd_send_cb(GaimConnection * gc, GaimRequestFields * fields);
+static void _qq_menu_any_cmd(GaimPluginAction * action);
+static void _qq_menu_locate_ip_cb(GaimConnection * gc, GaimRequestFields * fields);
+static void _qq_menu_locate_ip(GaimPluginAction *action);
+static void _qq_menu_search_or_add_permanent_group(GaimPluginAction * action);
+static void _qq_menu_create_permanent_group(GaimPluginAction * action);
+static void _qq_menu_unsubscribe_group(GaimBlistNode * node);
+static void _qq_menu_manage_group(GaimBlistNode * node);
+static void _qq_menu_show_system_message(GaimPluginAction *action);
+//static void _qq_menu_send_file(GaimBlistNode * node, gpointer ignored);
+static GList *_qq_actions(GaimPlugin * plugin, gpointer context);
+static GList *_qq_chat_menu(GaimBlistNode *node);
+static GList *_qq_buddy_menu(GaimBlistNode * node);
+static void _qq_keep_alive(GaimConnection * gc);
+static void _qq_get_chat_buddy_info(GaimConnection * gc, gint channel, const gchar * who);
+static gchar *_qq_get_chat_buddy_real_name(GaimConnection * gc, gint channel, const gchar * who);
+//static GaimPluginPrefFrame *get_plugin_pref_frame(GaimPlugin * plugin);
+static void init_plugin(GaimPlugin * plugin);
+
+static void _qq_login(GaimAccount * account)
+{
+	const gchar *qq_server, *qq_port;
+	qq_data *qd;
+	GaimConnection *gc;
+	GaimPresence *presence;				//gfhuang
+	gboolean login_hidden, use_tcp;
+
+	g_return_if_fail(account != NULL);
+
+	gc = gaim_account_get_connection(account);
+	g_return_if_fail(gc != NULL);
+
+	gc->flags |= GAIM_CONNECTION_HTML | GAIM_CONNECTION_NO_BGCOLOR | GAIM_CONNECTION_AUTO_RESP;
+
+	qd = g_new0(qq_data, 1);
+	gc->proto_data = qd;
+
+	qq_server = gaim_account_get_string(account, "server", NULL);
+	qq_port = gaim_account_get_string(account, "port", NULL);
+	use_tcp = gaim_account_get_bool(account, "use_tcp", FALSE);
+//	login_hidden = gaim_account_get_bool(account, "hidden", FALSE);		gfhuang
+        presence = gaim_account_get_presence(account);
+        login_hidden = gaim_presence_is_status_primitive_active(presence, GAIM_STATUS_INVISIBLE);
+
+	qd->use_tcp = use_tcp;
+
+	if (login_hidden) {
+		qd->login_mode = QQ_LOGIN_MODE_HIDDEN;
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "Login in hidden mode\n");
+	}
+	else {
+		qd->login_mode = QQ_LOGIN_MODE_NORMAL;
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "Login in normal mode\n");
+	}
+
+	if (qq_server == NULL || strlen(qq_server) == 0)
+		qq_server = use_tcp ?
+		    tcp_server_list[random() % tcp_server_amount] : udp_server_list[random() % udp_server_amount];
+
+	if (qq_port == NULL || strtol(qq_port, NULL, 10) == 0)
+		qq_port = use_tcp ? QQ_TCP_QUERY_PORT : QQ_UDP_PORT;
+
+	gaim_connection_update_progress(gc, _("Connecting"), 0, QQ_CONNECT_STEPS);
+
+	if (qq_connect(account, qq_server, strtol(qq_port, NULL, 10), use_tcp, FALSE) < 0)
+		gaim_connection_error(gc, _("Unable to connect."));
+}
+
+/* directly goes for qq_disconnect */
+static void _qq_close(GaimConnection * gc)
+{
+	g_return_if_fail(gc != NULL);
+	qq_disconnect(gc);
+}
+
+/* returns the icon name for a buddy or protocol */
+static const gchar *_qq_list_icon(GaimAccount * a, GaimBuddy * b)
+{
+	gchar *filename;
+	qq_buddy *q_bud;
+	gchar icon_suffix;
+
+	/* do not use g_return_val_if_fail, as it is not assertion */
+	if (b == NULL || b->proto_data == NULL)
+		return "qq";
+
+	q_bud = (qq_buddy *) b->proto_data;
+	icon_suffix = get_suffix_from_status(q_bud->status);
+	filename = get_icon_name(q_bud->icon / 3 + 1, icon_suffix);
+
+	return filename;
+}
+
+
+/* a short status text beside buddy icon*/
+static gchar *_qq_status_text(GaimBuddy *b)
+{
+	qq_buddy *q_bud;
+//	gboolean show_info;
+	GString *status;
+	gchar *ret;
+
+//	show_info = gaim_prefs_get_bool("/plugins/prpl/qq/show_status_by_icon");
+//	if (!show_info)
+//		return NULL;
+
+	q_bud = (qq_buddy *) b->proto_data;
+	if (q_bud == NULL)
+		return NULL;
+
+	status = g_string_new("");
+
+	//by gfhuang
+	switch(q_bud->status) {
+	case QQ_BUDDY_OFFLINE:
+		g_string_append(status, "My Offline");
+		break;
+	case QQ_BUDDY_ONLINE_NORMAL:
+		return NULL;
+		break;
+	case QQ_BUDDY_ONLINE_OFFLINE:
+		g_string_append(status, "Online Offline");
+		break;
+	case QQ_BUDDY_ONLINE_AWAY:
+		g_string_append(status, "Away");
+		break;
+	case QQ_BUDDY_ONLINE_INVISIBLE:
+		g_string_append(status, "Invisible");
+		break;
+	default:
+		g_string_printf(status, "Unknown-%d", q_bud->status);
+	}	
+	/*
+	switch (q_bud->gender) {
+	case QQ_BUDDY_GENDER_GG:
+		g_string_append(status, " GG");
+		break;
+	case QQ_BUDDY_GENDER_MM:
+		g_string_append(status, " MM");
+		break;
+	case QQ_BUDDY_GENDER_UNKNOWN:
+		g_string_append(status, "^_*");
+		break;
+	default:
+		g_string_append(status, "^_^");
+	}			
+
+	g_string_append_printf(status, " Age: %d", q_bud->age);
+	g_string_append_printf(status, " Client: %04x", q_bud->client_version);
+	*/
+//	having_video = q_bud->comm_flag & QQ_COMM_FLAG_VIDEO;
+//	if (having_video)
+//		g_string_append(status, " (video)");
+
+	ret = status->str;
+	g_string_free(status, FALSE);
+
+	return ret;
+}
+
+
+/* a floating text when mouse is on the icon, show connection status here */
+static void _qq_tooltip_text(GaimBuddy *b, GString *tooltip, gboolean full)
+{
+	qq_buddy *q_bud;
+	gchar *country, *country_utf8, *city, *city_utf8;
+	guint32 ip_value;
+	gchar *ip_str;
+
+	g_return_if_fail(b != NULL);
+
+	q_bud = (qq_buddy *) b->proto_data;
+	g_return_if_fail(q_bud != NULL);
+
+//	if (is_online(q_bud->status)) //disable by gfhuang
+	{
+		ip_value = ntohl(*(guint32 *) (q_bud->ip));
+//		tooltip = g_string_new("");                     beta2, gfhuang
+		if (qq_ip_get_location(ip_value, &country, &city)) {
+			country_utf8 = qq_to_utf8(country, QQ_CHARSET_DEFAULT);
+			city_utf8 = qq_to_utf8(city, QQ_CHARSET_DEFAULT);
+			g_string_append_printf(tooltip, "\n%s, %s", country_utf8, city_utf8);
+			g_free(country);
+			g_free(city);
+			g_free(country_utf8);
+			g_free(city_utf8);
+		}
+		//memory leak fixed by gfhuang
+		ip_str = gen_ip_str(q_bud->ip);
+		g_string_append_printf(tooltip, "\n<b>%s Address:</b> %s:%d", (q_bud->comm_flag & QQ_COMM_FLAG_TCP_MODE)
+				       ? "TCP" : "UDP", ip_str, q_bud->port);
+		g_free(ip_str);
+		//added by gfhuang
+		g_string_append_printf(tooltip, "\n<b>Age:</b> %d", q_bud->age);
+        	switch (q_bud->gender) {
+	        case QQ_BUDDY_GENDER_GG:
+                	g_string_append(tooltip, "\n<b>Gender:</b> GG");
+        	        break;
+	        case QQ_BUDDY_GENDER_MM:
+                	g_string_append(tooltip, "\n<b>Gender:</b> MM");
+        	        break;
+	        case QQ_BUDDY_GENDER_UNKNOWN:
+                	g_string_append(tooltip, "\n<b>Gender:</b> UNKNOWN");
+        	        break;
+	        default:
+                	g_string_append_printf(tooltip, "\n<b>Gender:</b> ERROR(%d)", q_bud->gender);
+	        }                       /* switch gender */
+		g_string_append_printf(tooltip, "\n<b>Flag:</b> %01x", q_bud->flag1);
+		g_string_append_printf(tooltip, "\n<b>CommFlag:</b> %01x", q_bud->comm_flag);
+	        g_string_append_printf(tooltip, "\n<b>Client:</b> %04x", q_bud->client_version);
+	}
+}
+
+/* we can show tiny icons on the four corners of buddy icon, */
+static void _qq_list_emblems(GaimBuddy * b, const char **se, const char **sw, const char **nw, const char **ne) {   //add const by gfhuang
+	// each char ** are refering to filename in pixmaps/gaim/status/default/*png
+
+	qq_buddy *q_bud = b->proto_data;
+//        GaimPresence *presence;
+        const char *emblems[4] = { NULL, NULL, NULL, NULL };
+        int i = 0;
+
+/*        presence = gaim_buddy_get_presence(b);
+
+        if (!gaim_presence_is_online(presence))
+                emblems[i++] = "offline";
+        else if (gaim_presence_is_status_active(presence, "busy") ||
+                         gaim_presence_is_status_active(presence, "phone"))
+                emblems[i++] = "occupied";
+        else if (!gaim_presence_is_available(presence))
+                emblems[i++] = "away";
+*/
+
+        if (q_bud == NULL)
+        {
+                emblems[0] = "offline";
+        }
+        else
+        {
+		if (q_bud->comm_flag & QQ_COMM_FLAG_QQ_MEMBER)
+			emblems[i++] = "qq_member";
+                if (q_bud->comm_flag & QQ_COMM_FLAG_BIND_MOBILE)
+                        emblems[i++] = "wireless";
+		if (q_bud->comm_flag & QQ_COMM_FLAG_VIDEO)
+			emblems[i++] = "video";
+
+
+//                if (!(user->list_op & (1 << MSN_LIST_RL)))
+//                        emblems[i++] = "nr";
+        }
+
+        *se = emblems[0];
+        *sw = emblems[1];
+        *nw = emblems[2];
+        *ne = emblems[3];
+
+	return;
+}
+
+/* QQ away status (used to initiate QQ away packet) */
+//Rewritten by gfhuang
+static GList *_qq_away_states(GaimAccount *ga) 
+{
+	GaimStatusType *status;
+	GList *types = NULL;
+
+	status = gaim_status_type_new_full(GAIM_STATUS_AVAILABLE,
+			"available", _("QQ: Available"), FALSE, TRUE, FALSE);
+	types = g_list_append(types, status);
+
+        status = gaim_status_type_new_full(GAIM_STATUS_AWAY, 
+			"away", _("QQ: Away"), FALSE, TRUE, FALSE);
+        types = g_list_append(types, status);
+
+        status = gaim_status_type_new_full(GAIM_STATUS_INVISIBLE, /* HIDDEN, change to gaim2beta2, gfhuang */ 
+			"invisible", _("QQ: Invisible"), FALSE, TRUE, FALSE);
+        types = g_list_append(types, status);
+
+	status = gaim_status_type_new_full(GAIM_STATUS_OFFLINE,
+                        "offline", _("QQ: Offline"), FALSE, TRUE, FALSE);
+        types = g_list_append(types, status);
+
+	return types;
+}
+/*
+GList *_qq_away_states(GaimConnection * gc)
+{
+	GList *m;
+
+	g_return_val_if_fail(gc != NULL, NULL);
+
+	m = NULL;
+	m = g_list_append(m, _("QQ: Available"));
+	m = g_list_append(m, _("QQ: Away"));
+	m = g_list_append(m, _("QQ: Invisible"));
+	m = g_list_append(m, GAIM_AWAY_CUSTOM);       
+	return m;
+}
+*/
+
+
+
+/* initiate QQ away with proper change_status packet */
+//void _qq_set_away(GaimConnection * gc, const char *state, const char *msg)
+static void _qq_set_away(GaimAccount *account, GaimStatus *status)
+{
+	// by gfhuang
+	GaimConnection *gc = gaim_account_get_connection(account);
+	const char *state = gaim_status_get_id(status);
+
+	qq_data *qd;
+	
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+
+	// by gfhuang
+	if(0 == strcmp(state, "available"))
+		qd->status = QQ_SELF_STATUS_AVAILABLE;
+	else if (0 == strcmp(state, "away"))
+		qd->status = QQ_SELF_STATUS_AWAY;
+	else if (0 == strcmp(state, "invisible"))
+		qd->status = QQ_SELF_STATUS_INVISIBLE;
+	else
+		qd->status = QQ_SELF_STATUS_AVAILABLE;
+
+/*	if (gc->away) {                //disable by gfhuang, 1.2006
+		g_free(gc->away);
+		gc->away = NULL;
+	}
+
+	if (msg) {
+		qd->status = QQ_SELF_STATUS_CUSTOM;
+		gc->away = g_strdup(msg);
+	} else if (state) {
+		gc->away = g_strdup("");
+		if (g_ascii_strcasecmp(state, _("QQ: Available")) == 0)
+			qd->status = QQ_SELF_STATUS_AVAILABLE;
+		else if (g_ascii_strcasecmp(state, _("QQ: Away")) == 0)
+			qd->status = QQ_SELF_STATUS_AWAY;
+		else if (g_ascii_strcasecmp(state, _("QQ: Invisible")) == 0)
+			qd->status = QQ_SELF_STATUS_INVISIBLE;
+		else if (g_ascii_strcasecmp(state, GAIM_AWAY_CUSTOM) == 0) {
+			if (gc->is_idle)
+				qd->status = QQ_SELF_STATUS_IDLE;
+			else
+				qd->status = QQ_SELF_STATUS_AVAILABLE;
+		}
+	} else if (gc->is_idle)
+		qd->status = QQ_SELF_STATUS_IDLE; 
+	else
+		qd->status = QQ_SELF_STATUS_AVAILABLE;
+*/
+	qq_send_packet_change_status(gc);
+}
+
+
+// IMPORTANT: GaimConvImFlags -> GaimMessageFlags    //gfhuang
+/* send an instance msg to a buddy */
+static gint _qq_send_im(GaimConnection * gc, const gchar * who, const gchar * message, GaimMessageFlags flags)
+{
+	gint type, to_uid;
+	gchar *msg, *msg_with_qq_smiley;
+	qq_data *qd;
+
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL && who != NULL, -1);
+
+	qd = (qq_data *) gc->proto_data;
+
+	g_return_val_if_fail(strlen(message) <= QQ_MSG_IM_MAX, -E2BIG);
+
+	type = (flags == GAIM_MESSAGE_AUTO_RESP ? QQ_IM_AUTO_REPLY : QQ_IM_TEXT);
+	to_uid = gaim_name_to_uid(who);
+
+	/* if msg is to myself, bypass the network */
+	if (to_uid == qd->uid)
+		serv_got_im(gc, who, message, flags, time(NULL));
+	else {
+		msg = utf8_to_qq(message, QQ_CHARSET_DEFAULT);
+		msg_with_qq_smiley = gaim_smiley_to_qq(msg);
+		qq_send_packet_im(gc, to_uid, msg_with_qq_smiley, type);
+		g_free(msg);
+		g_free(msg_with_qq_smiley);
+	}
+
+	return 1;
+}
+
+/* send a chat msg to a QQ Qun */
+static int _qq_chat_send(GaimConnection *gc, int channel, const char *message, GaimMessageFlags flags/*gfhuang*/)
+{
+	gchar *msg, *msg_with_qq_smiley;
+	qq_group *group;
+
+	g_return_val_if_fail(gc != NULL && message != NULL, -1);
+	g_return_val_if_fail(strlen(message) <= QQ_MSG_IM_MAX, -E2BIG);
+
+	group = qq_group_find_by_channel(gc, channel);
+	g_return_val_if_fail(group != NULL, -1);
+
+	msg = utf8_to_qq(message, QQ_CHARSET_DEFAULT);
+	msg_with_qq_smiley = gaim_smiley_to_qq(msg);
+	qq_send_packet_group_im(gc, group, msg_with_qq_smiley);
+	g_free(msg);
+	g_free(msg_with_qq_smiley);
+
+	return 1;
+}
+
+/* send packet to get who's detailed information */
+static void _qq_get_info(GaimConnection * gc, const gchar * who)
+{
+	guint32 uid;
+	qq_data *qd;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = gc->proto_data;
+	uid = gaim_name_to_uid(who);
+
+	if (uid <= 0) {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Not valid QQid: %s\n", who);
+		gaim_notify_error(gc, NULL, _("Invalid name, please input in qq-xxxxxxxx format"), NULL);
+		return;
+	}
+
+	qq_send_packet_get_info(gc, uid, TRUE);	/* need to show up info window */
+}
+
+/* get my own information */
+static void _qq_menu_get_my_info(GaimPluginAction * action)
+{
+	GaimConnection *gc = (GaimConnection *) action->context;
+	qq_data *qd;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+	_qq_get_info(gc, uid_to_gaim_name(qd->uid));
+}
+
+/* remove a buddy from my list and remove myself from his list */
+/* TODO: re-enable this
+static void _qq_menu_block_buddy(GaimBlistNode * node)
+{
+	guint32 uid;
+	gc_and_uid *g;
+	GaimBuddy *buddy;
+	GaimConnection *gc;
+//	const gchar *who = param_who;	gfhuang
+	const gchar *who;
+
+	g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
+
+	buddy = (GaimBuddy *) node;
+	gc = gaim_account_get_connection(buddy->account);
+	who = buddy->name;
+	g_return_if_fail(gc != NULL && who != NULL);
+
+	uid = gaim_name_to_uid(who);
+	g_return_if_fail(uid > 0);
+
+	g = g_new0(gc_and_uid, 1);
+	g->gc = gc;
+	g->uid = uid;
+
+	gaim_request_action(gc, _("Block Buddy"),
+			    _("Are you sure to block this buddy?"), NULL,
+			    1, g, 2,
+			    _("Cancel"),
+			    G_CALLBACK(qq_do_nothing_with_gc_and_uid),
+			    _("Block"), G_CALLBACK(qq_block_buddy_with_gc_and_uid));
+}
+*/
+
+/* show a brief summary of what we get from login packet */
+static void _qq_menu_show_login_info(GaimPluginAction * action)
+{
+	GaimConnection *gc = (GaimConnection *) action->context;
+	qq_data *qd;
+	GString *info;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+	info = g_string_new("<html><body>\n");
+
+	g_string_append_printf(info, _("<b>Current Online</b>: %d<br>\n"), qd->all_online);
+	g_string_append_printf(info, _("<b>Last Refresh</b>: %s<br>\n"), ctime(&qd->last_get_online));
+
+	g_string_append(info, "<hr>\n");
+
+	g_string_append_printf(info, _("<b>Connection Mode</b>: %s<br>\n"), qd->use_tcp ? "TCP" : "UDP");
+	g_string_append_printf(info, _("<b>Server IP</b>: %s: %d<br>\n"), qd->server_ip, qd->server_port);
+	g_string_append_printf(info, _("<b>My Public IP</b>: %s<br>\n"), qd->my_ip);
+
+	g_string_append(info, "<hr>\n");
+	g_string_append(info, "<i>Information below may not be accurate</i><br>\n");
+
+	g_string_append_printf(info, _("<b>Login Time</b>: %s<br>\n"), ctime(&qd->login_time));
+	g_string_append_printf(info, _("<b>Last Login IP</b>: %s<br>\n"), qd->last_login_ip);
+	g_string_append_printf(info, _("<b>Last Login Time</b>: %s\n"), ctime(&qd->last_login_time));
+
+	g_string_append(info, "</body></html>");
+
+	gaim_notify_formatted(gc, NULL, _("Login Information"), NULL, info->str, NULL, NULL);
+
+	g_string_free(info, TRUE);
+}
+
+/* show about page about QQ plugin */
+static void _qq_menu_show_about(GaimPluginAction * action)
+{
+	GaimConnection *gc = (GaimConnection *) action->context;
+	qq_data *qd;
+	GString *info;
+	gchar *head;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+	info = g_string_new("<html><body>\n");
+
+	g_string_append_printf(info, _("<b>Author</b> : %s<br>\n"), OPENQ_AUTHOR);
+	g_string_append(info, "Copyright (c) 2004.  All rights reserved.<br><br>\n");
+
+	g_string_append(info, _("<p><b>Code Contributors</b><br>\n"));
+	g_string_append(info, "gfhuang   : patches for gaim 2.0.0beta2<br>\n");
+	g_string_append(info, "henryouly : file transfer, udp sock5 proxy and qq_show<br>\n");
+	g_string_append(info, "arfankai  : fixed bugs in char_conv.c<br>\n");
+	g_string_append(info, "rakescar  : provided filter for HTML tag<br>\n");
+	g_string_append(info, "yyw       : improved performance on PPC linux<br>\n");
+	g_string_append(info, "lvxiang   : provided ip to location original code<br><br>\n");
+
+	g_string_append(info, _("<p><b>Acknowledgement</b><br>\n"));
+	g_string_append(info, "Shufeng Tan : http://sf.net/projects/perl-oicq<br>\n");
+	g_string_append(info, "Jeff Ye : http://www.sinomac.com<br>\n");
+	g_string_append(info, "Hu Zheng : http://forlinux.yeah.net<br><br>\n");
+
+	g_string_append(info, "<p>And, my parents...\n");
+
+	g_string_append(info, "</body></html>");
+
+	head = g_strdup_printf("About QQ Plugin Ver %s", VERSION);
+	gaim_notify_formatted(gc, NULL, head, NULL, info->str, NULL, NULL);
+
+	g_free(head);
+	g_string_free(info, TRUE);
+}
+
+/* callback of sending any command to QQ server */
+static void _qq_menu_any_cmd_send_cb(GaimConnection * gc, GaimRequestFields * fields)
+{
+	GList *groups, *flds;
+	GaimRequestField *field;
+	const gchar *id, *value;
+	gchar *cmd_str, *data_str, **segments;
+	guint16 cmd;
+	guint8 *data;
+	gint i, data_len;
+
+	cmd_str = NULL;
+	data_str = NULL;
+	cmd = 0x00;
+	data = NULL;
+	data_len = 0;
+
+	for (groups = gaim_request_fields_get_groups(fields); groups; groups = groups->next) {
+		for (flds = gaim_request_field_group_get_fields(groups->data); flds; flds = flds->next) {
+			field = flds->data;
+			id = gaim_request_field_get_id(field);
+			value = gaim_request_field_string_get_value(field);
+
+			if (!g_ascii_strcasecmp(id, "cmd"))
+				cmd_str = g_strdup(value);
+			else if (!g_ascii_strcasecmp(id, "data"))
+				data_str = g_strdup(value);
+		}
+	}
+
+	if (cmd_str != NULL)
+		cmd = (guint16) strtol(cmd_str, NULL, 16);
+
+	if (data_str != NULL) {
+		if (NULL == (segments = split_data(data_str, strlen(data_str), ",", 0))) {
+			g_free(cmd_str);
+			g_free(data_str);
+			return;
+		}
+		for (data_len = 0; segments[data_len] != NULL; data_len++) {;
+		}
+		data = g_newa(guint8, data_len);
+		for (i = 0; i < data_len; i++)
+			data[i] = (guint8) strtol(segments[i], NULL, 16);
+		g_strfreev(segments);
+	}
+
+	if (cmd && data_len > 0) {
+		gaim_debug(GAIM_DEBUG_INFO, "QQ",
+			   "Send Any cmd: %s, data dump\n%s", qq_get_cmd_desc(cmd), hex_dump_to_str(data, data_len));
+		qq_send_cmd(gc, cmd, TRUE, 0, TRUE, data, data_len);
+	}
+
+	g_free(cmd_str);
+	g_free(data_str);
+}				
+
+/* send any command with data to QQ server, for testing and debuggin only */
+static void _qq_menu_any_cmd(GaimPluginAction * action)
+{
+	GaimConnection *gc = (GaimConnection *) action->context;
+	qq_data *qd;
+	const char *tips;
+	GaimRequestField *field;
+	GaimRequestFields *fields;
+	GaimRequestFieldGroup *group;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	tips = _("Separate the value with \",\"\nAllow \"0x\" before each value");
+	fields = gaim_request_fields_new();
+	group = gaim_request_field_group_new(NULL);
+	gaim_request_fields_add_group(fields, group);
+
+	/* sample: 0x22 */
+	field = gaim_request_field_string_new("cmd", _("CMD Code"), NULL, FALSE);
+	gaim_request_field_group_add_field(group, field);
+	/* sample: 0x00,0x15,0xAB */
+	/*     or: 00,15,AB */
+	/* the delimit is ",", allow 0x before the value */
+	field = gaim_request_field_string_new("data", _("Raw Data"), NULL, FALSE);
+	gaim_request_field_group_add_field(group, field);
+
+	gaim_request_fields(gc, _("QQ Any Command"),
+			    _("Send Arbitrary Command"), tips, fields,
+			    _("Send"), G_CALLBACK(_qq_menu_any_cmd_send_cb), _("Cancel"), NULL, gc);
+}
+
+/* added by gfhuang */
+static void _qq_menu_locate_ip_cb(GaimConnection * gc, GaimRequestFields * fields)
+{
+        GList *groups, *flds;
+        GaimRequestField *field;
+        const gchar *id, *value;
+        gchar *ip_str = NULL, *ip_dupstr = NULL;
+	guint8 *ip;
+        gchar *country, *country_utf8, *city, *city_utf8;
+        guint32 ip_value;
+
+        for (groups = gaim_request_fields_get_groups(fields); groups && !ip_str; groups = groups->next) {
+                for (flds = gaim_request_field_group_get_fields(groups->data); flds && !ip_str; flds = flds->next) {
+                        field = flds->data;
+                        id = gaim_request_field_get_id(field);
+                        value = gaim_request_field_string_get_value(field);
+
+                        if (!g_ascii_strcasecmp(id, "ip")) {
+                                ip_str = g_strdup(value);
+				break;
+			}
+                }
+        }
+	
+	if(ip_str) {
+		ip = str_ip_gen(ip_str);
+		ip_dupstr = gen_ip_str(ip);
+        
+		ip_value = ntohl(*(guint32 *)ip);
+        	if (qq_ip_get_location(ip_value, &country, &city)) {
+                        country_utf8 = qq_to_utf8(country, QQ_CHARSET_DEFAULT);
+                        city_utf8 = qq_to_utf8(city, QQ_CHARSET_DEFAULT);
+			gaim_notify_info(gc, ip_dupstr, country_utf8, city_utf8);
+                        g_free(country);
+                        g_free(city);
+                        g_free(country_utf8);
+                        g_free(city_utf8);
+        	}
+		else 
+			gaim_notify_info(gc, ip_dupstr, "IP not found", NULL);
+		g_free(ip);
+		g_free(ip_dupstr);
+		g_free(ip_str);
+	}
+}
+
+/* added by gfhuang */
+static void _qq_menu_locate_ip(GaimPluginAction *action)
+{
+        GaimConnection *gc = (GaimConnection *) action->context;
+        GaimRequestField *field;
+        GaimRequestFields *fields;
+        GaimRequestFieldGroup *group;
+
+        g_return_if_fail(gc != NULL);
+
+        fields = gaim_request_fields_new();
+        group = gaim_request_field_group_new(NULL);
+        gaim_request_fields_add_group(fields, group);
+        
+	field = gaim_request_field_string_new("ip", _("IP Address"), NULL, FALSE);
+        gaim_request_field_group_add_field(group, field);
+        
+	gaim_request_fields(gc, _("Locate an IP"),
+			_("Locate an IP address"), NULL, fields,
+			 _("Check"), G_CALLBACK(_qq_menu_locate_ip_cb), _("Cancel"), NULL, gc);
+}
+
+static void _qq_menu_search_or_add_permanent_group(GaimPluginAction * action)
+{
+	gaim_gtk_roomlist_dialog_show();
+}
+
+static void _qq_menu_create_permanent_group(GaimPluginAction * action)
+{
+	GaimConnection *gc = (GaimConnection *) action->context;
+	g_return_if_fail(gc != NULL);
+	gaim_request_input(gc, _("Create QQ Qun"),
+			   _("Input Qun name here"),
+			   _("Only QQ member can create permanent Qun"),
+			   "OpenQ", FALSE, FALSE, NULL,
+			   _("Create"), G_CALLBACK(qq_group_create_with_name), _("Cancel"), NULL, gc);
+}
+
+static void _qq_menu_unsubscribe_group(GaimBlistNode * node) // by gfhuang, gpointer param_components)
+{
+//	GaimBuddy *buddy;		by gfhuang
+	GaimChat *chat = (GaimChat *)node;
+	GaimConnection *gc = gaim_account_get_connection(chat->account);
+	GHashTable *components = chat -> components;
+//	GHashTable *components = (GHashTable *) param_components;
+
+//	g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));	bug! found by gfhuang
+	g_return_if_fail(GAIM_BLIST_NODE_IS_CHAT(node));
+
+//	buddy = (GaimBuddy *) node;
+//	gc = gaim_account_get_connection(buddy->account);
+
+	g_return_if_fail(gc != NULL && components != NULL);
+	qq_group_exit(gc, components);
+}
+
+static void _qq_menu_manage_group(GaimBlistNode * node) // by gfhuang, gpointer param_components)
+{
+//	GaimBuddy *buddy;		by gfhuang
+	GaimChat *chat = (GaimChat *)node;
+	GaimConnection *gc = gaim_account_get_connection(chat->account);
+	GHashTable *components = chat -> components;
+//	GHashTable *components = (GHashTable *) param_components;
+
+//	g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));	bug! found by gfhuang
+	g_return_if_fail(GAIM_BLIST_NODE_IS_CHAT(node));
+
+//	buddy = (GaimBuddy *) node;
+//	gc = gaim_account_get_connection(buddy->account);
+
+	g_return_if_fail(gc != NULL && components != NULL);
+	qq_group_manage_group(gc, components);
+}
+
+static void _qq_menu_show_system_message(GaimPluginAction *action)
+{
+	GaimConnection *gc = (GaimConnection *) action->context;
+	g_return_if_fail ( gc != NULL );
+	gaim_gtk_log_show(GAIM_LOG_IM, "systemim", gaim_connection_get_account(gc));
+}
+
+/* TODO: re-enable this
+static void _qq_menu_send_file(GaimBlistNode * node, gpointer ignored)
+{
+	GaimBuddy *buddy;
+	GaimConnection *gc;
+	qq_buddy *q_bud;
+
+	g_return_if_fail (GAIM_BLIST_NODE_IS_BUDDY (node));
+	buddy = (GaimBuddy *) node;
+	q_bud = (qq_buddy *) buddy->proto_data;
+//	if (is_online (q_bud->status)) {
+	gc = gaim_account_get_connection (buddy->account);
+	g_return_if_fail (gc != NULL && gc->proto_data != NULL);
+	qq_send_file(gc, buddy->name, NULL);
+//	}
+}
+*/
+
+/* protocol related menus */
+static GList *_qq_actions(GaimPlugin * plugin, gpointer context)
+{
+	GList *m;
+	GaimPluginAction *act;
+
+	m = NULL;
+	act = gaim_plugin_action_new(_("Modify My Information"), _qq_menu_get_my_info);
+	m = g_list_append(m, act);
+
+	act = gaim_plugin_action_new(_("Show Login Information"), _qq_menu_show_login_info);
+	m = g_list_append(m, act);
+
+	act = gaim_plugin_action_new(_("Show System Message"), _qq_menu_show_system_message);
+	m = g_list_append(m, act);
+
+	act = gaim_plugin_action_new(_("Any QQ Command"), _qq_menu_any_cmd);
+	m = g_list_append(m, act);
+
+	act = gaim_plugin_action_new(_("Qun: Search a permanent Qun"), _qq_menu_search_or_add_permanent_group);
+	m = g_list_append(m, act);
+
+	act = gaim_plugin_action_new(_("Qun: Create a permanent Qun"), _qq_menu_create_permanent_group);
+	m = g_list_append(m, act);
+
+	act = gaim_plugin_action_new(_("Locate an IP"), _qq_menu_locate_ip);
+        m = g_list_append(m, act);
+	
+	act = gaim_plugin_action_new(_("About QQ Plugin"), _qq_menu_show_about);
+	m = g_list_append(m, act);
+
+	return m;
+}
+
+/* chat-related (QQ Qun) menu shown up with right-click */
+static GList *_qq_chat_menu(GaimBlistNode *node)	//gfhuang
+{
+	GList *m;
+	GaimMenuAction *act;
+
+	m = NULL;
+	act = gaim_menu_action_new(_("Exit this QQ Qun"), GAIM_CALLBACK(_qq_menu_unsubscribe_group), NULL, NULL);   //add NULL by gfhuang
+	m = g_list_append(m, act);
+
+	act = gaim_menu_action_new(_("Show Details"), GAIM_CALLBACK(_qq_menu_manage_group), NULL, NULL);  //add NULL by gfhuang
+	m = g_list_append(m, act);
+
+	return m;
+}
+/* buddy-related menu shown up with right-click */
+static GList *_qq_buddy_menu(GaimBlistNode * node)
+{
+	GList *m;
+	/*GaimBlistNodeAction->GaimMenuAction, gaim2beta2, gfhuang*/
+//	GaimMenuAction  *act;
+
+	if(GAIM_BLIST_NODE_IS_CHAT(node))		//by gfhuang
+		return _qq_chat_menu(node);
+	
+	m = NULL;
+	/*gaim_blist_node_action_new -> gaim_menu_action_new, gaim2beta2, gfhuang */
+
+/* TODO : not working, temp commented out by gfhuang 
+
+	act = gaim_menu_action_new(_("Block this buddy"), GAIM_CALLBACK(_qq_menu_block_buddy), NULL, NULL); //add NULL by gfhuang
+	m = g_list_append(m, act);
+//	if (q_bud && is_online(q_bud->status)) {
+		act = gaim_menu_action_new(_("Send File"), GAIM_CALLBACK(_qq_menu_send_file), NULL, NULL); //add NULL by gfhuang
+		m = g_list_append(m, act);
+//	}
+*/
+
+	return m;
+}
+
+
+static void _qq_keep_alive(GaimConnection * gc)
+{
+	qq_group *group;
+	qq_data *qd;
+	GList *list;
+
+	g_return_if_fail(gc != NULL);
+	if (NULL == (qd = (qq_data *) gc->proto_data))
+		return;
+
+	list = qd->groups;
+	while (list != NULL) {
+		group = (qq_group *) list->data;
+		if (group->my_status == QQ_GROUP_MEMBER_STATUS_IS_MEMBER ||
+		    group->my_status == QQ_GROUP_MEMBER_STATUS_IS_ADMIN)
+// no need to get info time and time again, online members enough, gfhuang
+//			qq_send_cmd_group_get_group_info(gc, group);
+			qq_send_cmd_group_get_online_member(gc, group);
+	
+		list = list->next;
+	}
+
+	qq_send_packet_keep_alive(gc);
+
+}
+
+/* convert chat nickname to qq-uid to get this buddy info */
+/* who is the nickname of buddy in QQ chat-room (Qun) */
+static void _qq_get_chat_buddy_info(GaimConnection * gc, gint channel, const gchar * who)
+{
+	gchar *gaim_name;
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL && who != NULL);
+
+	gaim_name = qq_group_find_member_by_channel_and_nickname(gc, channel, who);
+	if (gaim_name != NULL)
+		_qq_get_info(gc, gaim_name);
+
+}
+
+/* convert chat nickname to qq-uid to invite individual IM to buddy */
+/* who is the nickname of buddy in QQ chat-room (Qun) */
+static gchar *_qq_get_chat_buddy_real_name(GaimConnection * gc, gint channel, const gchar * who)
+{
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL && who != NULL, NULL);
+	return qq_group_find_member_by_channel_and_nickname(gc, channel, who);
+
+}
+
+void qq_function_not_implemented(GaimConnection * gc)
+{
+	gaim_notify_warning(gc, NULL, _("This function has not be implemented yet"), _("Please wait for new version"));
+}
+
+/*
+static GaimPluginPrefFrame *get_plugin_pref_frame(GaimPlugin * plugin)
+{
+	GaimPluginPrefFrame *frame;
+	GaimPluginPref *ppref;
+
+	frame = gaim_plugin_pref_frame_new();
+
+	ppref = gaim_plugin_pref_new_with_label(_("Convert IP to location"));
+	gaim_plugin_pref_frame_add(frame, ppref);
+
+	ppref = gaim_plugin_pref_new_with_name_and_label("/plugins/prpl/qq/ipfile", _("IP file"));
+	gaim_plugin_pref_frame_add(frame, ppref);
+
+	ppref = gaim_plugin_pref_new_with_label(_("Display Options"));
+	gaim_plugin_pref_frame_add(frame, ppref);
+
+	ppref = gaim_plugin_pref_new_with_name_and_label
+	    ("/plugins/prpl/qq/show_status_by_icon", _("Show gender/age information beside buddy icons"));
+	gaim_plugin_pref_frame_add(frame, ppref);
+
+	ppref = gaim_plugin_pref_new_with_name_and_label
+	    ("/plugins/prpl/qq/show_fake_video", _("Fake an video for GAIM QQ (re-login to activate)"));
+	gaim_plugin_pref_frame_add(frame, ppref);
+
+	ppref = gaim_plugin_pref_new_with_label(_("System Options"));
+	gaim_plugin_pref_frame_add(frame, ppref);
+
+	ppref = gaim_plugin_pref_new_with_name_and_label
+	    ("/plugins/prpl/qq/prompt_for_missing_packet", _("Prompt user for actions if there are missing packets"));
+	gaim_plugin_pref_frame_add(frame, ppref);
+
+	ppref = gaim_plugin_pref_new_with_name_and_label
+	    ("/plugins/prpl/qq/prompt_group_msg_on_recv", _("Pop up Qun chat window when receive Qun message"));
+	gaim_plugin_pref_frame_add(frame, ppref);
+
+	ppref = gaim_plugin_pref_new_with_name_and_label("/plugins/prpl/qq/datadir", _("OpenQ installed directory"));
+	gaim_plugin_pref_frame_add(frame, ppref);
+
+	return frame;
+}
+*/
+
+GaimPlugin *my_protocol = NULL;
+static GaimPluginProtocolInfo prpl_info	= {
+	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_USE_POINTSIZE,
+	NULL,				/* user_splits	*/
+	NULL,				/* protocol_options */
+	NO_BUDDY_ICONS,			/* icon_spec */
+	_qq_list_icon,			/* list_icon */
+	_qq_list_emblems,		/* list_emblems */
+	_qq_status_text,		/* status_text	*/
+	_qq_tooltip_text,		/* tooltip_text */
+	_qq_away_states,		/* away_states	*/
+	_qq_buddy_menu,			/* blist_node_menu */
+	qq_chat_info,			/* chat_info */
+	NULL,				/* chat_info_defaults */
+	_qq_login,			/* login */
+	_qq_close,			/* close */
+	_qq_send_im,			/* send_im */
+	NULL,				/* set_info */
+	NULL,				/* send_typing	*/
+	_qq_get_info,			/* get_info */
+	_qq_set_away,			/* set_away */
+	NULL,				/* set_idle */
+	NULL,				/* change_passwd */
+	qq_add_buddy,			/* add_buddy */
+	NULL,				/* add_buddies	*/
+	qq_remove_buddy,		/* remove_buddy */
+	NULL,				/* remove_buddies */
+	NULL,				/* add_permit */
+	NULL,				/* add_deny */
+	NULL,				/* rem_permit */
+	NULL,				/* rem_deny */
+	NULL,				/* set_permit_deny */
+	qq_group_join,			/* join_chat */
+	NULL,				/* reject chat	invite */
+	NULL,				/* get_chat_name */
+	NULL,				/* chat_invite	*/
+	NULL,				/* chat_leave */
+	NULL,				/* chat_whisper */
+	_qq_chat_send,			/* chat_send */
+	_qq_keep_alive,			/* keepalive */
+	NULL,				/* register_user */
+	_qq_get_chat_buddy_info,	/* get_cb_info	*/
+	NULL,				/* get_cb_away	*/
+	NULL,				/* alias_buddy	*/
+	NULL,				/* group_buddy	*/
+	NULL,				/* rename_group */
+	NULL,				/* buddy_free */     
+	NULL,				/* convo_closed */
+	NULL,				/* normalize */
+	NULL,				/* set_buddy_icon */
+	NULL,				/* remove_group */
+	_qq_get_chat_buddy_real_name,	/* get_cb_real_name */
+	NULL,				/* set_chat_topic */
+	NULL,				/* find_blist_chat */
+	qq_roomlist_get_list,		/* roomlist_get_list */
+	qq_roomlist_cancel,		/* roomlist_cancel */
+	NULL,				/* roomlist_expand_category */
+	qq_can_receive_file,				/* can_receive_file */
+	qq_send_file,				/* send_file */
+	NULL,                           /* new xfer, by gfhuang */
+	NULL,				/* offline_message, gaim2beta2, gfhuang */
+	NULL,				/* GaimWhiteboardPrplOps, gaim2beta2, gfhuang */
+};
+
+/*
+static GaimPluginUiInfo prefs_info = {
+	get_plugin_pref_frame
+};
+*/
+
+static GaimPluginInfo info = {
+	GAIM_PLUGIN_MAGIC,
+	GAIM_MAJOR_VERSION,
+	GAIM_MINOR_VERSION,
+	GAIM_PLUGIN_PROTOCOL,		/**< type		*/
+	NULL,				/**< ui_requirement	*/
+	0,				/**< flags		*/
+	NULL,				/**< dependencies	*/
+	GAIM_PRIORITY_DEFAULT,		/**< priority		*/
+
+	"prpl-qq",			/**< id			*/
+	"QQ",				/**< name		*/
+	VERSION,			/**< version		*/
+					/**  summary		*/
+	N_("QQ Protocol	Plugin"),
+					/**  description	*/
+	N_("QQ Protocol	Plugin"),
+	OPENQ_AUTHOR,			/**< author		*/
+	OPENQ_WEBSITE,			/**< homepage		*/
+
+	NULL,				/**< load		*/
+	NULL,				/**< unload		*/
+	NULL,				/**< destroy		*/
+
+	NULL,				/**< ui_info		*/
+	&prpl_info,			/**< extra_info		*/
+	NULL,			/**< prefs_info		*/
+	_qq_actions
+};
+
+
+static void init_plugin(GaimPlugin * plugin)
+{
+	GaimAccountOption *option;
+
+	bindtextdomain(PACKAGE, LOCALEDIR);
+	bind_textdomain_codeset(PACKAGE, "UTF-8");
+
+	option = gaim_account_option_bool_new(_("Login in TCP"), "use_tcp", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	option = gaim_account_option_bool_new(_("Login Hidden"), "hidden", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	option = gaim_account_option_string_new(_("QQ Server"), "server", NULL);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	option = gaim_account_option_string_new(_("QQ Port"), "port", NULL);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	my_protocol = plugin;
+
+	gaim_prefs_add_none("/plugins/prpl/qq");
+	gaim_prefs_add_string("/plugins/prpl/qq/ipfile", "");
+	gaim_prefs_add_bool("/plugins/prpl/qq/show_status_by_icon", TRUE);
+	gaim_prefs_add_bool("/plugins/prpl/qq/show_fake_video", FALSE);
+	gaim_prefs_add_string("/plugins/prpl/qq/datadir", DATADIR);
+	gaim_prefs_add_bool("/plugins/prpl/qq/prompt_for_missing_packet", TRUE);
+	gaim_prefs_add_bool("/plugins/prpl/qq/prompt_group_msg_on_recv", TRUE);
+}
+
+GAIM_INIT_PLUGIN(qq, init_plugin, info);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/qq.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,111 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_QQ_H_
+#define _QQ_QQ_H_
+
+#include <glib.h>
+#include "proxy.h"		// GaimProxyType
+#include "internal.h"		// socket
+#include "roomlist.h"		// GaimRoomlist
+#include "ft.h"		// GaimXfer
+
+#define QQ_KEY_LENGTH       16
+#define QQ_DEBUG            1	// whether we are doing DEBUG
+
+typedef struct _qq_data qq_data;
+typedef struct _qq_buddy qq_buddy;
+
+struct _qq_buddy {
+	guint32 uid;
+	guint8 icon;		// index: 01 - 85
+	guint8 age;
+	guint8 gender;
+	gchar *nickname;
+	guint8 ip[4];
+	guint16 port;
+	guint8 status;
+	guint8 flag1;
+	guint8 comm_flag;	// details in qq_buddy_list.c
+	guint16 client_version; // added by gfhuang
+	time_t signon;
+	time_t idle;
+	time_t last_refresh;
+
+	gint8  role;		// role in group, used only in group->members list, gfhuang
+};
+
+struct _qq_data {
+	gint fd;		// socket file handler
+	guint32 uid;		// QQ number
+	guint8 *inikey;		// initial key to encrypt login packet
+	guint8 *pwkey;		// password in md5 (or md5' md5)
+	guint8 *session_key;	// later use this as key in this session
+
+	guint16 send_seq;	// send sequence number
+	guint8 login_mode;	// online of invisible
+	guint8 status;		// 
+	gboolean logged_in;	// used by qq-add_buddy
+	gboolean use_tcp;	// network in tcp or udp
+
+	GaimProxyType proxy_type;	// proxy type
+	GaimXfer *xfer;		// file transfer handler
+	struct sockaddr_in dest_sin;
+
+	// from real connction
+	gchar *server_ip;
+	guint16 server_port;
+	// get from login reply packet
+	time_t login_time;
+	time_t last_login_time;
+	gchar *last_login_ip;
+	// get from keep_alive packet
+	gchar *my_ip;		// my ip address detected by server
+	guint16 my_port;	// my port detected by server
+	guint8 my_icon;		// my icon index
+	guint32 all_online;	// the number of online QQ users
+	time_t last_get_online;	// last time send get_friends_online packet
+
+	guint8 window[1 << 13];	// check up for duplicated packet
+	gint sendqueue_timeout;
+
+	GaimRoomlist *roomlist;
+	gint channel;		// the id for opened chat conversation
+
+	GList *groups;
+	GList *group_packets;
+	GList *buddies;
+	GList *contact_info_window;
+	GList *qun_info_window;
+	GList *sendqueue;
+	GList *info_query;
+	GList *add_buddy_request;
+	GQueue *before_login_packets;
+};
+
+void qq_function_not_implemented(GaimConnection * gc);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/qq_proxy.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,436 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *                    Henry Ou  <henry@linux.net>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "debug.h"		// gaim_debug
+#include "internal.h"		// strlen, _("get_text")#include "md5.h"                    // md5 functions
+//#include "md5.h"
+#include "cipher.h"            //gfhuang
+
+#ifdef _WIN32
+#define random rand
+#define srandom srand
+#endif
+
+#include "utils.h"		// qq_debug
+#include "packet_parse.h"	// MAX_PACKET_SIZE
+#include "buddy_info.h"		// qq_info_query_free
+#include "buddy_opt.h"		// qq_add_buddy_request_free
+#include "group_admindlg.h"	// qq_qun_info_window_free
+#include "group_free.h"		// qq_group_packets_free
+#include "infodlg.h"		// qq_contact_info_window_free
+#include "login_logout.h"	// qq_send_packet_login
+#include "qq_proxy.h"		//
+#include "recv_core.h"		// qq_pending, qq_b4_packets_free
+#include "send_core.h"		// qq_send_cmd
+#include "sendqueue.h"		// qq_sendqueue_timeout_callback
+#include "udp_proxy_s5.h"	// qq_proxy_sock5
+
+/*****************************************************************************/
+
+/* These functions are used only in development phased
+ *
+static void _qq_show_socket(gchar *desc, gint fd) {
+	struct sockaddr_in sin;
+	gint len = sizeof(sin);
+	getsockname(fd, (struct sockaddr *)&sin, &len);
+	gaim_debug(GAIM_DEBUG_INFO, desc, "%s:%d\n",
+            inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
+}
+*/
+
+static void _qq_show_packet(gchar * desc, gchar * buf, gint len)
+{
+	char buf1[4096], buf2[10];
+	int i;
+	buf1[0] = 0;
+	for (i = 0; i < len; i++) {
+		sprintf(buf2, " %02x(%d)", buf[i] & 0xff, buf[i] & 0xff);
+		strcat(buf1, buf2);
+	}
+	strcat(buf1, "\n");
+	gaim_debug(GAIM_DEBUG_INFO, desc, buf1);
+}
+
+/*****************************************************************************/
+// QQ 2003iii uses double MD5 for the pwkey to get the session key
+static guint8 *_gen_pwkey(const gchar * pwd)
+{
+//	md5_state_t ctx;        //gfhuang
+        GaimCipher *cipher;
+        GaimCipherContext *context;
+
+	gchar pwkey_tmp[QQ_KEY_LENGTH];
+/*
+	md5_init(&ctx);
+	md5_append(&ctx, pwd, strlen(pwd));
+	md5_finish(&ctx, pwkey_tmp);
+
+	md5_init(&ctx);
+	md5_append(&ctx, pwkey_tmp, QQ_KEY_LENGTH);
+	md5_finish(&ctx, pwkey_tmp);
+*/        //gfhuang
+
+	cipher = gaim_ciphers_find_cipher("md5");
+	context = gaim_cipher_context_new(cipher, NULL);
+	gaim_cipher_context_append(context, pwd, strlen(pwd));
+	gaim_cipher_context_digest(context, sizeof(pwkey_tmp), pwkey_tmp, NULL);
+	gaim_cipher_context_destroy(context);
+	context = gaim_cipher_context_new(cipher, NULL);
+	gaim_cipher_context_append(context, pwkey_tmp, QQ_KEY_LENGTH);
+	gaim_cipher_context_digest(context, sizeof(pwkey_tmp), pwkey_tmp, NULL);
+	gaim_cipher_context_destroy(context);
+
+
+	return g_memdup(pwkey_tmp, QQ_KEY_LENGTH);
+}				// _gen_pwkey
+
+
+/*****************************************************************************/
+static gint _qq_fill_host(struct sockaddr_in * addr, const gchar * host, guint16 port)
+{
+	if (!inet_aton(host, &(addr->sin_addr))) {
+		struct hostent *hp;
+		if (!(hp = gethostbyname(host))) {
+			return -1;
+		}
+		memset(addr, 0, sizeof(struct sockaddr_in));
+		memcpy(&(addr->sin_addr.s_addr), hp->h_addr, hp->h_length);
+		addr->sin_family = hp->h_addrtype;
+	} else
+		addr->sin_family = AF_INET;
+
+	addr->sin_port = htons(port);
+	return 0;
+}				// _qq_fill_host
+
+/*****************************************************************************/
+// the callback function after socket is built
+// we setup the qq protocol related configuration here
+static void _qq_got_login(gpointer data, gint source, GaimInputCondition cond)
+{
+	qq_data *qd;
+	GaimConnection *gc;
+	gchar *buf;
+	const gchar *passwd;
+
+	gc = (GaimConnection *) data;
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	if (g_list_find(gaim_connections_get_all(), gc) == NULL) {
+		close(source);
+		return;
+	}
+
+	if (source < 0) {	// socket returns -1
+		gaim_connection_error(gc, _("Unable to connect."));
+		return;
+	}
+
+	qd = (qq_data *) gc->proto_data;
+
+	// QQ use random seq, to minimize duplicated packets
+	srandom(time(NULL));
+	qd->send_seq = random() & 0x0000ffff;
+	qd->fd = source;
+	qd->logged_in = FALSE;
+	qd->channel = 1;
+	qd->uid = strtol(gaim_account_get_username(gaim_connection_get_account(gc)), NULL, 10);
+	qd->before_login_packets = g_queue_new();
+
+	// now generate md5 processed passwd                        
+	passwd = gaim_account_get_password(gaim_connection_get_account(gc));
+	qd->pwkey = _gen_pwkey(passwd);
+
+	qd->sendqueue_timeout = gaim_timeout_add(QQ_SENDQUEUE_TIMEOUT, qq_sendqueue_timeout_callback, gc);
+	gc->inpa = gaim_input_add(qd->fd, GAIM_INPUT_READ, qq_input_pending, gc);
+
+	// Update the login progress status display
+	buf = g_strdup_printf("Login as %d", qd->uid);
+	gaim_connection_update_progress(gc, buf, 1, QQ_CONNECT_STEPS);
+	g_free(buf);
+
+//	qq_send_packet_login(gc);	// finally ready to fire
+	qq_send_packet_request_login_token(gc);
+}				// _qq_got_login
+
+/*****************************************************************************/
+// clean up qq_data structure and all its components
+// always used before a redirectly connection
+static void _qq_common_clean(GaimConnection * gc)
+{
+	qq_data *qd;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	// finish  all I/O
+	if (qd->fd >= 0 && qd->logged_in)
+		qq_send_packet_logout(gc);
+	close(qd->fd);
+
+	if (qd->sendqueue_timeout > 0) {
+		gaim_timeout_remove(qd->sendqueue_timeout);
+		qd->sendqueue_timeout = 0;
+	}			// qd->sendqueue_timeout
+
+	if (gc->inpa > 0) {
+		gaim_input_remove(gc->inpa);
+		gc->inpa = 0;
+	}			// gc->inpa
+
+	qq_b4_packets_free(qd);
+	qq_sendqueue_free(qd);
+	qq_group_packets_free(qd);
+	qq_group_free_all(qd);
+	qq_add_buddy_request_free(qd);
+	qq_info_query_free(qd);
+	qq_contact_info_window_free(qd);
+	qq_qun_info_window_free(qd);
+	qq_buddies_list_free(gc->account /* by gfhuang */, qd);
+
+}				// _qq_common_clean
+
+/*****************************************************************************/
+static gint _qq_proxy_none(struct PHB *phb, struct sockaddr *addr, socklen_t addrlen)
+{
+	gint fd = -1;
+
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Using UDP without proxy\n");
+	fd = socket(PF_INET, SOCK_DGRAM, 0);
+
+	if (fd < 0) {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ Redirect", "Unable to create socket: %s\n", strerror(errno));
+		return -1;
+	}			// if fd
+
+	// we use non-blocking mode to speed up connection
+	fcntl(fd, F_SETFL, O_NONBLOCK);
+
+	/* From Unix-socket-FAQ: http://www.faqs.org/faqs/unix-faq/socket/
+	 *
+	 * If a UDP socket is unconnected, which is the normal state after a
+	 * bind() call, then send() or write() are not allowed, since no
+	 * destination is available; only sendto() can be used to send data.
+	 *   
+	 * Calling connect() on the socket simply records the specified address
+	 * and port number as being the desired communications partner. That
+	 * means that send() or write() are now allowed; they use the destination
+	 * address and port given on the connect call as the destination of packets.
+	 */
+	if (connect(fd, addr, addrlen) < 0) {
+		/* [EINPROGRESS]
+		 *    The socket is marked as non-blocking and the connection cannot be 
+		 *    completed immediately. It is possible to select for completion by 
+		 *    selecting the socket for writing.
+		 * [EINTR]
+		 *    A signal interrupted the call. 
+		 *    The connection is established asynchronously.
+		 */
+		if ((errno == EINPROGRESS) || (errno == EINTR))
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Connect in asynchronous mode.\n");
+		else {
+			gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Faiil connection: %d\n", strerror(errno));
+			close(fd);
+			return -1;
+		}		// if errno
+	} else {		// connect returns 0
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "Connected.\n");
+		fcntl(fd, F_SETFL, 0);
+		phb->func(phb->data, fd, GAIM_INPUT_READ);
+	}			// if connect
+
+	return fd;
+}				// _qq_proxy_none
+
+/*****************************************************************************/
+// returns the socket handler, or -1 if there is any error
+static gint _qq_udp_proxy_connect(GaimAccount * account,
+			   const gchar * server,
+			   guint16 port, void callback(gpointer, gint, GaimInputCondition), GaimConnection * gc)
+{
+	struct sockaddr_in sin;
+	struct PHB *phb;
+	GaimProxyInfo *info;
+	qq_data *qd;
+
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, -1);
+	qd = (qq_data *) gc->proto_data;
+
+	info = gaim_account_get_proxy_info(account);
+
+	phb = g_new0(struct PHB, 1);
+	phb->host = g_strdup(server);
+	phb->port = port;
+	phb->account = account;
+	phb->gpi = info;
+	phb->func = callback;
+	phb->data = gc;
+
+	if (_qq_fill_host(&sin, server, port) < 0) {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+			   "gethostbyname(\"%s\", %d) failed: %s\n", server, port, hstrerror(h_errno));
+		return -1;
+	}			// if _qq_fill_host
+
+	if (info == NULL) {
+		qd->proxy_type = GAIM_PROXY_NONE;
+		return _qq_proxy_none(phb, (struct sockaddr *) &sin, sizeof(sin));
+	}			// if info
+
+	qd->proxy_type = info->type;
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Choosing proxy type %d\n", info->type);
+
+	switch (info->type) {
+	case GAIM_PROXY_NONE:
+		return _qq_proxy_none(phb, (struct sockaddr *) &sin, sizeof(sin));
+	case GAIM_PROXY_SOCKS5:
+		// as the destination is always QQ server during the session, 
+		// we can set dest_sin here, instead of _qq_s5_canread_again
+		_qq_fill_host(&qd->dest_sin, phb->host, phb->port);
+		_qq_fill_host(&sin, phb->gpi->host, phb->gpi->port);
+		return qq_proxy_socks5(phb, (struct sockaddr *) &sin, sizeof(sin));
+	default:
+		return _qq_proxy_none(phb, (struct sockaddr *) &sin, sizeof(sin));
+	}			// switch
+
+	return -1;
+}
+
+/*****************************************************************************/
+// QQ connection via UDP/TCP. 
+// I use GAIM proxy function to provide TCP proxy support,
+// and qq_udp_proxy.c to add UDP proxy support (thanks henry)
+// return the socket handle, -1 means fail
+static gint _proxy_connect_full
+    (GaimAccount * account, const gchar * host, guint16 port, GaimInputFunction func, gpointer data, gboolean use_tcp) {
+
+	GaimConnection *gc;
+	qq_data *qd;
+
+	gc = gaim_account_get_connection(account);
+	qd = (qq_data *) gc->proto_data;
+	qd->server_ip = g_strdup(host);
+	qd->server_port = port;
+
+	return use_tcp ? gaim_proxy_connect(account, host, port, func, data) :	// TCP mode
+	    _qq_udp_proxy_connect(account, host, port, func, data);	// UDP mode
+
+}				// _gaim_proxy_connect_full
+
+/*****************************************************************************/
+// establish a generic QQ connection 
+// TCP/UDP, and direct/redirected
+// return the socket handler, or -1 if there is any error
+gint qq_connect(GaimAccount * account, const gchar * host, guint16 port, gboolean use_tcp, gboolean is_redirect) {
+
+	GaimConnection *gc;
+
+	g_return_val_if_fail(host != NULL, -1);
+	g_return_val_if_fail(port > 0, -1);
+
+	gc = gaim_account_get_connection(account);
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, -1);
+
+	if (is_redirect)
+		_qq_common_clean(gc);
+
+	return _proxy_connect_full(account, host, port, _qq_got_login, gc, use_tcp);
+}				// qq_connect
+
+/*****************************************************************************/
+// clean up the given QQ connection and free all resources
+void qq_disconnect(GaimConnection * gc)
+{
+	qq_data *qd;
+
+	g_return_if_fail(gc != NULL);
+
+	_qq_common_clean(gc);
+
+	qd = gc->proto_data;
+	g_free(qd->inikey);
+	g_free(qd->pwkey);
+	g_free(qd->session_key);
+	g_free(qd->my_ip);
+	g_free(qd);
+
+	gc->proto_data = NULL;
+}				// qq_disconnect
+
+/*****************************************************************************/
+// send packet with proxy support
+gint qq_proxy_write(qq_data * qd, guint8 * data, gint len)
+{
+	guint8 *buf;
+	gint ret;
+
+	g_return_val_if_fail(qd != NULL && qd->fd >= 0 && data != NULL && len > 0, -1);
+
+	// TCP sock5 may be processed twice
+	// so we need to check qd->use_tcp as well
+	if ((!qd->use_tcp) && qd->proxy_type == GAIM_PROXY_SOCKS5) {	// UDP sock5
+		buf = g_newa(guint8, len + 10);
+		buf[0] = 0x00;
+		buf[1] = 0x00;	//reserved
+		buf[2] = 0x00;	//frag
+		buf[3] = 0x01;	//type
+		g_memmove(buf + 4, &(qd->dest_sin.sin_addr.s_addr), 4);
+		g_memmove(buf + 8, &(qd->dest_sin.sin_port), 2);
+		g_memmove(buf + 10, data, len);
+		ret = send(qd->fd, buf, len + 10, 0);
+	} else
+		ret = send(qd->fd, data, len, 0);
+
+	return ret;
+}
+
+/*****************************************************************************/
+// read packet input with proxy support
+gint qq_proxy_read(qq_data * qd, guint8 * data, gint len)
+{
+	guint8 *buf;
+	gint bytes;
+	buf = g_newa(guint8, MAX_PACKET_SIZE + 10);
+
+	g_return_val_if_fail(qd != NULL && data != NULL && len > 0, -1);
+	g_return_val_if_fail(qd->fd > 0, -1);
+
+	bytes = read(qd->fd, buf, len + 10);
+	if (bytes < 0)
+		return -1;
+
+	if ((!qd->use_tcp) && qd->proxy_type == GAIM_PROXY_SOCKS5) {	// UDP sock5
+		if (bytes < 10)
+			return -1;
+		bytes -= 10;
+		g_memmove(data, buf + 10, bytes);	//cut off the header
+	} else
+		g_memmove(data, buf, bytes);
+
+	return bytes;
+}				// qq_proxy_read
+
+/*****************************************************************************/
+// END of FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/qq_proxy.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,57 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *                    Henry Ou  <henry@linux.net>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+
+#ifndef _QQ_PROXY_H
+#define _QQ_PROXY_H
+
+#include <glib.h>
+#include "proxy.h"		// GaimAccount, GaimConnection
+#include "qq.h"			// qq_data
+
+#define QQ_CONNECT_STEPS    2	// steps in cnnection
+
+struct PHB {
+	GaimInputFunction func;
+	gpointer data;
+	gchar *host;
+	gint port;
+	gint inpa;
+	GaimProxyInfo *gpi;
+	GaimAccount *account;
+	gint udpsock;
+	gpointer sockbuf;
+};
+
+gint qq_proxy_read(qq_data * qd, guint8 * data, gint len);
+gint qq_proxy_write(qq_data * qd, guint8 * data, gint len);
+
+gint qq_connect(GaimAccount * account, const gchar * host, guint16 port, gboolean use_tcp, gboolean is_redirect);
+
+void qq_disconnect(GaimConnection * gc);
+
+#endif				//_QQ_PROXY_H
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/recv_core.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,321 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "debug.h"		// gaim_debug
+#include "internal.h"		// _("get_text")
+
+#include "utils.h"		// hex_dump_to_str
+#include "packet_parse.h"	// MAX_PACKET_SIZE
+#include "buddy_info.h"		// qq_process_modify_info_reply
+#include "buddy_list.h"		// qq_process_get_buddies_list_reply
+#include "buddy_opt.h"		// qq_process_add_buddy_reply
+#include "buddy_status.h"	// qq_process_friend_change_status
+#include "char_conv.h"		// qq_to_utf8
+#include "crypt.h"		// qq_crypt
+#include "group_network.h"	// qq_process_group_cmd_reply
+#include "header_info.h"	// cmd alias
+#include "keep_alive.h"		// qq_process_keep_alive_reply
+#include "im.h"			// qq_process_send_im_reply
+#include "login_logout.h"	// qq_process_login_reply
+#include "qq_proxy.h"		// qq_proxy_read
+#include "recv_core.h"
+#include "sendqueue.h"		// qq_sendqueue_remove
+#include "sys_msg.h"		// qq_process_msg_sys
+
+typedef struct _packet_before_login packet_before_login;
+typedef struct _qq_recv_msg_header qq_recv_msg_header;
+
+struct _packet_before_login {
+	guint8 *buf;
+	gint len;
+};
+
+struct _qq_recv_msg_header {
+	guint8 header_tag;
+	guint16 source_tag;
+	guint16 cmd;
+	guint16 seq;		// can be ack_seq or send_seq, depends on cmd
+};
+
+/*****************************************************************************/
+// check whether one sequence number is duplicated or not
+// return TRUE if it is duplicated, otherwise FALSE
+static gboolean _qq_check_packet_set_window(guint16 seq, GaimConnection * gc)
+{
+	qq_data *qd;
+	gchar *byte, mask;
+
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, FALSE);
+	qd = (qq_data *) gc->proto_data;
+	byte = &(qd->window[seq / 8]);
+	mask = (1 << (seq % 8));
+
+	if ((*byte) & mask)
+		return TRUE;	// check mask
+	(*byte) |= mask;
+	return FALSE;		// set mask
+}				// _qq_check_packet_set_window
+
+/*****************************************************************************/
+// default process, decrypt and dump
+static void _qq_process_packet_default(guint8 * buf, gint buf_len, guint16 cmd, guint16 seq, GaimConnection * gc) {
+
+	qq_data *qd;
+	guint8 *data;
+	gchar *msg_utf8;
+	gint len;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	g_return_if_fail(buf != NULL && buf_len != 0);
+
+	qd = (qq_data *) gc->proto_data;
+	len = buf_len;
+	data = g_newa(guint8, len);
+	msg_utf8 = NULL;
+
+	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ",
+			   ">>> [%d] %s, %d bytes -> [default] decrypt and dump\n%s",
+			   seq, qq_get_cmd_desc(cmd), buf_len, hex_dump_to_str(data, len));
+		try_dump_as_gbk(data, len);
+	} else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail decrypt packet with default process\n");
+
+}				// _qq_process_packet_default
+
+/*****************************************************************************/
+// process the incoming packet from qq_pending
+static void _qq_packet_process(guint8 * buf, gint buf_len, GaimConnection * gc)
+{
+	qq_data *qd;
+	gint len, bytes_expected, bytes_read;
+	guint16 buf_len_read;	// two bytes in the begining of TCP packet
+	guint8 *cursor;
+	qq_recv_msg_header header;
+	packet_before_login *b4_packet;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	g_return_if_fail(buf != NULL && buf_len > 0);
+
+	qd = (qq_data *) gc->proto_data;
+	bytes_expected = qd->use_tcp ? QQ_TCP_HEADER_LENGTH : QQ_UDP_HEADER_LENGTH;
+
+	if (buf_len < bytes_expected) {
+		gaim_debug(GAIM_DEBUG_ERROR,
+			   "QQ", "Received packet is too short, dump and drop\n%s", hex_dump_to_str(buf, buf_len));
+		return;
+	}
+	// initialize
+	cursor = buf;
+	bytes_read = 0;
+
+	// QQ TCP packet returns first 2 bytes the length of this packet
+	if (qd->use_tcp) {
+		bytes_read += read_packet_w(buf, &cursor, buf_len, &buf_len_read);
+		if (buf_len_read != buf_len) {	// wrong
+			gaim_debug
+			    (GAIM_DEBUG_ERROR,
+			     "QQ",
+			     "TCP read %d bytes, header says %d bytes, use header anyway\n", buf_len, buf_len_read);
+			buf_len = buf_len_read;	// we believe header is more accurate
+		}		// if buf_len_read
+	}			// if use_tcp
+
+	// now goes the normal QQ packet as UDP packet
+	bytes_read += read_packet_b(buf, &cursor, buf_len, &header.header_tag);
+	bytes_read += read_packet_w(buf, &cursor, buf_len, &header.source_tag);
+	bytes_read += read_packet_w(buf, &cursor, buf_len, &header.cmd);
+	bytes_read += read_packet_w(buf, &cursor, buf_len, &header.seq);
+
+	if (bytes_read != bytes_expected) {	// read error
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+			   "Fail reading packet header, expect %d bytes, read %d bytes\n", bytes_expected, bytes_read);
+		return;
+	}			// if bytes_read
+
+	if ((buf[buf_len - 1] != QQ_PACKET_TAIL) || (header.header_tag != QQ_PACKET_TAG)) {
+		gaim_debug(GAIM_DEBUG_ERROR,
+			   "QQ", "Unknown QQ proctocol, dump and drop\n%s", hex_dump_to_str(buf, buf_len));
+		return;
+	}			// if header_tag
+
+	if (QQ_DEBUG)
+		gaim_debug(GAIM_DEBUG_INFO, "QQ",
+			   "==> [%05d] %s, from (%s)\n",
+			   header.seq, qq_get_cmd_desc(header.cmd), qq_get_source_str(header.source_tag));
+
+	if (header.cmd != QQ_CMD_LOGIN && header.cmd != QQ_CMD_REQUEST_LOGIN_TOKEN /* gfhuang */) {
+		if (!qd->logged_in) {	// packets before login
+			b4_packet = g_new0(packet_before_login, 1);
+			// must duplicate, buffer will be freed after exiting this function
+			b4_packet->buf = g_memdup(buf, buf_len);
+			b4_packet->len = buf_len;
+			if (qd->before_login_packets == NULL)
+				qd->before_login_packets = g_queue_new();
+			g_queue_push_head(qd->before_login_packets, b4_packet);
+			return;	// do not process it now
+		} else if (!g_queue_is_empty(qd->before_login_packets)) {
+			// logged_in, but we have packets before login
+			b4_packet = (packet_before_login *)
+			    g_queue_pop_head(qd->before_login_packets);
+			_qq_packet_process(b4_packet->buf, b4_packet->len, gc);
+			// in fact this is a recursive call, 
+			// all packets before login will be processed before goes on
+			g_free(b4_packet->buf);	// the buf is duplicated, need to be freed
+			g_free(b4_packet);
+		}		// if logged_in
+	}			//if header.cmd != QQ_CMD_LOGIN
+
+	// this is the length of all the encrypted data (also remove tail tag
+	len = buf_len - (bytes_read) - 1;
+
+	// whether it is an ack
+	switch (header.cmd) {
+	case QQ_CMD_RECV_IM:
+	case QQ_CMD_RECV_MSG_SYS:
+	case QQ_CMD_RECV_MSG_FRIEND_CHANGE_STATUS:
+		// server intiated packet, we need to send ack and check duplicaion
+		// this must be put after processing b4_packet
+		// as these packets will be passed in twice
+		if (_qq_check_packet_set_window(header.seq, gc)) {
+			gaim_debug(GAIM_DEBUG_WARNING,
+				   "QQ", "dup [%05d] %s, discard...\n", header.seq, qq_get_cmd_desc(header.cmd));
+			return;
+		}
+		break;
+	default:{		// ack packet, we need to update sendqueue
+			// we do not check duplication for server ack
+			qq_sendqueue_remove(qd, header.seq);
+			if (QQ_DEBUG)
+				gaim_debug(GAIM_DEBUG_INFO, "QQ",
+					   "ack [%05d] %s, remove from sendqueue\n",
+					   header.seq, qq_get_cmd_desc(header.cmd));
+		}		// default
+	}			// switch header.cmd
+
+	// now process the packet
+	switch (header.cmd) {
+	case QQ_CMD_KEEP_ALIVE:
+		qq_process_keep_alive_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_UPDATE_INFO:
+		qq_process_modify_info_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_ADD_FRIEND_WO_AUTH:
+		qq_process_add_buddy_reply(cursor, len, header.seq, gc);
+		break;
+	case QQ_CMD_DEL_FRIEND:
+		qq_process_remove_buddy_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_REMOVE_SELF:
+		qq_process_remove_self_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_BUDDY_AUTH:
+		qq_process_add_buddy_auth_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_GET_USER_INFO:
+		qq_process_get_info_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_CHANGE_ONLINE_STATUS:
+		qq_process_change_status_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_SEND_IM:
+		qq_process_send_im_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_RECV_IM:
+		qq_process_recv_im(cursor, len, header.seq, gc);
+		break;
+	case QQ_CMD_LOGIN:
+		qq_process_login_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_GET_FRIENDS_LIST:
+		qq_process_get_buddies_list_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_GET_FRIENDS_ONLINE:
+		qq_process_get_buddies_online_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_GROUP_CMD:
+		qq_process_group_cmd_reply(cursor, len, header.seq, gc);
+		break;
+	case QQ_CMD_GET_ALL_LIST_WITH_GROUP:  //added by gfhuang
+		qq_process_get_all_list_with_group_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_REQUEST_LOGIN_TOKEN:	//added by gfhuang
+		qq_process_request_login_token_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_RECV_MSG_SYS:
+		qq_process_msg_sys(cursor, len, header.seq, gc);
+		break;
+	case QQ_CMD_RECV_MSG_FRIEND_CHANGE_STATUS:
+		qq_process_friend_change_status(cursor, len, gc);
+		break;
+	default:
+		_qq_process_packet_default(cursor, len, header.cmd, header.seq, gc);
+		break;
+	}			// switch header.cmd
+}				// _qq_packet_process
+
+/*****************************************************************************/
+// clean up the packets before login
+void qq_b4_packets_free(qq_data * qd)
+{
+	packet_before_login *b4_packet;
+	g_return_if_fail(qd != NULL);
+	// now clean up my own data structures
+	if (qd->before_login_packets != NULL) {
+		while (NULL != (b4_packet = g_queue_pop_tail(qd->before_login_packets))) {
+			g_free(b4_packet->buf);
+			g_free(b4_packet);
+		}
+		g_queue_free(qd->before_login_packets);
+	}			// if 
+}				// qq_b4_packets_free
+
+/*****************************************************************************/
+void qq_input_pending(gpointer data, gint source, GaimInputCondition cond)
+{
+	GaimConnection *gc;
+	qq_data *qd;;
+	guint8 *buf;
+	gint len;
+
+	gc = (GaimConnection *) data;
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL && cond == GAIM_INPUT_READ);
+
+	qd = (qq_data *) gc->proto_data;
+	// according to glib manual memory allocated by g_newa could be 
+	// automatically freed when the current stack frame is cleaned up
+	buf = g_newa(guint8, MAX_PACKET_SIZE);
+
+	// here we have UDP proxy suppport
+	len = qq_proxy_read(qd, buf, MAX_PACKET_SIZE);
+	if (len <= 0) {
+		gaim_connection_error(gc, _("Unable to read from socket"));
+		return;
+	} else
+		_qq_packet_process(buf, len, gc);
+}				// qq_input_pending
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/recv_core.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,38 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_RECV_CORE_H_
+#define _QQ_RECV_CORE_H_
+
+#include <glib.h>
+#include "connection.h"		// GaimConnection
+#include "qq.h"			// qq_data
+
+void qq_b4_packets_free(qq_data * qd);
+
+void qq_input_pending(gpointer data, gint source, GaimInputCondition cond);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/send_core.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,176 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "debug.h"		// gaim_debug
+#include "internal.h"		// send, socket
+
+#include "packet_parse.h"	// MAX_PACKET_SIZE
+#include "crypt.h"		// qq_crypt
+#include "header_info.h"	// cmd alias
+#include "qq_proxy.h"		// qq_proxy_write
+#include "send_core.h"
+#include "sendqueue.h"		// qq_sendpacket
+#include "qq.h"			// qq_data, QQ_DEBUG
+
+/*****************************************************************************/
+// create qq packet header with given sequence
+// return the number of bytes in header if succeeds
+// return -1 if there is any error
+static gint _create_packet_head_seq(guint8 * buf, guint8 ** cursor,
+			     GaimConnection * gc, guint16 cmd, gboolean is_auto_seq, guint16 * seq)
+{
+	qq_data *qd;
+	gint bytes_expected, bytes_written;
+
+	g_return_val_if_fail(gc != NULL &&
+			     gc->proto_data != NULL && buf != NULL && cursor != NULL && *cursor != NULL, -1);
+
+	qd = (qq_data *) gc->proto_data;
+	if (is_auto_seq)
+		*seq = ++(qd->send_seq);
+
+	*cursor = buf;
+	bytes_written = 0;
+	bytes_expected = (qd->use_tcp) ? QQ_TCP_HEADER_LENGTH : QQ_UDP_HEADER_LENGTH;
+
+	// QQ TCP packet has two bytes in the begining defines packet length
+	// so I leave room here for size
+	if (qd->use_tcp)
+		bytes_written += create_packet_w(buf, cursor, 0x0000);
+
+	// now comes the normal QQ packet as UDP
+	bytes_written += create_packet_b(buf, cursor, QQ_PACKET_TAG);
+	bytes_written += create_packet_w(buf, cursor, QQ_CLIENT);
+	bytes_written += create_packet_w(buf, cursor, cmd);
+	bytes_written += create_packet_w(buf, cursor, *seq);
+
+	if (bytes_written != bytes_expected) {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+			   "Fail create qq header, expect %d bytes, written %d bytes\n", bytes_expected, bytes_written);
+		bytes_written = -1;
+	}
+	return bytes_written;
+}				// _create_packet_head_seq
+
+/*****************************************************************************/
+// for those need ack and resend no ack feed back from server
+// return number of bytes written to the socket,
+// return -1 if there is any error
+static gint _qq_send_packet(GaimConnection * gc, guint8 * buf, gint len, guint16 cmd)
+{
+	qq_data *qd;
+	qq_sendpacket *p;
+	gint bytes_sent;
+	guint8 *cursor;
+
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, -1);
+
+	qd = (qq_data *) gc->proto_data;
+
+	if (qd->use_tcp) {
+		if (len > MAX_PACKET_SIZE) {
+			gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+				   "xxx [%05d] %s, %d bytes is too large, do not send\n",
+				   qq_get_cmd_desc(cmd), qd->send_seq, len);
+			return -1;
+		} else {	// I update the len for TCP packet
+			cursor = buf;
+			create_packet_w(buf, &cursor, len);
+		}		// if len
+	}			// if use_tcp
+
+	bytes_sent = qq_proxy_write(qd, buf, len);
+
+	if (bytes_sent >= 0) {		//put to queue, for matching server ACK usage
+		p = g_new0(qq_sendpacket, 1);
+		p->fd = qd->fd;
+		p->cmd = cmd;
+		p->send_seq = qd->send_seq;
+		p->resend_times = 0;
+		p->sendtime = time(NULL);
+		p->buf = g_memdup(buf, len);	// don't use g_strdup, may have 0x00
+		p->len = len;
+		qd->sendqueue = g_list_append(qd->sendqueue, p);
+	}			// if bytes_sent
+
+	return bytes_sent;
+
+}				// _qq_send_packet
+
+/*****************************************************************************/
+// send the packet generated with the given cmd and data
+// return the number of bytes sent to socket if succeeds
+// return -1 if there is any error
+gint qq_send_cmd(GaimConnection * gc, guint16 cmd,
+		 gboolean is_auto_seq, guint16 seq, gboolean need_ack, guint8 * data, gint len)
+{
+	qq_data *qd;
+	guint8 *buf, *cursor, *encrypted_data;
+	guint16 seq_ret;
+	gint encrypted_len, bytes_written, bytes_expected, bytes_sent;
+
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, -1);
+
+	qd = (qq_data *) gc->proto_data;
+	g_return_val_if_fail(qd->session_key != NULL, -1);
+
+	// use g_newa, so that the allocated memory will be freed
+	// when the current stack frame is cleaned up
+	buf = g_newa(guint8, MAX_PACKET_SIZE);
+	encrypted_len = len + 16;	// at most 16 bytes more
+	encrypted_data = g_newa(guint8, encrypted_len);
+	cursor = buf;
+	bytes_written = 0;
+
+	qq_crypt(ENCRYPT, data, len, qd->session_key, encrypted_data, &encrypted_len);
+
+	seq_ret = seq;
+	if (_create_packet_head_seq(buf, &cursor, gc, cmd, is_auto_seq, &seq_ret) >= 0) {
+		bytes_expected = 4 + encrypted_len + 1;
+		bytes_written += create_packet_dw(buf, &cursor, (guint32) qd->uid);
+		bytes_written += create_packet_data(buf, &cursor, encrypted_data, encrypted_len);
+		bytes_written += create_packet_b(buf, &cursor, QQ_PACKET_TAIL);
+		if (bytes_written == bytes_expected) {	// packet OK
+			// if it does not need ACK, we send ACK manually several times
+			if (need_ack)   // my request, send it
+				bytes_sent = _qq_send_packet(gc, buf, cursor - buf, cmd);
+			else		// server's request, send ACK
+				bytes_sent = qq_proxy_write(qd, buf, cursor - buf);
+
+			if (QQ_DEBUG)
+				gaim_debug(GAIM_DEBUG_INFO, "QQ",
+					   "<== [%05d] %s, %d bytes\n", seq_ret, qq_get_cmd_desc(cmd), bytes_sent);
+			return bytes_sent;
+		} else {	// bad packet
+			gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+				   "Fail creating packet, expect %d bytes, written %d bytes\n",
+				   bytes_expected, bytes_written);
+			return -1;
+		}		// if bytes_written
+	}			// if create_packet_head_seq
+	return -1;
+}				// qq_send_cmd
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/send_core.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,40 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_SEND_CORE_H_
+#define _QQ_SEND_CORE_H_
+
+#include <glib.h>
+#include "connection.h"		// GaimConnection
+
+
+#define	QQ_CLIENT	0x0E1B
+
+gint
+qq_send_cmd(GaimConnection * gc,
+	    guint16 cmd, gboolean is_auto_seq, guint16 seq, gboolean need_ack, guint8 * data, gint len);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/send_file.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,978 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ *	Author: Henry Ou <henry@linux.net>
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+//#include <network.h>,				beta2, gfhuang
+
+#include "send_file.h"
+#include "debug.h"
+#include "notify.h"
+#include "network.h"	//gaim_network_get_my_ip
+
+#include "im.h"		//qq_create_packet_im_header
+#include "packet_parse.h"
+#include "crypt.h"
+#include "header_info.h"
+#include "send_core.h"
+#include "utils.h"		// gaim_name_to_uid
+#include "file_trans.h"	// qq_send_file_ctl
+#include "qq.h"
+#include "buddy_status.h" // by gfhuang
+#include "keep_alive.h" //by gfhuang
+
+enum
+{
+	QQ_FILE_TRANS_REQ = 0x0035,
+	QQ_FILE_TRANS_ACC_UDP = 0x0037,
+	QQ_FILE_TRANS_ACC_TCP = 0x0003,
+	QQ_FILE_TRANS_DENY_UDP = 0x0039,
+	QQ_FILE_TRANS_DENY_TCP = 0x0005,
+	QQ_FILE_TRANS_NOTIFY = 0x003b,
+	QQ_FILE_TRANS_NOTIFY_ACK = 0x003c,
+	QQ_FILE_TRANS_CANCEL = 0x0049,
+	QQ_FILE_TRANS_PASV = 0x003f
+};
+
+static int _qq_in_same_lan(ft_info *info)
+{
+	if (info->remote_internet_ip == info->local_internet_ip) return 1;
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Not in the same LAN, remote internet ip[%x], local internet ip[%x]\n",  info->remote_internet_ip
+			, info->local_internet_ip);
+	return 0;
+}
+
+static int _qq_xfer_init_udp_channel(ft_info *info)
+{
+	struct sockaddr_in sin;
+	memset(&sin, 0, sizeof(sin));
+	sin.sin_family = AF_INET;
+	if (!_qq_in_same_lan(info)) {
+		sin.sin_port = htons(info->remote_major_port);
+		sin.sin_addr.s_addr = htonl(info->remote_internet_ip);
+	} else {
+		sin.sin_port = htons(info->remote_minor_port);
+		sin.sin_addr.s_addr = htonl(info->remote_real_ip);
+	}
+	return 0;
+//	return connect(info->sender_fd, (struct sockaddr *) &sin, sizeof(sin));
+}
+
+/* these 2 functions send and recv buffer from/to UDP channel */
+static ssize_t _qq_xfer_udp_recv(char *buf, size_t len, GaimXfer *xfer)
+{
+	struct sockaddr_in sin;
+	int sinlen;
+	ft_info *info;
+	int r;
+
+	info = (ft_info *) xfer->data;
+	sinlen = sizeof(sin);
+	r = recvfrom(info->recv_fd, buf, len, 0, (struct sockaddr *) &sin, &sinlen);
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "==> recv %d bytes from File UDP Channel, remote ip[%s], remote port[%d]\n",
+			r, inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
+	return r;
+}
+
+/*
+static ssize_t _qq_xfer_udp_send(const char *buf, size_t len, GaimXfer *xfer)
+{
+	ft_info *info;
+
+	info = (ft_info *) xfer->data;
+	return send(info->sender_fd, buf, len, 0);
+}
+*/
+static ssize_t _qq_xfer_udp_send(const char *buf, size_t len, GaimXfer *xfer)
+{
+	struct sockaddr_in sin;
+	ft_info *info;
+
+	info = (ft_info *) xfer->data;
+	memset(&sin, 0, sizeof(sin));
+	sin.sin_family = AF_INET;
+	if (!_qq_in_same_lan(info)) {
+		sin.sin_port = htons(info->remote_major_port);
+		sin.sin_addr.s_addr = htonl(info->remote_internet_ip);
+	} else if (info->use_major) {
+		sin.sin_port = htons(info->remote_major_port);
+		sin.sin_addr.s_addr = htonl(info->remote_real_ip);
+	} else {
+		sin.sin_port = htons(info->remote_minor_port);
+		sin.sin_addr.s_addr = htonl(info->remote_real_ip);
+	}
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "sending to channel: %d.%d.%d.%d:%d\n",
+			sin.sin_addr.s_addr & 0xff,
+			(sin.sin_addr.s_addr >> 8) & 0xff,
+			(sin.sin_addr.s_addr >> 16) & 0xff,
+			sin.sin_addr.s_addr >> 24,
+			ntohs(sin.sin_port)
+		  );
+	return sendto(info->sender_fd, buf, len, 0, (struct sockaddr *) &sin, sizeof(sin));
+}
+
+/* user-defined functions for gaim_xfer_read and gaim_xfer_write */
+
+/*
+static ssize_t _qq_xfer_read(char **buf, GaimXfer *xfer)
+{
+	*buf = g_newa(char, QQ_FILE_FRAGMENT_MAXLEN + 100);
+	return _qq_xfer_udp_recv(*buf, QQ_FILE_FRAGMENT_MAXLEN + 100, xfer);
+}
+*/
+
+static gssize _qq_xfer_write(const guchar *buf, size_t len, GaimXfer *xfer) //gfhuang
+{
+	return _qq_xfer_udp_send(buf, len, xfer);
+}
+
+static void
+_qq_xfer_recv_packet(gpointer data, gint source, GaimInputCondition condition)
+{
+	GaimXfer *xfer = (GaimXfer *) data;
+	GaimAccount *account = gaim_xfer_get_account(xfer);
+	GaimConnection *gc = gaim_account_get_connection(account);
+	guint8 *buf;
+	gint size;
+	/* FIXME: It seems that the transfer never use a packet
+	 * larger than 1500 bytes, so if it happened to be a
+	 * larger packet, either error occured or protocol should
+	 * be modified
+	 */
+	ft_info *info;
+	info = xfer->data;
+	g_return_if_fail (source == info->recv_fd);
+	buf = g_newa(guint8, 1500);
+	size = _qq_xfer_udp_recv(buf, 1500, xfer);
+	qq_process_recv_file(gc, buf, size);
+}
+/*****************************************************************************/
+// start file transfer process
+static void
+_qq_xfer_send_start (GaimXfer * xfer)
+{
+	GaimAccount *account;
+	GaimConnection *gc;
+	ft_info *info;
+
+	account = gaim_xfer_get_account(xfer);
+	gc = gaim_account_get_connection(account);
+	info = (ft_info *) xfer->data;
+}
+
+static void
+_qq_xfer_send_ack (GaimXfer *xfer, const char *buffer, size_t len)
+{
+
+	GaimAccount *account;
+	GaimConnection *gc;
+
+	account = gaim_xfer_get_account(xfer);
+	gc = gaim_account_get_connection(account);
+	qq_process_recv_file(gc, (guint8 *) buffer, len);
+}
+
+static void
+_qq_xfer_recv_start(GaimXfer *xfer)
+{
+}
+
+static void
+_qq_xfer_end(GaimXfer *xfer)
+{
+	ft_info *info;
+	g_return_if_fail(xfer != NULL && xfer->data != NULL);
+	info = (ft_info *) xfer->data;
+
+	qq_xfer_close_file(xfer);
+	if (info->dest_fp != NULL) {
+		fclose(info->dest_fp);
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "file closed\n");
+	}
+	if (info->major_fd != 0) {
+		close(info->major_fd);
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "major port closed\n");
+	}
+	if (info->minor_fd != 0) {
+		close(info->minor_fd);
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "minor port closed\n");
+	}
+//	if (info->buffer != NULL) {
+//		munmap(info->buffer, gaim_xfer_get_size(xfer));
+//		gaim_debug(GAIM_DEBUG_INFO, "QQ", "file mapping buffer is freed.\n");
+//	}
+	g_free(info);
+}
+
+void qq_show_conn_info(ft_info *info)
+{
+	gchar *internet_ip_str, *real_ip_str;
+	guint32 ip;
+
+	ip = htonl(info->remote_real_ip);
+	real_ip_str = gen_ip_str((guint8 *) &ip);
+	ip = htonl(info->remote_internet_ip);
+	internet_ip_str = gen_ip_str((guint8 *) &ip);
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "remote internet ip[%s:%d], major port[%d], real ip[%s], minor port[%d]\n",
+			internet_ip_str, info->remote_internet_port,
+			info->remote_major_port, real_ip_str, info->remote_minor_port
+		  );
+	g_free(real_ip_str);
+	g_free(internet_ip_str);
+}
+
+void qq_get_conn_info(guint8 *data, guint8 **cursor, gint data_len, ft_info *info)
+{
+	read_packet_data(data, cursor, data_len, info->file_session_key, 16);
+	*cursor += 30;
+	read_packet_b(data, cursor, data_len, &info->conn_method);
+	read_packet_dw(data, cursor, data_len, &info->remote_internet_ip);
+	read_packet_w(data, cursor, data_len, &info->remote_internet_port);
+	read_packet_w(data, cursor, data_len, &info->remote_major_port);
+	read_packet_dw(data, cursor, data_len, &info->remote_real_ip);
+	read_packet_w(data, cursor, data_len, &info->remote_minor_port);
+	qq_show_conn_info(info);
+}
+
+gint qq_fill_conn_info(guint8 *raw_data, guint8 ** cursor, ft_info *info)
+{
+	gint bytes;
+	bytes = 0;
+	// 064: connection method, UDP 0x00, TCP 0x03
+	bytes += create_packet_b (raw_data, cursor, info->conn_method);
+	// 065-068: outer ip address of sender (proxy address)
+	bytes += create_packet_dw (raw_data, cursor, info->local_internet_ip);
+	// 069-070: sender port
+	bytes += create_packet_w (raw_data, cursor, info->local_internet_port);
+	// 071-072: the first listening port(TCP doesn't have this part)
+	bytes += create_packet_w (raw_data, cursor, info->local_major_port);
+	// 073-076: real ip
+	bytes += create_packet_dw (raw_data, cursor, info->local_real_ip);
+	// 077-078: the second listening port
+	bytes += create_packet_w (raw_data, cursor, info->local_minor_port);
+	return bytes;
+}
+
+
+extern gchar *_gen_session_md5(gint uid, gchar *session_key);
+
+/*****************************************************************************/
+// fill in the common information of file transfer
+static gint _qq_create_packet_file_header
+(guint8 *raw_data, guint8 ** cursor, guint32 to_uid, guint16 message_type, qq_data *qd, gboolean seq_ack)
+{
+	gint bytes;
+	time_t now;
+	gchar *md5;
+	guint16 seq;
+	ft_info *info;
+
+	bytes = 0;
+	now = time(NULL);
+	md5 = _gen_session_md5(qd->uid, qd->session_key);
+	if (!seq_ack) seq = qd->send_seq;
+	else {
+		info = (ft_info *) qd->xfer->data;
+		seq = info->send_seq;
+	}
+
+	// 000-003: receiver uid
+	bytes += create_packet_dw (raw_data, cursor, qd->uid);
+	// 004-007: sender uid
+	bytes += create_packet_dw (raw_data, cursor, to_uid);
+	// 008-009: sender client version
+	bytes += create_packet_w (raw_data, cursor, QQ_CLIENT);
+	// 010-013: receiver uid
+	bytes += create_packet_dw (raw_data, cursor, qd->uid);
+	// 014-017: sender uid
+	bytes += create_packet_dw (raw_data, cursor, to_uid);
+	// 018-033: md5 of (uid+session_key)
+	bytes += create_packet_data (raw_data, cursor, md5, 16);
+	// 034-035: message type
+	bytes += create_packet_w (raw_data, cursor, message_type);
+	// 036-037: sequence number
+	bytes += create_packet_w (raw_data, cursor, seq);
+	// 038-041: send time
+	bytes += create_packet_dw (raw_data, cursor, (guint32) now);
+	// 042-042: always 0x00
+	bytes += create_packet_b (raw_data, cursor, 0x00);
+	// 043-043: sender icon
+	bytes += create_packet_b (raw_data, cursor, qd->my_icon);
+	// 044-046: always 0x00
+	bytes += create_packet_w (raw_data, cursor, 0x0000);
+	bytes += create_packet_b (raw_data, cursor, 0x00);
+	// 047-047: we use font attr
+	bytes += create_packet_b (raw_data, cursor, 0x01);
+	// 048-051: always 0x00
+	bytes += create_packet_dw (raw_data, cursor, 0x00000000);
+
+	// 052-062: always 0x00
+	bytes += create_packet_dw (raw_data, cursor, 0x00000000);
+	bytes += create_packet_dw (raw_data, cursor, 0x00000000);
+	bytes += create_packet_w (raw_data, cursor, 0x0000);
+	bytes += create_packet_b (raw_data, cursor, 0x00);
+	// 063: transfer_type,  0x65: FILE 0x6b: FACE
+	bytes += create_packet_b (raw_data, cursor, QQ_FILE_TRANSFER_FILE); /* FIXME by gfhuang */
+
+	g_free (md5);
+	return bytes;
+}	//_qq_create_packet_file_header
+
+
+#if 0
+in_addr_t get_real_ip()
+{
+	char hostname[40];
+	struct hostent *host;
+
+	gethostname(hostname, sizeof(hostname));
+	host = gethostbyname(hostname);
+	return *(host->h_addr);
+}
+
+
+#include <sys/ioctl.h>
+#include <net/if.h>
+
+#define MAXINTERFACES 16
+in_addr_t get_real_ip()
+{
+	int fd, intrface, i;
+	struct ifconf ifc;
+	struct ifreq buf[MAXINTERFACES];
+	in_addr_t ret;
+
+	if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) return 0;
+	ifc.ifc_len = sizeof(buf);
+	ifc.ifc_buf = (caddr_t) buf;
+	if (ioctl(fd, SIOCGIFCONF, (char *) &ifc) < 0) return 0;
+	intrface = ifc.ifc_len / sizeof(struct ifreq);
+	for (i = 0; i < intrface; i++) {
+		//buf[intrface].ifr_name
+		if (ioctl(fd, SIOCGIFADDR, (char *) &buf[i]) >= 0)
+		{
+			ret = (((struct sockaddr_in *)(&buf[i].ifr_addr))->sin_addr).s_addr;
+			if (ret == ntohl(0x7f000001)) continue;
+			return ret;
+		}
+	}
+	return 0;
+}
+#endif
+
+static void
+_qq_xfer_init_socket(GaimXfer *xfer)
+{
+	int sockfd, listen_port = 0, i, sin_len;
+	struct sockaddr_in sin;
+	ft_info *info;
+
+	g_return_if_fail(xfer != NULL);
+	g_return_if_fail(xfer->data != NULL);
+	info = (ft_info *) xfer->data;
+
+//	info->local_real_ip = ntohl(get_real_ip());
+//debug
+//	info->local_real_ip = 0x7f000001;
+	info->local_real_ip = ntohl(inet_addr(gaim_network_get_my_ip(-1)));
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "local real ip is %x", info->local_real_ip);
+
+	for (i = 0; i < 2; i++) {
+		sockfd = socket(PF_INET, SOCK_DGRAM, 0);
+		g_return_if_fail(sockfd >= 0);
+
+		memset(&sin, 0, sizeof(sin));
+		sin.sin_family = AF_INET;
+		sin.sin_port = 0;
+		sin.sin_addr.s_addr = INADDR_ANY;
+		sin_len = sizeof(sin);
+		bind(sockfd, (struct sockaddr *) &sin, sin_len);
+		getsockname(sockfd, (struct sockaddr *) &sin, &sin_len);
+		listen_port = ntohs(sin.sin_port);
+
+		switch (i) {
+			case 0:
+				info->local_major_port = listen_port;
+				info->major_fd = sockfd;
+				gaim_debug(GAIM_DEBUG_INFO, "QQ", "UDP Major Channel created on port[%d]\n",
+						info->local_major_port);
+				break;
+			case 1:
+				info->local_minor_port = listen_port;
+				info->minor_fd = sockfd;
+				gaim_debug(GAIM_DEBUG_INFO, "QQ", "UDP Minor Channel created on port[%d]\n",
+						info->local_minor_port);
+				break;
+		}
+	}
+
+	if (_qq_in_same_lan(info)) {
+		info->sender_fd = info->recv_fd = info->minor_fd;
+	} else {
+		info->sender_fd = info->recv_fd = info->major_fd;
+	}
+
+//	xfer->watcher = gaim_input_add(info->recv_fd, GAIM_INPUT_READ, _qq_xfer_recv_packet, xfer);
+}
+
+/*****************************************************************************/
+// create the QQ_FILE_TRANS_REQ packet with file infomations
+static void
+_qq_send_packet_file_request (GaimConnection * gc, guint32 to_uid,
+			      gchar * filename, gint filesize)
+{
+	qq_data *qd;
+	guint8 *cursor, *raw_data;
+	gchar *filelen_str;
+	gint filename_len, filelen_strlen, packet_len, bytes;
+	ft_info *info;
+
+	qd = (qq_data *) gc->proto_data;
+
+	info = g_new0(ft_info, 1);
+	info->to_uid = to_uid;
+	info->send_seq = qd->send_seq;
+	info->local_internet_ip = ntohl(inet_addr(qd->my_ip));
+	info->local_internet_port = qd->my_port;
+	info->local_real_ip = 0x00000000;
+	info->conn_method = 0x00;
+	qd->xfer->data = info;
+
+	filename_len = strlen (filename);
+	filelen_str = g_strdup_printf ("%d ×Ö½Ú", filesize);
+	filelen_strlen = strlen (filelen_str);
+
+	packet_len = 82 + filename_len + filelen_strlen;
+	raw_data = g_newa (guint8, packet_len);
+	cursor = raw_data;
+
+	bytes = _qq_create_packet_file_header(raw_data, &cursor, to_uid, QQ_FILE_TRANS_REQ, qd, FALSE);
+	bytes += qq_fill_conn_info(raw_data, &cursor, info);
+	// 079: 0x20
+	bytes += create_packet_b (raw_data, &cursor, 0x20);
+	// 080: 0x1f
+	bytes += create_packet_b (raw_data, &cursor, 0x1f);
+	// undetermined len: filename
+	bytes += create_packet_data (raw_data, &cursor, filename,
+				     filename_len);
+	// 0x1f
+	bytes += create_packet_b (raw_data, &cursor, 0x1f);
+	// file length
+	bytes += create_packet_data (raw_data, &cursor, filelen_str,
+				     filelen_strlen);
+
+	if (packet_len == bytes)
+		qq_send_cmd (gc, QQ_CMD_SEND_IM, TRUE, 0, TRUE, raw_data,
+			     cursor - raw_data);
+	else
+		gaim_debug (GAIM_DEBUG_INFO, "qq_send_packet_file_request",
+			    "%d bytes expected but got %d bytes\n",
+			    packet_len, bytes);
+
+	g_free (filelen_str);
+}	//_qq_send_packet_file_request
+
+/*****************************************************************************/
+// tell the buddy we want to accept the file
+static void
+_qq_send_packet_file_accept(GaimConnection *gc, guint32 to_uid)
+{
+	qq_data *qd;
+	guint8 *cursor, *raw_data;
+	gint packet_len, bytes;
+	ft_info *info;
+
+	g_return_if_fail (gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+	info = (ft_info *) qd->xfer->data;
+
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "I've accepted the file transfer request from %d\n", to_uid);
+	_qq_xfer_init_socket(qd->xfer);
+	guint16 minor_port;
+	guint32 real_ip;
+
+	packet_len = 79;
+	raw_data = g_newa (guint8, packet_len);
+	cursor = raw_data;
+
+	minor_port = info->local_minor_port;
+	real_ip = info->local_real_ip;
+	info->local_minor_port = 0;
+	info->local_real_ip = 0;
+
+	bytes = _qq_create_packet_file_header(raw_data, &cursor, to_uid, QQ_FILE_TRANS_ACC_UDP, qd, TRUE);
+	bytes += qq_fill_conn_info(raw_data, &cursor, info);
+
+	info->local_minor_port = minor_port;
+	info->local_real_ip = real_ip;
+
+	if (packet_len == bytes)
+		qq_send_cmd (gc, QQ_CMD_SEND_IM, TRUE, 0, TRUE, raw_data,
+			     cursor - raw_data);
+	else
+		gaim_debug (GAIM_DEBUG_INFO, "qq_send_packet_file_accept",
+			    "%d bytes expected but got %d bytes\n",
+			    packet_len, bytes);
+}	//_qq_send_packet_packet_accept
+
+static void _qq_send_packet_file_notifyip(GaimConnection *gc, guint32 to_uid)
+{
+	GaimXfer *xfer;
+	ft_info *info;
+	qq_data *qd;
+	guint8 *cursor, *raw_data;
+	gint packet_len, bytes;
+
+	qd = (qq_data *) gc->proto_data;
+	xfer = qd->xfer;
+	info = xfer->data;
+
+	packet_len = 79;
+	raw_data = g_newa (guint8, packet_len);
+	cursor = raw_data;
+
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "<== sending qq file notify ip packet\n");
+	bytes = _qq_create_packet_file_header(raw_data, &cursor, to_uid, QQ_FILE_TRANS_NOTIFY, qd, TRUE);
+	bytes += qq_fill_conn_info(raw_data, &cursor, info);
+	if (packet_len == bytes)
+		qq_send_cmd (gc, QQ_CMD_SEND_IM, TRUE, 0, TRUE, raw_data,
+			     cursor - raw_data);
+	else
+		gaim_debug (GAIM_DEBUG_INFO, "qq_send_packet_file_notify",
+			    "%d bytes expected but got %d bytes\n",
+			    packet_len, bytes);
+
+	if (xfer->watcher) gaim_input_remove(xfer->watcher);
+	xfer->watcher = gaim_input_add(info->recv_fd, GAIM_INPUT_READ, _qq_xfer_recv_packet, xfer);
+	gaim_input_add(info->major_fd, GAIM_INPUT_READ, _qq_xfer_recv_packet, xfer);
+//	gaim_input_add(info->minor_fd, GAIM_INPUT_READ, _qq_xfer_recv_packet, xfer);
+}
+
+/*****************************************************************************/
+// tell the buddy we don't want the file
+static void
+_qq_send_packet_file_reject (GaimConnection *gc, guint32 to_uid)
+{
+	qq_data *qd;
+	guint8 *cursor, *raw_data;
+	gint packet_len, bytes;
+
+	gaim_debug(GAIM_DEBUG_INFO, "_qq_send_packet_file_reject", "start");
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	packet_len = 64;
+	raw_data = g_newa (guint8, packet_len);
+	cursor = raw_data;
+	bytes = 0;
+
+	bytes = _qq_create_packet_file_header(raw_data, &cursor, to_uid, QQ_FILE_TRANS_DENY_UDP, qd, TRUE);
+
+	if (packet_len == bytes)
+		qq_send_cmd (gc, QQ_CMD_SEND_IM, TRUE, 0, TRUE, raw_data,
+			     cursor - raw_data);
+	else
+		gaim_debug (GAIM_DEBUG_INFO, "qq_send_packet_file",
+			    "%d bytes expected but got %d bytes\n",
+			    packet_len, bytes);
+
+//	gaim_debug (GAIM_DEBUG_INFO, "qq_send_packet_file_reject", "end\n");
+}				// qq_send_packet_file_reject
+
+/*****************************************************************************/
+// tell the buddy to cancel transfer
+static void
+_qq_send_packet_file_cancel (GaimConnection *gc, guint32 to_uid)
+{
+	qq_data *qd;
+	guint8 *cursor, *raw_data;
+	gint packet_len, bytes;
+
+	gaim_debug(GAIM_DEBUG_INFO, "_qq_send_packet_file_cancel", "start\n");
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	packet_len = 64;
+	raw_data = g_newa (guint8, packet_len);
+	cursor = raw_data;
+	bytes = 0;
+
+	gaim_debug(GAIM_DEBUG_INFO, "_qq_send_packet_file_cancel", "before create header\n");
+	bytes = _qq_create_packet_file_header(raw_data, &cursor, to_uid, QQ_FILE_TRANS_CANCEL, qd, TRUE);
+	gaim_debug(GAIM_DEBUG_INFO, "_qq_send_packet_file_cancel", "end create header\n");
+
+	if (packet_len == bytes) {
+		gaim_debug(GAIM_DEBUG_INFO, "_qq_send_packet_file_cancel", "before send cmd\n");
+		qq_send_cmd (gc, QQ_CMD_SEND_IM, TRUE, 0, TRUE, raw_data,
+			     cursor - raw_data);
+	}
+	else
+		gaim_debug (GAIM_DEBUG_INFO, "qq_send_packet_file",
+			    "%d bytes expected but got %d bytes\n",
+			    packet_len, bytes);
+
+	gaim_debug (GAIM_DEBUG_INFO, "qq_send_packet_file_cancel", "end\n");
+}				// qq_send_packet_file_cancel
+
+/*****************************************************************************/
+// request to send a file
+static void
+_qq_xfer_init (GaimXfer * xfer)
+{
+	GaimConnection *gc;
+	GaimAccount *account;
+	guint32 to_uid;
+	gchar *filename, *filename_without_path;
+
+	g_return_if_fail (xfer != NULL);
+	account = gaim_xfer_get_account(xfer);
+	gc = gaim_account_get_connection(account);
+	g_return_if_fail (gc != NULL && gc->proto_data != NULL);
+
+	to_uid = gaim_name_to_uid (xfer->who);
+	g_return_if_fail (to_uid != 0);
+
+	filename = (gchar *) gaim_xfer_get_local_filename (xfer);
+	g_return_if_fail (filename != NULL);
+
+	filename_without_path = strrchr (filename, '/') + 1;
+
+	_qq_send_packet_file_request (gc, to_uid, filename_without_path,
+			gaim_xfer_get_size(xfer));
+}				// qq_xfer_init
+
+/*****************************************************************************/
+// cancel the transfer of receiving files
+static void
+_qq_xfer_cancel(GaimXfer *xfer)
+{
+	GaimConnection *gc;
+	GaimAccount *account;
+	guint16 *seq;
+
+	g_return_if_fail (xfer != NULL);
+	seq = (guint16 *) xfer->data;
+	account = gaim_xfer_get_account(xfer);
+	gc = gaim_account_get_connection(account);
+
+	switch (gaim_xfer_get_status(xfer)) {
+		case GAIM_XFER_STATUS_CANCEL_LOCAL:
+			_qq_send_packet_file_cancel(gc, gaim_name_to_uid(xfer->who));
+			break;
+		case GAIM_XFER_STATUS_CANCEL_REMOTE:
+			_qq_send_packet_file_cancel(gc, gaim_name_to_uid(xfer->who));
+			break;
+		case GAIM_XFER_STATUS_NOT_STARTED:
+			break;
+		case GAIM_XFER_STATUS_UNKNOWN:
+			_qq_send_packet_file_reject(gc, gaim_name_to_uid(xfer->who));
+			break;
+		case GAIM_XFER_STATUS_DONE:
+			break;
+		case GAIM_XFER_STATUS_ACCEPTED:
+			break;
+		case GAIM_XFER_STATUS_STARTED:
+			break;
+	}
+}
+
+/*****************************************************************************/
+// init the transfer of receiving files
+static void
+_qq_xfer_recv_init(GaimXfer *xfer)
+{
+	GaimConnection *gc;
+	GaimAccount *account;
+	ft_info *info;
+
+	g_return_if_fail (xfer != NULL && xfer->data != NULL);
+	info = (ft_info *) xfer->data;
+	account = gaim_xfer_get_account(xfer);
+	gc = gaim_account_get_connection(account);
+
+	_qq_send_packet_file_accept(gc, gaim_name_to_uid(xfer->who));
+}
+
+/*****************************************************************************/
+// process reject im for file transfer request
+void qq_process_recv_file_reject
+	(guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid,
+	 GaimConnection * gc)
+{
+	gchar *msg, *filename;
+	qq_data *qd;
+
+	g_return_if_fail (gc != NULL && data != NULL && data_len != 0);
+	qd = (qq_data *) gc->proto_data;
+	g_return_if_fail (qd != NULL && qd->xfer != NULL);
+
+	if (*cursor >= (data + data_len - 1)) {
+		gaim_debug (GAIM_DEBUG_WARNING, "QQ",
+			    "Received file reject message is empty\n");
+		return;
+	}
+	filename = strrchr(gaim_xfer_get_local_filename(qd->xfer), '/') + 1;
+	msg = g_strdup_printf
+		(_
+		 ("Your request to send file[%s] has been rejected by buddy[%d]"),
+		 filename, sender_uid);
+
+	gaim_notify_warning (gc, _("File Send"), msg, NULL);
+	gaim_xfer_request_denied(qd->xfer);
+	qd->xfer = NULL;
+
+	g_free (msg);
+}				// qq_process_recv_file_reject
+
+/*****************************************************************************/
+// process cancel im for file transfer request
+void qq_process_recv_file_cancel
+	(guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid,
+	 GaimConnection * gc)
+{
+	gchar *msg, *filename;
+	qq_data *qd;
+
+	g_return_if_fail (gc != NULL && data != NULL && data_len != 0);
+	qd = (qq_data *) gc->proto_data;
+	g_return_if_fail (qd != NULL && qd->xfer != NULL
+			&& gaim_xfer_get_filename(qd->xfer) != NULL);
+
+	if (*cursor >= (data + data_len - 1)) {
+		gaim_debug (GAIM_DEBUG_WARNING, "QQ",
+			    "Received file reject message is empty\n");
+		return;
+	}
+	filename = strrchr(gaim_xfer_get_local_filename(qd->xfer), '/') + 1;
+	msg = g_strdup_printf
+		(_("The sending process of file[%s] has been cancaled by buddy[%d]"),
+		 filename, sender_uid);
+
+	gaim_notify_warning (gc, _("File Send"), msg, NULL);
+	gaim_xfer_cancel_remote(qd->xfer);
+	qd->xfer = NULL;
+
+	g_free (msg);
+}				// qq_process_recv_file_cancel
+
+/*****************************************************************************/
+// process accept im for file transfer request
+void qq_process_recv_file_accept
+	(guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid,
+	 GaimConnection * gc)
+{
+	qq_data *qd;
+	ft_info *info;
+	GaimXfer *xfer;
+
+	g_return_if_fail (gc != NULL && data != NULL && data_len != 0);
+	qd = (qq_data *) gc->proto_data;
+	xfer = qd->xfer;
+
+	if (*cursor >= (data + data_len - 1)) {
+		gaim_debug (GAIM_DEBUG_WARNING, "QQ",
+			    "Received file reject message is empty\n");
+		return;
+	}
+
+	info = (ft_info *) qd->xfer->data;
+
+	*cursor = data + 18 + 12;
+	qq_get_conn_info(data, cursor, data_len, info);
+	_qq_xfer_init_socket(qd->xfer);
+
+	_qq_xfer_init_udp_channel(info);
+	_qq_send_packet_file_notifyip(gc, sender_uid);
+}				// qq_process_recv_file_accept
+
+/*****************************************************************************/
+// process request from buddy's im for file transfer request
+void qq_process_recv_file_request
+	(guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid,
+	 GaimConnection * gc)
+{
+	qq_data *qd;
+	GaimXfer *xfer;
+	gchar *sender_name;
+	ft_info *info;
+	GaimBuddy *b; //by gfhuang
+	qq_buddy *q_bud;
+
+	g_return_if_fail (gc != NULL && data != NULL && data_len != 0);
+	qd = (qq_data *) gc->proto_data;
+
+	if (*cursor >= (data + data_len - 1)) {
+		gaim_debug (GAIM_DEBUG_WARNING, "QQ",
+			    "Received file reject message is empty\n");
+		return;
+	}
+
+	info = g_new0(ft_info, 1);
+	info->local_internet_ip = ntohl(inet_addr(qd->my_ip));
+	info->local_internet_port = qd->my_port;
+	info->local_real_ip = 0x00000000;
+	info->to_uid = sender_uid;
+	read_packet_w(data, cursor, data_len, &(info->send_seq));
+
+	*cursor = data + 18 + 12;
+	qq_get_conn_info(data, cursor, data_len, info);
+
+	gchar **fileinfo;
+	fileinfo = g_strsplit(data + 81 + 12, "\x1f", 2);
+	g_return_if_fail (fileinfo != NULL && fileinfo[0] != NULL && fileinfo[1] != NULL);
+
+	sender_name = uid_to_gaim_name(sender_uid);
+
+	//FACE from IP detector, ignored by gfhuang
+	if(g_ascii_strcasecmp(fileinfo[0], "FACE") == 0) {
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ",
+			    "Received a FACE ip detect from qq-%d, so he/she must be online :)\n", sender_uid);
+
+		b = gaim_find_buddy(gc->account, sender_name);
+		q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
+		if (q_bud) {
+			if(0 != info->remote_real_ip) { //by gfhuang
+				g_memmove(q_bud->ip, &info->remote_real_ip, 4);
+				q_bud->port = info->remote_minor_port;
+			}
+			else if (0 != info->remote_internet_ip) {
+				g_memmove(q_bud->ip, &info->remote_internet_ip, 4);
+				q_bud->port = info->remote_major_port;
+			}
+
+			if(!is_online(q_bud->status)) {
+				q_bud->status = QQ_BUDDY_ONLINE_INVISIBLE;
+				qq_update_buddy_contact(gc, q_bud);
+			}
+			else 
+				gaim_debug(GAIM_DEBUG_INFO, "QQ", "buddy %d is already online\n", sender_uid);
+
+		}
+		else 
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "buddy %d is not in my friendlist\n", sender_uid);
+
+		g_free(sender_name);	    
+		g_strfreev(fileinfo);
+		return;
+	}
+	
+	xfer = gaim_xfer_new(gaim_connection_get_account(gc),
+			GAIM_XFER_RECEIVE,
+			sender_name);
+	gaim_xfer_set_filename(xfer, fileinfo[0]);
+	gaim_xfer_set_size(xfer, atoi(fileinfo[1]));
+
+	gaim_xfer_set_init_fnc(xfer, _qq_xfer_recv_init);
+	gaim_xfer_set_request_denied_fnc(xfer, _qq_xfer_cancel);
+	gaim_xfer_set_cancel_recv_fnc(xfer, _qq_xfer_cancel);
+	gaim_xfer_set_end_fnc(xfer, _qq_xfer_end);
+	gaim_xfer_set_write_fnc(xfer, _qq_xfer_write);
+
+	xfer->data = info;
+	qd->xfer = xfer;
+
+	gaim_xfer_request(xfer);
+
+	g_free(sender_name);
+	g_strfreev(fileinfo);
+
+//	gaim_debug (GAIM_DEBUG_INFO, "qq_process_recv_file_request", "end\n");
+}				// qq_process_recv_file_request
+
+static void
+_qq_xfer_send_notify_ip_ack(gpointer data, gint source, GaimInputCondition cond)
+{
+	GaimXfer *xfer = (GaimXfer *) data;
+	GaimAccount *account = gaim_xfer_get_account(xfer);
+	GaimConnection *gc = gaim_account_get_connection(account);
+	ft_info *info = (ft_info *) xfer->data;
+
+	gaim_input_remove(xfer->watcher);
+	xfer->watcher = gaim_input_add(info->recv_fd, GAIM_INPUT_READ, _qq_xfer_recv_packet, xfer);
+	qq_send_file_ctl_packet(gc, QQ_FILE_CMD_NOTIFY_IP_ACK, info->to_uid, 0);
+//	info->use_major = TRUE;
+//	qq_send_file_ctl_packet(gc, QQ_FILE_CMD_NOTIFY_IP_ACK, info->to_uid, 0);
+//	info->use_major = FALSE;
+}
+
+void qq_process_recv_file_notify
+	(guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid,
+	 GaimConnection * gc)
+{
+	qq_data *qd;
+	ft_info *info;
+	GaimXfer *xfer;
+
+	g_return_if_fail (gc != NULL && data != NULL && data_len != 0);
+	qd = (qq_data *) gc->proto_data;
+
+	if (*cursor >= (data + data_len - 1)) {
+		gaim_debug (GAIM_DEBUG_WARNING, "QQ",
+			    "Received file notify message is empty\n");
+		return;
+	}
+
+	xfer = qd->xfer;
+	info = (ft_info *) qd->xfer->data;
+	/* FIXME */
+	read_packet_w(data, cursor, data_len, &(info->send_seq));
+
+	*cursor = data + 18 + 12;
+	qq_get_conn_info(data, cursor, data_len, info);
+
+	_qq_xfer_init_udp_channel(info);
+
+	xfer->watcher = gaim_input_add(info->sender_fd, GAIM_INPUT_WRITE, _qq_xfer_send_notify_ip_ack, xfer);
+}
+
+//temp placeholder until a working function can be implemented
+gboolean qq_can_receive_file(GaimConnection *gc, const char *who)
+{
+	return TRUE;
+}
+
+void qq_send_file(GaimConnection *gc, const char *who, const char *file)
+{
+	qq_data *qd;
+	GaimXfer *xfer;
+
+	g_return_if_fail (gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	xfer = gaim_xfer_new (gc->account, GAIM_XFER_SEND,
+			      who);
+	gaim_xfer_set_init_fnc (xfer, _qq_xfer_init);
+	gaim_xfer_set_cancel_send_fnc (xfer, _qq_xfer_cancel);
+	gaim_xfer_set_write_fnc(xfer, _qq_xfer_write);
+
+	qd->xfer = xfer;
+	gaim_xfer_request (xfer);
+}
+
+static void qq_send_packet_request_key(GaimConnection *gc, guint8 key)
+{
+	qq_send_cmd(gc, QQ_CMD_REQUEST_KEY, TRUE, 0, TRUE, &key, 1);
+}
+
+static void qq_process_recv_request_key(GaimConnection *gc)
+{
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/send_file.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,66 @@
+#ifndef _QQ_QQ_SEND_FILE_H_
+#define _QQ_QQ_SEND_FILE_H_
+
+#include "ft.h"
+
+typedef struct _ft_info {
+	guint32 to_uid;
+	guint16 send_seq;
+	guint8 file_session_key[16];
+	guint8 conn_method;
+	guint32 remote_internet_ip;
+	guint16 remote_internet_port;
+	guint16 remote_major_port;
+	guint32 remote_real_ip;
+	guint16 remote_minor_port;
+	guint32 local_internet_ip;
+	guint16 local_internet_port;
+	guint16 local_major_port;
+	guint32 local_real_ip;
+	guint16 local_minor_port;
+	/* we use these to control the packets sent or received */
+	guint32 fragment_num;
+	guint32 fragment_len;
+	/* The max index of sending/receiving fragment
+	 * for sender, it is the lower bolder of a slide window for sending
+	 * for receiver, it seems that packets having a fragment index lower
+	 * than max_fragment_index have been received already
+	 */
+	guint32 max_fragment_index;
+	guint32 window;
+
+	/* It seems that using xfer's function is not enough for our
+	 * transfer module. So I will use our own structure instead
+	 * of xfer provided
+	 */
+	int major_fd;
+	int minor_fd;
+	int sender_fd;
+	int recv_fd;
+	union {
+		FILE *dest_fp;
+		guint8 *buffer;
+	};
+	gboolean use_major;
+} ft_info;
+
+void qq_process_recv_file_accept
+	(guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid,
+	 GaimConnection * gc);
+void qq_process_recv_file_reject
+	(guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid,
+	 GaimConnection * gc);
+void qq_process_recv_file_cancel
+	(guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid,
+	 GaimConnection * gc);
+void qq_process_recv_file_request
+	(guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid,
+	 GaimConnection * gc);
+void qq_process_recv_file_notify
+	(guint8 * data, guint8 ** cursor, gint data_len, guint32 sender_uid,
+	 GaimConnection * gc);
+gboolean qq_can_receive_file(GaimConnection *gc, const char *who);
+void qq_send_file(GaimConnection *gc, const char *who, const char *file);
+void qq_get_conn_info(guint8 *data, guint8 **cursor, gint data_len, ft_info *info);
+gint qq_fill_conn_info(guint8 *data, guint8 **cursor, ft_info *info);
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/sendqueue.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,230 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "connection.h"		// GaimConnection
+#include "debug.h"		// gaim_debug
+#include "internal.h"		// _("get_text")
+#include "notify.h"		// gaim_notify
+#include "prefs.h"		// gaim_prefs_get_bool
+#include "request.h"		// gaim_request_action
+
+#include "header_info.h"	// cmd alias
+#include "qq_proxy.h"		// qq_proxy_write
+#include "sendqueue.h"
+
+#define QQ_RESEND_MAX               5	// max resend per packet
+
+typedef struct _gc_and_packet gc_and_packet;
+
+struct _gc_and_packet {
+	GaimConnection *gc;
+	qq_sendpacket *packet;
+};
+
+/*****************************************************************************/
+// Remove a packet with send_seq from sendqueue
+void qq_sendqueue_remove(qq_data * qd, guint16 send_seq)
+{
+	GList *list;
+	qq_sendpacket *p;
+
+	g_return_if_fail(qd != NULL);
+
+	list = qd->sendqueue;
+	while (list != NULL) {
+		p = (qq_sendpacket *) (list->data);
+		if (p->send_seq == send_seq) {
+			qd->sendqueue = g_list_remove(qd->sendqueue, p);
+			g_free(p->buf);
+			g_free(p);
+			break;
+		}
+		list = list->next;
+	}			// while
+}				// qq_sendqueue_remove
+
+/*****************************************************************************/
+// clean up sendqueue and free all contents
+void qq_sendqueue_free(qq_data * qd)
+{
+	qq_sendpacket *p;
+	gint i;
+
+	i = 0;
+	while (qd->sendqueue != NULL) {
+		p = (qq_sendpacket *) (qd->sendqueue->data);
+		qd->sendqueue = g_list_remove(qd->sendqueue, p);
+		g_free(p->buf);
+		g_free(p);
+		i++;
+	}
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d packets in sendqueue are freed!\n", i);
+}				// qq_sendqueue_free
+
+/*****************************************************************************/
+// packet lost, agree to send again, (and will NOT prompt again)
+// it is removed only when ack-ed by server
+static void _qq_send_again(gc_and_packet * gp)
+{
+	GaimConnection *gc;
+	qq_data *qd;
+	qq_sendpacket *packet;
+	GList *list;
+
+	g_return_if_fail(gp != NULL && gp->gc != NULL && gp->packet != NULL);
+	g_return_if_fail(gp->gc->proto_data != NULL);
+
+	gc = gp->gc;
+	packet = gp->packet;
+	qd = (qq_data *) gc->proto_data;
+
+	list = g_list_find(qd->sendqueue, packet);
+	if (list != NULL) {
+		packet->resend_times = 0;
+		packet->sendtime = time(NULL);
+		qq_proxy_write(qd, packet->buf, packet->len);
+	}
+	g_free(gp);
+}				// _qq_send_again
+
+/*****************************************************************************/
+// packet lost, do not send again
+static void _qq_send_cancel(gc_and_packet * gp)
+{
+	GaimConnection *gc;
+	qq_data *qd;
+	qq_sendpacket *packet;
+	GList *list;
+
+	g_return_if_fail(gp != NULL && gp->gc != NULL && gp->packet != NULL);
+	g_return_if_fail(gp->gc->proto_data != NULL);
+
+	gc = gp->gc;
+	packet = gp->packet;
+	qd = (qq_data *) gc->proto_data;
+
+	list = g_list_find(qd->sendqueue, packet);
+	if (list != NULL)
+		qq_sendqueue_remove(qd, packet->send_seq);
+
+	g_free(gp);
+}				// _qq_send_cancel
+
+/*****************************************************************************/
+gboolean qq_sendqueue_timeout_callback(gpointer data)
+{
+	GaimConnection *gc;
+	qq_data *qd;
+	GList *list;
+	qq_sendpacket *p;
+	gc_and_packet *gp;
+	time_t now;
+	gint wait_time;
+	gboolean need_action;
+
+	gc = (GaimConnection *) data;
+	qd = (qq_data *) gc->proto_data;
+	now = time(NULL);
+	list = qd->sendqueue;
+
+	// empty queue, return TRUE so that timeout continues functioning
+	if (qd->sendqueue == NULL)
+		return TRUE;
+
+	while (list != NULL) {	// remove all packet whose resend_times == -1
+		p = (qq_sendpacket *) list->data;
+		if (p->resend_times == -1) {	// to remove
+			qd->sendqueue = g_list_remove(qd->sendqueue, p);
+			g_free(p->buf);
+			g_free(p);
+			list = qd->sendqueue;
+		} else
+			list = list->next;
+	}			// while list
+
+	list = qd->sendqueue;
+	while (list != NULL) {
+		p = (qq_sendpacket *) list->data;
+		if (p->resend_times >= QQ_RESEND_MAX) {
+			if (p->resend_times == QQ_RESEND_MAX) {	// reach max
+				switch (p->cmd) {
+				case QQ_CMD_KEEP_ALIVE:
+					if (qd->logged_in) {
+						gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Connection lost!\n");
+						gaim_connection_error(gc, _("Connection lost!"));
+						qd->logged_in = FALSE;
+					}	// if logged_in
+					p->resend_times = -1;
+					break;
+				case QQ_CMD_LOGIN:
+					if (!qd->logged_in)	// cancel logging progress
+						gaim_connection_error(gc, _("Login failed, no reply!"));
+					p->resend_times = -1;
+					break;
+				case QQ_CMD_UPDATE_INFO:
+					gaim_notify_error(gc, NULL,
+							  _("Connection timeout!"), _("User info is not updated"));
+					p->resend_times = -1;
+					break;
+				default:{
+						need_action =
+						    gaim_prefs_get_bool("/plugins/prpl/qq/prompt_for_missing_packet");
+						if (!need_action)
+							p->resend_times = -1;	// it will be removed next time
+						else {	// prompt for action
+							gp = g_new0(gc_and_packet, 1);
+							gp->gc = gc;
+							gp->packet = p;
+							gaim_request_action
+							    (gc, NULL,
+							     _
+							     ("Send packet"),
+							     _
+							     ("Packets lost, send again?"),
+							     0, gp, 2,
+							     _("Send"),
+							     G_CALLBACK
+							     (_qq_send_again),
+							     _("Cancel"), G_CALLBACK(_qq_send_cancel));
+							p->resend_times++;	// will send once more, but only once
+						}	// if !need_action
+					}	// default
+				}	// switch
+			}	// resend_times == QQ_RESEND_MAX
+		} else {	// resend_times < QQ_RESEND_MAX, so sent it again
+			wait_time = (gint) (QQ_SENDQUEUE_TIMEOUT / 1000);
+			if (difftime(now, p->sendtime) > (wait_time * (p->resend_times + 1))) {
+				qq_proxy_write(qd, p->buf, p->len);
+				p->resend_times++;
+				gaim_debug(GAIM_DEBUG_INFO,
+					   "QQ", "<<< [%05d] send again for %d times!\n", p->send_seq, p->resend_times);
+			}	// if difftime
+		}		// if resend_times >= QQ_RESEND_MAX            
+		list = list->next;
+	}			// whiile list
+	return TRUE;		// if we return FALSE, the timeout callback stops functioning
+}				// qq_sendqueue_timeout_callback
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/sendqueue.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,52 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_SEND_QUEUE_H_
+#define _QQ_SEND_QUEUE_H_
+
+#include <glib.h>
+#include "qq.h"			// qq_data
+
+#define QQ_SENDQUEUE_TIMEOUT 			5000	// in 1/1000 sec
+
+typedef struct _qq_sendpacket qq_sendpacket;
+
+struct _qq_sendpacket {
+	gint fd;
+	gint len;
+	gchar *buf;
+	guint16 cmd;
+	guint16 send_seq;
+	gint resend_times;
+	time_t sendtime;
+};
+
+void qq_sendqueue_free(qq_data * qd);
+
+void qq_sendqueue_remove(qq_data * qd, guint16 send_seq);
+gboolean qq_sendqueue_timeout_callback(gpointer data);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/show.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,222 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * This code is based on qq_show.c, kindly contributed by herryouly@linuxsir
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include <sys/types.h>		// mkdir
+#include <sys/stat.h>
+
+#include "internal.h"		// _("get_text")
+#include "debug.h"		// gaim_debug
+#include "notify.h"		// gaim_notify_error
+#include "util.h"		// gaim_url_fetch
+
+#include "show.h"
+
+#define QQ_SHOW_SERVER          "http://qqshow-user.tencent.com"
+#define QQ_SHOW_IMAGE           "10/00/00.gif"
+
+#define QQ_SHOW_CACHE_DIR       "qqshow"
+#define QQ_SHOW_DEFAULT_IMAGE   "qqshow_default.gif"
+
+#define QQ_SHOW_DEST_X          0
+#define QQ_SHOW_DEST_Y          0
+#define QQ_SHOW_DEST_WIDTH      120
+#define QQ_SHOW_DEST_HEIGHT     180
+#define QQ_SHOW_OFFSET_X        -10
+#define QQ_SHOW_OFFSET_Y        -35
+#define QQ_SHOW_SCALE_X         0.68
+#define QQ_SHOW_SCALE_Y         0.68
+
+enum {
+	QQ_SHOW_READ,
+	QQ_SHOW_WRITE,
+};
+
+/*****************************************************************************/
+// return the local cached QQ show file name for uid
+static gchar *_qq_show_get_cache_name(guint32 uid, gint io)
+{
+	gchar *path, *file, *file_fullname;
+
+	g_return_val_if_fail(uid > 0, NULL);
+
+	path = g_build_filename(gaim_user_dir(), QQ_SHOW_CACHE_DIR, NULL);
+	if (!g_file_test(path, G_FILE_TEST_EXISTS))
+		g_mkdir(path, 0700);
+
+	file = g_strdup_printf("%d.gif", uid);
+	file_fullname = g_build_filename(path, file, NULL);
+
+	if (io == QQ_SHOW_READ) {
+		if (!g_file_test(file_fullname, G_FILE_TEST_EXISTS)) {
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "No cached QQ show image for buddy %d\n", uid);
+			g_free(file_fullname);
+			file_fullname =
+			    g_build_filename(gaim_prefs_get_string
+					     ("/plugins/prpl/qq/datadir"),
+					     "pixmaps", "gaim", "status", "default", QQ_SHOW_DEFAULT_IMAGE, NULL);
+		}		// if g_file_test
+	}			// if io
+
+	g_free(path);
+	g_free(file);
+	return file_fullname;
+
+}				// _qq_show_get_cache_name
+
+/*****************************************************************************/
+// scale the image to suit my window
+static GdkPixbuf *_qq_show_scale_image(GdkPixbuf * pixbuf_src)
+{
+	GdkPixbuf *pixbuf_dst;
+
+	pixbuf_dst = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8,
+				    QQ_SHOW_DEST_WIDTH * QQ_SHOW_SCALE_X, QQ_SHOW_DEST_HEIGHT * QQ_SHOW_SCALE_Y);
+
+	gdk_pixbuf_scale(pixbuf_src, pixbuf_dst,
+			 QQ_SHOW_DEST_X * QQ_SHOW_SCALE_X,
+			 QQ_SHOW_DEST_Y * QQ_SHOW_SCALE_Y,
+			 QQ_SHOW_DEST_WIDTH * QQ_SHOW_SCALE_X,
+			 QQ_SHOW_DEST_HEIGHT * QQ_SHOW_SCALE_Y,
+			 QQ_SHOW_OFFSET_X * QQ_SHOW_SCALE_X,
+			 QQ_SHOW_OFFSET_Y * QQ_SHOW_SCALE_Y, QQ_SHOW_SCALE_X, QQ_SHOW_SCALE_Y, GDK_INTERP_BILINEAR);
+
+	g_object_unref(G_OBJECT(pixbuf_src));
+	return pixbuf_dst;
+}				// qq_show_scale_image
+
+/*****************************************************************************/
+// keep a local copy of QQ show image to speed up loading
+static void _qq_show_cache_image(const gchar * url_ret, size_t len, guint32 uid)
+{
+
+	GError *err;
+	GIOChannel *cache;
+	gchar *file;
+
+	g_return_if_fail(uid > 0);
+
+	err = NULL;
+	file = _qq_show_get_cache_name(uid, QQ_SHOW_WRITE);
+	cache = g_io_channel_new_file(file, "w", &err);
+
+	if (err != NULL) {	// file error
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Error with QQ show file: %s\n", err->message);
+		g_error_free(err);
+		return;
+	} else {		// OK, go ahead
+		g_io_channel_set_encoding(cache, NULL, NULL);	// binary mode
+		g_io_channel_write_chars(cache, url_ret, len, NULL, &err);
+		if (err != NULL) {
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Fail cache QQ show for %d: %s\n", uid, err->message);
+			g_error_free(err);
+		} else
+			gaim_debug(GAIM_DEBUG_INFO, "QQ", "Cache QQ show for %d, done\n", uid);
+		g_io_channel_shutdown(cache, TRUE, NULL);
+	}			// if err
+
+}				// _qq_show_cache_image
+
+/*****************************************************************************/
+// display the image for downloaded data
+static void qq_show_render_image(void *data, const gchar * url_ret, size_t len)
+{
+	guint32 uid;
+	GdkPixbufLoader *pixbuf_loader;
+	GdkPixbuf *pixbuf_src, *pixbuf_dst;
+	GtkWidget *qq_show;
+
+	g_return_if_fail(data != NULL && url_ret != NULL && len > 0);
+
+	qq_show = (GtkWidget *) data;
+
+	pixbuf_loader = gdk_pixbuf_loader_new();
+	gdk_pixbuf_loader_write(pixbuf_loader, url_ret, len, NULL);
+	gdk_pixbuf_loader_close(pixbuf_loader, NULL);	// finish loading
+
+	pixbuf_src = gdk_pixbuf_loader_get_pixbuf(pixbuf_loader);
+
+	if (pixbuf_src != NULL) {
+		uid = (guint32) g_object_get_data(G_OBJECT(qq_show), "user_data");
+		_qq_show_cache_image(url_ret, len, uid);
+		pixbuf_dst = _qq_show_scale_image(pixbuf_src);
+		gtk_image_set_from_pixbuf(GTK_IMAGE(qq_show), pixbuf_dst);
+	} else {
+		gaim_notify_error(NULL, NULL, _("Fail getting QQ show image"), NULL);
+	}			// if pixbuf_src
+
+}				// qq_show_render_image
+
+/*****************************************************************************/
+// show the default image (either local cache or default image)
+GtkWidget *qq_show_default(contact_info * info)
+{
+	guint32 uid;
+	GdkPixbuf *pixbuf_src;
+	GError *err;
+	gchar *file;
+
+	g_return_val_if_fail(info != NULL, NULL);
+
+	uid = strtol(info->uid, NULL, 10);
+	g_return_val_if_fail(uid != 0, NULL);
+
+	file = _qq_show_get_cache_name(uid, QQ_SHOW_READ);
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Load QQ show image: %s\n", file);
+
+	err = NULL;
+	pixbuf_src = gdk_pixbuf_new_from_file(file, &err);
+
+	if (err != NULL) {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail loaing QQ show: %s\n", err->message);
+		g_free(file);
+		return NULL;
+	}			// if err
+
+	g_free(file);
+	return gtk_image_new_from_pixbuf(_qq_show_scale_image(pixbuf_src));
+}				// qq_show_default
+
+/*****************************************************************************/
+// refresh the image
+void qq_show_get_image(GtkWidget * event_box, GdkEventButton * event, gpointer data)
+{
+	gchar *url;
+	gint uid;
+	GtkWidget *qq_show;
+
+	qq_show = (GtkWidget *) data;
+	g_return_if_fail(qq_show != NULL);
+
+	uid = (gint) g_object_get_data(G_OBJECT(qq_show), "user_data");
+	g_return_if_fail(uid != 0);
+
+	url = g_strdup_printf("%s/%d/%s", QQ_SHOW_SERVER, uid, QQ_SHOW_IMAGE);
+	gaim_url_fetch(url, FALSE, NULL, TRUE, qq_show_render_image, qq_show);
+
+	g_free(url);
+}				// qq_show_get_image
+
+/*****************************************************************************/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/show.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,38 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_SHOW_H_
+#define _QQ_SHOW_H_
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "buddy_info.h"		// contact_info
+
+GtkWidget *qq_show_default(contact_info * info);
+
+void qq_show_get_image(GtkWidget * event_box, GdkEventButton * event, gpointer data);
+
+#endif
+/*****************************************************************************/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/sys_msg.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,323 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "debug.h"		// gaim_debug
+#include "internal.h"		// _("get_text")
+#include "notify.h"		// gaim_noitfy_xx
+#include "request.h"		// gaim_request_action
+
+#include "utils.h"		// hex_dump_to_str
+#include "packet_parse.h"	// create_packet_
+#include "buddy_info.h"		// qq_send_packet_get_info
+#include "buddy_list.h"		// qq_send_packet_get_buddies_online
+#include "buddy_opt.h"		// gc_and_uid
+#include "char_conv.h"		// qq_to_utf8
+#include "crypt.h"		// qq_crypt
+#include "header_info.h"	// cmd alias
+#include "send_core.h"		// qq_send_cmd
+#include "sys_msg.h"
+#include "qq.h"			// qq_data
+
+enum {
+	QQ_MSG_SYS_BEING_ADDED = 0x01,
+	QQ_MSG_SYS_ADD_CONTACT_REQUEST = 0x02,
+	QQ_MSG_SYS_ADD_CONTACT_APPROVED = 0x03,
+	QQ_MSG_SYS_ADD_CONTACT_REJECTED = 0x04,
+	QQ_MSG_SYS_NEW_VERSION = 0x09,
+};
+
+/* Henry: private function for reading/writing of system log */
+static void _qq_sys_msg_log_write(GaimConnection *gc, gchar *msg, gchar *from)
+{
+	GaimLog *log;
+	GaimAccount *account;
+
+	account = gaim_connection_get_account(gc);
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	log = gaim_log_new(GAIM_LOG_IM,
+			"systemim",
+			account,
+			NULL,        //gfhuang
+			time(NULL),
+			NULL
+			);
+	gaim_log_write(log, GAIM_MESSAGE_SYSTEM, from,
+			time(NULL), msg);
+	gaim_log_free(log);
+}
+
+/*****************************************************************************/
+// suggested by rakescar@linuxsir, can still approve after search
+static void _qq_search_before_auth_with_gc_and_uid(gc_and_uid * g)
+{
+	GaimConnection *gc;
+	guint32 uid;
+
+	g_return_if_fail(g != NULL);
+
+	gc = g->gc;
+	uid = g->uid;
+	g_return_if_fail(gc != 0 && uid != 0);
+
+	qq_send_packet_get_info(gc, uid, TRUE);	// we wanna see window
+	gaim_request_action
+	    (gc, NULL, _("Do you wanna approve the request?"), "", 2, g, 2,
+	     _("Reject"),
+	     G_CALLBACK(qq_reject_add_request_with_gc_and_uid),
+	     _("Approve"), G_CALLBACK(qq_approve_add_request_with_gc_and_uid));
+
+}				// _qq_search_before_auth_with_gc_and_uid
+
+/*****************************************************************************/
+static void _qq_search_before_add_with_gc_and_uid(gc_and_uid * g)
+{
+	GaimConnection *gc;
+	guint32 uid;
+
+	g_return_if_fail(g != NULL);
+
+	gc = g->gc;
+	uid = g->uid;
+	g_return_if_fail(gc != 0 && uid != 0);
+
+	qq_send_packet_get_info(gc, uid, TRUE);	// we wanna see window
+	gaim_request_action
+	    (gc, NULL, _("Do you wanna add this buddy?"), "", 2, g, 2,
+	     _("Cancel"), NULL, _("Add"), G_CALLBACK(qq_add_buddy_with_gc_and_uid));
+
+}				// _qq_search_before_add_with_gc_and_uid
+
+/*****************************************************************************/
+// Send ACK if the sys message needs an ACK
+static void _qq_send_packet_ack_msg_sys(GaimConnection * gc, guint8 code, guint32 from, guint16 seq) {
+	guint8 bar, *ack, *cursor;
+	gchar *str;
+	gint ack_len, bytes;
+
+	str = g_strdup_printf("%d", from);
+	bar = 0x1e;
+	ack_len = 1 + 1 + strlen(str) + 1 + 2;
+	ack = g_newa(guint8, ack_len);
+	cursor = ack;
+	bytes = 0;
+
+	bytes += create_packet_b(ack, &cursor, code);
+	bytes += create_packet_b(ack, &cursor, bar);
+	bytes += create_packet_data(ack, &cursor, str, strlen(str));
+	bytes += create_packet_b(ack, &cursor, bar);
+	bytes += create_packet_w(ack, &cursor, seq);
+
+	g_free(str);
+
+	if (bytes == ack_len)	// creation OK
+		qq_send_cmd(gc, QQ_CMD_ACK_SYS_MSG, TRUE, 0, FALSE, ack, ack_len);
+	else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+			   "Fail creating sys msg ACK, expect %d bytes, build %d bytes\n", ack_len, bytes);
+
+}				// _qq_send_packet_ack_msg_sys
+
+/*****************************************************************************/
+// when you are added by a person, QQ server will send sys message
+static void _qq_process_msg_sys_being_added(GaimConnection * gc, gchar * from, gchar * to, gchar * msg_utf8) {
+	gchar *message;
+	GaimBuddy *b;
+	guint32 uid;
+	gc_and_uid *g;
+	gchar *name; //for memory leak bug, by gfhuang
+
+	g_return_if_fail(gc != NULL && from != NULL && to != NULL);
+
+	uid = strtol(from, NULL, 10);
+	name = uid_to_gaim_name(uid);   //by gfhuang
+	b = gaim_find_buddy(gc->account, name);
+	g_free(name);
+	if (b == NULL) {	// the person is not in my list 
+		g = g_new0(gc_and_uid, 1);
+		g->gc = gc;
+		g->uid = uid;	// only need to get value
+		message = g_strdup_printf(_("You have been added by %s"), from);
+		_qq_sys_msg_log_write(gc, message, from);
+		gaim_request_action(gc, NULL, message,
+				    _("Would like to add him?"), 2, g, 3,
+				    _("Cancel"), NULL, _("Add"),
+				    G_CALLBACK
+				    (qq_add_buddy_with_gc_and_uid),
+				    _("Search"), G_CALLBACK(_qq_search_before_add_with_gc_and_uid));
+	} else {
+		message = g_strdup_printf(_("%s has added you [%s]"), from, to);
+		_qq_sys_msg_log_write(gc, message, from);
+		gaim_notify_info(gc, NULL, message, NULL);
+	}
+
+	g_free(message);
+}				// qq_process_msg_sys_being_added
+
+/*****************************************************************************/
+// you are rejected by the person
+static void _qq_process_msg_sys_add_contact_rejected(GaimConnection * gc, gchar * from, gchar * to, gchar * msg_utf8) {
+	gchar *message, *reason;
+
+	g_return_if_fail(gc != NULL && from != NULL && to != NULL);
+
+	message = g_strdup_printf(_("User %s rejected your request"), from);
+	reason = g_strdup_printf(_("Reason: %s"), msg_utf8);
+	_qq_sys_msg_log_write(gc, message, from);
+
+	gaim_notify_info(gc, NULL, message, reason);
+	g_free(message);
+	g_free(reason);
+}				// qq_process_msg_sys_add_contact_rejected
+
+/*****************************************************************************/
+// the buddy approves your request of adding him/her as your friend
+static void _qq_process_msg_sys_add_contact_approved(GaimConnection * gc, gchar * from, gchar * to, gchar * msg_utf8) {
+	gchar *message;
+	qq_data *qd;
+
+	g_return_if_fail(gc != NULL && from != NULL && to != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+	qq_add_buddy_by_recv_packet(gc, strtol(from, NULL, 10), TRUE, TRUE);
+
+	message = g_strdup_printf(_("Use %s has approved your request"), from);
+	_qq_sys_msg_log_write(gc, message, from);
+	gaim_notify_info(gc, NULL, message, NULL);
+
+	g_free(message);
+}				// qq_process_msg_sys_add_contact_approved
+
+/*****************************************************************************/
+// someone wants to add you to his buddy list
+static void _qq_process_msg_sys_add_contact_request(GaimConnection * gc, gchar * from, gchar * to, gchar * msg_utf8) {
+	gchar *message, *reason;
+	guint32 uid;
+	gc_and_uid *g, *g2;
+	GaimBuddy *b;
+	gchar *name;  // by gfhuang
+
+	g_return_if_fail(gc != NULL && from != NULL && to != NULL);
+
+	uid = strtol(from, NULL, 10);
+	g = g_new0(gc_and_uid, 1);
+	g->gc = gc;
+	g->uid = uid;
+
+	message = g_strdup_printf(_("%s wanna add you [%s] as friends"), from, to);
+	reason = g_strdup_printf(_("Message: %s"), msg_utf8);
+	_qq_sys_msg_log_write(gc, message, from);
+
+	gaim_request_action
+	    (gc, NULL, message, reason, 2, g, 3,
+	     _("Reject"),
+	     G_CALLBACK(qq_reject_add_request_with_gc_and_uid),
+	     _("Approve"),
+	     G_CALLBACK(qq_approve_add_request_with_gc_and_uid),
+	     _("Search"), G_CALLBACK(_qq_search_before_auth_with_gc_and_uid));
+
+	g_free(message);
+	g_free(reason);
+
+	name = uid_to_gaim_name(uid);	//by gfhuang
+	b = gaim_find_buddy(gc->account, name);
+	g_free(name);
+	if (b == NULL) {	// the person is not in my list 
+		g2 = g_new0(gc_and_uid, 1);
+		g2->gc = gc;
+		g2->uid = strtol(from, NULL, 10);
+		message = g_strdup_printf(_("%s is not in your buddy list"), from);
+		gaim_request_action(gc, NULL, message,
+				    _("Would you like to add him?"), 2, g2,
+				    3, _("Cancel"), NULL, _("Add"),
+				    G_CALLBACK
+				    (qq_add_buddy_with_gc_and_uid),
+				    _("Search"), G_CALLBACK(_qq_search_before_add_with_gc_and_uid));
+		g_free(message);
+	}			// if b== NULL
+
+}				// qq_process_msg_sys_add_contact_request
+
+/*****************************************************************************/
+void qq_process_msg_sys(guint8 * buf, gint buf_len, guint16 seq, GaimConnection * gc) {
+	qq_data *qd;
+	gint len;
+	guint8 *data;
+	gchar **segments, *code, *from, *to, *msg, *msg_utf8;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	g_return_if_fail(buf != NULL && buf_len != 0);
+
+	qd = (qq_data *) gc->proto_data;
+	len = buf_len;
+	data = g_newa(gchar, len);
+
+	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+		if (NULL == (segments = split_data(data, len, "\x1f", 4)))
+			return;
+		code = segments[0];
+		from = segments[1];
+		to = segments[2];
+		msg = segments[3];
+
+		_qq_send_packet_ack_msg_sys(gc, code[0], strtol(from, NULL, 10), seq);
+
+		if (strtol(to, NULL, 10) != qd->uid) {	// not to me
+			gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Recv sys msg to [%s], not me!, discard\n", to);
+			g_strfreev(segments);
+			return;
+		}
+
+		msg_utf8 = qq_to_utf8(msg, QQ_CHARSET_DEFAULT);
+		switch (strtol(code, NULL, 10)) {
+		case QQ_MSG_SYS_BEING_ADDED:
+			_qq_process_msg_sys_being_added(gc, from, to, msg_utf8);
+			break;
+		case QQ_MSG_SYS_ADD_CONTACT_REQUEST:
+			_qq_process_msg_sys_add_contact_request(gc, from, to, msg_utf8);
+			break;
+		case QQ_MSG_SYS_ADD_CONTACT_APPROVED:
+			_qq_process_msg_sys_add_contact_approved(gc, from, to, msg_utf8);
+			break;
+		case QQ_MSG_SYS_ADD_CONTACT_REJECTED:
+			_qq_process_msg_sys_add_contact_rejected(gc, from, to, msg_utf8);
+			break;
+		case QQ_MSG_SYS_NEW_VERSION:
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ",
+				   "QQ server says there is newer version than %s\n", qq_get_source_str(QQ_CLIENT));
+			break;
+		default:
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Recv unknown sys msg code: %s\n", code);
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "the msg is : %s\n", msg_utf8);
+		}		// switch code
+		g_free(msg_utf8);
+		g_strfreev(segments);
+
+	} else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt recv msg sys\n");
+
+}				// qq_process_msg_sys
+
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/sys_msg.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,35 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_SYS_MSG_H_
+#define _QQ_SYS_MSG_H_
+
+#include <glib.h>
+#include "connection.h"		// GaimConnection
+
+void qq_process_msg_sys(guint8 * buf, gint buf_len, guint16 seq, GaimConnection * gc);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/udp_proxy_s5.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,375 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *                    Henry Ou  <henry@linux.net>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "debug.h"		// gaim_debug
+
+#include "udp_proxy_s5.h"
+
+extern gint			// defined in qq_proxy.c
+ _qq_fill_host(struct sockaddr_in *addr, const gchar * host, guint16 port);
+
+/*****************************************************************************/
+static void _qq_s5_canread_again(gpointer data, gint source, GaimInputCondition cond)
+{
+	unsigned char buf[512];
+	struct PHB *phb = data;
+	struct sockaddr_in sin;
+	int len;
+
+	gaim_input_remove(phb->inpa);
+	gaim_debug(GAIM_DEBUG_INFO, "socks5 proxy", "Able to read again.\n");
+
+	len = read(source, buf, 10);
+	if (len < 10) {
+		gaim_debug(GAIM_DEBUG_WARNING, "socks5 proxy", "or not...\n");
+		close(source);
+
+		if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) {
+
+			phb->func(phb->data, source, GAIM_INPUT_READ);
+		}
+
+		g_free(phb->host);
+		g_free(phb);
+		return;
+	}
+	if ((buf[0] != 0x05) || (buf[1] != 0x00)) {
+		if ((buf[0] == 0x05) && (buf[1] < 0x09))
+			gaim_debug(GAIM_DEBUG_ERROR, "socks5 proxy", "socks5 error: %x\n", buf[1]);
+		else
+			gaim_debug(GAIM_DEBUG_ERROR, "socks5 proxy", "Bad data.\n");
+		close(source);
+
+		if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) {
+
+			phb->func(phb->data, -1, GAIM_INPUT_READ);
+		}
+
+		g_free(phb->host);
+		g_free(phb);
+		return;
+	}
+
+	sin.sin_family = AF_INET;
+	memcpy(&sin.sin_addr.s_addr, buf + 4, 4);
+	memcpy(&sin.sin_port, buf + 8, 2);
+
+	if (connect(phb->udpsock, (struct sockaddr *) &sin, sizeof(struct sockaddr_in)) < 0) {
+		gaim_debug(GAIM_DEBUG_INFO, "s5_canread_again", "connect failed: %s\n", strerror(errno));
+		close(phb->udpsock);
+		close(source);
+		g_free(phb->host);
+		g_free(phb);
+		return;
+	}
+
+	int error = ETIMEDOUT;
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "Connect didn't block\n");
+	len = sizeof(error);
+	if (getsockopt(phb->udpsock, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "getsockopt failed.\n");
+		close(phb->udpsock);
+		return;
+	}
+	fcntl(phb->udpsock, F_SETFL, 0);
+
+	if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) {
+		phb->func(phb->data, phb->udpsock, GAIM_INPUT_READ);
+	}
+
+	g_free(phb->host);
+	g_free(phb);
+}
+
+/*****************************************************************************/
+static void _qq_s5_sendconnect(gpointer data, gint source)
+{
+	unsigned char buf[512];
+	struct PHB *phb = data;
+	struct sockaddr_in sin, ctlsin;
+	int port, ctllen;
+
+	gaim_debug(GAIM_DEBUG_INFO, "s5_sendconnect", "remote host is %s:%d\n", phb->host, phb->port);
+
+	buf[0] = 0x05;
+	buf[1] = 0x03;		/* udp relay */
+	buf[2] = 0x00;		/* reserved */
+	buf[3] = 0x01;		/* address type -- ipv4 */
+	memset(buf + 4, 0, 0x04);
+
+	ctllen = sizeof(ctlsin);
+	if (getsockname(source, (struct sockaddr *) &ctlsin, &ctllen) < 0) {
+		gaim_debug(GAIM_DEBUG_INFO, "QQ", "getsockname: %s\n", strerror(errno));
+		close(source);
+		g_free(phb->host);
+		g_free(phb);
+		return;
+	}
+
+	phb->udpsock = socket(PF_INET, SOCK_DGRAM, 0);
+	gaim_debug(GAIM_DEBUG_INFO, "s5_sendconnect", "UDP socket=%d\n", phb->udpsock);
+	if (phb->udpsock < 0) {
+		close(source);
+		g_free(phb->host);
+		g_free(phb);
+		return;
+	}
+
+	fcntl(phb->udpsock, F_SETFL, O_NONBLOCK);
+
+	port = ntohs(ctlsin.sin_port) + 1;
+	while (1) {
+		_qq_fill_host(&sin, "0.0.0.0", port);
+		if (bind(phb->udpsock, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
+			port++;
+			if (port > 65500) {
+				close(source);
+				g_free(phb->host);
+				g_free(phb);
+				return;
+			}
+		} else
+			break;
+	}
+
+	memset(buf + 4, 0, 0x04);
+	memcpy(buf + 8, &(sin.sin_port), 0x02);
+
+	if (write(source, buf, 10) < 10) {
+		close(source);
+		gaim_debug(GAIM_DEBUG_INFO, "s5_sendconnect", "packet too small\n");
+
+		if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) {
+			phb->func(phb->data, -1, GAIM_INPUT_READ);
+		}
+
+		g_free(phb->host);
+		g_free(phb);
+		return;
+	}
+
+	phb->inpa = gaim_input_add(source, GAIM_INPUT_READ, _qq_s5_canread_again, phb);
+}
+
+/*****************************************************************************/
+static void _qq_s5_readauth(gpointer data, gint source, GaimInputCondition cond)
+{
+	unsigned char buf[512];
+	struct PHB *phb = data;
+
+	gaim_input_remove(phb->inpa);
+	gaim_debug(GAIM_DEBUG_INFO, "socks5 proxy", "Got auth response.\n");
+
+	if (read(source, buf, 2) < 2) {
+		close(source);
+
+		if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) {
+
+			phb->func(phb->data, -1, GAIM_INPUT_READ);
+		}
+
+		g_free(phb->host);
+		g_free(phb);
+		return;
+	}
+
+	if ((buf[0] != 0x01) || (buf[1] != 0x00)) {
+		close(source);
+
+		if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) {
+
+			phb->func(phb->data, -1, GAIM_INPUT_READ);
+		}
+
+		g_free(phb->host);
+		g_free(phb);
+		return;
+	}
+
+	_qq_s5_sendconnect(phb, source);
+}
+
+/*****************************************************************************/
+static void _qq_s5_canread(gpointer data, gint source, GaimInputCondition cond)
+{
+	unsigned char buf[512];
+	struct PHB *phb = data;
+
+	gaim_input_remove(phb->inpa);
+	gaim_debug(GAIM_DEBUG_INFO, "socks5 proxy", "Able to read.\n");
+
+	int ret = read(source, buf, 2);
+	if (ret < 2) {
+		gaim_debug(GAIM_DEBUG_INFO, "s5_canread", "packet smaller than 2 octet\n");
+		close(source);
+
+		if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) {
+
+			phb->func(phb->data, source, GAIM_INPUT_READ);
+		}
+
+		g_free(phb->host);
+		g_free(phb);
+		return;
+	}
+
+	if ((buf[0] != 0x05) || (buf[1] == 0xff)) {
+		gaim_debug(GAIM_DEBUG_INFO, "s5_canread", "unsupport\n");
+		close(source);
+
+		if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) {
+
+			phb->func(phb->data, -1, GAIM_INPUT_READ);
+		}
+
+		g_free(phb->host);
+		g_free(phb);
+		return;
+	}
+
+	if (buf[1] == 0x02) {
+		unsigned int i, j;
+
+		i = strlen(gaim_proxy_info_get_username(phb->gpi));
+		j = strlen(gaim_proxy_info_get_password(phb->gpi));
+
+		buf[0] = 0x01;	/* version 1 */
+		buf[1] = i;
+		memcpy(buf + 2, gaim_proxy_info_get_username(phb->gpi), i);
+		buf[2 + i] = j;
+		memcpy(buf + 2 + i + 1, gaim_proxy_info_get_password(phb->gpi), j);
+
+		if (write(source, buf, 3 + i + j) < 3 + i + j) {
+			close(source);
+
+			if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) {
+
+				phb->func(phb->data, -1, GAIM_INPUT_READ);
+			}
+
+			g_free(phb->host);
+			g_free(phb);
+			return;
+		}
+
+		phb->inpa = gaim_input_add(source, GAIM_INPUT_READ, _qq_s5_readauth, phb);
+	} else {
+		gaim_debug(GAIM_DEBUG_INFO, "s5_canread", "calling s5_sendconnect\n");
+		_qq_s5_sendconnect(phb, source);
+	}
+}
+
+/*****************************************************************************/
+void _qq_s5_canwrite(gpointer data, gint source, GaimInputCondition cond)
+{
+	unsigned char buf[512];
+	int i;
+	struct PHB *phb = data;
+	unsigned int len;
+	int error = ETIMEDOUT;
+
+	gaim_debug(GAIM_DEBUG_INFO, "socks5 proxy", "Connected.\n");
+
+	if (phb->inpa > 0)
+		gaim_input_remove(phb->inpa);
+
+	len = sizeof(error);
+	if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
+		gaim_debug(GAIM_DEBUG_INFO, "getsockopt", "%s\n", strerror(errno));
+		close(source);
+		if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) {
+
+			phb->func(phb->data, -1, GAIM_INPUT_READ);
+		}
+
+		g_free(phb->host);
+		g_free(phb);
+		return;
+	}
+	fcntl(source, F_SETFL, 0);
+
+	i = 0;
+	buf[0] = 0x05;		/* SOCKS version 5 */
+
+	if (gaim_proxy_info_get_username(phb->gpi) != NULL) {
+		buf[1] = 0x02;	/* two methods */
+		buf[2] = 0x00;	/* no authentication */
+		buf[3] = 0x02;	/* username/password authentication */
+		i = 4;
+	} else {
+		buf[1] = 0x01;
+		buf[2] = 0x00;
+		i = 3;
+	}
+
+	if (write(source, buf, i) < i) {
+		gaim_debug(GAIM_DEBUG_INFO, "write", "%s\n", strerror(errno));
+		gaim_debug(GAIM_DEBUG_ERROR, "socks5 proxy", "Unable to write\n");
+		close(source);
+
+		if (phb->account == NULL || gaim_account_get_connection(phb->account) != NULL) {
+
+			phb->func(phb->data, -1, GAIM_INPUT_READ);
+		}
+
+		g_free(phb->host);
+		g_free(phb);
+		return;
+	}
+
+	phb->inpa = gaim_input_add(source, GAIM_INPUT_READ, _qq_s5_canread, phb);
+}
+
+/*****************************************************************************/
+gint qq_proxy_socks5(struct PHB * phb, struct sockaddr * addr, socklen_t addrlen)
+{
+
+	gint fd;
+	gaim_debug(GAIM_DEBUG_INFO, "QQ",
+		   "Connecting to %s:%d via %s:%d using SOCKS5\n",
+		   phb->host, phb->port, gaim_proxy_info_get_host(phb->gpi), gaim_proxy_info_get_port(phb->gpi));
+
+	if ((fd = socket(addr->sa_family, SOCK_STREAM, 0)) < 0)
+		return -1;
+
+	gaim_debug(GAIM_DEBUG_INFO, "QQ", "proxy_sock5 return fd=%d\n", fd);
+
+	fcntl(fd, F_SETFL, O_NONBLOCK);
+	if (connect(fd, addr, addrlen) < 0) {
+		if ((errno == EINPROGRESS) || (errno == EINTR)) {
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Connect in asynchronous mode.\n");
+			phb->inpa = gaim_input_add(fd, GAIM_INPUT_WRITE, _qq_s5_canwrite, phb);
+		} else {
+			close(fd);
+			return -1;
+		}		// if error
+	} else {
+		gaim_debug(GAIM_DEBUG_MISC, "QQ", "Connect in blocking mode.\n");
+		fcntl(fd, F_SETFL, 0);
+		_qq_s5_canwrite(phb, fd, GAIM_INPUT_WRITE);
+	}			// if connect
+
+	return fd;
+}				// qq_proxy_connect
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/udp_proxy_s5.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,37 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *                    Henry Ou  <henry@linux.net>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_UDP_PROXY_S5_H_
+#define _QQ_UDP_PROXY_S5_H_
+
+#include "internal.h"		// for socket stuff
+
+#include "qq_proxy.h"
+
+gint qq_proxy_socks5(struct PHB *phb, struct sockaddr *addr, socklen_t addrlen);
+
+#endif
+/*****************************************************************************/
+// END OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/utils.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,270 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "stdlib.h"		// strtol
+#include "limits.h"
+#include "string.h"		// strlen
+
+#ifdef _WIN32
+#include "win32dep.h"
+#endif
+
+#include "debug.h"		// gaim_debug
+#include "utils.h"
+#include "char_conv.h"		// qq_to_utf8
+#include "prefs.h"		// gaim_prefs_get_string
+
+#define QQ_NAME_FORMAT    "qq-%d"
+
+#ifndef g_str_has_prefix
+gint g_str_has_prefix(const gchar *str, const gchar *prefix)
+{
+	gint len = strlen(prefix);
+	return !strncmp(str, prefix, len);
+}
+#endif
+
+/*****************************************************************************/
+gchar *get_name_by_index_str(gchar ** array, const gchar * index_str, gint amount) {
+	gint index;
+
+	index = atoi(index_str);
+	if (index < 0 || index >= amount)
+		index = 0;
+
+	return array[index];
+}				// get_name_by_index_str
+
+/*****************************************************************************/
+gchar *get_index_str_by_name(gchar ** array, const gchar * name, gint amount) {
+	gint index;
+
+	for (index = 0; index <= amount; index++)
+		if (g_ascii_strcasecmp(array[index], name) == 0)
+			break;
+
+	if (index >= amount)
+		index = 0;	// meaning no match
+	return g_strdup_printf("%d", index);
+}				// get_index_str_by_name
+
+/*****************************************************************************/
+gint qq_string_to_dec_value(const gchar * str)
+{
+	g_return_val_if_fail(str != NULL, 0);
+	return strtol(str, NULL, 10);
+}				// _qq_string_to_dec_value
+
+/*****************************************************************************/
+// split the given data(len) with delimit,
+// check the number of field matches the expected_fields (<=0 means all)
+// return gchar* array (needs to be freed by g_strfreev later), or NULL
+gchar **split_data(guint8 * data, gint len, const gchar * delimit, gint expected_fields) {
+
+	guint8 *input;
+	gchar **segments;
+	gint i, j;
+
+	g_return_val_if_fail(data != NULL && len != 0 && delimit != 0, NULL);
+
+	// as the last field would be string, but data is not ended with 0x00
+	// we have to duplicate the data and append a 0x00 at the end
+	input = g_newa(guint8, len + 1);
+	g_memmove(input, data, len);
+	input[len] = 0x00;
+
+	segments = g_strsplit(input, delimit, 0);
+	if (expected_fields <= 0)
+		return segments;
+
+	for (i = 0; segments[i] != NULL; i++) {;
+	}
+	if (i < expected_fields) {	// not enough fields
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+			   "Invalid data, expect %d fields, found only %d, discard\n", expected_fields, i);
+		g_strfreev(segments);
+		return NULL;
+	} else if (i > expected_fields) {	// more fields, OK
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ",
+			   "Dangerous data, expect %d fields, found %d, return all\n", expected_fields, i);
+		// free up those not used
+		for (j = expected_fields; j < i; j++) {
+			gaim_debug(GAIM_DEBUG_WARNING, "QQ", "field[%d] is %s\n", j, segments[j]);
+			g_free(segments[j]); // bug found by gfhuang ! i -> j
+		}
+		
+		segments[expected_fields] = NULL;
+	}			// if i
+
+	return segments;
+}				// split_data
+
+/*****************************************************************************/
+// given a four-byte ip data, convert it into a human readable ip string
+// the return needs to be freed
+gchar *gen_ip_str(guint8 * ip)
+{
+	return g_strdup_printf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
+}				// gen_ip_str
+
+// by gfhuang
+guint8 *str_ip_gen(gchar *str) {
+	guint8 *ip = g_new(guint8, 4);
+	int a, b, c, d;
+	sscanf(str, "%d.%d.%d.%d", &a, &b, &c, &d);
+	ip[0] = a;
+	ip[1] = b;
+	ip[2] = c;
+	ip[3] = d;
+	return ip;
+}
+
+/*****************************************************************************/
+// return the QQ icon file name
+// the return needs to be freed
+gchar *get_icon_name(gint set, gint suffix)
+{
+	return g_strdup_printf("qq_%d-%d", set, suffix);
+}				// get_icon_name
+
+/*****************************************************************************/
+// convert a QQ UID to a unique name of GAIM
+// the return needs to be freed
+gchar *uid_to_gaim_name(guint32 uid)
+{
+	return g_strdup_printf(QQ_NAME_FORMAT, uid);
+}				// uid_to_gaim_name
+
+/*****************************************************************************/
+// convert GAIM name to original QQ UID
+guint32 gaim_name_to_uid(const gchar * name)
+{
+	gchar *p;
+
+	g_return_val_if_fail(g_str_has_prefix(name, QQ_NAME_PREFIX), 0);
+
+	p = g_strrstr(name, QQ_NAME_PREFIX);
+	// atoi is not thread-safe and also not async-cancel safe
+	// atoi is deprecated by strtol() and should not be used in new code
+	return (p == NULL) ? 0 : strtol(p + strlen(QQ_NAME_PREFIX), NULL, 10);
+}				// gaim_name_to_uid
+
+/*****************************************************************************/
+// convert QQ icon index into its pixbuf
+GdkPixbuf *get_face_gdkpixbuf(guint8 index)
+{
+	gint set, suffix;
+	gchar *image_name, *file_name;
+	GdkPixbuf *pixbuf;
+	const gchar *datadir;
+
+	set = (index / 3) + 1;
+	suffix = (index % 3) + 1;
+
+	image_name = g_strdup_printf("%s.png", get_icon_name(set, suffix));
+	// we need to configure DATADIR in Makefile.am
+	// st = -DDATADIR=\"$(datadir)\"
+	datadir = gaim_prefs_get_string("/plugins/prpl/qq/datadir");
+	if (datadir == NULL || strlen(datadir) == 0)
+		file_name = g_build_filename(datadir, "pixmaps", "gaim", "status", "default", image_name, NULL);
+	else
+		file_name = g_build_filename(DATADIR, "pixmaps", "gaim", "status", "default", image_name, NULL);
+
+	pixbuf = gdk_pixbuf_new_from_file(file_name, NULL);
+
+	g_free(image_name);
+	g_free(file_name);
+
+	return pixbuf;
+}				// get_face_gdkpixbuf
+
+/*****************************************************************************/
+// try to dump the data as GBK
+void try_dump_as_gbk(guint8 * data, gint len)
+{
+	gint i;
+	guint8 *incoming;
+	gchar *msg_utf8;
+
+	incoming = g_newa(guint8, len + 1);
+	g_memmove(incoming, data, len);
+	incoming[len] = 0x00;
+	// GBK code: 
+	// Single-byte ASCII:      0x21-0x7E
+	// GBK first byte range:   0x81-0xFE
+	// GBK second byte range:  0x40-0x7E and 0x80-0xFE
+	for (i = 0; i < len; i++)
+		if (incoming[i] >= 0x81)
+			break;
+
+	msg_utf8 = i < len ? qq_to_utf8(&incoming[i], QQ_CHARSET_DEFAULT) : NULL;
+
+	if (msg_utf8 != NULL) {
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Try extract GB msg: %s\n", msg_utf8);
+		g_free(msg_utf8);
+	}			// msg_utf8 != NULL
+}				// try_dump_gbk
+
+/*****************************************************************************/
+// dump a chunk of raw data into hex string
+// the return should be freed later
+gchar *hex_dump_to_str(const guint8 * buffer, gint bytes)
+{
+	GString *str;
+	gchar *ret;
+	gint i, j, ch;
+
+	str = g_string_new("");
+	for (i = 0; i < bytes; i += 16) {
+		// length label
+		g_string_append_printf(str, "%04d: ", i);
+
+		// dump hex value
+		for (j = 0; j < 16; j++)
+			if ((i + j) < bytes)
+				g_string_append_printf(str, " %02X", buffer[i + j]);
+			else
+				g_string_append(str, "   ");
+		g_string_append(str, "  ");
+
+		// dump ascii value
+		for (j = 0; j < 16 && (i + j) < bytes; j++) {
+			ch = buffer[i + j] & 127;
+			if (ch < ' ' || ch == 127)
+				g_string_append_c(str, '.');
+			else
+				g_string_append_c(str, ch);
+		}		// for j
+		g_string_append_c(str, '\n');
+	}			// for i
+
+	ret = str->str;
+	// GString can be freed without freeing it character data
+	g_string_free(str, FALSE);
+
+	return ret;
+}				// hex_dump_to_str
+
+/*****************************************************************************/
+// ENF OF FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/utils.h	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,54 @@
+/**
+ * The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#ifndef _QQ_MY_UTILS_H_
+#define _QQ_MY_UTILS_H_
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#define QQ_NAME_PREFIX    "qq-"
+
+gchar *get_name_by_index_str(gchar ** array, const gchar * index_str, gint amount);
+gchar *get_index_str_by_name(gchar ** array, const gchar * name, gint amount);
+gint qq_string_to_dec_value(const gchar * str);
+
+gchar **split_data(guint8 * data, gint len, const gchar * delimit, gint expected_fields);
+gchar *gen_ip_str(guint8 * ip);
+guint8 *str_ip_gen(gchar *str); //by gfhuang
+gchar *uid_to_gaim_name(guint32 uid);
+
+guint32 gaim_name_to_uid(const gchar * name);
+
+gchar *get_icon_name(gint set, gint suffix);
+
+GdkPixbuf *get_face_gdkpixbuf(guint8 index);
+
+void try_dump_as_gbk(guint8 * data, gint len);
+
+gchar *hex_dump_to_str(const guint8 * buf, gint buf_len);
+
+#endif
+/*****************************************************************************/
+// END OF FILE