changeset 25717:907ca9a36fe0

explicit merge of '714a7c7f903d11c96ffade34966121da549d998f' and 'd2c40fe4e2181eda5c1c631c7805f17e6b5d22c3' to branch 'org.darkrain42.pidgin.xmpp'
author Paul Aurich <paul@darkrain42.org>
date Thu, 20 Nov 2008 21:13:56 +0000
parents 94ccccab4e98 (current diff) ba362a67278c (diff)
children 9ab681f23007
files libpurple/protocols/jabber/Makefile.mingw libpurple/protocols/jabber/jabber.c libpurple/protocols/jabber/jabber.h libpurple/protocols/msn/slpsession.c libpurple/protocols/msn/slpsession.h libpurple/protocols/msn/soap2.c libpurple/protocols/msn/soap2.h libpurple/protocols/qq/buddy_status.c libpurple/protocols/qq/buddy_status.h libpurple/protocols/qq/keep_alive.c libpurple/protocols/qq/keep_alive.h libpurple/protocols/qq/login_logout.c libpurple/protocols/qq/login_logout.h libpurple/protocols/qq/qq_proxy.c libpurple/protocols/qq/qq_proxy.h libpurple/protocols/qq/recv_core.c libpurple/protocols/qq/recv_core.h libpurple/protocols/qq/send_core.c libpurple/protocols/qq/send_core.h libpurple/protocols/qq/sendqueue.c libpurple/protocols/qq/sendqueue.h libpurple/protocols/qq/udp_proxy_s5.c libpurple/protocols/qq/udp_proxy_s5.h
diffstat 282 files changed, 11991 insertions(+), 9639 deletions(-) [+]
line wrap: on
line diff
--- a/AUTHORS	Mon Aug 18 17:08:01 2008 +0000
+++ b/AUTHORS	Thu Nov 20 21:13:56 2008 +0000
@@ -24,6 +24,7 @@
 Bartosz Oler - Developer
 Etan 'deryni' Reisner - Developer
 Tim 'marv' Ringenbach - Developer
+Elliott 'QuLogic' Sales de Andrade - Developer
 Luke 'LSchiere' Schierer - Support
 Megan 'Cae' Schneider - support/QA
 Evan Schoenberg - Developer
@@ -37,7 +38,6 @@
 Felipe 'shx' Contreras
 Dennis 'EvilDennisR' Ristuccia
 Peter 'Fmoo' Ruibal
-Elliott 'QuLogic' Sales de Andrade
 Gabriel 'Nix' Schulhof
 Jorge 'Masca' Villaseñor
 
--- a/COPYRIGHT	Mon Aug 18 17:08:01 2008 +0000
+++ b/COPYRIGHT	Thu Nov 20 21:13:56 2008 +0000
@@ -8,7 +8,6 @@
 Dave Ahlswede
 Manuel Amador
 Matt Amato
-Elliott Sales de Andrade
 Geoffrey Antos
 Daniel Atallah
 Paul Aurich
@@ -49,6 +48,7 @@
 Chris Boyle
 Derrick J Brashear
 Mauro Sérgio Ferreira Brasil
+Luke Bratch
 Matt Brenneke
 Jeremy Brooks
 Jonathan Brossard
@@ -157,10 +157,12 @@
 Konrad Gräfe
 Miah Gregory
 David Grohmann
+Vladislav Guberinić
 Gideon N. Guillen
 Christian Hammond
 Erick Hamness
 Fred Hampton
+Phil Hannent
 Casey Harkins
 Andy Harrison
 Andrew Hart (arhart)
@@ -228,6 +230,7 @@
 Wesley Lin
 Artem Litvinovich
 Josh Littlefield
+Daniel Ljungborg
 Syd Logan
 Lokheed
 Norberto Lopes
@@ -338,6 +341,7 @@
 Michael Ruprecht
 Sam S.
 Thanumalayan S.
+Elliott Sales de Andrade
 Tomasz Sałaciński <tsalacinski@gmail.com>
 Pradyumna Sampath
 Arvind Samptur
--- a/ChangeLog	Mon Aug 18 17:08:01 2008 +0000
+++ b/ChangeLog	Thu Nov 20 21:13:56 2008 +0000
@@ -5,20 +5,41 @@
 	* Ability to create custom smileys (currently only the MSN protocol
 	  utilizes the feature). (Thanks to Mauro Sérgio Ferreira Brasil,
 	  Marcus Lundblad, Jorge Villaseñor and other contributors)
-	* Yahoo! Japan now uses UTF-8, matching the behavior of official clients
-	  and restoring compatibility with the web messenger (Yusuke Odate)
 	* Add a configure option, --with-system-ssl-certs to allow packagers
 	  to specify a system-wide SSL CA certificates directory.  When set,
 	  we don't install our SSL CA certs, so it's important that the
 	  libpurple package depend on the CA certificates.
 
+	IRC:
+	* /ctcp command (Vladislav Guberinić)
+	* Allow for auto-detection of incoming UTF-8 formatted text on
+	  accounts which are configured to use some other encoding.
+
+	MSN:
+	* Update MSN support to protocol 15 (Elliott Sales de Andrade, Jorge
+	  Villaseñor, Mike Ruprecht, Carlos Silva, Ma Yuan, Daniel Ljungborg
+	  and others)
+	* Personal messages are now supported. They are treated as status
+	  messages.
+	* Offline IM is now supported.
+	* Aliasing is now supported server-side.
+	* Buddies are now emblemed. Bots and web clients should now be
+	  distinguished.
+	* Update smiley set for non-faces.
+	* Failing to update a buddy icon when the buddy has gone offline no
+	  longer crashes.
+	* Custom smileys received in a chat no longer go to a new window.
+	* Processing is no longer completely frozen after the servers block a
+	  message because it contains (what they consider) inappropriate text.
+
 	Pidgin:
 	* Custom buddy icons can now be added to and removed from buddy list
 	  entries via the buddy list entry right-click menu.
 	* Resize large incoming custom smileys to a maximum of 96px on either
 	  side.
 	* Offer to add new buddies into the same contact as existing buddies
-          in the same group if the alias given is the same.
+	  in the same group if the alias given is the same.
+	* Minor smiley style update.
 
 	General:
 	* Group and Chat buddy list entries can now be given custom buddy
@@ -30,6 +51,25 @@
 	  logs.
 	* Added '/msgcolor' command to change colors of different classes of
 	  messages in a conversation. See '/help msgcolor' for details.
+	* Added tab-completion for commands in conversation windows.
+
+version 2.4.3 (07/01/2008):
+	libpurple:
+	* Yahoo! Japan now uses UTF-8, matching the behavior of official clients
+	  and restoring compatibility with the web messenger (Yusuke Odate)
+	* Setting your buddy icon once again works for Yahoo! accounts.
+	* Fixes in the Yahoo! protocol to prevent a double free, crashes on
+	  aliases, and alias functionality
+	* Fix crashes in the bonjour protocol
+	* Always use UTF-8 for Yahoo! (#5973)
+	* Fix a crash when the given jabber id is invalid.
+	* Make the IRC "unknown message" debugging messages UTF-8 safe.
+	* Fix connecting to ICQ
+	* Fix a memleak when handling jabber xforms.
+
+	Pidgin:
+	* Include the send button plugin in the win32 build
+	* Various memory leak fixes
 
 version 2.4.2 (05/17/2008):
 	libpurple:
--- a/ChangeLog.API	Mon Aug 18 17:08:01 2008 +0000
+++ b/ChangeLog.API	Thu Nov 20 21:13:56 2008 +0000
@@ -17,6 +17,10 @@
 		* purple_buddy_icons_node_set_custom_icon_from_file
 		* purple_notify_user_info_prepend_section_break
 		* purple_notify_user_info_prepend_section_header
+		* "website" and "dev_website" items to the ui_info hash table
+		* purple_cmds_get_handle, purple_cmds_init, purple_cmds_uninit
+		* cmd-added and cmd-removed signals
+		* purple_get_host_name
 
 		Deprecated:
 		* purple_blist_update_buddy_icon
@@ -25,6 +29,9 @@
 		* purple_buddy_icons_set_custom_icon
 		* pidgin_set_custom_buddy_icon
 
+		Changed:
+		* xmlnode_copy now copies the prefix and namespace map for nodes.
+
 	pidgin:
 		Added:
 		* gtk_imhtml_smiley_create, gtk_imhtml_smiley_reload and
--- a/ChangeLog.win32	Mon Aug 18 17:08:01 2008 +0000
+++ b/ChangeLog.win32	Thu Nov 20 21:13:56 2008 +0000
@@ -1,4 +1,10 @@
-version 2.4.3 (??/??/2008):
+version 2.5.0 (??/??/2008):
+	* Don't install the GSSAPI SASL plugin on NT4 to avoid an error popup.
+	* Upgrade to Perl 5.10 (System Perl runtime must be upgraded for Perl
+	  plugins to continue to work).
+	* Upgrade SILC to use the 1.1.7 toolkit
+
+version 2.4.3 (07/01/2008):
 	* No changes
 
 version 2.4.2 (05/17/2008):
--- a/NEWS	Mon Aug 18 17:08:01 2008 +0000
+++ b/NEWS	Thu Nov 20 21:13:56 2008 +0000
@@ -1,5 +1,9 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+2.4.3 (07/01/2008):
+	Richard: This release includes important bug fixes.  I'm just cutting
+	the release.  Thank you to the real heroes who did the fixing!
+
 2.4.2 (5/17/2008):
 	Sadrul: We added some usability changes in this release, including the
 	typing notification, buddyicon and input area size in the conversation
--- a/configure.ac	Mon Aug 18 17:08:01 2008 +0000
+++ b/configure.ac	Thu Nov 20 21:13:56 2008 +0000
@@ -336,6 +336,10 @@
 	AC_DEFINE_UNQUOTED(DISPLAY_VERSION, "$VERSION", [display version info])
 fi
 
+AC_ARG_ENABLE(missing-dependencies, [AC_HELP_STRING([--disable-missing-dependencies],
+		[skip missing dependencies instead of aborting configure])],
+	force_deps="$enableval", force_deps="yes")
+
 AC_ARG_WITH(x, [],
 	with_x="$withval", with_x="yes")
 AC_ARG_ENABLE(gtkui, [AC_HELP_STRING([--disable-gtkui],
@@ -419,10 +423,12 @@
 					X11_LIBS="$x_libpath_add"
 					X11_CFLAGS="$x_incpath_add"
 				else
-					AC_MSG_ERROR([
+					if test "x$force_deps" = "xyes" ; then
+						AC_MSG_ERROR([
 X11 development headers not found.
 Use --without-x if you do not need X11 support.
 ])
+					fi
 				fi
 			])
 		AC_SUBST(X11_LIBS)
@@ -461,11 +467,13 @@
 				AC_DEFINE(USE_SCREENSAVER, 1, [Define if we're using XScreenSaver.])
 				AC_SUBST(XSS_LIBS)
 			else
-				AC_MSG_ERROR([
+				if test "x$force_deps" = "xyes" ; then
+					AC_MSG_ERROR([
 XScreenSaver extension development headers not found.
 Use --disable-screensaver if you do not need XScreenSaver extension support,
 this is required for detecting idle time by mouse and keyboard usage.
 ])
+				fi
 			fi
 		else
 			AC_MSG_ERROR([X support is required to build with XScreenSaver extensions])
@@ -490,10 +498,12 @@
 				AC_DEFINE(USE_SM, 1, [Define if we're using X Session Management.])
 				AC_SUBST(SM_LIBS)
 			else
-				AC_MSG_ERROR([
+				if test "x$force_deps" = "xyes" ; then
+					AC_MSG_ERROR([
 X session management development headers not found.
 Use --disable-sm if you do not need session management support.
 ])
+				fi
 			fi
 		else
 			AC_MSG_ERROR([X support is required to build with X session management support])
@@ -515,10 +525,12 @@
 	if test "x$enable_startup_notification" = "xyes"; then
 		PKG_CHECK_MODULES(STARTUP_NOTIFICATION, [libstartup-notification-1.0 >= 0.5], , [
 			AC_MSG_RESULT(no)
-			AC_MSG_ERROR([
+			if test "x$force_deps" = "xyes" ; then
+				AC_MSG_ERROR([
 Startup notification development headers not found.
 Use --disable-startup-notification if you do not need it.
-])])
+])
+			fi])
 
 		if test "x$enable_startup_notification" = "xyes"; then
 			AC_DEFINE(HAVE_STARTUP_NOTIFICATION, 1, [Define if we're using libstartup-notification.])
@@ -533,10 +545,12 @@
 	if test "x$enable_gtkspell" = "xyes" ; then
 		PKG_CHECK_MODULES(GTKSPELL, gtkspell-2.0 >= 2.0.2, , [
 			AC_MSG_RESULT(no)
-			AC_MSG_ERROR([
+			if test "x$force_deps" = "xyes" ; then
+				AC_MSG_ERROR([
 GtkSpell development headers not found.
 Use --disable-gtkspell if you do not need it.
-])])
+])
+			fi])
 		if test "x$enable_gtkspell" = "xyes" ; then
 			AC_DEFINE(USE_GTKSPELL, 1, [Define if we're using GtkSpell])
 			AC_SUBST(GTKSPELL_CFLAGS)
@@ -566,10 +580,12 @@
 			AC_SUBST(EVOLUTION_ADDRESSBOOK_CFLAGS)
 			AC_SUBST(EVOLUTION_ADDRESSBOOK_LIBS)
 		else
-			AC_MSG_ERROR([
+			if test "x$force_deps" = "xyes" ; then
+				AC_MSG_ERROR([
 Evolution development headers not found.
 Use --disable-gevolution if you do not need it.
 ])
+			fi
 		fi
 	fi
 
@@ -579,10 +595,12 @@
 	if test "x$enable_cap" = "xyes"; then
 		PKG_CHECK_MODULES(SQLITE3, sqlite3 >= 3.3,,[
 			AC_MSG_RESULT(no)
-			AC_MSG_ERROR([
+			if test "x$force_deps" = "xyes" ; then
+				AC_MSG_ERROR([
 sqlite3 development headers not found.
 Use --disable-cap if you do not need the Contact Availability Prediction plugin.
-])])
+])
+			fi])
 	fi
         
 
@@ -719,10 +737,12 @@
 			[], [$GSTREAMER_LIBS])
 	], [
 		AC_MSG_RESULT(no)
-		AC_MSG_ERROR([
+		if test "x$force_deps" = "xyes" ; then
+			AC_MSG_ERROR([
 GStreamer development headers not found.
 Use --disable-gstreamer if you do not need GStreamer (sound) support.
-])])
+])
+		fi])
 fi
 
 dnl #######################################################################
@@ -737,10 +757,12 @@
 		have_meanwhile="yes"
 	], [
 		have_meanwhile="no"
-		AC_MSG_ERROR([
+		if test "x$force_deps" = "xyes" ; then
+			AC_MSG_ERROR([
 Meanwhile development headers not found.
 Use --disable-meanwhile if you do not need meanwhile (Sametime) support.
-])])
+])
+		fi])
 fi
 AC_SUBST(MEANWHILE_CFLAGS)
 AC_SUBST(MEANWHILE_LIBS)
@@ -783,7 +805,7 @@
 fi
 AC_CHECK_LIB(avahi-client, avahi_client_new, [avahilibs=yes], [avahilibs=no], $AVAHI_LIBS)
 
-if test "x$enable_avahi" = "xyes" -a \( "x$avahiincludes" = "xno" -o "x$avahilibs" = "xno" \); then
+if test "x$enable_avahi" = "xyes" -a "x$force_deps" = "xyes" -a \( "x$avahiincludes" = "xno" -o "x$avahilibs" = "xno" \); then
 	AC_MSG_ERROR([
 avahi development headers not found.
 Use --disable-avahi if you do not need avahi (Bonjour) support.
@@ -955,8 +977,8 @@
 AC_SUBST(GADU_LIBS)
 AC_SUBST(GADU_CFLAGS)
 
-# change the next line to make MSNP14 the default (s/enable/disable/; s/no/yes/;)
-AC_ARG_ENABLE(msnp14,[AC_HELP_STRING([--enable-msnp14], [Enable the newer MSNP14 protocol (unsupported)])],,enable_msnp14=no)
+# change the next line to not make MSNP15 the default (s/disable/enable/; s/yes/no/;)
+AC_ARG_ENABLE(msnp15,[AC_HELP_STRING([--disable-msnp15], [Disable the newer MSNP15 protocol])],enable_msnp15=$enableval,enable_msnp15=yes)
 
 AC_ARG_ENABLE(distrib,,,enable_distrib=no)
 AM_CONDITIONAL(DISTRIB, test "x$enable_distrib" = "xyes")
@@ -975,7 +997,7 @@
 if test "x$avahiincludes" != "xyes" -o "x$avahilibs" != "xyes"; then
 	STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/bonjour//'`
 fi
-if test "x$enable_msnp14" != "xyes" ; then
+if test "x$enable_msnp15" != "xyes" ; then
 	STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/msn/msnp9/'`
 fi
 if test "x$silcincludes" != "xyes" -o "x$silcclient" != "xyes"; then
@@ -1062,7 +1084,7 @@
 if test "x$avahiincludes" != "xyes" -o "x$avahilibs" != "xyes"; then
 	DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/bonjour//'`
 fi
-if test "x$enable_msnp14" != "xyes" ; then
+if test "x$enable_msnp15" != "xyes" ; then
 	DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/msn/msnp9/'`
 fi
 if test "x$silcincludes" != "xyes" -o "x$silcclient" != "xyes"; then
@@ -1155,7 +1177,6 @@
 			"-Wmissing-declarations" \
 			"-Wmissing-noreturn" \
 			"-Wmissing-prototypes" \
-			"-Wnested-externs" \
 			"-Wpointer-arith" \
 			"-Wundef" \
 	; do
@@ -1220,10 +1241,12 @@
 		AC_SUBST(DBUS_LIBS)
 		enable_dbus=yes
 	], [
+	if test "x$force_deps" = "xyes" ; then
 		AC_MSG_ERROR([
 D-Bus development headers not found.
 Use --disable-dbus if you do not need D-Bus support.
-])])
+])
+	fi])
 
 dnl Check for NetworkManager.h; if we don't have it, oh well
 	if test "x$enable_nm" = "xyes" ; then
@@ -1232,10 +1255,12 @@
 			AC_SUBST(NETWORKMANAGER_LIBS)
 			AC_DEFINE(HAVE_NETWORKMANAGER, 1, [Define if we have NetworkManager.])
 		], [
-			AC_MSG_ERROR([
+			if test "x$force_deps" = "xyes" ; then
+				AC_MSG_ERROR([
 NetworkManager development headers not found.
 Use --disable-nm if you do not need NetworkManager support.
-])])
+])
+			fi])
 	fi
 else
 	enable_nm=no
@@ -1548,7 +1573,7 @@
 	AM_CONDITIONAL(USE_PERL, false)
 fi
 
-if test "x$looked_for_perl" = "xyes" -a "x$enable_perl" = "xno"; then
+if test "x$looked_for_perl" = "xyes" -a "x$enable_perl" = "xno" -a "x$force_deps" = "xyes"; then
 	AC_MSG_ERROR([
 Perl development headers not found.
 Use --disable-perl if you do not need Perl scripting support.
@@ -1959,19 +1984,19 @@
 	msg_ssl=$msg_nss
 elif test "x$msg_gnutls" != "x"; then
 	msg_ssl=$msg_gnutls
-elif test "x$looked_for_gnutls" = "xyes" -a "x$looked_for_nss" = "xyes"; then
+elif test "x$looked_for_gnutls" = "xyes" -a "x$looked_for_nss" = "xyes" -a "x$force_deps" = "xyes" ; then
 	AC_MSG_ERROR([
 Neither GnuTLS or NSS SSL development headers found.
 Use --disable-nss --disable-gnutls if you do not need SSL support.
 MSN, Novell Groupwise and Google Talk will not work without GnuTLS or NSS. OpenSSL is NOT usable!
 ])
-elif test "x$looked_for_gnutls" = "xyes"; then
+elif test "x$looked_for_gnutls" = "xyes" -a "x$force_deps" = "xyes" ; then
 	AC_MSG_ERROR([
 GnuTLS SSL development headers not found.
 Use --disable-gnutls if you do not need SSL support.
 MSN, Novell Groupwise and Google Talk will not work without SSL support.
 ])
-elif test "x$looked_for_nss" = "xyes"; then
+elif test "x$looked_for_nss" = "xyes" -a "x$force_deps" = "xyes" ; then
 	AC_MSG_ERROR([
 NSS SSL development headers not found.
 Use --disable-nss if you do not need SSL support.
@@ -2010,10 +2035,12 @@
 	if test "$TCLCONFIG" = "no"; then
 		AC_MSG_RESULT([no])
 		enable_tcl=no
-		AC_MSG_ERROR([
+		if test "x$force_deps" = "xyes" ; then
+			AC_MSG_ERROR([
 Tcl development headers not found.
 Use --disable-tcl if you do not need Tcl scripting support.
 ])
+		fi
 	else
 		. $TCLCONFIG
 		AC_MSG_CHECKING([Tcl version compatability])
@@ -2078,10 +2105,12 @@
 	if test "$TKCONFIG" = "no"; then
 		AC_MSG_RESULT([no])
 		enable_tk=no
-		AC_MSG_ERROR([
+		if test "x$force_deps" = "xyes" ; then
+			AC_MSG_ERROR([
 Tk development headers not found.
 Use --disable-tk if you do not need Tk scripting support.
 ])
+		fi
 	else
 		. $TKCONFIG
 		eval "TK_LIB_SPEC=\"$TK_LIB_SPEC\""
@@ -2223,9 +2252,11 @@
 AC_CHECK_HEADERS(termios.h)
 
 # sys/sysctl.h on OpenBSD 4.2 requires sys/param.h
+# sys/sysctl.h on FreeBSD requires sys/types.h
 AC_CHECK_HEADERS(sys/param.h)
 AC_CHECK_HEADERS(sys/sysctl.h, [], [],
 	[[
+		#include <sys/types.h>
 		#ifdef HAVE_PARAM_H
 		# include <sys/param.h>
 		#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/cmd-signals.dox	Thu Nov 20 21:13:56 2008 +0000
@@ -0,0 +1,29 @@
+/** @page cmd-signals Command Signals
+  @signals
+  	@signal cmd-added
+	@signal cmd-removed
+  @endsignals
+
+  @see cmds.h
+
+  @signaldef cmd-added
+  	@signalproto
+void (*cmd_added)(const char *command, PurpleCmdPriority priority,
+                  PurpleCmdFlag flag);
+	@endsignalproto
+	@signaldesc
+	 Emitted when a new command is added.
+	@param command   The new command.
+	@param priority  The priority of the new command.
+	@param flag      The command flags.
+  @endsignaldef
+
+  @signaldef cmd-removed
+  	@signalproto
+void (*cmd_removed)(const char *command);
+	@endsignalproto
+	@signaldesc
+	 Emitted when a command is removed.
+	@param command   The removed command.
+  @endsignaldef
+*/
--- a/doc/connection-signals.dox	Mon Aug 18 17:08:01 2008 +0000
+++ b/doc/connection-signals.dox	Thu Nov 20 21:13:56 2008 +0000
@@ -5,6 +5,7 @@
   @signal signed-on
   @signal signing-off
   @signal signed-off
+  @signal connection-error
  @endsignals
 
  @see connection.h
--- a/doc/funniest_home_convos.txt	Mon Aug 18 17:08:01 2008 +0000
+++ b/doc/funniest_home_convos.txt	Thu Nov 20 21:13:56 2008 +0000
@@ -510,3 +510,8 @@
 12:58 <staggered_ranks> why hasn't support for napster been removed?
 12:58 <deryni> It has.
 12:59 <staggered_ranks> oh.. ok
+
+
+14:39 <rrobbertt> Does anyone know a way to get text to speech with pidgin?
+14:41 <elb> do you want to be rooted sooner, or later?
+14:42 <seanegan> good question"; rm -rf ~
--- a/doc/pidgin.1.in	Mon Aug 18 17:08:01 2008 +0000
+++ b/doc/pidgin.1.in	Thu Nov 20 21:13:56 2008 +0000
@@ -582,6 +582,8 @@
 .br
   Tim 'marv' Ringenbach (developer) <\fImarv_sf@users.sf.net\fR>
 .br
+  Elliott 'QuLogic' Sales de Andrade (developer)
+.br
   Luke 'LSchiere' Schierer (support)
 .br
   Megan 'Cae' Schneider (support/QA)
@@ -606,8 +608,6 @@
 .br
   Peter 'fmoo' Ruibal
 .br
-  Elliott 'QuLogic' Sales de Andrade
-.br
   Gabriel 'Nix' Schulhof
 .br
   Jorge 'Masca' Villaseñor
--- a/finch/finch.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/finch/finch.c	Thu Nov 20 21:13:56 2008 +0000
@@ -63,6 +63,8 @@
 
 		g_hash_table_insert(ui_info, "name", (char*)_("Finch"));
 		g_hash_table_insert(ui_info, "version", VERSION);
+		g_hash_table_insert(ui_info, "website", "http://pidgin.im");
+		g_hash_table_insert(ui_info, "dev_website", "http://developer.pidgin.im");
 	}
 
 	return ui_info;
--- a/finch/gntconv.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/finch/gntconv.c	Thu Nov 20 21:13:56 2008 +0000
@@ -141,7 +141,7 @@
 entry_key_pressed(GntWidget *w, FinchConv *ggconv)
 {
 	const char *text = gnt_entry_get_text(GNT_ENTRY(ggconv->entry));
-	if (*text == '/')
+	if (*text == '/' && *(text + 1) != '/')
 	{
 		PurpleConversation *conv = ggconv->active_conv;
 		PurpleCmdStatus status;
@@ -191,7 +191,7 @@
 	}
 	else
 	{
-		char *escape = g_markup_escape_text(text, -1);
+		char *escape = g_markup_escape_text((*text == '/' ? text + 1 : text), -1);
 		char *apos = purple_strreplace(escape, "&apos;", "'");
 		g_free(escape);
 		escape = apos;
@@ -692,11 +692,48 @@
 static void
 completion_cb(GntEntry *entry, const char *start, const char *end)
 {
-	if (start == entry->start)
+	if (start == entry->start && *start != '/')
 		gnt_widget_key_pressed(GNT_WIDGET(entry), ": ");
 }
 
 static void
+gg_setup_commands(FinchConv *fconv, gboolean remove_first)
+{
+	GList *commands;
+	char command[256] = "/";
+
+	if (remove_first) {
+		commands = purple_cmd_list(NULL);
+		for (; commands; commands = g_list_delete_link(commands, commands)) {
+			g_strlcpy(command + 1, commands->data, sizeof(command) - 1);
+			gnt_entry_remove_suggest(GNT_ENTRY(fconv->entry), command);
+		}
+	}
+
+	commands = purple_cmd_list(fconv->active_conv);
+	for (; commands; commands = g_list_delete_link(commands, commands)) {
+		g_strlcpy(command + 1, commands->data, sizeof(command) - 1);
+		gnt_entry_add_suggest(GNT_ENTRY(fconv->entry), command);
+	}
+}
+
+static void
+cmd_added_cb(const char *cmd, PurpleCmdPriority prior, PurpleCmdFlag flags,
+		FinchConv *fconv)
+{
+	gg_setup_commands(fconv, TRUE);
+}
+
+static void
+cmd_removed_cb(const char *cmd, FinchConv *fconv)
+{
+	char command[256] = "/";
+	g_strlcpy(command + 1, cmd, sizeof(command) - 1);
+	gnt_entry_remove_suggest(GNT_ENTRY(fconv->entry), command);
+	gg_setup_commands(fconv, TRUE);
+}
+
+static void
 finch_create_conversation(PurpleConversation *conv)
 {
 	FinchConv *ggc = FINCH_GET_DATA(conv);
@@ -819,6 +856,12 @@
 		ggc->flags |= FINCH_CONV_NO_SOUND;
 
 	gg_create_menu(ggc);
+	gg_setup_commands(ggc, FALSE);
+
+	purple_signal_connect(purple_cmds_get_handle(), "cmd-added", ggc,
+			G_CALLBACK(cmd_added_cb), ggc);
+	purple_signal_connect(purple_cmds_get_handle(), "cmd-removed", ggc,
+			G_CALLBACK(cmd_removed_cb), ggc);
 
 	g_free(title);
 	gnt_box_give_focus_to_child(GNT_BOX(ggc->window), ggc->entry);
@@ -831,11 +874,14 @@
 	/* do stuff here */
 	FinchConv *ggc = FINCH_GET_DATA(conv);
 	ggc->list = g_list_remove(ggc->list, conv);
-	if (ggc->list && conv == ggc->active_conv)
+	if (ggc->list && conv == ggc->active_conv) {
 		ggc->active_conv = ggc->list->data;
-	
+		gg_setup_commands(ggc, TRUE);
+	}
+
 	if (ggc->list == NULL) {
 		g_free(ggc->u.chat);
+		purple_signals_disconnect_by_handle(ggc);
 		if (ggc->window)
 			gnt_widget_destroy(ggc->window);
 		g_free(ggc);
@@ -1013,9 +1059,12 @@
 	if (!new_arrivals)
 	{
 		/* Print the list of users in the room */
-		GString *string = g_string_new(_("List of users:\n"));
+		GString *string = g_string_new(NULL);
 		GList *iter;
+		int count = g_list_length(users);
 
+		g_string_printf(string,
+				ngettext("List of %d user:\n", "List of %d users:\n", count), count);
 		for (iter = users; iter; iter = iter->next)
 		{
 			PurpleConvChatBuddy *cbuddy = iter->data;
@@ -1404,8 +1453,11 @@
 
 	g_return_if_fail(ggconv);
 	g_return_if_fail(g_list_find(ggconv->list, conv));
+	if (ggconv->active_conv == conv)
+		return;
 
 	ggconv->active_conv = conv;
+	gg_setup_commands(ggconv, TRUE);
 	account = purple_conversation_get_account(conv);
 	title = get_conversation_title(conv, account);
 	gnt_screen_rename_widget(ggconv->window, title);
--- a/finch/libgnt/gntbutton.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/finch/libgnt/gntbutton.c	Thu Nov 20 21:13:56 2008 +0000
@@ -77,18 +77,6 @@
 }
 
 static gboolean
-gnt_button_key_pressed(GntWidget *widget, const char *key)
-{
-	if (strcmp(key, GNT_KEY_ENTER) == 0 ||
-			strcmp(key, SAFE(cursor_down)) == 0)
-	{
-		gnt_widget_activate(widget);
-		return TRUE;
-	}
-	return FALSE;
-}
-
-static gboolean
 gnt_button_clicked(GntWidget *widget, GntMouseEvent event, int x, int y)
 {
 	if (event == GNT_LEFT_MOUSE_DOWN) {
@@ -106,23 +94,33 @@
 	g_free(button->priv);
 }
 
+static gboolean
+button_activate(GntBindable *bind, GList *null)
+{
+	gnt_widget_activate(GNT_WIDGET(bind));
+	return TRUE;
+}
+
 static void
 gnt_button_class_init(GntWidgetClass *klass)
 {
 	char *style;
+	GntBindableClass *bindable = GNT_BINDABLE_CLASS(klass);
 
 	parent_class = GNT_WIDGET_CLASS(klass);
 	parent_class->draw = gnt_button_draw;
 	parent_class->map = gnt_button_map;
 	parent_class->size_request = gnt_button_size_request;
-	parent_class->key_pressed = gnt_button_key_pressed;
 	parent_class->clicked = gnt_button_clicked;
 	parent_class->destroy = gnt_button_destroy;
 
 	style = gnt_style_get_from_name(NULL, "small-button");
 	small_button = gnt_style_parse_bool(style);
 	g_free(style);
-	GNTDEBUG;
+
+	gnt_bindable_class_register_action(bindable, "activate", button_activate,
+				GNT_KEY_ENTER, NULL);
+	gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), GNT_BINDABLE_CLASS(klass));
 }
 
 static void
--- a/finch/libgnt/gntkeys.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/finch/libgnt/gntkeys.c	Thu Nov 20 21:13:56 2008 +0000
@@ -80,6 +80,9 @@
 	INSERT_KEY("down",   GNT_KEY_DOWN);
 
 	INSERT_KEY("tab",    "\t");
+	INSERT_KEY("escape", "\033");
+	INSERT_KEY("space", " ");
+	INSERT_KEY("return", GNT_KEY_ENTER);
 	INSERT_KEY("menu",   GNT_KEY_POPUP);
 
 	INSERT_KEY("f1",   GNT_KEY_F1);
@@ -119,6 +122,9 @@
 				code[ind] = (c ? 1 : 'a') + ch;
 				INSERT_COMB(str, code);
 			}
+			if (c == 0) {
+				INSERT_COMB("tab", "\033\t");
+			}
 		}
 	}
 	c = 0;
--- a/libpurple/blist.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/blist.h	Thu Nov 20 21:13:56 2008 +0000
@@ -31,13 +31,20 @@
 
 #include <glib.h>
 
+/** @copydoc _PurpleBuddyList */
 typedef struct _PurpleBuddyList PurpleBuddyList;
+/** @copydoc _PurpleBlistUiOps */
 typedef struct _PurpleBlistUiOps PurpleBlistUiOps;
+/** @copydoc _PurpleBlistNode */
 typedef struct _PurpleBlistNode PurpleBlistNode;
 
+/** @copydoc _PurpleChat */
 typedef struct _PurpleChat PurpleChat;
+/** @copydoc _PurpleGroup */
 typedef struct _PurpleGroup PurpleGroup;
+/** @copydoc _PurpleContact */
 typedef struct _PurpleContact PurpleContact;
+/** @copydoc _PurpleBuddy */
 typedef struct _PurpleBuddy PurpleBuddy;
 
 /**************************************************************************/
--- a/libpurple/cipher.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/cipher.c	Thu Nov 20 21:13:56 2008 +0000
@@ -2402,7 +2402,6 @@
 	g_return_val_if_fail(context, FALSE);
 
 	cipher = context->cipher;
-	g_return_val_if_fail(context, FALSE);
 
 	if(cipher->ops && cipher->ops->digest)
 		return cipher->ops->digest(context, in_len, digest, out_len);
--- a/libpurple/cmds.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/cmds.c	Thu Nov 20 21:13:56 2008 +0000
@@ -81,6 +81,8 @@
 
 	cmds = g_list_insert_sorted(cmds, c, (GCompareFunc)cmds_compare_func);
 
+	purple_signal_emit(purple_cmds_get_handle(), "cmd-added", cmd, p, f);
+
 	return id;
 }
 
@@ -103,6 +105,7 @@
 
 		if (c->id == id) {
 			cmds = g_list_remove(cmds, c);
+			purple_signal_emit(purple_cmds_get_handle(), "cmd-removed", c->cmd);
 			purple_cmd_free(c);
 			return;
 		}
@@ -361,3 +364,28 @@
 	return ret;
 }
 
+gpointer purple_cmds_get_handle(void)
+{
+	static int handle;
+	return &handle;
+}
+
+void purple_cmds_init(void)
+{
+	gpointer handle = purple_cmds_get_handle();
+
+	purple_signal_register(handle, "cmd-added",
+			purple_marshal_VOID__POINTER_INT_INT, NULL, 3,
+			purple_value_new(PURPLE_TYPE_STRING),
+			purple_value_new(PURPLE_TYPE_INT),
+			purple_value_new(PURPLE_TYPE_INT));
+	purple_signal_register(handle, "cmd-removed",
+			purple_marshal_VOID__POINTER, NULL, 1,
+			purple_value_new(PURPLE_TYPE_STRING));
+}
+
+void purple_cmds_uninit(void)
+{
+	purple_signals_unregister_by_instance(purple_cmds_get_handle());
+}
+
--- a/libpurple/cmds.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/cmds.h	Thu Nov 20 21:13:56 2008 +0000
@@ -1,6 +1,7 @@
 /**
  * @file cmds.h Commands API
  * @ingroup core
+ * @see @ref cmd-signals
  */
 
 /* Copyright (C) 2003 Timothy Ringenbach <omarvo@hotmail.com>
@@ -30,6 +31,7 @@
 /**************************************************************************/
 /*@{*/
 
+/** The possible results of running a command with purple_cmd_do_command(). */
 typedef enum _PurpleCmdStatus {
 	PURPLE_CMD_STATUS_OK,
 	PURPLE_CMD_STATUS_FAILED,
@@ -39,16 +41,31 @@
 	PURPLE_CMD_STATUS_WRONG_TYPE,
 } PurpleCmdStatus;
 
+/** Commands registered with the core return one of these values when run.
+ *  Normally, a command will want to return one of the first two; in some
+ *  unusual cases, you might want to have several functions called for a
+ *  particular command; in this case, they should return
+ *  #PURPLE_CMD_RET_CONTINUE to cause the core to fall through to other
+ *  commands with the same name.
+ */
 typedef enum _PurpleCmdRet {
-	PURPLE_CMD_RET_OK,       /**< Everything's okay. Don't look for another command to call. */
+	PURPLE_CMD_RET_OK,       /**< Everything's okay; Don't look for another command to call. */
 	PURPLE_CMD_RET_FAILED,   /**< The command failed, but stop looking.*/
 	PURPLE_CMD_RET_CONTINUE, /**< Continue, looking for other commands with the same name to call. */
 } PurpleCmdRet;
 
 #define PURPLE_CMD_FUNC(func) ((PurpleCmdFunc)func)
 
+/** A function implementing a command, as passed to purple_cmd_register().
+ *
+ *  @todo document the arguments to these functions.
+ * */
 typedef PurpleCmdRet (*PurpleCmdFunc)(PurpleConversation *, const gchar *cmd,
                                   gchar **args, gchar **error, void *data);
+/** A unique integer representing a command registered with
+ *  purple_cmd_register(), which can subsequently be passed to
+ *  purple_cmd_unregister() to unregister that command.
+ */
 typedef guint PurpleCmdId;
 
 typedef enum _PurpleCmdPriority {
@@ -205,6 +222,25 @@
  */
 GList *purple_cmd_help(PurpleConversation *conv, const gchar *cmd);
 
+/**
+ * Get the handle for the commands API
+ * @return The handle
+ * @since 2.5.0
+ */
+gpointer purple_cmds_get_handle(void);
+
+/**
+ * Initialize the commands subsystem.
+ * @since 2.5.0
+ */
+void purple_cmds_init(void);
+
+/**
+ * Uninitialize the commands subsystem.
+ * @since 2.5.0
+ */
+void purple_cmds_uninit(void);
+
 /*@}*/
 
 #ifdef __cplusplus
--- a/libpurple/connection.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/connection.h	Thu Nov 20 21:13:56 2008 +0000
@@ -27,6 +27,7 @@
 #ifndef _PURPLE_CONNECTION_H_
 #define _PURPLE_CONNECTION_H_
 
+/** @copydoc _PurpleConnection */
 typedef struct _PurpleConnection PurpleConnection;
 
 /**
@@ -121,7 +122,7 @@
 	 */
 	PURPLE_CONNECTION_ERROR_CERT_OTHER_ERROR = 15,
 
-	/** Some other error occured which fits into none of the other
+	/** Some other error occurred which fits into none of the other
 	 *  categories.
 	 */
 	/* purple_connection_error_reason() in connection.c uses the fact that
@@ -223,6 +224,8 @@
 	void (*_purple_reserved3)(void);
 } PurpleConnectionUiOps;
 
+
+/* Represents an active connection on an account. */
 struct _PurpleConnection
 {
 	PurplePlugin *prpl;            /**< The protocol plugin.               */
--- a/libpurple/conversation.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/conversation.c	Thu Nov 20 21:13:56 2008 +0000
@@ -1621,7 +1621,7 @@
 		}
 
 		quiet = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_conversations_get_handle(),
-						 "chat-buddy-joining", conv, user, flag)) |
+						 "chat-buddy-joining", conv, user, flag)) ||
 				purple_conv_chat_is_user_ignored(chat, user);
 
 		cbuddy = purple_conv_chat_cb_new(user, alias, flag);
@@ -1633,18 +1633,18 @@
 		cbuddies = g_list_prepend(cbuddies, cbuddy);
 
 		if (!quiet && new_arrivals) {
-			char *escaped = g_markup_escape_text(alias, -1);
+			char *alias_esc = g_markup_escape_text(alias, -1);
 			char *tmp;
 
 			if (extra_msg == NULL)
-				tmp = g_strdup_printf(_("%s entered the room."), escaped);
+				tmp = g_strdup_printf(_("%s entered the room."), alias_esc);
 			else {
-				char *escaped2 = g_markup_escape_text(extra_msg, -1);
+				char *extra_msg_esc = g_markup_escape_text(extra_msg, -1);
 				tmp = g_strdup_printf(_("%s [<I>%s</I>] entered the room."),
-									  escaped, escaped2);
-				g_free(escaped2);
+				                      alias_esc, extra_msg_esc);
+				g_free(extra_msg_esc);
 			}
-			g_free(escaped);
+			g_free(alias_esc);
 
 			purple_conversation_write(conv, NULL, tmp,
 					PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LINKIFY,
@@ -1832,7 +1832,7 @@
 
 		if (!quiet) {
 			const char *alias = user;
-			char *escaped;
+			char *alias_esc;
 			char *tmp;
 
 			if (!(prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME)) {
@@ -1842,17 +1842,17 @@
 					alias = purple_buddy_get_contact_alias(buddy);
 			}
 
-			escaped = g_markup_escape_text(alias, -1);
+			alias_esc = g_markup_escape_text(alias, -1);
 
 			if (reason == NULL || !*reason)
-				tmp = g_strdup_printf(_("%s left the room."), escaped);
+				tmp = g_strdup_printf(_("%s left the room."), alias_esc);
 			else {
-				char *escaped2 = g_markup_escape_text(reason, -1);
+				char *reason_esc = g_markup_escape_text(reason, -1);
 				tmp = g_strdup_printf(_("%s left the room (%s)."),
-									  escaped, escaped2);
-				g_free(escaped2);
+				                      alias_esc, reason_esc);
+				g_free(reason_esc);
 			}
-			g_free(escaped);
+			g_free(alias_esc);
 
 			purple_conversation_write(conv, NULL, tmp,
 					PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LINKIFY,
--- a/libpurple/conversation.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/conversation.h	Thu Nov 20 21:13:56 2008 +0000
@@ -32,11 +32,17 @@
 /**************************************************************************/
 
 
+/** @copydoc _PurpleConversationUiOps */
 typedef struct _PurpleConversationUiOps PurpleConversationUiOps;
+/** @copydoc _PurpleConversation */
 typedef struct _PurpleConversation      PurpleConversation;
+/** @copydoc _PurpleConvIm */
 typedef struct _PurpleConvIm            PurpleConvIm;
+/** @copydoc _PurpleConvChat */
 typedef struct _PurpleConvChat          PurpleConvChat;
+/** @copydoc _PurpleConvChatBuddy */
 typedef struct _PurpleConvChatBuddy     PurpleConvChatBuddy;
+/** @copydoc _PurpleConvMessage */
 typedef struct _PurpleConvMessage       PurpleConvMessage;
 
 /**
@@ -279,11 +285,21 @@
  */
 struct _PurpleConvChatBuddy
 {
-	char *name;                      /**< The name                      */
-	char *alias;					 /**< The alias 					*/
-	char *alias_key;				 /**< The alias key					*/
-	gboolean buddy;					 /**< ChatBuddy is on the blist		*/
-	PurpleConvChatBuddyFlags flags;    /**< Flags (ops, voice etc.)       */
+	char *name;                      /**< The chat participant's name in the chat. */
+	char *alias;                     /**< The chat participant's alias, if known;
+	                                  *   @a NULL otherwise.
+	                                  */
+	char *alias_key;                 /**< A string by which this buddy will be sorted,
+	                                  *   or @c NULL if the buddy should be sorted by
+	                                  *   its @c name.  (This is currently always @c
+	                                  *   NULL.)
+	                                  */
+	gboolean buddy;                  /**< @a TRUE if this chat participant is on the
+	                                  *   buddy list; @a FALSE otherwise.
+	                                  */
+	PurpleConvChatBuddyFlags flags;  /**< A bitwise OR of flags for this participant,
+	                                  *   such as whether they are a channel operator.
+	                                  */
 };
 
 /**
--- a/libpurple/core.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/core.c	Thu Nov 20 21:13:56 2008 +0000
@@ -26,6 +26,7 @@
 #include "internal.h"
 #include "cipher.h"
 #include "certificate.h"
+#include "cmds.h"
 #include "connection.h"
 #include "conversation.h"
 #include "core.h"
@@ -130,6 +131,7 @@
 #endif
 
 	purple_ciphers_init();
+	purple_cmds_init();
 
 	/* Since plugins get probed so early we should probably initialize their
 	 * subsystem right away too.
@@ -230,6 +232,7 @@
 	purple_dbus_uninit();
 #endif
 
+	purple_cmds_uninit();
 	purple_util_uninit();
 
 	purple_signals_uninit();
--- a/libpurple/core.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/core.h	Thu Nov 20 21:13:56 2008 +0000
@@ -1,4 +1,5 @@
 /**
+ * @file core.h Startup and shutdown of libpurple
  * @defgroup core libpurple
  * @see @ref core-signals
  */
@@ -28,12 +29,36 @@
 
 typedef struct PurpleCore PurpleCore;
 
+/** Callbacks that fire at different points of the initialization and teardown
+ *  of libpurple, along with a hook to return descriptive information about the
+ *  UI.
+ */
 typedef struct
 {
+	/** Called just after the preferences subsystem is initialized; the UI
+	 *  could use this callback to add some preferences it needs to be in
+	 *  place when other subsystems are initialized.
+	 */
 	void (*ui_prefs_init)(void);
-	void (*debug_ui_init)(void); /* Unfortunate necessity. */
+	/** Called just after the debug subsystem is initialized, but before
+	 *  just about every other component's initialization.  The UI should
+	 *  use this hook to call purple_debug_set_ui_ops() so that debugging
+	 *  information for other components can be logged during their
+	 *  initialization.
+	 */
+	void (*debug_ui_init)(void);
+	/** Called after all of libpurple has been initialized.  The UI should
+	 *  use this hook to set all other necessary UiOps structures.
+	 *
+	 *  @see @ref ui-ops
+	 */
 	void (*ui_init)(void);
+	/** Called after most of libpurple has been uninitialized. */
 	void (*quit)(void);
+
+	/** Called by purple_core_get_ui_info(); should return the information
+	 *  documented there.
+	 */
 	GHashTable* (*get_ui_info)(void);
 
 	void (*_purple_reserved1)(void);
@@ -64,17 +89,23 @@
 void purple_core_quit(void);
 
 /**
+ * <p>
  * Calls purple_core_quit().  This can be used as the function 
  * passed to purple_timeout_add() when you want to shutdown Purple 
  * in a specified amount of time.  When shutting down Purple 
  * from a plugin, you must use this instead of purple_core_quit();
  * for an immediate exit, use a timeout value of 0: 
- *   purple_timeout_add(0, purple_core_quitcb, NULL);
+ * </p>
+ *
+ * <code>purple_timeout_add(0, purple_core_quitcb, NULL);</code>
+ *
+ * <p>
  * This is ensures that code from your plugin is not being 
  * executed when purple_core_quit() is called.  If the plugin
  * called purple_core_quit() directly, you would get a core dump
  * after purple_core_quit() executes and control returns to your
  * plugin because purple_core_quit() frees all plugins.
+ * </p>
  */
 gboolean purple_core_quit_cb(gpointer unused);
 
@@ -86,7 +117,8 @@
 const char *purple_core_get_version(void);
 
 /**
- * Returns the ID of the UI that is using the core.
+ * Returns the ID of the UI that is using the core, as passed to
+ * purple_core_init().
  *
  * @return The ID of the UI that is currently using the core.
  */
@@ -95,7 +127,7 @@
 /**
  * Returns a handle to the purple core.
  *
- * This is used for such things as signals.
+ * This is used to connect to @ref core-signals "core signals".
  */
 PurpleCore *purple_get_core(void);
 
@@ -114,10 +146,10 @@
 PurpleCoreUiOps *purple_core_get_ui_ops(void);
 
 /**
- * Migrates from .gaim to .purple.
+ * Migrates from <tt>.gaim</tt> to <tt>.purple</tt>.
  *
- * UIs MUST NOT call this if they have been told to use a custom
- * user directory.
+ * UIs <strong>must not</strong> call this if they have been told to use a
+ * custom user directory.
  *
  * @return A boolean indicating success or migration failure. On failure,
  *         the application must display an error to the user and then exit.
@@ -125,20 +157,39 @@
 gboolean purple_core_migrate(void);
 
 /**
- * Ensures that only one instance is running.
+ * Ensures that only one instance is running.  If libpurple is built with D-Bus
+ * support, this checks if another process owns the libpurple bus name and if
+ * so whether that process is using the same configuration directory as this
+ * process.
  *
- * @return A boolean such that @c TRUE indicates that this is the first instance,
- *         whereas @c FALSE indicates that there is another instance running.
+ * @return @c TRUE if this is the first instance of libpurple running;
+ *         @c FALSE if there is another instance running.
  *
  * @since 2.1.0
  */
 gboolean purple_core_ensure_single_instance(void);
 
 /**
- * Returns a hashtable containing various information about the UI
+ * Returns a hash table containing various information about the UI.  The
+ * following well-known entries may be in the table (along with any others the
+ * UI might choose to include):
+ *
+ * <dl>
+ *   <dt><tt>name</tt></dt>
+ *   <dd>the user-readable name for the UI.</dd>
+ *
+ *   <dt><tt>version</tt></dt>
+ *   <dd>a user-readable description of the current version of the UI.</dd>
+ *
+ *   <dt><tt>website</tt></dt>
+ *   <dd>the UI's website, such as http://pidgin.im.</dd>
+ *
+ *   <dt><tt>dev_website</tt></dt>
+ *   <dd>the UI's development/support website, such as http://developer.pidgin.im.</dd>
+ * </dl>
  *
  * @return A GHashTable with strings for keys and values.  This
- * hash table must not be freed.
+ * hash table must not be freed and should not be modified.
  *
  * @since 2.1.0
  *
--- a/libpurple/idle.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/idle.c	Thu Nov 20 21:13:56 2008 +0000
@@ -252,7 +252,7 @@
 	PurpleAccount *account;
 
 	account = purple_connection_get_account(gc);
-	idled_accts = g_list_remove(idled_accts, account);
+	set_account_unidle(account);
 }
 
 static void
--- a/libpurple/internal.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/internal.h	Thu Nov 20 21:13:56 2008 +0000
@@ -140,6 +140,14 @@
 #	define G_MAXUINT32 ((guint32) 0xffffffff)
 #endif
 
+#ifndef G_MAXSIZE
+#	if GLIB_SIZEOF_LONG == 8
+#		define G_MAXSIZE ((gsize) 0xffffffffffffffff)
+#	else
+#		define G_MAXSIZE ((gsize) 0xffffffff)
+#	endif
+#endif
+
 #if GLIB_CHECK_VERSION(2,6,0)
 #	include <glib/gstdio.h>
 #endif
--- a/libpurple/log.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/log.c	Thu Nov 20 21:13:56 2008 +0000
@@ -1083,7 +1083,7 @@
 				}
 
 				/* Determine if this (account, name) combination exists as a buddy. */
-				if (account != NULL)
+				if (account != NULL && name != NULL && *name != '\0')
 					set->buddy = (purple_find_buddy(account, name) != NULL);
 				else
 					set->buddy = FALSE;
--- a/libpurple/nat-pmp.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/nat-pmp.c	Thu Nov 20 21:13:56 2008 +0000
@@ -35,6 +35,10 @@
 #include "signals.h"
 #include "network.h"
 
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+
 #ifdef HAVE_SYS_SYSCTL_H
 #include <sys/sysctl.h>
 #endif
--- a/libpurple/plugin.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/plugin.h	Thu Nov 20 21:13:56 2008 +0000
@@ -199,9 +199,10 @@
  * Handles the initialization of modules.
  */
 #if !defined(PURPLE_PLUGINS) || defined(PURPLE_STATIC_PRPL)
+# define _FUNC_NAME(x) purple_init_##x##_plugin
 # define PURPLE_INIT_PLUGIN(pluginname, initfunc, plugininfo) \
-	gboolean purple_init_##pluginname##_plugin(void);\
-	gboolean purple_init_##pluginname##_plugin(void) { \
+	gboolean _FUNC_NAME(pluginname)(void);\
+	gboolean _FUNC_NAME(pluginname)(void) { \
 		PurplePlugin *plugin = purple_plugin_new(TRUE, NULL); \
 		plugin->info = &(plugininfo); \
 		initfunc((plugin)); \
--- a/libpurple/plugins/autoaccept.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/plugins/autoaccept.c	Thu Nov 20 21:13:56 2008 +0000
@@ -51,6 +51,7 @@
 #define PREF_PATH		PREF_PREFIX "/path"
 #define PREF_STRANGER	PREF_PREFIX "/reject_stranger"
 #define PREF_NOTIFY		PREF_PREFIX "/notify"
+#define PREF_NEWDIR     PREF_PREFIX "/newdir"
 
 typedef enum
 {
@@ -116,7 +117,11 @@
 			{
 				int count = 1;
 				const char *escape;
-				dirname = g_build_filename(pref, purple_normalize(account, xfer->who), NULL);
+
+				if (purple_prefs_get_bool(PREF_NEWDIR))
+					dirname = g_build_filename(pref, purple_normalize(account, xfer->who), NULL);
+				else
+					dirname = g_build_filename(pref, NULL);
 
 				if (!ensure_path_exists(dirname))
 				{
@@ -236,6 +241,10 @@
 					  "(only when there's no conversation with the sender)"));
 	purple_plugin_pref_frame_add(frame, pref);
 
+	pref = purple_plugin_pref_new_with_name_and_label(PREF_NEWDIR,
+			_("Create a new directory for each user"));
+	purple_plugin_pref_frame_add(frame, pref);
+
 	return frame;
 }
 
@@ -294,6 +303,7 @@
 	purple_prefs_add_string(PREF_PATH, dirname);
 	purple_prefs_add_bool(PREF_STRANGER, TRUE);
 	purple_prefs_add_bool(PREF_NOTIFY, TRUE);
+	purple_prefs_add_bool(PREF_NEWDIR, TRUE);
 	g_free(dirname);
 }
 
--- a/libpurple/plugins/perl/Makefile.mingw	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/plugins/perl/Makefile.mingw	Thu Nov 20 21:13:56 2008 +0000
@@ -47,7 +47,7 @@
 			-lws2_32 \
 			-lintl \
 			-lpurple \
-			-lperl58
+			-lperl510
 
 include $(PIDGIN_COMMON_RULES)
 
--- a/libpurple/plugins/perl/common/Makefile.mingw	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/plugins/perl/common/Makefile.mingw	Thu Nov 20 21:13:56 2008 +0000
@@ -5,6 +5,7 @@
 #
 
 PIDGIN_TREE_TOP := ../../../..
+GCCWARNINGS := -Wno-comment -Waggregate-return -Wcast-align -Wdeclaration-after-statement -Werror-implicit-function-declaration -Wextra -Wno-sign-compare -Wno-unused-parameter -Winit-self -Wmissing-declarations -Wmissing-prototypes -Wpointer-arith -Wundef -Wno-unused
 include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
 
 TARGET = Purple
@@ -12,8 +13,6 @@
 EXTUTILS ?= C:/perl/lib/ExtUtils
 PERL_PLUGIN_TOP := ..
 
-CFLAGS += -Wno-comment -Wno-unused
-
 ##
 ## INCLUDE PATHS
 ##
@@ -77,7 +76,7 @@
 ##
 ## LIBRARIES
 ##
-LIBS =			-lperl58 \
+LIBS =			-lperl510 \
 			-lperl \
 			-lpurple \
 			-lglib-2.0
--- a/libpurple/plugins/perl/common/Prefs.xs	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/plugins/perl/common/Prefs.xs	Thu Nov 20 21:13:56 2008 +0000
@@ -1,4 +1,5 @@
 #include "module.h"
+#include "../perl-handlers.h"
 
 MODULE = Purple::Prefs  PACKAGE = Purple::Prefs  PREFIX = purple_prefs_
 PROTOTYPES: ENABLE
@@ -62,13 +63,28 @@
 void
 purple_prefs_destroy()
 
+guint
+purple_prefs_connect_callback(plugin, name, callback, data = 0);
+	Purple::Plugin plugin
+	const char *name
+	SV *callback
+	SV *data
+CODE:
+	RETVAL = purple_perl_prefs_connect_callback(plugin, name, callback, data);
+OUTPUT:
+	RETVAL
+
 void
-purple_prefs_disconnect_by_handle(handle)
-	void * handle
+purple_prefs_disconnect_by_handle(plugin)
+	Purple::Plugin plugin
+CODE:
+	purple_perl_pref_cb_clear_for_plugin(plugin);
 
 void
 purple_prefs_disconnect_callback(callback_id)
 	guint callback_id
+CODE:
+	purple_perl_prefs_disconnect_callback(callback_id);
 
 gboolean
 purple_prefs_exists(name)
--- a/libpurple/plugins/perl/perl-common.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/plugins/perl/perl-common.c	Thu Nov 20 21:13:56 2008 +0000
@@ -32,7 +32,10 @@
 
 static MGVTBL vtbl_free_object =
 {
-	NULL, NULL, NULL, NULL, magic_free_object, NULL, NULL
+	0, 0, 0, 0, magic_free_object, 0, 0
+#if PERL_API_REVISION > 5 || (PERL_API_REVISION == 5 && PERL_API_VERSION >= 10)
+	, 0
+#endif
 };
 
 static SV *
--- a/libpurple/plugins/perl/perl-common.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/plugins/perl/perl-common.h	Thu Nov 20 21:13:56 2008 +0000
@@ -5,9 +5,9 @@
 #ifdef _WIN32
 #undef pipe
 #endif
-#include <XSUB.h>
 #include <EXTERN.h>
 #include <perl.h>
+#include <XSUB.h>
 
 /* XXX: perl defines it's own _ but I think it's safe to undef it */
 #undef _
--- a/libpurple/plugins/perl/perl-handlers.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/plugins/perl/perl-handlers.c	Thu Nov 20 21:13:56 2008 +0000
@@ -5,9 +5,10 @@
 #include "signals.h"
 
 extern PerlInterpreter *my_perl;
-static GList *cmd_handlers = NULL;
-static GList *signal_handlers = NULL;
-static GList *timeout_handlers = NULL;
+static GSList *cmd_handlers = NULL;
+static GSList *signal_handlers = NULL;
+static GSList *timeout_handlers = NULL;
+static GSList *pref_handlers = NULL;
 
 /* perl < 5.8.0 doesn't define PERL_MAGIC_ext */
 #ifndef PERL_MAGIC_ext
@@ -70,7 +71,7 @@
 	STRLEN na;
 	dSP;
 
-	gps = (PurplePerlScript *)plugin->info->extra_info;
+	gps = plugin->info->extra_info;
 
 	ENTER;
 	SAVETMPS;
@@ -131,7 +132,7 @@
 	STRLEN na;
 	dSP;
 
-	gps = (PurplePerlScript *)plugin->info->extra_info;
+	gps = plugin->info->extra_info;
 
 	ENTER;
 	SAVETMPS;
@@ -212,7 +213,7 @@
 {
 	gboolean ret = FALSE;
 
-	timeout_handlers = g_list_remove(timeout_handlers, handler);
+	timeout_handlers = g_slist_remove(timeout_handlers, handler);
 
 	if (handler->iotag > 0)
 		ret = purple_timeout_remove(handler->iotag);
@@ -231,7 +232,7 @@
 static void
 destroy_signal_handler(PurplePerlSignalHandler *handler)
 {
-	signal_handlers = g_list_remove(signal_handlers, handler);
+	signal_handlers = g_slist_remove(signal_handlers, handler);
 
 	if (handler->callback != NULL)
 		SvREFCNT_dec(handler->callback);
@@ -246,7 +247,7 @@
 static gboolean
 perl_timeout_cb(gpointer data)
 {
-	PurplePerlTimeoutHandler *handler = (PurplePerlTimeoutHandler *)data;
+	PurplePerlTimeoutHandler *handler = data;
 	gboolean ret = FALSE;
 	STRLEN na;
 
@@ -282,7 +283,7 @@
 static void *
 perl_signal_cb(va_list args, void *data)
 {
-	PurplePerlSignalHandler *handler = (PurplePerlSignalHandler *)data;
+	PurplePerlSignalHandler *handler = data;
 	void *ret_val = NULL;
 	int i;
 	int count;
@@ -414,10 +415,10 @@
 find_signal_handler(PurplePlugin *plugin, void *instance, const char *signal)
 {
 	PurplePerlSignalHandler *handler;
-	GList *l;
+	GSList *l;
 
 	for (l = signal_handlers; l != NULL; l = l->next) {
-		handler = (PurplePerlSignalHandler *)l->data;
+		handler = l->data;
 
 		if (handler->plugin == plugin &&
 			handler->instance == instance &&
@@ -447,9 +448,9 @@
 	handler->data     = (data != NULL && data != &PL_sv_undef
 	                     ? newSVsv(data) : NULL);
 
-	timeout_handlers = g_list_append(timeout_handlers, handler);
+	timeout_handlers = g_slist_append(timeout_handlers, handler);
 
-	handler->iotag = purple_timeout_add(seconds * 1000, perl_timeout_cb, handler);
+	handler->iotag = purple_timeout_add_seconds(seconds, perl_timeout_cb, handler);
 
 	return handler->iotag;
 }
@@ -457,15 +458,13 @@
 gboolean
 purple_perl_timeout_remove(guint handle)
 {
-	GList *l, *l_next;
+	PurplePerlTimeoutHandler *handler;
+	GSList *l, *l_next;
 
 	for (l = timeout_handlers; l != NULL; l = l_next) {
-		PurplePerlTimeoutHandler *handler;
-
+		handler =  l->data;
 		l_next = l->next;
 
-		handler = (PurplePerlTimeoutHandler *)l->data;
-
 		if (handler->iotag == handle)
 			return destroy_timeout_handler(handler);
 	}
@@ -478,15 +477,13 @@
 void
 purple_perl_timeout_clear_for_plugin(PurplePlugin *plugin)
 {
-	GList *l, *l_next;
+	PurplePerlTimeoutHandler *handler;
+	GSList *l, *l_next;
 
 	for (l = timeout_handlers; l != NULL; l = l_next) {
-		PurplePerlTimeoutHandler *handler;
-
+		handler = l->data;
 		l_next = l->next;
 
-		handler = (PurplePerlTimeoutHandler *)l->data;
-
 		if (handler->plugin == plugin)
 			destroy_timeout_handler(handler);
 	}
@@ -516,7 +513,7 @@
 	handler->data     = (data != NULL &&
 	                     data != &PL_sv_undef ? newSVsv(data) : NULL);
 
-	signal_handlers = g_list_append(signal_handlers, handler);
+	signal_handlers = g_slist_append(signal_handlers, handler);
 
 	purple_signal_connect_priority_vargs(instance, signal, plugin,
 	                                   PURPLE_CALLBACK(perl_signal_cb),
@@ -544,12 +541,11 @@
 purple_perl_signal_clear_for_plugin(PurplePlugin *plugin)
 {
 	PurplePerlSignalHandler *handler;
-	GList *l, *l_next;
+	GSList *l, *l_next;
 
 	for (l = signal_handlers; l != NULL; l = l_next) {
 		l_next = l->next;
-
-		handler = (PurplePerlSignalHandler *)l->data;
+		handler = l->data;
 
 		if (handler->plugin == plugin)
 			destroy_signal_handler(handler);
@@ -570,7 +566,7 @@
 	int i = 0, count, ret_value = PURPLE_CMD_RET_OK;
 	STRLEN na;
 	SV *cmdSV, *tmpSV, *convSV;
-	PurplePerlCmdHandler *handler = (PurplePerlCmdHandler *)data;
+	PurplePerlCmdHandler *handler = data;
 
 	dSP;
 	ENTER;
@@ -645,7 +641,7 @@
 	else
 		handler->data = NULL;
 
-	cmd_handlers = g_list_append(cmd_handlers, handler);
+	cmd_handlers = g_slist_append(cmd_handlers, handler);
 
 	handler->id = purple_cmd_register(command, args, priority, flag, prpl_id,
 	                                PURPLE_CMD_FUNC(perl_cmd_cb), helpstr,
@@ -657,7 +653,7 @@
 static void
 destroy_cmd_handler(PurplePerlCmdHandler *handler)
 {
-	cmd_handlers = g_list_remove(cmd_handlers, handler);
+	cmd_handlers = g_slist_remove(cmd_handlers, handler);
 
 	if (handler->callback != NULL)
 		SvREFCNT_dec(handler->callback);
@@ -673,11 +669,11 @@
 void
 purple_perl_cmd_clear_for_plugin(PurplePlugin *plugin)
 {
-	GList *l, *l_next;
+	PurplePerlCmdHandler *handler;
+	GSList *l, *l_next;
 
 	for (l = cmd_handlers; l != NULL; l = l_next) {
-		PurplePerlCmdHandler *handler = (PurplePerlCmdHandler *)l->data;
-
+		handler = l->data;
 		l_next = l->next;
 
 		if (handler->plugin == plugin)
@@ -688,10 +684,11 @@
 static PurplePerlCmdHandler *
 find_cmd_handler(PurpleCmdId id)
 {
-	GList *l;
+	PurplePerlCmdHandler *handler;
+	GSList *l;
 
 	for (l = cmd_handlers; l != NULL; l = l->next) {
-		PurplePerlCmdHandler *handler = (PurplePerlCmdHandler *)l->data;
+		handler = (PurplePerlCmdHandler *)l->data;
 
 		if (handler->id == id)
 			return handler;
@@ -715,3 +712,141 @@
 	purple_cmd_unregister(id);
 	destroy_cmd_handler(handler);
 }
+
+static void
+perl_pref_cb(const char *name, PurplePrefType type, gconstpointer value,
+			 gpointer data)
+{
+	PurplePerlPrefsHandler *handler = data;
+	STRLEN na;
+
+	dSP;
+	ENTER;
+	SAVETMPS;
+	PUSHMARK(sp);
+	XPUSHs(sv_2mortal(newSVpv(name, 0)));
+
+	XPUSHs(sv_2mortal(newSViv(type)));
+
+	switch(type) {
+		case PURPLE_PREF_INT:
+			XPUSHs(sv_2mortal(newSViv(GPOINTER_TO_INT(value))));
+			break;
+		case PURPLE_PREF_BOOLEAN:
+			XPUSHs((GPOINTER_TO_INT(value) == FALSE) ? &PL_sv_no : &PL_sv_yes);
+			break;
+		case PURPLE_PREF_STRING:
+		case PURPLE_PREF_PATH:
+			XPUSHs(sv_2mortal(newSVGChar(value)));
+			break;
+		case PURPLE_PREF_STRING_LIST:
+		case PURPLE_PREF_PATH_LIST:
+			{
+				AV* av = newAV();
+				const GList *l = value;
+
+				/* Append stuff backward to preserve order */
+				while (l && l->next) l = l->next;
+				while (l) {
+					av_push(av, sv_2mortal(newSVGChar(l->data)));
+					l = l->prev;
+				}
+				XPUSHs(sv_2mortal(newRV_noinc((SV *) av)));
+			} break;
+		default:
+		case PURPLE_PREF_NONE:
+			XPUSHs(&PL_sv_undef);
+			break;
+	}
+
+	XPUSHs((SV *)handler->data);
+	PUTBACK;
+	call_sv(handler->callback, G_EVAL | G_VOID | G_DISCARD);
+	SPAGAIN;
+
+	if (SvTRUE(ERRSV)) {
+		purple_debug_error("perl",
+		                 "Perl prefs callback function exited abnormally: %s\n",
+		                 SvPV(ERRSV, na));
+	}
+
+	PUTBACK;
+	FREETMPS;
+	LEAVE;
+}
+
+guint
+purple_perl_prefs_connect_callback(PurplePlugin *plugin, const char *name,
+								   SV *callback, SV *data)
+{
+	PurplePerlPrefsHandler *handler;
+
+	if (plugin == NULL) {
+		croak("Invalid handle in adding perl prefs handler.\n");
+		return 0;
+	}
+
+	handler = g_new0(PurplePerlPrefsHandler, 1);
+
+	handler->plugin   = plugin;
+	handler->callback = (callback != NULL && callback != &PL_sv_undef
+	                     ? newSVsv(callback) : NULL);
+	handler->data     = (data != NULL && data != &PL_sv_undef
+	                     ? newSVsv(data) : NULL);
+
+	pref_handlers = g_slist_prepend(pref_handlers, handler);
+
+	handler->iotag = purple_prefs_connect_callback(plugin, name, perl_pref_cb, handler);
+
+	return handler->iotag;
+}
+
+static void
+destroy_prefs_handler(PurplePerlPrefsHandler *handler)
+{
+	pref_handlers = g_slist_remove(pref_handlers, handler);
+
+	if (handler->iotag > 0)
+		purple_prefs_disconnect_callback(handler->iotag);
+
+	if (handler->callback != NULL)
+		SvREFCNT_dec(handler->callback);
+
+	if (handler->data != NULL)
+		SvREFCNT_dec(handler->data);
+
+	g_free(handler);
+}
+
+void purple_perl_prefs_disconnect_callback(guint callback_id)
+{
+	GSList *l, *l_next;
+	PurplePerlPrefsHandler *handler;
+
+	for (l = pref_handlers; l != NULL; l = l_next) {
+		l_next = l->next;
+		handler = l->data;
+
+		if (handler->iotag == callback_id) {
+			destroy_prefs_handler(handler);
+			return;
+		}
+	}
+
+	purple_debug_info("perl", "No prefs handler found with handle %u.\n",
+	                  callback_id);
+}
+
+void purple_perl_pref_cb_clear_for_plugin(PurplePlugin *plugin)
+{
+	GSList *l, *l_next;
+	PurplePerlPrefsHandler *handler;
+
+	for (l = pref_handlers; l != NULL; l = l_next) {
+		l_next = l->next;
+		handler = l->data;
+
+		if (handler->plugin == plugin)
+			destroy_prefs_handler(handler);
+	}
+}
--- a/libpurple/plugins/perl/perl-handlers.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/plugins/perl/perl-handlers.h	Thu Nov 20 21:13:56 2008 +0000
@@ -15,8 +15,8 @@
 	PurpleCmdId id;
 	SV *callback;
 	SV *data;
-	char *prpl_id;
-	char *cmd;
+	gchar *prpl_id;
+	gchar *cmd;
 	PurplePlugin *plugin;
 } PurplePerlCmdHandler;
 
@@ -31,7 +31,7 @@
 
 typedef struct
 {
-	char *signal;
+	gchar *signal;
 	SV *callback;
 	SV *data;
 	void *instance;
@@ -39,8 +39,17 @@
 
 } PurplePerlSignalHandler;
 
+typedef struct
+{
+	SV *callback;
+	SV *data;
+	PurplePlugin *plugin;
+	int iotag;
+
+} PurplePerlPrefsHandler;
+
 void purple_perl_plugin_action_cb(PurplePluginAction * gpa);
-GList *purple_perl_plugin_actions(PurplePlugin *plugin, gpointer context); 
+GList *purple_perl_plugin_actions(PurplePlugin *plugin, gpointer context);
 
 PurplePluginPrefFrame *purple_perl_get_plugin_frame(PurplePlugin *plugin);
 
@@ -69,4 +78,8 @@
 void purple_perl_cmd_unregister(PurpleCmdId id);
 void purple_perl_cmd_clear_for_plugin(PurplePlugin *plugin);
 
+guint purple_perl_prefs_connect_callback(PurplePlugin *plugin, const char *name, SV *callback, SV *data);
+void purple_perl_prefs_disconnect_callback(guint callback_id);
+void purple_perl_pref_cb_clear_for_plugin(PurplePlugin *plugin);
+
 #endif /* _PURPLE_PERL_HANDLERS_H_ */
--- a/libpurple/plugins/perl/perl.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/plugins/perl/perl.c	Thu Nov 20 21:13:56 2008 +0000
@@ -67,6 +67,10 @@
 #undef group
 
 /* perl module support */
+#ifdef _WIN32
+EXTERN_C void boot_Win32CORE (pTHX_ CV* cv);
+#endif
+
 #ifdef OLD_PERL
 extern void boot_DynaLoader _((CV * cv));
 #else
@@ -127,10 +131,14 @@
 #endif
 {
 	char *file = __FILE__;
+	dXSUB_SYS;
 
 	/* This one allows dynamic loading of perl modules in perl scripts by
 	 * the 'use perlmod;' construction */
 	newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
+#ifdef _WIN32
+	newXS("Win32CORE::bootstrap", boot_Win32CORE, file);
+#endif
 }
 
 static void
@@ -240,20 +248,66 @@
 static gboolean
 probe_perl_plugin(PurplePlugin *plugin)
 {
-	/* XXX This would be much faster if I didn't create a new
-	 *     PerlInterpreter every time I probed a plugin */
 
-	PerlInterpreter *prober = perl_alloc();
-	char *argv[] = {"", plugin->path };
+	char *args[] = {"", plugin->path };
+	char **argv = args;
+	int argc = 2, ret;
+	PerlInterpreter *prober;
 	gboolean status = TRUE;
 	HV *plugin_info;
+
+	PERL_SYS_INIT(&argc, &argv);
+
+	/* XXX This would be much faster if we didn't create a new
+	 *     PerlInterpreter every time we probe a plugin */
+	prober = perl_alloc();
+
 	PERL_SET_CONTEXT(prober);
+
 	PL_perl_destruct_level = 1;
 	perl_construct(prober);
 
-	perl_parse(prober, xs_init, 2, argv, NULL);
+/* Fix IO redirection to match where pidgin's is going.
+ * Without this, we lose stdout/stderr unless we redirect to a file */
+#ifdef _WIN32
+{
+	PerlIO* newprlIO = PerlIO_open("CONOUT$", "w");
+	if (newprlIO) {
+		int stdout_fd = PerlIO_fileno(PerlIO_stdout());
+		int stderr_fd = PerlIO_fileno(PerlIO_stderr());
+		PerlIO_close(PerlIO_stdout());
+		PerlIO_close(PerlIO_stderr());
+		PerlLIO_dup2(PerlIO_fileno(newprlIO), stdout_fd);
+		PerlLIO_dup2(PerlIO_fileno(newprlIO), stderr_fd);
+
+		PerlIO_close(newprlIO);
+	}
+}
+#endif
+
+	ret = perl_parse(prober, xs_init, argc, argv, NULL);
 
-	perl_run(prober);
+	if (ret != 0) {
+		STRLEN len;
+		const char * errmsg = "Unknown error";
+		if (SvTRUE(ERRSV))
+			errmsg = SvPV(ERRSV, len);
+		purple_debug_error("perl", "Unable to parse plugin %s (%d:%s)\n",
+						   plugin->path, ret, errmsg);
+		goto cleanup;
+	}
+
+	ret = perl_run(prober);
+
+	if (ret != 0) {
+		STRLEN len;
+		const char * errmsg = "Unknown error";
+		if (SvTRUE(ERRSV))
+			errmsg = SvPV(ERRSV, len);
+		purple_debug_error("perl", "Unable to run perl interpreter on plugin %s (%d:%s)\n",
+						   plugin->path, ret, errmsg);
+		goto cleanup;
+	}
 
 	plugin_info = perl_get_hv("PLUGIN_INFO", FALSE);
 
@@ -401,6 +455,7 @@
 		}
 	}
 
+	cleanup:
 	PL_perl_destruct_level = 1;
 	PERL_SET_CONTEXT(prober);
 	perl_destruct(prober);
@@ -523,6 +578,7 @@
 	purple_perl_cmd_clear_for_plugin(plugin);
 	purple_perl_signal_clear_for_plugin(plugin);
 	purple_perl_timeout_clear_for_plugin(plugin);
+	purple_perl_pref_cb_clear_for_plugin(plugin);
 
 	destroy_package(gps->package);
 
@@ -578,7 +634,7 @@
 	load_perl_plugin,                                 /**< load           */
 	unload_perl_plugin,                               /**< unload         */
 	destroy_perl_plugin,                              /**< destroy        */
-	
+
 	/* padding */
 	NULL,
 	NULL,
--- a/libpurple/plugins/perl/scripts/plugin_pref.pl	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/plugins/perl/scripts/plugin_pref.pl	Thu Nov 20 21:13:56 2008 +0000
@@ -44,8 +44,8 @@
 	$ppref = Purple::PluginPref->new_with_name_and_label(
 	    "/plugins/core/perl_test/choice", "Choice Preference");
 	$ppref->set_type(1);
-	$ppref->add_choice("ch0", $frame);
-	$ppref->add_choice("ch1", $frame);
+	$ppref->add_choice("ch0", "ch0-val");
+	$ppref->add_choice("ch1", "ch1-val");
 	$frame->add($ppref);
 	
 	$ppref = Purple::PluginPref->new_with_name_and_label(
@@ -56,12 +56,17 @@
 	return $frame;
 }
 
+sub pref_cb {
+	my ($pref, $type, $value, $data) = @_;
+	
+	print "pref changed: [$pref]($type)=$value data=$data\n";
+}
+
 sub plugin_init { 
 	
 	return %PLUGIN_INFO; 
 } 
 
-
 # This is the sub defined in %PLUGIN_INFO to be called when the plugin is loaded
 #	Note: The plugin has a reference to itself on top of the argument stack.
 sub plugin_load { 
@@ -75,7 +80,11 @@
 	Purple::Prefs::add_bool("/plugins/core/perl_test/bool", 1);	
 	Purple::Prefs::add_string("/plugins/core/perl_test/choice", "ch1");	
 	Purple::Prefs::add_string("/plugins/core/perl_test/text", "Foobar");	
-	
+
+	Purple::Prefs::connect_callback($plugin, "/plugins/core/perl_test", \&pref_cb, "none");
+	Purple::Prefs::connect_callback($plugin, "/plugins/core/perl_test/bool", \&pref_cb, "bool");
+	Purple::Prefs::connect_callback($plugin, "/plugins/core/perl_test/choice", \&pref_cb, "choice");
+	Purple::Prefs::connect_callback($plugin, "/plugins/core/perl_test/text", \&pref_cb, "text");
 
 	print "\n\n" . "#" x 80 . "\n\n";
 } 
--- a/libpurple/protocols/bonjour/bonjour.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/bonjour/bonjour.c	Thu Nov 20 21:13:56 2008 +0000
@@ -641,7 +641,6 @@
 	struct passwd *info;
 #endif
 	const char *fullname = NULL, *splitpoint, *tmp;
-	char hostname[255];
 	gchar *conv = NULL;
 
 #ifndef _WIN32
@@ -691,13 +690,7 @@
 
 	/* Try to figure out a good host name to use */
 	/* TODO: Avoid 'localhost,' if possible */
-	if (gethostname(hostname, sizeof(hostname)) != 0) {
-		purple_debug_warning("bonjour", "Error when getting host name: %s.  Using \"localhost.\"\n",
-				g_strerror(errno));
-		strcpy(hostname, "localhost");
-	}
-	hostname[sizeof(hostname) - 1] = '\0';
-	default_hostname = g_strdup(hostname);
+	default_hostname = g_strdup(purple_get_host_name());
 }
 
 static void
--- a/libpurple/protocols/bonjour/bonjour_ft.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/bonjour/bonjour_ft.c	Thu Nov 20 21:13:56 2008 +0000
@@ -300,6 +300,8 @@
 		}
 		if (xf->proxy_connection != NULL)
 			purple_proxy_connect_cancel(xf->proxy_connection);
+		if (xf->proxy_info != NULL)
+			purple_proxy_info_destroy(xf->proxy_info);
 		if (xf->listen_data != NULL)
 			purple_network_listen_cancel(xf->listen_data);
 		g_free(xf->iq_id);
@@ -802,6 +804,8 @@
 	xmlnode *q_node, *tmp_node;
 	BonjourData *bd;
 
+	xf->proxy_connection = NULL;
+
 	if(source < 0) {
 		purple_debug_error("bonjour", "Error connecting via SOCKS5 - %s\n",
 			error_message ? error_message : "(null)");
@@ -815,9 +819,6 @@
 
 	bd = xf->data;
 
-	purple_proxy_info_destroy(xf->proxy_info);
-	xf->proxy_connection = NULL;
-	xf->proxy_info = NULL;
 	/* Here, start the file transfer.*/
 
 	/* Notify Initiator of Connection */
@@ -871,8 +872,6 @@
 		xep_ft_si_reject(xf->data, xf->iq_id, xfer->who, "404", "cancel");
 		/* Cancel the connection */
 		purple_xfer_cancel_local(xfer);
-		/*purple_proxy_info_destroy(xf->proxy_info);
-		xf->proxy_info = NULL;*/
 	}
 }
 
--- a/libpurple/protocols/bonjour/jabber.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/bonjour/jabber.c	Thu Nov 20 21:13:56 2008 +0000
@@ -672,7 +672,6 @@
 bonjour_jabber_start(BonjourJabber *jdata)
 {
 	struct sockaddr_in my_addr;
-	int yes = 1;
 	int i;
 	gboolean bind_successful;
 
@@ -686,16 +685,6 @@
 		return -1;
 	}
 
-	/* Make the socket reusable */
-	if (setsockopt(jdata->socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) != 0)
-	{
-		purple_debug_error("bonjour", "Error setting socket options: %s\n", g_strerror(errno));
-		purple_connection_error_reason (jdata->account->gc,
-			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-			_("Error setting socket options"));
-		return -1;
-	}
-
 	memset(&my_addr, 0, sizeof(struct sockaddr_in));
 	my_addr.sin_family = AF_INET;
 
@@ -709,6 +698,8 @@
 			bind_successful = TRUE;
 			break;
 		}
+
+		purple_debug_info("bonjour", "Unable to bind to port %u.(%s)\n", jdata->port, g_strerror(errno));
 		jdata->port++;
 	}
 
--- a/libpurple/protocols/gg/gg.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/gg/gg.c	Thu Nov 20 21:13:56 2008 +0000
@@ -527,9 +527,11 @@
 	form->offset = g_strdup(form->last_uin);
 
 	ggp_search_remove(info->searches, form->seq);
+	purple_debug_info("gg", "ggp_callback_show_next(): Removed seq %u", form->seq);
 
 	seq = ggp_search_start(gc, form);
 	ggp_search_add(info->searches, seq, form);
+	purple_debug_info("gg", "ggp_callback_show_next(): Added seq %u", seq);
 }
 /* }}} */
 
@@ -607,6 +609,7 @@
 
 	seq = ggp_search_start(gc, form);
 	ggp_search_add(info->searches, seq, form);
+	purple_debug_info("gg", "ggp_callback_find_buddies(): Added seq %u", seq);
 }
 /* }}} */
 
@@ -991,6 +994,7 @@
 	GGPInfo *info = form->user_data;
 
 	ggp_search_remove(info->searches, form->seq);
+	purple_debug_info("gg", "ggp_sr_close_cb(): Removed seq %u", form->seq);
 	ggp_search_form_destroy(form);
 }
 /* }}} */
@@ -1206,7 +1210,7 @@
 
 	seq = gg_pubdir50_seq(req);
 	form = ggp_search_get(info->searches, seq);
-
+	purple_debug_info("gg", "ggp_pubdir_reply_handler(): seq %u --> form %p", seq, form);
 	/*
 	 * this can happen when user will request more results
 	 * and close the results window before they arrive.
@@ -1819,6 +1823,7 @@
 
 	seq = ggp_search_start(gc, form);
 	ggp_search_add(info->searches, seq, form);
+	purple_debug_info("gg", "ggp_get_info(): Added seq %u", seq);
 }
 /* }}} */
 
--- a/libpurple/protocols/gg/search.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/gg/search.h	Thu Nov 20 21:13:56 2008 +0000
@@ -130,7 +130,7 @@
  * @param gc   PurpleConnection.
  * @param form Filled in GGPSearchForm.
  *
- * @return Sequence number of a search or 0 if an error occured.
+ * @return Sequence number of a search or 0 if an error occurred.
  */
 guint32
 ggp_search_start(PurpleConnection *gc, GGPSearchForm *form);
--- a/libpurple/protocols/irc/cmds.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/irc/cmds.c	Thu Nov 20 21:13:56 2008 +0000
@@ -68,6 +68,31 @@
 	return 0;
 }
 
+int irc_cmd_ctcp(struct irc_conn *irc, const char *cmd, const char *target, const char **args)
+{
+	/* we have defined args as args[0] is target and args[1] is ctcp command */
+        char *buf;
+	GString *string;
+	
+	/* check if we have args */
+	if (!args || !args[0] || !args[1])
+		return 0;
+
+	/* TODO:strip newlines or send each line as separate ctcp or something
+	 * actually, this shouldn't be done here but somewhere else since irc should support escaping newlines */
+
+	string = g_string_new(args[1]);
+	g_string_prepend_c (string,'\001');
+	g_string_append_c (string,'\001');
+	buf = irc_format(irc, "vn:", "PRIVMSG", args[0], string->str);
+	g_string_free(string,TRUE);
+
+	irc_send(irc, buf);
+	g_free(buf);
+	
+	return 1;
+}
+
 int irc_cmd_ctcp_action(struct irc_conn *irc, const char *cmd, const char *target, const char **args)
 {
 	PurpleConnection *gc = purple_account_get_connection(irc->account);
--- a/libpurple/protocols/irc/irc.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/irc/irc.c	Thu Nov 20 21:13:56 2008 +0000
@@ -360,11 +360,11 @@
 
 static gboolean do_login(PurpleConnection *gc) {
 	char *buf, *tmp = NULL;
-	char hostname[256];
+	char *hostname, *server;
+	const char *hosttmp;
 	const char *username, *realname;
 	struct irc_conn *irc = gc->proto_data;
 	const char *pass = purple_connection_get_password(gc);
-	int ret;
 
 	if (pass && *pass) {
 		buf = irc_format(irc, "vv", "PASS", pass);
@@ -375,13 +375,6 @@
 		g_free(buf);
 	}
 
-
-	ret = gethostname(hostname, sizeof(hostname));
-	hostname[sizeof(hostname) - 1] = '\0';
-	if (ret < 0 || hostname[0] == '\0') {
-		purple_debug_warning("irc", "gethostname() failed -- is your hostname set?");
-		strcpy(hostname, "localhost");
-	}
 	realname = purple_account_get_string(irc->account, "realname", "");
 	username = purple_account_get_string(irc->account, "username", "");
 
@@ -396,9 +389,29 @@
 		}
 	}
 
-	buf = irc_format(irc, "vvvv:", "USER", tmp ? tmp : username, hostname, irc->server,
-			      strlen(realname) ? realname : IRC_DEFAULT_ALIAS);
+	hosttmp = purple_get_host_name();
+	if (*hosttmp == ':') {
+		/* This is either an IPv6 address, or something which
+		 * doesn't belong here.  Either way, we need to escape
+		 * it. */
+		hostname = g_strdup_printf("0%s", hosttmp);
+	} else {
+		/* Ugly, I know. */
+		hostname = g_strdup(hosttmp);
+	}
+
+	if (*irc->server == ':') {
+		/* Same as hostname, above. */
+		server = g_strdup_printf("0%s", irc->server);
+	} else {
+		server = g_strdup(irc->server);
+	}
+
+	buf = irc_format(irc, "vvvv:", "USER", tmp ? tmp : username, hostname, server,
+	                 strlen(realname) ? realname : IRC_DEFAULT_ALIAS);
 	g_free(tmp);
+	g_free(hostname);
+	g_free(server);
 	if (irc_send(irc, buf) < 0) {
 		g_free(buf);
 		return FALSE;
@@ -976,6 +989,9 @@
 	option = purple_account_option_string_new(_("Encodings"), "encoding", IRC_DEFAULT_CHARSET);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 
+	option = purple_account_option_bool_new(_("Auto-detect incoming UTF-8"), "autodetect_utf8", IRC_DEFAULT_AUTODETECT);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
 	option = purple_account_option_string_new(_("Username"), "username", "");
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 
--- a/libpurple/protocols/irc/irc.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/irc/irc.h	Thu Nov 20 21:13:56 2008 +0000
@@ -35,6 +35,7 @@
 #define IRC_DEFAULT_SSL_PORT 994
 
 #define IRC_DEFAULT_CHARSET "UTF-8"
+#define IRC_DEFAULT_AUTODETECT FALSE
 #define IRC_DEFAULT_ALIAS "purple"
 
 #define IRC_DEFAULT_QUIT "Leaving."
@@ -164,6 +165,7 @@
 
 int irc_cmd_default(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
 int irc_cmd_away(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
+int irc_cmd_ctcp(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
 int irc_cmd_ctcp_action(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
 int irc_cmd_ctcp_version(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
 int irc_cmd_invite(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
--- a/libpurple/protocols/irc/msgs.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/irc/msgs.c	Thu Nov 20 21:13:56 2008 +0000
@@ -215,10 +215,8 @@
 			/* This is an extended syntax, not in RFC 1459 */
 			int t1 = atoi(args[4]);
 			time_t t2 = time(NULL);
-			msg = g_strdup_printf(ngettext("Ban on %s by %s, set %ld second ago",
-						       "Ban on %s by %s, set %ld seconds ago",
-						       t2 - t1),
-			                      args[2], args[3], t2 - t1);
+			msg = g_strdup_printf(_("Ban on %s by %s, set %s ago"),
+			                      args[2], args[3], purple_str_seconds_to_string(t2 - t1));
 		} else {
 			msg = g_strdup_printf(_("Ban on %s"), args[2]);
 		}
@@ -1007,6 +1005,9 @@
 	irc->reqnick = newnick;
 	irc->nickused = TRUE;
 
+	purple_connection_set_display_name(
+		purple_account_get_connection(irc->account), newnick);
+
 	buf = irc_format(irc, "vn", "NICK", newnick);
 	irc_send(irc, buf);
 	g_free(buf);
--- a/libpurple/protocols/irc/parse.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/irc/parse.c	Thu Nov 20 21:13:56 2008 +0000
@@ -123,6 +123,7 @@
 } _irc_cmds[] = {
 	{ "action", ":", irc_cmd_ctcp_action, N_("action &lt;action to perform&gt;:  Perform an action.") },
 	{ "away", ":", irc_cmd_away, N_("away [message]:  Set an away message, or use no message to return from being away.") },
+	{ "ctcp", "t:", irc_cmd_ctcp, N_("ctcp <nick> <msg>: sends ctcp msg to nick.") },
 	{ "chanserv", ":", irc_cmd_service, N_("chanserv: Send a command to chanserv") },
 	{ "deop", ":", irc_cmd_op, N_("deop &lt;nick1&gt; [nick2] ...:  Remove channel operator status from someone. You must be a channel operator to do this.") },
 	{ "devoice", ":", irc_cmd_op, N_("devoice &lt;nick1&gt; [nick2] ...:  Remove channel voice status from someone, preventing them from speaking if the channel is moderated (+m). You must be a channel operator to do this.") },
@@ -252,6 +253,7 @@
 	char *utf8 = NULL;
 	const gchar *charset, *enclist;
 	gchar **encodings;
+	gboolean autodetect;
 	int i;
 
 	enclist = purple_account_get_string(irc->account, "encoding", IRC_DEFAULT_CHARSET);
@@ -262,6 +264,12 @@
 		return purple_utf8_salvage(string);
 	}
 
+	autodetect = purple_account_get_bool(irc->account, "autodetect_utf8", IRC_DEFAULT_AUTODETECT);
+
+	if (autodetect && g_utf8_validate(string, -1, NULL)) {
+		return g_strdup(string);
+	}
+
 	for (i = 0; encodings[i] != NULL; i++) {
 		charset = encodings[i];
 		while (*charset == ' ')
--- a/libpurple/protocols/jabber/Makefile.mingw	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/jabber/Makefile.mingw	Thu Nov 20 21:13:56 2008 +0000
@@ -89,10 +89,6 @@
 LIB_PATHS += -L$(CYRUS_SASL_TOP)/bin
 LIBS += -llibsasl
 CYRUS_SASL_DLLS = \
-			$(CYRUS_SASL_TOP)/bin/comerr32.dll \
-			$(CYRUS_SASL_TOP)/bin/gssapi32.dll \
-			$(CYRUS_SASL_TOP)/bin/k5sprt32.dll \
-			$(CYRUS_SASL_TOP)/bin/krb5_32.dll \
 			$(CYRUS_SASL_TOP)/bin/libsasl.dll
 
 CYRUS_SASL_PLUGINS = \
--- a/libpurple/protocols/jabber/buddy.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Thu Nov 20 21:13:56 2008 +0000
@@ -1793,22 +1793,6 @@
 	}
 }
 
-void jabber_buddy_get_info_chat(PurpleConnection *gc, int id,
-		const char *resource)
-{
-	JabberStream *js = gc->proto_data;
-	JabberChat *chat = jabber_chat_find_by_id(js, id);
-	char *full_jid;
-
-	if(!chat)
-		return;
-
-	full_jid = g_strdup_printf("%s@%s/%s", chat->room, chat->server, resource);
-	jabber_buddy_get_info_for_jid(js, full_jid);
-	g_free(full_jid);
-}
-
-
 static void jabber_buddy_set_invisibility(JabberStream *js, const char *who,
 		gboolean invisible)
 {
--- a/libpurple/protocols/jabber/buddy.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/jabber/buddy.h	Thu Nov 20 21:13:56 2008 +0000
@@ -96,8 +96,6 @@
 void jabber_buddy_remove_resource(JabberBuddy *jb, const char *resource);
 const char *jabber_buddy_get_status_msg(JabberBuddy *jb);
 void jabber_buddy_get_info(PurpleConnection *gc, const char *who);
-void jabber_buddy_get_info_chat(PurpleConnection *gc, int id,
-		const char *resource);
 
 GList *jabber_blist_node_menu(PurpleBlistNode *node);
 
--- a/libpurple/protocols/jabber/chat.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/jabber/chat.c	Thu Nov 20 21:13:56 2008 +0000
@@ -342,12 +342,18 @@
 {
 	JabberStream *js = gc->proto_data;
 	JabberChat *chat;
+	JabberChatMember *jcm;
 
 	chat = jabber_chat_find_by_id(js, id);
 
 	if(!chat)
 		return NULL;
 
+	jcm = g_hash_table_lookup(chat->members, who);
+	if (jcm != NULL && jcm->jid)
+		return g_strdup(jcm->jid);
+	
+
 	return g_strdup_printf("%s@%s/%s", chat->room, chat->server, who);
 }
 
--- a/libpurple/protocols/jabber/disco.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/jabber/disco.c	Thu Nov 20 21:13:56 2008 +0000
@@ -73,6 +73,15 @@
 			  "jid='%s' host='%s' port='%d' zeroconf='%s'\n",
 			   from ? from : "", sh->host ? sh->host : "",
 			   sh->port, sh->zeroconf ? sh->zeroconf : "");
+
+	/* TODO: When we support zeroconf proxies, fix this to handle them */
+	if (!(sh->jid && sh->host && sh->port > 0)) {
+		g_free(sh->jid);
+		g_free(sh->host);
+		g_free(sh->zeroconf);
+		g_free(sh);
+		js->bs_proxies = g_list_remove(js->bs_proxies, sh);
+	}
 }
 
 
@@ -280,6 +289,7 @@
 static void
 jabber_disco_finish_server_info_result_cb(JabberStream *js)
 {
+	const char *ft_proxies;
 
 	jabber_vcard_fetch_mine(js);
 
@@ -290,11 +300,44 @@
 
 	/* Send initial presence; this will trigger receipt of presence for contacts on the roster */
 	jabber_presence_send(js->gc->account, NULL);
-	
+
 	if (js->server_caps & JABBER_CAP_ADHOC) {
 		/* The server supports ad-hoc commands, so let's request the list */
 		jabber_adhoc_server_get_list(js);
 	}
+
+	/* If there are manually specified bytestream proxies, query them */
+	ft_proxies = purple_account_get_string(js->gc->account, "ft_proxies", NULL);
+	if (ft_proxies) {
+		JabberIq *iq;
+		JabberBytestreamsStreamhost *sh;
+		int i;
+		char *tmp;
+		gchar **ft_proxy_list = g_strsplit(ft_proxies, ",", 0);
+
+		for(i = 0; ft_proxy_list[i]; i++) {
+			g_strstrip(ft_proxy_list[i]);
+			if(!(*ft_proxy_list[i]))
+				continue;
+
+			/* We used to allow specifying a port directly here; get rid of it */
+			if((tmp = strchr(ft_proxy_list[i], ':')))
+				*tmp = '\0';
+
+			sh = g_new0(JabberBytestreamsStreamhost, 1);
+			sh->jid = g_strdup(ft_proxy_list[i]);
+			js->bs_proxies = g_list_prepend(js->bs_proxies, sh);
+
+			iq = jabber_iq_new_query(js, JABBER_IQ_GET,
+						 "http://jabber.org/protocol/bytestreams");
+			xmlnode_set_attrib(iq->node, "to", sh->jid);
+			jabber_iq_set_callback(iq, jabber_disco_bytestream_server_cb, sh);
+			jabber_iq_send(iq);
+		}
+
+		g_strfreev(ft_proxy_list);
+	}
+
 }
 
 static void
--- a/libpurple/protocols/jabber/google.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/jabber/google.c	Thu Nov 20 21:13:56 2008 +0000
@@ -314,11 +314,6 @@
 		buddies = buddies->next;
 	}
 
-	iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:roster");
-
-	query = xmlnode_get_child(iq->node, "query");
-	item = xmlnode_new_child(query, "item");
-
 	xmlnode_set_attrib(item, "jid", who);
 	xmlnode_set_attrib(item, "name", b->alias ? b->alias : "");
 	xmlnode_set_attrib(item, "gr:t", "B");
@@ -385,11 +380,6 @@
 		buddies = buddies->next;
 	}
 
-	iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:roster");
-
-	query = xmlnode_get_child(iq->node, "query");
-	item = xmlnode_new_child(query, "item");
-
 	xmlnode_set_attrib(item, "jid", who);
 	xmlnode_set_attrib(item, "name", b->alias ? b->alias : "");
 	xmlnode_set_attrib(query, "xmlns:gr", "google:roster");
--- a/libpurple/protocols/jabber/jabber.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Thu Nov 20 21:13:56 2008 +0000
@@ -68,6 +68,7 @@
 GHashTable *jabber_contact_info = NULL;
 
 static void jabber_unregister_account_cb(JabberStream *js);
+static void try_srv_connect(JabberStream *js);
 
 static void jabber_stream_init(JabberStream *js)
 {
@@ -278,9 +279,42 @@
 	purple_circ_buffer_mark_read(js->write_buffer, ret);
 }
 
+static gboolean do_jabber_send_raw(JabberStream *js, const char *data, int len)
+{
+	int ret;
+	gboolean success = TRUE;
+
+	if (len == -1)
+		len = strlen(data);
+
+	if (js->writeh == 0)
+		ret = jabber_do_send(js, data, len);
+	else {
+		ret = -1;
+		errno = EAGAIN;
+	}
+
+	if (ret < 0 && errno != EAGAIN) {
+		purple_connection_error_reason (js->gc,
+			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+			_("Write error"));
+		success = FALSE;
+	} else if (ret < len) {
+		if (ret < 0)
+			ret = 0;
+		if (js->writeh == 0)
+			js->writeh = purple_input_add(
+				js->gsc ? js->gsc->fd : js->fd,
+				PURPLE_INPUT_WRITE, jabber_send_cb, js);
+		purple_circ_buffer_append(js->write_buffer,
+			data + ret, len - ret);
+	}
+
+	return success;
+}
+
 void jabber_send_raw(JabberStream *js, const char *data, int len)
 {
-	int ret;
 
 	/* because printing a tab to debug every minute gets old */
 	if(strcmp(data, "\t"))
@@ -289,55 +323,33 @@
 
 	/* If we've got a security layer, we need to encode the data,
 	 * splitting it on the maximum buffer length negotiated */
-	
+
 	purple_signal_emit(my_protocol, "jabber-sending-text", js->gc, &data);
 	if (data == NULL)
 		return;
-	
+
 #ifdef HAVE_CYRUS_SASL
 	if (js->sasl_maxbuf>0) {
-		int pos;
+		int pos = 0;
 
 		if (!js->gsc && js->fd<0)
 			return;
-		pos = 0;
+
 		if (len == -1)
 			len = strlen(data);
+
 		while (pos < len) {
 			int towrite;
 			const char *out;
 			unsigned olen;
 
-			if ((len - pos) < js->sasl_maxbuf)
-				towrite = len - pos;
-			else
-				towrite = js->sasl_maxbuf;
+			towrite = MIN((len - pos), js->sasl_maxbuf);
 
 			sasl_encode(js->sasl, &data[pos], towrite, &out, &olen);
 			pos += towrite;
 
-			if (js->writeh == 0)
-				ret = jabber_do_send(js, out, olen);
-			else {
-				ret = -1;
-				errno = EAGAIN;
-			}
-
-			if (ret < 0 && errno != EAGAIN)
-				purple_connection_error_reason (js->gc,
-					PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-					_("Write error"));
-			else if (ret < olen) {
-				if (ret < 0)
-					ret = 0;
-				if (js->writeh == 0)
-					js->writeh = purple_input_add(
-						js->gsc ? js->gsc->fd : js->fd,
-						PURPLE_INPUT_WRITE,
-						jabber_send_cb, js);
-				purple_circ_buffer_append(js->write_buffer,
-					out + ret, olen - ret);
-			}
+			if (!do_jabber_send_raw(js, out, olen))
+				break;
 		}
 		return;
 	}
@@ -354,29 +366,8 @@
 							_("Someone tried to send non-XML in a Jabber world."));
 		}
 	} else {
-		if (js->writeh == 0)
-			ret = jabber_do_send(js, data, len);
-		else {
-			ret = -1;
-			errno = EAGAIN;
-		}
-
-		if (ret < 0 && errno != EAGAIN)
-			purple_connection_error_reason (js->gc,
-				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-				_("Write error"));
-		else if (ret < len) {
-			if (ret < 0)
-				ret = 0;
-			if (js->writeh == 0)
-				js->writeh = purple_input_add(
-					js->gsc ? js->gsc->fd : js->fd,
-					PURPLE_INPUT_WRITE, jabber_send_cb, js);
-			purple_circ_buffer_append(js->write_buffer,
-				data + ret, len - ret);
-		}
+		do_jabber_send_raw(js, data, len);
 	}
-	return;
 }
 
 int jabber_prpl_send_raw(PurpleConnection *gc, const char *buf, int len)
@@ -402,9 +393,9 @@
 	g_free(txt);
 }
 
-static void jabber_pong_cb(JabberStream *js, xmlnode *packet, gpointer timeout) 
+static void jabber_pong_cb(JabberStream *js, xmlnode *packet, gpointer unused)
 {
-	purple_timeout_remove(GPOINTER_TO_INT(timeout));
+	purple_timeout_remove(js->keepalive_timeout);
 	js->keepalive_timeout = -1;
 }
 
@@ -428,7 +419,7 @@
 		xmlnode_set_namespace(ping, "urn:xmpp:ping");
 		
 		js->keepalive_timeout = purple_timeout_add_seconds(120, (GSourceFunc)(jabber_pong_timeout), gc);
-		jabber_iq_set_callback(iq, jabber_pong_cb, GINT_TO_POINTER(js->keepalive_timeout));
+		jabber_iq_set_callback(iq, jabber_pong_cb, NULL);
 		jabber_iq_send(iq);
 	}
 }
@@ -457,12 +448,17 @@
 			jabber_stream_init(js);
 	}
 
-	if(errno == EAGAIN)
+	if(len < 0 && errno == EAGAIN)
 		return;
-	else
+	else {
+		if (len == 0)
+			purple_debug_info("jabber", "Server closed the connection.\n");
+		else
+			purple_debug_info("jabber", "Disconnected: %s\n", g_strerror(errno));
 		purple_connection_error_reason (js->gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 			_("Read Error"));
+	}
 }
 
 static void
@@ -497,9 +493,13 @@
 		jabber_parser_process(js, buf, len);
 		if(js->reinit)
 			jabber_stream_init(js);
-	} else if(errno == EAGAIN) {
+	} else if(len < 0 && errno == EAGAIN) {
 		return;
 	} else {
+		if (len == 0)
+			purple_debug_info("jabber", "Server closed the connection.\n");
+		else
+			purple_debug_info("jabber", "Disconnected: %s\n", g_strerror(errno));
 		purple_connection_error_reason (js->gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 			_("Read Error"));
@@ -578,11 +578,19 @@
 	JabberStream *js = gc->proto_data;
 
 	if (source < 0) {
-		purple_debug_info("jabber","Couldn't connect directly to %s. Trying to find alternative connection methods, like BOSH.\n", js->user->domain);
-		purple_txt_resolve("_xmppconnect", js->user->domain, txt_resolved_cb, gc);
+		if (js->srv_rec != NULL) {
+			purple_debug_error("jabber", "Unable to connect to server: %s.  Trying next SRV record.\n", error);
+			try_srv_connect(js);
+		} else {
+			purple_debug_info("jabber","Couldn't connect directly to %s. Trying to find alternative connection methods, like BOSH.\n", js->user->domain);
+			purple_txt_resolve("_xmppconnect", js->user->domain, txt_resolved_cb, gc);
+		}
 		return;
 	}
 
+	g_free(js->srv_rec);
+	js->srv_rec = NULL;
+
 	js->fd = source;
 
 	if(js->state == JABBER_STREAM_CONNECTING)
@@ -617,37 +625,62 @@
 			jabber_login_callback_ssl, jabber_ssl_connect_failure, js->certificate_CN, js->gc);
 }
 
-static void jabber_login_connect(JabberStream *js, const char *domain, const char *host, int port)
+static gboolean jabber_login_connect(JabberStream *js, const char *domain, const char *host, int port,
+				 gboolean fatal_failure)
 {
 	/* host should be used in preference to domain to
 	 * allow SASL authentication to work with FQDN of the server,
 	 * but we use domain as fallback for when users enter IP address
 	 * in connect server */
+	g_free(js->serverFQDN);
 	if (purple_ip_address_is_valid(host))
 		js->serverFQDN = g_strdup(domain);
 	else
 		js->serverFQDN = g_strdup(host);
 
 	if (purple_proxy_connect(js->gc, js->gc->account, host,
-			port, jabber_login_callback, js->gc) == NULL)
-		purple_connection_error_reason (js->gc,
-			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-			_("Unable to create socket"));
+			port, jabber_login_callback, js->gc) == NULL) {
+		if (fatal_failure) {
+			purple_connection_error_reason (js->gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Unable to create socket"));
+		}
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static void try_srv_connect(JabberStream *js)
+{
+	while (js->srv_rec != NULL && js->srv_rec_idx < js->max_srv_rec_idx) {
+		PurpleSrvResponse *tmp_resp = js->srv_rec + (js->srv_rec_idx++);
+		if (jabber_login_connect(js, tmp_resp->hostname, tmp_resp->hostname, tmp_resp->port, FALSE))
+			return;
+	}
+
+	g_free(js->srv_rec);
+	js->srv_rec = NULL;
+
+	/* Fall back to the defaults (I'm not sure if we should actually do this) */
+	jabber_login_connect(js, js->user->domain, js->user->domain,
+		purple_account_get_int(js->gc->account, "port", 5222), TRUE);
 }
 
 static void srv_resolved_cb(PurpleSrvResponse *resp, int results, gpointer data)
 {
-	JabberStream *js;
-
-	js = data;
+	JabberStream *js = data;
 	js->srv_query_data = NULL;
 
 	if(results) {
-		jabber_login_connect(js, resp->hostname, resp->hostname, resp->port);
-		g_free(resp);
+		js->srv_rec = resp;
+		js->srv_rec_idx = 0;
+		js->max_srv_rec_idx = results;
+		try_srv_connect(js);
 	} else {
 		jabber_login_connect(js, js->user->domain, js->user->domain,
-			purple_account_get_int(js->gc->account, "port", 5222));
+			purple_account_get_int(js->gc->account, "port", 5222), TRUE);
 	}
 }
 
@@ -729,7 +762,7 @@
 	 * invoke the magic of SRV lookups, to figure out host and port */
 	if(!js->gsc) {
 		if(connect_server[0]) { 
-			jabber_login_connect(js, js->user->domain, connect_server, purple_account_get_int(account, "port", 5222));
+			jabber_login_connect(js, js->user->domain, connect_server, purple_account_get_int(account, "port", 5222), TRUE);
 		} else {
 			js->srv_query_data = purple_srv_resolve("xmpp-client",
 					"tcp", js->user->domain, srv_resolved_cb, js);
@@ -1210,7 +1243,7 @@
 		if (connect_server[0]) {
 			jabber_login_connect(js, js->user->domain, server,
 			                     purple_account_get_int(account,
-			                                          "port", 5222));
+			                                          "port", 5222), TRUE);
 		} else {
 			js->srv_query_data = purple_srv_resolve("xmpp-client",
 			                                      "tcp",
@@ -1381,7 +1414,10 @@
 
 	if (js->keepalive_timeout != -1)
 		purple_timeout_remove(js->keepalive_timeout);
-	
+
+	g_free(js->srv_rec);
+	js->srv_rec = NULL;
+
 	g_free(js);
 
 	gc->proto_data = NULL;
--- a/libpurple/protocols/jabber/jabber.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Thu Nov 20 21:13:56 2008 +0000
@@ -203,6 +203,10 @@
 	/* A purple timeout tag for the keepalive */
 	int keepalive_timeout;
 	
+	PurpleSrvResponse *srv_rec;
+	guint srv_rec_idx;
+	guint max_srv_rec_idx;
+
 	/* BOSH stuff*/
     gboolean use_bosh;
     PurpleBOSHConnection bosh;
--- a/libpurple/protocols/jabber/libxmpp.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Thu Nov 20 21:13:56 2008 +0000
@@ -89,7 +89,7 @@
 	jabber_message_send_chat,		/* chat_send */
 	jabber_keepalive,				/* keepalive */
 	jabber_register_account,		/* register_user */
-	jabber_buddy_get_info_chat,		/* get_cb_info */
+	NULL,							/* get_cb_info */
 	NULL,							/* get_cb_away */
 	jabber_roster_alias_change,		/* alias_buddy */
 	jabber_roster_group_change,		/* group_buddy */
@@ -194,6 +194,7 @@
 {
 #ifdef HAVE_CYRUS_SASL
 #ifdef _WIN32
+	UINT old_error_mode;
 	gchar *sasldir;
 #endif
 	int ret;
@@ -236,7 +237,7 @@
 	option = purple_account_option_string_new(_("File transfer proxies"),
 						  "ft_proxies",
 						/* TODO: Is this an acceptable default? */
-						  "proxy.jabber.org:7777");
+						  "proxy.jabber.org");
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
 						  option);
 
@@ -250,10 +251,16 @@
 	sasldir = g_build_filename(wpurple_install_dir(), "sasl2", NULL);
 	sasl_set_path(SASL_PATH_TYPE_PLUGIN, sasldir);
 	g_free(sasldir);
+	/* Suppress error popups for failing to load sasl plugins */
+	old_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
 #endif
 	if ((ret = sasl_client_init(NULL)) != SASL_OK) {
 		purple_debug_error("xmpp", "Error (%d) initializing SASL.\n", ret);
 	}
+#ifdef _WIN32
+	/* Restore the original error mode */
+	SetErrorMode(old_error_mode);
+#endif
 #endif
 	jabber_register_commands();
 	
--- a/libpurple/protocols/jabber/parser.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/jabber/parser.c	Thu Nov 20 21:13:56 2008 +0000
@@ -132,6 +132,18 @@
 	xmlnode_insert_data(js->current, (const char*) text, text_len);
 }
 
+static void
+jabber_parser_structured_error_handler(void *user_data, xmlErrorPtr error)
+{
+	JabberStream *js = user_data;
+
+	purple_debug_error("jabber", "XML parser error for JabberStream %p: "
+								 "Domain %i, code %i, level %i: %s\n",
+					   js,
+					   error->domain, error->code, error->level,
+					   (error->message ? error->message : "(null)"));
+}
+
 static xmlSAXHandler jabber_parser_libxml = {
 	NULL,									/*internalSubset*/
 	NULL,									/*isStandalone*/
@@ -164,7 +176,7 @@
 	NULL,									/*_private*/
 	jabber_parser_element_start_libxml,		/*startElementNs*/
 	jabber_parser_element_end_libxml,		/*endElementNs*/
-	NULL									/*serror*/
+	jabber_parser_structured_error_handler	/*serror*/
 };
 
 void
@@ -187,15 +199,25 @@
 
 void jabber_parser_process(JabberStream *js, const char *buf, int len)
 {
-	if (js->context ==  NULL) {
+	int ret;
+
+	if (js->context == NULL) {
 		/* libxml inconsistently starts parsing on creating the
 		 * parser, so do a ParseChunk right afterwards to force it. */
 		js->context = xmlCreatePushParserCtxt(&jabber_parser_libxml, js, buf, len, NULL);
 		xmlParseChunk(js->context, "", 0, 0);
-	} else if (xmlParseChunk(js->context, buf, len, 0) < 0) {
-		purple_connection_error_reason (js->gc,
-			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-			_("XML Parse error"));
+	} else if ((ret = xmlParseChunk(js->context, buf, len, 0)) != XML_ERR_OK) {
+		purple_debug_error("jabber", "xmlParseChunk returned error %i", ret);
+
+		if ((ret >= XML_ERR_INVALID_HEX_CHARREF) && (ret <= XML_ERR_INVALID_CHAR)) {
+			/* If the error involves an invalid character, just drop this message.
+			 * We'll create a new parser next time it's needed. */
+			jabber_parser_free(js);
+		} else {
+			purple_connection_error_reason (js->gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("XML Parse error"));
+		}
 	}
 }
 
--- a/libpurple/protocols/jabber/pep.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/jabber/pep.h	Thu Nov 20 21:13:56 2008 +0000
@@ -56,7 +56,7 @@
  * @parameter id	The item id of the requested item (may be NULL)
  * @parameter cb	The callback to be used when this item is received
  *
- * The items element passed to the callback will be NULL if any error occured (like a permission error, node doesn't exist etc.)
+ * The items element passed to the callback will be NULL if any error occurred (like a permission error, node doesn't exist etc.)
  */
 void jabber_pep_request_item(JabberStream *js, const char *to, const char *node, const char *id, JabberPEPHandler cb);
 
--- a/libpurple/protocols/jabber/presence.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/jabber/presence.c	Thu Nov 20 21:13:56 2008 +0000
@@ -141,6 +141,11 @@
 	/* check for buzz support */
 	allowBuzz = purple_status_get_attr_boolean(status,"buzz");
 	/* changing the buzz state has to trigger a re-broadcasting of the presence for caps */
+
+	if (js->googletalk && stripped == NULL && purple_presence_is_status_primitive_active(p, PURPLE_STATUS_TUNE)) {
+		tune = purple_presence_get_status(p, "tune");
+		stripped = jabber_google_presence_outgoing(tune);
+	}
 	
 #define CHANGED(a,b) ((!a && b) || (a && a[0] == '\0' && b && b[0] != '\0') || \
 					  (a && !b) || (a && a[0] != '\0' && b && b[0] == '\0') || (a && b && strcmp(a,b)))
@@ -149,11 +154,6 @@
 		js->old_priority != priority || CHANGED(js->old_avatarhash, js->avatar_hash)) {
 		js->allowBuzz = allowBuzz;
 
-		if (js->googletalk && stripped == NULL && purple_presence_is_status_primitive_active(p, PURPLE_STATUS_TUNE)) {
-			tune = purple_presence_get_status(p, "tune");
-			stripped = jabber_google_presence_outgoing(tune);
-		}
-
 		presence = jabber_presence_create_js(js, state, stripped, priority);
 
 		if(js->avatar_hash) {
@@ -178,9 +178,9 @@
 		js->old_avatarhash = g_strdup(js->avatar_hash);
 		js->old_state = state;
 		js->old_priority = priority;
-		g_free(stripped);
 	}
-					  	
+	g_free(stripped);
+
 	/* next, check if there are any changes to the tune values */
 	tune = purple_presence_get_status(p, "tune");
 	if (tune && purple_status_is_active(tune)) {
--- a/libpurple/protocols/jabber/roster.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/jabber/roster.c	Thu Nov 20 21:13:56 2008 +0000
@@ -316,7 +316,6 @@
 {
 	JabberStream *js = gc->proto_data;
 	char *who;
-	GSList *groups = NULL;
 	JabberBuddy *jb;
 	JabberBuddyResource *jbr;
 	char *my_bare_jid;
@@ -329,20 +328,7 @@
 
 	jb = jabber_buddy_find(js, buddy->name, FALSE);
 
-	/*
-	 * For some reason if we're waiting for our subscription request
-	 * to be approved and we try to add the buddy to another group
-	 * then we remove the buddy from the old group.  I don't understand
-	 * the rationale for this, can someone please explain it?  It seems
-	 * like we should pass NULL as the groups parameter to
-	 * jabber_roster_update().
-	 */
-	if(!jb || !(jb->subscription & JABBER_SUB_TO)) {
-		groups = g_slist_append(groups, group->name);
-	}
-
-	jabber_roster_update(js, who, groups);
-	g_slist_free(groups);
+	jabber_roster_update(js, who, NULL);
 
 	my_bare_jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain);
 	if(!strcmp(who, my_bare_jid)) {
--- a/libpurple/protocols/jabber/si.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/jabber/si.c	Thu Nov 20 21:13:56 2008 +0000
@@ -623,7 +623,7 @@
 		return;
 	else if(acceptfd == -1) {
 		purple_debug_warning("jabber", "accept: %s\n", g_strerror(errno));
-		/* TODO: This should cancel the ft */
+		/* Don't cancel the ft - allow it to fall to the next streamhost.*/
 		return;
 	}
 
@@ -659,8 +659,11 @@
 
 	jsx = xfer->data;
 
-	if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "result"))
+	if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "result")) {
+		if (type && !strcmp(type, "error"))
+			purple_xfer_cancel_remote(xfer);
 		return;
+	}
 
 	if(!(from = xmlnode_get_attrib(packet, "from")))
 		return;
@@ -718,14 +721,16 @@
 	JabberSIXfer *jsx;
 	JabberIq *iq;
 	xmlnode *query, *streamhost;
-	char *jid, port[6];
-	const char *local_ip, *public_ip, *ft_proxies;
+	char port[6];
 	GList *tmp;
 	JabberBytestreamsStreamhost *sh, *sh2;
+	int streamhost_count = 0;
 
 	jsx = xfer->data;
 	jsx->listen_data = NULL;
 
+	/* I'm not sure under which conditions this can happen
+	 * (it seems like it shouldn't be possible */
 	if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL) {
 		purple_xfer_unref(xfer);
 		return;
@@ -733,13 +738,6 @@
 
 	purple_xfer_unref(xfer);
 
-	if (sock < 0) {
-		purple_xfer_cancel_local(xfer);
-		return;
-	}
-
-	jsx->local_streamhost_fd = sock;
-
 	iq = jabber_iq_new_query(jsx->js, JABBER_IQ_SET,
 			"http://jabber.org/protocol/bytestreams");
 	xmlnode_set_attrib(iq->node, "to", xfer->who);
@@ -747,84 +745,43 @@
 
 	xmlnode_set_attrib(query, "sid", jsx->stream_id);
 
-	jid = g_strdup_printf("%s@%s/%s", jsx->js->user->node,
-			jsx->js->user->domain, jsx->js->user->resource);
-	xfer->local_port = purple_network_get_port_from_fd(sock);
-	g_snprintf(port, sizeof(port), "%hu", xfer->local_port);
-
-	/* TODO: Should there be an option to not use the local host as a ft proxy?
-	 *       (to prevent revealing IP address, etc.) */
+	/* If we successfully started listening locally */
+	if (sock >= 0) {
+		gchar *jid;
+		const char *local_ip, *public_ip;
 
-	/* Include the localhost's IP (for in-network transfers) */
-	local_ip = purple_network_get_local_system_ip(jsx->js->fd);
-	if (strcmp(local_ip, "0.0.0.0") != 0)
-	{
-		streamhost = xmlnode_new_child(query, "streamhost");
-		xmlnode_set_attrib(streamhost, "jid", jid);
-		xmlnode_set_attrib(streamhost, "host", local_ip);
-		xmlnode_set_attrib(streamhost, "port", port);
-	}
-
-	/* Include the public IP (assuming that there is a port mapped somehow) */
-	/* TODO: Check that it isn't the same as above and is a valid IP */
-	public_ip = purple_network_get_my_ip(jsx->js->fd);
-	if (strcmp(public_ip, local_ip) != 0)
-	{
-		streamhost = xmlnode_new_child(query, "streamhost");
-		xmlnode_set_attrib(streamhost, "jid", jid);
-		xmlnode_set_attrib(streamhost, "host", public_ip);
-		xmlnode_set_attrib(streamhost, "port", port);
-	}
-
-	g_free(jid);
-
-	/* The listener for the local proxy */
-	xfer->watcher = purple_input_add(sock, PURPLE_INPUT_READ,
-			jabber_si_xfer_bytestreams_send_connected_cb, xfer);
+		jsx->local_streamhost_fd = sock;
 
-	/* insert proxies here */
-	ft_proxies = purple_account_get_string(xfer->account, "ft_proxies", NULL);
-	if (ft_proxies) {
-		int i, portnum;
-		char *tmp;
-		gchar **ft_proxy_list = g_strsplit(ft_proxies, ",", 0);
-
-		g_list_foreach(jsx->streamhosts, jabber_si_free_streamhost, NULL);
-		g_list_free(jsx->streamhosts);
-		jsx->streamhosts = NULL;
-
-		for(i = 0; ft_proxy_list[i]; i++) {
-			g_strstrip(ft_proxy_list[i]);
-			if(!(*ft_proxy_list[i]))
-				continue;
+		jid = g_strdup_printf("%s@%s/%s", jsx->js->user->node,
+			jsx->js->user->domain, jsx->js->user->resource);
+		xfer->local_port = purple_network_get_port_from_fd(sock);
+		g_snprintf(port, sizeof(port), "%hu", xfer->local_port);
 
-			if((tmp = strchr(ft_proxy_list[i], ':'))) {
-				portnum = atoi(tmp + 1);
-				*tmp = '\0';
-			} else
-				portnum = 7777;
-
-			g_snprintf(port, sizeof(port), "%hu", portnum);
-
-			purple_debug_info("jabber", "jabber_si_xfer_bytestreams_listen_cb() will be looking at jsx %p: jsx->streamhosts %p and ft_proxy_list[%i] %p",
-							  jsx, jsx->streamhosts, i, ft_proxy_list[i]);
-			if(g_list_find_custom(jsx->streamhosts, ft_proxy_list[i], jabber_si_compare_jid) != NULL)
-				continue;
-
+		/* Include the localhost's IP (for in-network transfers) */
+		local_ip = purple_network_get_local_system_ip(jsx->js->fd);
+		if (strcmp(local_ip, "0.0.0.0") != 0) {
+			streamhost_count++;
 			streamhost = xmlnode_new_child(query, "streamhost");
-			xmlnode_set_attrib(streamhost, "jid", ft_proxy_list[i]);
-			xmlnode_set_attrib(streamhost, "host", ft_proxy_list[i]);
+			xmlnode_set_attrib(streamhost, "jid", jid);
+			xmlnode_set_attrib(streamhost, "host", local_ip);
 			xmlnode_set_attrib(streamhost, "port", port);
-
-			sh = g_new0(JabberBytestreamsStreamhost, 1);
-			sh->jid = g_strdup(ft_proxy_list[i]);
-			sh->host = g_strdup(ft_proxy_list[i]);
-			sh->port = portnum;
-
-			jsx->streamhosts = g_list_prepend(jsx->streamhosts, sh);
 		}
 
-		g_strfreev(ft_proxy_list);
+		/* Include the public IP (assuming that there is a port mapped somehow) */
+		public_ip = purple_network_get_my_ip(jsx->js->fd);
+		if (strcmp(public_ip, local_ip) != 0 && strcmp(public_ip, "0.0.0.0") != 0) {
+			streamhost_count++;
+			streamhost = xmlnode_new_child(query, "streamhost");
+			xmlnode_set_attrib(streamhost, "jid", jid);
+			xmlnode_set_attrib(streamhost, "host", public_ip);
+			xmlnode_set_attrib(streamhost, "port", port);
+		}
+
+		g_free(jid);
+
+		/* The listener for the local proxy */
+		xfer->watcher = purple_input_add(sock, PURPLE_INPUT_READ,
+				jabber_si_xfer_bytestreams_send_connected_cb, xfer);
 	}
 
 	for (tmp = jsx->js->bs_proxies; tmp; tmp = tmp->next) {
@@ -840,6 +797,7 @@
 		if(g_list_find_custom(jsx->streamhosts, sh->jid, jabber_si_compare_jid) != NULL)
 			continue;
 
+		streamhost_count++;
 		streamhost = xmlnode_new_child(query, "streamhost");
 		xmlnode_set_attrib(streamhost, "jid", sh->jid);
 		xmlnode_set_attrib(streamhost, "host", sh->host);
@@ -855,6 +813,14 @@
 		jsx->streamhosts = g_list_prepend(jsx->streamhosts, sh2);
 	}
 
+	/* We have no way of transferring, cancel the transfer */
+	if (streamhost_count == 0) {
+		jabber_iq_free(iq);
+		/* We should probably notify the target, but this really shouldn't ever happen */
+		purple_xfer_cancel_local(xfer);
+		return;
+	}
+
 	jabber_iq_set_callback(iq, jabber_si_connect_proxy_cb, xfer);
 
 	jabber_iq_send(iq);
@@ -869,13 +835,14 @@
 	purple_xfer_ref(xfer);
 
 	jsx = xfer->data;
+
+	/* TODO: Should there be an option to not use the local host as a ft proxy?
+	 *       (to prevent revealing IP address, etc.) */
 	jsx->listen_data = purple_network_listen_range(0, 0, SOCK_STREAM,
 				jabber_si_xfer_bytestreams_listen_cb, xfer);
 	if (jsx->listen_data == NULL) {
-		purple_xfer_unref(xfer);
-		/* XXX: couldn't open a port, we're fscked */
-		purple_xfer_cancel_local(xfer);
-		return;
+		/* We couldn't open a local port.  Perhaps we can use a proxy. */
+		jabber_si_xfer_bytestreams_listen_cb(-1, xfer);
 	}
 
 }
@@ -1213,9 +1180,6 @@
 
 	js = gc->proto_data;
 
-	if(!purple_find_buddy(gc->account, who) || !jabber_buddy_find(js, who, FALSE))
-		return;
-
 	xfer = jabber_si_new_xfer(gc, who);
 
 	if (file)
--- a/libpurple/protocols/msn/Makefile.am	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/Makefile.am	Thu Nov 20 21:13:56 2008 +0000
@@ -48,12 +48,8 @@
 	slplink.h \
 	slpmsg.c \
 	slpmsg.h \
-	slpsession.c \
-	slpsession.h \
-	soap.c\
-	soap.h\
-	soap2.c \
-	soap2.h \
+	soap.c \
+	soap.h \
 	state.c \
 	state.h \
 	switchboard.c \
--- a/libpurple/protocols/msn/Makefile.mingw	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/Makefile.mingw	Thu Nov 20 21:13:56 2008 +0000
@@ -59,9 +59,7 @@
 			slpcall.c \
 			slplink.c \
 			slpmsg.c \
-			slpsession.c \
 			soap.c\
-            soap2.c\
 			state.c \
 			switchboard.c \
 			sync.c \
--- a/libpurple/protocols/msn/cmdproc.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/cmdproc.c	Thu Nov 20 21:13:56 2008 +0000
@@ -263,7 +263,7 @@
 	MsnTransaction *trans = NULL;
 
 	if (cmd->trId)
-		trans = msn_history_find(cmdproc->history, cmd->trId);
+		cmd->trans = trans = msn_history_find(cmdproc->history, cmd->trId);
 
 	if (trans != NULL)
 		if (trans->timer) {
@@ -309,8 +309,6 @@
 
 	if (cb == NULL && trans != NULL)
 	{
-		cmd->trans = trans;
-
 		if (trans->callbacks != NULL)
 			cb = g_hash_table_lookup(trans->callbacks, cmd->command);
 	}
--- a/libpurple/protocols/msn/command.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/command.c	Thu Nov 20 21:13:56 2008 +0000
@@ -36,53 +36,6 @@
 	return TRUE;
 }
 
-/*
- * check the command is the command with payload content
- *  if it is	return TRUE
- *  else 		return FALSE
- */
-static gboolean
-msn_check_payload_cmd(const char *str)
-{
-	g_return_val_if_fail(str != NULL, FALSE);
-
-	if((!strcmp(str,"ADL")) ||
-		(!strcmp(str,"GCF")) ||
-		(!strcmp(str,"SG")) ||
-		(!strcmp(str,"MSG")) ||
-		(!strcmp(str,"RML")) ||
-		(!strcmp(str,"UBX")) ||
-		(!strcmp(str,"UBN")) ||
-		(!strcmp(str,"UUM")) ||
-		(!strcmp(str,"UBM")) ||
-		(!strcmp(str,"FQY")) ||
-		(!strcmp(str,"UUN")) ||
-		(!strcmp(str,"UUX")) ||
-		(!strcmp(str,"IPG")) ||
-		(is_num(str))){
-			return TRUE;
-		}
-
-	return FALSE;
-}
-
-/*
- * set command Payload length
- */
-static void
-msn_set_payload_len(MsnCommand *cmd)
-{
-	char *param;
-	int len = 0;
-
-	if (msn_check_payload_cmd(cmd->command) && (cmd->param_count > 0)){
-		param = cmd->params[cmd->param_count - 1];
-		len = is_num(param) ? atoi(param) : 0;
-	}
-
-	cmd->payload_len = len;
-}
-
 MsnCommand *
 msn_command_from_string(const char *string)
 {
@@ -98,31 +51,28 @@
 	if (param_start)
 	{
 		*param_start++ = '\0';
-		cmd->params = g_strsplit(param_start, " ", 0);
+		cmd->params = g_strsplit_set(param_start, " ", 0);
 	}
 
 	if (cmd->params != NULL)
 	{
-		char *param;
 		int c;
 
-		for (c = 0; cmd->params[c]; c++);
+		for (c = 0; cmd->params[c] && cmd->params[c][0]; c++);
 		cmd->param_count = c;
 
-		param = cmd->params[0];
-
-		cmd->trId = is_num(param) ? atoi(param) : 0;
+		if (cmd->param_count) {
+			char *param = cmd->params[0];
+			cmd->trId = is_num(param) ? atoi(param) : 0;
+		} else {
+			cmd->trId = 0;
+		}
 	}
 	else
 	{
 		cmd->trId = 0;
 	}
 
-	/* khc: Huh! */
-	/*add payload Length checking*/
-	msn_set_payload_len(cmd);
-	purple_debug_info("MSNP14","get payload len:%" G_GSIZE_FORMAT "\n", cmd->payload_len);
-
 	msn_command_ref(cmd);
 
 	return cmd;
--- a/libpurple/protocols/msn/contact.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/contact.c	Thu Nov 20 21:13:56 2008 +0000
@@ -1,5 +1,5 @@
 /**
- * @file contact.c
+ * @file contact.c 
  * 	get MSN contacts via SOAP request
  *	created by MaYuan<mayuan2006@gmail.com>
  *
@@ -28,7 +28,8 @@
 #include "contact.h"
 #include "xmlnode.h"
 #include "group.h"
-#include "soap2.h"
+#include "soap.h"
+#include "nexus.h"
 
 const char *MsnSoapPartnerScenarioText[] =
 {
@@ -49,29 +50,10 @@
 };
 
 typedef struct {
-	MsnContact *contact;
+	MsnSession *session;
 	MsnSoapPartnerScenario which;
 } GetContactListCbData;
 
-/* new a contact */
-MsnContact *
-msn_contact_new(MsnSession *session)
-{
-	MsnContact *contact;
-
-	contact = g_new0(MsnContact, 1);
-	contact->session = session;
-
-	return contact;
-}
-
-/* destroy the contact */
-void
-msn_contact_destroy(MsnContact *contact)
-{
-	g_free(contact);
-}
-
 MsnCallbackState *
 msn_callback_state_new(MsnSession *session)
 {
@@ -82,6 +64,22 @@
 	return state;
 }
 
+MsnCallbackState *
+msn_callback_state_dup(MsnCallbackState *state)
+{
+	MsnCallbackState *new_state = g_new0(MsnCallbackState, 1);
+
+	new_state->session = state->session;
+	new_state->who = g_strdup(state->who);
+	new_state->uid = g_strdup(state->uid);
+	new_state->old_group_name = g_strdup(state->old_group_name);
+	new_state->new_group_name = g_strdup(state->new_group_name);
+	new_state->guid = g_strdup(state->guid);
+	/* The rest should be made new */
+
+	return new_state;
+}
+
 void
 msn_callback_state_free(MsnCallbackState *state)
 {
@@ -93,6 +91,7 @@
 	g_free(state->old_group_name);
 	g_free(state->new_group_name);
 	g_free(state->guid);
+	xmlnode_free(state->body);
 
 	g_free(state);
 }
@@ -177,56 +176,60 @@
 	return 0;
 }
 
-/*get User Type*/
-static int
-msn_get_user_type(char *type)
+/* get Network */
+/* QuLogic: These names don't really refer to the MsnNetwork,
+ *          but I haven't yet written the code to properly use them.
+ */
+static MsnNetwork
+msn_get_network(char *type)
 {
 	g_return_val_if_fail(type != NULL, 0);
 
 	if (!strcmp(type,"Regular")) {
-		return MSN_USER_TYPE_PASSPORT;
+		return MSN_NETWORK_PASSPORT;
 	}
 	if (!strcmp(type,"Live")) {
-		return MSN_USER_TYPE_PASSPORT;
+		return MSN_NETWORK_PASSPORT;
 	}
 	if (!strcmp(type,"LivePending")) {
-		return MSN_USER_TYPE_PASSPORT;
+		return MSN_NETWORK_PASSPORT;
 	}
 
-	return MSN_USER_TYPE_UNKNOWN;
+	return MSN_NETWORK_UNKNOWN;
 }
 
 /* Create the AddressBook in the server, if we don't have one */
 static void
 msn_create_address_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data)
 {
-	if (resp && msn_soap_xml_get(resp->xml, "Body/Fault") == NULL) {
-		purple_debug_info("msnab", "Address Book successfully created!\n");
-		msn_get_address_book((MsnContact *)data, MSN_PS_INITIAL, NULL, NULL);
+	if (resp && xmlnode_get_child(resp->xml, "Body/Fault") == NULL) {
+		purple_debug_info("msn", "Address Book successfully created!\n");
+		msn_get_address_book((MsnSession *)data, MSN_PS_INITIAL, NULL, NULL);
 	} else {
-		purple_debug_info("msnab", "Address Book creation failed!\n");
+		purple_debug_info("msn", "Address Book creation failed!\n");
 	}
 }
 
 static void
-msn_create_address_book(MsnContact * contact)
+msn_create_address_book(MsnSession *session)
 {
 	gchar *body;
 
-	g_return_if_fail(contact != NULL);
-	g_return_if_fail(contact->session != NULL);
-	g_return_if_fail(contact->session->user != NULL);
-	g_return_if_fail(contact->session->user->passport != NULL);
+	g_return_if_fail(session != NULL);
+	g_return_if_fail(session->user != NULL);
+	g_return_if_fail(session->user->passport != NULL);
+	
+	purple_debug_info("msn", "Creating an Address Book.\n");
 
-	purple_debug_info("msnab","Creating an Address Book.\n");
+	body = g_markup_printf_escaped(MSN_ADD_ADDRESSBOOK_TEMPLATE,
+		msn_nexus_get_token_str(session->nexus, MSN_AUTH_CONTACTS),
+		session->user->passport);
 
-	body = g_strdup_printf(MSN_ADD_ADDRESSBOOK_TEMPLATE, contact->session->user->passport);
-
-	msn_soap_message_send(contact->session,
+	msn_soap_message_send(session,
 		msn_soap_message_new(MSN_ADD_ADDRESSBOOK_SOAP_ACTION,
 			xmlnode_from_str(body, -1)),
-		MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL, msn_create_address_cb,
-		contact);
+		MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL, FALSE,
+		msn_create_address_cb, session);
 
 	g_free(body);
 }
@@ -240,7 +243,7 @@
 	char *member_id = xmlnode_get_data(xmlnode_get_child(member, "MembershipId"));
 	MsnUser *user = msn_userlist_find_add_user(session->userlist, passport, NULL);
 
-	purple_debug_info("msncl","%s name: %s, Type: %s, MembershipID: %s\n",
+	purple_debug_info("msn", "CL: %s name: %s, Type: %s, MembershipID: %s\n",
 		node, passport, type, member_id == NULL ? "(null)" : member_id);
 
 	if (member_id) {
@@ -259,7 +262,7 @@
 {
 	xmlnode *type;
 
-	if ((type = msn_soap_xml_get(service, "Info/Handle/Type"))) {
+	if ((type = xmlnode_get_child(service, "Info/Handle/Type"))) {
 		char *type_str = xmlnode_get_data(type);
 
 		if (g_str_equal(type_str, "Profile")) {
@@ -269,11 +272,11 @@
 			char *lastchange_str = xmlnode_get_data(lastchange);
 			xmlnode *membership;
 
-			purple_debug_info("msncl","last change: %s\n", lastchange_str);
+			purple_debug_info("msn", "CL last change: %s\n", lastchange_str);
 			purple_account_set_string(session->account,	"CLLastChange",
 				lastchange_str);
 
-			for (membership = msn_soap_xml_get(service,
+			for (membership = xmlnode_get_child(service,
 					"Memberships/Membership");
 				 membership; membership = xmlnode_get_next_twin(membership)) {
 
@@ -282,10 +285,10 @@
 				MsnListId list = msn_get_memberrole(role_str);
 				xmlnode *member;
 
-				purple_debug_info("msncl", "MemberRole role: %s, list: %d\n",
+				purple_debug_info("msn", "CL MemberRole role: %s, list: %d\n",
 					role_str, list);
 
-				for (member = msn_soap_xml_get(membership, "Members/Member");
+				for (member = xmlnode_get_child(membership, "Members/Member");
 					 member; member = xmlnode_get_next_twin(member)) {
 					const char *member_type = xmlnode_get_attrib(member, "type");
 					if (g_str_equal(member_type, "PassportMember")) {
@@ -310,7 +313,7 @@
 
 /*parse contact list*/
 static void
-msn_parse_contact_list(MsnContact *contact, xmlnode *node)
+msn_parse_contact_list(MsnSession *session, xmlnode *node)
 {
 	xmlnode *fault, *faultnode;
 
@@ -321,18 +324,18 @@
 	 *
 	 * this is not handled yet
 	 */
-	if ((fault = msn_soap_xml_get(node, "Body/Fault"))) {
+	if ((fault = xmlnode_get_child(node, "Body/Fault"))) {
 		if ((faultnode = xmlnode_get_child(fault, "faultstring"))) {
 			char *faultstring = xmlnode_get_data(faultnode);
-			purple_debug_info("msncl", "Retrieving contact list failed: %s\n",
+			purple_debug_info("msn", "Retrieving contact list failed: %s\n",
 				faultstring);
 			g_free(faultstring);
 		}
-		if ((faultnode = msn_soap_xml_get(fault, "detail/errorcode"))) {
+		if ((faultnode = xmlnode_get_child(fault, "detail/errorcode"))) {
 			char *errorcode = xmlnode_get_data(faultnode);
 
 			if (g_str_equal(errorcode, "ABDoesNotExist")) {
-				msn_create_address_book(contact);
+				msn_create_address_book(session);
 				g_free(errorcode);
 				return;
 			}
@@ -340,14 +343,14 @@
 			g_free(errorcode);
 		}
 
-		msn_get_contact_list(contact, MSN_PS_INITIAL, NULL);
+		msn_get_contact_list(session, MSN_PS_INITIAL, NULL);
 	} else {
 		xmlnode *service;
 
-		for (service = msn_soap_xml_get(node, "Body/FindMembershipResponse/"
+		for (service = xmlnode_get_child(node, "Body/FindMembershipResponse/"
 				"FindMembershipResult/Services/Service");
 			 service; service = xmlnode_get_next_twin(service)) {
-			msn_parse_each_service(contact->session, service);
+			msn_parse_each_service(session, service);
 		}
 	}
 }
@@ -357,8 +360,7 @@
 	gpointer data)
 {
 	GetContactListCbData *cb_data = data;
-	MsnContact *contact = cb_data->contact;
-	MsnSession *session = contact->session;
+	MsnSession *session = cb_data->session;
 
 	g_return_if_fail(session != NULL);
 
@@ -366,9 +368,9 @@
 		const char *abLastChange;
 		const char *dynamicItemLastChange;
 
-		purple_debug_misc("msncl","Got the contact list!\n");
+		purple_debug_misc("msn", "Got the contact list!\n");
 
-		msn_parse_contact_list(cb_data->contact, resp->xml);
+		msn_parse_contact_list(session, resp->xml);
 		abLastChange = purple_account_get_string(session->account,
 			"ablastChange", NULL);
 		dynamicItemLastChange = purple_account_get_string(session->account,
@@ -379,9 +381,9 @@
 			/* XXX: this should be enabled when we can correctly do partial
 			   syncs with the server. Currently we need to retrieve the whole
 			   list to detect sync issues */
-			msn_get_address_book(contact, MSN_PS_INITIAL, abLastChange, dynamicItemLastChange);
+			msn_get_address_book(session, MSN_PS_INITIAL, abLastChange, dynamicItemLastChange);
 #else
-			msn_get_address_book(contact, MSN_PS_INITIAL, NULL, NULL);
+			msn_get_address_book(session, MSN_PS_INITIAL, NULL, NULL);
 #endif
 		}
 	}
@@ -391,27 +393,29 @@
 
 /*SOAP  get contact list*/
 void
-msn_get_contact_list(MsnContact * contact,
+msn_get_contact_list(MsnSession *session,
 	const MsnSoapPartnerScenario partner_scenario, const char *update_time)
 {
 	gchar *body = NULL;
 	gchar *update_str = NULL;
-	GetContactListCbData cb_data = { contact, partner_scenario };
+	GetContactListCbData cb_data = { session, partner_scenario };
 	const gchar *partner_scenario_str = MsnSoapPartnerScenarioText[partner_scenario];
 
-	purple_debug_misc("MSNCL","Getting Contact List.\n");
+	purple_debug_misc("msn", "Getting Contact List.\n");
 
-	if ( update_time != NULL ) {
-		purple_debug_info("MSNCL","Last update time: %s\n",update_time);
+	if (update_time != NULL) {
+		purple_debug_info("msn", "CL Last update time: %s\n", update_time);
 		update_str = g_strdup_printf(MSN_GET_CONTACT_UPDATE_XML,update_time);
 	}
 
-	body = g_strdup_printf(MSN_GET_CONTACT_TEMPLATE, partner_scenario_str, update_str ? update_str : "");
+	body = g_markup_printf_escaped(MSN_GET_CONTACT_TEMPLATE, partner_scenario_str,
+		msn_nexus_get_token_str(session->nexus, MSN_AUTH_CONTACTS),
+		update_str ? update_str : "");
 
-	msn_soap_message_send(contact->session,
+	msn_soap_message_send(session,
 		msn_soap_message_new(MSN_GET_CONTACT_SOAP_ACTION,
 			xmlnode_from_str(body, -1)),
-		MSN_CONTACT_SERVER, MSN_GET_CONTACT_POST_URL,
+		MSN_CONTACT_SERVER, MSN_GET_CONTACT_POST_URL, FALSE,
 		msn_get_contact_list_cb, g_memdup(&cb_data, sizeof(cb_data)));
 
 	g_free(update_str);
@@ -419,12 +423,11 @@
 }
 
 static void
-msn_parse_addressbook_groups(MsnContact *contact, xmlnode *node)
+msn_parse_addressbook_groups(MsnSession *session, xmlnode *node)
 {
-	MsnSession *session = contact->session;
 	xmlnode *group;
 
-	purple_debug_info("MSNAB","msn_parse_addressbook_groups()\n");
+	purple_debug_info("msn", "msn_parse_addressbook_groups()\n");
 
 	for(group = xmlnode_get_child(node, "Group"); group;
 					group = xmlnode_get_next_twin(group)){
@@ -444,7 +447,7 @@
 			continue;
 		}
 
-		purple_debug_info("MsnAB","group_id: %s, name: %s\n", group_id, group_name ? group_name : "(null)");
+		purple_debug_info("msn", "AB group_id: %s, name: %s\n", group_id, group_name ? group_name : "(null)");
 		if ((purple_find_group(group_name)) == NULL){
 			PurpleGroup *g = purple_group_new(group_name);
 			purple_blist_add_group(g, NULL);
@@ -507,18 +510,19 @@
 }
 
 static void
-msn_parse_addressbook_contacts(MsnContact *contact, xmlnode *node)
+msn_parse_addressbook_contacts(MsnSession *session, xmlnode *node)
 {
-	MsnSession *session = contact->session;
 	xmlnode *contactNode;
-	char *passport = NULL, *Name = NULL, *uid = NULL, *type = NULL, *mobile_number = NULL;
+	char *passport = NULL, *Name = NULL, *uid = NULL, *type = NULL, *mobile_number = NULL, *alias = NULL;
 	gboolean mobile = FALSE;
+	PurpleConnection *pc = purple_account_get_connection(session->account);
 
 	for(contactNode = xmlnode_get_child(node, "Contact"); contactNode;
 				contactNode = xmlnode_get_next_twin(contactNode)) {
 		xmlnode *contactId, *contactInfo, *contactType, *passportName, *displayName, *guid, *groupIds, *messenger_user;
+		xmlnode *annotation;
 		MsnUser *user;
-		MsnUserType usertype;
+		MsnNetwork networkId;
 
 		if (!(contactId = xmlnode_get_child(contactNode,"contactId"))
 				|| !(contactInfo = xmlnode_get_child(contactNode, "contactInfo"))
@@ -527,10 +531,11 @@
 
 		g_free(passport);
 		g_free(Name);
+		g_free(alias);
 		g_free(uid);
 		g_free(type);
 		g_free(mobile_number);
-		passport = Name = uid = type = mobile_number = NULL;
+		passport = Name = uid = type = mobile_number = alias = NULL;
 		mobile = FALSE;
 
 		uid = xmlnode_get_data(contactId);
@@ -547,7 +552,7 @@
 		}
 
 		/* ignore non-messenger contacts */
-		if((messenger_user = xmlnode_get_child(contactInfo, "isMessengerUser"))) {
+		if ((messenger_user = xmlnode_get_child(contactInfo, "isMessengerUser"))) {
 			char *is_messenger_user = xmlnode_get_data(messenger_user);
 
 			if(is_messenger_user && !strcmp(is_messenger_user, "false")) {
@@ -558,7 +563,7 @@
 			g_free(is_messenger_user);
 		}
 
-		usertype = msn_get_user_type(type);
+		networkId = msn_get_network(type);
 		passportName = xmlnode_get_child(contactInfo, "passportName");
 		if (passportName == NULL) {
 			xmlnode *emailsNode, *contactEmailNode, *emailNode;
@@ -572,7 +577,7 @@
 				/*TODO:  need to support the Mobile type*/
 				continue;
 			}
-			for(contactEmailNode = xmlnode_get_child(emailsNode, "ContactEmail"); contactEmailNode;
+			for (contactEmailNode = xmlnode_get_child(emailsNode, "ContactEmail"); contactEmailNode;
 					contactEmailNode = xmlnode_get_next_twin(contactEmailNode) ){
 				if (!(messengerEnabledNode = xmlnode_get_child(contactEmailNode, "isMessengerEnabled"))) {
 					/* XXX: Should this be a continue instead of a break? It seems like it'd cause unpredictable results otherwise. */
@@ -586,15 +591,15 @@
 					passport = xmlnode_get_data(emailNode);
 				}
 
-				if(msnEnabled && !strcmp(msnEnabled, "true")) {
+				if (msnEnabled && !strcmp(msnEnabled, "true")) {
 					/*Messenger enabled, Get the Passport*/
-					purple_debug_info("MsnAB", "Yahoo User %s\n", passport ? passport : "(null)");
-					usertype = MSN_USER_TYPE_YAHOO;
+					purple_debug_info("msn", "AB Yahoo User %s\n", passport ? passport : "(null)");
+					networkId = MSN_NETWORK_YAHOO;
 					g_free(msnEnabled);
 					break;
 				} else {
 					/*TODO maybe we can just ignore it in Purple?*/
-					purple_debug_info("MSNAB", "Other type user\n");
+					purple_debug_info("msn", "AB Other type user\n");
 				}
 
 				g_free(msnEnabled);
@@ -611,24 +616,33 @@
 		else
 			Name = g_strdup(passport);
 
+		for (annotation = xmlnode_get_child(contactInfo, "annotations/Annotation");
+				annotation; annotation = xmlnode_get_next_twin(annotation)) {
+			char *name;
+			name = xmlnode_get_data(xmlnode_get_child(annotation, "Name"));
+			if (!strcmp(name, "AB.NickName"))
+				alias = xmlnode_get_data(xmlnode_get_child(annotation, "Value"));
+			g_free(name);
+		}
+
 		mobile = msn_parse_addressbook_mobile(contactInfo, &mobile_number);
 
-		purple_debug_misc("MsnAB","passport:{%s} uid:{%s} display:{%s} mobile:{%s} mobile number:{%s}\n",
-			passport, uid ? uid : "(null)", Name ? Name : "(null)",
+		purple_debug_misc("msn", "AB passport:{%s} uid:{%s} display:{%s} alias: {%s} mobile:{%s} mobile number:{%s}\n",
+			passport, uid ? uid : "(null)", Name ? Name : "(null)", alias ? alias : "(null)",
 			mobile ? "true" : "false", mobile_number ? mobile_number : "(null)");
 
 		user = msn_userlist_find_add_user(session->userlist, passport, Name);
 		msn_user_set_uid(user, uid);
-		msn_user_set_type(user, usertype);
+		msn_user_set_network(user, networkId);
 		msn_user_set_mobile_phone(user, mobile_number);
 
 		groupIds = xmlnode_get_child(contactInfo, "groupIds");
 		if (groupIds) {
 			for (guid = xmlnode_get_child(groupIds, "guid"); guid;
-							guid = xmlnode_get_next_twin(guid)){
+							guid = xmlnode_get_next_twin(guid)) {
 				char *group_id = xmlnode_get_data(guid);
 				msn_user_add_group_id(user, group_id);
-				purple_debug_misc("MsnAB", "guid:%s\n", group_id ? group_id : "(null)");
+				purple_debug_misc("msn", "AB guid:%s\n", group_id ? group_id : "(null)");
 				g_free(group_id);
 			}
 		} else {
@@ -639,12 +653,14 @@
 
 		msn_got_lst_user(session, user, MSN_LIST_FL_OP, NULL);
 
-		if(mobile && user)
+		if (mobile && user)
 		{
 			user->mobile = TRUE;
 			purple_prpl_got_user_status(session->account, user->passport, "mobile", NULL);
 			purple_prpl_got_user_status(session->account, user->passport, "available", NULL);
 		}
+		if (alias)
+			purple_serv_got_private_alias(pc, passport, alias);
 	}
 
 	g_free(passport);
@@ -655,30 +671,27 @@
 }
 
 static gboolean
-msn_parse_addressbook(MsnContact * contact, xmlnode *node)
+msn_parse_addressbook(MsnSession *session, xmlnode *node)
 {
-	MsnSession * session;
 	xmlnode *result;
 	xmlnode *groups;
 	xmlnode *contacts;
 	xmlnode *abNode;
 	xmlnode *fault;
 
-	session = contact->session;
-
-	if ((fault = msn_soap_xml_get(node, "Body/Fault"))) {
+	if ((fault = xmlnode_get_child(node, "Body/Fault"))) {
 		xmlnode *faultnode;
 
 		if ((faultnode = xmlnode_get_child(fault, "faultstring"))) {
 			gchar *faultstring = xmlnode_get_data(faultnode);
-			purple_debug_info("MSNAB","Faultstring: %s\n", faultstring);
+			purple_debug_info("msn", "AB Faultstring: %s\n", faultstring);
 			g_free(faultstring);
 		}
 
-		if ((faultnode = msn_soap_xml_get(fault, "detail/errorcode"))) {
+		if ((faultnode = xmlnode_get_child(fault, "detail/errorcode"))) {
 			gchar *errorcode = xmlnode_get_data(faultnode);
 
-			purple_debug_info("MSNAB", "Error Code: %s\n", errorcode);
+			purple_debug_info("msn", "AB Error Code: %s\n", errorcode);
 
 			if (g_str_equal(errorcode, "ABDoesNotExist")) {
 				g_free(errorcode);
@@ -690,24 +703,24 @@
 		return FALSE;
 	}
 
-	result = msn_soap_xml_get(node, "Body/ABFindAllResponse/ABFindAllResult");
-	if(result == NULL){
-		purple_debug_misc("MSNAB","receive no address book update\n");
+	result = xmlnode_get_child(node, "Body/ABFindAllResponse/ABFindAllResult");
+	if (result == NULL) {
+		purple_debug_misc("msn", "Received no address book update\n");
 		return TRUE;
 	}
 
 	/* I don't see this "groups" tag documented on msnpiki, need to find out
 	   if they are really there, and update msnpiki */
 	/*Process Group List*/
-	groups = xmlnode_get_child(result,"groups");
+	groups = xmlnode_get_child(result, "groups");
 	if (groups != NULL) {
-		msn_parse_addressbook_groups(contact, groups);
+		msn_parse_addressbook_groups(session, groups);
 	}
 
 	/*add a default No group to set up the no group Membership*/
 	msn_group_new(session->userlist, MSN_INDIVIDUALS_GROUP_ID,
 				  MSN_INDIVIDUALS_GROUP_NAME);
-	purple_debug_misc("MSNAB","group_id:%s name:%s\n",
+	purple_debug_misc("msn", "AB group_id:%s name:%s\n",
 					  MSN_INDIVIDUALS_GROUP_ID, MSN_INDIVIDUALS_GROUP_NAME);
 	if ((purple_find_group(MSN_INDIVIDUALS_GROUP_NAME)) == NULL){
 		PurpleGroup *g = purple_group_new(MSN_INDIVIDUALS_GROUP_NAME);
@@ -716,33 +729,33 @@
 
 	/*add a default No group to set up the no group Membership*/
 	msn_group_new(session->userlist, MSN_NON_IM_GROUP_ID, MSN_NON_IM_GROUP_NAME);
-	purple_debug_misc("MSNAB","group_id:%s name:%s\n", MSN_NON_IM_GROUP_ID, MSN_NON_IM_GROUP_NAME);
-	if ((purple_find_group(MSN_NON_IM_GROUP_NAME)) == NULL){
+	purple_debug_misc("msn", "AB group_id:%s name:%s\n", MSN_NON_IM_GROUP_ID, MSN_NON_IM_GROUP_NAME);
+	if ((purple_find_group(MSN_NON_IM_GROUP_NAME)) == NULL) {
 		PurpleGroup *g = purple_group_new(MSN_NON_IM_GROUP_NAME);
 		purple_blist_add_group(g, NULL);
 	}
 
 	/*Process contact List*/
-	purple_debug_info("MSNAB","process contact list...\n");
-	contacts =xmlnode_get_child(result,"contacts");
+	purple_debug_info("msn", "Process contact list...\n");
+	contacts = xmlnode_get_child(result, "contacts");
 	if (contacts != NULL) {
-		msn_parse_addressbook_contacts(contact, contacts);
+		msn_parse_addressbook_contacts(session, contacts);
 	}
 
-	abNode =xmlnode_get_child(result,"ab");
-	if(abNode != NULL){
+	abNode = xmlnode_get_child(result, "ab");
+	if (abNode != NULL) {
 		xmlnode *node2;
 		char *tmp = NULL;
 
 		if ((node2 = xmlnode_get_child(abNode, "lastChange")))
 			tmp = xmlnode_get_data(node2);
-		purple_debug_info("MSNAB"," lastchanged Time:{%s}\n", tmp ? tmp : "(null)");
+		purple_debug_info("msn", "AB lastchanged Time:{%s}\n", tmp ? tmp : "(null)");
 		purple_account_set_string(session->account, "ablastChange", tmp);
 
 		g_free(tmp); tmp = NULL;
 		if ((node2 = xmlnode_get_child(abNode, "DynamicItemLastChanged")))
 			tmp = xmlnode_get_data(node2);
-		purple_debug_info("MsnAB"," DynamicItemLastChanged :{%s}\n", tmp ? tmp : "(null)");
+		purple_debug_info("msn", "AB DynamicItemLastChanged :{%s}\n", tmp ? tmp : "(null)");
 		purple_account_set_string(session->account, "DynamicItemLastChanged", tmp);
 		g_free(tmp);
 	}
@@ -753,30 +766,25 @@
 static void
 msn_get_address_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data)
 {
-	MsnContact *contact = data;
-	MsnSession *session;
+	MsnSession *session = data;
 
 	if (resp == NULL)
 		return;
 
-	g_return_if_fail(contact != NULL);
-	session = contact->session;
 	g_return_if_fail(session != NULL);
 
-	purple_debug_misc("MSNAB", "Got the Address Book!\n");
+	purple_debug_misc("msn", "Got the Address Book!\n");
 
-	if (msn_parse_addressbook(contact, resp->xml)) {
-		if (!session->logged_in) {
-			msn_send_privacy(session->account->gc);
-			msn_notification_dump_contact(session);
-		}
+	if (msn_parse_addressbook(session, resp->xml)) {
+		msn_send_privacy(session->account->gc);
+		msn_notification_dump_contact(session);
 	} else {
 		/* This is making us loop infinitely when we fail to parse the
 		  address book, disable for now (we should re-enable when we
 		  send timestamps)
 		*/
 		/*
-		msn_get_address_book(contact, NULL, NULL);
+		msn_get_address_book(session, NULL, NULL);
 		*/
 		msn_session_disconnect(session);
 		purple_connection_error_reason(session->account->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to retrieve MSN Address Book"));
@@ -785,13 +793,13 @@
 
 /*get the address book*/
 void
-msn_get_address_book(MsnContact *contact,
+msn_get_address_book(MsnSession *session,
 	MsnSoapPartnerScenario partner_scenario, const char *LastChanged,
 	const char *dynamicItemLastChange)
 {
 	char *body, *update_str = NULL;
 
-	purple_debug_misc("MSNAB","Getting Address Book\n");
+	purple_debug_misc("msn", "Getting Address Book\n");
 
 	/*build SOAP and POST it*/
 	if (dynamicItemLastChange != NULL)
@@ -799,18 +807,121 @@
 	else if (LastChanged != NULL)
 		update_str = g_strdup_printf(MSN_GET_ADDRESS_UPDATE_XML, LastChanged);
 
-	body = g_strdup_printf(MSN_GET_ADDRESS_TEMPLATE, MsnSoapPartnerScenarioText[partner_scenario], update_str ? update_str : "");
+	body = g_markup_printf_escaped(MSN_GET_ADDRESS_TEMPLATE,
+		MsnSoapPartnerScenarioText[partner_scenario],
+		msn_nexus_get_token_str(session->nexus, MSN_AUTH_CONTACTS),
+		update_str ? update_str : "");
 
-	msn_soap_message_send(contact->session,
+	msn_soap_message_send(session,
 		msn_soap_message_new(MSN_GET_ADDRESS_SOAP_ACTION,
 			xmlnode_from_str(body, -1)),
-		MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL, msn_get_address_cb,
-		contact);
+		MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL, FALSE,
+		msn_get_address_cb, session);
 
 	g_free(update_str);
 	g_free(body);
 }
 
+/***************************************************************
+ * Contact Operations
+ ***************************************************************/
+
+static const char *
+msn_contact_operation_str(MsnCallbackAction action)
+{
+	/* Make sure this is large enough when adding more */
+	static char buf[BUF_LEN];
+	buf[0] = '\0';
+
+	if (action & MSN_ADD_BUDDY)
+		strcat(buf, "Adding Buddy,");
+	if (action & MSN_MOVE_BUDDY)
+		strcat(buf, "Moving Buddy,");
+	if (action & MSN_ACCEPTED_BUDDY)
+		strcat(buf, "Accepted Buddy,");
+	if (action & MSN_DENIED_BUDDY)
+		strcat(buf, "Denied Buddy,");
+	if (action & MSN_ADD_GROUP)
+		strcat(buf, "Adding Group,");
+	if (action & MSN_DEL_GROUP)
+		strcat(buf, "Deleting Group,");
+	if (action & MSN_RENAME_GROUP)
+		strcat(buf, "Renaming Group,");
+	if (action & MSN_UPDATE_INFO)
+		strcat(buf, "Updating Contact Info,");
+
+	return buf;
+}
+
+static gboolean msn_contact_request(MsnCallbackState *state);
+
+static void
+msn_contact_request_cb(MsnSoapMessage *req, MsnSoapMessage *resp,
+	gpointer data)
+{
+	MsnCallbackState *state = data;
+	xmlnode *faultcode;
+	char *faultcode_str;
+
+	if (resp == NULL) {
+		purple_debug_error("msn",
+		                   "Operation {%s} failed. No response received from server.\n",
+		                   msn_contact_operation_str(state->action));
+		return;
+	}
+
+	faultcode = xmlnode_get_child(resp->xml, "Body/Fault/faultcode");
+
+	if (faultcode == NULL) {
+		/* No errors */
+		if (state->cb)
+			((MsnSoapCallback)state->cb)(req, resp, data);
+		msn_callback_state_free(state);
+		return;
+	}
+
+	faultcode_str = xmlnode_get_data(faultcode);
+
+	if (faultcode_str && g_str_equal(faultcode_str, "q0:BadContextToken")) {
+		purple_debug_info("msn",
+		                  "Contact Operation {%s} failed because of bad token."
+		                  " Updating token now and retrying operation.\n",
+		                  msn_contact_operation_str(state->action));
+		/* Token has expired, so renew it, and try again later */
+		msn_nexus_update_token(state->session->nexus, MSN_AUTH_CONTACTS,
+		                       (GSourceFunc)msn_contact_request, data);
+	}
+	else
+	{
+		/* We don't know how to respond to this faultcode, so just log it */
+		/* XXX: Probably should notify the user or undo the change or something? */
+		char *str = xmlnode_to_str(xmlnode_get_child(resp->xml, "Body/Fault"), NULL);
+		purple_debug_error("msn", "Operation {%s} Failed, SOAP Fault was: %s\n",
+		                   msn_contact_operation_str(state->action), str);
+		g_free(str);
+		msn_callback_state_free(state);
+	}
+
+	g_free(faultcode_str);
+}
+
+static gboolean
+msn_contact_request(MsnCallbackState *state)
+{
+	if (state->token == NULL)
+		state->token = xmlnode_get_child(state->body,
+			"Header/ABAuthHeader/TicketToken");
+	/* delete old & replace with new token */
+	xmlnode_free(state->token->child);
+	xmlnode_insert_data(state->token,
+		msn_nexus_get_token_str(state->session->nexus, MSN_AUTH_CONTACTS), -1);
+	msn_soap_message_send(state->session,
+		msn_soap_message_new(state->post_action, xmlnode_copy(state->body)),
+		MSN_CONTACT_SERVER, state->post_url, FALSE,
+		msn_contact_request_cb, state);
+	return FALSE;
+}
+
 static void
 msn_add_contact_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp,
 	gpointer data)
@@ -818,33 +929,32 @@
 	MsnCallbackState *state = data;
 	MsnSession *session = state->session;
 
+	MsnUserList *userlist;
+	MsnUser *user;
+
 	g_return_if_fail(session != NULL);
 
-	if (resp != NULL) {
-		MsnUserList *userlist = session->userlist;
-		MsnUser *user;
+	userlist = session->userlist;
 
-		purple_debug_info("MSNCL","Contact added successfully\n");
+	purple_debug_info("msn", "Contact added successfully\n");
 
-		// the code this block is replacing didn't send ADL for yahoo contacts,
-		// but i haven't confirmed this is WLM's behaviour wrt yahoo contacts
-		if ( !msn_user_is_yahoo(session->account, state->who) ) {
-			msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_AL);
-			msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_FL);
-		}
-
-		msn_notification_send_fqy(session, state->who);
-
-		user = msn_userlist_find_add_user(userlist, state->who, state->who);
-		msn_user_add_group_id(user, state->guid);
+	/* the code this block is replacing didn't send ADL for yahoo contacts,
+	 * but i haven't confirmed this is WLM's behaviour wrt yahoo contacts
+	 */
+	if ( !msn_user_is_yahoo(session->account, state->who) ) {
+		msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_AL);
+		msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_FL);
 	}
 
-	msn_callback_state_free(state);
+	msn_notification_send_fqy(session, state->who);
+
+	user = msn_userlist_find_add_user(userlist, state->who, state->who);
+	msn_user_add_group_id(user, state->guid);
 }
 
 /* add a Contact in MSN_INDIVIDUALS_GROUP */
 void
-msn_add_contact(MsnContact *contact, MsnCallbackState *state, const char *passport)
+msn_add_contact(MsnSession *session, MsnCallbackState *state, const char *passport)
 {
 	gchar *body = NULL;
 	gchar *contact_xml = NULL;
@@ -861,16 +971,16 @@
 	contact_xml = g_strdup_printf(MSN_XML_ADD_CONTACT, escaped_displayname, passport);
 #endif
 
-	purple_debug_info("MSNCL","Adding contact %s to contact list\n", passport);
+	purple_debug_info("msn", "Adding contact %s to contact list\n", passport);
 
 	contact_xml = g_strdup_printf(MSN_CONTACT_XML, passport);
 	body = g_strdup_printf(MSN_ADD_CONTACT_TEMPLATE, contact_xml);
 
-	msn_soap_message_send(contact->session,
-		msn_soap_message_new(MSN_CONTACT_ADD_SOAP_ACTION,
-			xmlnode_from_str(body, -1)),
-		MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL,
-		msn_add_contact_read_cb, state);
+	state->body = xmlnode_from_str(body, -1);
+	state->post_action = MSN_CONTACT_ADD_SOAP_ACTION;
+	state->post_url = MSN_ADDRESS_BOOK_POST_URL;
+	state->cb = msn_add_contact_read_cb;
+	msn_contact_request(state);
 
 	g_free(contact_xml);
 	g_free(body);
@@ -887,41 +997,35 @@
 
 	userlist = state->session->userlist;
 
-	if (resp != NULL) {
-		if (msn_userlist_add_buddy_to_group(userlist, state->who,
-				state->new_group_name)) {
-			purple_debug_info("MSNCL", "Contact %s added to group %s successfully!\n", state->who, state->new_group_name);
-		} else {
-			purple_debug_info("MSNCL","Contact %s added to group %s successfully on server, but failed in the local list\n", state->who, state->new_group_name);
-		}
+	if (msn_userlist_add_buddy_to_group(userlist, state->who,
+			state->new_group_name)) {
+		purple_debug_info("msn", "Contact %s added to group %s successfully!\n", state->who, state->new_group_name);
+	} else {
+		purple_debug_info("msn", "Contact %s added to group %s successfully on server, but failed in the local list\n", state->who, state->new_group_name);
+	}
 
-		if (state->action & MSN_ADD_BUDDY) {
-			MsnUser *user = msn_userlist_find_user(userlist, state->who);
-
-        	if ( !msn_user_is_yahoo(state->session->account, state->who) ) {
+	if (state->action & MSN_ADD_BUDDY) {
+		MsnUser *user = msn_userlist_find_user(userlist, state->who);
 
-				msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_AL);
-				msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_FL);
-	        }
-	        msn_notification_send_fqy(state->session, state->who);
+		if ( !msn_user_is_yahoo(state->session->account, state->who) ) {
+			msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_AL);
+			msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_FL);
+		}
+		msn_notification_send_fqy(state->session, state->who);
 
-			if (msn_userlist_user_is_in_list(user, MSN_LIST_PL)) {
-				msn_del_contact_from_list(state->session->contact, NULL, state->who, MSN_LIST_PL);
-				msn_callback_state_free(state);
-				return;
-			}
-		}
-
-		if (state->action & MSN_MOVE_BUDDY) {
-			msn_del_contact_from_group(state->session->contact, state->who, state->old_group_name);
+		if (msn_userlist_user_is_in_list(user, MSN_LIST_PL)) {
+			msn_del_contact_from_list(state->session, NULL, state->who, MSN_LIST_PL);
+			return;
 		}
 	}
 
-	msn_callback_state_free(state);
+	if (state->action & MSN_MOVE_BUDDY) {
+		msn_del_contact_from_group(state->session, state->who, state->old_group_name);
+	}
 }
 
 void
-msn_add_contact_to_group(MsnContact *contact, MsnCallbackState *state,
+msn_add_contact_to_group(MsnSession *session, MsnCallbackState *state,
 			 const char *passport, const char *groupId)
 {
 	MsnUserList *userlist;
@@ -931,37 +1035,33 @@
 	g_return_if_fail(passport != NULL);
 	g_return_if_fail(groupId != NULL);
 
-	g_return_if_fail(contact != NULL);
-	g_return_if_fail(contact->session != NULL);
-	g_return_if_fail(contact->session->userlist != NULL);
+	g_return_if_fail(session != NULL);
 
-	userlist = contact->session->userlist;
+	userlist = session->userlist;
 
 	if (!strcmp(groupId, MSN_INDIVIDUALS_GROUP_ID) || !strcmp(groupId, MSN_NON_IM_GROUP_ID)) {
 
 		user = msn_userlist_find_add_user(userlist, passport, passport);
 
 		if (state->action & MSN_ADD_BUDDY) {
-			msn_add_contact(contact, state, passport);
+			msn_add_contact(session, state, passport);
 			return;
 		}
 
 		if (state->action & MSN_MOVE_BUDDY) {
 			msn_user_add_group_id(user, groupId);
-			msn_del_contact_from_group(contact, passport, state->old_group_name);
-		} else {
-			msn_callback_state_free(state);
+			msn_del_contact_from_group(session, passport, state->old_group_name);
 		}
 
 		return;
 	}
 
-	purple_debug_info("MSNCL", "Adding user %s to group %s\n", passport,
+	purple_debug_info("msn", "Adding user %s to group %s\n", passport,
 			  msn_userlist_find_group_name(userlist, groupId));
 
 	user = msn_userlist_find_user(userlist, passport);
 	if (user == NULL) {
-		purple_debug_warning("MSNCL", "Unable to retrieve user %s from the userlist!\n", passport);
+		purple_debug_warning("msn", "Unable to retrieve user %s from the userlist!\n", passport);
 		msn_callback_state_free(state);
 		return; /* guess this never happened! */
 	}
@@ -974,11 +1074,11 @@
 
 	body = g_strdup_printf(MSN_ADD_CONTACT_GROUP_TEMPLATE, groupId, contact_xml);
 
-	msn_soap_message_send(state->session,
-		msn_soap_message_new(MSN_ADD_CONTACT_GROUP_SOAP_ACTION,
-			xmlnode_from_str(body, -1)),
-		MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL,
-		msn_add_contact_to_group_read_cb, state);
+	state->body = xmlnode_from_str(body, -1);
+	state->post_action = MSN_ADD_CONTACT_GROUP_SOAP_ACTION;
+	state->post_url = MSN_ADDRESS_BOOK_POST_URL;
+	state->cb = msn_add_contact_to_group_read_cb;
+	msn_contact_request(state);
 
 	g_free(contact_xml);
 	g_free(body);
@@ -989,24 +1089,19 @@
 	gpointer data)
 {
 	MsnCallbackState *state = data;
-
-	if (resp != NULL) {
-		MsnUserList *userlist = state->session->userlist;
-		MsnUser *user = msn_userlist_find_user_with_id(userlist, state->uid);
-
-		purple_debug_info("MSNCL","Delete contact successful\n");
+	MsnUserList *userlist = state->session->userlist;
+	MsnUser *user = msn_userlist_find_user_with_id(userlist, state->uid);
 
-		if (user != NULL) {
-			msn_userlist_remove_user(userlist, user);
-		}
+	purple_debug_info("msn", "Delete contact successful\n");
+
+	if (user != NULL) {
+		msn_userlist_remove_user(userlist, user);
 	}
-
-	msn_callback_state_free(state);
 }
 
 /*delete a Contact*/
 void
-msn_delete_contact(MsnContact *contact, const char *contactId)
+msn_delete_contact(MsnSession *session, const char *contactId)
 {
 	gchar *body = NULL;
 	gchar *contact_id_xml = NULL ;
@@ -1015,17 +1110,18 @@
 	g_return_if_fail(contactId != NULL);
 	contact_id_xml = g_strdup_printf(MSN_CONTACT_ID_XML, contactId);
 
-	state = msn_callback_state_new(contact->session);
+	state = msn_callback_state_new(session);
 	msn_callback_state_set_uid(state, contactId);
 
 	/* build SOAP request */
-	purple_debug_info("MSNCL","Deleting contact with contactId: %s\n", contactId);
+	purple_debug_info("msn", "Deleting contact with contactId: %s\n", contactId);
 	body = g_strdup_printf(MSN_DEL_CONTACT_TEMPLATE, contact_id_xml);
-	msn_soap_message_send(contact->session,
-		msn_soap_message_new(MSN_CONTACT_DEL_SOAP_ACTION,
-			xmlnode_from_str(body, -1)),
-		MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL,
-		msn_delete_contact_read_cb, state);
+
+	state->body = xmlnode_from_str(body, -1);
+	state->post_action = MSN_CONTACT_DEL_SOAP_ACTION;
+	state->post_url = MSN_ADDRESS_BOOK_POST_URL;
+	state->cb = msn_delete_contact_read_cb;
+	msn_contact_request(state);
 
 	g_free(contact_id_xml);
 	g_free(body);
@@ -1037,20 +1133,16 @@
 {
 	MsnCallbackState *state = data;
 
-	if (resp != NULL) {
-		if (msn_userlist_rem_buddy_from_group(state->session->userlist,
-				state->who, state->old_group_name)) {
-			purple_debug_info("MSNCL", "Contact %s deleted successfully from group %s\n", state->who, state->old_group_name);
-		} else {
-			purple_debug_info("MSNCL", "Contact %s deleted successfully from group %s in the server, but failed in the local list\n", state->who, state->old_group_name);
-		}
+	if (msn_userlist_rem_buddy_from_group(state->session->userlist,
+			state->who, state->old_group_name)) {
+		purple_debug_info("msn", "Contact %s deleted successfully from group %s\n", state->who, state->old_group_name);
+	} else {
+		purple_debug_info("msn", "Contact %s deleted successfully from group %s in the server, but failed in the local list\n", state->who, state->old_group_name);
 	}
-
-	msn_callback_state_free(state);
 }
 
 void
-msn_del_contact_from_group(MsnContact *contact, const char *passport, const char *group_name)
+msn_del_contact_from_group(MsnSession *session, const char *passport, const char *group_name)
 {
 	MsnUserList * userlist;
 	MsnUser *user;
@@ -1060,24 +1152,22 @@
 
 	g_return_if_fail(passport != NULL);
 	g_return_if_fail(group_name != NULL);
-	g_return_if_fail(contact != NULL);
-	g_return_if_fail(contact->session != NULL);
-	g_return_if_fail(contact->session->userlist != NULL);
+	g_return_if_fail(session != NULL);
 
-	userlist = contact->session->userlist;
+	userlist = session->userlist;
 
 	groupId = msn_userlist_find_group_id(userlist, group_name);
 	if (groupId != NULL) {
-		purple_debug_info("MSNCL", "Deleting user %s from group %s\n", passport, group_name);
+		purple_debug_info("msn", "Deleting user %s from group %s\n", passport, group_name);
 	} else {
-		purple_debug_warning("MSNCL", "Unable to retrieve group id from group %s !\n", group_name);
+		purple_debug_warning("msn", "Unable to retrieve group id from group %s !\n", group_name);
 		return;
 	}
 
 	user = msn_userlist_find_user(userlist, passport);
 
 	if (user == NULL) {
-		purple_debug_warning("MSNCL", "Unable to retrieve user from passport %s!\n", passport);
+		purple_debug_warning("msn", "Unable to retrieve user from passport %s!\n", passport);
 		return;
 	}
 
@@ -1086,7 +1176,7 @@
 		return;
 	}
 
-	state = msn_callback_state_new(contact->session);
+	state = msn_callback_state_new(session);
 	msn_callback_state_set_who(state, passport);
 	msn_callback_state_set_guid(state, groupId);
 	msn_callback_state_set_old_group_name(state, group_name);
@@ -1094,11 +1184,11 @@
 	contact_id_xml = g_strdup_printf(MSN_CONTACT_ID_XML, user->uid);
 	body = g_strdup_printf(MSN_CONTACT_DEL_GROUP_TEMPLATE, contact_id_xml, groupId);
 
-	msn_soap_message_send(contact->session,
-		msn_soap_message_new(MSN_CONTACT_DEL_GROUP_SOAP_ACTION,
-			xmlnode_from_str(body, -1)),
-		MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL,
-		msn_del_contact_from_group_read_cb, state);
+	state->body = xmlnode_from_str(body, -1);
+	state->post_action = MSN_CONTACT_DEL_GROUP_SOAP_ACTION;
+	state->post_url = MSN_ADDRESS_BOOK_POST_URL;
+	state->cb = msn_del_contact_from_group_read_cb;
+	msn_contact_request(state);
 
 	g_free(contact_id_xml);
 	g_free(body);
@@ -1109,32 +1199,75 @@
 msn_update_contact_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp,
 	gpointer data)
 {
-	if (resp)
-		purple_debug_info("MSN CL","Contact updated successfully\n");
-	else
-		purple_debug_info("MSN CL","Contact updated successfully\n");
+	purple_debug_info("msn", "Contact updated successfully\n");
 }
 
-/* Update a contact's nickname */
+/* Update a contact's info */
 void
-msn_update_contact(MsnContact *contact, const char* nickname)
+msn_update_contact(MsnSession *session, const char *passport, MsnContactUpdateType type, const char* value)
 {
-	gchar *body = NULL, *escaped_nickname;
+	MsnCallbackState *state;
+	xmlnode *contact;
+	xmlnode *contact_info;
+	xmlnode *changes;
+
+	purple_debug_info("msn", "Update contact information with new %s: %s\n",
+		type==MSN_UPDATE_DISPLAY ? "display name" : "alias", value);
+	purple_debug_info("msn", "passport=%s\n", passport);
+	g_return_if_fail(passport != NULL);
+	contact_info = xmlnode_new("contactInfo");
+	changes = xmlnode_new("propertiesChanged");
 
-	purple_debug_info("MSN CL","Update contact information with new friendly name: %s\n", nickname);
+	switch (type) {
+		xmlnode *annotations;
+		xmlnode *display;
+		xmlnode *a, *n, *v;
+		case MSN_UPDATE_DISPLAY:
+			display = xmlnode_new_child(contact_info, "displayName");
+			xmlnode_insert_data(display, value, -1);
+			xmlnode_insert_data(changes, "DisplayName", -1);
+			break;
 
-	escaped_nickname = g_markup_escape_text(nickname, -1);
+		case MSN_UPDATE_ALIAS:
+			annotations = xmlnode_new_child(contact_info, "annotations");
+			xmlnode_insert_data(changes, "Annotation ", -1);
 
-	body = g_strdup_printf(MSN_CONTACT_UPDATE_TEMPLATE, escaped_nickname);
+			a = xmlnode_new_child(annotations, "Annotation");
+			n = xmlnode_new_child(a, "Name");
+			xmlnode_insert_data(n, "AB.NickName", -1);
+			v = xmlnode_new_child(a, "Value");
+			xmlnode_insert_data(v, value, -1);
+			break;
+
+		default:
+			g_return_if_reached();
+	}
+
+
+
+	state = msn_callback_state_new(session);
 
-	msn_soap_message_send(contact->session,
-		msn_soap_message_new(MSN_CONTACT_UPDATE_SOAP_ACTION,
-			xmlnode_from_str(body, -1)),
-		MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL,
-		msn_update_contact_read_cb, NULL);
+	state->body = xmlnode_from_str(MSN_CONTACT_UPDATE_TEMPLATE, -1);
+	state->action = MSN_UPDATE_INFO;
+	state->post_action = MSN_CONTACT_UPDATE_SOAP_ACTION;
+	state->post_url = MSN_ADDRESS_BOOK_POST_URL;
+	state->cb = msn_update_contact_read_cb;
+
+	contact = xmlnode_get_child(state->body, "Body/ABContactUpdate/contacts/Contact");
+	xmlnode_insert_child(contact, contact_info);
+	xmlnode_insert_child(contact, changes);
 
-	g_free(escaped_nickname);
-	g_free(body);
+	if (!strcmp(passport, "Me")) {
+		xmlnode *contactType = xmlnode_new_child(contact_info, "contactType");
+		xmlnode_insert_data(contactType, "Me", -1);
+	} else {
+		MsnUser *user = msn_userlist_find_user(session->userlist, passport);
+		xmlnode *contactId = xmlnode_new_child(contact, "contactId");
+		msn_callback_state_set_uid(state, user->uid);
+		xmlnode_insert_data(contactId, state->uid, -1);
+	}
+
+	msn_contact_request(state);
 }
 
 static void
@@ -1144,74 +1277,70 @@
 	MsnCallbackState *state = data;
 	MsnSession *session = state->session;
 
-	if (resp != NULL) {
-		purple_debug_info("MSN CL", "Contact %s deleted successfully from %s list on server!\n", state->who, MsnMemberRole[state->list_id]);
+	purple_debug_info("msn", "Contact %s deleted successfully from %s list on server!\n", state->who, MsnMemberRole[state->list_id]);
 
-		if (state->list_id == MSN_LIST_PL) {
-			MsnUser *user = msn_userlist_find_user(session->userlist, state->who);
+	if (state->list_id == MSN_LIST_PL) {
+		MsnUser *user = msn_userlist_find_user(session->userlist, state->who);
+		MsnCallbackState *new_state = msn_callback_state_dup(state);
 
-			if (user != NULL)
-				msn_user_unset_op(user, MSN_LIST_PL_OP);
+		if (user != NULL)
+			msn_user_unset_op(user, MSN_LIST_PL_OP);
 
-			msn_add_contact_to_list(session->contact, state, state->who, MSN_LIST_RL);
-			return;
-		} else if (state->list_id == MSN_LIST_AL) {
-			purple_privacy_permit_remove(session->account, state->who, TRUE);
-			msn_add_contact_to_list(session->contact, NULL, state->who, MSN_LIST_BL);
-		} else if (state->list_id == MSN_LIST_BL) {
-			purple_privacy_deny_remove(session->account, state->who, TRUE);
-			msn_add_contact_to_list(session->contact, NULL, state->who, MSN_LIST_AL);
-		}
+		msn_add_contact_to_list(session, new_state, state->who, MSN_LIST_RL);
+		return;
+	} else if (state->list_id == MSN_LIST_AL) {
+		purple_privacy_permit_remove(session->account, state->who, TRUE);
+		msn_add_contact_to_list(session, NULL, state->who, MSN_LIST_BL);
+	} else if (state->list_id == MSN_LIST_BL) {
+		purple_privacy_deny_remove(session->account, state->who, TRUE);
+		msn_add_contact_to_list(session, NULL, state->who, MSN_LIST_AL);
 	}
 
-	msn_callback_state_free(state);
 }
 
 void
-msn_del_contact_from_list(MsnContact *contact, MsnCallbackState *state,
+msn_del_contact_from_list(MsnSession *session, MsnCallbackState *state,
 			  const gchar *passport, const MsnListId list)
 {
 	gchar *body = NULL, *member = NULL;
 	MsnSoapPartnerScenario partner_scenario;
 	MsnUser *user;
 
-	g_return_if_fail(contact != NULL);
+	g_return_if_fail(session != NULL);
 	g_return_if_fail(passport != NULL);
 	g_return_if_fail(list < 5);
 
-	purple_debug_info("MSN CL", "Deleting contact %s from %s list\n", passport, MsnMemberRole[list]);
+	purple_debug_info("msn", "Deleting contact %s from %s list\n", passport, MsnMemberRole[list]);
 
 	if (state == NULL) {
-		state = msn_callback_state_new(contact->session);
+		state = msn_callback_state_new(session);
 	}
 	msn_callback_state_set_list_id(state, list);
 	msn_callback_state_set_who(state, passport);
 
 	if (list == MSN_LIST_PL) {
-		g_return_if_fail(contact->session != NULL);
-		g_return_if_fail(contact->session->userlist != NULL);
+		g_return_if_fail(session->userlist != NULL);
 
-		user = msn_userlist_find_user(contact->session->userlist, passport);
+		user = msn_userlist_find_user(session->userlist, passport);
 
 		partner_scenario = MSN_PS_CONTACT_API;
 		member = g_strdup_printf(MSN_MEMBER_MEMBERSHIPID_XML, user->membership_id[MSN_LIST_PL]);
 	} else {
 		/* list == MSN_LIST_AL || list == MSN_LIST_BL */
 		partner_scenario = MSN_PS_BLOCK_UNBLOCK;
-
+		
 		member = g_strdup_printf(MSN_MEMBER_PASSPORT_XML, passport);
 	}
 
-	body = g_strdup_printf( MSN_CONTACT_DELECT_FROM_LIST_TEMPLATE,
-			        MsnSoapPartnerScenarioText[partner_scenario],
-			        MsnMemberRole[list],
-			        member);
+	body = g_strdup_printf(MSN_CONTACT_DELECT_FROM_LIST_TEMPLATE,
+		MsnSoapPartnerScenarioText[partner_scenario],
+		MsnMemberRole[list], member);
 
-	msn_soap_message_send(contact->session,
-		msn_soap_message_new(MSN_DELETE_MEMBER_FROM_LIST_SOAP_ACTION,
-			xmlnode_from_str(body, -1)),
-		MSN_CONTACT_SERVER, MSN_SHARE_POST_URL,
-		msn_del_contact_from_list_read_cb, state);
+	state->body = xmlnode_from_str(body, -1);
+	state->post_action = MSN_DELETE_MEMBER_FROM_LIST_SOAP_ACTION;
+	state->post_url = MSN_SHARE_POST_URL;
+	state->cb = msn_del_contact_from_list_read_cb;
+	msn_contact_request(state);
 
 	g_free(member);
 	g_free(body);
@@ -1225,47 +1354,41 @@
 
 	g_return_if_fail(state != NULL);
 	g_return_if_fail(state->session != NULL);
-	g_return_if_fail(state->session->contact != NULL);
+
+	purple_debug_info("msn", "Contact %s added successfully to %s list on server!\n", state->who, MsnMemberRole[state->list_id]);
 
-	if (resp != NULL) {
-		purple_debug_info("MSN CL", "Contact %s added successfully to %s list on server!\n", state->who, MsnMemberRole[state->list_id]);
-
-		if (state->list_id == MSN_LIST_RL) {
-			MsnUser *user = msn_userlist_find_user(state->session->userlist, state->who);
+	if (state->list_id == MSN_LIST_RL) {
+		MsnUser *user = msn_userlist_find_user(state->session->userlist, state->who);
 
-			if (user != NULL) {
-				msn_user_set_op(user, MSN_LIST_RL_OP);
-			}
-
-			if (state->action & MSN_DENIED_BUDDY) {
+		if (user != NULL) {
+			msn_user_set_op(user, MSN_LIST_RL_OP);
+		}
 
-				msn_add_contact_to_list(state->session->contact, NULL, state->who, MSN_LIST_BL);
-			} else if (state->list_id == MSN_LIST_AL) {
-				purple_privacy_permit_add(state->session->account, state->who, TRUE);
-			} else if (state->list_id == MSN_LIST_BL) {
-				purple_privacy_deny_add(state->session->account, state->who, TRUE);
-			}
+		if (state->action & MSN_DENIED_BUDDY) {
+			msn_add_contact_to_list(state->session, NULL, state->who, MSN_LIST_BL);
+		} else if (state->list_id == MSN_LIST_AL) {
+			purple_privacy_permit_add(state->session->account, state->who, TRUE);
+		} else if (state->list_id == MSN_LIST_BL) {
+			purple_privacy_deny_add(state->session->account, state->who, TRUE);
 		}
 	}
-
-	msn_callback_state_free(state);
 }
 
 void
-msn_add_contact_to_list(MsnContact *contact, MsnCallbackState *state,
+msn_add_contact_to_list(MsnSession *session, MsnCallbackState *state,
 			const gchar *passport, const MsnListId list)
 {
 	gchar *body = NULL, *member = NULL;
 	MsnSoapPartnerScenario partner_scenario;
 
-	g_return_if_fail(contact != NULL);
+	g_return_if_fail(session != NULL);
 	g_return_if_fail(passport != NULL);
 	g_return_if_fail(list < 5);
 
-	purple_debug_info("MSN CL", "Adding contact %s to %s list\n", passport, MsnMemberRole[list]);
+	purple_debug_info("msn", "Adding contact %s to %s list\n", passport, MsnMemberRole[list]);
 
 	if (state == NULL) {
-		state = msn_callback_state_new(contact->session);
+		state = msn_callback_state_new(session);
 	}
 	msn_callback_state_set_list_id(state, list);
 	msn_callback_state_set_who(state, passport);
@@ -1275,15 +1398,14 @@
 	member = g_strdup_printf(MSN_MEMBER_PASSPORT_XML, state->who);
 
 	body = g_strdup_printf(MSN_CONTACT_ADD_TO_LIST_TEMPLATE,
-			       MsnSoapPartnerScenarioText[partner_scenario],
-			       MsnMemberRole[list],
-			       member);
+		MsnSoapPartnerScenarioText[partner_scenario],
+		MsnMemberRole[list], member);
 
-	msn_soap_message_send(contact->session,
-		msn_soap_message_new(MSN_ADD_MEMBER_TO_LIST_SOAP_ACTION,
-			xmlnode_from_str(body, -1)),
-		MSN_CONTACT_SERVER, MSN_SHARE_POST_URL,
-		msn_add_contact_to_list_read_cb, state);
+	state->body = xmlnode_from_str(body, -1);
+	state->post_action = MSN_ADD_MEMBER_TO_LIST_SOAP_ACTION;
+	state->post_url = MSN_SHARE_POST_URL;
+	state->cb = msn_add_contact_to_list_read_cb;
+	msn_contact_request(state);
 
 	g_free(member);
 	g_free(body);
@@ -1293,24 +1415,23 @@
 static void
 msn_gleams_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data)
 {
-	if (resp != NULL)
-		purple_debug_info("MSNP14","Gleams read done\n");
-	else
-		purple_debug_info("MSNP14","Gleams read failed\n");
+	purple_debug_info("msn", "Gleams read done\n");
 }
 
 /*get the gleams info*/
 void
-msn_get_gleams(MsnContact *contact)
+msn_get_gleams(MsnSession *session)
 {
 	MsnSoapReq *soap_request;
 
-	purple_debug_info("MSNP14","msn get gleams info...\n");
-	msn_soap_message_send(contact->session,
-		msn_soap_message_new(MSN_GET_GLEAMS_SOAP_ACTION,
-			xmlnode_from_str(MSN_GLEAMS_TEMPLATE, -1)),
-		MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL,
-		msn_gleams_read_cb, NULL);
+	purple_debug_info("msn", "msn get gleams info...\n");
+
+	state = msn_callback_state_new(session);
+	state->body = xmlnode_from_str(MSN_GLEAMS_TEMPLATE, -1);
+	state->post_action = MSN_GET_GLEAMS_SOAP_ACTION;
+	state->post_url = MSN_ADDRESS_BOOK_POST_URL;
+	state->cb = msn_gleams_read_cb;
+	msn_contact_request(state);
 }
 #endif
 
@@ -1323,68 +1444,62 @@
 msn_group_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data)
 {
 	MsnCallbackState *state = data;
+	MsnSession *session;
+	MsnUserList *userlist;
 
-	purple_debug_info("MSNCL", "Group request successful.\n");
+	purple_debug_info("msn", "Group request successful.\n");
 
 	g_return_if_fail(state->session != NULL);
 	g_return_if_fail(state->session->userlist != NULL);
-	g_return_if_fail(state->session->contact != NULL);
+
+	session = state->session;
+	userlist = session->userlist;
 
-	if (resp == NULL) {
-		msn_callback_state_free(state);
-		return;
+	if (state->action & MSN_RENAME_GROUP) {
+		msn_userlist_rename_group_id(session->userlist,
+					     state->guid,
+					     state->new_group_name);
 	}
 
-	if (state) {
-		MsnSession *session = state->session;
-		MsnUserList *userlist = session->userlist;
-
-		if (state->action & MSN_RENAME_GROUP) {
-			msn_userlist_rename_group_id(session->userlist,
-						     state->guid,
-						     state->new_group_name);
-		}
+	if (state->action & MSN_ADD_GROUP) {
+		/* the response is taken from
+		   http://telepathy.freedesktop.org/wiki/Pymsn/MSNP/ContactListActions
+		   should copy it to msnpiki some day */
+		xmlnode *guid_node = xmlnode_get_child(resp->xml,
+			"Body/ABGroupAddResponse/ABGroupAddResult/guid");
 
-		if (state->action & MSN_ADD_GROUP) {
-			/* the response is taken from
-			   http://telepathy.freedesktop.org/wiki/Pymsn/MSNP/ContactListActions
-			   should copy it to msnpiki some day */
-			xmlnode *guid_node = msn_soap_xml_get(resp->xml,
-				"Body/ABGroupAddResponse/ABGroupAddResult/guid");
+		if (guid_node) {
+			char *guid = xmlnode_get_data(guid_node);
 
-			if (guid_node) {
-				char *guid = xmlnode_get_data(guid_node);
-
-				/* create and add the new group to the userlist */
-				purple_debug_info("MSNCL", "Adding group %s with guid = %s to the userlist\n", state->new_group_name, guid);
-				msn_group_new(session->userlist, guid, state->new_group_name);
+			/* create and add the new group to the userlist */
+			purple_debug_info("msn", "Adding group %s with guid = %s to the userlist\n", state->new_group_name, guid);
+			msn_group_new(session->userlist, guid, state->new_group_name);
 
-				if (state->action & MSN_ADD_BUDDY) {
-					msn_userlist_add_buddy(session->userlist,
-						state->who,
-						state->new_group_name);
-				} else if (state->action & MSN_MOVE_BUDDY) {
-					msn_add_contact_to_group(session->contact, state, state->who, guid);
-					g_free(guid);
-					return;
-				}
-				g_free(guid);
-			} else {
-				purple_debug_info("MSNCL", "Adding group %s failed\n",
+			if (state->action & MSN_ADD_BUDDY) {
+				msn_userlist_add_buddy(session->userlist,
+					state->who,
 					state->new_group_name);
+			} else if (state->action & MSN_MOVE_BUDDY) {
+				/* This will be freed when the add contact callback fires */
+				MsnCallbackState *new_state = msn_callback_state_dup(state);
+				msn_add_contact_to_group(session, new_state, state->who, guid); 
+				g_free(guid);
+				return;
 			}
+			g_free(guid);
+		} else {
+			purple_debug_info("msn", "Adding group %s failed\n",
+				state->new_group_name);
 		}
-
-		if (state->action & MSN_DEL_GROUP) {
-			GList *l;
+	}
 
-			msn_userlist_remove_group_id(session->userlist, state->guid);
-			for (l = userlist->users; l != NULL; l = l->next) {
-				msn_user_remove_group_id( (MsnUser *)l->data, state->guid);
-			}
+	if (state->action & MSN_DEL_GROUP) {
+		GList *l;
+
+		msn_userlist_remove_group_id(session->userlist, state->guid);
+		for (l = userlist->users; l != NULL; l = l->next) {
+			msn_user_remove_group_id( (MsnUser *)l->data, state->guid);
 		}
-
-		msn_callback_state_free(state);
 	}
 }
 
@@ -1393,11 +1508,12 @@
 msn_add_group(MsnSession *session, MsnCallbackState *state, const char* group_name)
 {
 	char *body = NULL;
+	char *escaped_group_name = NULL;
 
 	g_return_if_fail(session != NULL);
 	g_return_if_fail(group_name != NULL);
 
-	purple_debug_info("MSNCL","Adding group %s to contact list.\n", group_name);
+	purple_debug_info("msn", "Adding group %s to contact list.\n", group_name);
 
 	if (state == NULL) {
 		state = msn_callback_state_new(session);
@@ -1409,14 +1525,16 @@
 	/* escape group name's html special chars so it can safely be sent
 	* in a XML SOAP request
 	*/
-	body = g_markup_printf_escaped(MSN_GROUP_ADD_TEMPLATE, group_name);
+	escaped_group_name = g_markup_escape_text(group_name, -1);
+	body = g_strdup_printf(MSN_GROUP_ADD_TEMPLATE, escaped_group_name);
 
-	msn_soap_message_send(session,
-		msn_soap_message_new(MSN_GROUP_ADD_SOAP_ACTION,
-			xmlnode_from_str(body, -1)),
-		MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL,
-		msn_group_read_cb, state);
+	state->body = xmlnode_from_str(body, -1);
+	state->post_action = MSN_GROUP_ADD_SOAP_ACTION;
+	state->post_url = MSN_ADDRESS_BOOK_POST_URL;
+	state->cb = msn_group_read_cb;
+	msn_contact_request(state);
 
+	g_free(escaped_group_name);
 	g_free(body);
 }
 
@@ -1431,7 +1549,7 @@
 	g_return_if_fail(session != NULL);
 
 	g_return_if_fail(group_name != NULL);
-	purple_debug_info("MSNCL","Deleting group %s from contact list\n", group_name);
+	purple_debug_info("msn", "Deleting group %s from contact list\n", group_name);
 
 	guid = msn_userlist_find_group_id(session->userlist, group_name);
 
@@ -1439,12 +1557,12 @@
 	*  we need to delete nothing
 	*/
 	if (guid == NULL) {
-		purple_debug_info("MSNCL", "Group %s guid not found, returning.\n", group_name);
+		purple_debug_info("msn", "Group %s guid not found, returning.\n", group_name);
 		return;
 	}
 
 	if ( !strcmp(guid, MSN_INDIVIDUALS_GROUP_ID) || !strcmp(guid, MSN_NON_IM_GROUP_ID) ) {
-		// XXX add back PurpleGroup since it isn't really removed in the server?
+		/* XXX add back PurpleGroup since it isn't really removed in the server? */
 		return;
 	}
 
@@ -1454,11 +1572,11 @@
 
 	body = g_strdup_printf(MSN_GROUP_DEL_TEMPLATE, guid);
 
-	msn_soap_message_send(session,
-		msn_soap_message_new(MSN_GROUP_DEL_SOAP_ACTION,
-			xmlnode_from_str(body, -1)),
-		MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL,
-		msn_group_read_cb, state);
+	state->body = xmlnode_from_str(body, -1);
+	state->post_action = MSN_GROUP_DEL_SOAP_ACTION;
+	state->post_url = MSN_ADDRESS_BOOK_POST_URL;
+	state->cb = msn_group_read_cb;
+	msn_contact_request(state);
 
 	g_free(body);
 }
@@ -1470,13 +1588,14 @@
 	gchar *body = NULL;
 	const gchar * guid;
 	MsnCallbackState *state;
+	char *escaped_group_name;
 
 	g_return_if_fail(session != NULL);
 	g_return_if_fail(session->userlist != NULL);
 	g_return_if_fail(old_group_name != NULL);
 	g_return_if_fail(new_group_name != NULL);
 
-	purple_debug_info("MSN CL", "Renaming group %s to %s.\n", old_group_name, new_group_name);
+	purple_debug_info("msn", "Renaming group %s to %s.\n", old_group_name, new_group_name);
 
 	guid = msn_userlist_find_group_id(session->userlist, old_group_name);
 	if (guid == NULL)
@@ -1487,20 +1606,22 @@
 	msn_callback_state_set_new_group_name(state, new_group_name);
 
 	if ( !strcmp(guid, MSN_INDIVIDUALS_GROUP_ID) || !strcmp(guid, MSN_NON_IM_GROUP_ID) ) {
-		msn_add_group(session, state, new_group_name);
-		// XXX move every buddy there (we probably need to fix concurrent SOAP reqs first)
+		MsnCallbackState *new_state = msn_callback_state_dup(state);
+		msn_add_group(session, new_state, new_group_name);
+		/* XXX move every buddy there (we probably need to fix concurrent SOAP reqs first) */
 	}
 
 	msn_callback_state_set_action(state, MSN_RENAME_GROUP);
 
-	body = g_markup_printf_escaped(MSN_GROUP_RENAME_TEMPLATE,
-		guid, new_group_name);
+	escaped_group_name = g_markup_escape_text(new_group_name, -1);
+	body = g_strdup_printf(MSN_GROUP_RENAME_TEMPLATE, guid, escaped_group_name);
 
-	msn_soap_message_send(session,
-		msn_soap_message_new(MSN_GROUP_RENAME_SOAP_ACTION,
-			xmlnode_from_str(body, -1)),
-		MSN_CONTACT_SERVER, MSN_ADDRESS_BOOK_POST_URL,
-		msn_group_read_cb, state);
+	state->body = xmlnode_from_str(body, -1);
+	state->post_action = MSN_GROUP_RENAME_SOAP_ACTION;
+	state->post_url = MSN_ADDRESS_BOOK_POST_URL;
+	state->cb = msn_group_read_cb;
+	msn_contact_request(state);
 
+	g_free(escaped_group_name);
 	g_free(body);
 }
--- a/libpurple/protocols/msn/contact.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/contact.h	Thu Nov 20 21:13:56 2008 +0000
@@ -25,25 +25,33 @@
 #ifndef _MSN_CONTACT_H_
 #define _MSN_CONTACT_H_
 
+#include "session.h"
+
+#define MSN_APPLICATION_ID "CFE80F9D-180F-4399-82AB-413F33A1FA11"
+
 #define MSN_CONTACT_SERVER	"contacts.msn.com"
 
 /* Get Contact List */
 
 #define MSN_GET_CONTACT_POST_URL	"/abservice/SharingService.asmx"
 #define MSN_GET_CONTACT_SOAP_ACTION "http://www.msn.com/webservices/AddressBook/FindMembership"
-#define MSN_GET_CONTACT_UPDATE_XML "<View>Full</View>"\
+
+#define MSN_GET_CONTACT_UPDATE_XML \
+	"<View>Full</View>"\
 	"<deltasOnly>true</deltasOnly>"\
 	"<lastChange>%s</lastChange>"
+
 #define MSN_GET_CONTACT_TEMPLATE	"<?xml version='1.0' encoding='utf-8'?>"\
 "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"\
 	"<soap:Header xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"\
 		"<ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
-			"<ApplicationId xmlns=\"http://www.msn.com/webservices/AddressBook\">09607671-1C32-421F-A6A6-CBFAA51AB5F4</ApplicationId>"\
+			"<ApplicationId xmlns=\"http://www.msn.com/webservices/AddressBook\">" MSN_APPLICATION_ID "</ApplicationId>"\
 			"<IsMigration xmlns=\"http://www.msn.com/webservices/AddressBook\">false</IsMigration>"\
 			"<PartnerScenario xmlns=\"http://www.msn.com/webservices/AddressBook\">%s</PartnerScenario>"\
 		 "</ABApplicationHeader>"\
 		"<ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
 			"<ManagedGroupRequest xmlns=\"http://www.msn.com/webservices/AddressBook\">false</ManagedGroupRequest>"\
+			"<TicketToken>%s</TicketToken>"\
 		"</ABAuthHeader>"\
 	"</soap:Header>"\
 	"<soap:Body xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"\
@@ -72,15 +80,20 @@
 #define MSN_ADD_ADDRESSBOOK_SOAP_ACTION     "http://www.msn.com/webservices/AddressBook/ABAdd"
 
 #define MSN_ADD_ADDRESSBOOK_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
-"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">"\
+"<soap:Envelope"\
+	" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""\
+	" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
+	" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\
+	" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">"\
 	"<soap:Header>"\
 		"<ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
-			"<ApplicationId>09607671-1C32-421F-A6A6-CBFAA51AB5F4</ApplicationId>"\
+			"<ApplicationId>" MSN_APPLICATION_ID "</ApplicationId>"\
 			"<IsMigration>false</IsMigration>"\
 			"<PartnerScenario>Initial</PartnerScenario>"\
 		"</ABApplicationHeader>"\
 		"<ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
 			"<ManagedGroupRequest>false</ManagedGroupRequest>"\
+			"<TicketToken>%s</TicketToken>"\
 		"</ABAuthHeader>"\
 	"</soap:Header>"\
 	"<soap:Body>"\
@@ -98,7 +111,8 @@
 /* Get AddressBook */
 #define MSN_GET_ADDRESS_SOAP_ACTION	"http://www.msn.com/webservices/AddressBook/ABFindAll"
 #define MSN_GET_ADDRESS_FULL_TIME	"0001-01-01T00:00:00.0000000-08:00"
-#define MSN_GET_ADDRESS_UPDATE_XML "<deltasOnly>true</deltasOnly>"\
+#define MSN_GET_ADDRESS_UPDATE_XML \
+	"<deltasOnly>true</deltasOnly>"\
 	"<lastChange>%s</lastChange>"
 
 #define MSN_GET_GLEAM_UPDATE_XML \
@@ -107,15 +121,20 @@
 	"<dynamicItemLastChange>%s</dynamicItemLastChange>"
 
 #define MSN_GET_ADDRESS_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
-"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">"\
+"<soap:Envelope"\
+	" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""\
+	" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
+	" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\
+	" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">"\
 	"<soap:Header>"\
 		"<ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
-			"<ApplicationId>09607671-1C32-421F-A6A6-CBFAA51AB5F4</ApplicationId>"\
+			"<ApplicationId>" MSN_APPLICATION_ID "</ApplicationId>"\
 			"<IsMigration>false</IsMigration>"\
 			"<PartnerScenario>%s</PartnerScenario>"\
 		"</ABApplicationHeader>"\
 		"<ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
 			"<ManagedGroupRequest>false</ManagedGroupRequest>"\
+			"<TicketToken>%s</TicketToken>"\
 		"</ABAuthHeader>"\
 	"</soap:Header>"\
 	"<soap:Body>"\
@@ -131,15 +150,20 @@
 /*Gleams SOAP request template*/
 #define MSN_GET_GLEAMS_SOAP_ACTION "http://www.msn.com/webservices/AddressBook/ABFindAll"
 #define MSN_GLEAMS_TEMPLATE "<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
-"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">"\
+"<soap:Envelope"\
+	" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""\
+	" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
+	" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\
+	" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">"\
 	"<soap:Header>"\
 		"<ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
-			"<ApplicationId>09607671-1C32-421F-A6A6-CBFAA51AB5F4</ApplicationId>"\
+			"<ApplicationId>" MSN_APPLICATION_ID "</ApplicationId>"\
 			"<IsMigration>false</IsMigration>"\
 			"<PartnerScenario>Initial</PartnerScenario>"\
 		"</ABApplicationHeader>"\
 		"<ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
 			"<ManagedGroupRequest>false</ManagedGroupRequest>"\
+			"<TicketToken>EMPTY</TicketToken>"\
 		"</ABAuthHeader>"\
 	"</soap:Header>"\
 	"<soap:Body>"\
@@ -157,34 +181,78 @@
  * Contact Management SOAP actions
  *******************************************************/
 
-/* Add a new contact t*/
+/* Add a new contact */
 #define MSN_CONTACT_ADD_SOAP_ACTION	"http://www.msn.com/webservices/AddressBook/ABContactAdd"
-#define MSN_CONTACT_LIVE_PENDING_XML	"<Contact xmlns=\"http://www.msn.com/webservices/AddressBook\"><contactInfo><contactType>LivePending</contactType><passportName>%s</passportName><isMessengerUser>true</isMessengerUser></contactInfo></Contact>"
+#define MSN_CONTACT_LIVE_PENDING_XML \
+	"<Contact xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+		"<contactInfo>"\
+			"<contactType>LivePending</contactType>"\
+			"<passportName>%s</passportName>"\
+			"<isMessengerUser>true</isMessengerUser>"\
+		"</contactInfo>"\
+	"</Contact>"
 
-#define MSN_CONTACT_XML	"<Contact xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
-				"<contactInfo>"\
-					"<passportName>%s</passportName>"\
-					"<isSmtp>false</isSmtp>"\
-					"<isMessengerUser>true</isMessengerUser>"\
-				"</contactInfo>"\
-			"</Contact>"
+#define MSN_CONTACT_XML	\
+	"<Contact xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+		"<contactInfo>"\
+			"<passportName>%s</passportName>"\
+			"<isSmtp>false</isSmtp>"\
+			"<isMessengerUser>true</isMessengerUser>"\
+		"</contactInfo>"\
+	"</Contact>"
 
-#define MSN_CONTACT_DISPLAYNAME_XML	"<Contact xmlns=\"http://www.msn.com/webservices/AddressBook\"><contactInfo><displayName>%s</displayName><passportName>%s</passportName><isMessengerUser>true</isMessengerUser></contactInfo></Contact>"
-
-#define MSN_ADD_CONTACT_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\"><soap:Header><ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\"><ApplicationId>09607671-1C32-421F-A6A6-CBFAA51AB5F4</ApplicationId><IsMigration>false</IsMigration><PartnerScenario>ContactSave</PartnerScenario></ABApplicationHeader><ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\"><ManagedGroupRequest>false</ManagedGroupRequest></ABAuthHeader></soap:Header><soap:Body><ABContactAdd xmlns=\"http://www.msn.com/webservices/AddressBook\"><abId>00000000-0000-0000-0000-000000000000</abId><contacts>%s</contacts><options><EnableAllowListManagement>true</EnableAllowListManagement></options></ABContactAdd></soap:Body></soap:Envelope>"
+#define MSN_CONTACT_DISPLAYNAME_XML	\
+	"<Contact xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+		"<contactInfo>"\
+			"<displayName>%s</displayName>"\
+			"<passportName>%s</passportName>"\
+			"<isMessengerUser>true</isMessengerUser>"\
+		"</contactInfo>"\
+	"</Contact>"
 
-/* Add a contact to a group */
-#define MSN_ADD_CONTACT_GROUP_SOAP_ACTION	"http://www.msn.com/webservices/AddressBook/ABGroupContactAdd"
-#define MSN_ADD_CONTACT_GROUP_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
-"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">"\
+#define MSN_ADD_CONTACT_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
+"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""\
+	" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
+	" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\
+	" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">"\
 	"<soap:Header>"\
 		"<ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
-			"<ApplicationId>09607671-1C32-421F-A6A6-CBFAA51AB5F4</ApplicationId>"\
+			"<ApplicationId>" MSN_APPLICATION_ID "</ApplicationId>"\
 			"<IsMigration>false</IsMigration>"\
 			"<PartnerScenario>ContactSave</PartnerScenario>"\
 		"</ABApplicationHeader>"\
 		"<ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
 			"<ManagedGroupRequest>false</ManagedGroupRequest>"\
+			"<TicketToken>EMPTY</TicketToken>"\
+		"</ABAuthHeader>"\
+	"</soap:Header>"\
+	"<soap:Body>"\
+		"<ABContactAdd xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+			"<abId>00000000-0000-0000-0000-000000000000</abId>"\
+			"<contacts>%s</contacts>"\
+			"<options>"\
+				"<EnableAllowListManagement>true</EnableAllowListManagement>"\
+			"</options>"\
+		"</ABContactAdd>"\
+	"</soap:Body>"\
+"</soap:Envelope>"
+
+/* Add a contact to a group */
+#define MSN_ADD_CONTACT_GROUP_SOAP_ACTION	"http://www.msn.com/webservices/AddressBook/ABGroupContactAdd"
+#define MSN_ADD_CONTACT_GROUP_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
+"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""\
+	" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
+	" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\
+	" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">"\
+	"<soap:Header>"\
+		"<ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+			"<ApplicationId>" MSN_APPLICATION_ID "</ApplicationId>"\
+			"<IsMigration>false</IsMigration>"\
+			"<PartnerScenario>ContactSave</PartnerScenario>"\
+		"</ABApplicationHeader>"\
+		"<ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+			"<ManagedGroupRequest>false</ManagedGroupRequest>"\
+			"<TicketToken>EMPTY</TicketToken>"\
 		"</ABAuthHeader>"\
 	"</soap:Header>"\
 	"<soap:Body>"\
@@ -207,25 +275,81 @@
 /* Delete a contact from the Contact List */
 #define MSN_CONTACT_DEL_SOAP_ACTION	"http://www.msn.com/webservices/AddressBook/ABContactDelete"
 #define MSN_CONTACT_ID_XML		"<Contact><contactId>%s</contactId></Contact>"
-#define MSN_DEL_CONTACT_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\"><soap:Header><ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\"><ApplicationId>09607671-1C32-421F-A6A6-CBFAA51AB5F4</ApplicationId><IsMigration>false</IsMigration><PartnerScenario>Timer</PartnerScenario></ABApplicationHeader><ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\"><ManagedGroupRequest>false</ManagedGroupRequest></ABAuthHeader></soap:Header><soap:Body><ABContactDelete xmlns=\"http://www.msn.com/webservices/AddressBook\"><abId>00000000-0000-0000-0000-000000000000</abId><contacts>%s</contacts></ABContactDelete></soap:Body></soap:Envelope>"
-
-/* Remove a contact from a group */
-#define MSN_CONTACT_DEL_GROUP_SOAP_ACTION	"http://www.msn.com/webservices/AddressBook/ABGroupContactDelete"
-#define MSN_CONTACT_DEL_GROUP_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\"><soap:Header><ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\"><ApplicationId>09607671-1C32-421F-A6A6-CBFAA51AB5F4</ApplicationId><IsMigration>false</IsMigration><PartnerScenario>Timer</PartnerScenario></ABApplicationHeader><ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\"><ManagedGroupRequest>false</ManagedGroupRequest></ABAuthHeader></soap:Header><soap:Body><ABGroupContactDelete xmlns=\"http://www.msn.com/webservices/AddressBook\"><abId>00000000-0000-0000-0000-000000000000</abId><contacts>%s</contacts><groupFilter><groupIds><guid>%s</guid></groupIds></groupFilter></ABGroupContactDelete></soap:Body></soap:Envelope>"
-
-
-/* Update Contact Nickname */
-#define MSN_CONTACT_UPDATE_SOAP_ACTION	"http://www.msn.com/webservices/AddressBook/ABContactUpdate"
-#define MSN_CONTACT_UPDATE_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
-"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">"\
+#define MSN_DEL_CONTACT_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
+"<soap:Envelope"\
+	" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""\
+	" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
+	" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\
+	" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">"\
 	"<soap:Header>"\
 		"<ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
-			"<ApplicationId>09607671-1C32-421F-A6A6-CBFAA51AB5F4</ApplicationId>"\
+			"<ApplicationId>" MSN_APPLICATION_ID "</ApplicationId>"\
 			"<IsMigration>false</IsMigration>"\
 			"<PartnerScenario>Timer</PartnerScenario>"\
 		"</ABApplicationHeader>"\
 		"<ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
 			"<ManagedGroupRequest>false</ManagedGroupRequest>"\
+			"<TicketToken>EMPTY</TicketToken>"\
+		"</ABAuthHeader>"\
+	"</soap:Header>"\
+	"<soap:Body>"\
+		"<ABContactDelete xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+			"<abId>00000000-0000-0000-0000-000000000000</abId>"\
+			"<contacts>%s</contacts>"\
+		"</ABContactDelete>"\
+	"</soap:Body>"\
+"</soap:Envelope>"
+
+/* Remove a contact from a group */
+#define MSN_CONTACT_DEL_GROUP_SOAP_ACTION	"http://www.msn.com/webservices/AddressBook/ABGroupContactDelete"
+#define MSN_CONTACT_DEL_GROUP_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
+"<soap:Envelope"\
+	" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""\
+	" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
+	" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\
+	" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">"\
+	"<soap:Header>"\
+		"<ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+			"<ApplicationId>" MSN_APPLICATION_ID "</ApplicationId>"\
+			"<IsMigration>false</IsMigration>"\
+			"<PartnerScenario>Timer</PartnerScenario>"\
+		"</ABApplicationHeader>"\
+		"<ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+			"<ManagedGroupRequest>false</ManagedGroupRequest>"\
+			"<TicketToken>EMPTY</TicketToken>"\
+		"</ABAuthHeader>"\
+	"</soap:Header>"\
+	"<soap:Body>"\
+		"<ABGroupContactDelete xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+			"<abId>00000000-0000-0000-0000-000000000000</abId>"\
+			"<contacts>%s</contacts>"\
+			"<groupFilter>"\
+				"<groupIds>"\
+					"<guid>%s</guid>"\
+				"</groupIds>"\
+			"</groupFilter>"\
+		"</ABGroupContactDelete>"\
+	"</soap:Body>"\
+"</soap:Envelope>"
+
+
+/* Update Contact Information */
+#define MSN_CONTACT_UPDATE_SOAP_ACTION	"http://www.msn.com/webservices/AddressBook/ABContactUpdate"
+#define MSN_CONTACT_UPDATE_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
+"<soap:Envelope"\
+	" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""\
+	" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
+	" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\
+	" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">"\
+	"<soap:Header>"\
+		"<ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+			"<ApplicationId>" MSN_APPLICATION_ID "</ApplicationId>"\
+			"<IsMigration>false</IsMigration>"\
+			"<PartnerScenario>Timer</PartnerScenario>"\
+		"</ABApplicationHeader>"\
+		"<ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+			"<ManagedGroupRequest>false</ManagedGroupRequest>"\
+			"<TicketToken>EMPTY</TicketToken>"\
 		"</ABAuthHeader>"\
 	"</soap:Header>"\
 	"<soap:Body>"\
@@ -233,18 +357,13 @@
 			"<abId>00000000-0000-0000-0000-000000000000</abId>"\
 			"<contacts>"\
 				"<Contact xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
-					"<contactInfo>"\
-						"<contactType>Me</contactType>"\
-						"<displayName>%s</displayName>"\
-					"</contactInfo>"\
-					"<propertiesChanged>DisplayName</propertiesChanged>"\
+					""\
 				"</Contact>"\
 			"</contacts>"\
 		"</ABContactUpdate>"\
 	"</soap:Body>"\
 "</soap:Envelope>"
 
-
 /*******************************************************
  * Add/Delete contact from lists SOAP actions
  *******************************************************/
@@ -255,30 +374,37 @@
 #define MSN_ADD_MEMBER_TO_LIST_SOAP_ACTION	"http://www.msn.com/webservices/AddressBook/AddMember"
 #define MSN_DELETE_MEMBER_FROM_LIST_SOAP_ACTION	"http://www.msn.com/webservices/AddressBook/DeleteMember"
 
-#define MSN_MEMBER_PASSPORT_XML	"<Member xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"PassportMember\">"\
-					"<Type>Passport</Type>"\
-					"<State>Accepted</State>"\
-					"<PassportName>%s</PassportName>"\
-				"</Member>"
+#define MSN_MEMBER_PASSPORT_XML	\
+	"<Member xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"PassportMember\">"\
+		"<Type>Passport</Type>"\
+		"<State>Accepted</State>"\
+		"<PassportName>%s</PassportName>"\
+	"</Member>"
 
-#define MSN_MEMBER_MEMBERSHIPID_XML	"<Member xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"PassportMember\">"\
-						"<Type>Passport</Type>"\
-						"<MembershipId>%u</MembershipId>"\
-						"<State>Accepted</State>"\
-					"</Member>"
+#define MSN_MEMBER_MEMBERSHIPID_XML	\
+	"<Member xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"PassportMember\">"\
+		"<Type>Passport</Type>"\
+		"<MembershipId>%u</MembershipId>"\
+		"<State>Accepted</State>"\
+	"</Member>"
 
 /* first delete contact from allow list */
 
 #define MSN_CONTACT_DELECT_FROM_LIST_TEMPLATE "<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
-"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">"\
+"<soap:Envelope"\
+	" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""\
+	" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
+	" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\
+	" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">"\
 	"<soap:Header>"\
 		"<ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
-			"<ApplicationId>09607671-1C32-421F-A6A6-CBFAA51AB5F4</ApplicationId>"\
+			"<ApplicationId>" MSN_APPLICATION_ID "</ApplicationId>"\
 			"<IsMigration>false</IsMigration>"\
 			"<PartnerScenario>%s</PartnerScenario>"\
 		"</ABApplicationHeader>"\
 		"<ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
 			"<ManagedGroupRequest>false</ManagedGroupRequest>"\
+			"<TicketToken>EMPTY</TicketToken>"\
 		"</ABAuthHeader>"\
 	"</soap:Header>"\
 	"<soap:Body>"\
@@ -301,15 +427,20 @@
 "</soap:Envelope>"
 
 #define MSN_CONTACT_ADD_TO_LIST_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
-"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">"\
+"<soap:Envelope"\
+	" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""\
+	" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
+	" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\
+	" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">"\
 	"<soap:Header>"\
 		"<ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
-			"<ApplicationId>09607671-1C32-421F-A6A6-CBFAA51AB5F4</ApplicationId>"\
+			"<ApplicationId>" MSN_APPLICATION_ID "</ApplicationId>"\
 			"<IsMigration>false</IsMigration>"\
 			"<PartnerScenario>%s</PartnerScenario>"\
 		"</ABApplicationHeader>"\
 		"<ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
 			"<ManagedGroupRequest>false</ManagedGroupRequest>"\
+			"<TicketToken>EMPTY</TicketToken>"\
 		"</ABAuthHeader>"\
 	"</soap:Header>"\
 	"<soap:Body>"\
@@ -339,36 +470,124 @@
 
 /* add a group */
 #define MSN_GROUP_ADD_SOAP_ACTION	"http://www.msn.com/webservices/AddressBook/ABGroupAdd"
-#define MSN_GROUP_ADD_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\"><soap:Header><ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\"><ApplicationId>09607671-1C32-421F-A6A6-CBFAA51AB5F4</ApplicationId><IsMigration>false</IsMigration><PartnerScenario>GroupSave</PartnerScenario></ABApplicationHeader><ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\"><ManagedGroupRequest>false</ManagedGroupRequest></ABAuthHeader></soap:Header><soap:Body><ABGroupAdd xmlns=\"http://www.msn.com/webservices/AddressBook\"><abId>00000000-0000-0000-0000-000000000000</abId><groupAddOptions><fRenameOnMsgrConflict>false</fRenameOnMsgrConflict></groupAddOptions><groupInfo><GroupInfo><name>%s</name><groupType>C8529CE2-6EAD-434d-881F-341E17DB3FF8</groupType><fMessenger>false</fMessenger><annotations><Annotation><Name>MSN.IM.Display</Name><Value>1</Value></Annotation></annotations></GroupInfo></groupInfo></ABGroupAdd></soap:Body></soap:Envelope>"
+#define MSN_GROUP_ADD_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
+"<soap:Envelope"\
+	" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""\
+	" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
+	" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\
+	" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">"\
+	"<soap:Header>"\
+		"<ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+			"<ApplicationId>" MSN_APPLICATION_ID "</ApplicationId>"\
+			"<IsMigration>false</IsMigration>"\
+			"<PartnerScenario>GroupSave</PartnerScenario>"\
+		"</ABApplicationHeader>"\
+		"<ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+			"<ManagedGroupRequest>false</ManagedGroupRequest>"\
+			"<TicketToken>EMPTY</TicketToken>"\
+		"</ABAuthHeader>"\
+	"</soap:Header>"\
+	"<soap:Body>"\
+		"<ABGroupAdd xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+			"<abId>00000000-0000-0000-0000-000000000000</abId>"\
+			"<groupAddOptions>"\
+				"<fRenameOnMsgrConflict>false</fRenameOnMsgrConflict>"\
+			"</groupAddOptions>"\
+			"<groupInfo>"\
+				"<GroupInfo>"\
+					"<name>%s</name>"\
+					"<groupType>C8529CE2-6EAD-434d-881F-341E17DB3FF8</groupType>"\
+					"<fMessenger>false</fMessenger>"\
+					"<annotations>"\
+						"<Annotation>"\
+							"<Name>MSN.IM.Display</Name>"\
+							"<Value>1</Value>"\
+						"</Annotation>"\
+					"</annotations>"\
+				"</GroupInfo>"\
+			"</groupInfo>"\
+		"</ABGroupAdd>"\
+	"</soap:Body>"\
+"</soap:Envelope>"
 
 /* delete a group */
 #define MSN_GROUP_DEL_SOAP_ACTION	"http://www.msn.com/webservices/AddressBook/ABGroupDelete"
-#define MSN_GROUP_DEL_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\"><soap:Header><ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\"><ApplicationId>09607671-1C32-421F-A6A6-CBFAA51AB5F4</ApplicationId><IsMigration>false</IsMigration><PartnerScenario>Timer</PartnerScenario></ABApplicationHeader><ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\"><ManagedGroupRequest>false</ManagedGroupRequest></ABAuthHeader></soap:Header><soap:Body><ABGroupDelete xmlns=\"http://www.msn.com/webservices/AddressBook\"><abId>00000000-0000-0000-0000-000000000000</abId><groupFilter><groupIds><guid>%s</guid></groupIds></groupFilter></ABGroupDelete></soap:Body></soap:Envelope>"
+#define MSN_GROUP_DEL_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
+"<soap:Envelope"\
+	" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""\
+	" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
+	" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\
+	" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">"\
+	"<soap:Header>"\
+		"<ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+			"<ApplicationId>" MSN_APPLICATION_ID "</ApplicationId>"\
+			"<IsMigration>false</IsMigration>"\
+			"<PartnerScenario>Timer</PartnerScenario>"\
+		"</ABApplicationHeader>"\
+		"<ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+			"<ManagedGroupRequest>false</ManagedGroupRequest>"\
+			"<TicketToken>EMPTY</TicketToken>"\
+		"</ABAuthHeader>"\
+	"</soap:Header>"\
+	"<soap:Body>"\
+		"<ABGroupDelete xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+			"<abId>00000000-0000-0000-0000-000000000000</abId>"\
+			"<groupFilter>"\
+				"<groupIds>"\
+					"<guid>%s</guid>"\
+				"</groupIds>"\
+			"</groupFilter>"\
+		"</ABGroupDelete>"\
+	"</soap:Body>"\
+"</soap:Envelope>"
 
 /* change a group's name */
 #define MSN_GROUP_RENAME_SOAP_ACTION	"http://www.msn.com/webservices/AddressBook/ABGroupUpdate"
-#define MSN_GROUP_RENAME_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\"><soap:Header><ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\"><ApplicationId>09607671-1C32-421F-A6A6-CBFAA51AB5F4</ApplicationId><IsMigration>false</IsMigration><PartnerScenario>Timer</PartnerScenario></ABApplicationHeader><ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\"><ManagedGroupRequest>false</ManagedGroupRequest></ABAuthHeader></soap:Header><soap:Body><ABGroupUpdate xmlns=\"http://www.msn.com/webservices/AddressBook\"><abId>00000000-0000-0000-0000-000000000000</abId><groups><Group><groupId>%s</groupId><groupInfo><name>%s</name></groupInfo><propertiesChanged>GroupName </propertiesChanged></Group></groups></ABGroupUpdate></soap:Body></soap:Envelope>"
+#define MSN_GROUP_RENAME_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
+"<soap:Envelope"\
+	" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""\
+	" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
+	" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\
+	" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">"\
+	"<soap:Header>"\
+		"<ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+			"<ApplicationId>" MSN_APPLICATION_ID "</ApplicationId>"\
+			"<IsMigration>false</IsMigration>"\
+			"<PartnerScenario>Timer</PartnerScenario>"\
+		"</ABApplicationHeader>"\
+		"<ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+			"<ManagedGroupRequest>false</ManagedGroupRequest>"\
+			"<TicketToken>EMPTY</TicketToken>"\
+		"</ABAuthHeader>"\
+	"</soap:Header>"\
+	"<soap:Body>"\
+		"<ABGroupUpdate xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+			"<abId>00000000-0000-0000-0000-000000000000</abId>"\
+			"<groups>"\
+				"<Group>"\
+					"<groupId>%s</groupId>"\
+					"<groupInfo>"\
+						"<name>%s</name>"\
+					"</groupInfo>"\
+					"<propertiesChanged>GroupName </propertiesChanged>"\
+				"</Group>"\
+			"</groups>"\
+		"</ABGroupUpdate>"\
+	"</soap:Body>"\
+"</soap:Envelope>"
 
 typedef enum
 {
-	MSN_ADD_BUDDY			= 0x01,
-	MSN_MOVE_BUDDY			= 0x02,
-	MSN_ACCEPTED_BUDDY		= 0x04,
-	MSN_DENIED_BUDDY		= 0x08,
-	MSN_ADD_GROUP			= 0x10,
-	MSN_DEL_GROUP			= 0x20,
-	MSN_RENAME_GROUP		= 0x40,
+	MSN_ADD_BUDDY       = 0x01,
+	MSN_MOVE_BUDDY      = 0x02,
+	MSN_ACCEPTED_BUDDY  = 0x04,
+	MSN_DENIED_BUDDY    = 0x08,
+	MSN_ADD_GROUP       = 0x10,
+	MSN_DEL_GROUP       = 0x20,
+	MSN_RENAME_GROUP    = 0x40,
+	MSN_UPDATE_INFO     = 0x80,
 } MsnCallbackAction;
 
-typedef struct _MsnContact MsnContact;
-
-struct _MsnContact
-{
-	MsnSession *session;
-
-	MsnSoapConn *soapconn;
-};
-
 typedef struct _MsnCallbackState MsnCallbackState;
 
 struct _MsnCallbackState
@@ -381,6 +600,11 @@
 	MsnListId list_id;
 	MsnCallbackAction action;
 	MsnSession *session;
+	xmlnode *body;
+	xmlnode *token;
+	const gchar *post_action;
+	const gchar *post_url;
+	/* MsnSoapCallback */ void *cb;
 };
 
 typedef enum
@@ -392,13 +616,18 @@
 	MSN_PS_BLOCK_UNBLOCK
 } MsnSoapPartnerScenario;
 
+typedef enum
+{
+	MSN_UPDATE_DISPLAY,	/* Real display name */
+	MSN_UPDATE_ALIAS,	/* Aliased display name */
+	MSN_UPDATE_COMMENT
+} MsnContactUpdateType;
+
 /************************************************
  * function prototype
  ************************************************/
-MsnContact * msn_contact_new(MsnSession *session);
-void msn_contact_destroy(MsnContact *contact);
-
 MsnCallbackState * msn_callback_state_new(MsnSession *session);
+MsnCallbackState * msn_callback_state_dup(MsnCallbackState *state);
 void msn_callback_state_free(MsnCallbackState *state);
 void msn_callback_state_set_who(MsnCallbackState *state, const gchar *who);
 void msn_callback_state_set_uid(MsnCallbackState *state, const gchar *uid);
@@ -411,24 +640,24 @@
 void msn_callback_state_set_action(MsnCallbackState *state,
 				   MsnCallbackAction action);
 
-void msn_contact_connect(MsnContact *contact);
-void msn_get_contact_list(MsnContact * contact,
+void msn_contact_connect(MsnSession *session);
+void msn_get_contact_list(MsnSession *session,
 			  const MsnSoapPartnerScenario partner_scenario,
 			  const char *update);
-void msn_get_address_book(MsnContact *contact,
+void msn_get_address_book(MsnSession *session,
 			  const MsnSoapPartnerScenario partner_scenario,
 			  const char * update, const char * gupdate);
 
 /* contact SOAP operations */
-void msn_update_contact(MsnContact *contact, const char* nickname);
+void msn_update_contact(MsnSession *session, const char *passport, MsnContactUpdateType type, const char* value);
 
-void msn_add_contact(MsnContact *contact, MsnCallbackState *state,
+void msn_add_contact(MsnSession *session, MsnCallbackState *state,
 		     const char *passport);
-void msn_delete_contact(MsnContact *contact, const char *contactId);
+void msn_delete_contact(MsnSession *session, const char *contactId);
 
-void msn_add_contact_to_group(MsnContact *contact, MsnCallbackState *state,
+void msn_add_contact_to_group(MsnSession *session, MsnCallbackState *state,
 			      const char *passport, const char *groupId);
-void msn_del_contact_from_group(MsnContact *contact, const char *passport,
+void msn_del_contact_from_group(MsnSession *session, const char *passport,
 				const char *group_name);
 /* group operations */
 void msn_add_group(MsnSession *session, MsnCallbackState *state,
@@ -438,12 +667,10 @@
 						   const char *new_group_name);
 
 /* lists operations */
-void msn_add_contact_to_list(MsnContact *contact, MsnCallbackState *state,
+void msn_add_contact_to_list(MsnSession *session, MsnCallbackState *state,
 			     const gchar *passport, const MsnListId list);
-void msn_del_contact_from_list(MsnContact *contact, MsnCallbackState *state,
+void msn_del_contact_from_list(MsnSession *session, MsnCallbackState *state,
 			       const gchar *passport, const MsnListId list);
 
-void msn_contact_connect_init(MsnSoapConn *soapconn);
-
 #endif /* _MSN_CONTACT_H_ */
 
--- a/libpurple/protocols/msn/group.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/group.h	Thu Nov 20 21:13:56 2008 +0000
@@ -30,16 +30,8 @@
 
 #include "session.h"
 #include "user.h"
-#include "soap.h"
 #include "userlist.h"
 
-#define MSN_ADD_GROUPS	"<GroupInfo><name>test111</name><groupType>C8529CE2-6EAD-434d-881F-341E17DB3FF8</groupType><fMessenger>false</fMessenger><annotations><Annotation><Name>MSN.IM.Display</Name><Value>1</Value></Annotation></annotations></GroupInfo>"
-
-#define MSN_ADD_GROUP_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\"><soap:Header><ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\"><ApplicationId>09607671-1C32-421F-A6A6-CBFAA51AB5F4</ApplicationId><IsMigration>false</IsMigration><PartnerScenario>GroupSave</PartnerScenario></ABApplicationHeader><ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\"><ManagedGroupRequest>false</ManagedGroupRequest></ABAuthHeader></soap:Header><soap:Body><ABGroupAdd xmlns=\"http://www.msn.com/webservices/AddressBook\"><abId>00000000-0000-0000-0000-000000000000</abId><groupAddOptions><fRenameOnMsgrConflict>false</fRenameOnMsgrConflict></groupAddOptions><groupInfo>%s</groupInfo></ABGroupAdd></soap:Body></soap:Envelope>"
-
-#define MSN_GROUP_IDS	"<guid>9e57e654-59f0-44d1-aedc-0a7500b7e51f</guid>"
-#define MSN_DELETE_GROUP_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\"><soap:Header><ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\"><ApplicationId>09607671-1C32-421F-A6A6-CBFAA51AB5F4</ApplicationId><IsMigration>false</IsMigration><PartnerScenario>Timer</PartnerScenario></ABApplicationHeader><ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\"><ManagedGroupRequest>false</ManagedGroupRequest></ABAuthHeader></soap:Header><soap:Body><ABGroupDelete xmlns=\"http://www.msn.com/webservices/AddressBook\"><abId>00000000-0000-0000-0000-000000000000</abId><groupFilter><groupIds>%s</groupIds></groupFilter></ABGroupDelete></soap:Body></soap:Envelope>"
-
 #define MSN_INDIVIDUALS_GROUP_ID	"1983"
 #define MSN_INDIVIDUALS_GROUP_NAME	"Other Contacts"
 
@@ -52,15 +44,14 @@
 struct _MsnGroup
 {
 	MsnSession *session;    /**< The MSN session.           */
-	MsnSoapConn *soapconn;
 
 	char *id;                 /**< The group ID.              */
 	char *name;             /**< The name of the group.     */
 };
 
-/**************************************************************************/
-/** @name Group API                                                       */
-/**************************************************************************/
+/**************************************************************************
+ ** @name Group API                                                       *
+ **************************************************************************/
 /*@{*/
 
 /**
@@ -114,4 +105,6 @@
  * @return The name.
  */
 const char *msn_group_get_name(const MsnGroup *group);
+
 #endif /* _MSN_GROUP_H_ */
+
--- a/libpurple/protocols/msn/httpconn.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/httpconn.c	Thu Nov 20 21:13:56 2008 +0000
@@ -381,6 +381,7 @@
 		else
 		{
 			msn_cmdproc_process_cmd_text(servconn->cmdproc, cur);
+			servconn->payload_len = servconn->cmdproc->last_cmd->payload_len;
 		}
 	} while (servconn->connected && servconn->rx_len > 0);
 
@@ -588,7 +589,8 @@
 
 	if (httpconn->virgin)
 	{
-		host = "gateway.messenger.hotmail.com";
+		/* QuLogic: This doesn't look right to me, but it still seems to work */
+		host = MSN_HTTPCONN_SERVER;
 
 		/* The first time servconn->host is the host we should connect to. */
 		params = g_strdup_printf("Action=open&Server=%s&IP=%s",
--- a/libpurple/protocols/msn/msn.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/msn.c	Thu Nov 20 21:13:56 2008 +0000
@@ -27,6 +27,7 @@
 
 #include "msn.h"
 #include "accountopt.h"
+#include "contact.h"
 #include "msg.h"
 #include "page.h"
 #include "pluginpref.h"
@@ -392,6 +393,32 @@
 						_("Cancel"), NULL);
 }
 
+/* QuLogic: Disabled until confirmed correct. */
+#if 0
+static void
+msn_show_blocked_text(PurplePluginAction *action)
+{
+	PurpleConnection *pc = (PurpleConnection *) action->context;
+	MsnSession *session;
+	char *title;
+
+	session = pc->proto_data;
+
+	title = g_strdup_printf(_("Blocked Text for %s"), session->account->username);
+	if (session->blocked_text == NULL) {
+		purple_notify_formatted(pc, title, title, NULL, _("No text is blocked for this account."), NULL, NULL);
+	} else {
+		char *blocked_text;
+		blocked_text = g_strdup_printf(_("MSN servers are currently blocking the following regular expressions:<br/>%s"),
+		                               session->blocked_text);
+		
+		purple_notify_formatted(pc, title, title, NULL, blocked_text, NULL, NULL);
+		g_free(blocked_text);
+	}
+	g_free(title);
+}
+#endif
+
 static void
 msn_show_hotmail_inbox(PurplePluginAction *action)
 {
@@ -401,14 +428,27 @@
 	gc = (PurpleConnection *) action->context;
 	session = gc->proto_data;
 
-	if (session->passport_info.file == NULL)
-	{
+	if (!session->passport_info.email_enabled) {
 		purple_notify_error(gc, NULL,
-						  _("This Hotmail account may not be active."), NULL);
+						  _("This account does not have email enabled."), NULL);
 		return;
 	}
 
-	purple_notify_uri(gc, session->passport_info.file);
+	/** apparently the correct value is 777, use 750 as a failsafe */ 
+	if ((session->passport_info.mail_url == NULL)
+		|| (time (NULL) - session->passport_info.mail_timestamp >= 750)) {
+		MsnTransaction *trans;
+		MsnCmdProc *cmdproc;
+
+		cmdproc = session->notification->cmdproc;
+
+		trans = msn_transaction_new(cmdproc, "URL", "%s", "INBOX");
+		msn_transaction_set_data(trans, GUINT_TO_POINTER(TRUE));
+
+		msn_cmdproc_send_trans(cmdproc, trans);
+
+	} else
+		purple_notify_uri(gc, session->passport_info.mail_url);
 }
 
 static void
@@ -543,17 +583,26 @@
 msn_can_receive_file(PurpleConnection *gc, const char *who)
 {
 	PurpleAccount *account;
-	char *normal;
+	gchar *normal;
 	gboolean ret;
 
 	account = purple_connection_get_account(gc);
 
 	normal = g_strdup(msn_normalize(account, purple_account_get_username(account)));
-
 	ret = strcmp(normal, msn_normalize(account, who));
-
 	g_free(normal);
 
+	if (ret) {
+		MsnSession *session = gc->proto_data;
+		if (session) {
+			MsnUser *user = msn_userlist_find_user(session->userlist, who);
+			if (user)
+				/* Include these too: MSN_CLIENT_CAP_MSNMOBILE|MSN_CLIENT_CAP_MSNDIRECT ? */
+				ret = (user->clientid & MSN_CLIENT_CAP_WEBMSGR) == 0;
+		} else
+			ret = FALSE;
+	}
+
 	return ret;
 }
 
@@ -567,6 +616,30 @@
 	return "msn";
 }
 
+static const char *
+msn_list_emblems(PurpleBuddy *b)
+{
+	MsnUser *user = b->proto_data;
+
+	if (user != NULL) {
+		if (user->clientid & MSN_CLIENT_CAP_BOT)
+			return "bot";
+		if (user->clientid & MSN_CLIENT_CAP_WIN_MOBILE)
+			return "mobile";
+#if 0
+		/* XXX: Since we don't support this, there's no point in showing it just yet */
+		if (user->clientid & MSN_CLIENT_CAP_SCHANNEL)
+			return "secure";
+#endif
+		if (user->clientid & MSN_CLIENT_CAP_WEBMSGR)
+			return "external";
+		if (user->networkid == MSN_NETWORK_YAHOO)
+			return "yahoo";
+	}
+
+	return NULL;
+}
+
 /*
  * Set the User status text
  */
@@ -773,10 +846,6 @@
 static GList *
 msn_actions(PurplePlugin *plugin, gpointer context)
 {
-	PurpleConnection *gc = (PurpleConnection *)context;
-	PurpleAccount *account;
-	const char *user;
-
 	GList *m = NULL;
 	PurplePluginAction *act;
 
@@ -808,17 +877,18 @@
 			msn_show_set_mobile_pages);
 	m = g_list_append(m, act);
 
-	account = purple_connection_get_account(gc);
-	user = msn_normalize(account, purple_account_get_username(account));
-
-	if ((strstr(user, "@hotmail.") != NULL) ||
-		(strstr(user, "@msn.com") != NULL))
-	{
-		m = g_list_append(m, NULL);
-		act = purple_plugin_action_new(_("Open Hotmail Inbox"),
-				msn_show_hotmail_inbox);
-		m = g_list_append(m, act);
-	}
+/* QuLogic: Disabled until confirmed correct. */
+#if 0
+	m = g_list_append(m, NULL);
+	act = purple_plugin_action_new(_("View Blocked Text..."),
+			msn_show_blocked_text);
+	m = g_list_append(m, act);
+#endif
+
+	m = g_list_append(m, NULL);
+	act = purple_plugin_action_new(_("Open Hotmail Inbox"),
+			msn_show_hotmail_inbox);
+	m = g_list_append(m, act);
 
 	return m;
 }
@@ -1042,15 +1112,20 @@
 {
 	PurpleAccount *account;
 	PurpleBuddy *buddy = purple_find_buddy(gc->account, who);
+	MsnSession *session;
+	MsnSwitchBoard *swboard;
 	MsnMessage *msg;
 	char *msgformat;
 	char *msgtext;
 	const char *username;
 
-	purple_debug_info("MSNP14","send IM {%s} to %s\n",message,who);
+	purple_debug_info("msn", "send IM {%s} to %s\n",message,who);
 	account = purple_connection_get_account(gc);
 	username = purple_account_get_username(account);
 
+	session = gc->proto_data;
+	swboard = msn_session_find_swboard(session, who);
+
 	if (buddy) {
 		PurplePresence *p = purple_buddy_get_presence(buddy);
 		if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_MOBILE)) {
@@ -1062,10 +1137,12 @@
 	}
 
 	msn_import_html(message, &msgformat, &msgtext);
-	if(msn_user_is_online(account, who)||
-		msn_user_is_yahoo(account, who)){
-		/*User online,then send Online Instant Message*/
-
+	if (msn_user_is_online(account, who)||
+		msn_user_is_yahoo(account, who) ||
+		swboard != NULL){
+		/*User online or have a swboard open because it's invisible
+		 * and sent us a message,then send Online Instant Message*/
+ 
 		if (strlen(msgtext) + strlen(msgformat) + strlen(VERSION) > 1564)
 		{
 			g_free(msgformat);
@@ -1081,22 +1158,19 @@
 		g_free(msgformat);
 		g_free(msgtext);
 
-		purple_debug_info("MSNP14","prepare to send online Message\n");
+		purple_debug_info("msn", "prepare to send online Message\n");
 		if (g_ascii_strcasecmp(who, username))
 		{
-			MsnSession *session;
-			MsnSwitchBoard *swboard;
 			MsnEmoticon *smile;
 			GSList *smileys;
 			GString *emoticons = NULL;
 
-			session = gc->proto_data;
 			if(msn_user_is_yahoo(account,who)){
 				/*we send the online and offline Message to Yahoo User via UBM*/
-				purple_debug_info("MSNP14","send to Yahoo User\n");
+				purple_debug_info("msn", "send to Yahoo User\n");
 				uum_send_msg(session,msg);
 			}else{
-				purple_debug_info("MSNP14","send via switchboard\n");
+				purple_debug_info("msn", "send via switchboard\n");
 				swboard = msn_session_get_swboard(session, who, MSN_SB_FLAG_IM);
 				smileys = msn_msg_grab_emoticons(message, username);
 				while (smileys) {
@@ -1145,19 +1219,20 @@
 		}
 
 		msn_message_destroy(msg);
-	}else	{
+	} else {
 		/*send Offline Instant Message,only to MSN Passport User*/
-		MsnSession *session;
 		char *friendname;
 
-		purple_debug_info("MSNP14","prepare to send offline Message\n");
-		session = gc->proto_data;
+		purple_debug_info("msn", "prepare to send offline Message\n");
 
 		friendname = msn_encode_mime(account->username);
 		msn_oim_prep_send_msg_info(session->oim,
 			purple_account_get_username(account),
-			friendname, who,	message);
+			friendname, who, msgtext);
 		msn_oim_send_msg(session->oim);
+
+		g_free(msgformat);
+		g_free(msgtext);
 		g_free(friendname);
 	}
 
@@ -1279,6 +1354,7 @@
 
 	if (group_id >= 0)
 	{
+		/* This is wrong... user->group_ids contains g_strdup()'d data now */
 		user->group_ids = g_list_append(user->group_ids,
 										GINT_TO_POINTER(group_id));
 	}
@@ -1298,7 +1374,7 @@
 	userlist = session->userlist;
 	who = msn_normalize(gc->account, buddy->name);
 
-	purple_debug_info("MSN","Add user:%s to group:%s\n", who, (group && group->name) ? group->name : "(null)");
+	purple_debug_info("msn", "Add user:%s to group:%s\n", who, (group && group->name) ? group->name : "(null)");
 	if (!session->logged_in)
 	{
 #if 0
@@ -1369,10 +1445,10 @@
 		msn_userlist_rem_buddy_from_list(userlist, who, MSN_LIST_BL);
 
 		/* delete contact from Block list and add it to Allow in the callback */
-		msn_del_contact_from_list(session->contact, NULL, who, MSN_LIST_BL);
+		msn_del_contact_from_list(session, NULL, who, MSN_LIST_BL);
 	} else {
 		/* just add the contact to Allow list */
-		msn_add_contact_to_list(session->contact, NULL, who, MSN_LIST_AL);
+		msn_add_contact_to_list(session, NULL, who, MSN_LIST_AL);
 	}
 
 
@@ -1397,10 +1473,10 @@
 		msn_userlist_rem_buddy_from_list(userlist, who, MSN_LIST_AL);
 
 		/* delete contact from Allow list and add it to Block in the callback */
-		msn_del_contact_from_list(session->contact, NULL, who, MSN_LIST_AL);
+		msn_del_contact_from_list(session, NULL, who, MSN_LIST_AL);
 	} else {
 		/* just add the contact to Block list */
-		msn_add_contact_to_list(session->contact, NULL, who, MSN_LIST_BL);
+		msn_add_contact_to_list(session, NULL, who, MSN_LIST_BL);
 	}
 
 	msn_userlist_add_buddy_to_list(userlist, who, MSN_LIST_BL);
@@ -1423,7 +1499,7 @@
 
 	msn_userlist_rem_buddy_from_list(userlist, who, MSN_LIST_AL);
 
-	msn_del_contact_from_list(session->contact, NULL, who, MSN_LIST_AL);
+	msn_del_contact_from_list(session, NULL, who, MSN_LIST_AL);
 
 	if (user != NULL && user->list_op & MSN_LIST_RL_OP)
 		msn_userlist_add_buddy_to_list(userlist, who, MSN_LIST_BL);
@@ -1446,7 +1522,7 @@
 
 	msn_userlist_rem_buddy_from_list(userlist, who, MSN_LIST_BL);
 
-	msn_del_contact_from_list(session->contact, NULL, who, MSN_LIST_BL);
+	msn_del_contact_from_list(session, NULL, who, MSN_LIST_BL);
 
 	if (user != NULL && user->list_op & MSN_LIST_RL_OP)
 		msn_userlist_add_buddy_to_list(userlist, who, MSN_LIST_AL);
@@ -1574,6 +1650,15 @@
 	}
 }
 
+static void msn_alias_buddy(PurpleConnection *pc, const char *name, const char *alias)
+{
+	MsnSession *session;
+
+	session = pc->proto_data;
+
+	msn_update_contact(session, name, MSN_UPDATE_ALIAS, alias);
+}
+
 static void
 msn_group_buddy(PurpleConnection *gc, const char *who,
 				const char *old_group_name, const char *new_group_name)
@@ -1669,12 +1754,12 @@
 	session = gc->proto_data;
 	cmdproc = session->notification->cmdproc;
 
-	purple_debug_info("MSN", "Remove group %s\n", group->name);
+	purple_debug_info("msn", "Remove group %s\n", group->name);
 	/*we can't delete the default group*/
 	if(!strcmp(group->name, MSN_INDIVIDUALS_GROUP_NAME)||
 		!strcmp(group->name, MSN_NON_IM_GROUP_NAME))
 	{
-		purple_debug_info("MSN", "This group can't be removed, returning.\n");
+		purple_debug_info("msn", "This group can't be removed, returning.\n");
 		return ;
 	}
 
@@ -1739,7 +1824,7 @@
 }
 
 static void msn_got_photo(PurpleUtilFetchUrlData *url_data, gpointer data,
-		const gchar *url_text, size_t len, const gchar *error_message);
+		const gchar *url_text, gsize len, const gchar *error_message);
 
 #endif
 
@@ -2174,7 +2259,7 @@
 #if PHOTO_SUPPORT
 	/* Find the URL to the photo; must be before the marshalling [Bug 994207] */
 	photo_url_text = msn_get_photo_url(url_text);
-	purple_debug_info("MSNP14","photo url:{%s}\n", photo_url_text ? photo_url_text : "(null)");
+	purple_debug_info("msn", "photo url:{%s}\n", photo_url_text ? photo_url_text : "(null)");
 
 	/* Marshall the existing state */
 	info2_data = g_new0(MsnGetInfoStepTwoData, 1);
@@ -2375,7 +2460,7 @@
 	NULL,					/* protocol_options */
 	{"png", 0, 0, 96, 96, 0, PURPLE_ICON_SCALE_SEND},	/* icon_spec */
 	msn_list_icon,			/* list_icon */
-	NULL,				/* list_emblems */
+	msn_list_emblems,		/* list_emblems */
 	msn_status_text,		/* status_text */
 	msn_tooltip_text,		/* tooltip_text */
 	msn_status_types,		/* away_states */
@@ -2411,7 +2496,7 @@
 	NULL,					/* register_user */
 	NULL,					/* get_cb_info */
 	NULL,					/* get_cb_away */
-	NULL,					/* alias_buddy */
+	msn_alias_buddy,		/* alias_buddy */
 	msn_group_buddy,		/* group_buddy */
 	msn_rename_group,		/* rename_group */
 	NULL,					/* buddy_free */
@@ -2484,11 +2569,11 @@
 	PurpleAccountOption *option;
 
 	option = purple_account_option_string_new(_("Server"), "server",
-											WLM_SERVER);
+											MSN_SERVER);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
 											   option);
 
-	option = purple_account_option_int_new(_("Port"), "port", WLM_PORT);
+	option = purple_account_option_int_new(_("Port"), "port", MSN_PORT);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
 											   option);
 
--- a/libpurple/protocols/msn/msn.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/msn.h	Thu Nov 20 21:13:56 2008 +0000
@@ -57,35 +57,25 @@
 
 #define MSN_BUF_LEN 8192
 
-#define USEROPT_MSNSERVER 3
+/* Windows Live Messenger Server*/
 #define MSN_SERVER "messenger.hotmail.com"
 #define MSN_HTTPCONN_SERVER "gateway.messenger.hotmail.com"
-#define USEROPT_MSNPORT 4
 #define MSN_PORT 1863
+#define WLM_PROT_VER		15
 
-/* Windows Live Messenger Server*/
-#define WLM_SERVER			"muser.messenger.hotmail.com"
-#define WLM_PORT			1863
-#define WLM_PROT_VER		13
-/*This MSNP14 Support chat with Yahoo Messenger*/
-#define WLM_YAHOO_PROT_VER	14
-
-#define WLM_MAX_PROTOCOL	14
-#define WLM_MIN_PROTOCOL	13
+#define WLM_MAX_PROTOCOL	15
+#define WLM_MIN_PROTOCOL	15
 
 #define MSN_TYPING_RECV_TIMEOUT 6
 #define MSN_TYPING_SEND_TIMEOUT	4
 
-#define HOTMAIL_URL "http://www.hotmail.com/cgi-bin/folders"w3
-#define PASSPORT_URL "http://lc1.law13.hotmail.passport.com/cgi-bin/dologin?login="
 #define PROFILE_URL "http://spaces.live.com/profile.aspx?mem="
 #define PHOTO_URL	" contactparams:photopreauthurl=\""
 
-#define USEROPT_HOTMAIL 0
-
 #define BUDDY_ALIAS_MAXLEN 387
 
-#define MSN_FT_GUID "{5D3E02AB-6190-11d3-BBBB-00C04F795683}"
+#define MSN_FT_GUID "5D3E02AB-6190-11D3-BBBB-00C04F795683"
+#define MSN_OBJ_GUID "A4268EEC-FEC5-49E5-95C3-F126696BDBF6"
 
 #define MSN_CLIENTINFO \
 	"Client-Name: Purple/" VERSION "\r\n" \
@@ -107,18 +97,25 @@
 
 typedef enum
 {
-	MSN_CLIENT_CAP_WIN_MOBILE = 0x00001,
-	MSN_CLIENT_CAP_UNKNOWN_1  = 0x00002,
-	MSN_CLIENT_CAP_INK_GIF    = 0x00004,
-	MSN_CLIENT_CAP_INK_ISF    = 0x00008,
-	MSN_CLIENT_CAP_VIDEO_CHAT = 0x00010,
-	MSN_CLIENT_CAP_BASE       = 0x00020,
-	MSN_CLIENT_CAP_MSNMOBILE  = 0x00040,
-	MSN_CLIENT_CAP_MSNDIRECT  = 0x00080,
-	MSN_CLIENT_CAP_WEBMSGR    = 0x00100,
-	MSN_CLIENT_CAP_DIRECTIM   = 0x04000,
-	MSN_CLIENT_CAP_WINKS      = 0x08000,
-	MSN_CLIENT_CAP_SEARCH     = 0x10000
+	MSN_CLIENT_CAP_WIN_MOBILE = 0x000001,
+	MSN_CLIENT_CAP_INK_GIF    = 0x000004,
+	MSN_CLIENT_CAP_INK_ISF    = 0x000008,
+	MSN_CLIENT_CAP_VIDEO_CHAT = 0x000010,
+	MSN_CLIENT_CAP_PACKET     = 0x000020,
+	MSN_CLIENT_CAP_MSNMOBILE  = 0x000040,
+	MSN_CLIENT_CAP_MSNDIRECT  = 0x000080,
+	MSN_CLIENT_CAP_WEBMSGR    = 0x000200,
+	MSN_CLIENT_CAP_TGW        = 0x000800,
+	MSN_CLIENT_CAP_SPACE      = 0x001000,
+	MSN_CLIENT_CAP_MCE        = 0x002000,
+	MSN_CLIENT_CAP_DIRECTIM   = 0x004000,
+	MSN_CLIENT_CAP_WINKS      = 0x008000,
+	MSN_CLIENT_CAP_SEARCH     = 0x010000,
+	MSN_CLIENT_CAP_BOT        = 0x020000,
+	MSN_CLIENT_CAP_VOICEIM    = 0x040000,
+	MSN_CLIENT_CAP_SCHANNEL   = 0x080000,
+	MSN_CLIENT_CAP_SIP_INVITE = 0x100000,
+	MSN_CLIENT_CAP_SDRIVE     = 0x400000
 
 } MsnClientCaps;
 
@@ -129,19 +126,18 @@
 	MSN_CLIENT_VER_6_1 = 0x20,	/* MSNC2 */
 	MSN_CLIENT_VER_6_2 = 0x30,	/* MSNC3 */
 	MSN_CLIENT_VER_7_0 = 0x40,	/* MSNC4 */
-	MSN_CLIENT_VER_7_5 = 0x50	/* MSNC5 */
+	MSN_CLIENT_VER_7_5 = 0x50,	/* MSNC5 */
+	MSN_CLIENT_VER_8_0 = 0x60,	/* MSNC6 */
+	MSN_CLIENT_VER_8_1 = 0x70,	/* MSNC7 */
+	MSN_CLIENT_VER_8_5 = 0x80	/* MSNC8 */
 
 } MsnClientVerId;
 
 #define MSN_CLIENT_ID_VERSION      MSN_CLIENT_VER_7_0
-#define MSN_CLIENT_ID_RESERVED_1   0x00
-#define MSN_CLIENT_ID_RESERVED_2   0x00
-#define MSN_CLIENT_ID_CAPABILITIES MSN_CLIENT_CAP_BASE
+#define MSN_CLIENT_ID_CAPABILITIES MSN_CLIENT_CAP_PACKET
 
 #define MSN_CLIENT_ID \
 	((MSN_CLIENT_ID_VERSION    << 24) | \
-	 (MSN_CLIENT_ID_RESERVED_1 << 16) | \
-	 (MSN_CLIENT_ID_RESERVED_2 <<  8) | \
 	 (MSN_CLIENT_ID_CAPABILITIES))
 
 void msn_act_id(PurpleConnection *gc, const char *entry);
--- a/libpurple/protocols/msn/msnutils.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/msnutils.c	Thu Nov 20 21:13:56 2008 +0000
@@ -23,8 +23,8 @@
  */
 #include "msn.h"
 #include "msnutils.h"
-#include "time.h"
-//#include <openssl/md5.h>
+
+#include "cipher.h"
 
 char *rand_guid(void);
 
@@ -465,132 +465,118 @@
 
 	host = g_strdup(str);
 
-	if ((c = strchr(host, ':')) != NULL){
+	if ((c = strchr(host, ':')) != NULL) {
 		*c = '\0';
 		port = atoi(c + 1);
-	}else{
+	} else {
 		port = 1863;
 	}
 
 	*ret_host = host;
 	*ret_port = port;
 }
-/***************************************************************************
- * MSN Time Related Funciton
- ***************************************************************************/
-#if 0
-int
-msn_convert_iso8601(const char *timestr,struct tm tm_time)
-{
-	char temp[64];
-	struct tm ctime;
-	time_t ts;
-
-	purple_debug_info("MSNP14","convert string is{%s}\n",timestr);
-	tzset();
-	/*copy string first*/
-	memset(temp, 0, sizeof(temp));
-	strncpy(temp, timestr, strlen(timestr));
-
-	/*convert via strptime()*/
-	memset(&ctime, 0, sizeof(struct tm));
-	strptime(temp, "%d %b %Y %T %Z", &ctime);
-	ts = mktime(&ctime) - timezone;
-	localtime_r(&ts, tm_time);
-}
-#endif
 
 /***************************************************************************
  * MSN Challenge Computing Function
  ***************************************************************************/
 
 /*
- * Handle MSN Chanllege computation
- *This algorithm reference with http://msnpiki.msnfanatic.com/index.php/MSNP11:Challenges
+ * Handle MSN Challenge computation
+ * This algorithm references
+ *  http://imfreedom.org/wiki/index.php/MSN:NS/Challenges
  */
 #define BUFSIZE	256
 void
 msn_handle_chl(char *input, char *output)
 {
-		PurpleCipher *cipher;
-		PurpleCipherContext *context;
-		char *productKey = MSNP13_WLM_PRODUCT_KEY,
-			 *productID  = MSNP13_WLM_PRODUCT_ID,
-			 *hexChars   = "0123456789abcdef",
-			 buf[BUFSIZE];
-		unsigned char md5Hash[16], *newHash;
-		unsigned int *md5Parts, *chlStringParts, newHashParts[5];
+	PurpleCipher *cipher;
+	PurpleCipherContext *context;
+	const guchar productKey[] = MSNP15_WLM_PRODUCT_KEY;
+	const guchar productID[]  = MSNP15_WLM_PRODUCT_ID;
+	const char hexChars[]     = "0123456789abcdef";
+	char buf[BUFSIZE];
+	unsigned char md5Hash[16];
+	unsigned char *newHash;
+	unsigned int *md5Parts;
+	unsigned int *chlStringParts;
+	unsigned int newHashParts[5];
 
-		long long nHigh=0, nLow=0;
-
-		int i;
+	long long nHigh = 0, nLow = 0;
 
-		/* Create the MD5 hash by using Purple MD5 algorithm*/
-		cipher = purple_ciphers_find_cipher("md5");
-		context = purple_cipher_context_new(cipher, NULL);
+	int len;
+	int i;
 
-		purple_cipher_context_append(context, (const guchar *)input,
-						strlen(input));
-		purple_cipher_context_append(context, (const guchar *)productKey,
-						strlen(productKey));
-		purple_cipher_context_digest(context, sizeof(md5Hash), md5Hash, NULL);
-		purple_cipher_context_destroy(context);
+	/* Create the MD5 hash by using Purple MD5 algorithm */
+	cipher = purple_ciphers_find_cipher("md5");
+	context = purple_cipher_context_new(cipher, NULL);
+
+	purple_cipher_context_append(context, (guchar *)input, strlen(input));
+	purple_cipher_context_append(context, productKey, sizeof(productKey) - 1);
+	purple_cipher_context_digest(context, sizeof(md5Hash), md5Hash, NULL);
+	purple_cipher_context_destroy(context);
 
-		/* Split it into four integers */
-		md5Parts = (unsigned int *)md5Hash;
-		for(i=0; i<4; i++){
-				/* adjust endianess */
-				md5Parts[i] = GUINT_TO_LE(md5Parts[i]);
+	/* Split it into four integers */
+	md5Parts = (unsigned int *)md5Hash;
+	for (i = 0; i < 4; i++) {
+		/* adjust endianess */
+		md5Parts[i] = GUINT_TO_LE(md5Parts[i]);
 
-				/* & each integer with 0x7FFFFFFF          */
-				/* and save one unmodified array for later */
-				newHashParts[i] = md5Parts[i];
-				md5Parts[i] &= 0x7FFFFFFF;
-		}
+		/* & each integer with 0x7FFFFFFF          */
+		/* and save one unmodified array for later */
+		newHashParts[i] = md5Parts[i];
+		md5Parts[i] &= 0x7FFFFFFF;
+	}
 
-		/* make a new string and pad with '0' */
-		snprintf(buf, BUFSIZE-5, "%s%s", input, productID);
-		i = strlen(buf);
-		memset(&buf[i], '0', 8 - (i % 8));
-		buf[i + (8 - (i % 8))]='\0';
-
-		/* split into integers */
-		chlStringParts = (unsigned int *)buf;
+	/* make a new string and pad with '0' to length that's a multiple of 8 */
+	snprintf(buf, BUFSIZE - 5, "%s%s", input, productID);
+	len = strlen(buf);
+	if ((len % 8) != 0) {
+		int fix = 8 - (len % 8);
+		memset(&buf[len], '0', fix);
+		buf[len + fix] = '\0';
+		len += fix;
+	}
 
-		/* this is magic */
-		for (i=0; i<(strlen(buf)/4)-1; i+=2){
-				long long temp;
+	/* split into integers */
+	chlStringParts = (unsigned int *)buf;
 
-				chlStringParts[i]   = GUINT_TO_LE(chlStringParts[i]);
-				chlStringParts[i+1] = GUINT_TO_LE(chlStringParts[i+1]);
+	/* this is magic */
+	for (i = 0; i < (strlen(buf) / 4); i += 2) {
+		long long temp;
 
-				temp=(md5Parts[0] * (((0x0E79A9C1 * (long long)chlStringParts[i]) % 0x7FFFFFFF)+nHigh) + md5Parts[1])%0x7FFFFFFF;
-				nHigh=(md5Parts[2] * (((long long)chlStringParts[i+1]+temp) % 0x7FFFFFFF) + md5Parts[3]) % 0x7FFFFFFF;
-				nLow=nLow + nHigh + temp;
-		}
-		nHigh=(nHigh+md5Parts[1]) % 0x7FFFFFFF;
-		nLow=(nLow+md5Parts[3]) % 0x7FFFFFFF;
+		chlStringParts[i] = GUINT_TO_LE(chlStringParts[i]);
+		chlStringParts[i + 1] = GUINT_TO_LE(chlStringParts[i + 1]);
+
+		temp = (0x0E79A9C1 * (long long)chlStringParts[i]) % 0x7FFFFFFF;
+		temp = (md5Parts[0] * (temp + nLow) + md5Parts[1]) % 0x7FFFFFFF;
+		nHigh += temp;
 
-		newHashParts[0]^=nHigh;
-		newHashParts[1]^=nLow;
-		newHashParts[2]^=nHigh;
-		newHashParts[3]^=nLow;
+		temp = ((long long)chlStringParts[i + 1] + temp) % 0x7FFFFFFF;
+		nLow = (md5Parts[2] * temp + md5Parts[3]) % 0x7FFFFFFF;
+		nHigh += nLow;
+	}
+	nLow = (nLow + md5Parts[1]) % 0x7FFFFFFF;
+	nHigh = (nHigh + md5Parts[3]) % 0x7FFFFFFF;
 
-		/* adjust endianness */
-		for(i=0; i<4; i++)
-				newHashParts[i] = GUINT_TO_LE(newHashParts[i]);
-
-		/* make a string of the parts */
-		newHash = (unsigned char *)newHashParts;
+	newHashParts[0] ^= nLow;
+	newHashParts[1] ^= nHigh;
+	newHashParts[2] ^= nLow;
+	newHashParts[3] ^= nHigh;
 
-		/* convert to hexadecimal */
-		for (i=0; i<16; i++)
-		{
-				output[i*2]=hexChars[(newHash[i]>>4)&0xF];
-				output[(i*2)+1]=hexChars[newHash[i]&0xF];
-		}
+	/* adjust endianness */
+	for(i = 0; i < 4; i++)
+		newHashParts[i] = GUINT_TO_LE(newHashParts[i]);
+
+	/* make a string of the parts */
+	newHash = (unsigned char *)newHashParts;
 
-		output[32]='\0';
+	/* convert to hexadecimal */
+	for (i = 0; i < 16; i++)
+	{
+		output[i * 2] = hexChars[(newHash[i] >> 4) & 0xF];
+		output[(i * 2) + 1] = hexChars[newHash[i] & 0xF];
+	}
 
-//		purple_debug_info("MSNP14","chl output{%s}\n",output);
+	output[32] = '\0';
 }
+
--- a/libpurple/protocols/msn/nexus.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/nexus.c	Thu Nov 20 21:13:56 2008 +0000
@@ -22,11 +22,27 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
-#include "soap2.h"
+#include "soap.h"
 #include "nexus.h"
 #include "notification.h"
 
-#undef NEXUS_LOGIN_TWN
+/**************************************************************************
+ * Valid Ticket Tokens
+ **************************************************************************/
+
+#define SSO_VALID_TICKET_DOMAIN 0
+#define SSO_VALID_TICKET_POLICY 1
+static char *ticket_domains[][2] = {
+	/* http://msnpiki.msnfanatic.com/index.php/MSNP15:SSO */
+	/* {"Domain", "Policy Ref URI"}, Purpose */
+	{"messengerclear.live.com", NULL},       /* Authentication for messenger. */
+	{"messenger.msn.com", "?id=507"},        /* Authentication for receiving OIMs. */
+	{"contacts.msn.com", "MBI"},             /* Authentication for the Contact server. */
+	{"messengersecure.live.com", "MBI_SSL"}, /* Authentication for sending OIMs. */
+	{"spaces.live.com", "MBI"},              /* Authentication for the Windows Live Spaces */
+	{"livecontacts.live.com", "MBI"},        /* Live Contacts API, a simplified version of the Contacts SOAP service */
+	{"storage.live.com", "MBI"},             /* Storage REST API */
+};
 
 /**************************************************************************
  * Main
@@ -36,12 +52,17 @@
 msn_nexus_new(MsnSession *session)
 {
 	MsnNexus *nexus;
+	int i;
 
 	nexus = g_new0(MsnNexus, 1);
 	nexus->session = session;
 
-	nexus->challenge_data = g_hash_table_new_full(g_str_hash,
-		g_str_equal, g_free, g_free);
+	nexus->token_len = sizeof(ticket_domains) / sizeof(char *[2]);
+	nexus->tokens = g_new0(MsnTicketToken, nexus->token_len);
+
+	for (i = 0; i < nexus->token_len; i++)
+		nexus->tokens[i].token = g_hash_table_new_full(g_str_hash, g_str_equal,
+		                                               g_free, g_free);
 
 	return nexus;
 }
@@ -49,79 +70,307 @@
 void
 msn_nexus_destroy(MsnNexus *nexus)
 {
-	if (nexus->challenge_data != NULL)
-		g_hash_table_destroy(nexus->challenge_data);
+	int i;
+	for (i = 0; i < nexus->token_len; i++) {
+		g_hash_table_destroy(nexus->tokens[i].token);
+		g_free(nexus->tokens[i].secret);
+	}
+
+	g_free(nexus->tokens);
+	g_free(nexus->policy);
+	g_free(nexus->nonce);
+	g_free(nexus->cipher);
+	g_free(nexus->secret);
+	g_free(nexus);
+}
+
+/**************************************************************************
+ * RPS/SSO Authentication
+ **************************************************************************/
+
+static char *
+rps_create_key(const char *key, int key_len, const char *data, size_t data_len)
+{
+	const guchar magic[] = "WS-SecureConversation";
+	const int magic_len = sizeof(magic) - 1;
+
+	PurpleCipherContext *hmac;
+	guchar hash1[20], hash2[20], hash3[20], hash4[20];
+	char *result;
+
+	hmac = purple_cipher_context_new_by_name("hmac", NULL);
+
+	purple_cipher_context_set_option(hmac, "hash", "sha1");
+	purple_cipher_context_set_key_with_len(hmac, (guchar *)key, key_len);
+	purple_cipher_context_append(hmac, magic, magic_len);
+	purple_cipher_context_append(hmac, (guchar *)data, data_len);
+	purple_cipher_context_digest(hmac, sizeof(hash1), hash1, NULL);
+
+	purple_cipher_context_reset(hmac, NULL);
+	purple_cipher_context_set_option(hmac, "hash", "sha1");
+	purple_cipher_context_set_key_with_len(hmac, (guchar *)key, key_len);
+	purple_cipher_context_append(hmac, hash1, 20);
+	purple_cipher_context_append(hmac, magic, magic_len);
+	purple_cipher_context_append(hmac, (guchar *)data, data_len);
+	purple_cipher_context_digest(hmac, sizeof(hash2), hash2, NULL);
+
+	purple_cipher_context_reset(hmac, NULL);
+	purple_cipher_context_set_option(hmac, "hash", "sha1");
+	purple_cipher_context_set_key_with_len(hmac, (guchar *)key, key_len);
+	purple_cipher_context_append(hmac, hash1, 20);
+	purple_cipher_context_digest(hmac, sizeof(hash3), hash3, NULL);
+
+	purple_cipher_context_reset(hmac, NULL);
+	purple_cipher_context_set_option(hmac, "hash", "sha1");
+	purple_cipher_context_set_key_with_len(hmac, (guchar *)key, key_len);
+	purple_cipher_context_append(hmac, hash3, sizeof(hash3));
+	purple_cipher_context_append(hmac, magic, magic_len);
+	purple_cipher_context_append(hmac, (guchar *)data, data_len);
+	purple_cipher_context_digest(hmac, sizeof(hash4), hash4, NULL);
+
+	purple_cipher_context_destroy(hmac);
+
+	result = g_malloc(24);
+	memcpy(result, hash2, sizeof(hash2));
+	memcpy(result + sizeof(hash2), hash4, 4);
+
+	return result;
+}
+
+static char *
+des3_cbc(const char *key, const char *iv, const char *data, int len, gboolean decrypt)
+{
+	PurpleCipherContext *des3;
+	char *out;
+	size_t outlen;
 
-	g_free(nexus->challenge_data_str);
-	g_free(nexus);
+	des3 = purple_cipher_context_new_by_name("des3", NULL);
+	purple_cipher_context_set_key(des3, (guchar *)key);
+	purple_cipher_context_set_batch_mode(des3, PURPLE_CIPHER_BATCH_MODE_CBC);
+	purple_cipher_context_set_iv(des3, (guchar *)iv, 8);
+
+	out = g_malloc(len);
+	if (decrypt)
+		purple_cipher_context_decrypt(des3, (guchar *)data, len, (guchar *)out, &outlen);
+	else
+		purple_cipher_context_encrypt(des3, (guchar *)data, len, (guchar *)out, &outlen);
+
+	purple_cipher_context_destroy(des3);
+
+	return out;
+}
+
+#define CRYPT_MODE_CBC 1
+#define CIPHER_TRIPLE_DES 0x6603
+#define HASH_SHA1 0x8004
+static char *
+msn_rps_encrypt(MsnNexus *nexus)
+{
+	MsnUsrKey *usr_key;
+	const char magic1[] = "SESSION KEY HASH";
+	const char magic2[] = "SESSION KEY ENCRYPTION";
+	PurpleCipherContext *hmac;
+	size_t len;
+	guchar hash[20];
+	char *key1, *key2, *key3;
+	gsize key1_len;
+	int *iv;
+	char *nonce_fixed;
+	char *cipher;
+	char *response;
+
+	usr_key = g_malloc(sizeof(MsnUsrKey));
+	usr_key->size = GUINT32_TO_LE(28);
+	usr_key->crypt_mode = GUINT32_TO_LE(CRYPT_MODE_CBC);
+	usr_key->cipher_type = GUINT32_TO_LE(CIPHER_TRIPLE_DES);
+	usr_key->hash_type = GUINT32_TO_LE(HASH_SHA1);
+	usr_key->iv_len = GUINT32_TO_LE(8);
+	usr_key->hash_len = GUINT32_TO_LE(20);
+	usr_key->cipher_len = GUINT32_TO_LE(72);
+
+	key1 = (char *)purple_base64_decode((const char *)nexus->tokens[MSN_AUTH_MESSENGER].secret, &key1_len);
+	key2 = rps_create_key(key1, key1_len, magic1, sizeof(magic1) - 1);
+	key3 = rps_create_key(key1, key1_len, magic2, sizeof(magic2) - 1);
+
+	iv = (int *)usr_key->iv;
+	iv[0] = rand();
+	iv[1] = rand();
+
+	len = strlen(nexus->nonce);
+	hmac = purple_cipher_context_new_by_name("hmac", NULL);
+	purple_cipher_context_set_option(hmac, "hash", "sha1");
+	purple_cipher_context_set_key_with_len(hmac, (guchar *)key2, 24);
+	purple_cipher_context_append(hmac, (guchar *)nexus->nonce, len);
+	purple_cipher_context_digest(hmac, 20, hash, NULL);
+	purple_cipher_context_destroy(hmac);
+
+	/* We need to pad this to 72 bytes, apparently */
+	nonce_fixed = g_malloc(len + 8);
+	memcpy(nonce_fixed, nexus->nonce, len);
+	memset(nonce_fixed + len, 0x08, 8);
+	cipher = des3_cbc(key3, usr_key->iv, nonce_fixed, len + 8, FALSE);
+	g_free(nonce_fixed);
+
+	memcpy(usr_key->hash, hash, 20);
+	memcpy(usr_key->cipher, cipher, 72);
+
+	g_free(key1);
+	g_free(key2);
+	g_free(key3);
+	g_free(cipher);
+
+	response = purple_base64_encode((guchar *)usr_key, sizeof(MsnUsrKey));
+
+	g_free(usr_key);
+
+	return response;
 }
 
 /**************************************************************************
  * Login
  **************************************************************************/
 
+/* Used to specify which token to update when only doing single updates */
+typedef struct _MsnNexusUpdateData MsnNexusUpdateData;
+struct _MsnNexusUpdateData {
+	MsnNexus *nexus;
+	int id;
+	GSourceFunc cb;
+	gpointer data;
+};
+
+#if !GLIB_CHECK_VERSION(2, 12, 0)
+static gboolean
+nexus_remove_all_cb(gpointer key, gpointer val, gpointer data)
+{
+	return TRUE;
+}
+#endif
+
+
+static gboolean
+nexus_parse_token(MsnNexus *nexus, int id, xmlnode *node)
+{
+	char *token_str, *expiry_str;
+	const char *id_str;
+	char **elems, **cur, **tokens;
+	xmlnode *token = xmlnode_get_child(node, "RequestedSecurityToken/BinarySecurityToken");
+	xmlnode *secret = xmlnode_get_child(node, "RequestedProofToken/BinarySecret");
+	xmlnode *expires = xmlnode_get_child(node, "LifeTime/Expires");
+
+	if (!token)
+		return FALSE;
+
+	/* Use the ID that the server sent us */
+	if (id == -1) {
+		id_str = xmlnode_get_attrib(token, "Id");
+		if (id_str == NULL)
+			return FALSE;
+
+		id = atol(id_str + 7) - 1;	/* 'Compact#' or 'PPToken#' */
+		if (id >= nexus->token_len)
+			return FALSE;	/* Where did this come from? */
+	}
+
+	token_str = xmlnode_get_data(token);
+	if (token_str == NULL)
+		return FALSE;
+
+#if GLIB_CHECK_VERSION(2, 12, 0)
+	g_hash_table_remove_all(nexus->tokens[id].token);
+#else
+	g_hash_table_foreach_remove(nexus->tokens[id].token,
+		nexus_remove_all_cb, NULL);
+#endif
+
+	elems = g_strsplit(token_str, "&", 0);
+
+	for (cur = elems; *cur != NULL; cur++) {
+		tokens = g_strsplit(*cur, "=", 2);
+		g_hash_table_insert(nexus->tokens[id].token, tokens[0], tokens[1]);
+		/* Don't free each of the tokens, only the array. */
+		g_free(tokens);
+	}
+	g_strfreev(elems);
+	g_free(token_str);
+
+	if (secret)
+		nexus->tokens[id].secret = xmlnode_get_data(secret);
+	else
+		nexus->tokens[id].secret = NULL;
+
+	/* Yay for MS using ISO-8601 */
+	expiry_str = xmlnode_get_data(expires);
+	nexus->tokens[id].expiry = purple_str_to_time(expiry_str,
+		FALSE, NULL, NULL, NULL);
+	g_free(expiry_str);
+
+	purple_debug_info("msn", "Updated ticket for domain '%s', expires at %" G_GINT64_FORMAT ".\n",
+	                  ticket_domains[id][SSO_VALID_TICKET_DOMAIN],
+	                  (gint64)nexus->tokens[id].expiry);
+	return TRUE;
+}
+
+static gboolean
+nexus_parse_collection(MsnNexus *nexus, int id, xmlnode *collection)
+{
+	xmlnode *node;
+	gboolean result;
+
+	node = xmlnode_get_child(collection, "RequestSecurityTokenResponse");
+
+	if (!node)
+		return FALSE;
+
+	result = TRUE;
+	for (; node && result; node = node->next) {
+		xmlnode *endpoint = xmlnode_get_child(node, "AppliesTo/EndpointReference/Address");
+		char *address = xmlnode_get_data(endpoint);
+
+		if (g_str_equal(address, "http://Passport.NET/tb")) {
+			/* This node contains the stuff for updating tokens. */
+			char *data;
+			xmlnode *cipher = xmlnode_get_child(node, "RequestedSecurityToken/EncryptedData/CipherData/CipherValue");
+			xmlnode *secret = xmlnode_get_child(node, "RequestedProofToken/BinarySecret");
+
+			nexus->cipher = xmlnode_get_data(cipher);
+			data = xmlnode_get_data(secret);
+			nexus->secret = (char *)purple_base64_decode(data, NULL);
+			g_free(data);
+
+		} else {
+			result = nexus_parse_token(nexus, id, node);
+		}
+		g_free(address);
+	}
+
+	return result;
+}
+
 static void
 nexus_got_response_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data)
 {
 	MsnNexus *nexus = data;
 	MsnSession *session = nexus->session;
-	xmlnode *node;
+	const char *ticket;
+	char *response;
 
 	if (resp == NULL) {
 		msn_session_set_error(session, MSN_ERROR_SERVCONN, _("Windows Live ID authentication:Unable to connect"));
 		return;
 	}
 
-	node = msn_soap_xml_get(resp->xml,	"Body/"
-		"RequestSecurityTokenResponseCollection/RequestSecurityTokenResponse");
-
-	for (; node; node = node->next) {
-		xmlnode *token = msn_soap_xml_get(node,
-			"RequestedSecurityToken/BinarySecurityToken");
-
-		if (token) {
-			char *token_str = xmlnode_get_data(token);
-			char **elems, **cur, **tokens;
-			char *msn_twn_t, *msn_twn_p, *cert_str;
-
-			if (token_str == NULL) continue;
-
-			elems = g_strsplit(token_str, "&", 0);
-
-			for (cur = elems; *cur != NULL; cur++){
-				tokens = g_strsplit(*cur, "=", 2);
-				g_hash_table_insert(nexus->challenge_data, tokens[0], tokens[1]);
-				/* Don't free each of the tokens, only the array. */
-				g_free(tokens);
-			}
-
-			g_free(token_str);
-			g_strfreev(elems);
-
-			msn_twn_t = g_hash_table_lookup(nexus->challenge_data, "t");
-			msn_twn_p = g_hash_table_lookup(nexus->challenge_data, "p");
-
-			/*setup the t and p parameter for session*/
-			g_free(session->passport_info.t);
-			session->passport_info.t = g_strdup(msn_twn_t);
-
-			g_free(session->passport_info.p);
-			session->passport_info.p = g_strdup(msn_twn_p);
-
-			cert_str = g_strdup_printf("t=%s&p=%s",msn_twn_t,msn_twn_p);
-			msn_got_login_params(session, cert_str);
-
-			purple_debug_info("MSN Nexus","Close nexus connection!\n");
-			g_free(cert_str);
-			msn_nexus_destroy(nexus);
-			session->nexus = NULL;
-
-			return;
-		}
+	if (!nexus_parse_collection(nexus, -1,
+	                            xmlnode_get_child(resp->xml,
+	                                              "Body/RequestSecurityTokenResponseCollection"))) {
+		msn_session_set_error(session, MSN_ERROR_SERVCONN, _("Windows Live ID authentication:Invalid response"));
+		return;
 	}
 
-	/* we must have failed! */
-	msn_session_set_error(session, MSN_ERROR_AUTH, _("Windows Live ID authentication: cannot find authenticate token in server response"));
+	ticket = msn_nexus_get_token_str(nexus, MSN_AUTH_MESSENGER);
+	response = msn_rps_encrypt(nexus);
+	msn_got_login_params(session, ticket, response);
+	g_free(response);
 }
 
 /*when connect, do the SOAP Style windows Live ID authentication */
@@ -129,92 +378,258 @@
 msn_nexus_connect(MsnNexus *nexus)
 {
 	MsnSession *session = nexus->session;
-	char *ru,*lc,*id,*tw,*ct,*kpp,*kv,*ver,*rn,*tpf;
-	char *fs0,*fs;
 	const char *username;
 	char *password;
-	char *tail;
-#ifdef NEXUS_LOGIN_TWN
-	char *challenge_str;
-#else
-	char *rst1_str,*rst2_str,*rst3_str;
-#endif
+	GString *domains;
+	char *request;
+	int i;
 
 	MsnSoapMessage *soap;
 
-	purple_debug_info("MSN Nexus","Starting Windows Live ID authentication\n");
+	purple_debug_info("msn", "Starting Windows Live ID authentication\n");
 	msn_session_set_login_step(session, MSN_LOGIN_STEP_GET_COOKIE);
 
-	/*prepare the Windows Live ID authentication token*/
 	username = purple_account_get_username(session->account);
-	password = g_strndup(purple_connection_get_password(session->account->gc), 16);
+	password = g_markup_escape_text(purple_connection_get_password(session->account->gc), 16);
+
+	purple_debug_info("msn", "Logging on %s, with policy '%s', nonce '%s'\n",
+	                  username, nexus->policy, nexus->nonce);
+
+	domains = g_string_new(NULL);
+	for (i = 0; i < nexus->token_len; i++) {
+		g_string_append_printf(domains, MSN_SSO_RST_TEMPLATE,
+		                       i+1,
+		                       ticket_domains[i][SSO_VALID_TICKET_DOMAIN],
+		                       ticket_domains[i][SSO_VALID_TICKET_POLICY] != NULL ?
+		                           ticket_domains[i][SSO_VALID_TICKET_POLICY] :
+		                           nexus->policy);
+	}
+
+	request = g_strdup_printf(MSN_SSO_TEMPLATE, username, password, domains->str);
+	g_free(password);
+	g_string_free(domains, TRUE);
+
+	soap = msn_soap_message_new(NULL, xmlnode_from_str(request, -1));
+	g_free(request);
+	msn_soap_message_send(session, soap, MSN_SSO_SERVER, SSO_POST_URL, TRUE,
+	                      nexus_got_response_cb, nexus);
+}
 
-	lc =	(char *)g_hash_table_lookup(nexus->challenge_data, "lc");
-	id =	(char *)g_hash_table_lookup(nexus->challenge_data, "id");
-	tw =	(char *)g_hash_table_lookup(nexus->challenge_data, "tw");
-	fs0=	(char *)g_hash_table_lookup(nexus->challenge_data, "fs");
-	ru =	(char *)g_hash_table_lookup(nexus->challenge_data, "ru");
-	ct =	(char *)g_hash_table_lookup(nexus->challenge_data, "ct");
-	kpp=	(char *)g_hash_table_lookup(nexus->challenge_data, "kpp");
-	kv =	(char *)g_hash_table_lookup(nexus->challenge_data, "kv");
-	ver=	(char *)g_hash_table_lookup(nexus->challenge_data, "ver");
-	rn =	(char *)g_hash_table_lookup(nexus->challenge_data, "rn");
-	tpf=	(char *)g_hash_table_lookup(nexus->challenge_data, "tpf");
+static void
+nexus_got_update_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data)
+{
+	MsnNexusUpdateData *ud = data;
+	MsnNexus *nexus = ud->nexus;
+	char iv[8] = {0,0,0,0,0,0,0,0};
+	xmlnode *enckey;
+	char *tmp;
+	char *nonce;
+	gsize len;
+	char *key;
 
-	/*
-	 * add some fail-safe code to avoid windows Purple Crash bug #1540454
-	 * If any of these string is NULL, will return Authentication Fail!
-	 * for when windows g_strdup_printf() implementation get NULL point,It crashed!
-	 */
-	if(!(lc && id && tw && ru && ct && kpp && kv && ver && tpf)){
-		purple_debug_error("MSN Nexus","WLM Authenticate Key Error!\n");
-		msn_session_set_error(session, MSN_ERROR_AUTH, _("Windows Live ID authentication Failed"));
-		g_free(password);
-		msn_nexus_destroy(nexus);
-		session->nexus = NULL;
+#if 0
+	char *decrypted_pp;
+#endif
+	char *decrypted_data;
+
+	purple_debug_info("msn", "Got Update Response for %s.\n", ticket_domains[ud->id][SSO_VALID_TICKET_DOMAIN]);
+
+	enckey = xmlnode_get_child(resp->xml, "Header/Security/DerivedKeyToken");
+	while (enckey) {
+		if (g_str_equal(xmlnode_get_attrib(enckey, "Id"), "EncKey"))
+			break;
+		enckey = xmlnode_get_next_twin(enckey);
+	}
+	if (!enckey) {
+		purple_debug_error("msn", "Invalid response in token update.\n");
 		return;
 	}
 
-	/*
-	 * in old MSN NS server's "USR TWN S" return,didn't include fs string
-	 * so we use a default "1" for fs.
-	 */
-	if(fs0){
-		fs = g_strdup(fs0);
-	}else{
-		fs = g_strdup("1");
+	tmp = xmlnode_get_data(xmlnode_get_child(enckey, "Nonce"));
+	nonce = (char *)purple_base64_decode(tmp, &len);
+	key = rps_create_key(nexus->secret, 24, nonce, len);
+	g_free(tmp);
+	g_free(nonce);
+
+#if 0
+	/* Don't know what this is for yet */
+	tmp = xmlnode_get_data(xmlnode_get_child(resp->xml,
+		"Header/EncryptedPP/EncryptedData/CipherData/CipherValue"));
+	if (tmp) {
+		decrypted_pp = des3_cbc(key, iv, tmp, len, TRUE);
+		g_free(tmp);
+		purple_debug_info("msn", "Got Response Header EncryptedPP: %s\n", decrypted_pp);
+		g_free(decrypted_pp);
+	}
+#endif
+
+	tmp = xmlnode_get_data(xmlnode_get_child(resp->xml,
+		"Body/EncryptedData/CipherData/CipherValue"));
+	if (tmp) {
+		char *unescaped;
+		xmlnode *rstresponse;
+
+		unescaped = (char *)purple_base64_decode(tmp, &len);
+		g_free(tmp);
+
+		decrypted_data = des3_cbc(key, iv, unescaped, len, TRUE);
+		g_free(unescaped);
+		purple_debug_info("msn", "Got Response Body EncryptedData: %s\n", decrypted_data);
+
+		rstresponse = xmlnode_from_str(decrypted_data, -1);
+		if (g_str_equal(rstresponse->name, "RequestSecurityTokenResponse"))
+			nexus_parse_token(nexus, ud->id, rstresponse);
+		else
+			nexus_parse_collection(nexus, ud->id, rstresponse);
+		g_free(decrypted_data);
 	}
 
-#ifdef NEXUS_LOGIN_TWN
-	challenge_str = g_strdup_printf(
-		"lc=%s&amp;id=%s&amp;tw=%s&amp;fs=%s&amp;ru=%s&amp;ct=%s&amp;kpp=%s&amp;kv=%s&amp;ver=%s&amp;rn=%s&amp;tpf=%s\r\n",
-		lc,id,tw,fs,ru,ct,kpp,kv,ver,rn,tpf
-		);
+	if (ud->cb)
+		purple_timeout_add(0, ud->cb, ud->data);
 
-	/*build the SOAP windows Live ID XML body */
-	tail = g_strdup_printf(TWN_ENVELOP_TEMPLATE, username, password, challenge_str);
-	g_free(challenge_str);
-#else
-	rst1_str = g_strdup_printf(
-		"id=%s&amp;tw=%s&amp;fs=%s&amp;kpp=%s&amp;kv=%s&amp;ver=%s&amp;rn=%s",
-		id,tw,fs,kpp,kv,ver,rn
-		);
-	rst2_str = g_strdup_printf(
-		"fs=%s&amp;id=%s&amp;kv=%s&amp;rn=%s&amp;tw=%s&amp;ver=%s",
-		fs,id,kv,rn,tw,ver
-		);
-	rst3_str = g_strdup_printf("id=%s",id);
-	tail = g_strdup_printf(TWN_LIVE_ENVELOP_TEMPLATE,username,password,rst1_str,rst2_str,rst3_str);
-	g_free(rst1_str);
-	g_free(rst2_str);
-	g_free(rst3_str);
-#endif
-	g_free(fs);
-	g_free(password);
-
-	soap = msn_soap_message_new(NULL, xmlnode_from_str(tail, -1));
-	g_free(tail);
-	msn_soap_message_send(nexus->session, soap, MSN_TWN_SERVER, TWN_POST_URL,
-		nexus_got_response_cb, nexus);
+	g_free(ud);
 }
 
+void
+msn_nexus_update_token(MsnNexus *nexus, int id, GSourceFunc cb, gpointer data)
+{
+	MsnSession *session = nexus->session;
+	MsnNexusUpdateData *ud;
+	PurpleCipherContext *sha1;
+	PurpleCipherContext *hmac;
+
+	char *key;
+
+	guchar digest[20];
+
+	struct tm *tm;
+	time_t now;
+	char *now_str;
+	char *timestamp;
+	char *timestamp_b64;
+
+	char *domain;
+	char *domain_b64;
+
+	char *signedinfo;
+	gint32 nonce[6];
+	int i;
+	char *nonce_b64;
+	char *signature_b64;
+	guchar signature[20];
+
+	char *request;
+	MsnSoapMessage *soap;
+
+	purple_debug_info("msn",
+	                  "Updating ticket for user '%s' on domain '%s'\n",
+	                  purple_account_get_username(session->account),
+	                  ticket_domains[id][SSO_VALID_TICKET_DOMAIN]);
+
+	ud = g_new0(MsnNexusUpdateData, 1);
+	ud->nexus = nexus;
+	ud->id = id;
+	ud->cb = cb;
+	ud->data = data;
+
+	sha1 = purple_cipher_context_new_by_name("sha1", NULL);
+
+	domain = g_strdup_printf(MSN_SSO_RST_TEMPLATE,
+	                         id,
+	                         ticket_domains[id][SSO_VALID_TICKET_DOMAIN],
+	                         ticket_domains[id][SSO_VALID_TICKET_POLICY] != NULL ?
+	                             ticket_domains[id][SSO_VALID_TICKET_POLICY] :
+	                             nexus->policy);
+	purple_cipher_context_append(sha1, (guchar *)domain, strlen(domain));
+	purple_cipher_context_digest(sha1, 20, digest, NULL);
+	domain_b64 = purple_base64_encode(digest, 20);
+
+	now = time(NULL);
+	tm = gmtime(&now);
+	now_str = g_strdup(purple_utf8_strftime("%Y-%m-%dT%H:%M:%SZ", tm));
+	now += 5*60;
+	tm = gmtime(&now);
+	timestamp = g_strdup_printf(MSN_SSO_TIMESTAMP_TEMPLATE,
+	                            now_str,
+	                            purple_utf8_strftime("%Y-%m-%dT%H:%M:%SZ", tm));
+	purple_cipher_context_reset(sha1, NULL);
+	purple_cipher_context_append(sha1, (guchar *)timestamp, strlen(timestamp));
+	purple_cipher_context_digest(sha1, 20, digest, NULL);
+	timestamp_b64 = purple_base64_encode(digest, 20);
+	g_free(now_str);
+
+	purple_cipher_context_destroy(sha1);
+
+	signedinfo = g_strdup_printf(MSN_SSO_SIGNEDINFO_TEMPLATE,
+	                             id,
+	                             domain_b64,
+	                             timestamp_b64);
+
+	for (i = 0; i < 6; i++)
+		nonce[i] = rand();
+	nonce_b64 = purple_base64_encode((guchar *)&nonce, sizeof(nonce));
+
+	key = rps_create_key(nexus->secret, 24, (char *)nonce, sizeof(nonce));
+	hmac = purple_cipher_context_new_by_name("hmac", NULL);
+	purple_cipher_context_set_option(hmac, "hash", "sha1");
+	purple_cipher_context_set_key_with_len(hmac, (guchar *)key, 24);
+	purple_cipher_context_append(hmac, (guchar *)signedinfo, strlen(signedinfo));
+	purple_cipher_context_digest(hmac, 20, signature, NULL);
+	purple_cipher_context_destroy(hmac);
+	signature_b64 = purple_base64_encode(signature, 20);
+
+	request = g_strdup_printf(MSN_SSO_TOKEN_UPDATE_TEMPLATE,
+	                          nexus->cipher,
+	                          nonce_b64,
+	                          timestamp,
+	                          signedinfo,
+	                          signature_b64,
+	                          domain);
+
+	g_free(nonce_b64);
+	g_free(domain_b64);
+	g_free(timestamp_b64);
+	g_free(timestamp);
+	g_free(key);
+	g_free(signature_b64);
+	g_free(signedinfo);
+	g_free(domain);
+
+	soap = msn_soap_message_new(NULL, xmlnode_from_str(request, -1));
+	g_free(request);
+	msn_soap_message_send(session, soap, MSN_SSO_SERVER, SSO_POST_URL, TRUE,
+	                      nexus_got_update_cb, ud);
+}
+
+GHashTable *
+msn_nexus_get_token(MsnNexus *nexus, MsnAuthDomains id)
+{
+	g_return_val_if_fail(nexus != NULL, NULL);
+	g_return_val_if_fail(id < nexus->token_len, NULL);
+
+	return nexus->tokens[id].token;
+}
+
+const char *
+msn_nexus_get_token_str(MsnNexus *nexus, MsnAuthDomains id)
+{
+	static char buf[1024];
+	GHashTable *token = msn_nexus_get_token(nexus, id);
+	const char *msn_t;
+	const char *msn_p;
+	gint ret;
+
+	g_return_val_if_fail(token != NULL, NULL);
+
+	msn_t = g_hash_table_lookup(token, "t");
+	msn_p = g_hash_table_lookup(token, "p");
+
+	g_return_val_if_fail(msn_t != NULL, NULL);
+	g_return_val_if_fail(msn_p != NULL, NULL);
+
+	ret = g_snprintf(buf, sizeof(buf) - 1, "t=%s&p=%s", msn_t, msn_p);
+	g_return_val_if_fail(ret != -1, NULL);
+
+	return buf;
+}
+
--- a/libpurple/protocols/msn/nexus.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/nexus.h	Thu Nov 20 21:13:56 2008 +0000
@@ -24,127 +24,210 @@
 #ifndef _MSN_NEXUS_H_
 #define _MSN_NEXUS_H_
 
-#include "soap.h"
+/* Index into ticket_tokens in nexus.c Keep updated! */
+typedef enum
+{
+	MSN_AUTH_MESSENGER     = 0,
+	MSN_AUTH_MESSENGER_WEB = 1,
+	MSN_AUTH_CONTACTS      = 2,
+	MSN_AUTH_LIVE_SECURE   = 3,
+	MSN_AUTH_SPACES        = 4,
+	MSN_AUTH_LIVE_CONTACTS = 5,
+	MSN_AUTH_STORAGE       = 6
+} MsnAuthDomains;
 
-/*#define MSN_TWN_SERVER	"loginnet.passport.com"*/
-#define MSN_TWN_SERVER	"login.live.com"
+#define MSN_SSO_SERVER	"login.live.com"
+#define SSO_POST_URL	"/RST.srf"
 
-#define TWN_START_TOKEN		"<wsse:BinarySecurityToken Id=\"PPToken1\">"
-#define TWN_END_TOKEN		"</wsse:BinarySecurityToken>"
+#define MSN_SSO_RST_TEMPLATE \
+"<wst:RequestSecurityToken xmlns=\"http://schemas.xmlsoap.org/ws/2004/04/trust\" Id=\"RST%d\">"\
+	"<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>"\
+	"<wsp:AppliesTo xmlns=\"http://schemas.xmlsoap.org/ws/2002/12/policy\">"\
+		"<wsa:EndpointReference xmlns=\"http://schemas.xmlsoap.org/ws/2004/03/addressing\">"\
+			"<wsa:Address>%s</wsa:Address>"\
+		"</wsa:EndpointReference>"\
+	"</wsp:AppliesTo>"\
+	"<wsse:PolicyReference xmlns=\"http://schemas.xmlsoap.org/ws/2003/06/secext\" URI=\"%s\"></wsse:PolicyReference>"\
+"</wst:RequestSecurityToken>"
 
-#define TWN_POST_URL			"/RST.srf"
-#define TWN_ENVELOP_TEMPLATE 	"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"\
-						"<Envelope xmlns=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:wsse=\"http://schemas.xmlsoap.org/ws/2003/06/secext\" xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\" xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2002/12/policy\" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/03/addressing\" xmlns:wssc=\"http://schemas.xmlsoap.org/ws/2004/04/sc\" xmlns:wst=\"http://schemas.xmlsoap.org/ws/2004/04/trust\">"\
-						"<Header>"\
-						"<ps:AuthInfo xmlns:ps=\"http://schemas.microsoft.com/Passport/SoapServices/PPCRL\" Id=\"PPAuthInfo\">"\
-						"<ps:HostingApp>{3:B}</ps:HostingApp>"\
-						"<ps:BinaryVersion>4</ps:BinaryVersion>"\
-						"<ps:UIVersion>1</ps:UIVersion>"\
-						"<ps:Cookies></ps:Cookies>"\
-						"<ps:RequestParams>AQAAAAIAAABsYwQAAAAzMDg0</ps:RequestParams>"\
-						"</ps:AuthInfo>"\
-						"<wsse:Security>"\
-						"<wsse:UsernameToken Id=\"user\">"\
-						"<wsse:Username>%s</wsse:Username>"\
-						"<wsse:Password>%s</wsse:Password>"\
-						"</wsse:UsernameToken>"\
-						"</wsse:Security>"\
-						"</Header>"\
-						"<Body>"\
-						"<ps:RequestMultipleSecurityTokens xmlns:ps=\"http://schemas.microsoft.com/Passport/SoapServices/PPCRL\" Id=\"RSTS\">"\
-						"<wst:RequestSecurityToken Id=\"RST0\">"\
-						"<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>"\
-						"<wsp:AppliesTo>"\
-						"<wsa:EndpointReference>"\
+#define MSN_SSO_TEMPLATE "<?xml version='1.0' encoding='utf-8'?>"\
+"<Envelope xmlns=\"http://schemas.xmlsoap.org/soap/envelope/\""\
+	" xmlns:wsse=\"http://schemas.xmlsoap.org/ws/2003/06/secext\""\
+	" xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\""\
+	" xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2002/12/policy\""\
+	" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\""\
+	" xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/03/addressing\""\
+	" xmlns:wssc=\"http://schemas.xmlsoap.org/ws/2004/04/sc\""\
+	" xmlns:wst=\"http://schemas.xmlsoap.org/ws/2004/04/trust\">"\
+	"<Header>"\
+		"<ps:AuthInfo"\
+			" xmlns:ps=\"http://schemas.microsoft.com/Passport/SoapServices/PPCRL\""\
+			" Id=\"PPAuthInfo\">"\
+			"<ps:HostingApp>{7108E71A-9926-4FCB-BCC9-9A9D3F32E423}</ps:HostingApp>"\
+			"<ps:BinaryVersion>4</ps:BinaryVersion>"\
+			"<ps:UIVersion>1</ps:UIVersion>"\
+			"<ps:Cookies></ps:Cookies>"\
+			"<ps:RequestParams>AQAAAAIAAABsYwQAAAAxMDMz</ps:RequestParams>"\
+		"</ps:AuthInfo>"\
+		"<wsse:Security>"\
+			"<wsse:UsernameToken Id=\"user\">"\
+				"<wsse:Username>%s</wsse:Username>"\
+				"<wsse:Password>%s</wsse:Password>"\
+			"</wsse:UsernameToken>"\
+		"</wsse:Security>"\
+	"</Header>"\
+	"<Body>"\
+		"<ps:RequestMultipleSecurityTokens"\
+			" xmlns:ps=\"http://schemas.microsoft.com/Passport/SoapServices/PPCRL\""\
+			" Id=\"RSTS\">"\
+			"<wst:RequestSecurityToken Id=\"RST0\">"\
+				"<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>"\
+				"<wsp:AppliesTo>"\
+					"<wsa:EndpointReference>"\
 						"<wsa:Address>http://Passport.NET/tb</wsa:Address>"\
-						"</wsa:EndpointReference>"\
-						"</wsp:AppliesTo>"\
-						"</wst:RequestSecurityToken>"\
-						"<wst:RequestSecurityToken Id=\"RST1\">"\
-						"<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>"\
-						"<wsp:AppliesTo>"\
-						"<wsa:EndpointReference>"\
-						"<wsa:Address>messenger.msn.com</wsa:Address>"\
-						"</wsa:EndpointReference>"\
-						"</wsp:AppliesTo>"\
-						"<wsse:PolicyReference URI=\"?%s\">"\
-						"</wsse:PolicyReference>"\
-						"</wst:RequestSecurityToken>"\
-						"</ps:RequestMultipleSecurityTokens>"\
-						"</Body>"\
-						"</Envelope>"
+					"</wsa:EndpointReference>"\
+				"</wsp:AppliesTo>"\
+			"</wst:RequestSecurityToken>"\
+			"%s"	/* Other RSTn tokens */\
+		"</ps:RequestMultipleSecurityTokens>"\
+	"</Body>"\
+"</Envelope>"
+
+#define MSN_SSO_AUTHINFO_TEMPLATE \
+"<ps:AuthInfo xmlns:ps=\"http://schemas.microsoft.com/Passport/SoapServices/PPCRL\" Id=\"PPAuthInfo\">"\
+	"<ps:HostingApp>{7108E71A-9926-4FCB-BCC9-9A9D3F32E423}</ps:HostingApp>"\
+	"<ps:BinaryVersion>4</ps:BinaryVersion>"\
+	"<ps:UIVersion>1</ps:UIVersion>"\
+	"<ps:Cookies></ps:Cookies>"\
+	"<ps:RequestParams>AQAAAAIAAABsYwQAAAA0MTA1</ps:RequestParams>"\
+"</ps:AuthInfo>"
+/* Not sure what's editable here, so I'll just hard-code the SHA1 hash */
+#define MSN_SSO_AUTHINFO_SHA1_BASE64 "d2IeTF4DAkPEa/tVETHznsivEpc="
+
+#define MSN_SSO_TIMESTAMP_TEMPLATE \
+"<wsu:Timestamp xmlns=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" Id=\"Timestamp\">"\
+	"<wsu:Created>%s</wsu:Created>"\
+	"<wsu:Expires>%s</wsu:Expires>"\
+"</wsu:Timestamp>"
 
-#define TWN_LIVE_START_TOKEN	"<wsse:BinarySecurityToken Id=\"PPToken1\">"
-#define TWN_LIVE_END_TOKEN	"</wsse:BinarySecurityToken>"
-#define TWN_LIVE_ENVELOP_TEMPLATE	"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"\
-"<Envelope xmlns=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:wsse=\"http://schemas.xmlsoap.org/ws/2003/06/secext\" xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\" xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2002/12/policy\" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/03/addressing\" xmlns:wssc=\"http://schemas.xmlsoap.org/ws/2004/04/sc\" xmlns:wst=\"http://schemas.xmlsoap.org/ws/2004/04/trust\">"\
-  "<Header>"\
-    "<ps:AuthInfo xmlns:ps=\"http://schemas.microsoft.com/Passport/SoapServices/PPCRL\" Id=\"PPAuthInfo\">"\
-      "<ps:HostingApp>{7108E71A-9926-4FCB-BCC9-9A9D3F32E423}</ps:HostingApp>"\
-      "<ps:BinaryVersion>4</ps:BinaryVersion>"\
-      "<ps:UIVersion>1</ps:UIVersion>"\
-      "<ps:Cookies></ps:Cookies>"\
-      "<ps:RequestParams>AQAAAAIAAABsYwQAAAAyMDUy</ps:RequestParams>"\
-    "</ps:AuthInfo>"\
-    "<wsse:Security>"\
-      "<wsse:UsernameToken Id=\"user\">"\
-        "<wsse:Username>%s</wsse:Username>"\
-        "<wsse:Password>%s</wsse:Password>"\
-      "</wsse:UsernameToken>"\
-    "</wsse:Security>"\
-  "</Header>"\
-  "<Body>"\
-    "<ps:RequestMultipleSecurityTokens xmlns:ps=\"http://schemas.microsoft.com/Passport/SoapServices/PPCRL\" Id=\"RSTS\">"\
-      "<wst:RequestSecurityToken Id=\"RST0\">"\
-        "<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>"\
-        "<wsp:AppliesTo>"\
-          "<wsa:EndpointReference>"\
-            "<wsa:Address>http://Passport.NET/tb</wsa:Address>"\
-          "</wsa:EndpointReference>"\
-        "</wsp:AppliesTo>"\
-      "</wst:RequestSecurityToken>"\
-      "<wst:RequestSecurityToken Id=\"RST1\">"\
-        "<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>"\
-        "<wsp:AppliesTo>"\
-          "<wsa:EndpointReference>"\
-            "<wsa:Address>messenger.msn.com</wsa:Address>"\
-          "</wsa:EndpointReference>"\
-        "</wsp:AppliesTo>"\
-        "<wsse:PolicyReference URI=\"?%s\"></wsse:PolicyReference>"\
-      "</wst:RequestSecurityToken>"\
-      "<wst:RequestSecurityToken Id=\"RST2\">"\
-        "<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>"\
-        "<wsp:AppliesTo>"\
-          "<wsa:EndpointReference>"\
-            "<wsa:Address>contacts.msn.com</wsa:Address>"\
-         "</wsa:EndpointReference>"\
-        "</wsp:AppliesTo>"\
-       "<wsse:PolicyReference URI=\"?%s\"></wsse:PolicyReference>"\
-     " </wst:RequestSecurityToken>"\
-      "<wst:RequestSecurityToken Id=\"RST3\">"\
-        "<wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>"\
-        "<wsp:AppliesTo>"\
-          "<wsa:EndpointReference>"\
-            "<wsa:Address>voice.messenger.msn.com</wsa:Address>"\
-          "</wsa:EndpointReference>"\
-       " </wsp:AppliesTo>"\
-        "<wsse:PolicyReference URI=\"?%s\"></wsse:PolicyReference>"\
-      "</wst:RequestSecurityToken>"\
-    "</ps:RequestMultipleSecurityTokens>"\
-  "</Body>"\
+#define MSN_SSO_SIGNEDINFO_TEMPLATE \
+"<SignedInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"\
+	"<CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"></CanonicalizationMethod>"\
+	"<SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#hmac-sha1\"></SignatureMethod>"\
+	"<Reference URI=\"#RST%d\">"\
+		"<Transforms>"\
+			"<Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"></Transform>"\
+		"</Transforms>"\
+		"<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"></DigestMethod>"\
+		"<DigestValue>%s</DigestValue>"\
+	"</Reference>"\
+	"<Reference URI=\"#Timestamp\">"\
+		"<Transforms>"\
+			"<Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"></Transform>"\
+		"</Transforms>"\
+		"<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"></DigestMethod>"\
+		"<DigestValue>%s</DigestValue>"\
+	"</Reference>"\
+	"<Reference URI=\"#PPAuthInfo\">"\
+		"<Transforms>"\
+			"<Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"></Transform>"\
+		"</Transforms>"\
+		"<DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"></DigestMethod>"\
+		"<DigestValue>" MSN_SSO_AUTHINFO_SHA1_BASE64 "</DigestValue>"\
+	"</Reference>"\
+"</SignedInfo>"
+
+#define MSN_SSO_TOKEN_UPDATE_TEMPLATE "<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
+"<Envelope"\
+	" xmlns=\"http://schemas.xmlsoap.org/soap/envelope/\""\
+	" xmlns:wsse=\"http://schemas.xmlsoap.org/ws/2003/06/secext\""\
+	" xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\""\
+	" xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2002/12/policy\""\
+	" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\""\
+	" xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/03/addressing\""\
+	" xmlns:wssc=\"http://schemas.xmlsoap.org/ws/2004/04/sc\""\
+	" xmlns:wst=\"http://schemas.xmlsoap.org/ws/2004/04/trust\">"\
+	"<Header>"\
+		MSN_SSO_AUTHINFO_TEMPLATE /* ps:AuthInfo */ \
+		"<wsse:Security>"\
+			"<EncryptedData xmlns=\"http://www.w3.org/2001/04/xmlenc#\" Id=\"BinaryDAToken0\" Type=\"http://www.w3.org/2001/04/xmlenc#Element\">"\
+				"<EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#tripledes-cbc\"></EncryptionMethod>"\
+				"<ds:KeyInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">"\
+					"<ds:KeyName>http://Passport.NET/STS</ds:KeyName>"\
+				"</ds:KeyInfo>"\
+				"<CipherData>"\
+					"<CipherValue>%s</CipherValue>"\
+				"</CipherData>"\
+			"</EncryptedData>"\
+			"<wssc:DerivedKeyToken Id=\"SignKey\">"\
+				"<wsse:RequestedTokenReference>"\
+					"<wsse:KeyIdentifier ValueType=\"http://docs.oasis-open.org/wss/2004/XX/oasis-2004XX-wss-saml-token-profile-1.0#SAMLAssertionID\" />"\
+					"<wsse:Reference URI=\"#BinaryDAToken0\" />"\
+				"</wsse:RequestedTokenReference>"\
+				"<wssc:Nonce>%s</wssc:Nonce>"\
+			"</wssc:DerivedKeyToken>"\
+			"%s" /* wsu:Timestamp */\
+			"<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"\
+				"%s" /* SignedInfo */\
+				"<SignatureValue>%s</SignatureValue>"\
+				"<KeyInfo>"\
+					"<wsse:SecurityTokenReference>"\
+						"<wsse:Reference URI=\"#SignKey\" />"\
+					"</wsse:SecurityTokenReference>"\
+				"</KeyInfo>"\
+			"</Signature>"\
+		"</wsse:Security>"\
+	"</Header>"\
+	"<Body>"\
+		"%s" /* wst:RequestSecurityToken */ \
+	"</Body>"\
 "</Envelope>"
 
+typedef struct _MsnUsrKey MsnUsrKey;
+struct _MsnUsrKey
+{
+	int size; /* 28. Does not count data */
+	int crypt_mode; /* CRYPT_MODE_CBC (1) */
+	int cipher_type; /* TripleDES (0x6603) */
+	int hash_type; /* SHA1 (0x8004) */
+	int iv_len;    /* 8 */
+	int hash_len;  /* 20 */
+	int cipher_len; /* 72 */
+	/* Data */
+	char iv[8];
+	char hash[20];
+	char cipher[72];
+};
+
+typedef struct _MsnTicketToken MsnTicketToken;
+struct _MsnTicketToken {
+	GHashTable *token;
+	char *secret;
+	time_t expiry;
+};
+
 typedef struct _MsnNexus MsnNexus;
 
 struct _MsnNexus
 {
 	MsnSession *session;
-	char * challenge_data_str;
-	GHashTable *challenge_data;
+
+	/* From server via USR command */
+	char *policy;
+	char *nonce;
+
+	/* From server via SOAP stuff */
+	char *cipher;
+	char *secret;
+	MsnTicketToken *tokens;
+	int token_len;
 };
 
 void msn_nexus_connect(MsnNexus *nexus);
 MsnNexus *msn_nexus_new(MsnSession *session);
 void msn_nexus_destroy(MsnNexus *nexus);
+GHashTable *msn_nexus_get_token(MsnNexus *nexus, MsnAuthDomains id);
+const char *msn_nexus_get_token_str(MsnNexus *nexus, MsnAuthDomains id);
+void msn_nexus_update_token(MsnNexus *nexus, int id, GSourceFunc cb, gpointer data);
+#endif /* _MSN_NEXUS_H_ */
 
-#endif /* _MSN_NEXUS_H_ */
--- a/libpurple/protocols/msn/notification.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/notification.c	Thu Nov 20 21:13:56 2008 +0000
@@ -23,6 +23,7 @@
  */
 #include "msn.h"
 #include "notification.h"
+#include "contact.h"
 #include "state.h"
 #include "error.h"
 #include "msnutils.h"
@@ -104,7 +105,6 @@
 
 	vers = g_string_new("");
 
-/*	for (i = session->protocol_ver; i >= WLM_MIN_PROTOCOL; i--) */
 	for (i = WLM_MAX_PROTOCOL; i >= WLM_MIN_PROTOCOL; i--)
 		g_string_append_printf(vers, " MSNP%d", i);
 
@@ -132,7 +132,7 @@
 	servconn = notification->servconn;
 
 	msn_servconn_set_connect_cb(servconn, connect_cb);
-	notification->in_use = msn_servconn_connect(servconn, host, port);
+	notification->in_use = msn_servconn_connect(servconn, host, port, TRUE);
 
 	return notification->in_use;
 }
@@ -195,7 +195,7 @@
  **************************************************************************/
 
 void
-msn_got_login_params(MsnSession *session, const char *login_params)
+msn_got_login_params(MsnSession *session, const char *ticket, const char *response)
 {
 	MsnCmdProc *cmdproc;
 
@@ -203,7 +203,7 @@
 
 	msn_session_set_login_step(session, MSN_LOGIN_STEP_AUTH_END);
 
-	msn_cmdproc_send(cmdproc, "USR", "TWN S %s", login_params);
+	msn_cmdproc_send(cmdproc, "USR", "SSO S %s %s", ticket, response);
 }
 
 static void
@@ -212,8 +212,8 @@
 	PurpleAccount *account;
 
 	account = cmdproc->session->account;
-	msn_cmdproc_send(cmdproc, "USR", "TWN I %s",
-					 purple_account_get_username(account));
+
+	msn_cmdproc_send(cmdproc, "USR", "SSO I %s", purple_account_get_username(account));
 }
 
 static void
@@ -228,43 +228,16 @@
 	if (!g_ascii_strcasecmp(cmd->params[1], "OK"))
 	{
 		/* authenticate OK */
-		/* friendly name part no longer true in msnp11 */
-#if 0
-		const char *friendly = purple_url_decode(cmd->params[3]);
-
-		purple_connection_set_display_name(gc, friendly);
-#endif
 		msn_session_set_login_step(session, MSN_LOGIN_STEP_SYN);
-
-//		msn_cmdproc_send(cmdproc, "SYN", "%s", "0");
-		//TODO we should use SOAP contact to fetch contact list
 	}
-	else if (!g_ascii_strcasecmp(cmd->params[1], "TWN"))
+	else if (!g_ascii_strcasecmp(cmd->params[1], "SSO"))
 	{
-		/* Passport authentication */
-		char **elems, **cur, **tokens;
+		/* RPS authentication */
 
 		session->nexus = msn_nexus_new(session);
 
-		/* Parse the challenge data. */
-		session->nexus->challenge_data_str = g_strdup(cmd->params[3]);
-		elems = g_strsplit(cmd->params[3], ",", 0);
-
-		for (cur = elems; *cur != NULL; cur++)
-		{
-			tokens = g_strsplit(*cur, "=", 2);
-			if(tokens[0] && tokens[1])
-			{
-				purple_debug_info("MSNP14","challenge %p,key:%s,value:%s\n",
-									session->nexus->challenge_data,tokens[0],tokens[1]);
-				g_hash_table_insert(session->nexus->challenge_data, tokens[0], tokens[1]);
-				/* Don't free each of the tokens, only the array. */
-				g_free(tokens);
-			} else
-				g_strfreev(tokens);
-		}
-
-		g_strfreev(elems);
+		session->nexus->policy = g_strdup(cmd->params[3]);
+		session->nexus->nonce = g_strdup(cmd->params[4]);
 
 		msn_session_set_login_step(session, MSN_LOGIN_STEP_AUTH_START);
 
@@ -327,14 +300,13 @@
 	}
 
 	/*
-	 * Windows Live Messenger 8.0
+	 * Windows Live Messenger 8.5
 	 * Notice :CVR String discriminate!
 	 * reference of http://www.microsoft.com/globaldev/reference/oslocversion.mspx
 	 * to see the Local ID
 	 */
 	msn_cmdproc_send(cmdproc, "CVR",
-//					 "0x0409 winnt 5.1 i386 MSG80BETA 8.0.0689 msmsgs %s",
-					"0x0804 winnt 5.1 i386 MSNMSGR 8.0.0792 msmsgs %s",
+					"0x0409 winnt 5.1 i386 MSNMSGR 8.5.1288 msmsgs %s",
 					 purple_account_get_username(account));
 }
 
@@ -345,7 +317,9 @@
 static void
 out_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
-	if (!g_ascii_strcasecmp(cmd->params[0], "OTH"))
+	if (cmd->param_count == 0)
+		msn_session_set_error(cmdproc->session, -1, NULL);
+	else if (!g_ascii_strcasecmp(cmd->params[0], "OTH"))
 		msn_session_set_error(cmdproc->session, MSN_ERROR_SIGN_OTHER,
 							  NULL);
 	else if (!g_ascii_strcasecmp(cmd->params[0], "SSD"))
@@ -377,7 +351,7 @@
 
 	msg = msn_message_new_from_cmd(cmdproc->session, cmd);
 
-	msn_message_parse_payload(msg, payload, len,MSG_LINE_DEM,MSG_BODY_DEM);
+	msn_message_parse_payload(msg, payload, len, MSG_LINE_DEM, MSG_BODY_DEM);
 #ifdef MSN_DEBUG_NS
 	msn_message_show_readable(msg, "Notification", TRUE);
 #endif
@@ -390,23 +364,19 @@
 static void
 msg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
-	purple_debug_info("MSNP14","Processing MSG... \n");
-	if(cmd->payload_len == 0){
-		return;
-	}
+	purple_debug_info("msn", "Processing MSG... \n");
+
 	/* NOTE: cmd is not always cmdproc->last_cmd, sometimes cmd is a queued
 	 * command and we are processing it */
-	if (cmd->payload == NULL)
-	{
+	if (cmd->payload == NULL) {
 		cmdproc->last_cmd->payload_cb  = msg_cmd_post;
-		cmdproc->servconn->payload_len = atoi(cmd->params[2]);
-	}
-	else
-	{
+		cmd->payload_len = atoi(cmd->params[2]);
+
+	} else {
 		g_return_if_fail(cmd->payload_cb != NULL);
 
 #if 0 /* glib on win32 doesn't correctly support precision modifiers for a string */
-		purple_debug_info("MSNP14", "MSG payload:{%.*s}\n", cmd->payload_len, cmd->payload);
+		purple_debug_info("msn", "MSG payload:{%.*s}\n", cmd->payload_len, cmd->payload);
 #endif
 		cmd->payload_cb(cmdproc, cmd, cmd->payload, cmd->payload_len);
 	}
@@ -425,7 +395,7 @@
 	cmdproc = session->notification->cmdproc;
 	g_return_if_fail(msg     != NULL);
 	payload = msn_message_gen_payload(msg, &payload_len);
-	purple_debug_info("MSNP14",
+	purple_debug_info("msn",
 		"send UUM, payload{%s}, strlen:%" G_GSIZE_FORMAT ", len:%" G_GSIZE_FORMAT "\n",
 		payload, strlen(payload), payload_len);
 	type = msg->type;
@@ -435,6 +405,7 @@
 	msn_cmdproc_send_trans(cmdproc, trans);
 }
 
+#if 0
 static void
 ubm_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload,
 			 size_t len)
@@ -444,7 +415,7 @@
 	const char *passport;
 	const char *content_type;
 
-	purple_debug_info("MSNP14","Process UBM payload:%.*s\n", (guint)len, payload);
+	purple_debug_info("msn", "Process UBM payload:%.*s\n", (guint)len, payload);
 	msg = msn_message_new_from_cmd(cmdproc->session, cmd);
 
 	msn_message_parse_payload(msg, payload, len,MSG_LINE_DEM,MSG_BODY_DEM);
@@ -456,7 +427,7 @@
 	passport = msg->remote_user;
 
 	content_type = msn_message_get_content_type(msg);
-	purple_debug_info("MSNP14", "type:%s\n", content_type);
+	purple_debug_info("msn", "type:%s\n", content_type);
 	if(!strcmp(content_type,"text/plain")){
 		const char *value;
 		const char *body;
@@ -508,25 +479,24 @@
 	}
 	msn_message_destroy(msg);
 }
+#endif
 
 /*Yahoo msg process*/
 static void
 ubm_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
-	purple_debug_info("MSNP14","Processing UBM... \n");
-	if(cmd->payload_len == 0){
-		return;
-	}
+	purple_debug_info("msn", "Processing UBM... \n");
+
 	/* NOTE: cmd is not always cmdproc->last_cmd, sometimes cmd is a queued
 	 * command and we are processing it */
-	if (cmd->payload == NULL){
-		cmdproc->last_cmd->payload_cb  = ubm_cmd_post;
-		cmdproc->servconn->payload_len = atoi(cmd->params[2]);
-	}else{
+	if (cmd->payload == NULL) {
+		cmdproc->last_cmd->payload_cb = msg_cmd_post;
+		cmd->payload_len = atoi(cmd->params[4]);
+	} else {
 		g_return_if_fail(cmd->payload_cb != NULL);
 
-		purple_debug_info("MSNP14", "UBM payload:{%.*s}\n", (guint)(cmd->payload_len), cmd->payload);
-		ubm_cmd_post(cmdproc, cmd, cmd->payload, cmd->payload_len);
+		purple_debug_info("msn", "UBM payload:{%.*s}\n", (guint)(cmd->payload_len), cmd->payload);
+		msg_cmd_post(cmdproc, cmd, cmd->payload, cmd->payload_len);
 	}
 }
 
@@ -540,27 +510,8 @@
 	MsnTransaction *trans;
 	char buf[33];
 
-#if 0
-	cipher = purple_ciphers_find_cipher("md5");
-	context = purple_cipher_context_new(cipher, NULL);
-	purple_cipher_context_append(context, (const guchar *)cmd->params[1],
-							   strlen(cmd->params[1]));
-	challenge_resp = MSNP13_WLM_PRODUCT_KEY;
-
-	purple_cipher_context_append(context, (const guchar *)challenge_resp,
-							   strlen(challenge_resp));
-	purple_cipher_context_digest(context, sizeof(digest), digest, NULL);
-	purple_cipher_context_destroy(context);
-
-	for (i = 0; i < 16; i++)
-	{
-		g_snprintf(buf + (i*2), 3, "%02x", digest[i]);
-	}
-#else
 	msn_handle_chl(cmd->params[1], buf);
-#endif
-//	purple_debug_info("MSNP14","<<challenge:{%s}:{%s}\n",cmd->params[1],buf);
-	trans = msn_transaction_new(cmdproc, "QRY", "%s 32", MSNP13_WLM_PRODUCT_ID);
+	trans = msn_transaction_new(cmdproc, "QRY", "%s 32", MSNP15_WLM_PRODUCT_ID);
 
 	msn_transaction_set_payload(trans, buf, 32);
 
@@ -572,7 +523,7 @@
  **************************************************************************/
 /* add contact to xmlnode */
 static void
-msn_add_contact_xml(MsnSession *session, xmlnode *mlNode,const char *passport, MsnListOp list_op, MsnUserType type)
+msn_add_contact_xml(MsnSession *session, xmlnode *mlNode,const char *passport, MsnListOp list_op, MsnNetwork networkId)
 {
 	xmlnode *d_node,*c_node;
 	char **tokens;
@@ -581,7 +532,7 @@
 
 	g_return_if_fail(passport != NULL);
 
-	purple_debug_info("MSNP14","Passport: %s, type: %d\n", passport, type);
+	purple_debug_info("msn", "Passport: %s, type: %d\n", passport, networkId);
 	tokens = g_strsplit(passport, "@", 2);
 	email = tokens[0];
 	domain = tokens[1];
@@ -615,16 +566,16 @@
 	c_node = xmlnode_new("c");
 	xmlnode_set_attrib(c_node, "n", email);
 
-	purple_debug_info("MSNP14", "list_op: %d\n", list_op);
+	purple_debug_info("msn", "list_op: %d\n", list_op);
 	g_snprintf(fmt_str, sizeof(fmt_str), "%d", list_op);
 	xmlnode_set_attrib(c_node, "l", fmt_str);
 
-	if (type != MSN_USER_TYPE_UNKNOWN)
-		g_snprintf(fmt_str, sizeof(fmt_str), "%d", type);
+	if (networkId != MSN_NETWORK_UNKNOWN)
+		g_snprintf(fmt_str, sizeof(fmt_str), "%d", networkId);
 	else if (msn_user_is_yahoo(session->account, passport))
-		g_snprintf(fmt_str, sizeof(fmt_str), "%d", MSN_USER_TYPE_YAHOO);
+		g_snprintf(fmt_str, sizeof(fmt_str), "%d", MSN_NETWORK_YAHOO);
 	else
-		g_snprintf(fmt_str, sizeof(fmt_str), "%d", MSN_USER_TYPE_PASSPORT);
+		g_snprintf(fmt_str, sizeof(fmt_str), "%d", MSN_NETWORK_PASSPORT);
 
 	/*mobile*/
 	//type_str = g_strdup_printf("4");
@@ -639,7 +590,7 @@
 msn_notification_post_adl(MsnCmdProc *cmdproc, const char *payload, int payload_len)
 {
 	MsnTransaction *trans;
-	purple_debug_info("MSN Notification","Sending ADL with payload: %s\n", payload);
+	purple_debug_info("msn", "Sending ADL with payload: %s\n", payload);
 	trans = msn_transaction_new(cmdproc, "ADL", "%i", payload_len);
 	msn_transaction_set_payload(trans, payload, payload_len);
 	msn_cmdproc_send_trans(cmdproc, trans);
@@ -670,7 +621,7 @@
 			continue;
 
 		msn_add_contact_xml(session, adl_node, user->passport,
-			user->list_op & MSN_LIST_OP_MASK, user->type);
+			user->list_op & MSN_LIST_OP_MASK, user->networkid);
 
 		/* each ADL command may contain up to 150 contacts */
 		if (++adl_count % 150 == 0 || l->next == NULL) {
@@ -743,14 +694,14 @@
 {
 	xmlnode *root, *domain_node;
 
-	purple_debug_misc("MSN Notification", "Parsing received ADL XML data\n");
+	purple_debug_misc("msn", "Parsing received ADL XML data\n");
 
 	g_return_if_fail(payload != NULL);
 
 	root = xmlnode_from_str(payload, (gssize) len);
 
 	if (root == NULL) {
-		purple_debug_info("MSN Notification", "Invalid XML!\n");
+		purple_debug_info("msn", "Invalid XML in ADL!\n");
 		return;
 	}
 	for (domain_node = xmlnode_get_child(root, "d"); domain_node; domain_node = xmlnode_get_next_twin(domain_node)) {
@@ -760,33 +711,18 @@
 		domain = xmlnode_get_attrib(domain_node, "n");
 
 		for (contact_node = xmlnode_get_child(domain_node, "c"); contact_node; contact_node = xmlnode_get_next_twin(contact_node)) {
-//			gchar *name = NULL, *friendlyname = NULL, *passport= NULL;
 			const gchar *list;
 			gint list_op = 0;
 
-//			name = xmlnode_get_attrib(contact_node, "n");
 			list = xmlnode_get_attrib(contact_node, "l");
 			if (list != NULL) {
 				list_op = atoi(list);
 			}
-//			friendlyname = xmlnode_get_attrib(contact_node, "f");
-
-//			passport = g_strdup_printf("%s@%s", name, domain);
-
-//			if (friendlyname != NULL) {
-//				decoded_friendlyname = g_strdup(purple_url_decode(friendlyname));
-//			} else {
-//				decoded_friendlyname = g_strdup(passport);
-//			}
 
 			if (list_op & MSN_LIST_RL_OP) {
 				/* someone is adding us */
-//				got_new_entry(cmdproc->session->account->gc, passport, decoded_friendly_name);
-				msn_get_contact_list(cmdproc->session->contact, MSN_PS_PENDING_LIST, NULL);
+				msn_get_contact_list(cmdproc->session, MSN_PS_PENDING_LIST, NULL);
 			}
-
-//			g_free(decoded_friendly_name);
-//			g_free(passport);
 		}
 	}
 
@@ -805,11 +741,12 @@
 
 	session = cmdproc->session;
 
-	if ( !strcmp(cmd->params[1], "OK")) {
+	if (!strcmp(cmd->params[1], "OK")) {
 		/* ADL ack */
 		msn_session_finish_login(session);
 	} else {
 		cmdproc->last_cmd->payload_cb = adl_cmd_parse;
+		cmd->payload_len = atoi(cmd->params[1]);
 	}
 
 	return;
@@ -827,7 +764,7 @@
 	account = session->account;
 	gc = purple_account_get_connection(account);
 
-	purple_debug_error("msn","ADL error\n");
+	purple_debug_error("msn", "ADL error\n");
 	reason = g_strdup_printf(_("Unknown error (%d)"), error);
 	purple_notify_error(gc, NULL, _("Unable to add user"), reason);
 	g_free(reason);
@@ -837,36 +774,34 @@
 fqy_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload,
 			 size_t len)
 {
-	purple_debug_info("MSN Notification","FQY payload:\n%s\n", payload);
+	purple_debug_info("msn", "FQY payload:\n%s\n", payload);
 	g_return_if_fail(cmdproc->session != NULL);
-	g_return_if_fail(cmdproc->session->contact != NULL);
-//	msn_notification_post_adl(cmdproc, payload, len);
-//	msn_get_address_book(cmdproc->session->contact, MSN_AB_SAVE_CONTACT, NULL, NULL);
+/*	msn_notification_post_adl(cmdproc, payload, len); */
+/*	msn_get_address_book(cmdproc->session, MSN_AB_SAVE_CONTACT, NULL, NULL); */
 }
 
 static void
 fqy_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
-	purple_debug_info("MSNP14","Process FQY\n");
+	purple_debug_info("msn", "Process FQY\n");
 	cmdproc->last_cmd->payload_cb = fqy_cmd_post;
+	cmd->payload_len = atoi(cmd->params[1]);
+}
+
+static void
+rml_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload,
+			 size_t len)
+{
+	if (payload != NULL)
+		purple_debug_info("msn", "Received RML:\n%s\n", payload);
 }
 
 static void
 rml_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
-#if 0
-	MsnTransaction *trans;
-	char * payload;
-#endif
-
-	purple_debug_info("MSNP14","Process RML\n");
-#if 0
-	trans = msn_transaction_new(cmdproc, "RML","");
-
-	msn_transaction_set_payload(trans, payload, strlen(payload));
-
-	msn_cmdproc_send_trans(cmdproc, trans);
-#endif
+	purple_debug_info("msn", "Process RML\n");
+	cmd->payload_len = atoi(cmd->params[1]);
+	cmdproc->last_cmd->payload_cb = rml_cmd_post;
 }
 
 static void
@@ -975,7 +910,7 @@
 			msn_userlist_move_buddy(userlist, data->who, data->old_group_name, group_name);
 			g_free(data->old_group_name);
 		} else {
-			// msn_add_contact_to_group(userlist, data, data->who, group_name);
+			/* msn_add_contact_to_group(userlist, data, data->who, group_name); */
 		}
 	}
 }
@@ -983,29 +918,7 @@
 static void
 qng_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
-	MsnSession *session;
-	static int count = 0;
-	const char *passport;
-	PurpleAccount *account;
-
-	session = cmdproc->session;
-	account = session->account;
-
-	if (session->passport_info.file == NULL)
-		return;
-
-	passport = purple_normalize(account, purple_account_get_username(account));
-
-	if ((strstr(passport, "@hotmail.") == NULL) &&
-		(strstr(passport, "@live.com") == NULL) &&
-		(strstr(passport, "@msn.com") == NULL))
-		return;
-
-	if (count++ < 26)
-		return;
-
-	count = 0;
-	msn_cmdproc_send(cmdproc, "URL", "%s", "INBOX");
+	/* TODO: Call PNG after the timeout specified. */
 }
 
 
@@ -1017,7 +930,7 @@
 
 	/* Tell libpurple that the user has signed off */
 	user = msn_userlist_find_user(cmdproc->session->userlist, cmd->params[0]);
-	user->status = "offline";
+	msn_user_set_state(user, NULL);
 	msn_user_update(user);
 
 	/* If we have an open MsnSlpLink with the user then close it */
@@ -1036,7 +949,7 @@
 	MsnUser *user;
 	MsnObject *msnobj;
 	unsigned long clientid;
-	int wlmclient;
+	int networkid;
 	const char *state, *passport, *friendly;
 
 	session = cmdproc->session;
@@ -1046,7 +959,7 @@
 	state    = cmd->params[1];
 	passport = cmd->params[2];
 	/*if a contact is actually on the WLM part or the yahoo part*/
-	wlmclient = atoi(cmd->params[3]);
+	networkid = atoi(cmd->params[3]);
 	friendly = purple_url_decode(cmd->params[4]);
 
 	user = msn_userlist_find_user(session->userlist, passport);
@@ -1055,7 +968,7 @@
 
 	msn_user_set_friendly_name(user, friendly);
 
-	if (session->protocol_ver >= 9 && cmd->param_count == 8)
+	if (cmd->param_count == 7)
 	{
 		msnobj = msn_object_new_from_string(purple_url_decode(cmd->params[6]));
 		msn_user_set_object(user, msnobj);
@@ -1063,6 +976,8 @@
 
 	clientid = strtoul(cmd->params[5], NULL, 10);
 	user->mobile = (clientid & MSN_CLIENT_CAP_MSNMOBILE) || (user->phone.mobile && user->phone.mobile[0] == '+');
+	msn_user_set_clientid(user, clientid);
+	msn_user_set_network(user, networkid);
 
 	msn_user_set_state(user, state);
 	msn_user_update(user);
@@ -1074,7 +989,8 @@
 	PurpleConnection *gc;
 	MsnUserList *userlist;
 	char *who = NULL, *text = NULL;
-	xmlnode *payloadNode, *from, *textNode;
+	const char *id = NULL;
+	xmlnode *payloadNode, *from, *msg, *textNode;
 
 	purple_debug_misc("msn", "Incoming Page: {%s}\n", payload);
 
@@ -1099,9 +1015,27 @@
 	   </NOTIFICATION>
 	*/
 
+	/* This is the payload if your message was too long:
+	   <NOTIFICATION id="TrID" siteid="111100400" siteurl="http://mobile.msn.com/">
+	     <TO name="passport@example.com">
+	       <VIA agent="mobile"/>
+	     </TO>
+	     <FROM name="tel:+XXXXXXXXXXX"/>
+	     <MSG pri="1" id="407">
+	       <CAT Id="110110001"/>
+	       <ACTION url="2wayIM.asp"/>
+	       <SUBSCR url="2wayIM.asp"/>
+	       <BODY lcid="1033">
+	         <TEXT></TEXT>
+	       </BODY>
+	     </MSG>
+	   </NOTIFICATION>
+	*/
+
 	if (!(payloadNode = xmlnode_from_str(payload, len)) ||
 		!(from = xmlnode_get_child(payloadNode, "FROM")) ||
-		!(textNode = xmlnode_get_child(payloadNode, "MSG/BODY/TEXT")))
+		!(msg = xmlnode_get_child(payloadNode, "MSG")) ||
+		!(textNode = xmlnode_get_child(msg, "BODY/TEXT")))
 		return;
 
 	who = g_strdup(xmlnode_get_attrib(from, "name"));
@@ -1111,7 +1045,7 @@
 
 	/* Match number to user's mobile number, FROM is a phone number if the
 	   other side page you using your phone number */
-	if(!strncmp(who, "tel:+", 5)) {
+	if (!strncmp(who, "tel:+", 5)) {
 		MsnUser *user =
 			msn_userlist_find_user_with_mobile_phone(userlist, who + 4);
 
@@ -1121,7 +1055,20 @@
 		}
 	}
 
-	serv_got_im(gc, who, text, 0, time(NULL));
+	id = xmlnode_get_attrib(msg, "id");
+
+	if (id && !strcmp(id, "407")) {
+		/* TODO: Use this to NAK the transaction, maybe print the text, too.
+		unsigned int trId;
+		id = xmlnode_get_attrib(payloadNode, "id");
+		trId = atol(id);
+		*/
+		purple_conv_present_error(who, gc->account,
+			_("Mobile message was not sent because it was too long."));
+
+	} else {
+		serv_got_im(gc, who, text, 0, time(NULL));
+	}
 
 	g_free(text);
 	g_free(who);
@@ -1131,7 +1078,7 @@
 static void
 ipg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
-	cmdproc->servconn->payload_len = atoi(cmd->params[0]);
+	cmd->payload_len = atoi(cmd->params[0]);
 	cmdproc->last_cmd->payload_cb = ipg_cmd_post;
 }
 
@@ -1144,7 +1091,7 @@
 	MsnUser *user;
 	MsnObject *msnobj;
 	unsigned long clientid;
-	int wlmclient;
+	int networkid;
 	const char *state, *passport, *friendly, *old_friendly;
 
 	session = cmdproc->session;
@@ -1153,7 +1100,7 @@
 
 	state    = cmd->params[0];
 	passport = cmd->params[1];
-	wlmclient = atoi(cmd->params[2]);
+	networkid = atoi(cmd->params[2]);
 	friendly = purple_url_decode(cmd->params[3]);
 
 	user = msn_userlist_find_user(session->userlist, passport);
@@ -1165,22 +1112,22 @@
 		msn_user_set_friendly_name(user, friendly);
 	}
 
-	if (session->protocol_ver >= 9)
+	if (cmd->param_count == 6)
 	{
-		if (cmd->param_count == 7)
-		{
-			msnobj = msn_object_new_from_string(purple_url_decode(cmd->params[5]));
-			msn_user_set_object(user, msnobj);
-		}
-		else
-		{
-			msn_user_set_object(user, NULL);
-		}
+		msnobj = msn_object_new_from_string(purple_url_decode(cmd->params[5]));
+		msn_user_set_object(user, msnobj);
+	}
+	else
+	{
+		msn_user_set_object(user, NULL);
 	}
 
 	clientid = strtoul(cmd->params[4], NULL, 10);
 	user->mobile = (clientid & MSN_CLIENT_CAP_MSNMOBILE) || (user->phone.mobile && user->phone.mobile[0] == '+');
 
+	msn_user_set_clientid(user, clientid);
+	msn_user_set_network(user, networkid);
+
 	msn_user_set_state(user, state);
 	msn_user_update(user);
 }
@@ -1231,35 +1178,6 @@
 }
 
 static void
-rea_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
-{
-	MsnSession *session;
-	PurpleAccount *account;
-	PurpleConnection *gc;
-	const char *friendly;
-	char *username;
-
-	session = cmdproc->session;
-	account = session->account;
-	username = g_strdup(purple_normalize(account,
-						purple_account_get_username(account)));
-
-	/* Only set display name if our *own* friendly name changed! */
-	if (strcmp(username, purple_normalize(account, cmd->params[2])))
-	{
-		g_free(username);
-		return;
-	}
-
-	g_free(username);
-
-	gc = account->gc;
-	friendly = purple_url_decode(cmd->params[3]);
-
-	purple_connection_set_display_name(gc, friendly);
-}
-
-static void
 prp_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
 	MsnSession *session = cmdproc->session;
@@ -1292,7 +1210,7 @@
 			if (!strcmp(type, "MFN")) {
 				friendlyname = purple_url_decode(cmd->params[2]);
 
-				msn_update_contact(session->contact, friendlyname);
+				msn_update_contact(session, "Me", MSN_UPDATE_DISPLAY, friendlyname);
 
 				purple_connection_set_display_name(
 					purple_account_get_connection(session->account),
@@ -1330,34 +1248,6 @@
 	g_strfreev(params);
 }
 
-#if 0
-static void
-rem_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
-{
-	MsnSession *session;
-	MsnUser *user;
-	const char *group_id, *list, *passport;
-	MsnListId list_id;
-
-	session = cmdproc->session;
-	list = cmd->params[1];
-	passport = cmd->params[3];
-	user = msn_userlist_find_user(session->userlist, passport);
-
-	g_return_if_fail(user != NULL);
-
-	list_id = msn_get_list_id(list);
-
-	if (cmd->param_count == 5)
-		group_id = cmd->params[4];
-	else
-		group_id = NULL;
-
-	msn_got_rem_user(session, user, list_id, group_id);
-	msn_user_update(user);
-}
-#endif
-
 static void
 rmg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
@@ -1385,39 +1275,6 @@
 	g_strfreev(params);
 }
 
-#if 0
-static void
-syn_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
-{
-	MsnSession *session;
-	MsnSync *sync;
-	int total_users;
-
-	session = cmdproc->session;
-
-	if (cmd->param_count == 2)
-	{
-		/*
-		 * This can happen if we sent a SYN with an up-to-date
-		 * buddy list revision, but we send 0 to get a full list.
-		 * So, error out.
-		 */
-
-		msn_session_set_error(cmdproc->session, MSN_ERROR_BAD_BLIST, NULL);
-		return;
-	}
-
-	total_users  = atoi(cmd->params[2]);
-
-	sync = msn_sync_new(session);
-	sync->total_users = total_users;
-	sync->old_cbs_table = cmdproc->cbs_table;
-
-	session->sync = sync;
-	cmdproc->cbs_table = sync->cbs_table;
-}
-#endif
-
 /**************************************************************************
  * Misc commands
  **************************************************************************/
@@ -1426,136 +1283,51 @@
 url_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
 	MsnSession *session;
+	PurpleConnection *gc;
 	PurpleAccount *account;
 	const char *rru;
 	const char *url;
-	PurpleCipher *cipher;
-	PurpleCipherContext *context;
-	guchar digest[16];
-	FILE *fd;
+	PurpleCipherContext *cipher;
+	gchar digest[33];
 	char *buf;
-	char buf2[3];
-	char sendbuf[64];
-	int i;
+
+	gulong tmp_timestamp;
 
 	session = cmdproc->session;
 	account = session->account;
+	gc = account->gc;
 
 	rru = cmd->params[1];
 	url = cmd->params[2];
 
+	session->passport_info.mail_timestamp = time(NULL);
+	tmp_timestamp = session->passport_info.mail_timestamp - session->passport_info.sl;
+
 	buf = g_strdup_printf("%s%lu%s",
 			   session->passport_info.mspauth ? session->passport_info.mspauth : "BOGUS",
-			   time(NULL) - session->passport_info.sl,
-			   purple_connection_get_password(account->gc));
+			   tmp_timestamp,
+			   purple_connection_get_password(gc));
 
-	cipher = purple_ciphers_find_cipher("md5");
-	context = purple_cipher_context_new(cipher, NULL);
-
-	purple_cipher_context_append(context, (const guchar *)buf, strlen(buf));
-	purple_cipher_context_digest(context, sizeof(digest), digest, NULL);
-	purple_cipher_context_destroy(context);
+	cipher = purple_cipher_context_new_by_name("md5", NULL);
+	purple_cipher_context_append(cipher, (const guchar *)buf, strlen(buf));
+	purple_cipher_context_digest_to_str(cipher, sizeof(digest), digest, NULL);
+	purple_cipher_context_destroy(cipher);
 
 	g_free(buf);
 
-	memset(sendbuf, 0, sizeof(sendbuf));
-
-	for (i = 0; i < 16; i++)
-	{
-		g_snprintf(buf2, sizeof(buf2), "%02x", digest[i]);
-		strcat(sendbuf, buf2);
-	}
-
-	if (session->passport_info.file != NULL)
-	{
-		g_unlink(session->passport_info.file);
-		g_free(session->passport_info.file);
-	}
-
-	if ((fd = purple_mkstemp(&session->passport_info.file, FALSE)) == NULL)
-	{
-		purple_debug_error("msn",
-						 "Error opening temp passport file: %s\n",
-						 g_strerror(errno));
-	}
-	else
-	{
-#ifdef _WIN32
-		fputs("<!-- saved from url=(0013)about:internet -->\n", fd);
-#endif
-		fputs("<html>\n"
-			  "<head>\n"
-			  "<noscript>\n"
-			  "<meta http-equiv=\"Refresh\" content=\"0; "
-			  "url=http://www.hotmail.com\">\n"
-			  "</noscript>\n"
-			  "</head>\n\n",
-			  fd);
+	g_free(session->passport_info.mail_url);
+	session->passport_info.mail_url = g_strdup_printf("%s&auth=%s&creds=%s&sl=%ld&username=%s&mode=ttl&sid=%s&id=2&rru=%ssvc_mail&js=yes",
+                                                        url,
+                                                        session->passport_info.mspauth ? session->passport_info.mspauth : "BOGUS",
+                                                        buf,
+                                                        tmp_timestamp,
+                                                        msn_user_get_passport(session->user),
+                                                        session->passport_info.sid,
+                                                        rru);
 
-		fprintf(fd, "<body onload=\"document.pform.submit(); \">\n");
-		fprintf(fd, "<form name=\"pform\" action=\"%s\" method=\"POST\">\n\n",
-				url);
-		fprintf(fd, "<input type=\"hidden\" name=\"mode\" value=\"ttl\">\n");
-		fprintf(fd, "<input type=\"hidden\" name=\"login\" value=\"%s\">\n",
-				purple_account_get_username(account));
-		fprintf(fd, "<input type=\"hidden\" name=\"username\" value=\"%s\">\n",
-				purple_account_get_username(account));
-		if (session->passport_info.sid != NULL)
-			fprintf(fd, "<input type=\"hidden\" name=\"sid\" value=\"%s\">\n",
-					session->passport_info.sid);
-		if (session->passport_info.kv != NULL)
-			fprintf(fd, "<input type=\"hidden\" name=\"kv\" value=\"%s\">\n",
-					session->passport_info.kv);
-		fprintf(fd, "<input type=\"hidden\" name=\"id\" value=\"2\">\n");
-		fprintf(fd, "<input type=\"hidden\" name=\"sl\" value=\"%ld\">\n",
-				time(NULL) - session->passport_info.sl);
-		fprintf(fd, "<input type=\"hidden\" name=\"rru\" value=\"%s\">\n",
-				rru);
-		if (session->passport_info.mspauth != NULL)
-			fprintf(fd, "<input type=\"hidden\" name=\"auth\" value=\"%s\">\n",
-					session->passport_info.mspauth);
-		fprintf(fd, "<input type=\"hidden\" name=\"creds\" value=\"%s\">\n",
-				sendbuf); /* TODO Digest me (huh? -- ChipX86) */
-		fprintf(fd, "<input type=\"hidden\" name=\"svc\" value=\"mail\">\n");
-		fprintf(fd, "<input type=\"hidden\" name=\"js\" value=\"yes\">\n");
-		fprintf(fd, "</form></body>\n");
-		fprintf(fd, "</html>\n");
-
-		if (fclose(fd))
-		{
-			purple_debug_error("msn",
-							 "Error closing temp passport file: %s\n",
-							 g_strerror(errno));
-
-			g_unlink(session->passport_info.file);
-			g_free(session->passport_info.file);
-			session->passport_info.file = NULL;
-		}
-#ifdef _WIN32
-		else
-		{
-			/*
-			 * Renaming file with .html extension, so that the
-			 * win32 open_url will work.
-			 */
-			char *tmp;
-
-			if ((tmp =
-				g_strdup_printf("%s.html",
-					session->passport_info.file)) != NULL)
-			{
-				if (g_rename(session->passport_info.file,
-							tmp) == 0)
-				{
-					g_free(session->passport_info.file);
-					session->passport_info.file = tmp;
-				}
-				else
-					g_free(tmp);
-			}
-		}
-#endif
-	}
+	/* The user wants to check his or her email */
+	if (cmd->trans && cmd->trans->data)
+		purple_notify_uri(purple_account_get_connection(account), session->passport_info.mail_url);
 }
 /**************************************************************************
  * Switchboards
@@ -1629,42 +1401,62 @@
 gcf_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload,
 			 size_t len)
 {
-	xmlnode * root;
-	gchar * buf;
-	int xmllen;
+/* QuLogic: Disabled until confirmed correct. */
+#if 0
+	xmlnode *root;
+	xmlnode *policy;
 
 	g_return_if_fail(cmd->payload != NULL);
 
 	if ( (root = xmlnode_from_str(cmd->payload, cmd->payload_len)) == NULL)
 	{
-		purple_debug_error("MSN","Unable to parse GCF payload into a XML tree");
+		purple_debug_error("msn", "Unable to parse GCF payload into a XML tree");
 		return;
 	}
 
-	buf = xmlnode_to_formatted_str(root, &xmllen);
+
+	g_free(cmdproc->session->blocked_text);
+	cmdproc->session->blocked_text = NULL;
+
+	/* We need a get_child with attrib... */
+	policy = xmlnode_get_child(root, "Policy");
+	while (policy) {
+		if (g_str_equal(xmlnode_get_attrib(policy, "type"), "SHIELDS"))
+			break;
+		policy = xmlnode_get_next_twin(policy);
+	}
 
-	/* get the payload content */
-	purple_debug_info("MSNP14","GCF command payload:\n%.*s\n", xmllen, buf);
+	if (policy) {
+		GString *blocked = g_string_new(NULL);
+		xmlnode *imtext = xmlnode_get_child(policy,
+		                                    "config/block/regexp/imtext");
+		while (imtext) {
+			const char *value = xmlnode_get_attrib(imtext, "value");
+			g_string_append_printf(blocked, "%s<br/>\n",
+			                       purple_base64_decode(value, NULL));
+			imtext = xmlnode_get_next_twin(imtext);
+		}
 
-	g_free(buf);
+		cmdproc->session->blocked_text = g_string_free(blocked, FALSE);
+	}
+
 	xmlnode_free(root);
+#endif
 }
 
 static void
 gcf_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
-	purple_debug_info("MSNP14","Processing GCF command\n");
+	purple_debug_info("msn", "Processing GCF command\n");
+
 	cmdproc->last_cmd->payload_cb  = gcf_cmd_post;
-	return;
+	cmd->payload_len = atoi(cmd->params[1]);
 }
 
 static void
 sbs_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
-	purple_debug_info("MSNP14","Processing SBS... \n");
-	if(cmd->payload_len == 0){
-		return;
-	}
+	purple_debug_info("msn", "Processing SBS... \n");
 	/*get the payload content*/
 }
 
@@ -1710,17 +1502,26 @@
 static void
 ubx_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
-	purple_debug_misc("MSNP14","UBX received.\n");
-	if(cmd->payload_len == 0){
-		return;
-	}
+	purple_debug_misc("msn", "UBX received.\n");
 	cmdproc->last_cmd->payload_cb  = ubx_cmd_post;
+	cmd->payload_len = atoi(cmd->params[2]);
+}
+
+static void
+uux_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload,
+			 size_t len)
+{
+	/* Do Nothing, right now. */
+	if (payload != NULL)
+		purple_debug_info("msn", "UUX payload:\n%s\n", payload);
 }
 
 static void
 uux_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
-	purple_debug_misc("MSNP14","UUX received.\n");
+	purple_debug_misc("msn", "UUX received.\n");
+	cmdproc->last_cmd->payload_cb = uux_cmd_post;
+	cmd->payload_len = atoi(cmd->params[1]);
 }
 
 /**************************************************************************
@@ -1772,19 +1573,21 @@
 	if ((value = msn_message_get_attr(msg, "LoginTime")) != NULL)
 		session->passport_info.sl = atol(value);
 
+	if ((value = msn_message_get_attr(msg, "EmailEnabled")) != NULL)
+		session->passport_info.email_enabled = (gboolean)atol(value);
+
 	/*starting retrieve the contact list*/
 	clLastChange = purple_account_get_string(session->account, "CLLastChange", NULL);
-	session->contact = msn_contact_new(session);
 #ifdef MSN_PARTIAL_LISTS
 	/* msn_userlist_load defeats all attempts at trying to detect blist sync issues */
 	msn_userlist_load(session);
-	msn_get_contact_list(session->contact, MSN_PS_INITIAL, clLastChange);
+	msn_get_contact_list(session, MSN_PS_INITIAL, clLastChange);
 #else
 	/* always get the full list? */
-	msn_get_contact_list(session->contact, MSN_PS_INITIAL, NULL);
+	msn_get_contact_list(session, MSN_PS_INITIAL, NULL);
 #endif
 #if 0
-	msn_contact_connect(session->contact);
+	msn_contact_connect(session);
 #endif
 }
 
@@ -1803,7 +1606,7 @@
 		/* This isn't an official message. */
 		return;
 
-	if (session->passport_info.file == NULL)
+	if (session->passport_info.mail_url == NULL)
 	{
 		MsnTransaction *trans;
 		trans = msn_transaction_new(cmdproc, "URL", "%s", "INBOX");
@@ -1831,7 +1634,7 @@
 			const char *url;
 
 			passport = msn_user_get_passport(session->user);
-			url = session->passport_info.file;
+			url = session->passport_info.mail_url;
 
 			purple_notify_emails(gc, count, FALSE, NULL, NULL,
 							   &passport, &url, NULL, NULL);
@@ -1857,10 +1660,6 @@
 		/* This isn't an official message. */
 		return;
 
-	/*new a oim session*/
-//	session->oim = msn_oim_new(session);
-//	msn_oim_connect(session->oim);
-
 	table = msn_message_get_hashtable_from_body(msg);
 
 	mdata = g_hash_table_lookup(table, "Mail-Data");
@@ -1874,7 +1673,7 @@
 		return;
 	}
 
-	if (session->passport_info.file == NULL)
+	if (session->passport_info.mail_url == NULL)
 	{
 		MsnTransaction *trans;
 		trans = msn_transaction_new(cmdproc, "URL", "%s", "INBOX");
@@ -1904,7 +1703,7 @@
 			const char *url;
 
 			passport = msn_user_get_passport(session->user);
-			url = session->passport_info.file;
+			url = session->passport_info.mail_url;
 
 			purple_notify_emails(gc, count, FALSE, NULL, NULL,
 							   &passport, &url, NULL, NULL);
@@ -1918,7 +1717,7 @@
 static void
 delete_oim_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
 {
-	purple_debug_misc("MSN Notification","Delete OIM message.\n");
+	purple_debug_misc("msn", "Delete OIM message.\n");
 }
 
 static void
@@ -1936,7 +1735,7 @@
 		/* This isn't an official message. */
 		return;
 
-	if (session->passport_info.file == NULL)
+	if (session->passport_info.mail_url == NULL)
 	{
 		MsnTransaction *trans;
 		trans = msn_transaction_new(cmdproc, "URL", "%s", "INBOX");
@@ -1966,7 +1765,7 @@
 					  (subject != NULL ? subject : ""),
 					  (from != NULL ?  from : ""),
 					  msn_user_get_passport(session->user),
-					  session->passport_info.file, NULL, NULL);
+					  session->passport_info.mail_url, NULL, NULL);
 
 	g_free(from);
 	g_free(subject);
@@ -2037,7 +1836,7 @@
 	adl_node->child = NULL;
 
 	msn_add_contact_xml(notification->session, adl_node, who, list_op,
-						MSN_USER_TYPE_PASSPORT);
+						MSN_NETWORK_PASSPORT);
 
 	payload = xmlnode_to_str(adl_node,&payload_len);
 	xmlnode_free(adl_node);
@@ -2063,12 +1862,12 @@
 	rml_node = xmlnode_new("ml");
 	rml_node->child = NULL;
 
-	msn_add_contact_xml(notification->session, rml_node, who, list_op, MSN_USER_TYPE_PASSPORT);
+	msn_add_contact_xml(notification->session, rml_node, who, list_op, MSN_NETWORK_PASSPORT);
 
 	payload = xmlnode_to_str(rml_node, &payload_len);
 	xmlnode_free(rml_node);
 
-	purple_debug_info("MSN Notification","Send RML with payload:\n%s\n", payload);
+	purple_debug_info("msn", "Send RML with payload:\n%s\n", payload);
 	trans = msn_transaction_new(cmdproc, "RML","%" G_GSIZE_FORMAT, strlen(payload));
 	msn_transaction_set_payload(trans, payload, strlen(payload));
 	msn_cmdproc_send_trans(cmdproc, trans);
@@ -2081,25 +1880,19 @@
 void
 msn_notification_init(void)
 {
-	/* TODO: check prp, blp */
-
 	cbs_table = msn_table_new();
 
 	/* Synchronous */
 	msn_table_add_cmd(cbs_table, "CHG", "CHG", NULL);
 	msn_table_add_cmd(cbs_table, "CHG", "ILN", iln_cmd);
 	msn_table_add_cmd(cbs_table, "ADL", "ILN", iln_cmd);
-//	msn_table_add_cmd(cbs_table, "REM", "REM", rem_cmd);	/* Removed as of MSNP13 */
 	msn_table_add_cmd(cbs_table, "USR", "USR", usr_cmd);
 	msn_table_add_cmd(cbs_table, "USR", "XFR", xfr_cmd);
 	msn_table_add_cmd(cbs_table, "USR", "GCF", gcf_cmd);
-//	msn_table_add_cmd(cbs_table, "SYN", "SYN", syn_cmd);	/* Removed as of MSNP13 */
 	msn_table_add_cmd(cbs_table, "CVR", "CVR", cvr_cmd);
 	msn_table_add_cmd(cbs_table, "VER", "VER", ver_cmd);
-	msn_table_add_cmd(cbs_table, "REA", "REA", rea_cmd);
 	msn_table_add_cmd(cbs_table, "PRP", "PRP", prp_cmd);
 	msn_table_add_cmd(cbs_table, "BLP", "BLP", blp_cmd);
-//	msn_table_add_cmd(cbs_table, "BLP", "BLP", NULL);
 	msn_table_add_cmd(cbs_table, "REG", "REG", reg_cmd);
 	msn_table_add_cmd(cbs_table, "ADG", "ADG", adg_cmd);
 	msn_table_add_cmd(cbs_table, "RMG", "RMG", rmg_cmd);
@@ -2137,7 +1930,6 @@
 	msn_table_add_error(cbs_table, "ADL", adl_error);
 	msn_table_add_error(cbs_table, "REG", reg_error);
 	msn_table_add_error(cbs_table, "RMG", rmg_error);
-	/* msn_table_add_error(cbs_table, "REA", rea_error); */
 	msn_table_add_error(cbs_table, "USR", usr_error);
 
 	msn_table_add_msg_type(cbs_table,
--- a/libpurple/protocols/msn/notification.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/notification.h	Thu Nov 20 21:13:56 2008 +0000
@@ -25,6 +25,11 @@
 #define _MSN_NOTIFICATION_H_
 
 /*MSN protocol challenge info*/
+
+/*MSNP15 challenge: WLM 8.5.1288.816*/
+#define MSNP15_WLM_PRODUCT_KEY "ILTXC!4IXB5FB*PX"
+#define MSNP15_WLM_PRODUCT_ID "PROD0119GSJUC$18"
+
 /*MSNP13 challenge*/
 #define MSNP13_WLM_PRODUCT_KEY	"O4BG@C7BWLYQX?5G"
 #define MSNP13_WLM_PRODUCT_ID	"PROD01065C%ZFN6F"
@@ -81,6 +86,6 @@
  */
 void msn_notification_close(MsnNotification *notification);
 
-void msn_got_login_params(MsnSession *session, const char *login_params);
+void msn_got_login_params(MsnSession *session, const char *ticket, const char *response);
 
 #endif /* _MSN_NOTIFICATION_H_ */
--- a/libpurple/protocols/msn/oim.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/oim.c	Thu Nov 20 21:13:56 2008 +0000
@@ -24,7 +24,7 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 #include "msn.h"
-#include "soap2.h"
+#include "soap.h"
 #include "oim.h"
 #include "msnutils.h"
 
@@ -41,6 +41,7 @@
 } MsnOimRecvData;
 
 /*Local Function Prototype*/
+static void msn_parse_oim_xml(MsnOim *oim, xmlnode *node);
 static void msn_oim_post_single_get_msg(MsnOim *oim, char *msgid);
 static MsnOimSendReq *msn_oim_new_send_req(const char *from_member,
 										   const char *friendname,
@@ -72,7 +73,7 @@
 {
 	MsnOimSendReq *request;
 
-	purple_debug_info("OIM", "destroy the OIM %p\n", oim);
+	purple_debug_info("msn", "destroy the OIM %p\n", oim);
 	g_free(oim->run_id);
 	g_free(oim->challenge);
 
@@ -114,22 +115,195 @@
 }
 
 /****************************************
+ * Manage OIM Tokens
+ ****************************************/
+typedef struct _MsnOimRequestData {
+	MsnOim *oim;
+	gboolean send;
+	const char *action;
+	const char *host;
+	const char *url;
+	xmlnode *body;
+	MsnSoapCallback cb;
+	gpointer cb_data;
+} MsnOimRequestData;
+
+static void msn_oim_request_helper(MsnOimRequestData *data);
+
+static void
+msn_oim_request_cb(MsnSoapMessage *request, MsnSoapMessage *response,
+	gpointer req_data)
+{
+	MsnOimRequestData *data = (MsnOimRequestData *)req_data;
+	xmlnode *fault = NULL;
+	xmlnode *faultcode = NULL;
+
+	if (response == NULL)
+		return;
+
+	fault = xmlnode_get_child(response->xml, "Body/Fault");
+	if (fault)
+		faultcode = xmlnode_get_child(fault, "faultcode");
+
+	if (faultcode) {
+		gchar *faultcode_str = xmlnode_get_data(faultcode);
+
+		if (faultcode_str && g_str_equal(faultcode_str, "q0:BadContextToken")) {
+			purple_debug_warning("msn", "OIM Request Error, Updating token now.");
+			msn_nexus_update_token(data->oim->session->nexus,
+				data->send ? MSN_AUTH_LIVE_SECURE : MSN_AUTH_MESSENGER_WEB,
+				(GSourceFunc)msn_oim_request_helper, data);
+			g_free(faultcode_str);
+			return;
+
+		} else if (faultcode_str && g_str_equal(faultcode_str, "q0:AuthenticationFailed")) {
+			if (xmlnode_get_child(fault, "detail/RequiredAuthPolicy") != NULL) {
+				purple_debug_warning("msn", "OIM Request Error, Updating token now.");
+				msn_nexus_update_token(data->oim->session->nexus,
+					data->send ? MSN_AUTH_LIVE_SECURE : MSN_AUTH_MESSENGER_WEB,
+					(GSourceFunc)msn_oim_request_helper, data);
+				g_free(faultcode_str);
+				return;
+			}
+		}
+		g_free(faultcode_str);
+	}
+
+	if (data->cb)
+		data->cb(request, response, data->cb_data);
+	xmlnode_free(data->body);
+	g_free(data);
+}
+
+static void
+msn_oim_request_helper(MsnOimRequestData *data)
+{
+	MsnSession *session = data->oim->session;
+
+	if (data->send) {
+		/* The Sending of OIM's uses a different token for some reason. */
+		xmlnode *ticket;
+		ticket = xmlnode_get_child(data->body, "Header/Ticket");
+		xmlnode_set_attrib(ticket, "passport",
+			msn_nexus_get_token_str(session->nexus, MSN_AUTH_LIVE_SECURE));
+	}
+	else
+	{
+		xmlnode *passport;
+		xmlnode *xml_t;
+		xmlnode *xml_p;
+		GHashTable *token;
+		const char *msn_t;
+		const char *msn_p;
+
+		token = msn_nexus_get_token(session->nexus, MSN_AUTH_MESSENGER_WEB);
+		g_return_if_fail(token != NULL);
+
+		msn_t = g_hash_table_lookup(token, "t");
+		msn_p = g_hash_table_lookup(token, "p");
+
+		g_return_if_fail(msn_t != NULL);
+		g_return_if_fail(msn_p != NULL);
+
+		passport = xmlnode_get_child(data->body, "Header/PassportCookie");
+		xml_t = xmlnode_get_child(passport, "t");
+		xml_p = xmlnode_get_child(passport, "p");
+
+		/* frees old token text, or the 'EMPTY' text if first time */
+		xmlnode_free(xml_t->child);
+		xmlnode_free(xml_p->child);
+
+		xmlnode_insert_data(xml_t, msn_t, -1);
+		xmlnode_insert_data(xml_p, msn_p, -1);
+	}
+
+	msn_soap_message_send(session,
+		msn_soap_message_new(data->action, xmlnode_copy(data->body)),
+		data->host, data->url, FALSE,
+		msn_oim_request_cb, data);
+}
+
+
+static void
+msn_oim_make_request(MsnOim *oim, gboolean send, const char *action,
+	const char *host, const char *url, xmlnode *body, MsnSoapCallback cb,
+	gpointer cb_data)
+{
+	MsnOimRequestData *data = g_new0(MsnOimRequestData, 1);
+	data->oim = oim;
+	data->send = send;
+	data->action = action;
+	data->host = host;
+	data->url = url;
+	data->body = body;
+	data->cb = cb;
+	data->cb_data = cb_data;
+
+	msn_oim_request_helper(data);
+}
+
+/****************************************
+ * OIM GetMetadata request
+ * **************************************/
+static void
+msn_oim_get_metadata_cb(MsnSoapMessage *request, MsnSoapMessage *response,
+	gpointer data)
+{
+	MsnOim *oim = data;
+
+	if (response) {
+		msn_parse_oim_xml(oim,
+			xmlnode_get_child(response->xml, "Body/GetMetadataResponse/MD"));
+	}
+}
+
+/* Post to get the OIM Metadata */
+static void
+msn_oim_get_metadata(MsnOim *oim)
+{
+	msn_oim_make_request(oim, FALSE, MSN_OIM_GET_METADATA_ACTION,
+		MSN_OIM_RETRIEVE_HOST, MSN_OIM_RETRIEVE_URL,
+		xmlnode_from_str(MSN_OIM_GET_METADATA_TEMPLATE, -1),
+		msn_oim_get_metadata_cb, oim);
+}
+
+/****************************************
  * OIM send SOAP request
  * **************************************/
 /*encode the message to OIM Message Format*/
 static gchar *
 msn_oim_msg_to_str(MsnOim *oim, const char *body)
 {
-	char *oim_body,*oim_base64;
+	GString *oim_body;
+	char *oim_base64;
+	char *c;
+	int len;
+	size_t base64_len;
+
+	purple_debug_info("msn", "Encoding OIM Message...\n");
+	len = strlen(body);
+	c = oim_base64 = purple_base64_encode((const guchar *)body, len);
+	base64_len = strlen(oim_base64);
+	purple_debug_info("msn", "Encoded base64 body:{%s}\n", oim_base64);
 
-	purple_debug_info("MSN OIM","encode OIM Message...\n");
-	oim_base64 = purple_base64_encode((const guchar *)body, strlen(body));
-	purple_debug_info("MSN OIM","encoded base64 body:{%s}\n",oim_base64);
-	oim_body = g_strdup_printf(MSN_OIM_MSG_TEMPLATE,
-				oim->run_id,oim->send_seq,oim_base64);
+	oim_body = g_string_new(NULL);
+	g_string_printf(oim_body, MSN_OIM_MSG_TEMPLATE,
+		oim->run_id, oim->send_seq);
+
+#define OIM_LINE_LEN 76
+	while (base64_len > OIM_LINE_LEN) {
+		g_string_append_len(oim_body, c, OIM_LINE_LEN);
+		g_string_append_c(oim_body, '\n');
+		c += OIM_LINE_LEN;
+		base64_len -= OIM_LINE_LEN;
+	}
+#undef OIM_LINE_LEN
+
+	g_string_append(oim_body, c);
+
 	g_free(oim_base64);
 
-	return oim_body;
+	return g_string_free(oim_body, FALSE);
 }
 
 /*
@@ -146,13 +320,13 @@
 	g_return_if_fail(msg != NULL);
 
 	if (response == NULL) {
-		purple_debug_info("MSNP14", "cannot send OIM: %s\n", msg->oim_msg);
+		purple_debug_info("msn", "cannot send OIM: %s\n", msg->oim_msg);
 	} else {
-		xmlnode	*faultNode = msn_soap_xml_get(response->xml, "Body/Fault");
+		xmlnode	*faultNode = xmlnode_get_child(response->xml, "Body/Fault");
 
 		if (faultNode == NULL) {
 			/*Send OK! return*/
-			purple_debug_info("MSNP14", "sent OIM: %s\n", msg->oim_msg);
+			purple_debug_info("msn", "sent OIM: %s\n", msg->oim_msg);
 		} else {
 			xmlnode *faultcode = xmlnode_get_child(faultNode, "faultcode");
 
@@ -160,7 +334,7 @@
 				char *faultcode_str = xmlnode_get_data(faultcode);
 
 				if (g_str_equal(faultcode_str, "q0:AuthenticationFailed")) {
-					xmlnode *challengeNode = msn_soap_xml_get(faultNode,
+					xmlnode *challengeNode = xmlnode_get_child(faultNode,
 						"detail/LockKeyChallenge");
 
 					if (challengeNode == NULL) {
@@ -168,13 +342,13 @@
 							g_free(oim->challenge);
 							oim->challenge = NULL;
 
-							purple_debug_info("msnoim","resending OIM: %s\n",
+							purple_debug_info("msn", "Resending OIM: %s\n",
 								msg->oim_msg);
 							g_queue_push_head(oim->send_queue, msg);
 							msn_oim_send_msg(oim);
 						} else {
-							purple_debug_info("msnoim",
-								"can't find lock key for OIM: %s\n",
+							purple_debug_info("msn",
+								"Can't find lock key for OIM: %s\n",
 								msg->oim_msg);
 						}
 					} else {
@@ -186,13 +360,39 @@
 						g_free(oim->challenge);
 						oim->challenge = g_strndup(buf, sizeof(buf));
 						g_free(challenge);
-						purple_debug_info("MSNP14","lockkey:{%s}\n",oim->challenge);
+						purple_debug_info("msn", "Found lockkey:{%s}\n", oim->challenge);
 
 						/*repost the send*/
-						purple_debug_info("MSNP14","resending OIM: %s\n", msg->oim_msg);
+						purple_debug_info("msn", "Resending OIM: %s\n", msg->oim_msg);
 						g_queue_push_head(oim->send_queue, msg);
 						msn_oim_send_msg(oim);
 					}
+				} else {
+					/* Report the error */
+					const char *str_reason;
+
+					if (g_str_equal(faultcode_str, "q0:SystemUnavailable")) {
+						str_reason = _("Message was not sent because the system is "
+						               "unavailable. This normally happens when the "
+						               "user is blocked or does not exist.");
+
+					} else if (g_str_equal(faultcode_str, "q0:SenderThrottleLimitExceeded")) {
+						str_reason = _("Message was not sent because messages "
+						               "are being sent too quickly.");
+
+					} else if (g_str_equal(faultcode_str, "q0:InvalidContent")) {
+						str_reason = _("Message was not sent because an unknown "
+						               "encoding error occurred.");
+
+					} else {
+						str_reason = _("Message was not sent because an unknown "
+						               "error occurred.");
+					}
+					
+					msn_session_report_user(oim->session, msg->to_member, 
+						str_reason, PURPLE_MESSAGE_ERROR);
+					msn_session_report_user(oim->session, msg->to_member,
+						msg->oim_msg, PURPLE_MESSAGE_RAW);
 				}
 
 				g_free(faultcode_str);
@@ -217,24 +417,20 @@
 msn_oim_send_msg(MsnOim *oim)
 {
 	MsnOimSendReq *oim_request;
-	char *soap_body,*mspauth;
+	char *soap_body;
 	char *msg_body;
 
 	g_return_if_fail(oim != NULL);
 	oim_request = g_queue_peek_head(oim->send_queue);
 	g_return_if_fail(oim_request != NULL);
 
-	purple_debug_info("MSNP14","sending OIM: %s\n", oim_request->oim_msg);
-	mspauth = g_strdup_printf("t=%s&amp;p=%s",
-		oim->session->passport_info.t,
-		oim->session->passport_info.p
-		);
+	purple_debug_info("msn", "Sending OIM: %s\n", oim_request->oim_msg);
 
 	/* if we got the challenge lock key, we compute it
 	 * else we go for the SOAP fault and resend it.
 	 */
-	if(oim->challenge == NULL){
-		purple_debug_info("MSNP14","no lock key challenge,wait for SOAP Fault and Resend\n");
+	if (oim->challenge == NULL){
+		purple_debug_info("msn", "No lock key challenge, waiting for SOAP Fault and Resend\n");
 	}
 
 	msg_body = msn_oim_msg_to_str(oim, oim_request->oim_msg);
@@ -242,23 +438,20 @@
 					oim_request->from_member,
 					oim_request->friendname,
 					oim_request->to_member,
-					mspauth,
-					MSNP13_WLM_PRODUCT_ID,
+					MSNP15_WLM_PRODUCT_ID,
 					oim->challenge ? oim->challenge : "",
 					oim->send_seq,
 					msg_body);
 
-	msn_soap_message_send(oim->session,
-		msn_soap_message_new(MSN_OIM_SEND_SOAP_ACTION,
-			xmlnode_from_str(soap_body, -1)),
-		MSN_OIM_SEND_HOST, MSN_OIM_SEND_URL, msn_oim_send_read_cb, oim);
+	msn_oim_make_request(oim, TRUE, MSN_OIM_SEND_SOAP_ACTION, MSN_OIM_SEND_HOST,
+		MSN_OIM_SEND_URL, xmlnode_from_str(soap_body, -1), msn_oim_send_read_cb,
+		oim);
 
 	/*increase the offline Sequence control*/
 	if (oim->challenge != NULL) {
 		oim->send_seq++;
 	}
 
-	g_free(mspauth);
 	g_free(msg_body);
 	g_free(soap_body);
 }
@@ -272,13 +465,13 @@
 {
 	MsnOimRecvData *rdata = data;
 
-	if (response && msn_soap_xml_get(response->xml, "Body/Fault") == NULL) {
-		purple_debug_info("msnoim", "delete OIM success\n");
+	if (response && xmlnode_get_child(response->xml, "Body/Fault") == NULL) {
+		purple_debug_info("msn", "Delete OIM success\n");
 		rdata->oim->oim_list = g_list_remove(rdata->oim->oim_list,
 			rdata->msg_id);
 		g_free(rdata->msg_id);
 	} else {
-		purple_debug_info("msnoim", "delete OIM failed\n");
+		purple_debug_info("msn", "Delete OIM failed\n");
 	}
 
 	g_free(rdata);
@@ -292,16 +485,12 @@
 	char *msgid = rdata->msg_id;
 	char *soap_body;
 
-	purple_debug_info("MSNP14","Delete single OIM Message {%s}\n",msgid);
-
-	soap_body = g_strdup_printf(MSN_OIM_DEL_TEMPLATE,
-		oim->session->passport_info.t, oim->session->passport_info.p, msgid);
+	purple_debug_info("msn", "Delete single OIM Message {%s}\n",msgid);
 
-	msn_soap_message_send(oim->session,
-		msn_soap_message_new(MSN_OIM_DEL_SOAP_ACTION,
-			xmlnode_from_str(soap_body, -1)),
-		MSN_OIM_RETRIEVE_HOST, MSN_OIM_RETRIEVE_URL,
-		msn_oim_delete_read_cb, rdata);
+	soap_body = g_strdup_printf(MSN_OIM_DEL_TEMPLATE, msgid);
+
+	msn_oim_make_request(oim, FALSE, MSN_OIM_DEL_SOAP_ACTION, MSN_OIM_RETRIEVE_HOST,
+		MSN_OIM_RETRIEVE_URL, xmlnode_from_str(soap_body, -1), msn_oim_delete_read_cb, rdata);
 
 	g_free(soap_body);
 }
@@ -351,7 +540,7 @@
 				long sys_tzoff;
 #endif
 
-				if (!offset_positive)
+				if (offset_positive)
 					tzoff *= -1;
 
 				t.tm_year -= 1900;
@@ -361,7 +550,7 @@
 					tzoff += sys_tzoff;
 #else
 #ifdef HAVE_TM_GMTOFF
-				tzoff -= t.tm_gmtoff;
+				tzoff += t.tm_gmtoff;
 #else
 #	ifdef HAVE_TIMEZONE
 				tzset();    /* making sure */
@@ -375,7 +564,7 @@
 		}
 	}
 
-	purple_debug_info("MSNP14:OIM", "Can't parse timestamp %s\n", timestamp);
+	purple_debug_info("msn", "Can't parse timestamp %s\n", timestamp);
 	return tval;
 }
 
@@ -396,30 +585,30 @@
 
 	msn_message_parse_payload(message, msg_str, strlen(msg_str),
 							  MSG_OIM_LINE_DEM, MSG_OIM_BODY_DEM);
-	purple_debug_info("MSNP14","oim body:{%s}\n",message->body);
+	purple_debug_info("msn", "oim body:{%s}\n", message->body);
 	decode_msg = (char *)purple_base64_decode(message->body,&body_len);
 	date =	(char *)g_hash_table_lookup(message->attr_table, "Date");
 	from =	(char *)g_hash_table_lookup(message->attr_table, "From");
-	if(strstr(from," ")){
+	if (strstr(from," ")) {
 		has_nick = 1;
 	}
-	if(has_nick){
+	if (has_nick) {
 		tokens = g_strsplit(from , " " , 2);
 		passport_str = g_strdup(tokens[1]);
-		purple_debug_info("MSNP14","oim Date:{%s},nickname:{%s},tokens[1]:{%s} passport{%s}\n",
-							date,tokens[0],tokens[1],passport_str);
+		purple_debug_info("msn", "oim Date:{%s},nickname:{%s},tokens[1]:{%s} passport{%s}\n",
+							date, tokens[0], tokens[1], passport_str);
 		g_strfreev(tokens);
-	}else{
+	} else {
 		passport_str = g_strdup(from);
-		purple_debug_info("MSNP14","oim Date:{%s},passport{%s}\n",
-					date,passport_str);
+		purple_debug_info("msn", "oim Date:{%s},passport{%s}\n",
+					date, passport_str);
 	}
 	start = strstr(passport_str,"<");
 	start += 1;
 	end = strstr(passport_str,">");
 	passport = g_strndup(start,end - start);
 	g_free(passport_str);
-	purple_debug_info("MSN OIM","oim Date:{%s},passport{%s}\n",date,passport);
+	purple_debug_info("msn", "oim Date:{%s},passport{%s}\n", date, passport);
 
 	stamp = msn_oim_parse_timestamp(date);
 
@@ -445,7 +634,7 @@
 	MsnOimRecvData *rdata = data;
 
 	if (response != NULL) {
-		xmlnode *msg_node = msn_soap_xml_get(response->xml,
+		xmlnode *msg_node = xmlnode_get_child(response->xml,
 			"Body/GetMessageResponse/GetMessageResult");
 
 		if (msg_node) {
@@ -454,11 +643,11 @@
 			g_free(msg_str);
 		} else {
 			char *str = xmlnode_to_str(response->xml, NULL);
-			purple_debug_info("msnoim", "Unknown response: %s\n", str);
+			purple_debug_info("msn", "Unknown OIM response: %s\n", str);
 			g_free(str);
 		}
 	} else {
-		purple_debug_info("msnoim", "Failed to get OIM\n");
+		purple_debug_info("msn", "Failed to get OIM\n");
 	}
 }
 
@@ -468,26 +657,43 @@
 void
 msn_parse_oim_msg(MsnOim *oim,const char *xmlmsg)
 {
-	xmlnode *node, *mNode;
+	xmlnode *node;
+
+	purple_debug_info("msn", "%s\n", xmlmsg);
+
+	if (!strcmp(xmlmsg, "too-large")) {
+		/* Too many OIM's to send via NS, so we need to request them via SOAP. */
+		msn_oim_get_metadata(oim);
+	} else {
+		node = xmlnode_from_str(xmlmsg, -1);
+		msn_parse_oim_xml(oim, node);
+		xmlnode_free(node);
+	}
+}
+
+static void
+msn_parse_oim_xml(MsnOim *oim, xmlnode *node)
+{
+	xmlnode *mNode;
 	xmlnode *iu_node;
 	MsnSession *session = oim->session;
 
-	purple_debug_info("MSNP14:OIM", "%s\n", xmlmsg);
+	g_return_if_fail(node != NULL);
 
-	node = xmlnode_from_str(xmlmsg, -1);
 	if (strcmp(node->name, "MD") != 0) {
-		purple_debug_info("msnoim", "WTF is this? %s\n", xmlmsg);
-		xmlnode_free(node);
+		char *xmlmsg = xmlnode_to_str(node, NULL);
+		purple_debug_info("msn", "WTF is this? %s\n", xmlmsg);
+		g_free(xmlmsg);
 		return;
 	}
 
-	iu_node = msn_soap_xml_get(node, "E/IU");
+	iu_node = xmlnode_get_child(node, "E/IU");
 
 	if (iu_node != NULL && purple_account_get_check_mail(session->account))
 	{
 		char *unread = xmlnode_get_data(iu_node);
 		const char *passport = msn_user_get_passport(session->user);
-		const char *url = session->passport_info.file;
+		const char *url = session->passport_info.mail_url;
 		int count = atoi(unread);
 
 		/* XXX/khc: pretty sure this is wrong */
@@ -515,7 +721,7 @@
 		if (rt_node != NULL) {
 			rtime = xmlnode_get_data(rt_node);
 		}
-/*		purple_debug_info("msnoim","E:{%s},I:{%s},rTime:{%s}\n",passport,msgid,rTime); */
+/*		purple_debug_info("msn", "E:{%s},I:{%s},rTime:{%s}\n",passport,msgid,rTime); */
 
 		if (!g_list_find_custom(oim->oim_list, msgid, (GCompareFunc)strcmp)) {
 			oim->oim_list = g_list_append(oim->oim_list, msgid);
@@ -528,8 +734,6 @@
 		g_free(rtime);
 		g_free(nickname);
 	}
-
-	xmlnode_free(node);
 }
 
 /*Post to get the Offline Instant Message*/
@@ -539,19 +743,16 @@
 	char *soap_body;
 	MsnOimRecvData *data = g_new0(MsnOimRecvData, 1);
 
-	purple_debug_info("MSNP14","Get single OIM Message\n");
+	purple_debug_info("msn", "Get single OIM Message\n");
 
 	data->oim = oim;
 	data->msg_id = msgid;
 
-	soap_body = g_strdup_printf(MSN_OIM_GET_TEMPLATE,
-		oim->session->passport_info.t, oim->session->passport_info.p, msgid);
+	soap_body = g_strdup_printf(MSN_OIM_GET_TEMPLATE, msgid);
 
-	msn_soap_message_send(oim->session,
-		msn_soap_message_new(MSN_OIM_GET_SOAP_ACTION,
-			xmlnode_from_str(soap_body, -1)),
-		MSN_OIM_RETRIEVE_HOST, MSN_OIM_RETRIEVE_URL,
-		msn_oim_get_read_cb, data);
+	msn_oim_make_request(oim, FALSE, MSN_OIM_GET_SOAP_ACTION, MSN_OIM_RETRIEVE_HOST,
+		MSN_OIM_RETRIEVE_URL, xmlnode_from_str(soap_body, -1), msn_oim_get_read_cb,
+		data);
 
 	g_free(soap_body);
 }
--- a/libpurple/protocols/msn/oim.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/oim.h	Thu Nov 20 21:13:56 2008 +0000
@@ -25,17 +25,41 @@
 #ifndef _MSN_OIM_H_
 #define _MSN_OIM_H_
 
-/*OIM Retrieve SOAP Template*/
+/* OIM Retrieval Info */
 #define MSN_OIM_RETRIEVE_HOST	"rsi.hotmail.com"
 #define MSN_OIM_RETRIEVE_URL	"/rsi/rsi.asmx"
+
+/* OIM GetMetadata SOAP Template */
+#define MSN_OIM_GET_METADATA_ACTION "http://www.hotmail.msn.com/ws/2004/09/oim/rsi/GetMetadata"
+
+#define MSN_OIM_GET_METADATA_TEMPLATE "<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
+"<soap:Envelope"\
+	" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
+	" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\
+	" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"\
+	"<soap:Header>"\
+		"<PassportCookie xmlns=\"http://www.hotmail.msn.com/ws/2004/09/oim/rsi\">"\
+			"<t>EMPTY</t>"\
+			"<p>EMPTY</p>"\
+		"</PassportCookie>"\
+	"</soap:Header>"\
+	"<soap:Body>"\
+		"<GetMetadata xmlns=\"http://www.hotmail.msn.com/ws/2004/09/oim/rsi\" />"\
+	"</soap:Body>"\
+"</soap:Envelope>"
+
+/*OIM GetMessage SOAP Template*/
 #define MSN_OIM_GET_SOAP_ACTION	"http://www.hotmail.msn.com/ws/2004/09/oim/rsi/GetMessage"
 
 #define MSN_OIM_GET_TEMPLATE "<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
-"<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"\
+"<soap:Envelope"\
+	" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
+	" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\
+	" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"\
 	"<soap:Header>"\
 		"<PassportCookie xmlns=\"http://www.hotmail.msn.com/ws/2004/09/oim/rsi\">"\
-			"<t>%s</t>"\
-			"<p>%s</p>"\
+			"<t>EMPTY</t>"\
+			"<p>EMPTY</p>"\
 		"</PassportCookie>"\
 	"</soap:Header>"\
 	"<soap:Body>"\
@@ -46,15 +70,18 @@
 	"</soap:Body>"\
 "</soap:Envelope>"
 
-/*OIM Delete SOAP Template*/
+/*OIM DeleteMessages SOAP Template*/
 #define MSN_OIM_DEL_SOAP_ACTION	"http://www.hotmail.msn.com/ws/2004/09/oim/rsi/DeleteMessages"
 
 #define MSN_OIM_DEL_TEMPLATE "<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
-"<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"\
+"<soap:Envelope"\
+	" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
+	" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\
+	" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"\
 	"<soap:Header>"\
 		"<PassportCookie xmlns=\"http://www.hotmail.msn.com/ws/2004/09/oim/rsi\">"\
-			"<t>%s</t>"\
-			" <p>%s</p>"\
+			"<t>EMPTY</t>"\
+			"<p>EMPTY</p>"\
 		"</PassportCookie>"\
 	"</soap:Header>"\
 	"<soap:Body>"\
@@ -72,18 +99,27 @@
 	"Content-Transfer-Encoding: base64\n"\
 	"X-OIM-Message-Type: OfflineMessage\n"\
 	"X-OIM-Run-Id: {%s}\n"\
-	"X-OIM-Sequence-Num: %d\n\n"\
-	"%s"
+	"X-OIM-Sequence-Num: %d\n\n"
 
 #define MSN_OIM_SEND_HOST	"ows.messenger.msn.com"
 #define MSN_OIM_SEND_URL	"/OimWS/oim.asmx"
-#define MSN_OIM_SEND_SOAP_ACTION	"http://messenger.msn.com/ws/2004/09/oim/Store"
+#define MSN_OIM_SEND_SOAP_ACTION	"http://messenger.live.com/ws/2006/09/oim/Store2"
 #define MSN_OIM_SEND_TEMPLATE "<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
-"<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"\
+"<soap:Envelope"\
+	" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
+	" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\
+	" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"\
 	"<soap:Header>"\
-		"<From memberName=\"%s\" friendlyName=\"%s\" xml:lang=\"en-US\" proxy=\"MSNMSGR\" xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\" msnpVer=\"MSNP14\" buildVer=\"8.0.0792\"/>"\
+		"<From"\
+			" memberName=\"%s\""\
+			" friendlyName=\"%s\""\
+			" xml:lang=\"en-US\""\
+			" proxy=\"MSNMSGR\""\
+			" xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\""\
+			" msnpVer=\"MSNP15\""\
+			" buildVer=\"8.5.1288\"/>"\
 		"<To memberName=\"%s\" xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\"/>"\
-		"<Ticket passport=\"%s\" appid=\"%s\" lockkey=\"%s\" xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\"/>"\
+		"<Ticket passport=\"EMPTY\" appid=\"%s\" lockkey=\"%s\" xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\"/>"\
 		"<Sequence xmlns=\"http://schemas.xmlsoap.org/ws/2003/03/rm\">"\
 			"<Identifier xmlns=\"http://schemas.xmlsoap.org/ws/2002/07/utility\">http://messenger.msn.com</Identifier>"\
 			"<MessageNumber>%d</MessageNumber>"\
@@ -101,10 +137,8 @@
 {
 	MsnSession *session;
 
-	MsnSoapConn *retrieveconn;
 	GList * oim_list;
 
-	MsnSoapConn *sendconn;
 	char *challenge;
 	char *run_id;
 	gint send_seq;
--- a/libpurple/protocols/msn/page.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/page.c	Thu Nov 20 21:13:56 2008 +0000
@@ -53,9 +53,9 @@
 
 	g_return_val_if_fail(page != NULL, NULL);
 
-	str =
-		g_strdup_printf("<TEXT xml:space=\"preserve\" enc=\"utf-8\">%s</TEXT>",
-						msn_page_get_body(page));
+	str = g_markup_printf_escaped(
+			"<TEXT xml:space=\"preserve\" enc=\"utf-8\">%s</TEXT>",
+			msn_page_get_body(page));
 
 	if (ret_size != NULL)
 		*ret_size = strlen(str);
--- a/libpurple/protocols/msn/servconn.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/servconn.c	Thu Nov 20 21:13:56 2008 +0000
@@ -203,7 +203,7 @@
 }
 
 gboolean
-msn_servconn_connect(MsnServConn *servconn, const char *host, int port)
+msn_servconn_connect(MsnServConn *servconn, const char *host, int port, gboolean force)
 {
 	MsnSession *session;
 
@@ -223,7 +223,7 @@
 	{
 		/* HTTP Connection. */
 
-		if (!servconn->httpconn->connected)
+		if (!servconn->httpconn->connected || force)
 			if (!msn_httpconn_connect(servconn->httpconn, host, port))
 				return FALSE;
 
@@ -255,6 +255,12 @@
 {
 	g_return_if_fail(servconn != NULL);
 
+	if (servconn->connect_data != NULL)
+	{
+		purple_proxy_connect_cancel(servconn->connect_data);
+		servconn->connect_data = NULL;
+	}
+
 	if (!servconn->connected)
 	{
 		/* We could not connect. */
@@ -273,12 +279,6 @@
 		return;
 	}
 
-	if (servconn->connect_data != NULL)
-	{
-		purple_proxy_connect_cancel(servconn->connect_data);
-		servconn->connect_data = NULL;
-	}
-
 	if (servconn->inpa > 0)
 	{
 		purple_input_remove(servconn->inpa);
@@ -391,23 +391,19 @@
 	session = servconn->session;
 
 	len = read(servconn->fd, buf, sizeof(buf) - 1);
-	servconn->session->account->gc->last_received = time(NULL);
+	if (servconn->type == MSN_SERVCONN_NS)
+		servconn->session->account->gc->last_received = time(NULL);
 
-	if (len <= 0) {
-		switch (errno) {
-
-			case 0:
+	if (len < 0 && errno == EAGAIN) {
+		return;
 
-			case EBADF:
-			case EAGAIN: return;
+	} else if (len <= 0) {
+		purple_debug_error("msn", "servconn read error,"
+		                          "len: %d, errno: %d, error: %s\n",
+		                          len, errno, g_strerror(errno));
+		msn_servconn_got_error(servconn, MSN_SERVCONN_ERROR_READ);
 
-			default: purple_debug_error("msn", "servconn read error,"
-						"len: %d, errno: %d, error: %s\n",
-						len, errno, g_strerror(errno));
-				 msn_servconn_got_error(servconn,
-						 MSN_SERVCONN_ERROR_READ);
-				 return;
-		}
+		return;
 	}
 
 	buf[len] = '\0';
--- a/libpurple/protocols/msn/servconn.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/servconn.h	Thu Nov 20 21:13:56 2008 +0000
@@ -115,8 +115,10 @@
  * @param servconn The connection.
  * @param host The host.
  * @param port The port.
+ * @param force Force this servconn to connect to a new server.
  */
-gboolean msn_servconn_connect(MsnServConn *servconn, const char *host, int port);
+gboolean msn_servconn_connect(MsnServConn *servconn, const char *host, int port,
+                              gboolean force);
 
 /**
  * Disconnects.
--- a/libpurple/protocols/msn/session.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/session.c	Thu Nov 20 21:13:56 2008 +0000
@@ -45,8 +45,6 @@
 								 purple_account_get_username(account), NULL);
 	session->oim = msn_oim_new(session);
 
-	/*if you want to chat with Yahoo Messenger*/
-	//session->protocol_ver = WLM_YAHOO_PROT_VER;
 	session->protocol_ver = WLM_PROT_VER;
 
 	return session;
@@ -74,18 +72,15 @@
 	msn_userlist_destroy(session->userlist);
 
 	g_free(session->psm);
-	g_free(session->passport_info.t);
-	g_free(session->passport_info.p);
+
+	g_free(session->blocked_text);
+
 	g_free(session->passport_info.kv);
 	g_free(session->passport_info.sid);
 	g_free(session->passport_info.mspauth);
 	g_free(session->passport_info.client_ip);
 
-	if (session->passport_info.file != NULL)
-	{
-		g_unlink(session->passport_info.file);
-		g_free(session->passport_info.file);
-	}
+	g_free(session->passport_info.mail_url);
 
 	if (session->sync != NULL)
 		msn_sync_destroy(session->sync);
@@ -93,15 +88,13 @@
 	if (session->nexus != NULL)
 		msn_nexus_destroy(session->nexus);
 
-	if (session->contact != NULL)
-		msn_contact_destroy(session->contact);
 	if (session->oim != NULL)
 		msn_oim_destroy(session->oim);
 
 	if (session->user != NULL)
 		msn_user_destroy(session->user);
 
-	if (session->soap_table)
+	if (session->soap_table != NULL)
 		g_hash_table_destroy(session->soap_table);
 
 	if (session->soap_cleanup_handle)
@@ -195,7 +188,7 @@
  * 	passport - the one want to talk to you
  */
 void
-msn_session_report_user(MsnSession *session,const char *passport,char *msg,PurpleMessageFlags flags)
+msn_session_report_user(MsnSession *session,const char *passport,const char *msg,PurpleMessageFlags flags)
 {
 	PurpleConversation * conv;
 
@@ -457,7 +450,6 @@
 	PurpleAccount *account;
 	PurpleConnection *gc;
 	PurpleStoredImage *img;
-	const char *passport;
 
 	if (session->logged_in)
 		return;
@@ -477,17 +469,5 @@
 
 	/* Sync users */
 	msn_session_sync_users(session);
-	/* It seems that some accounts that haven't accessed hotmail for a while
-	 * and @msn.com accounts don't automatically get the initial email
-	 * notification so we always request it on login
-	 */
-
-	passport = purple_normalize(account, purple_account_get_username(account));
-
-	if ((strstr(passport, "@hotmail.") != NULL) ||
-		(strstr(passport, "@msn.com") != NULL))
-	{
-		msn_cmdproc_send(session->notification->cmdproc, "URL", "%s", "INBOX");
-	}
 }
 
--- a/libpurple/protocols/msn/session.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/session.h	Thu Nov 20 21:13:56 2008 +0000
@@ -38,7 +38,6 @@
 #include "cmdproc.h"
 #include "nexus.h"
 #include "httpconn.h"
-#include "contact.h"
 #include "oim.h"
 
 #include "userlist.h"
@@ -96,7 +95,6 @@
 
 	MsnNotification *notification;
 	MsnNexus *nexus;
-	MsnContact *contact;
 	MsnOim		*oim;
 	MsnSync *sync;
 
@@ -109,19 +107,19 @@
 	/*psm info*/
 	char *psm;
 
+	char *blocked_text;
+
 	struct
 	{
-		/*t and p, get via USR TWN*/
-		char *t;
-		char *p;
-
 		char *kv;
 		char *sid;
 		char *mspauth;
 		unsigned long sl;
-		char *file;
 		char *client_ip;
 		int client_port;
+		char *mail_url;
+		gulong mail_timestamp;
+		gboolean email_enabled;
 	} passport_info;
 
 	GHashTable *soap_table;
@@ -236,6 +234,6 @@
 
 /*post message to User*/
 void msn_session_report_user(MsnSession *session,const char *passport,
-							char *msg,PurpleMessageFlags flags);
+							const char *msg,PurpleMessageFlags flags);
 
 #endif /* _MSN_SESSION_H_ */
--- a/libpurple/protocols/msn/slp.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/slp.c	Thu Nov 20 21:13:56 2008 +0000
@@ -25,7 +25,6 @@
 #include "slp.h"
 #include "slpcall.h"
 #include "slpmsg.h"
-#include "slpsession.h"
 
 #include "object.h"
 #include "user.h"
@@ -44,7 +43,7 @@
 static void send_decline(MsnSlpCall *slpcall, const char *branch,
 						 const char *type, const char *content);
 
-void msn_request_user_display(MsnUser *user);
+static void request_user_display(MsnUser *user);
 
 /**************************************************************************
  * Util
@@ -251,12 +250,11 @@
 got_sessionreq(MsnSlpCall *slpcall, const char *branch,
 			   const char *euf_guid, const char *context)
 {
-	if (!strcmp(euf_guid, "A4268EEC-FEC5-49E5-95C3-F126696BDBF6"))
+	if (!strcmp(euf_guid, MSN_OBJ_GUID))
 	{
 		/* Emoticon or UserDisplay */
 		char *content;
 		gsize len;
-		MsnSlpSession *slpsession;
 		MsnSlpLink *slplink;
 		MsnSlpMessage *slpmsg;
 		MsnObject *obj;
@@ -306,14 +304,10 @@
 			g_return_if_reached();
 		}
 
-		slpsession = msn_slplink_find_slp_session(slplink,
-												  slpcall->session_id);
-
 		/* DATA PREP */
 		slpmsg = msn_slpmsg_new(slplink);
 		slpmsg->slpcall = slpcall;
-		slpmsg->slpsession = slpsession;
-		slpmsg->session_id = slpsession->id;
+		slpmsg->session_id = slpcall->session_id;
 		msn_slpmsg_set_body(slpmsg, NULL, 4);
 #ifdef MSN_DEBUG_SLP
 		slpmsg->info = "SLP DATA PREP";
@@ -323,7 +317,6 @@
 		/* DATA */
 		slpmsg = msn_slpmsg_new(slplink);
 		slpmsg->slpcall = slpcall;
-		slpmsg->slpsession = slpsession;
 		slpmsg->flags = 0x20;
 #ifdef MSN_DEBUG_SLP
 		slpmsg->info = "SLP DATA";
@@ -332,7 +325,7 @@
 		msn_slplink_queue_slpmsg(slplink, slpmsg);
 		purple_imgstore_unref(img);
 	}
-	else if (!strcmp(euf_guid, "5D3E02AB-6190-11D3-BBBB-00C04F795683"))
+	else if (!strcmp(euf_guid, MSN_FT_GUID))
 	{
 		/* File Transfer */
 		PurpleAccount *account;
@@ -384,7 +377,8 @@
 
 			purple_xfer_request(xfer);
 		}
-	}
+	} else
+		purple_debug_warning("msn", "SLP SessionReq with unknown EUF-GUID: %s\n", euf_guid);
 }
 
 void
@@ -781,16 +775,13 @@
 got_emoticon(MsnSlpCall *slpcall,
 			 const guchar *data, gsize size)
 {
-
 	PurpleConversation *conv;
-	PurpleConnection *gc;
-	const char *who;
+	MsnSwitchBoard *swboard;
 
-	gc = slpcall->slplink->session->account->gc;
-	who = slpcall->slplink->remote_user;
+	swboard = slpcall->slplink->swboard;
+	conv = swboard->conv;
 
-	if ((conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, who, gc->account))) {
-
+	if (conv) {
 		/* FIXME: it would be better if we wrote the data as we received it
 		   instead of all at once, calling write multiple times and
 		   close once at the very end
@@ -808,6 +799,7 @@
 {
 	MsnSession *session;
 	MsnSlpLink *slplink;
+	MsnSwitchBoard *swboard;
 	MsnObject *obj;
 	char **tokens;
 	char *smile, *body_str;
@@ -847,8 +839,9 @@
 
 		slplink = msn_session_get_slplink(session, who);
 
-		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, who,
-												   session->account);
+		swboard = cmdproc->data;
+		slplink->swboard = swboard;
+		conv = swboard->conv;
 
 		/* If the conversation doesn't exist then this is a custom smiley
 		 * used in the first message in a MSN conversation: we need to create
@@ -930,7 +923,7 @@
 		username = user->passport;
 
 		userlist->buddy_icon_window--;
-		msn_request_user_display(user);
+		request_user_display(user);
 
 #ifdef MSN_DEBUG_UD
 		purple_debug_info("msn", "msn_release_buddy_icon_request(): buddy_icon_window-- yields =%d\n",
@@ -1066,8 +1059,8 @@
 														  msn_release_buddy_icon_request_timeout, userlist);
 }
 
-void
-msn_request_user_display(MsnUser *user)
+static void
+request_user_display(MsnUser *user)
 {
 	PurpleAccount *account;
 	MsnSession *session;
@@ -1115,7 +1108,7 @@
 		session->userlist->buddy_icon_window++;
 
 #ifdef MSN_DEBUG_UD
-		purple_debug_info("msn", "msn_request_user_display(): buddy_icon_window++ yields =%d\n",
+		purple_debug_info("msn", "request_user_display(): buddy_icon_window++ yields =%d\n",
 						session->userlist->buddy_icon_window);
 #endif
 
--- a/libpurple/protocols/msn/slpcall.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/slpcall.c	Thu Nov 20 21:13:56 2008 +0000
@@ -24,7 +24,6 @@
 #include "msn.h"
 #include "msnutils.h"
 #include "slpcall.h"
-#include "slpsession.h"
 
 #include "slp.h"
 
@@ -115,12 +114,8 @@
 void
 msn_slp_call_session_init(MsnSlpCall *slpcall)
 {
-	MsnSlpSession *slpsession;
-
-	slpsession = msn_slp_session_new(slpcall);
-
 	if (slpcall->session_init_cb)
-		slpcall->session_init_cb(slpsession);
+		slpcall->session_init_cb(slpcall);
 
 	slpcall->started = TRUE;
 }
--- a/libpurple/protocols/msn/slpcall.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/slpcall.h	Thu Nov 20 21:13:56 2008 +0000
@@ -29,7 +29,6 @@
 typedef struct _MsnSlpCall MsnSlpCall;
 
 #include "slplink.h"
-#include "slpsession.h"
 
 /* The official client seems to timeout slp calls after 5 minutes */
 #define MSN_SLPCALL_TIMEOUT 300000
@@ -66,7 +65,7 @@
 
 	void (*progress_cb)(MsnSlpCall *slpcall,
 						gsize total_length, gsize len, gsize offset);
-	void (*session_init_cb)(MsnSlpSession *slpsession);
+	void (*session_init_cb)(MsnSlpCall *slpcall);
 
 	/* Can be checksum, or smile */
 	char *data_info;
--- a/libpurple/protocols/msn/slplink.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/slplink.c	Thu Nov 20 21:13:56 2008 +0000
@@ -154,23 +154,6 @@
 	return slplink;
 }
 
-MsnSlpSession *
-msn_slplink_find_slp_session(MsnSlpLink *slplink, long session_id)
-{
-	GList *l;
-	MsnSlpSession *slpsession;
-
-	for (l = slplink->slp_sessions; l != NULL; l = l->next)
-	{
-		slpsession = l->data;
-
-		if (slpsession->id == session_id)
-			return slpsession;
-	}
-
-	return NULL;
-}
-
 void
 msn_slplink_add_slpcall(MsnSlpLink *slplink, MsnSlpCall *slpcall)
 {
@@ -394,12 +377,12 @@
 	}
 	else if (slpmsg->flags == 0x20 || slpmsg->flags == 0x1000030)
 	{
-		MsnSlpSession *slpsession;
-		slpsession = slpmsg->slpsession;
+		MsnSlpCall *slpcall;
+		slpcall = slpmsg->slpcall;
 
-		g_return_if_fail(slpsession != NULL);
-		msg->msnslp_header.session_id = slpsession->id;
-		msg->msnslp_footer.value = slpsession->app_id;
+		g_return_if_fail(slpcall != NULL);
+		msg->msnslp_header.session_id = slpcall->session_id;
+		msg->msnslp_footer.value = slpcall->app_id;
 		msg->msnslp_header.ack_id = rand() % 0xFFFFFF00;
 	}
 	else if (slpmsg->flags == 0x100)
@@ -476,18 +459,15 @@
 }
 
 static void
-send_file_cb(MsnSlpSession *slpsession)
+send_file_cb(MsnSlpCall *slpcall)
 {
-	MsnSlpCall *slpcall;
 	MsnSlpMessage *slpmsg;
 	struct stat st;
 	PurpleXfer *xfer;
 
-	slpcall = slpsession->slpcall;
 	slpmsg = msn_slpmsg_new(slpcall->slplink);
 	slpmsg->slpcall = slpcall;
 	slpmsg->flags = 0x1000030;
-	slpmsg->slpsession = slpsession;
 #ifdef MSN_DEBUG_SLP
 	slpmsg->info = "SLP FILE";
 #endif
@@ -593,7 +573,7 @@
 	}
 	else if (slpmsg->size)
 	{
-		if ((offset + len) > slpmsg->size)
+		if (G_MAXSIZE - len < offset || (offset + len) > slpmsg->size)
 		{
 			purple_debug_error("msn",
 				"Oversized slpmsg - msgsize=%lld offset=%" G_GSIZE_FORMAT " len=%" G_GSIZE_FORMAT "\n",
@@ -774,8 +754,7 @@
 
 	context = gen_context(fn, fp);
 
-	msn_slp_call_invite(slpcall, "5D3E02AB-6190-11D3-BBBB-00C04F795683", 2,
-						context);
+	msn_slp_call_invite(slpcall, MSN_FT_GUID, 2, context);
 
 	g_free(context);
 }
@@ -805,8 +784,7 @@
 	slpcall->cb = cb;
 	slpcall->end_cb = end_cb;
 
-	msn_slp_call_invite(slpcall, "A4268EEC-FEC5-49E5-95C3-F126696BDBF6", 1,
-						msnobj_base64);
+	msn_slp_call_invite(slpcall, MSN_OBJ_GUID, 1, msnobj_base64);
 
 	g_free(msnobj_base64);
 }
--- a/libpurple/protocols/msn/slplink.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/slplink.h	Thu Nov 20 21:13:56 2008 +0000
@@ -53,7 +53,6 @@
 	MsnDirectConn *directconn;
 
 	GList *slp_calls;
-	GList *slp_sessions;
 	GList *slp_msgs;
 
 	GQueue *slp_msg_queue;
@@ -74,8 +73,6 @@
  */
 MsnSlpLink *msn_session_get_slplink(MsnSession *session, const char *username);
 
-MsnSlpSession *msn_slplink_find_slp_session(MsnSlpLink *slplink,
-											long session_id);
 void msn_slplink_add_slpcall(MsnSlpLink *slplink, MsnSlpCall *slpcall);
 void msn_slplink_remove_slpcall(MsnSlpLink *slplink, MsnSlpCall *slpcall);
 MsnSlpCall *msn_slplink_find_slp_call(MsnSlpLink *slplink,
--- a/libpurple/protocols/msn/slpmsg.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/slpmsg.h	Thu Nov 20 21:13:56 2008 +0000
@@ -28,7 +28,6 @@
 
 #include "imgstore.h"
 
-#include "slpsession.h"
 #include "slpcall.h"
 #include "slplink.h"
 #include "session.h"
@@ -42,7 +41,6 @@
  */
 struct _MsnSlpMessage
 {
-	MsnSlpSession *slpsession;
 	MsnSlpCall *slpcall; /**< The slpcall to which this slp message belongs (if applicable). */
 	MsnSlpLink *slplink; /**< The slplink through which this slp message is being sent. */
 	MsnSession *session;
--- a/libpurple/protocols/msn/slpsession.c	Mon Aug 18 17:08:01 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-/**
- * @file slpsession.h SLP Session functions
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-#include "slpsession.h"
-
-/**************************************************************************
- * SLP Session
- **************************************************************************/
-
-MsnSlpSession *
-msn_slp_session_new(MsnSlpCall *slpcall)
-{
-	MsnSlpSession *slpsession;
-
-	g_return_val_if_fail(slpcall != NULL, NULL);
-
-	slpsession = g_new0(MsnSlpSession, 1);
-
-	slpsession->slpcall = slpcall;
-	slpsession->id = slpcall->session_id;
-	slpsession->call_id = slpcall->id;
-	slpsession->app_id = slpcall->app_id;
-
-	slpcall->slplink->slp_sessions =
-		g_list_append(slpcall->slplink->slp_sessions, slpsession);
-
-	return slpsession;
-}
-
-void
-msn_slp_session_destroy(MsnSlpSession *slpsession)
-{
-	g_return_if_fail(slpsession != NULL);
-
-	if (slpsession->call_id != NULL)
-		g_free(slpsession->call_id);
-
-	slpsession->slpcall->slplink->slp_sessions =
-		g_list_remove(slpsession->slpcall->slplink->slp_sessions, slpsession);
-
-	g_free(slpsession);
-}
-
-#if 0
-static void
-msn_slp_session_send_slpmsg(MsnSlpSession *slpsession, MsnSlpMessage *slpmsg)
-{
-	slpmsg->slpsession = slpsession;
-
-#if 0
-	slpmsg->session_id = slpsession->id;
-	slpmsg->app_id = slpsession->app_id;
-#endif
-
-	msn_slplink_send_slpmsg(slpsession->slpcall->slplink, slpmsg);
-}
-#endif
--- a/libpurple/protocols/msn/slpsession.h	Mon Aug 18 17:08:01 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-/**
- * @file slpsession.h SLP Session functions
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-#ifndef _MSN_SLPSESSION_H_
-#define _MSN_SLPSESSION_H_
-
-typedef struct _MsnSlpSession MsnSlpSession;
-
-#include "slpcall.h"
-#include "slpsession.h"
-#include "slpmsg.h"
-
-struct _MsnSlpSession
-{
-	/* MsnSlpLink *slplink; */
-	MsnSlpCall *slpcall;
-
-	long id;
-
-	long app_id;
-	char *call_id;
-};
-
-MsnSlpSession *msn_slp_session_new(MsnSlpCall *slpcall);
-void msn_slp_session_destroy(MsnSlpSession *slpsession);
-void msn_slpsession_send_slpmsg(MsnSlpSession *slpsession,
-								MsnSlpMessage *slpmsg);
-#endif /* _MSN_SLPSESSION_H_ */
--- a/libpurple/protocols/msn/soap.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/soap.c	Thu Nov 20 21:13:56 2008 +0000
@@ -1,8 +1,7 @@
 /**
  * @file soap.c
- * 	SOAP connection related process
- *	Author
- * 		MaYuan<mayuan2006@gmail.com>
+ * 	C file for SOAP connection related process
+ *
  * purple
  *
  * Purple is the legal property of its developers, whose names are too numerous
@@ -23,856 +22,671 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
-#include "msn.h"
+
+#include "internal.h"
+
 #include "soap.h"
 
-#define MSN_SOAP_DEBUG
-/*local function prototype*/
-void msn_soap_set_process_step(MsnSoapConn *soapconn, MsnSoapStep step);
+#include "session.h"
+
+#include "debug.h"
+#include "xmlnode.h"
+
+#include <glib.h>
+#if !defined(_WIN32) || !defined(_WINERROR_)
+#include <error.h>
+#endif
 
-/*setup the soap process step*/
-void
-msn_soap_set_process_step(MsnSoapConn *soapconn, MsnSoapStep step)
-{
-#ifdef MSN_SOAP_DEBUG
-	const char *MsnSoapStepText[] =
-	{
-		"Unconnected",
-		"Connecting",
-		"Connected",
-		"Processing",
-		"Connected Idle"
-	};
+#define SOAP_TIMEOUT (5 * 60)
+#define MSN_UNSAFE_DEBUG 1
+typedef struct _MsnSoapRequest {
+	char *path;
+	MsnSoapMessage *message;
+	gboolean secure;
+	MsnSoapCallback cb;
+	gpointer cb_data;
+} MsnSoapRequest;
 
-	purple_debug_info("MSN SOAP", "Setting SOAP process step to %s\n", MsnSoapStepText[step]);
-#endif
-	soapconn->step = step;
-}
+typedef struct _MsnSoapConnection {
+	MsnSession *session;
+	char *host;
 
-/*new a soap connection*/
-MsnSoapConn *
-msn_soap_new(MsnSession *session,gpointer data, gboolean ssl)
-{
-	MsnSoapConn *soapconn;
-
-	soapconn = g_new0(MsnSoapConn, 1);
-	soapconn->session = session;
-	soapconn->parent = data;
-	soapconn->ssl_conn = ssl;
+	time_t last_used;
+	PurpleSslConnection *ssl;
+	gboolean connected;
 
-	soapconn->gsc = NULL;
-	soapconn->input_handler = 0;
-	soapconn->output_handler = 0;
-
-	msn_soap_set_process_step(soapconn, MSN_SOAP_UNCONNECTED);
-	soapconn->soap_queue = g_queue_new();
+	guint event_handle;
+	GString *buf;
+	gsize handled_len;
+	gsize body_len;
+	int response_code;
+	gboolean headers_done;
+	gboolean close_when_done;
 
-	return soapconn;
-}
+	MsnSoapMessage *message;
 
-/*ssl soap connect callback*/
-void
-msn_soap_connect_cb(gpointer data, PurpleSslConnection *gsc,
-				 PurpleInputCondition cond)
-{
-	MsnSoapConn * soapconn;
-	MsnSession *session;
-	gboolean soapconn_is_valid = FALSE;
+	GQueue *queue;
+	MsnSoapRequest *current_request;
+} MsnSoapConnection;
+
+static void msn_soap_connection_destroy_foreach_cb(gpointer item, gpointer data);
+static gboolean msn_soap_connection_run(gpointer data);
 
-	purple_debug_misc("MSN SOAP","SOAP server connection established!\n");
-
-	soapconn = data;
-	g_return_if_fail(soapconn != NULL);
+static MsnSoapConnection *msn_soap_connection_new(MsnSession *session,
+	const char *host);
+static void msn_soap_connection_handle_next(MsnSoapConnection *conn);
+static void msn_soap_connection_destroy(MsnSoapConnection *conn);
 
-	session = soapconn->session;
-	g_return_if_fail(session != NULL);
-
-	soapconn->gsc = gsc;
+static void msn_soap_message_send_internal(MsnSession *session, MsnSoapMessage *message,
+	const char *host, const char *path, gboolean secure,
+	MsnSoapCallback cb, gpointer cb_data, gboolean first);
 
-	msn_soap_set_process_step(soapconn, MSN_SOAP_CONNECTED);
+static void msn_soap_request_destroy(MsnSoapRequest *req, gboolean keep_message);
+static void msn_soap_connection_sanitize(MsnSoapConnection *conn, gboolean disconnect);
+static gboolean msn_soap_write_cb_internal(gpointer data, gint fd, PurpleInputCondition cond, gboolean initial);
+static void msn_soap_process(MsnSoapConnection *conn);
 
-	/*connection callback*/
-	if (soapconn->connect_cb != NULL) {
-		soapconn_is_valid = soapconn->connect_cb(soapconn, gsc);
-	}
+static gboolean
+msn_soap_cleanup_each(gpointer key, gpointer value, gpointer data)
+{
+	MsnSoapConnection *conn = value;
+	time_t *t = data;
 
-	if (!soapconn_is_valid) {
-		return;
+	if ((*t - conn->last_used) > SOAP_TIMEOUT * 2) {
+		purple_debug_info("soap", "cleaning up soap conn %p\n", conn);
+		return TRUE;
 	}
 
-	/*we do the SOAP request here*/
-	msn_soap_post_head_request(soapconn);
-}
-
-/*ssl soap error callback*/
-static void
-msn_soap_error_cb(PurpleSslConnection *gsc, PurpleSslErrorType error, void *data)
-{
-	MsnSoapConn * soapconn = data;
-
-	g_return_if_fail(data != NULL);
-
-	purple_debug_warning("MSN SOAP","Soap connection error!\n");
-
-	msn_soap_set_process_step(soapconn, MSN_SOAP_UNCONNECTED);
-
-	/*error callback*/
-	if (soapconn->error_cb != NULL) {
-		soapconn->error_cb(soapconn, gsc, error);
-	} else {
-		msn_soap_post(soapconn, NULL);
-	}
-}
-
-/*init the soap connection*/
-void
-msn_soap_init(MsnSoapConn *soapconn,char * host, gboolean ssl,
-				MsnSoapSslConnectCbFunction connect_cb,
-				MsnSoapSslErrorCbFunction error_cb)
-{
-	purple_debug_misc("MSN SOAP","Initializing SOAP connection\n");
-	g_free(soapconn->login_host);
-	soapconn->login_host = g_strdup(host);
-	soapconn->ssl_conn = ssl;
-	soapconn->connect_cb = connect_cb;
-	soapconn->error_cb = error_cb;
+	return FALSE;
 }
 
-/*connect the soap connection*/
-void
-msn_soap_connect(MsnSoapConn *soapconn)
+static gboolean
+msn_soap_cleanup_for_session(gpointer data)
 {
-	if (soapconn->ssl_conn) {
-		purple_ssl_connect(soapconn->session->account, soapconn->login_host,
-				PURPLE_SSL_DEFAULT_PORT, msn_soap_connect_cb, msn_soap_error_cb,
-				soapconn);
-	} else {
+	MsnSession *sess = data;
+	time_t t = time(NULL);
+
+	purple_debug_info("soap", "session cleanup timeout\n");
+
+	if (sess->soap_table) {
+		g_hash_table_foreach_remove(sess->soap_table, msn_soap_cleanup_each,
+			&t);
+
+		if (g_hash_table_size(sess->soap_table) == 0) {
+			purple_timeout_remove(sess->soap_cleanup_handle);
+			sess->soap_cleanup_handle = 0;
+		}
 	}
 
-	msn_soap_set_process_step(soapconn, MSN_SOAP_CONNECTING);
-}
-
-
-static void
-msn_soap_close_handler(guint *handler)
-{
-	if (*handler > 0) {
-		purple_input_remove(*handler);
-		*handler = 0;
-	}
-#ifdef MSN_SOAP_DEBUG
-	else {
-		purple_debug_misc("MSN SOAP", "Handler inactive, not removing\n");
-	}
-#endif
-
-}
-
-
-/*close the soap connection*/
-void
-msn_soap_close(MsnSoapConn *soapconn)
-{
-	if (soapconn->ssl_conn) {
-		if (soapconn->gsc != NULL) {
-			purple_ssl_close(soapconn->gsc);
-			soapconn->gsc = NULL;
-		}
-	} else {
-	}
-	msn_soap_set_process_step(soapconn, MSN_SOAP_UNCONNECTED);
+	return TRUE;
 }
 
-/*clean the unhandled SOAP request*/
-void
-msn_soap_clean_unhandled_requests(MsnSoapConn *soapconn)
+static MsnSoapConnection *
+msn_soap_get_connection(MsnSession *session, const char *host)
 {
-	MsnSoapReq *request;
-
-	g_return_if_fail(soapconn != NULL);
-
-	soapconn->body = NULL;
-
-	while ((request = g_queue_pop_head(soapconn->soap_queue)) != NULL){
-		if (soapconn->read_cb) {
-			soapconn->read_cb(soapconn);
-		}
-		msn_soap_request_free(request);
-	}
-}
-
-/*destroy the soap connection*/
-void
-msn_soap_destroy(MsnSoapConn *soapconn)
-{
-	g_free(soapconn->login_host);
-
-	g_free(soapconn->login_path);
+	MsnSoapConnection *conn = NULL;
 
-	/*remove the write handler*/
-	if (soapconn->output_handler > 0){
-		purple_input_remove(soapconn->output_handler);
-		soapconn->output_handler = 0;
-	}
-	/*remove the read handler*/
-	if (soapconn->input_handler > 0){
-		purple_input_remove(soapconn->input_handler);
-		soapconn->input_handler = 0;
-	}
-	msn_soap_free_read_buf(soapconn);
-	msn_soap_free_write_buf(soapconn);
-
-	/*close ssl connection*/
-	msn_soap_close(soapconn);
-
-	/*process the unhandled soap request*/
-	msn_soap_clean_unhandled_requests(soapconn);
-
-	g_queue_free(soapconn->soap_queue);
-	g_free(soapconn);
-}
-
-/*check the soap is connected?
- * if connected return 1
- */
-int
-msn_soap_connected(MsnSoapConn *soapconn)
-{
-	if (soapconn->ssl_conn) {
-		return (soapconn->gsc == NULL ? 0 : 1);
-	}
-	return (soapconn->fd > 0 ? 1 : 0);
-}
-
-/*read and append the content to the buffer*/
-static gssize
-msn_soap_read(MsnSoapConn *soapconn)
-{
-	gssize len, requested_len;
-	char temp_buf[MSN_SOAP_READ_BUFF_SIZE];
-
-	if ( soapconn->need_to_read == 0 || soapconn->need_to_read > MSN_SOAP_READ_BUFF_SIZE) {
-		requested_len = MSN_SOAP_READ_BUFF_SIZE;
-	}
-	else {
-		requested_len = soapconn->need_to_read;
+	if (session->soap_table) {
+		conn = g_hash_table_lookup(session->soap_table, host);
+	} else {
+		session->soap_table = g_hash_table_new_full(g_str_hash, g_str_equal,
+			NULL, (GDestroyNotify)msn_soap_connection_destroy);
 	}
 
-	if ( soapconn->ssl_conn ) {
-		len = purple_ssl_read(soapconn->gsc, temp_buf, requested_len);
-	} else {
-		len = read(soapconn->fd, temp_buf, requested_len);
+	if (session->soap_cleanup_handle == 0)
+		session->soap_cleanup_handle = purple_timeout_add(SOAP_TIMEOUT * 1000,
+			msn_soap_cleanup_for_session, session);
+
+	if (conn == NULL) {
+		conn = msn_soap_connection_new(session, host);
+		g_hash_table_insert(session->soap_table, conn->host, conn);
 	}
 
+	conn->last_used = time(NULL);
 
-	if ( len <= 0 ) {
-		switch (errno) {
+	return conn;
+}
 
-			case 0:
-			case EBADF: /* we are sometimes getting this in Windows */
-			case EAGAIN: return len;
+static MsnSoapConnection *
+msn_soap_connection_new(MsnSession *session, const char *host)
+{
+	MsnSoapConnection *conn = g_new0(MsnSoapConnection, 1);
+	conn->session = session;
+	conn->host = g_strdup(host);
+	conn->queue = g_queue_new();
+	return conn;
+}
 
-			default : purple_debug_error("MSN SOAP", "Read error!"
-						"read len: %" G_GSSIZE_FORMAT ", error = %s\n",
-						len, g_strerror(errno));
-				  purple_input_remove(soapconn->input_handler);
-				  //soapconn->input_handler = 0;
-				  g_free(soapconn->read_buf);
-				  soapconn->read_buf = NULL;
-				  soapconn->read_len = 0;
-				  /* TODO: error handling */
-				  return len;
-		}
-	}
-	else {
-		soapconn->read_buf = g_realloc(soapconn->read_buf,
-						soapconn->read_len + len + 1);
-		if ( soapconn->read_buf != NULL ) {
-			memcpy(soapconn->read_buf + soapconn->read_len, temp_buf, len);
-			soapconn->read_len += len;
-			soapconn->read_buf[soapconn->read_len] = '\0';
-		}
-		else {
-			purple_debug_error("MSN SOAP",
-				"Failure re-allocating %" G_GSIZE_FORMAT " bytes of memory!\n",
-				soapconn->read_len + len + 1);
-			exit(EXIT_FAILURE);
-		}
+static void
+msn_soap_connected_cb(gpointer data, PurpleSslConnection *ssl,
+		PurpleInputCondition cond)
+{
+	MsnSoapConnection *conn = data;
+
+	conn->connected = TRUE;
 
-	}
+	if (conn->event_handle == 0)
+		conn->event_handle = purple_timeout_add(0, msn_soap_connection_run, conn);
+}
 
-#if defined(MSN_SOAP_DEBUG)
-	if (len > 0)
-		purple_debug_info("MSN SOAP",
-			"Read %" G_GSIZE_FORMAT " bytes from SOAP server:\n%s\n", len,
-			soapconn->read_buf + soapconn->read_len - len);
-#endif
+static void
+msn_soap_error_cb(PurpleSslConnection *ssl, PurpleSslErrorType error,
+		gpointer data)
+{
+	MsnSoapConnection *conn = data;
 
-	return len;
+	/* sslconn already frees the connection in case of error */
+	conn->ssl = NULL;
+
+	g_hash_table_remove(conn->session->soap_table, conn->host);
 }
 
-/*read the whole SOAP server response*/
-static void
-msn_soap_read_cb(gpointer data, gint source, PurpleInputCondition cond)
+static gboolean
+msn_soap_handle_redirect(MsnSoapConnection *conn, const char *url)
 {
-	MsnSoapConn *soapconn = data;
-	MsnSession *session;
-	int len;
-	char * body_start,*body_len;
-	char *length_start,*length_end;
-#ifdef MSN_SOAP_DEBUG
-#if !defined(_WIN32)
-	gchar * formattedxml = NULL;
-	gchar * http_headers = NULL;
-	xmlnode * node = NULL;
-#endif
-	purple_debug_misc("MSN SOAP", "msn_soap_read_cb()\n");
-#endif
-	session = soapconn->session;
-	g_return_if_fail(session != NULL);
+	char *host;
+	char *path;
+
+	if (purple_url_parse(url, &host, NULL, &path, NULL, NULL)) {
+		msn_soap_message_send_internal(conn->session, conn->current_request->message,
+			host, path, conn->current_request->secure,
+			conn->current_request->cb, conn->current_request->cb_data, TRUE);
 
-
-	/*read the request header*/
-	len = msn_soap_read(soapconn);
+		msn_soap_request_destroy(conn->current_request, TRUE);
+		conn->current_request = NULL;
 
-	if ( len < 0 )
-		return;
+		g_free(host);
+		g_free(path);
 
-	if (soapconn->read_buf == NULL) {
-		return;
+		return TRUE;
 	}
 
-	if ( (strstr(soapconn->read_buf, "HTTP/1.1 302") != NULL)
-		|| ( strstr(soapconn->read_buf, "HTTP/1.1 301") != NULL ) )
-	{
-		/* Redirect. */
-		char *location, *c;
+	return FALSE;
+}
+
+static gboolean
+msn_soap_handle_body(MsnSoapConnection *conn, MsnSoapMessage *response)
+{
+	xmlnode *body = xmlnode_get_child(response->xml, "Body");
+	xmlnode *fault = xmlnode_get_child(response->xml, "Fault");
+
+	if (fault) {
+		xmlnode *faultcode = xmlnode_get_child(fault, "faultcode");
+
+		if (faultcode != NULL) {
+			char *faultdata = xmlnode_get_data(faultcode);
+
+			if (g_str_equal(faultdata, "psf:Redirect")) {
+				xmlnode *url = xmlnode_get_child(fault, "redirectUrl");
 
-		purple_debug_info("MSN SOAP", "HTTP Redirect\n");
-		location = strstr(soapconn->read_buf, "Location: ");
-		if (location == NULL)
-		{
-			c = (char *) g_strstr_len(soapconn->read_buf, soapconn->read_len,"\r\n\r\n");
-			if (c != NULL) {
-				/* we have read the whole HTTP headers and found no Location: */
-				msn_soap_free_read_buf(soapconn);
-				msn_soap_post(soapconn, NULL);
+				if (url) {
+					char *urldata = xmlnode_get_data(url);
+					msn_soap_handle_redirect(conn, urldata);
+					g_free(urldata);
+				}
+
+				g_free(faultdata);
+				msn_soap_message_destroy(response);
+				return TRUE;
+			} else if (g_str_equal(faultdata, "wsse:FailedAuthentication")) {
+				xmlnode *reason = xmlnode_get_child(fault, "faultstring");
+				char *reasondata = xmlnode_get_data(reason);
+
+				msn_soap_connection_sanitize(conn, TRUE);
+				msn_session_set_error(conn->session, MSN_ERROR_AUTH,
+					reasondata);
+
+				g_free(reasondata);
+				g_free(faultdata);
+				msn_soap_message_destroy(response);
+				return FALSE;
 			}
 
-			return;
-		}
-		location = strchr(location, ' ') + 1;
-
-		if ((c = strchr(location, '\r')) != NULL)
-			*c = '\0';
-		else
-			return;
-
-		/* Skip the http:// */
-		if ((c = strchr(location, '/')) != NULL)
-			location = c + 2;
-
-		if ((c = strchr(location, '/')) != NULL)
-		{
-			g_free(soapconn->login_path);
-			soapconn->login_path = g_strdup(c);
-
-			*c = '\0';
-		}
-
-		g_free(soapconn->login_host);
-		soapconn->login_host = g_strdup(location);
-
-		msn_soap_close_handler( &(soapconn->input_handler) );
-		msn_soap_close(soapconn);
-
-		if (purple_ssl_connect(session->account, soapconn->login_host,
-			PURPLE_SSL_DEFAULT_PORT, msn_soap_connect_cb,
-			msn_soap_error_cb, soapconn) == NULL) {
-
-			purple_debug_error("MSN SOAP", "Unable to connect to %s !\n", soapconn->login_host);
-			// dispatch next request
-			msn_soap_post(soapconn, NULL);
-		}
-	}
-	/* Another case of redirection, active on May, 2007
-	   See http://msnpiki.msnfanatic.com/index.php/MSNP13:SOAPTweener#Redirect
-	 */
-	else if (strstr(soapconn->read_buf,
-                    "<faultcode>psf:Redirect</faultcode>") != NULL)
-	{
-		char *location, *c;
-
-		if ( (location = strstr(soapconn->read_buf, "<psf:redirectUrl>") ) == NULL)
-			return;
-
-		/* Omit the tag preceding the URL */
-		location += strlen("<psf:redirectUrl>");
-		if (location > soapconn->read_buf + soapconn->read_len)
-			return;
-		if ( (location = strstr(location, "://")) == NULL)
-			return;
-
-		location += strlen("://"); /* Skip http:// or https:// */
-
-		if ( (c = strstr(location, "</psf:redirectUrl>")) != NULL )
-			*c = '\0';
-		else
-			return;
-
-		if ( (c = strstr(location, "/")) != NULL )
-		{
-			g_free(soapconn->login_path);
-			soapconn->login_path = g_strdup(c);
-			*c = '\0';
-		}
-
-		g_free(soapconn->login_host);
-		soapconn->login_host = g_strdup(location);
-
-		msn_soap_close_handler( &(soapconn->input_handler) );
-		msn_soap_close(soapconn);
-
-		if (purple_ssl_connect(session->account, soapconn->login_host,
-				PURPLE_SSL_DEFAULT_PORT, msn_soap_connect_cb,
-				msn_soap_error_cb, soapconn) == NULL) {
-
-			purple_debug_error("MSN SOAP", "Unable to connect to %s !\n", soapconn->login_host);
-			// dispatch next request
-			msn_soap_post(soapconn, NULL);
+			g_free(faultdata);
 		}
 	}
-	else if (strstr(soapconn->read_buf, "HTTP/1.1 401 Unauthorized") != NULL)
-	{
-		const char *error;
-
-		purple_debug_error("MSN SOAP", "Received HTTP error 401 Unauthorized\n");
-		if ((error = strstr(soapconn->read_buf, "WWW-Authenticate")) != NULL)
-		{
-			if ((error = strstr(error, "cbtxt=")) != NULL)
-			{
-				const char *c;
-				char *temp;
-
-				error += strlen("cbtxt=");
-
-				if ((c = strchr(error, '\n')) == NULL)
-					c = error + strlen(error);
-
-				temp = g_strndup(error, c - error);
-				error = purple_url_decode(temp);
-				g_free(temp);
-			}
-		}
-
-		msn_session_set_error(session, MSN_ERROR_AUTH, error);
-	}
-	/* Handle Passport 3.0 authentication failures.
-	 * Further info: http://msnpiki.msnfanatic.com/index.php/MSNP13:SOAPTweener
-	 */
-	else if (strstr(soapconn->read_buf,
-				"<faultcode>wsse:FailedAuthentication</faultcode>") != NULL)
-	{
-		gchar *faultstring;
-
-		faultstring = strstr(soapconn->read_buf, "<faultstring>");
-
-		if (faultstring != NULL)
-		{
-			gchar *c;
-			faultstring += strlen("<faultstring>");
-			if (faultstring < soapconn->read_buf + soapconn->read_len) {
-				c = strstr(soapconn->read_buf, "</faultstring>");
-				if (c != NULL) {
-					*c = '\0';
-					msn_session_set_error(session, MSN_ERROR_AUTH, faultstring);
-				}
-			}
-		}
-
-	}
-	else if (strstr(soapconn->read_buf, "HTTP/1.1 503 Service Unavailable"))
-	{
-		msn_session_set_error(session, MSN_ERROR_SERV_UNAVAILABLE, NULL);
-	}
-	else if ((strstr(soapconn->read_buf, "HTTP/1.1 200 OK"))
-		||(strstr(soapconn->read_buf, "HTTP/1.1 500")))
-	{
-		gboolean soapconn_is_valid = FALSE;
-
-		/*OK! process the SOAP body*/
-		body_start = (char *)g_strstr_len(soapconn->read_buf, soapconn->read_len,"\r\n\r\n");
-		if (!body_start) {
-			return;
-		}
-		body_start += 4;
-
-		if (body_start > soapconn->read_buf + soapconn->read_len)
-			return;
-
-		/* we read the content-length*/
-		if ( (length_start = g_strstr_len(soapconn->read_buf, soapconn->read_len, "Content-Length: ")) != NULL)
-			length_start += strlen("Content-Length: ");
-
-		if (length_start > soapconn->read_buf + soapconn->read_len)
-			return;
 
-		if ( (length_end = strstr(length_start, "\r\n")) == NULL )
-			return;
-
-		body_len = g_strndup(length_start, length_end - length_start);
-
-		/*setup the conn body */
-		soapconn->body		= body_start;
-		soapconn->body_len	= atoi(body_len);
-		g_free(body_len);
-#ifdef MSN_SOAP_DEBUG
-		purple_debug_misc("MSN SOAP",
-			"SOAP bytes read so far: %" G_GSIZE_FORMAT ", Content-Length: %d\n",
-			soapconn->read_len, soapconn->body_len);
-#endif
-		soapconn->need_to_read = (body_start - soapconn->read_buf + soapconn->body_len) - soapconn->read_len;
-		if ( soapconn->need_to_read > 0 ) {
-			return;
-		}
-
-#if defined(MSN_SOAP_DEBUG) && !defined(_WIN32)
-
-		node = xmlnode_from_str(soapconn->body, soapconn->body_len);
-
-		if (node != NULL) {
-			formattedxml = xmlnode_to_formatted_str(node, NULL);
-			http_headers = g_strndup(soapconn->read_buf, soapconn->body - soapconn->read_buf);
-
-			purple_debug_info("MSN SOAP","Data with XML payload received from the SOAP server:\n%s%s\n", http_headers, formattedxml);
-			g_free(http_headers);
-			g_free(formattedxml);
-			xmlnode_free(node);
-		}
-		else
-			purple_debug_info("MSN SOAP","Data received from the SOAP server:\n%s\n", soapconn->read_buf);
-#endif
+	if (fault || body) {
+		MsnSoapRequest *request = conn->current_request;
+		conn->current_request = NULL;
+		request->cb(request->message, response,
+			request->cb_data);
+		msn_soap_message_destroy(response);
+		msn_soap_request_destroy(request, FALSE);
+	}
 
-		/*remove the read handler*/
-		msn_soap_close_handler( &(soapconn->input_handler) );
-//		purple_input_remove(soapconn->input_handler);
-//		soapconn->input_handler = 0;
-		/*
-		 * close the soap connection,if more soap request came,
-		 * Just reconnect to do it,
-		 *
-		 * To solve the problem described below:
-		 * When I post the soap request in one socket one after the other,
-		 * The first read is ok, But the second soap read always got 0 bytes,
-		 * Weird!
-		 * */
-		msn_soap_close(soapconn);
-
-		/*call the read callback*/
-		if ( soapconn->read_cb != NULL ) {
-			soapconn_is_valid = soapconn->read_cb(soapconn);
-		}
-
-		if (!soapconn_is_valid) {
-			return;
-		}
-
-		/* dispatch next request in queue */
-		msn_soap_post(soapconn, NULL);
-	}
-	return;
-}
-
-void
-msn_soap_free_read_buf(MsnSoapConn *soapconn)
-{
-	g_return_if_fail(soapconn != NULL);
-
-	if (soapconn->read_buf) {
-		g_free(soapconn->read_buf);
-	}
-	soapconn->read_buf = NULL;
-	soapconn->read_len = 0;
-	soapconn->need_to_read = 0;
+	return TRUE;
 }
 
-void
-msn_soap_free_write_buf(MsnSoapConn *soapconn)
+static void
+msn_soap_read_cb(gpointer data, gint fd, PurpleInputCondition cond)
 {
-	g_return_if_fail(soapconn != NULL);
-
-	if (soapconn->write_buf) {
-		g_free(soapconn->write_buf);
-	}
-	soapconn->write_buf = NULL;
-	soapconn->written_len = 0;
-}
+	MsnSoapConnection *conn = data;
+	int count = 0, cnt, perrno;
+	/* This buffer needs to be larger than any packets received from
+		login.live.com or Adium will fail to receive the packet
+		(something weird with the login.live.com server). With NSS it works
+		fine, so I believe it's some bug with OS X */ 
+	char buf[16 * 1024];
 
-/*Soap write process func*/
-static void
-msn_soap_write_cb(gpointer data, gint source, PurpleInputCondition cond)
-{
-	MsnSoapConn *soapconn = data;
-	int len, total_len;
-
-	g_return_if_fail(soapconn != NULL);
-	if ( soapconn->write_buf == NULL ) {
-		purple_debug_error("MSN SOAP","SOAP write buffer is NULL\n");
-	//	msn_soap_check_conn_errors(soapconn);
-	//	purple_input_remove(soapconn->output_handler);
-	//	soapconn->output_handler = 0;
-		msn_soap_close_handler( &(soapconn->output_handler) );
-		return;
+	if (conn->message == NULL) {
+		conn->message = msn_soap_message_new(NULL, NULL);
 	}
-	total_len = strlen(soapconn->write_buf);
-
-	/*
-	 * write the content to SSL server,
-	 */
-	len = purple_ssl_write(soapconn->gsc,
-		soapconn->write_buf + soapconn->written_len,
-		total_len - soapconn->written_len);
-
-	if (len < 0 && errno == EAGAIN)
-		return;
-	else if (len <= 0){
-		/*SSL write error!*/
-//		msn_soap_check_conn_errors(soapconn);
 
-		msn_soap_close_handler( &(soapconn->output_handler) );
-//		purple_input_remove(soapconn->output_handler);
-//		soapconn->output_handler = 0;
-
-		msn_soap_close(soapconn);
+	if (conn->buf == NULL) {
+		conn->buf = g_string_new_len(buf, 0);
+	}
+	
+	while ((cnt = purple_ssl_read(conn->ssl, buf, sizeof(buf))) > 0) {
+		purple_debug_info("soap", "read %d bytes\n", cnt);
+		count += cnt;
+		g_string_append_len(conn->buf, buf, cnt);
+	}
 
-		/* TODO: notify of the error */
-		purple_debug_error("MSN SOAP", "Error writing to SSL connection!\n");
-		msn_soap_post(soapconn, NULL);
-		return;
-	}
-	soapconn->written_len += len;
-
-	if (soapconn->written_len < total_len)
+	/* && count is necessary for Adium, on OS X the last read always
+	   return an error, so we want to proceed anyway. See #5212 for
+	   discussion on this and the above buffer size issues */
+	if(cnt < 0 && errno == EAGAIN && count == 0)
 		return;
 
-	msn_soap_close_handler( &(soapconn->output_handler) );
-//	purple_input_remove(soapconn->output_handler);
-//	soapconn->output_handler = 0;
-
-	/*clear the write buff*/
-	msn_soap_free_write_buf(soapconn);
-
-	/* Write finish!
-	 * callback for write done
-	 */
-	if(soapconn->written_cb != NULL){
-		soapconn->written_cb(soapconn);
-	}
-	/*maybe we need to read the input?*/
-	if ( soapconn->input_handler == 0 ) {
-		soapconn->input_handler = purple_input_add(soapconn->gsc->fd,
-			PURPLE_INPUT_READ, msn_soap_read_cb, soapconn);
-	}
-}
-
-/*write the buffer to SOAP connection*/
-void
-msn_soap_write(MsnSoapConn * soapconn, char *write_buf, MsnSoapWrittenCbFunction written_cb)
-{
-	if (soapconn == NULL) {
-		return;
-	}
-
-	msn_soap_set_process_step(soapconn, MSN_SOAP_PROCESSING);
-
-	/* Ideally this wouldn't ever be necessary, but i believe that it is leaking the previous value */
-	g_free(soapconn->write_buf);
-	soapconn->write_buf = write_buf;
-	soapconn->written_len = 0;
-	soapconn->written_cb = written_cb;
-
-	msn_soap_free_read_buf(soapconn);
-
-	/*clear the read buffer first*/
-	/*start the write*/
-	soapconn->output_handler = purple_input_add(soapconn->gsc->fd, PURPLE_INPUT_WRITE,
-						    msn_soap_write_cb, soapconn);
-	msn_soap_write_cb(soapconn, soapconn->gsc->fd, PURPLE_INPUT_WRITE);
-}
-
-/* New a soap request*/
-MsnSoapReq *
-msn_soap_request_new(const char *host,const char *post_url,const char *soap_action,
-				const char *body, const gpointer data_cb,
-				MsnSoapReadCbFunction read_cb,
-				MsnSoapWrittenCbFunction written_cb,
-				MsnSoapConnectInitFunction connect_init)
-{
-	MsnSoapReq *request;
-
-	request = g_new0(MsnSoapReq, 1);
-	request->id = 0;
-
-	request->login_host = g_strdup(host);
-	request->login_path = g_strdup(post_url);
-	request->soap_action		= g_strdup(soap_action);
-	request->body		= g_strdup(body);
-	request->data_cb 	= data_cb;
-	request->read_cb	= read_cb;
-	request->written_cb	= written_cb;
-	request->connect_init	= connect_init;
-
-	return request;
-}
-
-/*free a soap request*/
-void
-msn_soap_request_free(MsnSoapReq *request)
-{
-	g_return_if_fail(request != NULL);
-
-	g_free(request->login_host);
-	g_free(request->login_path);
-	g_free(request->soap_action);
-	g_free(request->body);
-	request->read_cb	= NULL;
-	request->written_cb	= NULL;
-	request->connect_init	= NULL;
-
-	g_free(request);
-}
-
-/*post the soap request queue's head request*/
-void
-msn_soap_post_head_request(MsnSoapConn *soapconn)
-{
-	g_return_if_fail(soapconn != NULL);
-	g_return_if_fail(soapconn->soap_queue != NULL);
-
-	if (soapconn->step == MSN_SOAP_CONNECTED ||
-	    soapconn->step == MSN_SOAP_CONNECTED_IDLE) {
-
-		purple_debug_info("MSN SOAP", "Posting new request from head of the queue\n");
-
-		if ( !g_queue_is_empty(soapconn->soap_queue) ) {
-			MsnSoapReq *request;
-
-			if ( (request = g_queue_pop_head(soapconn->soap_queue)) != NULL ) {
-				msn_soap_post_request(soapconn,request);
-			}
-		} else {
-			purple_debug_info("MSN SOAP", "No requests to process found.\n");
-			msn_soap_set_process_step(soapconn, MSN_SOAP_CONNECTED_IDLE);
+	/* msn_soap_process could alter errno */
+	perrno = errno;
+	msn_soap_process(conn);
+	
+	if (cnt < 0 && perrno != EAGAIN) {
+		purple_debug_info("soap", "read: %s\n", g_strerror(perrno));
+		/* It's possible msn_soap_process closed the ssl connection */
+		if (conn->ssl) {
+			purple_ssl_close(conn->ssl);
+			conn->ssl = NULL;
+			msn_soap_connection_handle_next(conn);
 		}
 	}
 }
 
-/*post the soap request ,
- * if not connected, Connected first.
- */
-void
-msn_soap_post(MsnSoapConn *soapconn, MsnSoapReq *request)
-{
-	MsnSoapReq *head_request;
+static void
+msn_soap_process(MsnSoapConnection *conn) {
+	gboolean handled = FALSE;
+	char *cursor;
+	char *linebreak;
+
+#ifndef MSN_UNSAFE_DEBUG
+	if (conn->current_request->secure)
+		purple_debug_info("soap", "Received secure request.\n");
+	else
+#endif
+	purple_debug_info("soap", "current %s\n", conn->buf->str);
+
+	cursor = conn->buf->str + conn->handled_len;
+
+	if (!conn->headers_done) {
+		while ((linebreak = strstr(cursor, "\r\n"))	!= NULL) {
+			conn->handled_len = linebreak - conn->buf->str + 2;
+
+			if (conn->response_code == 0) {
+				if (sscanf(cursor, "HTTP/1.1 %d", &conn->response_code) != 1) {
+					/* something horribly wrong */
+					purple_ssl_close(conn->ssl);
+					conn->ssl = NULL;
+					msn_soap_connection_handle_next(conn);
+					handled = TRUE;
+					break;
+				} else if (conn->response_code == 503) {
+					msn_soap_connection_sanitize(conn, TRUE);
+					msn_session_set_error(conn->session, MSN_ERROR_SERV_UNAVAILABLE, NULL);
+					return;
+				}
+			} else if (cursor == linebreak) {
+				/* blank line */
+				conn->headers_done = TRUE;
+				cursor = conn->buf->str + conn->handled_len;
+				break;
+			} else {
+				char *line = g_strndup(cursor, linebreak - cursor);
+				char *sep = strstr(line, ": ");
+				char *key = line;
+				char *value;
 
-	if (soapconn == NULL)
-		return;
+				if (sep == NULL) {
+					purple_debug_info("soap", "ignoring malformed line: %s\n", line);
+					g_free(line);
+					goto loop_end;
+				}
+
+				value = sep + 2;
+				*sep = '\0';
+				msn_soap_message_add_header(conn->message, key, value);
+
+				if ((conn->response_code == 301 || conn->response_code == 300)
+					&& strcmp(key, "Location") == 0) {
+
+					msn_soap_handle_redirect(conn, value);
+
+					handled = TRUE;
+					g_free(line);
+					break;
+				} else if (conn->response_code == 401 &&
+					strcmp(key, "WWW-Authenticate") == 0) {
+					char *error = strstr(value, "cbtxt=");
 
-	if (request != NULL) {
-#ifdef MSN_SOAP_DEBUG
-		purple_debug_misc("MSN SOAP", "Request added to the queue\n");
-#endif
-		g_queue_push_tail(soapconn->soap_queue, request);
+					if (error) {
+						error += strlen("cbtxt=");
+					}
+
+					msn_soap_connection_sanitize(conn, TRUE);
+					msn_session_set_error(conn->session, MSN_ERROR_AUTH,
+						error ? purple_url_decode(error) : NULL);
+
+					g_free(line);
+					return;
+				} else if (strcmp(key, "Content-Length") == 0) {
+					conn->body_len = atoi(value);
+				} else if (strcmp(key, "Connection") == 0) {
+					if (strcmp(value, "close") == 0) {
+						conn->close_when_done = TRUE;
+					}
+				}
+				g_free(line);
+			}
+
+		loop_end:
+			cursor = conn->buf->str + conn->handled_len;
+		}
 	}
 
-	if ( !g_queue_is_empty(soapconn->soap_queue)) {
-
-		/* we may have to reinitialize the soap connection, so avoid
-		 * reusing the connection for now */
+	if (!handled && conn->headers_done) {
+		if (conn->buf->len - conn->handled_len >=
+			conn->body_len) {
+			xmlnode *node = xmlnode_from_str(cursor, conn->body_len);
 
-		if (soapconn->step == MSN_SOAP_CONNECTED_IDLE) {
-			purple_debug_misc("MSN SOAP","Already connected to SOAP server, re-initializing\n");
-			msn_soap_close_handler( &(soapconn->input_handler) );
-			msn_soap_close_handler( &(soapconn->output_handler) );
-			msn_soap_close(soapconn);
+			if (node == NULL) {
+				purple_debug_info("soap", "Malformed SOAP response: %s\n",
+					cursor);
+			} else {
+				MsnSoapMessage *message = conn->message;
+				conn->message = NULL;
+				message->xml = node;
+
+				if (!msn_soap_handle_body(conn, message)) {
+					return;
+				}
+			}
+
+			msn_soap_connection_handle_next(conn);
 		}
 
-		if (!msn_soap_connected(soapconn) && (soapconn->step == MSN_SOAP_UNCONNECTED)) {
+		return;
+	}
 
-			/*not connected?and we have something to process connect it first*/
-			purple_debug_misc("MSN SOAP","No connection to SOAP server. Connecting...\n");
-			head_request = g_queue_peek_head(soapconn->soap_queue);
+	if (handled) {
+		msn_soap_connection_handle_next(conn);
+	}
+}
 
-			if (head_request == NULL) {
-				purple_debug_error("MSN SOAP", "Queue is not empty, but failed to peek the head request!\n");
-				return;
-			}
+static void
+msn_soap_write_cb(gpointer data, gint fd, PurpleInputCondition cond)
+{
+	msn_soap_write_cb_internal(data, fd, cond, FALSE);
+}
 
-			if (head_request->connect_init != NULL) {
-				head_request->connect_init(soapconn);
-			}
-			msn_soap_connect(soapconn);
-			return;
-		}
+static gboolean
+msn_soap_write_cb_internal(gpointer data, gint fd, PurpleInputCondition cond,
+		gboolean initial)
+{
+	MsnSoapConnection *conn = data;
+	int written;
+
+	if (cond != PURPLE_INPUT_WRITE) return TRUE;
 
-#ifdef MSN_SOAP_DEBUG
-		purple_debug_info("MSN SOAP", "Currently processing another SOAP request\n");
-	} else {
-		purple_debug_info("MSN SOAP", "No requests left to dispatch\n");
-#endif
+	written = purple_ssl_write(conn->ssl, conn->buf->str + conn->handled_len,
+		conn->buf->len - conn->handled_len);
+
+	if (written < 0 && errno == EAGAIN)
+		return TRUE;
+	else if (written <= 0) {
+		purple_ssl_close(conn->ssl);
+		conn->ssl = NULL;
+		if (!initial) msn_soap_connection_handle_next(conn);
+		return FALSE;
 	}
 
+	conn->handled_len += written;
+
+	if (conn->handled_len < conn->buf->len)
+		return TRUE;
+
+	/* we are done! */
+	g_string_free(conn->buf, TRUE);
+	conn->buf = NULL;
+	conn->handled_len = 0;
+	conn->body_len = 0;
+	conn->response_code = 0;
+	conn->headers_done = FALSE;
+	conn->close_when_done = FALSE;
+
+	purple_input_remove(conn->event_handle);
+	conn->event_handle = purple_input_add(conn->ssl->fd, PURPLE_INPUT_READ,
+		msn_soap_read_cb, conn);
+	return TRUE;
+}
+
+static gboolean
+msn_soap_connection_run(gpointer data)
+{
+	MsnSoapConnection *conn = data;
+	MsnSoapRequest *req = g_queue_peek_head(conn->queue);
+
+	conn->event_handle = 0;
+
+	if (req) {
+		if (conn->ssl == NULL) {
+			conn->ssl = purple_ssl_connect(conn->session->account, conn->host,
+				443, msn_soap_connected_cb, msn_soap_error_cb, conn);
+		} else if (conn->connected) {
+			int len = -1;
+			char *body = xmlnode_to_str(req->message->xml, &len);
+			GSList *iter;
+
+			g_queue_pop_head(conn->queue);
+
+			conn->buf = g_string_new("");
+
+			g_string_append_printf(conn->buf,
+				"POST /%s HTTP/1.1\r\n"
+				"SOAPAction: %s\r\n"
+				"Content-Type:text/xml; charset=utf-8\r\n"
+				"User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)\r\n"
+				"Accept: */*\r\n"
+				"Host: %s\r\n"
+				"Content-Length: %d\r\n"
+				"Connection: Keep-Alive\r\n"
+				"Cache-Control: no-cache\r\n",
+				req->path, req->message->action ? req->message->action : "",
+				conn->host, len);
+
+			for (iter = req->message->headers; iter; iter = iter->next) {
+				g_string_append(conn->buf, (char *)iter->data);
+				g_string_append(conn->buf, "\r\n");
+			}
+
+			g_string_append(conn->buf, "\r\n");
+			g_string_append(conn->buf, body);
+
+#ifndef MSN_UNSAFE_DEBUG
+			if (req->secure)
+				purple_debug_info("soap", "Sending secure request.\n");
+			else
+#endif
+			purple_debug_info("soap", "%s\n", conn->buf->str);
+
+			conn->handled_len = 0;
+			conn->current_request = req;
+
+			conn->event_handle = purple_input_add(conn->ssl->fd,
+				PURPLE_INPUT_WRITE, msn_soap_write_cb, conn);
+			if (!msn_soap_write_cb_internal(conn, conn->ssl->fd, PURPLE_INPUT_WRITE, TRUE)) {
+				/* Not connected => reconnect and retry */
+				purple_debug_info("soap", "not connected, reconnecting\n");
+				
+				conn->connected = FALSE;
+				conn->current_request = NULL;
+				msn_soap_connection_sanitize(conn, FALSE);
+				
+				g_queue_push_head(conn->queue, req);
+				conn->event_handle = purple_timeout_add(0, msn_soap_connection_run, conn);
+			}
+
+			g_free(body);
+		}
+	}
+
+	return FALSE;
+}
+
+void
+msn_soap_message_send(MsnSession *session, MsnSoapMessage *message,
+	const char *host, const char *path, gboolean secure,
+	MsnSoapCallback cb, gpointer cb_data)
+{
+	msn_soap_message_send_internal(session, message, host, path, secure,
+		cb, cb_data, FALSE);
 }
 
-/*Post the soap request action*/
-void
-msn_soap_post_request(MsnSoapConn *soapconn, MsnSoapReq *request)
+static void
+msn_soap_message_send_internal(MsnSession *session, MsnSoapMessage *message,
+	const char *host, const char *path, gboolean secure,
+	MsnSoapCallback cb, gpointer cb_data, gboolean first)
 {
-	char * request_str = NULL;
-#ifdef MSN_SOAP_DEBUG
-#if !defined(_WIN32)
-	xmlnode * node;
-#endif
-	purple_debug_misc("MSN SOAP","msn_soap_post_request()\n");
-#endif
+	MsnSoapConnection *conn = msn_soap_get_connection(session, host);
+	MsnSoapRequest *req = g_new0(MsnSoapRequest, 1);
+
+	req->path = g_strdup(path);
+	req->message = message;
+	req->secure = secure;
+	req->cb = cb;
+	req->cb_data = cb_data;
+
+	if (first) {
+		g_queue_push_head(conn->queue, req);
+	} else {
+		g_queue_push_tail(conn->queue, req);
+	}
+
+	if (conn->event_handle == 0)
+		conn->event_handle = purple_timeout_add(0, msn_soap_connection_run,
+			conn);
+}
+
+static void
+msn_soap_connection_sanitize(MsnSoapConnection *conn, gboolean disconnect)
+{
+	if (conn->event_handle) {
+		purple_input_remove(conn->event_handle);
+		conn->event_handle = 0;
+	}
 
-	msn_soap_set_process_step(soapconn, MSN_SOAP_PROCESSING);
-	request_str = g_strdup_printf(
-					"POST %s HTTP/1.1\r\n"
-					"SOAPAction: %s\r\n"
-					"Content-Type:text/xml; charset=utf-8\r\n"
-					"Cookie: MSPAuth=%s\r\n"
-					"User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)\r\n"
-					"Accept: */*\r\n"
-					"Host: %s\r\n"
-					"Content-Length: %" G_GSIZE_FORMAT "\r\n"
-					"Connection: Keep-Alive\r\n"
-					"Cache-Control: no-cache\r\n\r\n"
-					"%s",
-					request->login_path,
-					request->soap_action,
-					soapconn->session->passport_info.mspauth,
-					request->login_host,
-					strlen(request->body),
-					request->body
-					);
+	if (conn->message) {
+		msn_soap_message_destroy(conn->message);
+		conn->message = NULL;
+	}
+
+	if (conn->buf) {
+		g_string_free(conn->buf, TRUE);
+		conn->buf = NULL;
+	}
+
+	if (conn->ssl && (disconnect || conn->close_when_done)) {
+		purple_ssl_close(conn->ssl);
+		conn->ssl = NULL;
+	}
 
-#if defined(MSN_SOAP_DEBUG) && !defined(_WIN32)
-	node = xmlnode_from_str(request->body, -1);
-	if (node != NULL) {
-		char *formattedstr = xmlnode_to_formatted_str(node, NULL);
-		purple_debug_info("MSN SOAP","Posting request to SOAP server:\n%s%s\n",request_str, formattedstr);
-		g_free(formattedstr);
-		xmlnode_free(node);
+	if (conn->current_request) {
+		msn_soap_request_destroy(conn->current_request, FALSE);
+		conn->current_request = NULL;
 	}
-	else
-		purple_debug_info("MSN SOAP","Failed to parse SOAP request being sent:\n%s\n", request_str);
-#endif
+}
+
+static void
+msn_soap_connection_handle_next(MsnSoapConnection *conn)
+{
+	msn_soap_connection_sanitize(conn, FALSE);
 
-	/*free read buffer*/
-	// msn_soap_free_read_buf(soapconn);
-	/*post it to server*/
-	soapconn->data_cb = request->data_cb;
-	msn_soap_write(soapconn, request_str, request->written_cb);
+	conn->event_handle = purple_timeout_add(0, msn_soap_connection_run,	conn);
+
+	if (conn->current_request) {
+		MsnSoapRequest *req = conn->current_request;
+		conn->current_request = NULL;
+		msn_soap_connection_destroy_foreach_cb(req, conn);
+	}
 }
 
+static void
+msn_soap_connection_destroy_foreach_cb(gpointer item, gpointer data)
+{
+	MsnSoapRequest *req = item;
+
+	if (req->cb)
+		req->cb(req->message, NULL, req->cb_data);
+
+	msn_soap_request_destroy(req, FALSE);
+}
+
+static void
+msn_soap_connection_destroy(MsnSoapConnection *conn)
+{
+	if (conn->current_request) {
+		MsnSoapRequest *req = conn->current_request;
+		conn->current_request = NULL;
+		msn_soap_connection_destroy_foreach_cb(req, conn);
+	}
+
+	msn_soap_connection_sanitize(conn, TRUE);
+	g_queue_foreach(conn->queue, msn_soap_connection_destroy_foreach_cb, conn);
+	g_queue_free(conn->queue);
+
+	g_free(conn->host);
+	g_free(conn);
+}
+
+MsnSoapMessage *
+msn_soap_message_new(const char *action, xmlnode *xml)
+{
+	MsnSoapMessage *message = g_new0(MsnSoapMessage, 1);
+
+	message->action = g_strdup(action);
+	message->xml = xml;
+
+	return message;
+}
+
+void
+msn_soap_message_destroy(MsnSoapMessage *message)
+{
+	if (message) {
+		g_slist_foreach(message->headers, (GFunc)g_free, NULL);
+		g_slist_free(message->headers);
+		g_free(message->action);
+		if (message->xml)
+			xmlnode_free(message->xml);
+		g_free(message);
+	}
+}
+
+void
+msn_soap_message_add_header(MsnSoapMessage *message,
+		const char *name, const char *value)
+{
+	char *header = g_strdup_printf("%s: %s\r\n", name, value);
+
+	message->headers = g_slist_prepend(message->headers, header);
+}
+
+static void
+msn_soap_request_destroy(MsnSoapRequest *req, gboolean keep_message)
+{
+	g_free(req->path);
+	if (!keep_message)
+		msn_soap_message_destroy(req->message);
+	g_free(req);
+}
+
--- a/libpurple/protocols/msn/soap.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/soap.h	Thu Nov 20 21:13:56 2008 +0000
@@ -1,8 +1,7 @@
 /**
  * @file soap.h
  * 	header file for SOAP connection related process
- *	Author
- * 		MaYuan<mayuan2006@gmail.com>
+ *
  * purple
  *
  * Purple is the legal property of its developers, whose names are too numerous
@@ -23,144 +22,35 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
-#ifndef _MSN_SOAP_H_
-#define _MSN_SOAP_H_
 
-#define MSN_SOAP_READ_BUFF_SIZE		8192
-
-/* define this to debug the communications with the SOAP server */
-/* #define MSN_SOAP_DEBUG */
-
-#define MSN_SOAP_READ 1
-#define MSN_SOAP_WRITE 2
+#ifndef _MSN_SOAP_H
+#define _MSN_SOAP_H
 
-typedef enum
-{
-	MSN_SOAP_UNCONNECTED,
-	MSN_SOAP_CONNECTING,
-	MSN_SOAP_CONNECTED,
-	MSN_SOAP_PROCESSING,
-	MSN_SOAP_CONNECTED_IDLE
-}MsnSoapStep;
-
-/* MSN SoapRequest structure*/
-typedef struct _MsnSoapReq MsnSoapReq;
+#include "session.h"
+#include "sslconn.h"
+#include "xmlnode.h"
 
-/* MSN Https connection structure*/
-typedef struct _MsnSoapConn MsnSoapConn;
-
-typedef void (*MsnSoapConnectInitFunction)(MsnSoapConn *);
-typedef gboolean (*MsnSoapReadCbFunction)(MsnSoapConn *);
-typedef void (*MsnSoapWrittenCbFunction)(MsnSoapConn *);
-
-typedef gboolean (*MsnSoapSslConnectCbFunction)(MsnSoapConn *, PurpleSslConnection *);
-typedef void (*MsnSoapSslErrorCbFunction)(MsnSoapConn *, PurpleSslConnection *, PurpleSslErrorType);
-
+#include <glib.h>
 
-struct _MsnSoapReq{
-	/*request sequence*/
-	int	 id;
+typedef struct _MsnSoapMessage MsnSoapMessage;
+typedef void (*MsnSoapCallback)(MsnSoapMessage *request,
+	MsnSoapMessage *response, gpointer cb_data);
 
-	char *login_host;
-	char *login_path;
-	char *soap_action;
-
-	char *body;
-
-	gpointer data_cb;
-	MsnSoapReadCbFunction read_cb;
-	MsnSoapWrittenCbFunction written_cb;
-	MsnSoapConnectInitFunction connect_init;
+struct _MsnSoapMessage {
+	char *action;
+	xmlnode *xml;
+	GSList *headers;
 };
 
-struct _MsnSoapConn{
-	MsnSession *session;
-	gpointer parent;
-
-	char *login_host;
-	char *login_path;
-	char *soap_action;
-
-	MsnSoapStep step;
-	/*ssl connection?*/
-	gboolean ssl_conn;
-	/*normal connection*/
-	guint fd;
-	/*SSL connection*/
-	PurpleSslConnection *gsc;
-	/*ssl connection callback*/
-	MsnSoapSslConnectCbFunction connect_cb;
-	/*ssl error callback*/
-	MsnSoapSslErrorCbFunction error_cb;
+MsnSoapMessage *msn_soap_message_new(const char *action, xmlnode *xml);
 
-	/*read handler*/
-	guint input_handler;
-	/*write handler*/
-	guint output_handler;
-
-	/*Queue of SOAP request to send*/
-	int soap_id;
-	GQueue *soap_queue;
-
-	/*write buffer*/
-	char *write_buf;
-	gsize written_len;
-	MsnSoapWrittenCbFunction written_cb;
-
-	/*read buffer*/
-	char *read_buf;
-	gsize read_len;
-	gsize need_to_read;
-	MsnSoapReadCbFunction read_cb;
-
-	gpointer data_cb;
+void msn_soap_message_add_header(MsnSoapMessage *req,
+	const char *name, const char *value);
 
-	/*HTTP reply body part*/
-	char *body;
-	int body_len;
-};
-
-
-/*Function Prototype*/
-/*Soap Request Function */
-MsnSoapReq *msn_soap_request_new(const char *host, const char *post_url,
-				 const char *soap_action, const char *body,
-				 const gpointer data_cb,
-				 MsnSoapReadCbFunction read_cb,
-				 MsnSoapWrittenCbFunction written_cb,
-				 MsnSoapConnectInitFunction connect_init);
-
-void msn_soap_request_free(MsnSoapReq *request);
-void msn_soap_post_request(MsnSoapConn *soapconn,MsnSoapReq *request);
-void msn_soap_post_head_request(MsnSoapConn *soapconn);
-
-/*new a soap conneciton */
-MsnSoapConn *msn_soap_new(MsnSession *session, gpointer data, gboolean ssl);
-
-/*destroy */
-void msn_soap_destroy(MsnSoapConn *soapconn);
+void msn_soap_message_send(MsnSession *session, MsnSoapMessage *message,
+	const char *host, const char *path, gboolean secure,
+	MsnSoapCallback cb, gpointer cb_data);
 
-/*init a soap conneciton */
-void msn_soap_init(MsnSoapConn *soapconn, char * host, gboolean ssl,
-		   MsnSoapSslConnectCbFunction connect_cb,
-		   MsnSoapSslErrorCbFunction error_cb);
-void msn_soap_connect(MsnSoapConn *soapconn);
-void msn_soap_close(MsnSoapConn *soapconn);
-
-/*write to soap*/
-void msn_soap_write(MsnSoapConn * soapconn, char *write_buf, MsnSoapWrittenCbFunction written_cb);
-void msn_soap_post(MsnSoapConn *soapconn,MsnSoapReq *request);
+void msn_soap_message_destroy(MsnSoapMessage *message);
 
-void msn_soap_free_read_buf(MsnSoapConn *soapconn);
-void msn_soap_free_write_buf(MsnSoapConn *soapconn);
-void msn_soap_connect_cb(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
-
-/*clean the unhandled requests*/
-void msn_soap_clean_unhandled_requests(MsnSoapConn *soapconn);
-
-/*check if the soap connection is connected*/
-int msn_soap_connected(MsnSoapConn *soapconn);
-void msn_soap_set_process_step(MsnSoapConn *soapconn, MsnSoapStep step);
-
-#endif/*_MSN_SOAP_H_*/
-
+#endif
--- a/libpurple/protocols/msn/soap2.c	Mon Aug 18 17:08:01 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,693 +0,0 @@
-/**
- * @file soap2.c
- * 	C file for SOAP connection related process
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * 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"
-
-#include "soap2.h"
-
-#include "session.h"
-
-#include "debug.h"
-#include "xmlnode.h"
-
-#include <glib.h>
-#if !defined(_WIN32) || !defined(_WINERROR_)
-#include <error.h>
-#endif
-
-#define SOAP_TIMEOUT (5 * 60)
-
-typedef struct _MsnSoapRequest {
-	char *path;
-	MsnSoapMessage *message;
-	MsnSoapCallback cb;
-	gpointer cb_data;
-} MsnSoapRequest;
-
-typedef struct _MsnSoapConnection {
-	MsnSession *session;
-	char *host;
-
-	time_t last_used;
-	PurpleSslConnection *ssl;
-	gboolean connected;
-
-	guint event_handle;
-	GString *buf;
-	gsize handled_len;
-	gsize body_len;
-	int response_code;
-	gboolean headers_done;
-	gboolean close_when_done;
-
-	MsnSoapMessage *message;
-
-	GQueue *queue;
-	MsnSoapRequest *current_request;
-} MsnSoapConnection;
-
-static void msn_soap_connection_destroy_foreach_cb(gpointer item, gpointer data);
-static gboolean msn_soap_connection_run(gpointer data);
-
-static MsnSoapConnection *msn_soap_connection_new(MsnSession *session,
-	const char *host);
-static void msn_soap_connection_handle_next(MsnSoapConnection *conn);
-static void msn_soap_connection_destroy(MsnSoapConnection *conn);
-
-static void msn_soap_message_send_internal(MsnSession *session,
-	MsnSoapMessage *message, const char *host, const char *path,
-	MsnSoapCallback cb, gpointer cb_data, gboolean first);
-
-static void msn_soap_request_destroy(MsnSoapRequest *req);
-static void msn_soap_connection_sanitize(MsnSoapConnection *conn, gboolean disconnect);
-static void msn_soap_process(MsnSoapConnection *conn);
-
-static gboolean
-msn_soap_cleanup_each(gpointer key, gpointer value, gpointer data)
-{
-	MsnSoapConnection *conn = value;
-	time_t *t = data;
-
-	if ((*t - conn->last_used) > SOAP_TIMEOUT * 2) {
-		purple_debug_info("soap", "cleaning up soap conn %p\n", conn);
-		return TRUE;
-	}
-
-	return FALSE;
-}
-
-static gboolean
-msn_soap_cleanup_for_session(gpointer data)
-{
-	MsnSession *sess = data;
-	time_t t = time(NULL);
-
-	purple_debug_info("soap", "session cleanup timeout\n");
-
-	if (sess->soap_table) {
-		g_hash_table_foreach_remove(sess->soap_table, msn_soap_cleanup_each,
-			&t);
-
-		if (g_hash_table_size(sess->soap_table) == 0) {
-			purple_timeout_remove(sess->soap_cleanup_handle);
-			sess->soap_cleanup_handle = 0;
-		}
-	}
-
-	return TRUE;
-}
-
-static MsnSoapConnection *
-msn_soap_get_connection(MsnSession *session, const char *host)
-{
-	MsnSoapConnection *conn = NULL;
-
-	if (session->soap_table) {
-		conn = g_hash_table_lookup(session->soap_table, host);
-	} else {
-		session->soap_table = g_hash_table_new_full(g_str_hash, g_str_equal,
-			NULL, (GDestroyNotify)msn_soap_connection_destroy);
-	}
-
-	if (session->soap_cleanup_handle == 0)
-		session->soap_cleanup_handle = purple_timeout_add(SOAP_TIMEOUT * 1000,
-			msn_soap_cleanup_for_session, session);
-
-	if (conn == NULL) {
-		conn = msn_soap_connection_new(session, host);
-		g_hash_table_insert(session->soap_table, conn->host, conn);
-	}
-
-	conn->last_used = time(NULL);
-
-	return conn;
-}
-
-static MsnSoapConnection *
-msn_soap_connection_new(MsnSession *session, const char *host)
-{
-	MsnSoapConnection *conn = g_new0(MsnSoapConnection, 1);
-	conn->session = session;
-	conn->host = g_strdup(host);
-	conn->queue = g_queue_new();
-	return conn;
-}
-
-static void
-msn_soap_connected_cb(gpointer data, PurpleSslConnection *ssl,
-		PurpleInputCondition cond)
-{
-	MsnSoapConnection *conn = data;
-
-	conn->connected = TRUE;
-
-	if (conn->event_handle == 0)
-		conn->event_handle = purple_timeout_add(0, msn_soap_connection_run, conn);
-}
-
-static void
-msn_soap_error_cb(PurpleSslConnection *ssl, PurpleSslErrorType error,
-		gpointer data)
-{
-	MsnSoapConnection *conn = data;
-
-	/* sslconn already frees the connection in case of error */
-	conn->ssl = NULL;
-
-	g_hash_table_remove(conn->session->soap_table, conn->host);
-}
-
-static gboolean
-msn_soap_handle_redirect(MsnSoapConnection *conn, const char *url)
-{
-	char *c;
-
-	/* Skip the http:// */
-	if ((c = strchr(url, '/')) != NULL)
-		url += 2;
-
-	if ((c = strchr(url, '/')) != NULL) {
-		char *host, *path;
-
-		host = g_strndup(url, c - url);
-		path = g_strdup(c);
-
-		msn_soap_message_send_internal(conn->session,
-			conn->current_request->message,	host, path,
-			conn->current_request->cb, conn->current_request->cb_data, TRUE);
-
-		msn_soap_request_destroy(conn->current_request);
-		conn->current_request = NULL;
-
-		g_free(host);
-		g_free(path);
-
-		return TRUE;
-	}
-
-	return FALSE;
-}
-
-static gboolean
-msn_soap_handle_body(MsnSoapConnection *conn, MsnSoapMessage *response)
-{
-	xmlnode *body = xmlnode_get_child(response->xml, "Body");
-	xmlnode *fault = xmlnode_get_child(response->xml, "Fault");
-
-	if (fault) {
-		xmlnode *faultcode = xmlnode_get_child(fault, "faultcode");
-
-		if (faultcode != NULL) {
-			char *faultdata = xmlnode_get_data(faultcode);
-
-			if (g_str_equal(faultdata, "psf:Redirect")) {
-				xmlnode *url = xmlnode_get_child(body, "redirectUrl");
-
-				if (url) {
-					char *urldata = xmlnode_get_data(url);
-					msn_soap_handle_redirect(conn, urldata);
-					g_free(urldata);
-				}
-
-				g_free(faultdata);
-				return TRUE;
-			} else if (g_str_equal(faultdata, "wsse:FailedAuthentication")) {
-				xmlnode *reason = xmlnode_get_child(body, "faultstring");
-				char *reasondata = xmlnode_get_data(reason);
-
-				msn_soap_connection_sanitize(conn, TRUE);
-				msn_session_set_error(conn->session, MSN_ERROR_AUTH,
-					reasondata);
-
-				g_free(reasondata);
-				g_free(faultdata);
-				return FALSE;
-			}
-
-			g_free(faultdata);
-		}
-	}
-
-	if (fault || body) {
-		MsnSoapRequest *request = conn->current_request;
-		conn->current_request = NULL;
-		request->cb(request->message, response,
-			request->cb_data);
-		msn_soap_request_destroy(request);
-	}
-
-	return TRUE;
-}
-
-static void
-msn_soap_read_cb(gpointer data, gint fd, PurpleInputCondition cond)
-{
-	MsnSoapConnection *conn = data;
-	int count = 0, cnt, perrno;
-	/* This buffer needs to be larger than any packets received from
-		login.live.com or Adium will fail to receive the packet
-		(something weird with the login.live.com server). With NSS it works
-		fine, so I believe it's some bug with OS X */ 
-	char buf[16 * 1024];
-
-	if (conn->message == NULL) {
-		conn->message = msn_soap_message_new(NULL, NULL);
-	}
-
-	if (conn->buf == NULL) {
-		conn->buf = g_string_new_len(buf, 0);
-	}
-	
-	while ((cnt = purple_ssl_read(conn->ssl, buf, sizeof(buf))) > 0) {
-		purple_debug_info("soap", "read %d bytes\n", cnt);
-		count += cnt;
-		g_string_append_len(conn->buf, buf, cnt);
-	}
-
-	/* && count is necessary for Adium, on OS X the last read always
-	   return an error, so we want to proceed anyway. See #5212 for
-	   discussion on this and the above buffer size issues */
-	if(cnt < 0 && errno == EAGAIN && count == 0)
-		return;
-
-	// msn_soap_process could alter errno
-	perrno = errno;
-	msn_soap_process(conn);
-	
-	if (cnt < 0 && perrno != EAGAIN) {
-		purple_debug_info("soap", "read: %s\n", g_strerror(perrno));
-		// It's possible msn_soap_process closed the ssl connection
-		if (conn->ssl) {
-			purple_ssl_close(conn->ssl);
-			conn->ssl = NULL;
-			msn_soap_connection_handle_next(conn);
-		}
-	}
-}
-
-static void
-msn_soap_process(MsnSoapConnection *conn) {
-	gboolean handled = FALSE;
-	char *cursor;
-	char *linebreak;
-
-	purple_debug_info("soap", "current %s\n", conn->buf->str);
-
-	cursor = conn->buf->str + conn->handled_len;
-
-	if (!conn->headers_done) {
-		while ((linebreak = strstr(cursor, "\r\n"))	!= NULL) {
-			conn->handled_len = linebreak - conn->buf->str + 2;
-
-			if (conn->response_code == 0) {
-				if (sscanf(cursor, "HTTP/1.1 %d", &conn->response_code) != 1) {
-					/* something horribly wrong */
-					purple_ssl_close(conn->ssl);
-					conn->ssl = NULL;
-					msn_soap_connection_handle_next(conn);
-					handled = TRUE;
-					break;
-				} else if (conn->response_code == 503) {
-					msn_soap_connection_sanitize(conn, TRUE);
-					msn_session_set_error(conn->session, MSN_ERROR_SERV_UNAVAILABLE, NULL);
-					return;
-				}
-			} else if (cursor == linebreak) {
-				/* blank line */
-				conn->headers_done = TRUE;
-				cursor = conn->buf->str + conn->handled_len;
-				break;
-			} else {
-				char *line = g_strndup(cursor, linebreak - cursor);
-				char *sep = strstr(line, ": ");
-				char *key = line;
-				char *value;
-
-				if (sep == NULL) {
-					purple_debug_info("soap", "ignoring malformed line: %s\n", line);
-					g_free(line);
-					goto loop_end;
-				}
-
-				value = sep + 2;
-				*sep = '\0';
-				msn_soap_message_add_header(conn->message, key, value);
-
-				if ((conn->response_code == 301 || conn->response_code == 300)
-					&& strcmp(key, "Location") == 0) {
-
-					msn_soap_handle_redirect(conn, value);
-
-					handled = TRUE;
-					g_free(line);
-					break;
-				} else if (conn->response_code == 401 &&
-					strcmp(key, "WWW-Authenticate") == 0) {
-					char *error = strstr(value, "cbtxt=");
-
-					if (error) {
-						error += strlen("cbtxt=");
-					}
-
-					msn_soap_connection_sanitize(conn, TRUE);
-					msn_session_set_error(conn->session, MSN_ERROR_AUTH,
-						error ? purple_url_decode(error) : NULL);
-
-					g_free(line);
-					return;
-				} else if (strcmp(key, "Content-Length") == 0) {
-					conn->body_len = atoi(value);
-				} else if (strcmp(key, "Connection") == 0) {
-					if (strcmp(value, "close") == 0) {
-						conn->close_when_done = TRUE;
-					}
-				}
-				g_free(line);
-			}
-
-		loop_end:
-			cursor = conn->buf->str + conn->handled_len;
-		}
-	}
-
-	if (!handled && conn->headers_done) {
-		if (conn->buf->len - conn->handled_len >=
-			conn->body_len) {
-			xmlnode *node = xmlnode_from_str(cursor, conn->body_len);
-
-			if (node == NULL) {
-				purple_debug_info("soap", "Malformed SOAP response: %s\n",
-					cursor);
-			} else {
-				MsnSoapMessage *message = conn->message;
-				conn->message = NULL;
-				message->xml = node;
-
-				if (!msn_soap_handle_body(conn, message)) {
-					msn_soap_message_destroy(message);
-					return;
-				}
-				msn_soap_message_destroy(message);
-			}
-
-			msn_soap_connection_handle_next(conn);
-		}
-
-		return;
-	}
-
-	if (handled) {
-		msn_soap_connection_handle_next(conn);
-	}
-}
-
-static void
-msn_soap_write_cb(gpointer data, gint fd, PurpleInputCondition cond)
-{
-	MsnSoapConnection *conn = data;
-	int written;
-
-	g_return_if_fail(cond == PURPLE_INPUT_WRITE);
-
-	written = purple_ssl_write(conn->ssl, conn->buf->str + conn->handled_len,
-		conn->buf->len - conn->handled_len);
-
-	if (written < 0 && errno == EAGAIN)
-		return;
-	else if (written <= 0) {
-		purple_ssl_close(conn->ssl);
-		conn->ssl = NULL;
-		msn_soap_connection_handle_next(conn);
-		return;
-	}
-
-	conn->handled_len += written;
-
-	if (conn->handled_len < conn->buf->len)
-		return;
-
-	/* we are done! */
-	g_string_free(conn->buf, TRUE);
-	conn->buf = NULL;
-	conn->handled_len = 0;
-	conn->body_len = 0;
-	conn->response_code = 0;
-	conn->headers_done = FALSE;
-	conn->close_when_done = FALSE;
-
-	purple_input_remove(conn->event_handle);
-	conn->event_handle = purple_input_add(conn->ssl->fd, PURPLE_INPUT_READ,
-		msn_soap_read_cb, conn);
-}
-
-static gboolean
-msn_soap_connection_run(gpointer data)
-{
-	MsnSoapConnection *conn = data;
-	MsnSoapRequest *req = g_queue_peek_head(conn->queue);
-
-	conn->event_handle = 0;
-
-	if (req) {
-		if (conn->ssl == NULL) {
-			conn->ssl = purple_ssl_connect(conn->session->account, conn->host,
-				443, msn_soap_connected_cb, msn_soap_error_cb, conn);
-		} else if (conn->connected) {
-			int len = -1;
-			char *body = xmlnode_to_str(req->message->xml, &len);
-			GSList *iter;
-			char *authstr = NULL;
-
-			g_queue_pop_head(conn->queue);
-
-			conn->buf = g_string_new("");
-
-			if (conn->session->passport_info.mspauth)
-				authstr = g_strdup_printf("Cookie: MSPAuth=%s\r\n",
-					conn->session->passport_info.mspauth);
-
-
-			g_string_append_printf(conn->buf,
-				"POST %s HTTP/1.1\r\n"
-				"SOAPAction: %s\r\n"
-				"Content-Type:text/xml; charset=utf-8\r\n"
-				"%s"
-				"User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)\r\n"
-				"Accept: */*\r\n"
-				"Host: %s\r\n"
-				"Content-Length: %d\r\n"
-				"Connection: Keep-Alive\r\n"
-				"Cache-Control: no-cache\r\n",
-				req->path, req->message->action ? req->message->action : "",
-				authstr ? authstr : "",	conn->host, len);
-
-			for (iter = req->message->headers; iter; iter = iter->next) {
-				g_string_append(conn->buf, (char *)iter->data);
-				g_string_append(conn->buf, "\r\n");
-			}
-
-			g_string_append(conn->buf, "\r\n");
-			g_string_append(conn->buf, body);
-
-			purple_debug_info("soap", "%s\n", conn->buf->str);
-
-			conn->handled_len = 0;
-			conn->current_request = req;
-
-			conn->event_handle = purple_input_add(conn->ssl->fd,
-				PURPLE_INPUT_WRITE, msn_soap_write_cb, conn);
-			msn_soap_write_cb(conn, conn->ssl->fd, PURPLE_INPUT_WRITE);
-
-			g_free(authstr);
-			g_free(body);
-		}
-	}
-
-	return FALSE;
-}
-
-void
-msn_soap_message_send(MsnSession *session, MsnSoapMessage *message,
-	const char *host, const char *path,
-	MsnSoapCallback cb, gpointer cb_data)
-{
-	msn_soap_message_send_internal(session, message, host, path, cb, cb_data,
-		FALSE);
-}
-
-static void
-msn_soap_message_send_internal(MsnSession *session,
-	MsnSoapMessage *message, const char *host, const char *path,
-	MsnSoapCallback cb, gpointer cb_data, gboolean first)
-{
-	MsnSoapConnection *conn = msn_soap_get_connection(session, host);
-	MsnSoapRequest *req = g_new0(MsnSoapRequest, 1);
-
-	req->path = g_strdup(path);
-	req->message = message;
-	req->cb = cb;
-	req->cb_data = cb_data;
-
-	if (first) {
-		g_queue_push_head(conn->queue, req);
-	} else {
-		g_queue_push_tail(conn->queue, req);
-	}
-
-	if (conn->event_handle == 0)
-		conn->event_handle = purple_timeout_add(0, msn_soap_connection_run,
-			conn);
-}
-
-static void
-msn_soap_connection_sanitize(MsnSoapConnection *conn, gboolean disconnect)
-{
-	if (conn->event_handle) {
-		purple_input_remove(conn->event_handle);
-		conn->event_handle = 0;
-	}
-
-	if (conn->message) {
-		msn_soap_message_destroy(conn->message);
-		conn->message = NULL;
-	}
-
-	if (conn->buf) {
-		g_string_free(conn->buf, TRUE);
-		conn->buf = NULL;
-	}
-
-	if (conn->ssl && (disconnect || conn->close_when_done)) {
-		purple_ssl_close(conn->ssl);
-		conn->ssl = NULL;
-	}
-
-	if (conn->current_request) {
-		msn_soap_request_destroy(conn->current_request);
-		conn->current_request = NULL;
-	}
-}
-
-static void
-msn_soap_connection_handle_next(MsnSoapConnection *conn)
-{
-	msn_soap_connection_sanitize(conn, FALSE);
-
-	conn->event_handle = purple_timeout_add(0, msn_soap_connection_run,	conn);
-
-	if (conn->current_request) {
-		MsnSoapRequest *req = conn->current_request;
-		conn->current_request = NULL;
-		msn_soap_connection_destroy_foreach_cb(req, conn);
-	}
-}
-
-static void
-msn_soap_connection_destroy_foreach_cb(gpointer item, gpointer data)
-{
-	MsnSoapRequest *req = item;
-
-	if (req->cb)
-		req->cb(req->message, NULL, req->cb_data);
-
-	msn_soap_request_destroy(req);
-}
-
-static void
-msn_soap_connection_destroy(MsnSoapConnection *conn)
-{
-	if (conn->current_request) {
-		MsnSoapRequest *req = conn->current_request;
-		conn->current_request = NULL;
-		msn_soap_connection_destroy_foreach_cb(req, conn);
-	}
-
-	msn_soap_connection_sanitize(conn, TRUE);
-	g_queue_foreach(conn->queue, msn_soap_connection_destroy_foreach_cb, conn);
-	g_queue_free(conn->queue);
-
-	g_free(conn->host);
-	g_free(conn);
-}
-
-MsnSoapMessage *
-msn_soap_message_new(const char *action, xmlnode *xml)
-{
-	MsnSoapMessage *message = g_new0(MsnSoapMessage, 1);
-
-	message->action = g_strdup(action);
-	message->xml = xml;
-
-	return message;
-}
-
-void
-msn_soap_message_destroy(MsnSoapMessage *message)
-{
-	if (message) {
-		g_slist_foreach(message->headers, (GFunc)g_free, NULL);
-		g_slist_free(message->headers);
-		g_free(message->action);
-		if (message->xml)
-			xmlnode_free(message->xml);
-		g_free(message);
-	}
-}
-
-void
-msn_soap_message_add_header(MsnSoapMessage *message,
-		const char *name, const char *value)
-{
-	char *header = g_strdup_printf("%s: %s\r\n", name, value);
-
-	message->headers = g_slist_prepend(message->headers, header);
-}
-
-static void
-msn_soap_request_destroy(MsnSoapRequest *req)
-{
-	g_free(req->path);
-	msn_soap_message_destroy(req->message);
-	g_free(req);
-}
-
-xmlnode *
-msn_soap_xml_get(xmlnode *parent, const char *node)
-{
-	xmlnode *ret = NULL;
-	char **tokens = g_strsplit(node, "/", -1);
-	int i;
-
-	for (i = 0; tokens[i]; i++) {
-		if ((ret = xmlnode_get_child(parent, tokens[i])) != NULL)
-			parent = ret;
-		else
-			break;
-	}
-
-	g_strfreev(tokens);
-	return ret;
-}
-
--- a/libpurple/protocols/msn/soap2.h	Mon Aug 18 17:08:01 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-/**
- * @file soap2.h
- * 	header file for SOAP connection related process
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * 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 _MSN_SOAP2_H
-#define _MSN_SOAP2_H
-
-#include "session.h"
-#include "sslconn.h"
-#include "xmlnode.h"
-
-#include <glib.h>
-
-typedef struct _MsnSoapMessage MsnSoapMessage;
-typedef void (*MsnSoapCallback)(MsnSoapMessage *request,
-	MsnSoapMessage *response, gpointer cb_data);
-
-struct _MsnSoapMessage {
-	char *action;
-	xmlnode *xml;
-	GSList *headers;
-};
-
-MsnSoapMessage *msn_soap_message_new(const char *action, xmlnode *xml);
-
-void msn_soap_message_add_header(MsnSoapMessage *req,
-	const char *name, const char *value);
-
-void msn_soap_message_send(MsnSession *session,
-	MsnSoapMessage *message, const char *host, const char *path,
-	MsnSoapCallback cb, gpointer cb_data);
-
-void msn_soap_message_destroy(MsnSoapMessage *message);
-
-xmlnode *msn_soap_xml_get(xmlnode *parent, const char *node);
-
-#endif
--- a/libpurple/protocols/msn/state.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/state.c	Thu Nov 20 21:13:56 2008 +0000
@@ -151,15 +151,15 @@
 	xmlnode *payloadNode, *currentmediaNode;
 	char *currentmedia;
 
-	purple_debug_info("msn","msn get CurrentMedia\n");
+	purple_debug_info("msn", "Get CurrentMedia\n");
 	payloadNode = xmlnode_from_str(xml_str, len);
-	if (!payloadNode){
-		purple_debug_error("msn","PSM XML parse Error!\n");
+	if (!payloadNode) {
+		purple_debug_error("msn", "PSM XML parse Error!\n");
 		return NULL;
 	}
 	currentmediaNode = xmlnode_get_child(payloadNode, "CurrentMedia");
-	if (currentmediaNode == NULL){
-		purple_debug_info("msn","No CurrentMedia Node");
+	if (currentmediaNode == NULL) {
+		purple_debug_info("msn", "No CurrentMedia Node");
 		xmlnode_free(payloadNode);
 		return NULL;
 	}
@@ -177,15 +177,15 @@
 	xmlnode *payloadNode, *psmNode;
 	char *psm;
 
-	purple_debug_info("MSNP14","msn get PSM\n");
+	purple_debug_info("msn", "msn get PSM\n");
 	payloadNode = xmlnode_from_str(xml_str, len);
 	if (!payloadNode){
-		purple_debug_error("MSNP14","PSM XML parse Error!\n");
+		purple_debug_error("msn", "PSM XML parse Error!\n");
 		return NULL;
 	}
 	psmNode = xmlnode_get_child(payloadNode, "PSM");
 	if (psmNode == NULL){
-		purple_debug_info("MSNP14","No PSM status Node");
+		purple_debug_info("msn", "No PSM status Node");
 		xmlnode_free(payloadNode);
 		return NULL;
 	}
@@ -249,7 +249,7 @@
 	session->psm = msn_build_psm(statusline_stripped, media, NULL);
 
 	payload = session->psm;
-	purple_debug_misc("MSNP14","Sending UUX command with payload: %s\n",payload);
+	purple_debug_misc("msn", "Sending UUX command with payload: %s\n", payload);
 	trans = msn_transaction_new(cmdproc, "UUX", "%" G_GSIZE_FORMAT, strlen(payload));
 	msn_transaction_set_payload(trans, payload, strlen(payload));
 	msn_cmdproc_send_trans(cmdproc, trans);
--- a/libpurple/protocols/msn/switchboard.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/switchboard.c	Thu Nov 20 21:13:56 2008 +0000
@@ -338,7 +338,7 @@
 {
 	g_return_if_fail(swboard != NULL);
 
-	purple_debug_warning("msg", "Error: Unable to call the user %s for reason %i\n",
+	purple_debug_warning("msn", "Error: Unable to call the user %s for reason %i\n",
 					   passport ? passport : "(null)", reason);
 
 	/* TODO: if current_users > 0, this is probably a chat and an invite failed,
@@ -540,7 +540,7 @@
 	payload = msn_message_gen_payload(msg, &payload_len);
 
 #ifdef MSN_DEBUG_SB
-	purple_debug_info("MSNP14","SB length:{%d}",payload_len);
+	purple_debug_info("msn", "SB length:{%d}", payload_len);
 	msn_message_show_readable(msg, "SB SEND", FALSE);
 #endif
 
@@ -628,7 +628,7 @@
 	g_return_if_fail(swboard != NULL);
 	g_return_if_fail(msg     != NULL);
 
-	purple_debug_info("MSNP14","switchboard send msg..\n");
+	purple_debug_info("msn", "switchboard send msg..\n");
 	if (msn_switchboard_can_send(swboard))
 		release_msg(swboard, msg);
 	else if (queue)
@@ -662,7 +662,7 @@
 	g_return_if_fail(swboard != NULL);
 
 	if (!(swboard->flag & MSN_SB_FLAG_IM) && (swboard->conv != NULL))
-		purple_debug_error("msn_switchboard", "bye_cmd: helper bug\n");
+		purple_debug_error("msn", "bye_cmd: helper bug\n");
 
 	if (swboard->conv == NULL)
 	{
@@ -752,15 +752,15 @@
 static void
 msg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
-	cmdproc->servconn->payload_len = atoi(cmd->params[2]);
+	cmd->payload_len = atoi(cmd->params[2]);
 	cmdproc->last_cmd->payload_cb = msg_cmd_post;
 }
 
 static void
 ubm_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
-	purple_debug_misc("MSNP14","get UBM...\n");
-	cmdproc->servconn->payload_len = atoi(cmd->params[4]);
+	purple_debug_misc("msn", "get UBM...\n");
+	cmd->payload_len = atoi(cmd->params[4]);
 	cmdproc->last_cmd->payload_cb = msg_cmd_post;
 }
 
@@ -960,17 +960,46 @@
 }
 
 static void
-nudge_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
+datacast_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
 {
-	MsnSwitchBoard *swboard;
-	PurpleAccount *account;
-	const char *user;
+	GHashTable *body;
+	const char *id;
+	body = msn_message_get_hashtable_from_body(msg);
+
+	id = g_hash_table_lookup(body, "ID");
+
+	if (!strcmp(id, "1")) {
+		/* Nudge */
+		MsnSwitchBoard *swboard;
+		PurpleAccount *account;
+		const char *user;
+
+		swboard = cmdproc->data;
+		account = cmdproc->session->account;
+		user = msg->remote_user;
 
-	swboard = cmdproc->data;
-	account = cmdproc->session->account;
-	user = msg->remote_user;
+		if (swboard->current_users > 1 ||
+			((swboard->conv != NULL) &&
+			 purple_conversation_get_type(swboard->conv) == PURPLE_CONV_TYPE_CHAT))
+			purple_prpl_got_attention_in_chat(account->gc, swboard->chat_id, user, MSN_NUDGE);
+
+		else
+			purple_prpl_got_attention(account->gc, user, MSN_NUDGE);
+
+	} else if (!strcmp(id, "2")) {
+		/* Wink */
 
-	serv_got_attention(account->gc, user, MSN_NUDGE);
+	} else if (!strcmp(id, "3")) {
+		/* Voiceclip */
+
+	} else if (!strcmp(id, "4")) {
+		/* Action */
+
+	} else {
+		purple_debug_warning("msn", "Got unknown datacast with ID %s.\n", id);
+	}
+
+	g_hash_table_destroy(body);
 }
 
 /**************************************************************************
@@ -1059,7 +1088,7 @@
 	msn_servconn_set_connect_cb(swboard->servconn, connect_cb);
 	msn_servconn_set_disconnect_cb(swboard->servconn, disconnect_cb);
 
-	return msn_servconn_connect(swboard->servconn, host, port);
+	return msn_servconn_connect(swboard->servconn, host, port, FALSE);
 }
 
 void
@@ -1114,18 +1143,13 @@
 	}
 
 	purple_debug_warning("msn", "cal_error: command %s gave error %i\n", trans->command, error);
-	purple_debug_warning("msn", "Will Use Offline Message to sendit\n");
-
-//	cal_error_helper(trans, reason);
-	/*offline Message send Process*/
 
 	while ((msg = g_queue_pop_head(swboard->msg_queue)) != NULL){
-		purple_debug_warning("MSNP14","offline msg to send:{%s}\n",msg->body);
+		purple_debug_warning("msn", "Unable to send msg: {%s}\n", msg->body);
 		/* The messages could not be sent due to a switchboard error */
 		swboard->error = MSN_SB_ERROR_USER_OFFLINE;
 		msg_error_helper(swboard->cmdproc, msg,
 							 MSN_MSG_ERROR_SB);
-		msn_message_unref(msg);
 	}
 	cal_error_helper(trans, reason);
 }
@@ -1170,7 +1194,7 @@
 		/* The conversation window was closed. */
 		return;
 
-	purple_debug_info("MSNP14","Switchboard:auth:{%s} socket:{%s}\n",cmd->params[4],cmd->params[2]);
+	purple_debug_info("msn", "Switchboard:auth:{%s} socket:{%s}\n", cmd->params[4], cmd->params[2]);
 	msn_switchboard_set_auth_key(swboard, cmd->params[4]);
 
 	msn_parse_socket(cmd->params[2], &host, &port);
@@ -1313,7 +1337,7 @@
 	msn_table_add_msg_type(cbs_table, "text/x-mms-animemoticon",
 	                                           msn_emoticon_msg);
 	msn_table_add_msg_type(cbs_table, "text/x-msnmsgr-datacast",
-						   nudge_msg);
+						   datacast_msg);
 #if 0
 	msn_table_add_msg_type(cbs_table, "text/x-msmmsginvite",
 						   msn_invite_msg);
--- a/libpurple/protocols/msn/sync.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/sync.c	Thu Nov 20 21:13:56 2008 +0000
@@ -216,8 +216,6 @@
 void
 msn_sync_init(void)
 {
-	/* TODO: check prp, blp, bpr */
-
 	cbs_table = msn_table_new();
 
 	/* Syncing */
--- a/libpurple/protocols/msn/user.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/user.c	Thu Nov 20 21:13:56 2008 +0000
@@ -28,7 +28,7 @@
 /*new a user object*/
 MsnUser *
 msn_user_new(MsnUserList *userlist, const char *passport,
-			 const char *store_name)
+			 const char *friendly_name)
 {
 	MsnUser *user;
 
@@ -37,16 +37,7 @@
 	user->userlist = userlist;
 
 	msn_user_set_passport(user, passport);
-	msn_user_set_store_name(user, store_name);
-
-	/*
-	 * XXX This seems to reset the friendly name from what it should be
-	 *     to the passport when moving users. So, screw it :)
-	 */
-#if 0
-	if (name != NULL)
-		msn_user_set_name(user, name);
-#endif
+	msn_user_set_friendly_name(user, friendly_name);
 
 	return user;
 }
@@ -75,7 +66,6 @@
 
 	g_free(user->passport);
 	g_free(user->friendly_name);
-	g_free(user->store_name);
 	g_free(user->uid);
 	g_free(user->phone.home);
 	g_free(user->phone.work);
@@ -92,37 +82,36 @@
 msn_user_update(MsnUser *user)
 {
 	PurpleAccount *account;
+	gboolean offline;
 
 	account = user->userlist->session->account;
 
-	if (user->status != NULL) {
-		gboolean offline = (strcmp(user->status, "offline") == 0);
+	offline = (user->status == NULL);
 
-		if (!offline) {
-			purple_prpl_got_user_status(account, user->passport, user->status,
-					"message", user->statusline, NULL);
+	if (!offline) {
+		purple_prpl_got_user_status(account, user->passport, user->status,
+				"message", user->statusline, NULL);
+	} else {
+		if (user->mobile) {
+			purple_prpl_got_user_status(account, user->passport, "mobile", NULL);
+			purple_prpl_got_user_status(account, user->passport, "available", NULL);
 		} else {
-			if (user->mobile) {
-				purple_prpl_got_user_status(account, user->passport, "mobile", NULL);
-				purple_prpl_got_user_status(account, user->passport, "available", NULL);
-			} else {
-				purple_prpl_got_user_status(account, user->passport, user->status, NULL);
-			}
+			purple_prpl_got_user_status(account, user->passport, "offline", NULL);
 		}
+	}
 
-		if (!offline || !user->mobile) {
-			purple_prpl_got_user_status_deactive(account, user->passport, "mobile");
-		}
+	if (!offline || !user->mobile) {
+		purple_prpl_got_user_status_deactive(account, user->passport, "mobile");
+	}
 
-		if (!offline && user->media.title) {
-			purple_prpl_got_user_status(account, user->passport, "tune",
-					PURPLE_TUNE_ARTIST, user->media.artist,
-					PURPLE_TUNE_ALBUM, user->media.album,
-					PURPLE_TUNE_TITLE, user->media.title,
-					NULL);
-		} else {
-			purple_prpl_got_user_status_deactive(account, user->passport, "tune");
-		}
+	if (!offline && user->media.title) {
+		purple_prpl_got_user_status(account, user->passport, "tune",
+				PURPLE_TUNE_ARTIST, user->media.artist,
+				PURPLE_TUNE_ALBUM, user->media.album,
+				PURPLE_TUNE_TITLE, user->media.title,
+				NULL);
+	} else {
+		purple_prpl_got_user_status_deactive(account, user->passport, "tune");
 	}
 
 	if (user->idle)
@@ -136,6 +125,11 @@
 {
 	const char *status;
 
+	if (state == NULL) {
+		user->status = NULL;
+		return;
+	}
+
 	if (!g_ascii_strcasecmp(state, "BSY"))
 		status = "busy";
 	else if (!g_ascii_strcasecmp(state, "BRB"))
@@ -199,18 +193,6 @@
 }
 
 void
-msn_user_set_store_name(MsnUser *user, const char *name)
-{
-	g_return_if_fail(user != NULL);
-
-	if (name != NULL)
-	{
-		g_free(user->store_name);
-		user->store_name = g_strdup(name);
-	}
-}
-
-void
 msn_user_set_uid(MsnUser *user, const char *uid)
 {
 	g_return_if_fail(user != NULL);
@@ -220,14 +202,6 @@
 }
 
 void
-msn_user_set_type(MsnUser *user, MsnUserType type)
-{
-	g_return_if_fail(user != NULL);
-
-	user->type = type;
-}
-
-void
 msn_user_set_op(MsnUser *user, int list_op)
 {
 	g_return_if_fail(user != NULL);
@@ -261,21 +235,19 @@
 
 /*add group id to User object*/
 void
-msn_user_add_group_id(MsnUser *user, const char* id)
+msn_user_add_group_id(MsnUser *user, const char* group_id)
 {
 	MsnUserList *userlist;
 	PurpleAccount *account;
 	PurpleBuddy *b;
 	PurpleGroup *g;
 	const char *passport;
-	char *group_id;
 	const char *group_name;
 
 	g_return_if_fail(user != NULL);
-	g_return_if_fail(id != NULL);
+	g_return_if_fail(group_id != NULL);
 
-	group_id = g_strdup(id);
-	user->group_ids = g_list_append(user->group_ids, group_id);
+	user->group_ids = g_list_append(user->group_ids, g_strdup(group_id));
 
 	userlist = user->userlist;
 	account = userlist->session->account;
@@ -283,11 +255,11 @@
 
 	group_name = msn_userlist_find_group_name(userlist, group_id);
 
-	purple_debug_info("User","group id:%s,name:%s,user:%s\n", group_id, group_name, passport);
+	purple_debug_info("msn", "User: group id:%s,name:%s,user:%s\n", group_id, group_name, passport);
 
 	g = purple_find_group(group_name);
 
-	if ((id == NULL) && (g == NULL))
+	if ((group_id == NULL) && (g == NULL))
 	{
 		g = purple_group_new(group_name);
 		purple_blist_add_group(g, NULL);
@@ -325,12 +297,9 @@
 	if (gc != NULL)
 		session = gc->proto_data;
 
-	if ((session != NULL) && (session->protocol_ver == WLM_PROT_VER))
-		return FALSE;
-
 	if ((session != NULL) && (user = msn_userlist_find_user(session->userlist, name)) != NULL)
 	{
-		return (user->type == MSN_USER_TYPE_YAHOO);
+		return (user->networkid == MSN_NETWORK_YAHOO);
 	}
 	return (strstr(name,"@yahoo.") != NULL);
 }
@@ -380,6 +349,22 @@
 }
 
 void
+msn_user_set_clientid(MsnUser *user, guint clientid)
+{
+	g_return_if_fail(user != NULL);
+
+	user->clientid = clientid;
+}
+
+void
+msn_user_set_network(MsnUser *user, MsnNetwork network)
+{
+	g_return_if_fail(user != NULL);
+
+	user->networkid = network;
+}
+
+void
 msn_user_set_object(MsnUser *user, MsnObject *obj)
 {
 	g_return_if_fail(user != NULL);
@@ -422,14 +407,6 @@
 }
 
 const char *
-msn_user_get_store_name(const MsnUser *user)
-{
-	g_return_val_if_fail(user != NULL, NULL);
-
-	return user->store_name;
-}
-
-const char *
 msn_user_get_home_phone(const MsnUser *user)
 {
 	g_return_val_if_fail(user != NULL, NULL);
@@ -453,6 +430,14 @@
 	return user->phone.mobile;
 }
 
+guint
+msn_user_get_clientid(const MsnUser *user)
+{
+	g_return_val_if_fail(user != NULL, 0);
+
+	return user->clientid;
+}
+
 MsnObject *
 msn_user_get_object(const MsnUser *user)
 {
--- a/libpurple/protocols/msn/user.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/user.h	Thu Nov 20 21:13:56 2008 +0000
@@ -33,14 +33,14 @@
 
 typedef enum
 {
-	MSN_USER_TYPE_UNKNOWN  = 0x00,
-	MSN_USER_TYPE_PASSPORT = 0x01,
-	MSN_USER_TYPE_UNKNOWN1 = 0x02,
-	MSN_USER_TYPE_MOBILE   = 0x04,
-	MSN_USER_TYPE_UNKNOWN2 = 0x08,
-	MSN_USER_TYPE_UNKNOWN3 = 0x10,
-	MSN_USER_TYPE_YAHOO    = 0x20
-} MsnUserType;
+	MSN_NETWORK_UNKNOWN      = 0x00,
+	MSN_NETWORK_PASSPORT     = 0x01,
+	MSN_NETWORK_COMMUNICATOR = 0x02,
+	MSN_NETWORK_MOBILE       = 0x04,
+	MSN_NETWORK_MNI          = 0x08,
+	MSN_NETWORK_SMTP         = 0x10,
+	MSN_NETWORK_YAHOO        = 0x20
+} MsnNetwork;
 
 /**
  * Current media.
@@ -60,7 +60,6 @@
 	MsnUserList *userlist;
 
 	char *passport;         /**< The passport account.          */
-	char *store_name;       /**< The name stored in the server. */
 	char *friendly_name;    /**< The friendly name.             */
 
 	char * uid;				/*< User Id							*/
@@ -88,7 +87,9 @@
 
 	GHashTable *clientcaps; /**< The client's capabilities.     */
 
-	MsnUserType type;       /**< The user type                  */
+	guint clientid;         /**< The client's ID                */
+
+	MsnNetwork networkid;   /**< The user's network             */
 
 	int list_op;            /**< Which lists the user is in     */
 
@@ -96,9 +97,9 @@
 				     indexed by the list it belongs to		*/
 };
 
-/**************************************************************************/
-/** @name User API                                                        */
-/**************************************************************************/
+/**************************************************************************
+ ** @name User API                                                        *
+ **************************************************************************/
 /*@{*/
 
 /**
@@ -111,7 +112,7 @@
  * @return A new user structure.
  */
 MsnUser *msn_user_new(MsnUserList *userlist, const char *passport,
-					  const char *store_name);
+					  const char *friendly_name);
 
 /**
  * Destroys a user structure.
@@ -171,14 +172,6 @@
 void msn_user_set_friendly_name(MsnUser *user, const char *name);
 
 /**
- * Sets the store name for a user.
- *
- * @param user The user.
- * @param name The store name.
- */
-void msn_user_set_store_name(MsnUser *user, const char *name);
-
-/**
  * Sets the buddy icon for a local user.
  *
  * @param user     The user.
@@ -227,7 +220,22 @@
 void msn_user_set_work_phone(MsnUser *user, const char *number);
 
 void msn_user_set_uid(MsnUser *user, const char *uid);
-void msn_user_set_type(MsnUser *user, MsnUserType type);
+
+/**
+ * Sets the client id for a user.
+ *
+ * @param user     The user.
+ * @param clientid The client id.
+ */
+void msn_user_set_clientid(MsnUser *user, guint clientid);
+
+/**
+ * Sets the network id for a user.
+ *
+ * @param user    The user.
+ * @param network The network id.
+ */
+void msn_user_set_network(MsnUser *user, MsnNetwork network);
 
 /**
  * Sets the mobile phone number for a user.
@@ -273,15 +281,6 @@
 const char *msn_user_get_friendly_name(const MsnUser *user);
 
 /**
- * Returns the store name for a user.
- *
- * @param user The user.
- *
- * @return The store name.
- */
-const char *msn_user_get_store_name(const MsnUser *user);
-
-/**
  * Returns the home phone number for a user.
  *
  * @param user The user.
@@ -309,6 +308,24 @@
 const char *msn_user_get_mobile_phone(const MsnUser *user);
 
 /**
+ * Returns the client id for a user.
+ *
+ * @param user    The user.
+ *
+ * @return The user's client id.
+ */
+guint msn_user_get_clientid(const MsnUser *user);
+
+/**
+ * Returns the network id for a user.
+ *
+ * @param user    The user.
+ *
+ * @return The user's network id.
+ */
+MsnNetwork msn_user_get_network(const MsnUser *user);
+
+/**
  * Returns the MSNObject for a user.
  *
  * @param user The user.
--- a/libpurple/protocols/msn/userlist.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msn/userlist.c	Thu Nov 20 21:13:56 2008 +0000
@@ -24,6 +24,8 @@
 #include "msn.h"
 #include "userlist.h"
 
+#include "contact.h"
+
 const char *lists[] = { "FL", "AL", "BL", "RL" };
 
 typedef struct
@@ -42,7 +44,7 @@
 {
 	MsnPermitAdd *pa = data;
 
-	purple_debug_misc("MSN Userlist", "Accepted the new buddy: %s\n", pa->who);
+	purple_debug_misc("msn", "Accepted the new buddy: %s\n", pa->who);
 
 	if (PURPLE_CONNECTION_IS_VALID(pa->gc))
 	{
@@ -51,7 +53,7 @@
 
 		msn_userlist_add_buddy_to_list(userlist, pa->who, MSN_LIST_AL);
 
-		msn_del_contact_from_list(session->contact, NULL, pa->who, MSN_LIST_PL);
+		msn_del_contact_from_list(session, NULL, pa->who, MSN_LIST_PL);
 	}
 
 	g_free(pa->who);
@@ -64,7 +66,7 @@
 {
 	MsnPermitAdd *pa = data;
 
-	purple_debug_misc("MSN Userlist", "Denied the new buddy: %s\n", pa->who);
+	purple_debug_misc("msn", "Denied the new buddy: %s\n", pa->who);
 
 	if (PURPLE_CONNECTION_IS_VALID(pa->gc))
 	{
@@ -75,7 +77,7 @@
 		msn_callback_state_set_action(state, MSN_DENIED_BUDDY);
 
 		msn_userlist_add_buddy_to_list(userlist, pa->who, MSN_LIST_BL);
-		msn_del_contact_from_list(session->contact, state, pa->who, MSN_LIST_PL);
+		msn_del_contact_from_list(session, state, pa->who, MSN_LIST_PL);
 	}
 
 	g_free(pa->who);
@@ -136,36 +138,6 @@
 		return FALSE;
 }
 
-#if 0
-static const char*
-get_store_name(MsnUser *user)
-{
-	const char *store_name;
-
-	g_return_val_if_fail(user != NULL, NULL);
-
-	store_name = msn_user_get_store_name(user);
-
-	if (store_name != NULL)
-		store_name = purple_url_encode(store_name);
-	else
-		store_name = msn_user_get_passport(user);
-
-	/* this might be a bit of a hack, but it should prevent notification server
-	 * disconnections for people who have buddies with insane friendly names
-	 * who added you to their buddy list from being disconnected. Stu. */
-	/* Shx: What? Isn't the store_name obtained from the server, and hence it's
-	 * below the BUDDY_ALIAS_MAXLEN ? */
-	/* Stu: yeah, that's why it's a bit of a hack, as you pointed out, we're
-	 * probably decoding the incoming store_name wrong, or something. bleh. */
-
-	if (strlen(store_name) > BUDDY_ALIAS_MAXLEN)
-		store_name = msn_user_get_passport(user);
-
-	return store_name;
-}
-#endif
-
 /**************************************************************************
  * Server functions
  **************************************************************************/
@@ -194,7 +166,7 @@
 	const char *passport;
 	const char *friendly;
 
-	purple_debug_info("MSNP14","got add user...\n");
+	purple_debug_info("msn", "got add user...\n");
 	account = session->account;
 
 	passport = msn_user_get_passport(user);
@@ -254,7 +226,7 @@
 			 *       looked at this.  Maybe we should use the store
 			 *       name instead? --KingAnt
 			 */
-//			got_new_entry(gc, passport, friendly);
+/*			got_new_entry(gc, passport, friendly); */
 		}
 	}
 
@@ -340,7 +312,7 @@
 	gc = purple_account_get_connection(account);
 
 	passport = msn_user_get_passport(user);
-	store = msn_user_get_store_name(user);
+	store = msn_user_get_friendly_name(user);
 
 	msn_user_set_op(user, list_op);
 
@@ -384,7 +356,7 @@
 
 		if (!(list_op & (MSN_LIST_AL_OP | MSN_LIST_BL_OP)))
 		{
-//			got_new_entry(gc, passport, store);
+/*			got_new_entry(gc, passport, store); */
 		}
 	}
 
@@ -454,7 +426,7 @@
 		user = msn_user_new(userlist, passport, userName);
 		msn_userlist_add_user(userlist, user);
 	} else {
-		msn_user_set_store_name(user, userName);
+		msn_user_set_friendly_name(user, userName);
 	}
 	return user;
 }
@@ -482,11 +454,9 @@
 	{
 		MsnUser *user = (MsnUser *)l->data;
 
-//		purple_debug_info("MsnUserList","user passport:%s,passport:%s\n",user->passport,passport);
 		g_return_val_if_fail(user->passport != NULL, NULL);
 
 		if (!g_strcasecmp(passport, user->passport)){
-//			purple_debug_info("MsnUserList","return:%p\n",user);
 			return user;
 		}
 	}
@@ -647,7 +617,6 @@
 
 	g_return_if_fail(userlist != NULL);
 	g_return_if_fail(userlist->session != NULL);
-	g_return_if_fail(userlist->session->contact != NULL);
 	g_return_if_fail(who != NULL);
 
 	user = msn_userlist_find_user(userlist, who);
@@ -656,7 +625,7 @@
 
 	/* delete the contact from address book via soap action */
 	if (user != NULL) {
-		msn_delete_contact(userlist->session->contact, user->uid);
+		msn_delete_contact(userlist->session, user->uid);
 	}
 }
 
@@ -674,7 +643,7 @@
 
 	if ( !msn_userlist_user_is_in_list(user, list_id)) {
 		list = lists[list_id];
-		purple_debug_info("MSN Userlist", "User %s is not in list %s, not removing.\n", who, list);
+		purple_debug_info("msn", "User %s is not in list %s, not removing.\n", who, list);
 		return;
 	}
 
@@ -693,16 +662,10 @@
 
 	new_group_name = group_name == NULL ? MSN_INDIVIDUALS_GROUP_NAME : group_name;
 
-
 	g_return_if_fail(userlist != NULL);
 	g_return_if_fail(userlist->session != NULL);
 
-
-	purple_debug_info("MSN Userlist", "Add user: %s to group: %s\n", who, new_group_name);
-
-	state = msn_callback_state_new(userlist->session);
-	msn_callback_state_set_who(state, who);
-	msn_callback_state_set_new_group_name(state, new_group_name);
+	purple_debug_info("msn", "Add user: %s to group: %s\n", who, new_group_name);
 
 	if (!purple_email_is_valid(who))
 	{
@@ -719,12 +682,16 @@
 		return;
 	}
 
+	state = msn_callback_state_new(userlist->session);
+	msn_callback_state_set_who(state, who);
+	msn_callback_state_set_new_group_name(state, new_group_name);
+
 	group_id = msn_userlist_find_group_id(userlist, new_group_name);
 
 	if (group_id == NULL)
 	{
 		/* Whoa, we must add that group first. */
-		purple_debug_info("MSN Userlist", "Adding user %s to a new group, creating group %s first\n", who, new_group_name);
+		purple_debug_info("msn", "Adding user %s to a new group, creating group %s first\n", who, new_group_name);
 
 		msn_callback_state_set_action(state, MSN_ADD_BUDDY);
 
@@ -742,23 +709,24 @@
 
 	if ( msn_userlist_user_is_in_list(user, MSN_LIST_FL) ) {
 
-		purple_debug_info("MSN Userlist", "User %s already exists\n", who);
+		purple_debug_info("msn", "User %s already exists\n", who);
 
 		msn_userlist_rem_buddy_from_list(userlist, who, MSN_LIST_BL);
 
 		if (msn_userlist_user_is_in_group(user, group_id)) {
-			purple_debug_info("MSN Userlist", "User %s is already in group %s, returning\n", who, new_group_name);
+			purple_debug_info("msn", "User %s is already in group %s, returning\n", who, new_group_name);
+			msn_callback_state_free(state);
 			return;
 		}
 	}
 
-	purple_debug_info("MSN Userlist", "Adding user: %s to group id: %s\n", who, group_id);
+	purple_debug_info("msn", "Adding user: %s to group id: %s\n", who, group_id);
 
 	msn_callback_state_set_action(state, MSN_ADD_BUDDY);
 
 	/* Add contact in the Contact server with a SOAP request and if
 	   successful, send ADL with MSN_LIST_AL and MSN_LIST_FL and a FQY */
-	msn_add_contact_to_group(userlist->session->contact, state, who, group_id);
+	msn_add_contact_to_group(userlist->session, state, who, group_id);
 }
 
 void
@@ -777,14 +745,10 @@
 	if (msn_userlist_user_is_in_list(user, list_id))
 	{
 		list = lists[list_id];
-		purple_debug_info("MSN Userlist", "User '%s' is already in list: %s\n", who, list);
+		purple_debug_info("msn", "User '%s' is already in list: %s\n", who, list);
 		return;
 	}
 
-	//store_name = (user != NULL) ? get_store_name(user) : who;
-
-	//purple_debug_info("MSN Userlist", "store_name = %s\n", store_name);
-
 	/* XXX: see XXX above, this should really be done when we get the response from
 		the server */
 
@@ -804,15 +768,15 @@
 	g_return_val_if_fail(group_name != NULL, FALSE);
 	g_return_val_if_fail(who != NULL, FALSE);
 
-	purple_debug_info("MSN Userlist","Adding buddy with passport %s to group %s\n", who, group_name);
+	purple_debug_info("msn", "Adding buddy with passport %s to group %s\n", who, group_name);
 
 	if ( (group_id = (gchar *)msn_userlist_find_group_id(userlist, group_name)) == NULL) {
-		purple_debug_error("MSN Userlist", "Group %s has no guid!\n", group_name);
+		purple_debug_error("msn", "Group %s has no guid!\n", group_name);
 		return FALSE;
 	}
 
 	if ( (user = msn_userlist_find_user(userlist, who)) == NULL) {
-		purple_debug_error("MSN Userlist", "User %s not found!", who);
+		purple_debug_error("msn", "User %s not found!", who);
 		return FALSE;
 	}
 
@@ -833,15 +797,15 @@
 	g_return_val_if_fail(group_name != NULL, FALSE);
 	g_return_val_if_fail(who != NULL, FALSE);
 
-	purple_debug_info("MSN Userlist","Removing buddy with passport %s from group %s\n", who, group_name);
+	purple_debug_info("msn", "Removing buddy with passport %s from group %s\n", who, group_name);
 
 	if ( (group_id = msn_userlist_find_group_id(userlist, group_name)) == NULL) {
-		purple_debug_error("MSN Userlist", "Group %s has no guid!\n", group_name);
+		purple_debug_error("msn", "Group %s has no guid!\n", group_name);
 		return FALSE;
 	}
 
 	if ( (user = msn_userlist_find_user(userlist, who)) == NULL) {
-		purple_debug_error("MSN Userlist", "User %s not found!", who);
+		purple_debug_error("msn", "User %s not found!", who);
 		return FALSE;
 	}
 
@@ -859,7 +823,6 @@
 
 	g_return_if_fail(userlist != NULL);
 	g_return_if_fail(userlist->session != NULL);
-	g_return_if_fail(userlist->session->contact != NULL);
 
 	state = msn_callback_state_new(userlist->session);
 	msn_callback_state_set_who(state, who);
@@ -878,7 +841,7 @@
 	/* add the contact to the new group, and remove it from the old one in
 	 * the callback
 	*/
-	msn_add_contact_to_group(userlist->session->contact, state, who, new_group_id);
+	msn_add_contact_to_group(userlist->session, state, who, new_group_id);
 }
 
 /*load userlist from the Blist file cache*/
--- a/libpurple/protocols/msnp9/command.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msnp9/command.c	Thu Nov 20 21:13:56 2008 +0000
@@ -65,9 +65,12 @@
 		for (c = 0; cmd->params[c]; c++);
 		cmd->param_count = c;
 
-		param = cmd->params[0];
-
-		cmd->trId = is_num(param) ? atoi(param) : 0;
+		if (cmd->param_count) {
+			char *param = cmd->params[0];
+			cmd->trId = is_num(param) ? atoi(param) : 0;
+		} else {
+			cmd->trId = 0;
+		}
 	}
 	else
 		cmd->trId = 0;
--- a/libpurple/protocols/msnp9/servconn.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msnp9/servconn.c	Thu Nov 20 21:13:56 2008 +0000
@@ -251,6 +251,12 @@
 {
 	g_return_if_fail(servconn != NULL);
 
+	if (servconn->connect_data != NULL)
+	{
+		purple_proxy_connect_cancel(servconn->connect_data);
+		servconn->connect_data = NULL;
+	}
+
 	if (!servconn->connected)
 	{
 		/* We could not connect. */
@@ -269,12 +275,6 @@
 		return;
 	}
 
-	if (servconn->connect_data != NULL)
-	{
-		purple_proxy_connect_cancel(servconn->connect_data);
-		servconn->connect_data = NULL;
-	}
-
 	if (servconn->inpa > 0)
 	{
 		purple_input_remove(servconn->inpa);
--- a/libpurple/protocols/msnp9/slplink.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/msnp9/slplink.c	Thu Nov 20 21:13:56 2008 +0000
@@ -597,7 +597,7 @@
 	}
 	else if (slpmsg->size)
 	{
-		if ((offset + len) > slpmsg->size)
+		if (G_MAXSIZE - len < offset || (offset + len) > slpmsg->size)
 		{
 			purple_debug_error("msn", "Oversized slpmsg\n");
 			g_return_if_reached();
--- a/libpurple/protocols/myspace/user.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/myspace/user.c	Thu Nov 20 21:13:56 2008 +0000
@@ -635,7 +635,7 @@
 	if (!body) {
 		purple_debug_info("msim_username_is_available_cb", "No body for %s?!\n", username);
 		purple_connection_error_reason(session->gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, 
-				"An error occured while trying to set the username.\n"
+				"An error occurred while trying to set the username.\n"
 				"Please try again, or visit http://editprofile.myspace.com/index.cfm?"
 				"fuseaction=profile.username to set your username.");
 		return;
@@ -778,7 +778,7 @@
 	uid = msim_msg_get_integer(userinfo, "uid");
 	lid = msim_msg_get_integer(userinfo, "lid");
 	body = msim_msg_get_dictionary(userinfo, "body");
-	errmsg = g_strdup("An error occured while trying to set the username.\n"
+	errmsg = g_strdup("An error occurred while trying to set the username.\n"
 			"Please try again, or visit http://editprofile.myspace.com/index.cfm?"
 			"fuseaction=profile.username to set your username.");
 	
--- a/libpurple/protocols/oscar/family_auth.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/oscar/family_auth.c	Thu Nov 20 21:13:56 2008 +0000
@@ -81,12 +81,9 @@
 static int
 aim_encode_password_md5(const char *password, size_t password_len, const char *key, guint8 *digest)
 {
-	PurpleCipher *cipher;
 	PurpleCipherContext *context;
 
-	cipher = purple_ciphers_find_cipher("md5");
-
-	context = purple_cipher_context_new(cipher, NULL);
+	context = purple_cipher_context_new_by_name("md5", NULL);
 	purple_cipher_context_append(context, (const guchar *)key, strlen(key));
 	purple_cipher_context_append(context, (const guchar *)password, password_len);
 	purple_cipher_context_append(context, (const guchar *)AIM_MD5_STRING, strlen(AIM_MD5_STRING));
--- a/libpurple/protocols/oscar/family_icq.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/oscar/family_icq.c	Thu Nov 20 21:13:56 2008 +0000
@@ -36,6 +36,8 @@
 	if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICQ)))
 		return -EINVAL;
 
+	purple_debug_info("oscar", "Requesting offline messages from %s", od->sn);
+
 	bslen = 2 + 4 + 2 + 2;
 
 	byte_stream_new(&bs, 4 + bslen);
@@ -68,6 +70,8 @@
 	if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICQ)))
 		return -EINVAL;
 
+	purple_debug_info("oscar", "Acknowledged receipt of offline messages from %s", od->sn);
+
 	bslen = 2 + 4 + 2 + 2;
 
 	byte_stream_new(&bs, 4 + bslen);
@@ -214,7 +218,7 @@
 	byte_stream_putle16(&bs, 0x04b2); /* shrug. */
 	byte_stream_putle32(&bs, atoi(uin));
 
-	flap_connection_send_snac(od, conn, SNAC_FAMILY_ICQ, 0x0002, 0x0000, snacid, &bs);
+	flap_connection_send_snac_with_priority(od, conn, SNAC_FAMILY_ICQ, 0x0002, 0x0000, snacid, &bs, FALSE);
 
 	byte_stream_destroy(&bs);
 
@@ -242,6 +246,8 @@
 	if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICQ)))
 		return -EINVAL;
 
+	purple_debug_info("oscar", "Requesting ICQ alias for %s", uin);
+
 	bslen = 2 + 4 + 2 + 2 + 2 + 4;
 
 	byte_stream_new(&bs, 4 + bslen);
@@ -259,7 +265,7 @@
 	byte_stream_putle16(&bs, 0x04ba); /* shrug. */
 	byte_stream_putle32(&bs, atoi(uin));
 
-	flap_connection_send_snac(od, conn, SNAC_FAMILY_ICQ, 0x0002, 0x0000, snacid, &bs);
+	flap_connection_send_snac_with_priority(od, conn, SNAC_FAMILY_ICQ, 0x0002, 0x0000, snacid, &bs, FALSE);
 
 	byte_stream_destroy(&bs);
 
@@ -303,7 +309,7 @@
 	byte_stream_putle16(&bs, 0x051f); /* shrug. */
 	byte_stream_putle32(&bs, atoi(uin));
 
-	flap_connection_send_snac(od, conn, SNAC_FAMILY_ICQ, 0x0002, 0x0000, snacid, &bs);
+	flap_connection_send_snac_with_priority(od, conn, SNAC_FAMILY_ICQ, 0x0002, 0x0000, snacid, &bs, FALSE);
 
 	byte_stream_destroy(&bs);
 
@@ -497,7 +503,7 @@
 	byte_stream_put16(&bs, strlen(uin));
 	byte_stream_putstr(&bs, uin);
 
-	flap_connection_send_snac(od, conn, SNAC_FAMILY_ICQ, 0x0002, 0x000, snacid, &bs);
+	flap_connection_send_snac_with_priority(od, conn, SNAC_FAMILY_ICQ, 0x0002, 0x000, snacid, &bs, FALSE);
 
 	byte_stream_destroy(&bs);
 
@@ -877,7 +883,7 @@
 				info->next = od->icq_info;
 				od->icq_info = info;
 
-				flap_connection_send_snac(od, conn, 0x0004, 0x0006, 0x0000, snacid, &bs);
+				flap_connection_send_snac_with_priority(od, conn, 0x0004, 0x0006, 0x0000, snacid, &bs, FALSE);
 
 				byte_stream_destroy(&bs);
 			}
--- a/libpurple/protocols/oscar/family_locate.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/oscar/family_locate.c	Thu Nov 20 21:13:56 2008 +0000
@@ -53,10 +53,9 @@
 	 * These are in ascending numerical order.
 	 */
 
-	/*
-	 * Perhaps better called OSCAR_CAPABILITY_SHORTCAPS
-	 */
-	{OSCAR_CAPABILITY_ICHAT,
+	/* Client understands short caps, a UUID of the form
+	 * 0946XXYY-4C7F-11D1-8222-444553540000 where XXYY is the short cap. */
+	{OSCAR_CAPABILITY_SHORTCAPS,
 	 {0x09, 0x46, 0x00, 0x00, 0x4c, 0x7f, 0x11, 0xd1,
 	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
 
@@ -64,11 +63,16 @@
 	 {0x09, 0x46, 0x00, 0x01, 0x4c, 0x7f, 0x11, 0xd1,
 	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
 
+	/* OSCAR_CAPABILITY_XHTML_IM */
+	{OSCAR_CAPABILITY_GENERICUNKNOWN,
+	 {0x09, 0x46, 0x00, 0x02, 0x4c, 0x7f, 0x11, 0xd1,
+	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
 	{OSCAR_CAPABILITY_VIDEO,
 	 {0x09, 0x46, 0x01, 0x00, 0x4c, 0x7f, 0x11, 0xd1,
 	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
 
-	/* "Live Video" support in Windows AIM 5.5.3501 and newer */
+	/* "Live Video" (SIP/RTC Video) support in Windows AIM 5.5.3501 and newer */
 	{OSCAR_CAPABILITY_LIVEVIDEO,
 	 {0x09, 0x46, 0x01, 0x01, 0x4c, 0x7f, 0x11, 0xd1,
 	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
@@ -78,22 +82,38 @@
 	 {0x09, 0x46, 0x01, 0x02, 0x4c, 0x7f, 0x11, 0xd1,
 	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
 
-	/* In Windows AIM 5.5.3501 and newer */
+	/* "Microphone" support in Windows AIM 5.5.3501 and newer */
+	/* OSCAR_CAPABILITY_MICROPHONE */
 	{OSCAR_CAPABILITY_GENERICUNKNOWN,
 	 {0x09, 0x46, 0x01, 0x03, 0x4c, 0x7f, 0x11, 0xd1,
 	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
 
+	/* Supports RTC Audio */
+	/* OSCAR_CAPABILITY_RTCAUDIO */
+	{OSCAR_CAPABILITY_GENERICUNKNOWN,
+	 {0x09, 0x46, 0x01, 0x04, 0x4c, 0x7f, 0x11, 0xd1,
+	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
 	/* In iChatAV (version numbers...?) */
 	{OSCAR_CAPABILITY_ICHATAV,
 	 {0x09, 0x46, 0x01, 0x05, 0x4c, 0x7f, 0x11, 0xd1,
 	  0x82, 0x22, 0x44, 0x45, 0x45, 0x53, 0x54, 0x00}},
 
-	/*
-	 * Not really sure about this one.  In an email from
-	 * 26 Sep 2003, Matthew Sachs suggested that, "this
-	 * is probably the capability for the SMS features."
-	 */
-	{OSCAR_CAPABILITY_SMS,
+	/* Supports "new status message features" (Who advertises this one?) */
+	/* OSCAR_CAPABILITY_HOST_STATUS_TEXT_AWARE */ 
+	{OSCAR_CAPABILITY_GENERICUNKNOWN,
+	 {0x09, 0x46, 0x01, 0x0a, 0x4c, 0x7f, 0x11, 0xd1,
+	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+	/* Supports "see as I type" (Who advertises this one?) */
+	/* OSCAR_CAPABILITY_SEE_AS_I_TYPE */
+	{OSCAR_CAPABILITY_GENERICUNKNOWN,
+	 {0x09, 0x46, 0x01, 0x0b, 0x4c, 0x7f, 0x11, 0xd1,
+	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
+
+	/* Client only asserts caps for services in which it is participating */
+	/* OSCAR_CAPABILITY_SMARTCAPS */ 
+	{OSCAR_CAPABILITY_GENERICUNKNOWN,
 	 {0x09, 0x46, 0x01, 0xff, 0x4c, 0x7f, 0x11, 0xd1,
 	  0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}},
 
@@ -1386,7 +1406,7 @@
 	byte_stream_putstr(&bs, sn);
 
 	snacid = aim_cachesnac(od, SNAC_FAMILY_LOCATE, 0x0015, 0x0000, sn, strlen(sn)+1);
-	flap_connection_send_snac(od, conn, SNAC_FAMILY_LOCATE, 0x0015, 0x0000, snacid, &bs);
+	flap_connection_send_snac_with_priority(od, conn, SNAC_FAMILY_LOCATE, 0x0015, 0x0000, snacid, &bs, FALSE);
 
 	byte_stream_destroy(&bs);
 
--- a/libpurple/protocols/oscar/family_oservice.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/oscar/family_oservice.c	Thu Nov 20 21:13:56 2008 +0000
@@ -952,13 +952,10 @@
 		byte_stream_putraw(&bs, buf, 0x10);
 
 	} else if (buf && (len > 0)) { /* use input buffer */
-		PurpleCipher *cipher;
 		PurpleCipherContext *context;
 		guchar digest[16];
 
-		cipher = purple_ciphers_find_cipher("md5");
-
-		context = purple_cipher_context_new(cipher, NULL);
+		context = purple_cipher_context_new_by_name("md5", NULL);
 		purple_cipher_context_append(context, buf, len);
 		purple_cipher_context_digest(context, 16, digest, NULL);
 		purple_cipher_context_destroy(context);
@@ -966,7 +963,6 @@
 		byte_stream_putraw(&bs, digest, 0x10);
 
 	} else if (len == 0) { /* no length, just hash NULL (buf is optional) */
-		PurpleCipher *cipher;
 		PurpleCipherContext *context;
 		guchar digest[16];
 		guint8 nil = '\0';
@@ -975,9 +971,7 @@
 		 * I'm not sure if we really need the empty append with the
 		 * new MD5 functions, so I'll leave it in, just in case.
 		 */
-		cipher = purple_ciphers_find_cipher("md5");
-
-		context = purple_cipher_context_new(cipher, NULL);
+		context = purple_cipher_context_new_by_name("md5", NULL);
 		purple_cipher_context_append(context, &nil, 0);
 		purple_cipher_context_digest(context, 16, digest, NULL);
 		purple_cipher_context_destroy(context);
--- a/libpurple/protocols/oscar/flap_connection.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/oscar/flap_connection.c	Thu Nov 20 21:13:56 2008 +0000
@@ -107,21 +107,21 @@
 	return MIN(((rateclass->current * (rateclass->windowsize - 1)) + timediff) / rateclass->windowsize, rateclass->max);
 }
 
-static gboolean flap_connection_send_queued(gpointer data)
+/*
+ * Attempt to send the contents of a given queue
+ *
+ * @return TRUE if the queue was completely emptied or was initially
+ *         empty; FALSE if rate limiting prevented it from being
+ *         emptied.
+ */
+static gboolean flap_connection_send_snac_queue(FlapConnection *conn, struct timeval now, GQueue *queue)
 {
-	FlapConnection *conn;
-	struct timeval now;
-
-	conn = data;
-	gettimeofday(&now, NULL);
-
-	purple_debug_info("oscar", "Attempting to send %u queued SNACs for %p\n", g_queue_get_length(conn->queued_snacs), conn);
-	while (!g_queue_is_empty(conn->queued_snacs))
+	while (!g_queue_is_empty(queue))
 	{
 		QueuedSnac *queued_snac;
 		struct rateclass *rateclass;
 
-		queued_snac = g_queue_peek_head(conn->queued_snacs);
+		queued_snac = g_queue_peek_head(queue);
 
 		rateclass = flap_connection_get_rateclass(conn, queued_snac->family, queued_snac->subtype);
 		if (rateclass != NULL)
@@ -133,7 +133,7 @@
 			/* (Add 100ms padding to account for inaccuracies in the calculation) */
 			if (new_current < rateclass->alert + 100)
 				/* Not ready to send this SNAC yet--keep waiting. */
-				return TRUE;
+				return FALSE;
 
 			rateclass->current = new_current;
 			rateclass->last.tv_sec = now.tv_sec;
@@ -142,11 +142,35 @@
 
 		flap_connection_send(conn, queued_snac->frame);
 		g_free(queued_snac);
-		g_queue_pop_head(conn->queued_snacs);
+		g_queue_pop_head(queue);
 	}
 
-	conn->queued_timeout = 0;
-	return FALSE;
+	/* We emptied the queue */
+	return TRUE;
+}
+
+static gboolean flap_connection_send_queued(gpointer data)
+{
+	FlapConnection *conn;
+	struct timeval now;
+
+	conn = data;
+	gettimeofday(&now, NULL);
+
+	purple_debug_info("oscar", "Attempting to send %u queued SNACs and %u queued low-priority SNACs for %p\n",
+					  (conn->queued_snacs ? g_queue_get_length(conn->queued_snacs) : 0),
+					  (conn->queued_lowpriority_snacs ? g_queue_get_length(conn->queued_lowpriority_snacs) : 0),
+					  conn);
+	if (!conn->queued_snacs || flap_connection_send_snac_queue(conn, now, conn->queued_snacs)) {
+		if (!conn->queued_lowpriority_snacs || flap_connection_send_snac_queue(conn, now, conn->queued_lowpriority_snacs)) {
+			/* Both queues emptied. */
+			conn->queued_timeout = 0;
+			return FALSE;
+		}
+	}
+
+	/* We couldn't send all our SNACs. Keep trying */
+	return TRUE;
 }
 
 /**
@@ -157,9 +181,12 @@
  *
  * @param data The optional bytestream that makes up the data portion
  *        of this SNAC.  For empty SNACs this should be NULL.
+ * @param high_priority If TRUE, the SNAC will be queued normally if
+ *        needed. If FALSE, it wil be queued separately, to be sent
+ *        only if all high priority SNACs have been sent.
  */
 void
-flap_connection_send_snac(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, guint16 flags, aim_snacid_t snacid, ByteStream *data)
+flap_connection_send_snac_with_priority(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, guint16 flags, aim_snacid_t snacid, ByteStream *data, gboolean high_priority)
 {
 	FlapFrame *frame;
 	guint32 length;
@@ -213,7 +240,16 @@
 		queued_snac->family = family;
 		queued_snac->subtype = subtype;
 		queued_snac->frame = frame;
-		g_queue_push_tail(conn->queued_snacs, queued_snac);
+
+		if (high_priority) {
+			if (!conn->queued_snacs)
+				conn->queued_snacs = g_queue_new();
+			g_queue_push_tail(conn->queued_snacs, queued_snac);
+		} else {
+			if (!conn->queued_lowpriority_snacs)
+				conn->queued_lowpriority_snacs = g_queue_new();
+			g_queue_push_tail(conn->queued_lowpriority_snacs, queued_snac);
+		}
 
 		if (conn->queued_timeout == 0)
 			conn->queued_timeout = purple_timeout_add(500, flap_connection_send_queued, conn);
@@ -224,6 +260,12 @@
 	flap_connection_send(conn, frame);
 }
 
+void
+flap_connection_send_snac(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, guint16 flags, aim_snacid_t snacid, ByteStream *data)
+{
+	flap_connection_send_snac_with_priority(od, conn, family, subtype, flags, snacid, data, TRUE);
+}
+
 /**
  * This sends an empty channel 4 FLAP.  This is sent to signify
  * that we're logging off.  This shouldn't really be necessary--
@@ -275,7 +317,6 @@
 	conn->fd = -1;
 	conn->subtype = -1;
 	conn->type = type;
-	conn->queued_snacs = g_queue_new();
 
 	od->oscar_connections = g_slist_prepend(od->oscar_connections, conn);
 
@@ -299,12 +340,12 @@
 		conn->connect_data = NULL;
 	}
 
-	if (conn->connect_data != NULL)
+	if (conn->new_conn_data != NULL)
 	{
 		if (conn->type == SNAC_FAMILY_CHAT)
 		{
 			oscar_chat_destroy(conn->new_conn_data);
-			conn->connect_data = NULL;
+			conn->new_conn_data = NULL;
 		}
 	}
 
@@ -434,14 +475,28 @@
 		conn->rateclasses = g_slist_delete_link(conn->rateclasses, conn->rateclasses);
 	}
 
-	while (!g_queue_is_empty(conn->queued_snacs))
-	{
-		QueuedSnac *queued_snac;
-		queued_snac = g_queue_pop_head(conn->queued_snacs);
-		flap_frame_destroy(queued_snac->frame);
-		g_free(queued_snac);
+	if (conn->queued_snacs) {
+		while (!g_queue_is_empty(conn->queued_snacs))
+		{
+			QueuedSnac *queued_snac;
+			queued_snac = g_queue_pop_head(conn->queued_snacs);
+			flap_frame_destroy(queued_snac->frame);
+			g_free(queued_snac);
+		}
+		g_queue_free(conn->queued_snacs);
 	}
-	g_queue_free(conn->queued_snacs);
+
+	if (conn->queued_lowpriority_snacs) {
+		while (!g_queue_is_empty(conn->queued_lowpriority_snacs))
+		{
+			QueuedSnac *queued_snac;
+			queued_snac = g_queue_pop_head(conn->queued_lowpriority_snacs);
+			flap_frame_destroy(queued_snac->frame);
+			g_free(queued_snac);
+		}
+		g_queue_free(conn->queued_lowpriority_snacs);
+	}
+
 	if (conn->queued_timeout > 0)
 		purple_timeout_remove(conn->queued_timeout);
 
@@ -998,4 +1053,3 @@
 	sendframe_flap(conn, frame);
 	flap_frame_destroy(frame);
 }
-
--- a/libpurple/protocols/oscar/oscar.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Thu Nov 20 21:13:56 2008 +0000
@@ -66,7 +66,9 @@
 
 #define OSCAR_CONNECT_STEPS 6
 
-static OscarCapability purple_caps = OSCAR_CAPABILITY_CHAT | OSCAR_CAPABILITY_BUDDYICON | OSCAR_CAPABILITY_DIRECTIM | OSCAR_CAPABILITY_SENDFILE | OSCAR_CAPABILITY_UNICODE | OSCAR_CAPABILITY_INTEROPERATE | OSCAR_CAPABILITY_ICHAT;
+static OscarCapability purple_caps = (OSCAR_CAPABILITY_CHAT | OSCAR_CAPABILITY_BUDDYICON | OSCAR_CAPABILITY_DIRECTIM |
+									  OSCAR_CAPABILITY_SENDFILE | OSCAR_CAPABILITY_UNICODE | OSCAR_CAPABILITY_INTEROPERATE |
+									  OSCAR_CAPABILITY_SHORTCAPS);
 
 static guint8 features_aim[] = {0x01, 0x01, 0x01, 0x02};
 static guint8 features_icq[] = {0x01, 0x06};
@@ -1510,6 +1512,7 @@
 			/* Suspended account */
 			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Your account is currently suspended."));
 			break;
+		case 0x02:
 		case 0x14:
 			/* service temporarily unavailable */
 			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("The AOL Instant Messenger service is temporarily unavailable."));
@@ -1519,10 +1522,14 @@
 			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("You have been connecting and disconnecting too frequently. Wait ten minutes and try again. If you continue to try, you will need to wait even longer."));
 			break;
 		case 0x1c:
+		{
 			/* client too old */
-			g_snprintf(buf, sizeof(buf), _("The client version you are using is too old. Please upgrade at %s"), PURPLE_WEBSITE);
+			GHashTable *ui_info = purple_core_get_ui_info();
+			g_snprintf(buf, sizeof(buf), _("The client version you are using is too old. Please upgrade at %s"),
+					   ((ui_info && g_hash_table_lookup(ui_info, "website")) ? (char *)g_hash_table_lookup(ui_info, "website") : PURPLE_WEBSITE));
 			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, buf);
 			break;
+		}
 		case 0x1d:
 			/* IP address connecting too frequently */
 			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("You have been connecting and disconnecting too frequently. Wait ten minutes and try again. If you continue to try, you will need to wait even longer."));
@@ -1640,8 +1647,10 @@
 	}
 	if (in != '\n') {
 		char buf[256];
+		GHashTable *ui_info = purple_core_get_ui_info();		
 		g_snprintf(buf, sizeof(buf), _("You may be disconnected shortly.  You may want to use TOC until "
-			"this is fixed.  Check %s for updates."), PURPLE_WEBSITE);
+			"this is fixed.  Check %s for updates."),
+				   ((ui_info && g_hash_table_lookup(ui_info, "website")) ? (char *)g_hash_table_lookup(ui_info, "website") : PURPLE_WEBSITE));
 		purple_notify_warning(pos->gc, NULL,
 							_("Unable to get a valid AIM login hash."),
 							buf);
@@ -1684,8 +1693,10 @@
 	pos->fd = source;
 
 	if (source < 0) {
+		GHashTable *ui_info = purple_core_get_ui_info();				
 		buf = g_strdup_printf(_("You may be disconnected shortly.  "
-				"Check %s for updates."), PURPLE_WEBSITE);
+				"Check %s for updates."),
+				((ui_info && g_hash_table_lookup(ui_info, "website")) ? (char *)g_hash_table_lookup(ui_info, "website") : PURPLE_WEBSITE));
 		purple_notify_warning(pos->gc, NULL,
 							_("Unable to get a valid AIM login hash."),
 							buf);
@@ -1781,10 +1792,13 @@
 			straight_to_hell, pos) == NULL)
 	{
 		char buf[256];
+		GHashTable *ui_info = purple_core_get_ui_info();
 		g_free(pos->modname);
 		g_free(pos);
+
 		g_snprintf(buf, sizeof(buf), _("You may be disconnected shortly.  "
-			"Check %s for updates."), PURPLE_WEBSITE);
+			"Check %s for updates."),
+			((ui_info && g_hash_table_lookup(ui_info, "website")) ? (char *)g_hash_table_lookup(ui_info, "website") : PURPLE_WEBSITE));
 		purple_notify_warning(pos->gc, NULL,
 							_("Unable to get a valid login hash."),
 							buf);
@@ -1882,6 +1896,38 @@
 	return 1;
 }
 
+static gboolean purple_requesticqstatusnote(gpointer data)
+{
+	PurpleConnection *gc = data;
+	OscarData *od = gc->proto_data;
+
+	while (od->statusnotes_queue != NULL)
+	{
+		char *sn;
+		struct aim_ssi_item *ssi_item;
+		aim_tlv_t *note_hash;
+
+		sn = od->statusnotes_queue->data;
+
+		ssi_item = aim_ssi_itemlist_finditem(od->ssi.local,
+											 NULL, sn, AIM_SSI_TYPE_BUDDY);
+		if (ssi_item != NULL)
+		{
+			note_hash = aim_tlv_gettlv(ssi_item->data, 0x015c, 1);
+			if (note_hash != NULL) {
+				aim_icq_getstatusnote(od, sn, note_hash->value, note_hash->length);
+			}
+		}
+
+		od->statusnotes_queue = g_slist_remove(od->statusnotes_queue, sn);
+		g_free(sn);
+	}
+
+	od->statusnotes_queue_timer = 0;
+	return FALSE;
+}
+
+
 static int purple_parse_oncoming(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...)
 {
 	PurpleConnection *gc;
@@ -1977,7 +2023,17 @@
 	}
 	else
 	{
-		purple_prpl_got_user_status(account, info->sn, status_id, NULL);
+		PurpleBuddy *b = purple_find_buddy(account, info->sn);
+		PurplePresence *presence = purple_buddy_get_presence(b);
+		PurpleStatus *old_status = purple_presence_get_active_status(presence);
+		PurpleStatus *new_status = purple_presence_get_status(presence, status_id);
+		
+		/* If our status_id would change with this update, pass it to the core.
+		 * However, if our status_id would not change, do nothing, as we would clear out any existing
+		 * attributes on the status prematurely. purple_got_infoblock() will update the message as needed.
+		 */
+		if (old_status != new_status)
+			purple_prpl_got_user_status(account, info->sn, status_id, NULL);
 	}
 
 	/* Login time stuff */
@@ -2053,8 +2109,28 @@
 		if (ssi_item != NULL)
 		{
 			note_hash = aim_tlv_gettlv(ssi_item->data, 0x015c, 1);
-			if (note_hash != NULL)
-				aim_icq_getstatusnote(od, info->sn, note_hash->value, note_hash->length);
+			if (note_hash != NULL) {
+				/* We do automatic rate limiting at the FLAP level, so
+				 * a flood of requests won't disconnect us.  However,
+				 * it WOULD mean that we would have to wait a
+				 * potentially long time to be able to message in real
+				 * time again.  Also, since we're requesting with every
+				 * purple_parse_oncoming() call, which often come in
+				 * groups, we should coalesce to do only one lookup per
+				 * buddy.
+				 */
+				if (od->statusnotes_queue == NULL ||
+					g_slist_find_custom(od->statusnotes_queue, info->sn, (GCompareFunc)strcmp) == NULL)
+				{
+					od->statusnotes_queue = g_slist_prepend(od->statusnotes_queue,
+							g_strdup(info->sn));
+
+					if (od->statusnotes_queue_timer > 0)
+						purple_timeout_remove(od->statusnotes_queue_timer);
+					od->statusnotes_queue_timer = purple_timeout_add_seconds(3,
+							purple_requesticqstatusnote, gc);
+				}
+			}
 		}
 	}
 
@@ -3061,7 +3137,7 @@
 
 	oscar_user_info_append_status(gc, user_info, /* PurpleBuddy */ NULL, userinfo, /* strip_html_tags */ FALSE);
 
-	if (userinfo->present & AIM_USERINFO_PRESENT_IDLE) {
+	if ((userinfo->present & AIM_USERINFO_PRESENT_IDLE) && userinfo->idletime != 0) {
 		tmp = purple_str_seconds_to_string(userinfo->idletime*60);
 		oscar_user_info_add_pair(user_info, _("Idle"), tmp);
 		g_free(tmp);
@@ -4907,11 +4983,6 @@
 	purple_debug_info("oscar",
 			   "ssi: syncing local list and server list\n");
 
-	if ((timestamp == 0) || (numitems == 0)) {
-		purple_debug_info("oscar", "Got AIM SSI with a 0 timestamp or 0 numitems--not syncing.  This probably means your buddy list is empty.\n");
-		return 1;
-	}
-
 	/* Clean the buddy list */
 	aim_ssi_cleanlist(od);
 
@@ -6414,15 +6485,12 @@
 	if (img == NULL) {
 		aim_ssi_delicon(od);
 	} else {
-		PurpleCipher *cipher;
 		PurpleCipherContext *context;
 		guchar md5[16];
 		gconstpointer data = purple_imgstore_get_data(img);
 		size_t len = purple_imgstore_get_size(img);
 
-
-		cipher = purple_ciphers_find_cipher("md5");
-		context = purple_cipher_context_new(cipher, NULL);
+		context = purple_cipher_context_new_by_name("md5", NULL);
 		purple_cipher_context_append(context, data, len);
 		purple_cipher_context_digest(context, 16, md5, NULL);
 		purple_cipher_context_destroy(context);
--- a/libpurple/protocols/oscar/oscar.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/oscar/oscar.h	Thu Nov 20 21:13:56 2008 +0000
@@ -258,6 +258,15 @@
 	"us", "en", \
 }
 
+#define CLIENTINFO_ICQ6_6_0_6059 { \
+	"ICQ Client", \
+	0x010a, \
+	0x0006, 0x0000, \
+	0x0000, 0x17ab, \
+	0x00007535, \
+	"us", "en", \
+}
+
 #define CLIENTINFO_ICQBASIC_14_3_1068 { \
 	"ICQBasic", \
 	0x010a, \
@@ -302,9 +311,9 @@
 #define CLIENTINFO_PURPLE_ICQ { \
 	"Purple/" VERSION, \
 	0x010a, \
-	0x0014, 0x0034, \
-	0x0000, 0x0bb8, \
-	0x0000043d, \
+	0x0006, 0x0000, \
+	0x0000, 0x17ab, \
+	0x00007535, \
 	"us", "en", \
 }
 
@@ -344,16 +353,16 @@
 	OSCAR_CAPABILITY_TRILLIANCRYPT        = 0x00010000,
 	OSCAR_CAPABILITY_UNICODE              = 0x00020000,
 	OSCAR_CAPABILITY_INTEROPERATE         = 0x00040000,
-	OSCAR_CAPABILITY_ICHAT                = 0x00080000,
+	OSCAR_CAPABILITY_SHORTCAPS            = 0x00080000,
 	OSCAR_CAPABILITY_HIPTOP               = 0x00100000,
 	OSCAR_CAPABILITY_SECUREIM             = 0x00200000,
 	OSCAR_CAPABILITY_SMS                  = 0x00400000,
-	OSCAR_CAPABILITY_GENERICUNKNOWN       = 0x00800000,
-	OSCAR_CAPABILITY_VIDEO                = 0x01000000,
-	OSCAR_CAPABILITY_ICHATAV              = 0x02000000,
-	OSCAR_CAPABILITY_LIVEVIDEO            = 0x04000000,
-	OSCAR_CAPABILITY_CAMERA               = 0x08000000,
-	OSCAR_CAPABILITY_ICHAT_SCREENSHARE	  = 0x10000000,
+	OSCAR_CAPABILITY_VIDEO                = 0x00800000,
+	OSCAR_CAPABILITY_ICHATAV              = 0x01000000,
+	OSCAR_CAPABILITY_LIVEVIDEO            = 0x02000000,
+	OSCAR_CAPABILITY_CAMERA               = 0x04000000,
+	OSCAR_CAPABILITY_ICHAT_SCREENSHARE    = 0x08000000,
+	OSCAR_CAPABILITY_GENERICUNKNOWN       = 0x10000000,
 	OSCAR_CAPABILITY_LAST                 = 0x20000000
 } OscarCapability;
 
@@ -424,6 +433,7 @@
 	GSList *rateclasses; /* Contains nodes of struct rateclass. */
 
 	GQueue *queued_snacs; /**< Contains QueuedSnacs. */
+	GQueue *queued_lowpriority_snacs; /**< Contains QueuedSnacs to send only once queued_snacs is empty */
 	guint queued_timeout;
 
 	void *internal; /* internal conn-specific libfaim data */
@@ -534,6 +544,10 @@
 
 	/** A linked list containing PeerConnections. */
 	GSList *peer_connections;
+
+	/** Queue of ICQ Status Notes to request. */
+	GSList *statusnotes_queue;
+	guint statusnotes_queue_timer;
 };
 
 /* Valid for calling aim_icq_setstatus() and for aim_userinfo_t->icqinfo.status */
@@ -616,6 +630,7 @@
 void flap_connection_send_version(OscarData *od, FlapConnection *conn);
 void flap_connection_send_version_with_cookie(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy);
 void flap_connection_send_snac(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, guint16 flags, aim_snacid_t snacid, ByteStream *data);
+void flap_connection_send_snac_with_priority(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, guint16 flags, aim_snacid_t snacid, ByteStream *data, gboolean high_priority);
 void flap_connection_send_keepalive(OscarData *od, FlapConnection *conn);
 FlapFrame *flap_frame_new(OscarData *od, guint16 channel, int datalen);
 
--- a/libpurple/protocols/oscar/oscar_data.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/oscar/oscar_data.c	Thu Nov 20 21:13:56 2008 +0000
@@ -88,10 +88,17 @@
 
 	while (od->requesticon)
 	{
-		gchar *sn = od->requesticon->data;
-		od->requesticon = g_slist_remove(od->requesticon, sn);
-		g_free(sn);
+		g_free(od->requesticon->data);
+		od->requesticon = g_slist_delete_link(od->requesticon, od->requesticon);
 	}
+	while (od->statusnotes_queue)
+	{
+		g_free(od->statusnotes_queue->data);
+		od->statusnotes_queue = g_slist_delete_link(od->statusnotes_queue,
+				od->statusnotes_queue);
+	}
+	if (od->statusnotes_queue_timer > 0)
+		purple_timeout_remove(od->statusnotes_queue_timer);
 	g_free(od->email);
 	g_free(od->newp);
 	g_free(od->oldp);
--- a/libpurple/protocols/qq/AUTHORS	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/AUTHORS	Thu Nov 20 21:13:56 2008 +0000
@@ -9,11 +9,27 @@
 rakescar    : provided filter for HTML tag
 yyw         : improved performance on PPC linux
 lvxiang     : provided ip to location original code
-csyfek      : faces
 markhuetsch : OpenQ merge into libpurple, maintainer 2006-2007
+ccpaging    : maintainer since 2007
+icesky      : maintainer since 2007
+csyfek      : faces, maintainer since 2007
+
+Lovely Patch Writers
+=====
+gnap        : message displaying, documentation
+manphiz     : qun processing
+moo         : qun processing
+Coly Li     : qun processing
 
 Acknowledgement
 =====
 Shufeng Tan : http://sf.net/projects/perl-oicq
 Jeff Ye     : http://www.sinomac.com
 Hu Zheng    : http://forlinux.yeah.net
+yunfan      : http://www.myswear.net
+khc@pidgin.im
+qulogic@pidgin.im
+rlaager@pidgin.im
+OpenQ Team
+LumaQQ Team
+OpenQ Google Group
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/qq/ChangeLog	Thu Nov 20 21:13:56 2008 +0000
@@ -0,0 +1,125 @@
+2008.08.03 - csyfek <csyfek(at)gmail.com>
+	* Commit lost files to Pidgin
+
+2008.08.02 - csyfek <csyfek(at)gmail.com>
+	* Commit to Pidgin
+	* Tickets:
+		Fixes #1861
+		Fixes #1902
+		References #5112
+
+2008.08.02 - ccpaging <ecc_hy(at)hotmail.com>
+	* Store all keys and md5 values of qq_data in char[QQ_KEY_LENGTH]
+	* Use random value in inikey
+	* TEA header padding in crypt.c
+	* Rewrite login part of qq_process
+
+2008.07.31 - ccpaging <ecc_hy(at)hotmail.com>
+	* Fixed: send reply when get duplicate server command. The server may not get our reply before.
+	* Tag custom picture as text "(Broken)"
+
+2008.07.30 - ccpaging <ecc_hy(at)hotmail.com>, csyfek <csyfek(at)gmail.com>
+	* Change some debug message
+	* Modify buddy status flag according to eva for QQ2006
+	* Modify buddy status parse and correspond to eva2
+	* Add getIP/putIP functions to packet_parse.c, and replace some gen_ip_str
+	* Replace guint32 *ip with struct in_addr, and reduce g_new/g_free operation
+	* Source file changed:
+   		Merge buddy_status into buddy_list
+   		Change login_logout to qq_base
+   		Merge keep_alive into qq_base
+   		New qq_process extract from qq_network
+	* Fixed: Byte alignment bug in crypt.c, tested in ARM PDA
+	* Fixed: group chat message may get in before getting group info, and so group info is empty
+	* Add qq_send_cmd_group_get_group_info when joined a group chat in group_im.c
+	* Add some new group command identify according eva but further program
+	* Add some new QQ client version identify
+	* Fixed: Identify buddy's client version by IM packet, and not by status
+	* Add some new info in buddy's tooltip text
+	* Add video falg to buddy's emblem. But those flag in buddy status may not prasing correctly
+	* Use new timeout function to handle send keep_alive, resend packet, update buddy status
+	* Add new advanced options:
+		The end user may change interval of keep_alive, resend packet, update buddy status to feed their need.
+		For example, saving network flow when use mobile phone.
+		Keep alive packet must be sent in 60-120 seconds whatever client rcved data of not.
+		The intervals of keep alive and update status should be multiple of resend's interval,
+		Since we use counter not time() in a single timeout function for efficiency.
+	* Rewrite qq_trans.c, and use one g_list to manage:
+		Store server packet before login, and prase all of them when get login
+		Store client send packet for resend scanning, confirm server reply, filter duplicate server reply
+		Store server packet for filter out duplicate
+	* Add QQ_MSG_SYS_NOTICE = 0x06 in sys_msg.c
+	* Rewrite qq_proc_cmd_reply and qq_proc_cmd_server:
+		In QQ protocol, one packet reply may need a new packet send later.
+		We may call it packet trigger. The triggers always is hided in every qq_process_reply.
+		Now we try to extract those triggers and put into a single function, 
+		and then every trigger should be obviously and easy to manage.
+	
+2008.07.12 - ccpaging <ecc_hy(at)hotmail.com>
+	* Fixed: Always lost connection. Now send keep alive packet in every 30 seconds
+	* Minor fix for debug information
+	* Filter \r\n and replace with SPCAE in group notive
+	* Fixed a memory leak
+	* Tickets:
+	* Fixes #4024.
+
+2008.06.29 - csyfek <csyfek(at)gmail.com>
+	* Minor bug fix
+	* Add some doxygen syntax for preparing development documentation
+	* References #6199
+
+2008.06.28 - ccpaging <ecc_hy(at)hotmail.com>, moo <phpxcache(at)gmail.com>
+	* Patches from moo<phpxcache@gmail.com> and ccpaging<ccpaging@foxmail.com>.
+	* Tickets:
+	* Fixes #4956.
+	* Fixes #2998.
+
+2008.06.07 - ccpaging <ecc_hy(at)hotmail.com>, csyfek <csyfek(at)gmail.com>
+	* Clean code and apply patches from QuLogic
+
+2008.05.19 - ccpaging <ecc_hy(at)hotmail.com>, csyfek <csyfek(at)gmail.com>
+	* Reconnect server 5 time in 5000 ms, when connect failed
+	* Rename sendqueue.c/sendqueue.h to qq_trans.c/qq_trans.h
+	* Rewrite packet_process
+	* Rewrite qq_send_cmd
+	* Create server list, try to connect every server when failed
+
+2008.05.14 - ccpaging <ecc_hy(at)hotmail.com>
+	* Move function for before login packets storing to sendqueue
+	* Use transaction data structure to store before login packets
+	* Rewrite tcp_pending and packet_process in qq_network.c
+
+2008.05.09 - ccpaging <ecc_hy(at)hotmail.com>
+	* Remove function _create_packet_head_seq in qq_network.c
+	* Create new function encap in qq_netowork.c
+	* Clean code of qq_send_packet_request_login_token and qq_send_packet_login in login_out.c
+
+2008.05.09 - ccpaging <ecc_hy(at)hotmail.com>
+	* Clean code of packet_parse.c, enable PARSER_DEBUG
+	* Rewrite send_queue
+
+2008.05.08 - ccpaging <ecc_hy(at)hotmail.com>
+	* Rewrite qq_network
+	* Add srv resolve function when qq_login
+	* Merge function _qq_common_clean in qq_proxy.c to qq_disconnect
+	* Move orignal qq_disconnect to qq_close
+	* qq_data alloc in qq_open and release in qq_close
+	* Network connect of QQ is created in qq_connect, and release in qq_disconnect
+
+2008.05.05 - ccpaging <ecc_hy(at)hotmail.com>
+	* Merge function _qq_common_clean in qq_proxy.c to qq_disconnect
+	* Move orignal qq_disconnect to qq_close
+	* qq_data alloc in qq_open and release in qq_close
+	* Network connect of QQ is created in qq_connect, and release in qq_disconnect
+
+2008.05.05 - ccpaging <ecc_hy(at)hotmail.com>
+	* Add qq_hex_dump function
+
+2008.04.25 - ccpaging <ecc_hy(at)hotmail.com>, csyfek <csyfek(at)gmail.com>
+	* Rewrite read_packet and create_packet functions, use qq_put and qq_get functions instead
+	* New logic in accord with protocol models to handle packets, some related functions rewritten
+
+2008.03.24 - ccpaging <ecc_hy(at)hotmail.com>
+	* Remove qq_crypt function in crypt.c, use qq_crypt and qq_decrypt directly
+
+** since pidgin-2.4.0 ***
--- a/libpurple/protocols/qq/Makefile.am	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/Makefile.am	Thu Nov 20 21:13:56 2008 +0000
@@ -10,8 +10,6 @@
 	buddy_list.h \
 	buddy_opt.c \
 	buddy_opt.h \
-	buddy_status.c \
-	buddy_status.h \
 	char_conv.c \
 	char_conv.h \
 	crypt.c \
@@ -44,28 +42,22 @@
 	header_info.h \
 	im.c \
 	im.h \
-	keep_alive.c \
-	keep_alive.h \
-	login_logout.c \
-	login_logout.h \
+	qq_process.c \
+	qq_process.h \
+	qq_base.c \
+	qq_base.h \
 	packet_parse.c \
 	packet_parse.h \
 	qq.c \
 	qq.h \
-	qq_proxy.c \
-	qq_proxy.h \
-	recv_core.c \
-	recv_core.h \
-	send_core.c \
-	send_core.h \
+	qq_network.c \
+	qq_network.h \
 	send_file.c \
 	send_file.h \
-	sendqueue.c \
-	sendqueue.h \
+	qq_trans.c \
+	qq_trans.h \
 	sys_msg.c \
 	sys_msg.h \
-	udp_proxy_s5.c \
-	udp_proxy_s5.h \
 	utils.c \
 	utils.h
 
--- a/libpurple/protocols/qq/Makefile.mingw	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/Makefile.mingw	Thu Nov 20 21:13:56 2008 +0000
@@ -59,17 +59,14 @@
 	group_search.c \
 	header_info.c \
 	im.c \
-	keep_alive.c \
-	login_logout.c \
+	qq_process.c \
+	qq_base.c \
 	packet_parse.c \
 	qq.c \
-	qq_proxy.c \
-	recv_core.c \
-	send_core.c \
+	qq_network.c \
 	send_file.c \
-	sendqueue.c \
+	qq_trans.c \
 	sys_msg.c \
-	udp_proxy_s5.c \
 	utils.c
 
 OBJECTS = $(C_SRC:%.c=%.o)
--- a/libpurple/protocols/qq/buddy_info.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/buddy_info.c	Thu Nov 20 21:13:56 2008 +0000
@@ -29,12 +29,13 @@
 
 #include "utils.h"
 #include "packet_parse.h"
+#include "buddy_list.h"
 #include "buddy_info.h"
 #include "char_conv.h"
 #include "crypt.h"
 #include "header_info.h"
-#include "keep_alive.h"
-#include "send_core.h"
+#include "qq_base.h"
+#include "qq_network.h"
 
 #define QQ_PRIMARY_INFORMATION _("Primary Information")
 #define QQ_ADDITIONAL_INFORMATION _("Additional Information")
@@ -85,6 +86,7 @@
 };
 
 #define QQ_CONTACT_FIELDS                               37
+#define QQ_FACES	    100
 
 /* 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 */
@@ -94,6 +96,46 @@
 	gboolean modify_info;
 } qq_info_query;
 
+typedef struct _contact_info {
+	gchar *uid;
+	gchar *nick;
+	gchar *country;
+	gchar *province;
+	gchar *zipcode;
+	gchar *address;
+	gchar *tel;
+	gchar *age;
+	gchar *gender;
+	gchar *name;
+	gchar *email;
+	gchar *pager_sn;
+	gchar *pager_num;
+	gchar *pager_sp;
+	gchar *pager_base_num;
+	gchar *pager_type;
+	gchar *occupation;
+	gchar *homepage;
+	gchar *auth_type;
+	gchar *unknown1;
+	gchar *unknown2;
+	gchar *face;
+	gchar *hp_num;
+	gchar *hp_type;
+	gchar *intro;
+	gchar *city;
+	gchar *unknown3;
+	gchar *unknown4;
+	gchar *unknown5;
+	gchar *is_open_hp;
+	gchar *is_open_contact;
+	gchar *college;
+	gchar *horoscope;
+	gchar *zodiac;
+	gchar *blood;
+	gchar *qq_show;
+	gchar *unknown6;        /* always 0x2D */
+} contact_info;
+
 /* We get an info packet on ourselves before we modify our information.
  * Even though not all of the information is modifiable, it still
  * all needs to be there when we send out the modify info packet */
@@ -137,7 +179,7 @@
 			} else {
 				return NULL;
 			}
-		/* else ASCIIized index */
+			/* else ASCIIized index */
 		} else {
 			if (strcmp(choice[index], "-") != 0)
 				return g_strdup(choice[index]);
@@ -161,15 +203,15 @@
 	if (value != NULL) {
 		purple_notify_user_info_add_pair(user_info, title, value);
 		g_free(value);
-		
+
 		return TRUE;
 	}
-	
+
 	return FALSE;
 }
 
 static PurpleNotifyUserInfo *
-info_to_notify_user_info(const contact_info *info)
+	info_to_notify_user_info(const contact_info *info)
 {
 	PurpleNotifyUserInfo *user_info = purple_notify_user_info_new();
 	const gchar *intro;
@@ -209,25 +251,25 @@
 
 	/* for debugging */
 	/*
-	g_string_append_printf(info_text, "<br /><br /><b>%s</b><br />", "Miscellaneous");
-	append_field_value(info_text, info->pager_sn, "pager_sn", NULL, 0);
-	append_field_value(info_text, info->pager_num, "pager_num", NULL, 0);
-	append_field_value(info_text, info->pager_sp, "pager_sp", NULL, 0);
-	append_field_value(info_text, info->pager_base_num, "pager_base_num", NULL, 0);
-	append_field_value(info_text, info->pager_type, "pager_type", NULL, 0);
-	append_field_value(info_text, info->auth_type, "auth_type", NULL, 0);
-	append_field_value(info_text, info->unknown1, "unknown1", NULL, 0);
-	append_field_value(info_text, info->unknown2, "unknown2", NULL, 0);
-	append_field_value(info_text, info->face, "face", NULL, 0);
-	append_field_value(info_text, info->hp_type, "hp_type", NULL, 0);
-	append_field_value(info_text, info->unknown3, "unknown3", NULL, 0);
-	append_field_value(info_text, info->unknown4, "unknown4", NULL, 0);
-	append_field_value(info_text, info->unknown5, "unknown5", NULL, 0);
-	append_field_value(info_text, info->is_open_hp, "is_open_hp", NULL, 0);
-	append_field_value(info_text, info->is_open_contact, "is_open_contact", NULL, 0);
-	append_field_value(info_text, info->qq_show, "qq_show", NULL, 0);
-	append_field_value(info_text, info->unknown6, "unknown6", NULL, 0);
-	*/
+	   g_string_append_printf(info_text, "<br /><br /><b>%s</b><br />", "Miscellaneous");
+	   append_field_value(info_text, info->pager_sn, "pager_sn", NULL, 0);
+	   append_field_value(info_text, info->pager_num, "pager_num", NULL, 0);
+	   append_field_value(info_text, info->pager_sp, "pager_sp", NULL, 0);
+	   append_field_value(info_text, info->pager_base_num, "pager_base_num", NULL, 0);
+	   append_field_value(info_text, info->pager_type, "pager_type", NULL, 0);
+	   append_field_value(info_text, info->auth_type, "auth_type", NULL, 0);
+	   append_field_value(info_text, info->unknown1, "unknown1", NULL, 0);
+	   append_field_value(info_text, info->unknown2, "unknown2", NULL, 0);
+	   append_field_value(info_text, info->face, "face", NULL, 0);
+	   append_field_value(info_text, info->hp_type, "hp_type", NULL, 0);
+	   append_field_value(info_text, info->unknown3, "unknown3", NULL, 0);
+	   append_field_value(info_text, info->unknown4, "unknown4", NULL, 0);
+	   append_field_value(info_text, info->unknown5, "unknown5", NULL, 0);
+	   append_field_value(info_text, info->is_open_hp, "is_open_hp", NULL, 0);
+	   append_field_value(info_text, info->is_open_contact, "is_open_contact", NULL, 0);
+	   append_field_value(info_text, info->qq_show, "qq_show", NULL, 0);
+	   append_field_value(info_text, info->unknown6, "unknown6", NULL, 0);
+	   */
 
 	return user_info;
 }
@@ -243,7 +285,7 @@
 
 	qd = (qq_data *) gc->proto_data;
 	g_snprintf(uid_str, sizeof(uid_str), "%d", uid);
-	qq_send_cmd(gc, QQ_CMD_GET_USER_INFO, TRUE, 0, TRUE, (guint8 *) uid_str, strlen(uid_str));
+	qq_send_cmd(qd, QQ_CMD_GET_USER_INFO, (guint8 *) uid_str, strlen(uid_str));
 
 	query = g_new0(qq_info_query, 1);
 	query->uid = uid;
@@ -271,27 +313,141 @@
 }
 
 /* send packet to modify personal information */
-static void qq_send_packet_modify_info(PurpleConnection *gc, gchar **segments)
+static void qq_send_packet_modify_info(PurpleConnection *gc, contact_info *info)
 {
-	gint i;
-	guint8 *raw_data, *cursor, bar;
+	qq_data *qd = (qq_data *) gc->proto_data;
+	gint bytes = 0;
+	guint8 raw_data[MAX_PACKET_SIZE - 128] = {0};
+	guint8 bar;
 
-	g_return_if_fail(segments != NULL);
+	g_return_if_fail(info != NULL);
 
 	bar = 0x1f;
-	raw_data = g_newa(guint8, MAX_PACKET_SIZE - 128);
-	cursor = raw_data;
 
-	create_packet_b(raw_data, &cursor, bar);
+	bytes += qq_put8(raw_data + bytes, bar);
 
 	/* 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, (guint8 *) segments[i], strlen(segments[i]));
-	}
-	create_packet_b(raw_data, &cursor, bar);
+	/*
+	   for (i = 1; i < QQ_CONTACT_FIELDS; i++) {
+	   create_packet_b(raw_data, &cursor, bar);
+	   create_packet_data(raw_data, &cursor, (guint8 *) segments[i], strlen(segments[i]));
+	   }
+	   */
+	/* uid */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->uid, strlen(info->uid));
+	/* nick */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->nick, strlen(info->nick));
+	/* country */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->country, strlen(info->country));
+	/* province */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->province, strlen(info->province));
+	/* zipcode */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->zipcode, strlen(info->zipcode));
+	/* address */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->address, strlen(info->address));
+	/* tel */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->tel, strlen(info->tel));
+	/* age */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->age, strlen(info->age));
+	/* gender */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->gender, strlen(info->gender));
+	/* name */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->name, strlen(info->name));
+	/* email */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->email, strlen(info->email));
+	/* pager_sn */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->pager_sn, strlen(info->pager_sn));
+	/* pager_num */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->pager_num, strlen(info->pager_num));
+	/* pager_sp */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->pager_sp, strlen(info->pager_sp));
+	/* pager_base_num */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->pager_base_num, strlen(info->pager_base_num));
+	/* pager_type */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->pager_type, strlen(info->pager_type));
+	/* occupation */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->occupation, strlen(info->occupation));
+	/* homepage */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->homepage, strlen(info->homepage));
+	/* auth_type */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->auth_type, strlen(info->auth_type));
+	/* unknown1 */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->unknown1, strlen(info->unknown1));
+	/* unknown2 */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->unknown2, strlen(info->unknown2));
+	/* face */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->face, strlen(info->face));
+	/* hp_num */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->hp_num, strlen(info->hp_num));
+	/* hp_type */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->hp_type, strlen(info->hp_type));
+	/* intro */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->intro, strlen(info->intro));
+	/* city */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->city, strlen(info->city));
+	/* unknown3 */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->unknown3, strlen(info->unknown3));
+	/* unknown4 */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->unknown4, strlen(info->unknown4));
+	/* unknown5 */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->unknown5, strlen(info->unknown5));
+	/* is_open_hp */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->is_open_hp, strlen(info->is_open_hp));
+	/* is_open_contact */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->is_open_contact, strlen(info->is_open_contact));
+	/* college */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->college, strlen(info->college));
+	/* horoscope */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->horoscope, strlen(info->horoscope));
+	/* zodiac */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->zodiac, strlen(info->zodiac));
+	/* blood */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->blood, strlen(info->blood));
+	/* qq_show */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->qq_show, strlen(info->qq_show));
+	/* unknown6 */
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *)info->unknown6, strlen(info->unknown6));
 
-	qq_send_cmd(gc, QQ_CMD_UPDATE_INFO, TRUE, 0, TRUE, raw_data, cursor - raw_data);
+	bytes += qq_put8(raw_data + bytes, bar);
+
+	qq_send_cmd(qd, QQ_CMD_UPDATE_INFO, raw_data, bytes);
 
 }
 
@@ -407,8 +563,11 @@
 		groups = groups->next;
 	}
 
-	/* This casting looks like a horrible idea to me -DAA */
-	qq_send_packet_modify_info(gc, (gchar **) info);
+	/* This casting looks like a horrible idea to me -DAA
+	 * yes, rewritten -s3e
+	 * qq_send_packet_modify_info(gc, (gchar **) info);
+	 */
+	qq_send_packet_modify_info(gc, info);
 
 	g_strfreev((gchar **) mid->info);
 	g_free(mid);
@@ -520,11 +679,11 @@
 		mid->info->unknown6 = g_strdup(info->unknown6);
 
 		purple_request_fields(gc, _("Modify my information"),
-			_("Modify my information"), NULL, fields,
-			_("Update my information"), G_CALLBACK(modify_info_ok_cb),
-			_("Cancel"), G_CALLBACK(modify_info_cancel_cb),
-			purple_connection_get_account(gc), NULL, NULL,
-			mid);
+				_("Modify my information"), NULL, fields,
+				_("Update my information"), G_CALLBACK(modify_info_ok_cb),
+				_("Cancel"), G_CALLBACK(modify_info_cancel_cb),
+				purple_connection_get_account(gc), NULL, NULL,
+				mid);
 	}
 }
 
@@ -578,10 +737,9 @@
 	gchar *data;
 	gsize len;
 
-	if (!g_file_get_contents(iconfile, &data, &len, NULL))
+	if (!g_file_get_contents(iconfile, &data, &len, NULL)) {
 		g_return_if_reached();
-	else
-	{
+	} else {
 		purple_buddy_icons_set_for_user(account, who, data, len, icon_num);
 	}
 }
@@ -608,10 +766,10 @@
 
 	/* make sure we're using an appropriate icon */
 	if (!(g_ascii_strncasecmp(icon_path, buddy_icon_dir, dir_len) == 0
-		&& icon_path[dir_len] == G_DIR_SEPARATOR
-			&& g_ascii_strncasecmp(icon_path + dir_len + 1, QQ_ICON_PREFIX, prefix_len) == 0
-			&& g_ascii_strncasecmp(icon_path + dir_len + 1 + prefix_len + icon_len, QQ_ICON_SUFFIX, suffix_len) == 0
-			&& icon_len <= 3)) {
+				&& icon_path[dir_len] == G_DIR_SEPARATOR
+				&& g_ascii_strncasecmp(icon_path + dir_len + 1, QQ_ICON_PREFIX, prefix_len) == 0
+				&& g_ascii_strncasecmp(icon_path + dir_len + 1 + prefix_len + icon_len, QQ_ICON_SUFFIX, suffix_len) == 0
+				&& icon_len <= 3)) {
 		if (icon_global)
 			purple_debug(PURPLE_DEBUG_ERROR, "QQ", "%s\n", errmsg);
 		else
@@ -650,13 +808,13 @@
 		old_icon_num = purple_buddy_icons_get_checksum_for_user(buddy);
 
 	if (old_icon_num == NULL ||
-	    strcmp(icon_num_str, old_icon_num))
+			strcmp(icon_num_str, old_icon_num))
 	{
 		gchar *icon_path;
 
 		icon_path = g_strconcat(qq_buddy_icon_dir(), G_DIR_SEPARATOR_S,
-		                        QQ_ICON_PREFIX, icon_num_str,
-		                        QQ_ICON_SUFFIX, NULL);
+				QQ_ICON_PREFIX, icon_num_str,
+				QQ_ICON_SUFFIX, NULL);
 
 		qq_set_buddy_icon_for_user(account, name, icon_num_str, icon_path);
 		g_free(icon_path);
@@ -665,12 +823,13 @@
 }
 
 /* after getting info or modify myself, refresh the buddy list accordingly */
-void qq_refresh_buddy_and_myself(contact_info *info, PurpleConnection *gc)
+static void qq_refresh_buddy_and_myself(contact_info *info, PurpleConnection *gc)
 {
 	PurpleBuddy *b;
 	qq_data *qd;
 	qq_buddy *q_bud;
-	gchar *alias_utf8, *purple_name;
+	gchar *alias_utf8;
+	gchar *purple_name;
 	PurpleAccount *account = purple_connection_get_account(gc);
 
 	qd = (qq_data *) gc->proto_data;
@@ -728,7 +887,7 @@
 			qd->modifying_face = FALSE;
 			g_free(info->face);
 			info->face = icon;
-			qq_send_packet_modify_info(gc, segments);
+			qq_send_packet_modify_info(gc, (contact_info *)segments);
 		}
 
 		qq_refresh_buddy_and_myself(info, gc);
@@ -777,41 +936,43 @@
 
 void qq_send_packet_get_level(PurpleConnection *gc, guint32 uid)
 {
-	guint8 buf[5];
-	guint32 tmp = g_htonl(uid);
-	buf[0] = 0;
-	memcpy(buf+1, &tmp, 4);
-	qq_send_cmd(gc, QQ_CMD_GET_LEVEL, TRUE, 0, TRUE, buf, 5);
+	qq_data *qd = (qq_data *) gc->proto_data;
+	guint8 buf[16] = {0};
+	gint bytes = 0;
+
+	bytes += qq_put8(buf + bytes, 0x00);
+	bytes += qq_put32(buf + bytes, uid);
+
+	qd = (qq_data *) gc->proto_data;
+	qq_send_cmd(qd, QQ_CMD_GET_LEVEL, buf, bytes);
 }
 
 void qq_send_packet_get_buddies_levels(PurpleConnection *gc)
 {
-	guint8 *buf, *tmp;
+	guint8 *buf;
 	guint16 size;
 	qq_buddy *q_bud;
 	qq_data *qd = (qq_data *) gc->proto_data;
 	GList *node = qd->buddies;
-
-	if (qd->buddies) {
-		/* server only sends back levels for online buddies, no point
- 	 	* in asking for anyone else */
-		size = 4*g_list_length(qd->buddies) + 1;
-		buf = g_new0(guint8, size);
-		tmp = buf + 1;
+	gint bytes = 0;
 
-		while (node != NULL) {
-			guint32 tmp4;
-			q_bud = (qq_buddy *) node->data;
-			if (q_bud != NULL) {
-				tmp4 = g_htonl(q_bud->uid);
-				memcpy(tmp, &tmp4, 4);
-				tmp += 4;
-			}
-			node = node->next;
+	if ( qd->buddies == NULL) {
+		return;
+	}
+	/* server only sends back levels for online buddies, no point
+	 * in asking for anyone else */
+	size = 4 * g_list_length(qd->buddies) + 1;
+	buf = g_newa(guint8, size);
+	bytes += qq_put8(buf + bytes, 0x00);
+	
+	while (NULL != node) {
+		q_bud = (qq_buddy *) node->data;
+		if (NULL != q_bud) {
+			bytes += qq_put32(buf + bytes, q_bud->uid);
 		}
-		qq_send_cmd(gc, QQ_CMD_GET_LEVEL, TRUE, 0, TRUE, buf, size);
-		g_free(buf);
+		node = node->next;
 	}
+	qq_send_cmd(qd, QQ_CMD_GET_LEVEL, buf, size);
 }
 
 void qq_process_get_level_reply(guint8 *buf, gint buf_len, PurpleConnection *gc)
@@ -822,10 +983,11 @@
 	PurpleBuddy *b;
 	qq_buddy *q_bud;
 	gint decr_len, i;
-	guint8 *decr_buf, *tmp;
+	guint8 *decr_buf;
 	PurpleAccount *account = purple_connection_get_account(gc);
 	qq_data *qd = (qq_data *) gc->proto_data;
-	
+	gint bytes = 0;
+
 	decr_len = buf_len;
 	decr_buf = g_new0(guint8, buf_len);
 	if (!qq_decrypt(buf, buf_len, qd->session_key, decr_buf, &decr_len)) {
@@ -835,28 +997,23 @@
 	decr_len--; 
 	if (decr_len % 12 != 0) {
 		purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
-			"Get levels list of abnormal length. Truncating last %d bytes.\n", decr_len % 12);
+				"Get levels list of abnormal length. Truncating last %d bytes.\n", decr_len % 12);
 		decr_len -= (decr_len % 12);
 	}
-		
-	tmp = decr_buf + 1;
+
+	bytes += 1;
 	/* this byte seems random */
 	/*
-	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Byte one of get_level packet: %d\n", buf[0]);
-	*/
+	   purple_debug(PURPLE_DEBUG_INFO, "QQ", "Byte one of get_level packet: %d\n", buf[0]);
+	   */
 	for (i = 0; i < decr_len; i += 12) {
-		uid = g_ntohl(*(guint32 *) tmp);
-		tmp += 4;
-		onlineTime = g_ntohl(*(guint32 *) tmp);
-		tmp += 4;
-		level = g_ntohs(*(guint16 *) tmp);
-		tmp += 2;
-		timeRemainder = g_ntohs(*(guint16 *) tmp);
-		tmp += 2;
-		/*
-		purple_debug(PURPLE_DEBUG_INFO, "QQ", "Level packet entry:\nuid: %d\nonlineTime: %d\nlevel: %d\ntimeRemainder: %d\n", 
+		bytes += qq_get32(&uid, decr_buf + bytes);
+		bytes += qq_get32(&onlineTime, decr_buf + bytes);
+		bytes += qq_get16(&level, decr_buf + bytes);
+		bytes += qq_get16(&timeRemainder, decr_buf + bytes);
+		purple_debug(PURPLE_DEBUG_INFO, "QQ_LEVEL", 
+				"%d, tmOnline: %d, level: %d, tmRemainder: %d\n", 
 				uid, onlineTime, level, timeRemainder);
-		*/
 		purple_name = uid_to_purple_name(uid);
 		b = purple_find_buddy(account, purple_name);
 		q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
@@ -872,9 +1029,10 @@
 			}
 		} else {
 			purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
-				"Got an online buddy %d, but not in my buddy list\n", uid);
+					"Got an online buddy %d, but not in my buddy list\n", uid);
 		}
 		g_free(purple_name);
 	}
 	g_free(decr_buf);
 }
+
--- a/libpurple/protocols/qq/buddy_info.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/buddy_info.h	Thu Nov 20 21:13:56 2008 +0000
@@ -31,11 +31,34 @@
 #include "buddy_opt.h"
 #include "qq.h"
 
-#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
+/* use is openq2005
+ * ext_flag: (0-7)
+ *        bit1 => qq space
+ * comm_flag: (0-7)
+ *        bit1 => member
+ *        bit4 => TCP mode
+ *        bit5 => open mobile QQ
+ *        bit6 => bind to mobile
+ *        bit7 => whether having a video
+#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
+ */
+/* status in eva for qq2006
+#define QQ_FRIEND_FLAG_QQ_MEMBER  0x01
+#define QQ_FRIEND_FLAG_MOBILE           0x10
+#define QQ_FRIEND_FLAG_BIND_MOBILE  0x20
+*/
+#define QQ_COMM_FLAG_QQ_MEMBER		0x02
+#define QQ_COMM_FLAG_QQ_VIP			0x04
+#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_EXT_FLAG_SPACE				0x02
 
 #define QQ_BUDDY_GENDER_GG          0x00
 #define QQ_BUDDY_GENDER_MM          0x01
@@ -44,47 +67,6 @@
 #define QQ_ICON_PREFIX "qq_"
 #define QQ_ICON_SUFFIX ".png"
 
-typedef struct _contact_info {
-        gchar *uid;
-        gchar *nick;
-        gchar *country;
-        gchar *province;
-        gchar *zipcode;
-        gchar *address;
-        gchar *tel;
-        gchar *age;
-        gchar *gender;
-        gchar *name;
-        gchar *email;
-        gchar *pager_sn;
-        gchar *pager_num;
-        gchar *pager_sp;
-        gchar *pager_base_num;
-        gchar *pager_type;
-        gchar *occupation;
-        gchar *homepage;
-        gchar *auth_type;
-        gchar *unknown1;
-        gchar *unknown2;
-        gchar *face;
-        gchar *hp_num;
-        gchar *hp_type;
-        gchar *intro;
-        gchar *city;
-        gchar *unknown3;
-        gchar *unknown4;
-        gchar *unknown5;
-        gchar *is_open_hp;
-        gchar *is_open_contact;
-        gchar *college;
-        gchar *horoscope;
-        gchar *zodiac;
-        gchar *blood;
-        gchar *qq_show;
-        gchar *unknown6;        /* always 0x2D */
-} contact_info;
-
-void qq_refresh_buddy_and_myself(contact_info *info, PurpleConnection *gc);
 void qq_send_packet_get_info(PurpleConnection *gc, guint32 uid, gboolean show_window);
 void qq_set_my_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img);
 void qq_set_buddy_icon_for_user(PurpleAccount *account, const gchar *who, const gchar *icon_num, const gchar *iconfile);
@@ -95,5 +77,4 @@
 void qq_send_packet_get_level(PurpleConnection *gc, guint32 uid);
 void qq_send_packet_get_buddies_levels(PurpleConnection *gc);
 void qq_process_get_level_reply(guint8 *buf, gint buf_len, PurpleConnection *gc);
-
 #endif
--- a/libpurple/protocols/qq/buddy_list.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/buddy_list.c	Thu Nov 20 21:13:56 2008 +0000
@@ -32,57 +32,53 @@
 #include "packet_parse.h"
 #include "buddy_info.h"
 #include "buddy_list.h"
-#include "buddy_status.h"
 #include "buddy_opt.h"
 #include "char_conv.h"
 #include "crypt.h"
 #include "header_info.h"
-#include "keep_alive.h"
-#include "send_core.h"
+#include "qq_base.h"
 #include "group.h"
 #include "group_find.h"
 #include "group_internal.h"
 #include "group_info.h"
 
-#include "qq_proxy.h"
+#include "qq_network.h"
 
 #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;
+typedef struct _qq_buddy_online {
+	qq_buddy_status bs;
 	guint16 unknown1;
-	guint8 flag1;
+	guint8 ext_flag;
 	guint8 comm_flag;
 	guint16 unknown2;
 	guint8 ending;		/* 0x00 */
-} qq_friends_online_entry;
+} qq_buddy_online;
 
 /* get a list of online_buddies */
 void qq_send_packet_get_buddies_online(PurpleConnection *gc, guint8 position)
 {
 	qq_data *qd;
-	guint8 *raw_data, *cursor;
+	guint8 *raw_data;
+	gint bytes = 0;
 
 	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);
+	bytes += qq_put8(raw_data + bytes, QQ_GET_ONLINE_BUDDY_02);
 	/* 001-001 seems it supports 255 online buddies at most */
-	create_packet_b(raw_data, &cursor, position);
+	bytes += qq_put8(raw_data + bytes, position);
 	/* 002-002 */
-	create_packet_b(raw_data, &cursor, 0x00);
+	bytes += qq_put8(raw_data + bytes, 0x00);
 	/* 003-004 */
-	create_packet_w(raw_data, &cursor, 0x0000);
+	bytes += qq_put16(raw_data + bytes, 0x0000);
 
-	qq_send_cmd(gc, QQ_CMD_GET_FRIENDS_ONLINE, TRUE, 0, TRUE, raw_data, 5);
+	qq_send_cmd(qd, QQ_CMD_GET_BUDDIES_ONLINE, raw_data, 5);
 	qd->last_get_online = time(NULL);
 }
 
@@ -90,326 +86,605 @@
  * server may return a position tag if list is too long for one packet */
 void qq_send_packet_get_buddies_list(PurpleConnection *gc, guint16 position)
 {
-	guint8 *raw_data, *cursor;
-	gint data_len;
+	qq_data *qd = (qq_data *) gc->proto_data;
+	guint8 raw_data[16] = {0};
+	gint bytes = 0;
 
-	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);
+	bytes += qq_put16(raw_data + bytes, 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, found the 00,00,00 starts to work as well */
-	create_packet_b(raw_data, &cursor, 0x00);
+	bytes += qq_put8(raw_data + bytes, 0x00);
 
-	qq_send_cmd(gc, QQ_CMD_GET_FRIENDS_LIST, TRUE, 0, TRUE, raw_data, data_len);
+	qq_send_cmd(qd, QQ_CMD_GET_BUDDIES_LIST, raw_data, bytes);
 }
 
 /* get all list, buddies & Quns with groupsid support */
 void qq_send_packet_get_all_list_with_group(PurpleConnection *gc, guint32 position)
 {
-	guint8 *raw_data, *cursor;
-	gint data_len;
+	qq_data *qd = (qq_data *) gc->proto_data;
+	guint8 raw_data[16] = {0};
+	gint bytes = 0;
 
-	data_len = 10;
-	raw_data = g_newa(guint8, data_len);
-	cursor = raw_data;
 	/* 0x01 download, 0x02, upload */
-	create_packet_b(raw_data, &cursor, 0x01);
+	bytes += qq_put8(raw_data + bytes, 0x01);
 	/* unknown 0x02 */
-	create_packet_b(raw_data, &cursor, 0x02);
+	bytes += qq_put8(raw_data + bytes, 0x02);
 	/* unknown 00 00 00 00 */
-	create_packet_dw(raw_data, &cursor, 0x00000000);
-	create_packet_dw(raw_data, &cursor, position);
+	bytes += qq_put32(raw_data + bytes, 0x00000000);
+	bytes += qq_put32(raw_data + bytes, position);
+
+	qq_send_cmd(qd, QQ_CMD_GET_ALL_LIST_WITH_GROUP, raw_data, bytes);
+}
+
+/* parse the data into qq_buddy_status */
+static gint get_buddy_status(qq_buddy_status *bs, guint8 *data)
+{
+	gint bytes = 0;
+
+	g_return_val_if_fail(data != NULL && bs != NULL, -1);
+
+	/* 000-003: uid */
+	bytes += qq_get32(&bs->uid, data + bytes);
+	/* 004-004: 0x01 */
+	bytes += qq_get8(&bs->unknown1, data + bytes);
+	/* this is no longer the IP, it seems QQ (as of 2006) no longer sends
+	 * the buddy's IP in this packet. all 0s */
+	/* 005-008: ip */
+	bytes += qq_getIP(&bs->ip, data + bytes);
+	/* port info is no longer here either */
+	/* 009-010: port */
+	bytes += qq_get16(&bs->port, data + bytes);
+	/* 011-011: 0x00 */
+	bytes += qq_get8(&bs->unknown2, data + bytes);
+	/* 012-012: status */
+	bytes += qq_get8(&bs->status, data + bytes);
+	/* 013-014: client_version */
+	bytes += qq_get16(&bs->unknown3, data + bytes);
+	/* 015-030: unknown key */
+	bytes += qq_getdata(&(bs->unknown_key[0]), QQ_KEY_LENGTH, data + bytes);
+
+	purple_debug(PURPLE_DEBUG_INFO, "QQ_STATUS", 
+			"uid: %d, U1: %d, ip: %s:%d, U2:%d, status:%d, U3:%04X\n", 
+			bs->uid, bs->unknown1, inet_ntoa(bs->ip), bs->port,
+			bs->unknown2, bs->status, bs->unknown3);
+
+	return bytes;
+}
+
+#define QQ_ONLINE_BUDDY_ENTRY_LEN       38
+
+/* process the reply packet for get_buddies_online packet */
+guint8 qq_process_get_buddies_online_reply(guint8 *buf, gint buf_len, PurpleConnection *gc)
+{
+	qq_data *qd;
+	gint len, bytes, bytes_buddy;
+	gint count;
+	guint8 *data, position;
+	PurpleBuddy *b;
+	qq_buddy *q_bud;
+	qq_buddy_online bo;
+
+	g_return_val_if_fail(buf != NULL && buf_len != 0, -1);
+
+	qd = (qq_data *) gc->proto_data;
+	len = buf_len;
+	data = g_newa(guint8, len);
+
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "processing get_buddies_online_reply\n");
+
+	if (!qq_decrypt(buf, buf_len, qd->session_key, data, &len)) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt buddies online");
+		return -1;
+	}
 
-	qq_send_cmd(gc, QQ_CMD_GET_ALL_LIST_WITH_GROUP, TRUE, 0, TRUE, raw_data, data_len);
+	/* qq_show_packet("Get buddies online reply packet", data, len); */
+
+	bytes = 0;
+	bytes += qq_get8(&position, data + bytes);
+
+	count = 0;
+	while (bytes < len) {
+		if (len - bytes < QQ_ONLINE_BUDDY_ENTRY_LEN) {
+			purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
+					"[buddies online] only %d, need %d", 
+					(len - bytes), QQ_ONLINE_BUDDY_ENTRY_LEN);
+			break;
+		}
+		memset(&bo, 0 ,sizeof(bo));
+		
+		/* set flag */
+		bytes_buddy = bytes;
+		/* based on one online buddy entry */
+		/* 000-030 qq_buddy_status */
+		bytes += get_buddy_status(&(bo.bs), data + bytes);
+		/* 031-032: */
+		bytes += qq_get16(&bo.unknown1, data + bytes);
+		/* 033-033: ext_flag */
+		bytes += qq_get8(&bo.ext_flag, data + bytes);
+		/* 034-034: comm_flag */
+		bytes += qq_get8(&bo.comm_flag, data + bytes);
+		/* 035-036: */
+		bytes += qq_get16(&bo.unknown2, data + bytes);
+		/* 037-037: */
+		bytes += qq_get8(&bo.ending, data + bytes);	/* 0x00 */
+
+		if (bo.bs.uid == 0 || (bytes - bytes_buddy) != QQ_ONLINE_BUDDY_ENTRY_LEN) {
+			purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
+					"uid=0 or entry complete len(%d) != %d", 
+					(bytes - bytes_buddy), QQ_ONLINE_BUDDY_ENTRY_LEN);
+			continue;
+		}	/* check if it is a valid entry */
+
+		/* update buddy information */
+		b = purple_find_buddy(purple_connection_get_account(gc), 
+												uid_to_purple_name(bo.bs.uid) );
+		q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
+		if (q_bud == NULL) {
+			purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
+					"Got an online buddy %d, but not in my buddy list\n", bo.bs.uid);
+			continue;
+		}
+		/* we find one and update qq_buddy */
+		/*
+		if(0 != fe->s->client_version)
+			q_bud->client_version = fe->s->client_version;
+		*/
+		q_bud->ip.s_addr = bo.bs.ip.s_addr;
+		q_bud->port = bo.bs.port;
+		q_bud->status = bo.bs.status;
+		q_bud->ext_flag = bo.ext_flag;
+		q_bud->comm_flag = bo.comm_flag;
+		qq_update_buddy_contact(gc, q_bud);
+		count++;
+	}
+
+	if(bytes > len) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
+				"qq_process_get_buddies_online_reply: Dangerous error! maybe protocol changed, notify developers!\n");
+	}
+
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Received %d online buddies, nextposition=%u\n",
+							count, (guint) position);
+	return position;
 }
 
-static void _qq_buddies_online_reply_dump_unclear(qq_friends_online_entry *fe)
+
+/* process reply for get_buddies_list */
+guint16 qq_process_get_buddies_list_reply(guint8 *buf, gint buf_len, PurpleConnection *gc)
 {
-	GString *dump;
+	qq_data *qd;
+	qq_buddy *q_bud;
+	gint len, bytes_expected, count;
+	gint bytes, buddy_bytes;
+	guint16 position, unknown;
+	guint8 *data, pascal_len;
+	gchar *name;
+	PurpleBuddy *b;
+
+	g_return_val_if_fail(buf != NULL && buf_len != 0, -1);
+
+	qd = (qq_data *) gc->proto_data;
+	len = buf_len;
+	data = g_newa(guint8, len);
 
-	g_return_if_fail(fe != NULL);
+	if (!qq_decrypt(buf, buf_len, qd->session_key, data, &len)) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt buddies list");
+		return -1;
+	}
+	bytes = 0;
+	bytes += qq_get16(&position, data + bytes);
+	/* the following data is buddy list in this packet */
+	count = 0;
+	while (bytes < len) {
+		q_bud = g_new0(qq_buddy, 1);
+		/* set flag */
+		buddy_bytes = bytes;
+		/* 000-003: uid */
+		bytes += qq_get32(&q_bud->uid, data + bytes);
+		/* 004-005: icon index (1-255) */
+		bytes += qq_get16(&q_bud->face, data + bytes);
+		/* 006-006: age */
+		bytes += qq_get8(&q_bud->age, data + bytes);
+		/* 007-007: gender */
+		bytes += qq_get8(&q_bud->gender, data + bytes);
 
-	qq_buddy_status_dump_unclear(fe->s);
+		pascal_len = convert_as_pascal_string(data + bytes, &q_bud->nickname, QQ_CHARSET_DEFAULT);
+		bytes += pascal_len;
 
-	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);
+		bytes += qq_get16(&unknown, data + bytes);
+		bytes += qq_get8(&q_bud->ext_flag, data + bytes);
+		bytes += qq_get8(&q_bud->comm_flag, data + bytes);
+
+		bytes_expected = 12 + pascal_len;
+
+		if (q_bud->uid == 0 || (bytes - buddy_bytes) != bytes_expected) {
+			purple_debug(PURPLE_DEBUG_INFO, "QQ",
+					"Buddy entry, expect %d bytes, read %d bytes\n", bytes_expected, bytes - buddy_bytes);
+			g_free(q_bud->nickname);
+			g_free(q_bud);
+			continue;
+		} else {
+			count++;
+		}
+
+		if (QQ_DEBUG) {
+			purple_debug(PURPLE_DEBUG_INFO, "QQ",
+					"buddy [%09d]: ext_flag=0x%02x, comm_flag=0x%02x, nick=%s\n",
+					q_bud->uid, q_bud->ext_flag, q_bud->comm_flag, q_bud->nickname);
+		}
 
-	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Online buddy entry, %s", dump->str);
-	g_string_free(dump, TRUE);
+		name = uid_to_purple_name(q_bud->uid);
+		b = purple_find_buddy(gc->account, name);
+		g_free(name);
+
+		if (b == NULL) {
+			b = qq_add_buddy_by_recv_packet(gc, q_bud->uid, TRUE, FALSE);
+		}
+
+		b->proto_data = q_bud;
+		qd->buddies = g_list_append(qd->buddies, q_bud);
+		qq_update_buddy_contact(gc, q_bud);
+	}
+
+	if(bytes > len) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
+				"qq_process_get_buddies_list_reply: Dangerous error! maybe protocol changed, notify developers!");
+	}
+
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Received %d buddies, nextposition=%u\n",
+		count, (guint) position);
+	return position;
 }
 
-/* process the reply packet for get_buddies_online packet */
-void qq_process_get_buddies_online_reply(guint8 *buf, gint buf_len, PurpleConnection *gc)
+guint32 qq_process_get_all_list_with_group_reply(guint8 *buf, gint buf_len, PurpleConnection *gc)
+{
+	qq_data *qd;
+	gint len, i, j;
+	gint bytes = 0;
+	guint8 *data;
+	guint8 sub_cmd, reply_code;
+	guint32 unknown, position;
+	guint32 uid;
+	guint8 type, groupid;
+	qq_group *group;
+
+	g_return_val_if_fail(buf != NULL && buf_len != 0, -1);
+
+	qd = (qq_data *) gc->proto_data;
+	len = buf_len;
+	data = g_newa(guint8, len);
+
+	if (!qq_decrypt(buf, buf_len, qd->session_key, data, &len)) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt all list with group");
+		return -1;
+	}
+
+	bytes += qq_get8(&sub_cmd, data + bytes);
+	g_return_val_if_fail(sub_cmd == 0x01, -1);
+
+	bytes += qq_get8(&reply_code, data + bytes);
+	if(0 != reply_code) {
+		purple_debug(PURPLE_DEBUG_WARNING, "QQ", 
+				"Get all list with group reply, reply_code(%d) is not zero", reply_code);
+	}
+
+	bytes += qq_get32(&unknown, data + bytes);
+	bytes += qq_get32(&position, data + bytes);
+	/* the following data is all list in this packet */
+	i = 0;
+	j = 0;
+	while (bytes < len) {
+		/* 00-03: uid */
+		bytes += qq_get32(&uid, data + bytes);
+		/* 04: type 0x1:buddy 0x4:Qun */
+		bytes += qq_get8(&type, data + bytes);
+		/* 05: groupid*4 */ /* seems to always be 0 */
+		bytes += qq_get8(&groupid, data + bytes);
+		/*
+		   purple_debug(PURPLE_DEBUG_INFO, "QQ", "groupid: %i\n", groupid);
+		   groupid >>= 2;
+		   */
+		if (uid == 0 || (type != 0x1 && type != 0x4)) {
+			purple_debug(PURPLE_DEBUG_INFO, "QQ",
+					"Buddy entry, uid=%d, type=%d", uid, type);
+			continue;
+		} 
+		if(0x1 == type) { /* a buddy */
+			/* don't do anything but count - buddies are handled by 
+			 * qq_send_packet_get_buddies_list */
+			++i;
+		} else { /* a group */
+			group = qq_group_find_by_id(gc, uid, QQ_INTERNAL_ID);
+			if(group == NULL) {
+				qq_set_pending_id(&qd->adding_groups_from_server, uid, TRUE);
+				group = g_newa(qq_group, 1);
+				group->internal_group_id = uid;
+				qq_send_cmd_group_get_group_info(gc, group);
+			} else {
+				group->my_status = QQ_GROUP_MEMBER_STATUS_IS_MEMBER;
+				qq_group_refresh(gc, group);
+				qq_send_cmd_group_get_group_info(gc, group);
+			}
+			++j;
+		}
+	}
+
+	if(bytes > len) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
+				"qq_process_get_all_list_with_group_reply: Dangerous error! maybe protocol changed, notify developers!");
+	}
+
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Received %d buddies and %d groups, nextposition=%u\n", i, j, (guint) position);
+	return position;
+}
+
+#define QQ_MISC_STATUS_HAVING_VIIDEO      0x00000001
+#define QQ_CHANGE_ONLINE_STATUS_REPLY_OK 	0x30	/* ASCII value of "0" */
+
+/* TODO: figure out what's going on with the IP region. Sometimes I get valid IP addresses, 
+ * but the port number's weird, other times I get 0s. I get these simultaneously on the same buddy, 
+ * using different accounts to get info. */
+
+/* check if status means online or offline */
+gboolean is_online(guint8 status)
+{
+	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;
+}
+
+/* Help calculate the correct icon index to tell the server. */
+gint get_icon_offset(PurpleConnection *gc)
+{ 
+	PurpleAccount *account;
+	PurplePresence *presence; 
+
+	account = purple_connection_get_account(gc);
+	presence = purple_account_get_presence(account);
+
+	if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_INVISIBLE)) {
+		return 2;
+	} else if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_AWAY)
+			|| purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_EXTENDED_AWAY)
+			|| purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_UNAVAILABLE)) {
+		return 1;
+	} else {
+		return 0;
+	}
+}
+
+/* send a packet to change my online status */
+void qq_send_packet_change_status(PurpleConnection *gc)
+{
+	qq_data *qd;
+	guint8 raw_data[16] = {0};
+	gint bytes = 0;
+	guint8 away_cmd;
+	guint32 misc_status;
+	gboolean fake_video;
+	PurpleAccount *account;
+	PurplePresence *presence; 
+
+	account = purple_connection_get_account(gc);
+	presence = purple_account_get_presence(account);
+
+	qd = (qq_data *) gc->proto_data;
+	if (!qd->logged_in)
+		return;
+
+	if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_INVISIBLE)) {
+		away_cmd = QQ_BUDDY_ONLINE_INVISIBLE;
+	} else if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_AWAY)
+			|| purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_EXTENDED_AWAY)
+			|| purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_UNAVAILABLE)) {
+		away_cmd = QQ_BUDDY_ONLINE_AWAY;
+	} else {
+		away_cmd = QQ_BUDDY_ONLINE_NORMAL;
+	}
+
+	misc_status = 0x00000000;
+	fake_video = purple_prefs_get_bool("/plugins/prpl/qq/show_fake_video");
+	if (fake_video)
+		misc_status |= QQ_MISC_STATUS_HAVING_VIIDEO;
+
+	bytes = 0;
+	bytes += qq_put8(raw_data + bytes, away_cmd);
+	bytes += qq_put32(raw_data + bytes, misc_status);
+
+	qq_send_cmd(qd, QQ_CMD_CHANGE_ONLINE_STATUS, raw_data, bytes);
+}
+
+/* parse the reply packet for change_status */
+void qq_process_change_status_reply(guint8 *buf, gint buf_len, PurpleConnection *gc)
 {
 	qq_data *qd;
 	gint len, bytes;
-	guint8 *data, *cursor, position;
+	guint8 *data, reply;
 	PurpleBuddy *b;
 	qq_buddy *q_bud;
-	qq_friends_online_entry *fe;
+	gchar *name;
 
 	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;
 
-	purple_debug(PURPLE_DEBUG_INFO, "QQ", "processing get_buddies_online_reply\n");
-	
-	if (qq_decrypt(buf, buf_len, qd->session_key, data, &len)) {
-
-		_qq_show_packet("Get buddies online reply packet", data, len);
-
-		read_packet_b(data, &cursor, len, &position);
-
-		fe = g_newa(qq_friends_online_entry, 1);
-		fe->s = g_newa(qq_buddy_status, 1);
-
-		while (cursor < (data + len)) {
-			/* based on one online buddy entry */
-			bytes = 0;
-			/* 000-030 qq_buddy_status */
-			bytes += qq_buddy_status_read(data, &cursor, len, fe->s);
-			/* 031-032: unknown4 */
-			bytes += read_packet_w(data, &cursor, len, &fe->unknown1);
-			/* 033-033: flag1 */
-			bytes += read_packet_b(data, &cursor, len, &fe->flag1);
-			/* 034-034: comm_flag */
-			bytes += read_packet_b(data, &cursor, len, &fe->comm_flag);
-			/* 035-036: */
-			bytes += read_packet_w(data, &cursor, len, &fe->unknown2);
-			/* 037-037: */
-			bytes += read_packet_b(data, &cursor, len, &fe->ending);	/* 0x00 */
-
-			if (fe->s->uid == 0 || bytes != QQ_ONLINE_BUDDY_ENTRY_LEN) {
-				purple_debug(PURPLE_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_decrypt(buf, buf_len, qd->session_key, data, &len) ) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt chg status reply\n");
+		return;
+	}
 
-			if (QQ_DEBUG)
-				_qq_buddies_online_reply_dump_unclear(fe);
-
-			/* update buddy information */
-			b = purple_find_buddy(purple_connection_get_account(gc), uid_to_purple_name(fe->s->uid));
-			q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
-
-			if (q_bud != NULL) {	/* we find one and update qq_buddy */
-				if(0 != fe->s->client_version)
-					q_bud->client_version = fe->s->client_version;
-				g_memmove(q_bud->ip, fe->s->ip, 4);
-				q_bud->port = fe->s->port;
-				q_bud->status = fe->s->status;
-				q_bud->flag1 = fe->flag1;
-				q_bud->comm_flag = fe->comm_flag;
-				qq_update_buddy_contact(gc, q_bud);
-			} else {
-				purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
-						"Got an online buddy %d, but not in my buddy list\n", fe->s->uid);
-			}
+	bytes = 0;
+	bytes = qq_get8(&reply, data + bytes);
+	if (reply != QQ_CHANGE_ONLINE_STATUS_REPLY_OK) {
+		purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Change status fail 0x%02X\n", reply);
+		return;
+	}
 
-			g_free(fe->s->ip);
-			g_free(fe->s->unknown_key);
-		}
-		
-		if(cursor > (data + len)) {
-			 purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
-					"qq_process_get_buddies_online_reply: Dangerous error! maybe protocol changed, notify developers!\n");
-		}
-
-		if (position != QQ_FRIENDS_ONLINE_POSITION_END) {
-			purple_debug(PURPLE_DEBUG_INFO, "QQ", "Has more online buddies, position from %d\n", position);
-
-			qq_send_packet_get_buddies_online(gc, position);
-		} else {
-			qq_send_packet_get_buddies_levels(gc);
-			qq_refresh_all_buddy_status(gc);
-		}
-
-	} else {
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt buddies online");
+	/* purple_debug(PURPLE_DEBUG_INFO, "QQ", "Change status OK\n"); */
+	name = uid_to_purple_name(qd->uid);
+	b = purple_find_buddy(gc->account, name);
+	g_free(name);
+	q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
+	if (q_bud != NULL) {
+		qq_update_buddy_contact(gc, q_bud);
 	}
 }
 
-/* process reply for get_buddies_list */
-void qq_process_get_buddies_list_reply(guint8 *buf, gint buf_len, PurpleConnection *gc)
+/* it is a server message indicating that one of my buddies has changed its status */
+void qq_process_buddy_change_status(guint8 *buf, gint buf_len, PurpleConnection *gc) 
 {
 	qq_data *qd;
+	gint bytes;
+	guint32 my_uid;
+	guint8 *data;
+	gint data_len;
+	PurpleBuddy *b;
 	qq_buddy *q_bud;
-	gint len, bytes, bytes_expected, i;
-	guint16 position, unknown;
-	guint8 *data, *cursor, pascal_len;
+	qq_buddy_status bs;
 	gchar *name;
-	PurpleBuddy *b;
 
 	g_return_if_fail(buf != NULL && buf_len != 0);
 
 	qd = (qq_data *) gc->proto_data;
-	len = buf_len;
-	data = g_newa(guint8, len);
-	cursor = data;
+	data_len = buf_len;
+	data = g_newa(guint8, data_len);
+
+	if ( !qq_decrypt(buf, buf_len, qd->session_key, data, &data_len) ) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "[buddy status change] Failed decrypt\n");
+		return;
+	}
+
+	if (data_len < 35) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "[buddy status change] only %d, need 35 bytes\n", data_len);
+		return;
+	}
+	
+	memset(&bs, 0, sizeof(bs));
+	bytes = 0;
+	/* 000-030: qq_buddy_status */
+	bytes += get_buddy_status(&bs, data + bytes);
+	/* 031-034:  Unknow, maybe my uid */ 
+	/* This has a value of 0 when we've changed our status to 
+	 * QQ_BUDDY_ONLINE_INVISIBLE */
+	bytes += qq_get32(&my_uid, data + bytes);
+
+	name = uid_to_purple_name(bs.uid);
+	b = purple_find_buddy(gc->account, name);
+	g_free(name);
+	q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
+	if (q_bud == NULL) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
+				"got information of unknown buddy %d\n", bs.uid);
+		return;
+	}
+
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "status:.uid = %d, q_bud->uid = %d\n", bs.uid , q_bud->uid);
+	if(bs.ip.s_addr != 0) { 
+		q_bud->ip.s_addr = bs.ip.s_addr;
+		q_bud->port = bs.port;
+	}
+	q_bud->status =bs.status;
+
+	if (q_bud->status == QQ_BUDDY_ONLINE_NORMAL) {
+		qq_send_packet_get_level(gc, q_bud->uid);
+	}
+	qq_update_buddy_contact(gc, q_bud);
+}
+
+/*TODO: maybe this should be qq_update_buddy_status() ?*/
+void qq_update_buddy_contact(PurpleConnection *gc, qq_buddy *q_bud)
+{
+	gchar *name;
+	PurpleBuddy *bud;
+	gchar *status_id;
+	
+	g_return_if_fail(q_bud != NULL);
+
+	name = uid_to_purple_name(q_bud->uid);
+	bud = purple_find_buddy(gc->account, name);
 
-	if (qq_decrypt(buf, buf_len, qd->session_key, data, &len)) {
-		read_packet_w(data, &cursor, len, &position);
-		/* the following data is buddy list in this packet */
-		i = 0;
-		while (cursor < (data + len)) {
-			q_bud = g_new0(qq_buddy, 1);
-			bytes = 0;
-			/* 000-003: uid */
-			bytes += read_packet_dw(data, &cursor, len, &q_bud->uid);
-			/* 004-005: icon index (1-255) */
-			bytes += read_packet_w(data, &cursor, len, &q_bud->face);
-			/* 006-006: age */
-			bytes += read_packet_b(data, &cursor, len, &q_bud->age);
-			/* 007-007: gender */
-			bytes += read_packet_b(data, &cursor, len, &q_bud->gender);
-			pascal_len = convert_as_pascal_string(cursor, &q_bud->nickname, QQ_CHARSET_DEFAULT);
-			cursor += pascal_len;
-			bytes += pascal_len;
-			bytes += read_packet_w(data, &cursor, len, &unknown);
-			/* flag1: (0-7)
-			 *        bit1 => qq show
-			 * comm_flag: (0-7)
-			 *        bit1 => member
-			 *        bit4 => TCP mode
-			 *        bit5 => open mobile QQ
-			 *        bit6 => bind to mobile
-			 *        bit7 => whether having a video
-			 */
-			bytes += read_packet_b(data, &cursor, len, &q_bud->flag1);
-			bytes += read_packet_b(data, &cursor, len, &q_bud->comm_flag);
+	if (bud == NULL) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "unknown buddy: %d\n", q_bud->uid);
+		g_free(name);
+		return;
+	}
+	
+	purple_blist_server_alias_buddy(bud, q_bud->nickname); /* server */
+	q_bud->last_refresh = time(NULL);
 
-			bytes_expected = 12 + pascal_len;
-
-			if (q_bud->uid == 0 || bytes != bytes_expected) {
-				purple_debug(PURPLE_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++;
-			}
+	/* purple supports signon and idle time
+	 * but it is not much use for QQ, I do not use them */
+	/* serv_got_update(gc, name, online, 0, q_bud->signon, q_bud->idle, bud->uc); */
+	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";
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "unknown status: %x\n", q_bud->status);
+		break;
+	}
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "buddy %d %s\n", q_bud->uid, status_id);
+	purple_prpl_got_user_status(gc->account, name, status_id, NULL);
 
-			if (QQ_DEBUG) {
-				purple_debug(PURPLE_DEBUG_INFO, "QQ",
-					   "buddy [%09d]: flag1=0x%02x, comm_flag=0x%02x\n",
-					   q_bud->uid, q_bud->flag1, q_bud->comm_flag);
-			}
+	if (q_bud->comm_flag & QQ_COMM_FLAG_MOBILE && q_bud->status != QQ_BUDDY_OFFLINE)
+		purple_prpl_got_user_status(gc->account, name, "mobile", NULL);
+	else
+		purple_prpl_got_user_status_deactive(gc->account, name, "mobile");
+
+	if (q_bud->comm_flag & QQ_COMM_FLAG_VIDEO && q_bud->status != QQ_BUDDY_OFFLINE)
+		purple_prpl_got_user_status(gc->account, name, "video", NULL);
+	else
+		purple_prpl_got_user_status_deactive(gc->account, name, "video");
+
+	g_free(name);
+}
 
-			name = uid_to_purple_name(q_bud->uid);
-			b = purple_find_buddy(gc->account, name);
-			g_free(name);
+/* refresh all buddies online/offline,
+ * after receiving reply for get_buddies_online packet */
+void qq_refresh_all_buddy_status(PurpleConnection *gc)
+{
+	time_t now;
+	GList *list;
+	qq_data *qd;
+	qq_buddy *q_bud;
 
-			if (b == NULL)
-				b = qq_add_buddy_by_recv_packet(gc, q_bud->uid, TRUE, FALSE);
+	qd = (qq_data *) (gc->proto_data);
+	now = time(NULL);
+	list = qd->buddies;
 
-			b->proto_data = q_bud;
-			qd->buddies = g_list_append(qd->buddies, q_bud);
+	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) {
+			q_bud->status = QQ_BUDDY_ONLINE_OFFLINE;
 			qq_update_buddy_contact(gc, q_bud);
 		}
-
-		if(cursor > (data + len)) {
-			purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
-					"qq_process_get_buddies_list_reply: Dangerous error! maybe protocol changed, notify developers!");
-                }
-		if (position == QQ_FRIENDS_LIST_POSITION_END) {
-			purple_debug(PURPLE_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 {
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt buddies list");
+		list = list->next;
 	}
 }
-
-void qq_process_get_all_list_with_group_reply(guint8 *buf, gint buf_len, PurpleConnection *gc)
-{
-	qq_data *qd;
-	gint len, i, j;
-	guint8 *data, *cursor;
-	guint8 sub_cmd, reply_code;
-	guint32 unknown, position;
-	guint32 uid;
-	guint8 type, groupid;
-	qq_group *group;
-
-	g_return_if_fail(buf != NULL && buf_len != 0);
-
-	qd = (qq_data *) gc->proto_data;
-	len = buf_len;
-	data = g_newa(guint8, len);
-	cursor = data;
-
-	if (qq_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) {
-			purple_debug(PURPLE_DEBUG_WARNING, "QQ", 
-					"Get all list with group reply, reply_code(%d) is not zero", reply_code);
-		}
-		read_packet_dw(data, &cursor, len, &unknown);
-		read_packet_dw(data, &cursor, len, &position);
-		/* the following data is all list in this packet */
-		i = 0;
-		j = 0;
-		while (cursor < (data + len)) {
-			/* 00-03: uid */
-			read_packet_dw(data, &cursor, len, &uid);
-			/* 04: type 0x1:buddy 0x4:Qun */
-			read_packet_b(data, &cursor, len, &type);
-			/* 05: groupid*4 */ /* seems to always be 0 */
-			read_packet_b(data, &cursor, len, &groupid);
-			/*
-			purple_debug(PURPLE_DEBUG_INFO, "QQ", "groupid: %i\n", groupid);
-			groupid >>= 2;
-			*/
-			if (uid == 0 || (type != 0x1 && type != 0x4)) {
-				purple_debug(PURPLE_DEBUG_INFO, "QQ",
-					   "Buddy entry, uid=%d, type=%d", uid, type);
-				continue;
-			} 
-			if(0x1 == type) { /* a buddy */
-				/* don't do anything but count - buddies are handled by 
-				 * qq_send_packet_get_buddies_list */
-				++i;
-			} else { /* a group */
-				group = qq_group_find_by_id(gc, uid, QQ_INTERNAL_ID);
-				if(group == NULL) {
-					qq_set_pending_id(&qd->adding_groups_from_server, uid, TRUE);
-					group = g_newa(qq_group, 1);
-					group->internal_group_id = uid;
-					qq_send_cmd_group_get_group_info(gc, group);
-				} else {
-					group->my_status = QQ_GROUP_MEMBER_STATUS_IS_MEMBER;
-					qq_group_refresh(gc, group);
-					qq_send_cmd_group_get_group_info(gc, group);
-				}
-				++j;
-			}
-		}
-		if(cursor > (data + len)) {
-			 purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
-					"qq_process_get_all_list_with_group_reply: Dangerous error! maybe protocol changed, notify developers!");
-		}
-		purple_debug(PURPLE_DEBUG_INFO, "QQ", "Get all list done, %d buddies and %d Quns\n", i, j);
-	} else {
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt all list with group");
-	}
-}
--- a/libpurple/protocols/qq/buddy_list.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/buddy_list.h	Thu Nov 20 21:13:56 2008 +0000
@@ -28,16 +28,45 @@
 #include <glib.h>
 #include "connection.h"
 
-#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
+#include "qq.h"
+typedef struct _qq_buddy_status {
+	guint32 uid;
+	guint8 unknown1;
+	struct in_addr ip;
+	guint16 port;
+	guint8 unknown2;
+	guint8 status;
+	guint16 unknown3;
+	guint8 unknown_key[QQ_KEY_LENGTH];
+} qq_buddy_status;
+
+enum {
+	QQ_BUDDY_OFFLINE = 0x00,
+	QQ_BUDDY_ONLINE_NORMAL = 0x0a,
+	QQ_BUDDY_ONLINE_OFFLINE = 0x14,
+	QQ_BUDDY_ONLINE_AWAY = 0x1e,
+	QQ_BUDDY_ONLINE_INVISIBLE = 0x28
+};
 
 void qq_send_packet_get_buddies_online(PurpleConnection *gc, guint8 position);
-void qq_process_get_buddies_online_reply(guint8 *buf, gint buf_len, PurpleConnection *gc);
+guint8 qq_process_get_buddies_online_reply(guint8 *buf, gint buf_len, PurpleConnection *gc);
+
 void qq_send_packet_get_buddies_list(PurpleConnection *gc, guint16 position);
-void qq_process_get_buddies_list_reply(guint8 *buf, gint buf_len, PurpleConnection *gc);
+guint16 qq_process_get_buddies_list_reply(guint8 *buf, gint buf_len, PurpleConnection *gc);
+
 void qq_send_packet_get_all_list_with_group(PurpleConnection *gc, guint32 position);
-void qq_process_get_all_list_with_group_reply(guint8 *buf, gint buf_len, PurpleConnection *gc);
+guint32 qq_process_get_all_list_with_group_reply(guint8 *buf, gint buf_len, PurpleConnection *gc);
+
+void qq_refresh_all_buddy_status(PurpleConnection *gc);
+
+gboolean is_online(guint8 status);
+
+gint get_icon_offset(PurpleConnection *gc);
 
+void qq_send_packet_change_status(PurpleConnection *gc);
+void qq_process_change_status_reply(guint8 *buf, gint buf_len, PurpleConnection *gc);
+void qq_process_buddy_change_status(guint8 *buf, gint buf_len, PurpleConnection *gc);
+
+void qq_refresh_all_buddy_status(PurpleConnection *gc);
+void qq_update_buddy_contact(PurpleConnection *gc, qq_buddy *q_bud);
 #endif
--- a/libpurple/protocols/qq/buddy_opt.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/buddy_opt.c	Thu Nov 20 21:13:56 2008 +0000
@@ -34,9 +34,9 @@
 #include "crypt.h"
 #include "header_info.h"
 #include "im.h"
-#include "keep_alive.h"
+#include "qq_base.h"
 #include "packet_parse.h"
-#include "send_core.h"
+#include "qq_network.h"
 #include "utils.h"
 
 #define PURPLE_GROUP_QQ_FORMAT          "QQ (%s)"
@@ -61,33 +61,33 @@
 /* send packet to remove a buddy from my buddy list */
 static void _qq_send_packet_remove_buddy(PurpleConnection *gc, guint32 uid)
 {
+	qq_data *qd = (qq_data *) gc->proto_data;
 	gchar uid_str[11];
 
 	g_return_if_fail(uid > 0);
 
 	g_snprintf(uid_str, sizeof(uid_str), "%d", uid);
-	qq_send_cmd(gc, QQ_CMD_DEL_FRIEND, TRUE, 0, 
-			TRUE, (guint8 *) uid_str, strlen(uid_str));
+	qq_send_cmd(qd, QQ_CMD_DEL_BUDDY, (guint8 *) uid_str, strlen(uid_str));
 }
 
 /* try to remove myself from someone's buddy list */
 static void _qq_send_packet_remove_self_from(PurpleConnection *gc, guint32 uid)
 {
-	guint8 *raw_data, *cursor;
+	qq_data *qd = (qq_data *) gc->proto_data;
+	guint8 raw_data[16] = {0};
+	gint bytes = 0;
 
 	g_return_if_fail(uid > 0);
 
-	raw_data = g_newa(guint8, 4);
-	cursor = raw_data;
-	create_packet_dw(raw_data, &cursor, uid);
+	bytes += qq_put32(raw_data + bytes, uid);
 
-	qq_send_cmd(gc, QQ_CMD_REMOVE_SELF, TRUE, 0, TRUE, raw_data, 4);
+	qq_send_cmd(qd, QQ_CMD_REMOVE_SELF, raw_data, bytes);
 }
 
 /* try to add a buddy without authentication */
 static void _qq_send_packet_add_buddy(PurpleConnection *gc, guint32 uid)
 {
-	qq_data *qd;
+	qq_data *qd = (qq_data *) gc->proto_data;
 	qq_add_buddy_request *req;
 	gchar uid_str[11];
 
@@ -95,11 +95,9 @@
 
 	/* we need to send the ascii code of this uid to qq server */
 	g_snprintf(uid_str, sizeof(uid_str), "%d", uid);
-	qq_send_cmd(gc, QQ_CMD_ADD_FRIEND_WO_AUTH, TRUE, 0, 
-			TRUE, (guint8 *) uid_str, strlen(uid_str));
+	qq_send_cmd(qd, QQ_CMD_ADD_BUDDY_WO_AUTH, (guint8 *) uid_str, strlen(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;
@@ -109,28 +107,29 @@
 /* this buddy needs authentication, text conversion is done at lowest level */
 static void _qq_send_packet_buddy_auth(PurpleConnection *gc, guint32 uid, const gchar response, const gchar *text)
 {
+	qq_data *qd = (qq_data *) gc->proto_data;
 	gchar *text_qq, uid_str[11];
-	guint8 bar, *cursor, *raw_data;
+	guint8 bar, *raw_data;
+	gint bytes = 0;
 
 	g_return_if_fail(uid != 0);
 
 	g_snprintf(uid_str, sizeof(uid_str), "%d", uid);
 	bar = 0x1f;
 	raw_data = g_newa(guint8, QQ_MSG_IM_MAX);
-	cursor = raw_data;
 
-	create_packet_data(raw_data, &cursor, (guint8 *) uid_str, strlen(uid_str));
-	create_packet_b(raw_data, &cursor, bar);
-	create_packet_b(raw_data, &cursor, response);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *) uid_str, strlen(uid_str));
+	bytes += qq_put8(raw_data + bytes, bar);
+	bytes += qq_put8(raw_data + bytes, 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, (guint8 *) text_qq, strlen(text_qq));
+		bytes += qq_put8(raw_data + bytes, bar);
+		bytes += qq_putdata(raw_data + bytes, (guint8 *) 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);
+	qq_send_cmd(qd, QQ_CMD_BUDDY_AUTH, raw_data, bytes);
 }
 
 static void _qq_send_packet_add_buddy_auth_with_gc_and_uid(gc_and_uid *g, const gchar *text)
@@ -210,10 +209,10 @@
 
 	nombre = uid_to_purple_name(uid);
 	purple_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,
-			   purple_connection_get_account(gc), nombre, NULL,
-			   g2);
+			_("Sorry, you are not my type..."), TRUE, FALSE,
+			NULL, _("Reject"), G_CALLBACK(_qq_reject_add_request_real), _("Cancel"), NULL,
+			purple_connection_get_account(gc), nombre, NULL,
+			g2);
 	g_free(nombre);
 }
 
@@ -257,7 +256,8 @@
 {
 	qq_data *qd;
 	gint len;
-	guint8 *data, *cursor, reply;
+	gint bytes = 0;
+	guint8 *data, reply;
 	gchar **segments, *msg_utf8;
 
 	g_return_if_fail(buf != NULL && buf_len != 0);
@@ -265,22 +265,23 @@
 	qd = (qq_data *) gc->proto_data;
 	len = buf_len;
 	data = g_newa(guint8, len);
-	cursor = data;
+
+	if (!qq_decrypt(buf, buf_len, qd->session_key, data, &len)) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt add buddy with auth reply\n");
+	}
+
+	bytes += qq_get8(&reply, data + bytes);
 
-	if (qq_decrypt(buf, buf_len, qd->session_key, data, &len)) {
-		read_packet_b(data, &cursor, len, &reply);
-		if (reply != QQ_ADD_BUDDY_AUTH_REPLY_OK) {
-			purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Add buddy with auth request failed\n");
-			if (NULL == (segments = split_data(data, len, "\x1f", 2)))
-				return;
-			msg_utf8 = qq_to_utf8(segments[1], QQ_CHARSET_DEFAULT);
-			purple_notify_error(gc, NULL, _("Add buddy with auth request failed"), msg_utf8);
-			g_free(msg_utf8);
-		} else {
-			purple_debug(PURPLE_DEBUG_INFO, "QQ", "Add buddy with auth request OK\n");
+	if (reply != QQ_ADD_BUDDY_AUTH_REPLY_OK) {
+		purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Add buddy with auth request failed\n");
+		if (NULL == (segments = split_data(data, len, "\x1f", 2))) {
+			return;
 		}
+		msg_utf8 = qq_to_utf8(segments[1], QQ_CHARSET_DEFAULT);
+		purple_notify_error(gc, NULL, _("Add buddy with auth request failed"), msg_utf8);
+		g_free(msg_utf8);
 	} else {
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt add buddy with auth reply\n");
+		purple_debug(PURPLE_DEBUG_INFO, "QQ", "Add buddy with auth request OK\n");
 	}
 }
 
@@ -289,7 +290,8 @@
 {
 	qq_data *qd;
 	gint len;
-	guint8 *data, *cursor, reply;
+	gint bytes = 0;
+	guint8 *data, reply;
 
 	g_return_if_fail(buf != NULL && buf_len != 0);
 
@@ -297,20 +299,20 @@
 	len = buf_len;
 	data = g_newa(guint8, len);
 
-	if (qq_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 */
-			purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Remove buddy fails\n");
-		} else {		/* if reply */
-			purple_debug(PURPLE_DEBUG_INFO, "QQ", "Remove buddy OK\n");
-			/* TODO: We don't really need to notify the user about this, do we? */
-			purple_notify_info(gc, NULL, _("You have successfully removed a buddy"), NULL);
-		}
-	} else {
+	if (!qq_decrypt(buf, buf_len, qd->session_key, data, &len)) {
 		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt remove buddy reply\n");
 	}
+
+	bytes += qq_get8(&reply, data + bytes);
+
+	if (reply != QQ_REMOVE_BUDDY_REPLY_OK) {
+		/* there is no reason return from server */
+		purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Remove buddy fails\n");
+	} else {		/* if reply */
+		purple_debug(PURPLE_DEBUG_INFO, "QQ", "Remove buddy OK\n");
+		/* TODO: We don't really need to notify the user about this, do we? */
+		purple_notify_info(gc, NULL, _("You have successfully removed a buddy"), NULL);
+	}
 }
 
 /* process the server reply for my request to remove myself from a buddy */
@@ -318,7 +320,8 @@
 {
 	qq_data *qd;
 	gint len;
-	guint8 *data, *cursor, reply;
+	gint bytes = 0;
+	guint8 *data, reply;
 
 	g_return_if_fail(buf != NULL && buf_len != 0);
 
@@ -326,20 +329,20 @@
 	len = buf_len;
 	data = g_newa(guint8, len);
 
-	if (qq_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 */
-			purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Remove self fails\n");
-		else {		/* if reply */
-			purple_debug(PURPLE_DEBUG_INFO, "QQ", "Remove self from a buddy OK\n");
-			/* TODO: Does the user really need to be notified about this? */
-			purple_notify_info(gc, NULL, _("You have successfully removed yourself from your friend's buddy list"), NULL);
-		}
-	} else {
+	if (!qq_decrypt(buf, buf_len, qd->session_key, data, &len)) {
 		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt remove self reply\n");
 	}
+
+	bytes += qq_get8(&reply, data + bytes);
+
+	if (reply != QQ_REMOVE_SELF_REPLY_OK) {
+		/* there is no reason return from server */
+		purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Remove self fails\n");
+	} else {		/* if reply */
+		purple_debug(PURPLE_DEBUG_INFO, "QQ", "Remove self from a buddy OK\n");
+		/* TODO: Does the user really need to be notified about this? */
+		purple_notify_info(gc, NULL, _("You have successfully removed yourself from your friend's buddy list"), NULL);
+	}
 }
 
 void qq_process_add_buddy_reply(guint8 *buf, gint buf_len, guint16 seq, PurpleConnection *gc)
@@ -403,14 +406,14 @@
 			g->uid = for_uid;
 			msg = g_strdup_printf(_("User %d needs authentication"), for_uid);
 			purple_request_input(gc, NULL, msg,
-					   _("Input request here"), /* TODO: Awkward string to fix post string freeze - standardize auth dialogues? -evands */
-					   _("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),
-					   purple_connection_get_account(gc), nombre, NULL,
-					   g);
+					_("Input request here"), /* TODO: Awkward string to fix post string freeze - standardize auth dialogues? -evands */
+					_("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),
+					purple_connection_get_account(gc), nombre, NULL,
+					g);
 			g_free(msg);
 			g_free(nombre);
 		} else {	/* add OK */
@@ -457,7 +460,7 @@
 	g_return_val_if_fail(a != NULL && uid != 0, NULL);
 
 	group_name = is_known ?
-	    g_strdup_printf(PURPLE_GROUP_QQ_FORMAT, purple_account_get_username(a)) : g_strdup(PURPLE_GROUP_QQ_UNKNOWN);
+		g_strdup_printf(PURPLE_GROUP_QQ_FORMAT, purple_account_get_username(a)) : g_strdup(PURPLE_GROUP_QQ_UNKNOWN);
 
 	g = qq_get_purple_group(group_name);
 
@@ -478,7 +481,7 @@
 		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);
+		qq_send_packet_get_buddies_online(gc, 0);
 	}
 
 	purple_blist_add_buddy(b, NULL, g, NULL);
@@ -512,8 +515,8 @@
 		if (b != NULL)
 			purple_blist_remove_buddy(b);
 		purple_notify_error(gc, NULL,
-				  _("QQid Error"),
-				  _("Invalid QQid"));
+				_("QQid Error"),
+				_("Invalid QQid"));
 	}
 }
 
--- a/libpurple/protocols/qq/buddy_status.c	Mon Aug 18 17:08:01 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,280 +0,0 @@
-/**
- * @file buddy_status.c
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#include <string.h>
-#include "internal.h"
-#include "debug.h"
-#include "prefs.h"
-
-#include "buddy_info.h"
-#include "buddy_status.h"
-#include "crypt.h"
-#include "header_info.h"
-#include "keep_alive.h"
-#include "packet_parse.h"
-#include "send_core.h"
-#include "utils.h"
-
-#include "qq_proxy.h"
-
-#define QQ_MISC_STATUS_HAVING_VIIDEO      0x00000001
-#define QQ_CHANGE_ONLINE_STATUS_REPLY_OK 	0x30	/* ASCII value of "0" */
-
-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, "005-008:     %09x   (ip)\n", *(s->ip)); */
-	g_string_append_printf(dump, "009-010:     %04x   (port)\n", s->port);
-	g_string_append_printf(dump, "011:     %02x   (unknown)\n", s->unknown2);
-	g_string_append_printf(dump, "012:     %02x   (status)\n", s->status);
-	g_string_append_printf(dump, "013-014:     %04x   (client_version)\n", s->client_version);
-	/* g_string_append_printf(dump, "015-030:     %s   (unknown key)\n", s->unknown_key); */
-	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Buddy status entry, %s", dump->str);
-	_qq_show_packet("Unknown key", s->unknown_key, QQ_KEY_LENGTH);
-	g_string_free(dump, TRUE);
-}
-
-/* TODO: figure out what's going on with the IP region. Sometimes I get valid IP addresses, 
- * but the port number's weird, other times I get 0s. I get these simultaneously on the same buddy, 
- * using different accounts to get info. */
-
-/* parse the data into qq_buddy_status */
-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);
-	/* this is no longer the IP, it seems QQ (as of 2006) no longer sends
-	 * the buddy's IP in this packet. all 0s */
-	/* 005-008: ip */
-	s->ip = g_new0(guint8, 4);
-	bytes += read_packet_data(data, cursor, len, s->ip, 4);
-	/* port info is no longer here either */
-	/* 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;
-}
-
-/* check if status means online or offline */
-gboolean is_online(guint8 status)
-{
-	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;
-}
-
- /* Help calculate the correct icon index to tell the server. */
-gint get_icon_offset(PurpleConnection *gc)
-{ 
-	PurpleAccount *account;
-	PurplePresence *presence; 
-
-	account = purple_connection_get_account(gc);
-	presence = purple_account_get_presence(account);
-
-	if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_INVISIBLE)) {
-		return 2;
-	} else if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_AWAY)
-			|| purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_EXTENDED_AWAY)
-			|| purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_UNAVAILABLE)) {
-		return 1;
-        } else {
-		return 0;
-	}
-}
-
-/* send a packet to change my online status */
-void qq_send_packet_change_status(PurpleConnection *gc)
-{
-	qq_data *qd;
-	guint8 *raw_data, *cursor, away_cmd;
-	guint32 misc_status;
-	gboolean fake_video;
-	PurpleAccount *account;
-	PurplePresence *presence; 
-
-	account = purple_connection_get_account(gc);
-	presence = purple_account_get_presence(account);
-
-	qd = (qq_data *) gc->proto_data;
-	if (!qd->logged_in)
-		return;
-
-	if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_INVISIBLE)) {
-		away_cmd = QQ_BUDDY_ONLINE_INVISIBLE;
-	} else if (purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_AWAY)
-			|| purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_EXTENDED_AWAY)
-			|| purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_UNAVAILABLE)) {
-		away_cmd = QQ_BUDDY_ONLINE_AWAY;
-	} else {
-		away_cmd = QQ_BUDDY_ONLINE_NORMAL;
-	}
-
-	raw_data = g_new0(guint8, 5);
-	cursor = raw_data;
-	misc_status = 0x00000000;
-
-	fake_video = purple_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);
-}
-
-/* parse the reply packet for change_status */
-void qq_process_change_status_reply(guint8 *buf, gint buf_len, PurpleConnection *gc)
-{
-	qq_data *qd;
-	gint len;
-	guint8 *data, *cursor, reply;
-	PurpleBuddy *b;
-	qq_buddy *q_bud;
-	gchar *name;
-
-	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_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) {
-			purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Change status fail\n");
-		} else {
-			purple_debug(PURPLE_DEBUG_INFO, "QQ", "Change status OK\n");
-			name = uid_to_purple_name(qd->uid);
-			b = purple_find_buddy(gc->account, name);
-			g_free(name);
-			q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
-			qq_update_buddy_contact(gc, q_bud);
-		}
-	} else {
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt chg status reply\n");
-	}
-}
-
-/* it is a server message indicating that one of my buddies has changed its status */
-void qq_process_friend_change_status(guint8 *buf, gint buf_len, PurpleConnection *gc) 
-{
-	qq_data *qd;
-	gint len, bytes;
-	guint32 my_uid;
-	guint8 *data, *cursor;
-	PurpleBuddy *b;
-	qq_buddy *q_bud;
-	qq_buddy_status *s;
-	gchar *name;
-
-	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_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 */ 
-		/* This has a value of 0 when we've changed our status to 
-		 * QQ_BUDDY_ONLINE_INVISIBLE */
-		bytes += read_packet_dw(data, &cursor, len, &my_uid);
-
-		if (bytes != 35) {
-			purple_debug(PURPLE_DEBUG_ERROR, "QQ", "bytes(%d) != 35\n", bytes);
-			g_free(s->ip);
-			g_free(s->unknown_key);
-			g_free(s);
-			return;
-		}
-
-		name = uid_to_purple_name(s->uid);
-		b = purple_find_buddy(gc->account, name);
-		g_free(name);
-		q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
-		if (q_bud) {
-			purple_debug(PURPLE_DEBUG_INFO, "QQ", "s->uid = %d, q_bud->uid = %d\n", s->uid , q_bud->uid);
-			if(0 != *((guint32 *)s->ip)) { 
-				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; 
-			if (q_bud->status == QQ_BUDDY_ONLINE_NORMAL)
-				qq_send_packet_get_level(gc, q_bud->uid);
-			qq_update_buddy_contact(gc, q_bud);
-		} else {
-			purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
-					"got information of unknown buddy %d\n", s->uid);
-		}
-
-		g_free(s->ip);
-		g_free(s->unknown_key);
-		g_free(s);
-	} else {
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt buddy status change packet\n");
-	}
-}
--- a/libpurple/protocols/qq/buddy_status.h	Mon Aug 18 17:08:01 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-/**
- * @file buddy_status.h
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- *
- */
-
-#ifndef _QQ_BUDDY_STATUS_H_
-#define _QQ_BUDDY_STATUS_H_
-
-#include <glib.h>
-#include "connection.h"
-#include "qq.h"
-
-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,
-	QQ_BUDDY_ONLINE_NORMAL = 0x0a,
-	QQ_BUDDY_ONLINE_OFFLINE = 0x14,
-	QQ_BUDDY_ONLINE_AWAY = 0x1e,
-	QQ_BUDDY_ONLINE_INVISIBLE = 0x28
-};
-
-void qq_buddy_status_dump_unclear(qq_buddy_status *s);
-gboolean is_online(guint8 status);
-
-gint qq_buddy_status_read(guint8 *data, guint8 **cursor, gint len, qq_buddy_status *s);
-gint get_icon_offset(PurpleConnection *gc);
-
-void qq_send_packet_change_status(PurpleConnection *gc);
-
-void qq_process_change_status_reply(guint8 *buf, gint buf_len, PurpleConnection *gc);
-void qq_process_friend_change_status(guint8 *buf, gint buf_len, PurpleConnection *gc);
-#endif
--- a/libpurple/protocols/qq/char_conv.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/char_conv.c	Thu Nov 20 21:13:56 2008 +0000
@@ -37,10 +37,7 @@
 #define QQ_CHARSET_ENG        "ISO-8859-1"
 
 #define QQ_NULL_MSG           "(NULL)"	/* return this if conversion fails */
-#define QQ_NULL_SMILEY        "(SM)"	/* return this if smiley conversion fails */
-
-/* a debug function */
-void _qq_show_packet(const gchar *desc, const guint8 *buf, gint len);
+#define QQ_NULL_SMILEY        "(Broken)"	/* return this if smiley conversion fails */
 
 const gchar qq_smiley_map[QQ_SMILEY_AMOUNT] = {
 	0x41, 0x43, 0x42, 0x44, 0x45, 0x46, 0x47, 0x48,
@@ -111,21 +108,26 @@
 
 	ret = g_convert(str, len, to_charset, from_charset, &byte_read, &byte_write, &error);
 
-	if (error == NULL)
+	if (error == NULL) {
 		return ret;	/* conversion is OK */
-	else {			/* conversion error */
-		gchar *failed = hex_dump_to_str((guint8 *) str, (len == -1) ? strlen(str) : len);
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "%s\n", error->message);
-		purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Dump failed text\n%s", failed);
-		g_free(failed);
-		g_error_free(error);
-		return g_strdup(QQ_NULL_MSG);
 	}
+	
+	/* conversion error */
+	purple_debug(PURPLE_DEBUG_ERROR, "QQ_CONVERT", "%s\n", error->message);
+
+	qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ_CONVERT",
+		(guint8 *) str, (len == -1) ? strlen(str) : len,
+		"Dump failed text");
+
+	g_error_free(error);
+	return g_strdup(QQ_NULL_MSG);
 }
 
-/* take the input as a pascal string and return a converted c-string in UTF-8
+/*
+ * 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 saved in ret */
+ * the converted UTF-8 will be saved in ret
+ */ 
 gint convert_as_pascal_string(guint8 *data, gchar **ret, const gchar *from_charset) 
 {
 	guint8 len;
@@ -142,22 +144,23 @@
 gchar *qq_encode_to_purple(guint8 *data, gint len, const gchar *msg)
 {
 	GString *encoded;
-	guint8 font_attr, font_size, color[3], bar, *cursor;
+	guint8 font_attr, font_size, color[3], bar;
 	gboolean is_bold, is_italic, is_underline;
 	guint16 charset_code;
 	gchar *font_name, *color_code, *msg_utf8, *tmp, *ret;
+	gint bytes = 0;
 
-	cursor = data;
-	_qq_show_packet("QQ_MESG recv for font style", data, len);
+	/* checked qq_show_packet OK */
+	/* 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 */
+	bytes += qq_get8(&font_attr, data + bytes);
+	bytes += qq_getdata(color, 3, data + bytes);	/* 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);
+	bytes += qq_get8(&bar, data + bytes);	/* skip, not sure of its use */
+	bytes += qq_get16(&charset_code, data + bytes);
 
-	tmp = g_strndup((gchar *) cursor, data + len - cursor);
+	tmp = g_strndup((gchar *)(data + bytes), len - bytes);
 	font_name = qq_to_utf8(tmp, QQ_CHARSET_DEFAULT);
 	g_free(tmp);
 
@@ -177,11 +180,11 @@
 	/* 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);
+			"<font color=\"%s\"><font face=\"%s\"><font size=\"%d\">",
+			color_code, font_name, font_size / 3);
 	purple_debug(PURPLE_DEBUG_INFO, "QQ_MESG",
-		   "recv <font color=\"%s\"><font face=\"%s\"><font size=\"%d\">\n",
-		   color_code, font_name, font_size / 3);
+			"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) {
@@ -228,7 +231,7 @@
 	GString *converted;
 
 	converted = g_string_new("");
-	segments = split_data((guint8 *) text, strlen(text), "\x14", 0);
+	segments = split_data((guint8 *) text, strlen(text), "\x14\x15", 0);
 	g_string_append(converted, segments[0]);
 
 	while ((*(++segments)) != NULL) {
@@ -275,3 +278,17 @@
 	g_string_free(converted, FALSE);
 	return ret;
 }
+
+void qq_filter_str(gchar *str) {
+	gchar *temp;
+	if (str == NULL) {
+		return;
+	}
+
+	for (temp = str; *temp != 0; temp++) {
+		if (*temp == '\r' || *temp == '\n')  *temp = ' ';
+	}
+	g_strstrip(str);
+}
+
+
--- a/libpurple/protocols/qq/char_conv.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/char_conv.h	Thu Nov 20 21:13:56 2008 +0000
@@ -40,5 +40,5 @@
 gchar *qq_encode_to_purple(guint8 *font_attr_data, gint len, const gchar *msg);
 
 gchar *qq_im_filter_html(const gchar *text);
-
+void qq_filter_str(gchar *str);
 #endif
--- a/libpurple/protocols/qq/crypt.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/crypt.c	Thu Nov 20 21:13:56 2008 +0000
@@ -19,7 +19,7 @@
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  *
  *
  * QQ encryption algorithm
@@ -28,7 +28,7 @@
  * Puzzlebird, Nov-Dec 2002
  */
 
-/*Notes: (QQ uses 16 rounds, and modified something...)
+/* Notes: (QQ uses 16 rounds, and modified something...)
 
 IN : 64  bits of data in v[0] - v[1].
 OUT: 64  bits of data in w[0] - w[1].
@@ -45,6 +45,13 @@
 #include "crypt.h"
 #include "debug.h"
 
+/* 1, fixed alignment problem, when compiled on different platform
+ * 2, whether we need core debug
+ * 20070717, s3e */
+#if 0 
+#define CORE_DEBUG
+#endif
+
 /********************************************************************
  * encryption 
  *******************************************************************/
@@ -52,7 +59,8 @@
 /* Tiny Encryption Algorithm (TEA) */
 static void qq_encipher(guint32 *const v, const guint32 *const k, guint32 *const w)
 {
-	register guint32 y = g_ntohl(v[0]), 
+	register guint32
+		y = g_ntohl(v[0]), 
 		 z = g_ntohl(v[1]), 
 		 a = g_ntohl(k[0]), 
 		 b = g_ntohl(k[1]), 
@@ -72,24 +80,86 @@
 	w[1] = g_htonl(z);
 }
 
-static gint rand(void) {	/* it can be the real random seed function */
-	return 0xdead;
-}			/* override with number, convenient for debug */
+/* it can be the real random seed function */
+/* override with number, convenient for debug */
+#ifdef DEBUG
+static gint rand(void) {	
+	return 0xdead; 
+}
+#else
+#include <stdlib.h>
+#endif
 
 /* 64-bit blocks and some kind of feedback mode of operation */
-static void encrypt_block(guint8 *plain, guint8 *plain_pre_8, guint8 **crypted, 
+static inline void encrypt_block(guint8 *plain, guint8 *plain_pre_8, guint8 **crypted, 
 		guint8 **crypted_pre_8, const guint8 *const key, gint *count, 
 		gint *pos_in_block, gint *is_header) 
 {
+	/* loop it */
+	int j;
+	/* ships in encipher */
+	guint32 ptr_p[2];	/* 64 bits, guint32[2] */
+	guint32 ptr_k[4];	/* 128 bits, guint32[4] */
+	guint32 ptr_c[2];	/* 64 bits, guint32[2] */
+
 	/* prepare input text */
-	if (!*is_header)
-		*(guint64 *) plain ^= **(guint64 **) crypted_pre_8;
+#ifdef CORE_DEBUG
+	purple_debug(PURPLE_DEBUG_ERROR, "QQ_CORE_DEBUG",
+		"!we are in encrypt_block! *pos_in_block comes: %d, *is_header comes: %d\n",
+		*pos_in_block, *is_header);
+#endif
+	for(j = 0; j < 8; j++) {
+#ifdef CORE_DEBUG
+		purple_debug(PURPLE_DEBUG_INFO, "QQ_CORE_DEBUG",
+			"plain[%d]: 0x%02x, plain_pre_8[%d]: 0x%02x\n",
+			j, plain[j], j, plain_pre_8[j]);
+#endif
+		if (!*is_header) {
+#ifdef CORE_DEBUG
+			purple_debug(PURPLE_DEBUG_INFO, "QQ_CORE_DEBUG",
+				"(*crypted_pre_8 + %d): 0x%02x\n",
+				j, *(*crypted_pre_8 + j));
+#endif
+			plain[j] ^= (*(*crypted_pre_8 + j));
+#ifdef CORE_DEBUG
+			purple_debug(PURPLE_DEBUG_INFO, "QQ_CORE_DEBUG",
+				"NOW plain[%d]: 0x%02x\n",
+				j, plain[j]);
+#endif
+		} else {
+			plain[j] ^= plain_pre_8[j];
+#ifdef CORE_DEBUG
+			purple_debug(PURPLE_DEBUG_INFO, "QQ_CORE_DEBUG",
+				"NOW plain[%d]: 0x%02x\n",
+				j, plain[j]);
+#endif
+		}
+	}
+
+	g_memmove(ptr_p, plain, 8);
+	g_memmove(ptr_k, key, 16);
+	g_memmove(ptr_c, *crypted, 8);
 
 	/* encrypt it */
-	qq_encipher((guint32 *) plain, (guint32 *) key, (guint32 *) *crypted);
+	qq_encipher(ptr_p, ptr_k, ptr_c);
+	
+	g_memmove(plain, ptr_p, 8);
+	g_memmove(*crypted, ptr_c, 8);
 
-	**(guint64 **) crypted ^= *(guint64 *) plain_pre_8;
-
+	for(j = 0; j < 8; j++) {
+#ifdef CORE_DEBUG
+		purple_debug(PURPLE_DEBUG_INFO, "QQ_CORE_DEBUG",
+			"j: %d, *(*crypted + %d): 0x%02x, plain_pre_8[%d]: 0x%02x\n",
+			j, j, *(*crypted + j), j, plain_pre_8[j]);
+#endif
+		(*(*crypted + j)) ^= plain_pre_8[j];
+#ifdef CORE_DEBUG
+		purple_debug(PURPLE_DEBUG_INFO, "QQ_CORE_DEBUG",
+			"NOW *(*crypted + [%d]): 0x%02x\n",
+			j, *(*crypted + j));
+#endif
+	}
+	
 	memcpy(plain_pre_8, plain, 8);	/* prepare next */
 
 	*crypted_pre_8 = *crypted;	/* store position of previous 8 byte */
@@ -171,7 +241,8 @@
 
 static void qq_decipher(guint32 *const v, const guint32 *const k, guint32 *const w)
 {
-	register guint32 y = g_ntohl(v[0]), 
+	register guint32
+		y = g_ntohl(v[0]), 
 		z = g_ntohl(v[1]), 
 		a = g_ntohl(k[0]), 
 		b = g_ntohl(k[1]), 
@@ -196,12 +267,25 @@
 		const guint8 *const key, gint *context_start, 
 		guint8 *decrypted, gint *pos_in_block)
 {
+	/* loop */
+	int i;
+	/* ships in decipher */
+	guint32 ptr_v[2];
+	guint32 ptr_k[4];
+
 	if (*context_start == instrlen)
 		return 1;
 
-	*(guint64 *) decrypted ^= **(guint64 **) crypt_buff;
+	for(i = 0; i < 8; i++) {
+		decrypted[i] ^= (*(*crypt_buff + i));
+	}
+	
+	g_memmove(ptr_v, decrypted, 8);
+	g_memmove(ptr_k, key, 16);
 
-	qq_decipher((guint32 *) decrypted, (guint32 *) key, (guint32 *) decrypted);
+	qq_decipher(ptr_v, ptr_k, ptr_v);
+
+	g_memmove(decrypted, ptr_v, 8);
 
 	*context_start += 8;
 	*crypt_buff += 8;
@@ -218,6 +302,10 @@
 	guint8 decrypted[8], m[8], *outp;
 	const guint8 *crypt_buff, *crypt_buff_pre_8;
 	gint count, context_start, pos_in_block, padding;
+	/* ships */
+	guint32 ptr_instr[2];
+	guint32 ptr_key[4];
+	guint32 ptr_decr[2];
 
 	/* at least 16 bytes and %8 == 0 */
 	if ((instrlen % 8) || (instrlen < 16)) { 
@@ -226,8 +314,14 @@
 			instrlen);
 		return 0;
 	}
-	/* get information from header */
-	qq_decipher((guint32 *) instr, (guint32 *) key, (guint32 *) decrypted);
+	g_memmove(ptr_instr, instr, 8);
+	g_memmove(ptr_key, key, 16);
+	g_memmove(ptr_decr, decrypted, 8);
+
+	qq_decipher(ptr_instr, ptr_key, ptr_decr);
+
+	g_memmove(decrypted, ptr_decr, 8);
+
 	pos_in_block = decrypted[0] & 0x7;
 	count = instrlen - pos_in_block - 10;	/* this is the plaintext length */
 	/* return if outstr buffer is not large enough or error plaintext length */
@@ -294,5 +388,6 @@
 			}
 		}
 	}
+
 	return 1;
 }
--- a/libpurple/protocols/qq/crypt.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/crypt.h	Thu Nov 20 21:13:56 2008 +0000
@@ -1,4 +1,4 @@
-/**
+ /**
  * @file crypt.h
  *
  * purple
@@ -19,7 +19,7 @@
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
 #ifndef _QQ_CRYPT_H_
@@ -27,6 +27,9 @@
 
 #include <glib.h>
 
+#define DECRYPT 0x00
+#define ENCRYPT 0x01
+
 void qq_encrypt(const guint8 *const instr, gint instrlen, 
 		const guint8 *const key, 
 		guint8 *outstr, gint *outstrlen_ptr);
@@ -34,5 +37,4 @@
 gint qq_decrypt(const guint8 *const instr, gint instrlen, 
 		const guint8 *const key,
 		guint8 *outstr, gint *outstrlen_ptr);
-		
 #endif
--- a/libpurple/protocols/qq/file_trans.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/file_trans.c	Thu Nov 20 21:13:56 2008 +0000
@@ -38,12 +38,11 @@
 #include "im.h"
 #include "packet_parse.h"
 #include "proxy.h"
-#include "send_core.h"
+#include "qq_network.h"
 #include "send_file.h"
 #include "utils.h"
 
 struct _qq_file_header {
-	guint8 tag;
 	guint16 client_ver;
 	guint8 file_key;
 	guint32 sender_uid;
@@ -58,11 +57,11 @@
 	key = seed | (seed << 8) | (seed << 16) | (seed << 24);
 	return key;
 }
-		
+
 static guint32 _gen_file_key(void)
 {
 	guint8 seed;
-	
+
 	seed = random();
 	return _get_file_key(seed);
 }
@@ -77,26 +76,10 @@
 	return (~uid) ^ key;
 }
 
-static void _fill_filename_md5(const gchar *filename, guint8 *md5)
-{
-	PurpleCipher *cipher;
-	PurpleCipherContext *context;
-
-	g_return_if_fail(filename != NULL && md5 != NULL);
-
-	cipher = purple_ciphers_find_cipher("md5");
-	context = purple_cipher_context_new(cipher, NULL);
-	purple_cipher_context_append(context, (guint8 *) filename, strlen(filename));
-	purple_cipher_context_digest(context, 16, md5, NULL);
-	purple_cipher_context_destroy(context);
-}
-
 static void _fill_file_md5(const gchar *filename, gint filelen, guint8 *md5)
 {
 	FILE *fp;
 	guint8 *buffer;
-	PurpleCipher *cipher;
-	PurpleCipherContext *context;
 	size_t wc;
 
 	const gint QQ_MAX_FILE_MD5_LENGTH = 10002432;
@@ -119,23 +102,20 @@
 		return;
 	}
 
-	cipher = purple_ciphers_find_cipher("md5");
-	context = purple_cipher_context_new(cipher, NULL);
-	purple_cipher_context_append(context, buffer, filelen);
-	purple_cipher_context_digest(context, 16, md5, NULL);
-	purple_cipher_context_destroy(context);
+	qq_get_md5(md5, QQ_KEY_LENGTH, buffer, filelen);
 }
 
-static void _qq_get_file_header(guint8 *buf, guint8 **cursor, gint buflen, qq_file_header *fh)
+static gint _qq_get_file_header(qq_file_header *fh, guint8 *buf)
 {
-	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));
+	gint bytes = 0;
+	bytes += qq_get16(&(fh->client_ver), buf + bytes);
+	bytes += qq_get8(&fh->file_key, buf + bytes);
+	bytes += qq_get32(&(fh->sender_uid), buf + bytes);
+	bytes += qq_get32(&(fh->receiver_uid), buf + bytes);
 
 	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));
+	return bytes;
 }
 
 static const gchar *qq_get_file_cmd_desc(gint type)
@@ -190,7 +170,7 @@
 		fd = open(purple_xfer_get_local_filename(xfer), O_RDWR|O_CREAT, 0644);
 		info->buffer = mmap(0, purple_xfer_get_size(xfer), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FILE, fd, 0);
 	}
-		
+
 	if (info->buffer == NULL) {
 		return - 1;
 	}
@@ -258,30 +238,28 @@
 
 static gint _qq_send_file(PurpleConnection *gc, guint8 *data, gint len, guint16 packet_type, guint32 to_uid)
 {
-	gint bytes;
-	guint8 *cursor, *buf;
+	guint8 *raw_data;
+	gint bytes = 0;
 	guint32 file_key;
 	qq_data *qd;
 	ft_info *info;
 
 	qd = (qq_data *) gc->proto_data;
-	g_return_val_if_fail(qd->session_key != NULL, -1);
+
 	info = (ft_info *) qd->xfer->data;
-	bytes = 0;
 
-	buf = g_newa(guint8, MAX_PACKET_SIZE);
-	cursor = buf;
+	raw_data = g_newa(guint8, MAX_PACKET_SIZE);
 	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);
+	bytes += qq_put8(raw_data + bytes, packet_type);
+	bytes += qq_put16(raw_data + bytes, QQ_CLIENT);
+	bytes += qq_put8(raw_data + bytes, file_key & 0xff);
+	bytes += qq_put32(raw_data + bytes, _encrypt_qq_uid(qd->uid, file_key));
+	bytes += qq_put32(raw_data + bytes, _encrypt_qq_uid(to_uid, file_key));
+	bytes += qq_putdata(raw_data + bytes, data, len);
 
 	if (bytes == len + 12) {
-		_qq_xfer_write(buf, bytes, qd->xfer);
+		_qq_xfer_write(raw_data, bytes, qd->xfer);
 	} else
 		purple_debug(PURPLE_DEBUG_INFO, "QQ", "send_file: want %d but got %d\n", len + 12, bytes);
 	return bytes;
@@ -292,57 +270,56 @@
 {
 	qq_data *qd;
 	gint bytes, bytes_expected, encrypted_len;
-	guint8 *raw_data, *cursor, *encrypted_data;
+	guint8 *raw_data, *encrypted_data;
 	time_t now;
 	ft_info *info;
-	
+
 	qd = (qq_data *) gc->proto_data;
 	info = (ft_info *) qd->xfer->data;
 
-	raw_data = g_new0 (guint8, 61);
-	cursor = raw_data;
-	
+	raw_data = g_newa (guint8, 61);
 	bytes = 0;
+
 	now = time(NULL);
 
-	bytes += create_packet_data(raw_data, &cursor, qd->session_md5, 16);
-	bytes += create_packet_w(raw_data, &cursor, packet_type);
+	bytes += qq_putdata(raw_data + bytes, qd->session_md5, 16);
+	bytes += qq_put16(raw_data + bytes, 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);
+			bytes += qq_put16(raw_data + bytes, info->send_seq);
 			break;
 		default:
-			bytes += create_packet_w(raw_data, &cursor, ++qd->send_seq);
+			bytes += qq_put16(raw_data + bytes, ++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);
+	bytes += qq_put32(raw_data + bytes, (guint32) now);
+	bytes += qq_put8(raw_data + bytes, 0x00);
+	bytes += qq_put8(raw_data + bytes, qd->my_icon);
+	bytes += qq_put32(raw_data + bytes, 0x00000000);
+	bytes += qq_put32(raw_data + bytes, 0x00000000);
+	bytes += qq_put32(raw_data + bytes, 0x00000000);
+	bytes += qq_put32(raw_data + bytes, 0x00000000);
+	bytes += qq_put16(raw_data + bytes, 0x0000);
+	bytes += qq_put8(raw_data + bytes, 0x00);
 	/* 0x65: send a file, 0x6b: send a custom face */
-	bytes += create_packet_b(raw_data, &cursor, QQ_FILE_TRANSFER_FILE); /* FIXME temp by gfhuang */
+	bytes += qq_put8(raw_data + bytes, 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 += qq_put8(raw_data + bytes, 0x00);
+			bytes += qq_put8(raw_data + bytes, 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 += qq_fill_conn_info(raw_data, info);
 			bytes_expected = 61;
 			break;
 		default:
@@ -350,53 +327,55 @@
 					packet_type);
 			bytes_expected = 0;
 	}
-	
-	if (bytes == bytes_expected) {
-		gchar *hex_dump = hex_dump_to_str(raw_data, bytes);
-		purple_debug(PURPLE_DEBUG_INFO, "QQ", "sending packet[%s]: \n%s", qq_get_file_cmd_desc(packet_type), hex_dump);
-		g_free(hex_dump);
-		encrypted_len = bytes + 16;
-		encrypted_data = g_newa(guint8, encrypted_len);
-		qq_encrypt(raw_data, bytes, info->file_session_key, encrypted_data, &encrypted_len);
-		/*debug: try to decrypt it */
-		/*
-		if (QQ_DEBUG) {
-			guint8 *buf;
-			int buflen;
-			hex_dump = hex_dump_to_str(encrypted_data, encrypted_len);
-			purple_debug(PURPLE_DEBUG_INFO, "QQ", "encrypted packet: \n%s", hex_dump);
-			g_free(hex_dump);
-			buf = g_newa(guint8, MAX_PACKET_SIZE);
-			buflen = encrypted_len;
-			if (qq_decrypt(encrypted_data, encrypted_len, info->file_session_key, buf, &buflen)) {
-				purple_debug(PURPLE_DEBUG_INFO, "QQ", "decrypt success\n");
-				if (buflen == bytes && memcmp(raw_data, buf, buflen) == 0)
-					purple_debug(PURPLE_DEBUG_INFO, "QQ", "checksum ok\n");
-				hex_dump = hex_dump_to_str(buf, buflen);
-				purple_debug(PURPLE_DEBUG_INFO, "QQ", "decrypted packet: \n%s", hex_dump);
-				g_free(hex_dump);
-			} else {
-				purple_debug(PURPLE_DEBUG_INFO, "QQ", "decrypt fail\n");
-			}
-		}
-		*/
 
-		purple_debug(PURPLE_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
+	if (bytes != bytes_expected) {
 		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "qq_send_file_ctl_packet: Expected to get %d bytes, but get %d",
 				bytes_expected, bytes);
+		return;
+	}
+
+	qq_hex_dump(PURPLE_DEBUG_INFO, "QQ",
+		raw_data, bytes,
+		"sending packet[%s]:", qq_get_file_cmd_desc(packet_type));
+
+	encrypted_len = bytes + 16;
+	encrypted_data = g_newa(guint8, encrypted_len);
+	qq_encrypt(raw_data, bytes, info->file_session_key, encrypted_data, &encrypted_len);
+	/*debug: try to decrypt it */
+	/*
+	   if (QQ_DEBUG) {
+	   guint8 *buf;
+	   int buflen;
+	   hex_dump = hex_dump_to_str(encrypted_data, encrypted_len);
+	   purple_debug(PURPLE_DEBUG_INFO, "QQ", "encrypted packet: \n%s", hex_dump);
+	   g_free(hex_dump);
+	   buf = g_newa(guint8, MAX_PACKET_SIZE);
+	   buflen = encrypted_len;
+	   if (qq_crypt(DECRYPT, encrypted_data, encrypted_len, info->file_session_key, buf, &buflen)) {
+	   purple_debug(PURPLE_DEBUG_INFO, "QQ", "decrypt success\n");
+	   if (buflen == bytes && memcmp(raw_data, buf, buflen) == 0)
+	   purple_debug(PURPLE_DEBUG_INFO, "QQ", "checksum ok\n");
+	   hex_dump = hex_dump_to_str(buf, buflen);
+	   purple_debug(PURPLE_DEBUG_INFO, "QQ", "decrypted packet: \n%s", hex_dump);
+	   g_free(hex_dump);
+	   } else {
+	   purple_debug(PURPLE_DEBUG_INFO, "QQ", "decrypt fail\n");
+	   }
+	   }
+	   */
+
+	purple_debug(PURPLE_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);
 }
 
 /* send a file to udp channel with QQ_FILE_DATA_PACKET_TAG */
 static void _qq_send_file_data_packet(PurpleConnection *gc, guint16 packet_type, guint8 sub_type, 
 		guint32 fragment_index, guint16 seq, guint8 *data, gint len)
 {
+	guint8 *raw_data, filename_md5[QQ_KEY_LENGTH], file_md5[QQ_KEY_LENGTH];
 	gint bytes;
-	guint8 *raw_data, *cursor, filename_md5[QQ_KEY_LENGTH], file_md5[QQ_KEY_LENGTH];
 	guint32 fragment_size = 1000;
-	gchar *filename;
+	const char *filename;
 	gint filename_len, filesize;
 	qq_data *qd;
 	ft_info *info;
@@ -404,32 +383,31 @@
 	qd = (qq_data *) gc->proto_data;
 	info = (ft_info *) qd->xfer->data;
 
-	filename = (gchar *) purple_xfer_get_filename(qd->xfer);
+	filename = purple_xfer_get_filename(qd->xfer);
 	filesize = purple_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);
+	bytes += qq_put8(raw_data + bytes, 0x00);
+	bytes += qq_put16(raw_data + bytes, 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);
+			bytes += qq_put16(raw_data + bytes, 0x0000);
+			bytes += qq_put8(raw_data + bytes, 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);
+					qq_get_md5(filename_md5, sizeof(filename_md5), (guint8 *)filename, filename_len);
 					_fill_file_md5(purple_xfer_get_local_filename(qd->xfer),
 							purple_xfer_get_size(qd->xfer),
 							file_md5);
-
+					
 					info->fragment_num = (filesize - 1) / QQ_FILE_FRAGMENT_MAXLEN + 1;
 					info->fragment_len = QQ_FILE_FRAGMENT_MAXLEN;
 
@@ -437,44 +415,44 @@
 							"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);
+					bytes += qq_put16(raw_data  + bytes, 0x0000);
 					/* Sub-operation type */
-					bytes += create_packet_b(raw_data, &cursor, sub_type);
+					bytes += qq_put8(raw_data + bytes, sub_type);
 					/* Length of file */
-					bytes += create_packet_dw(raw_data, &cursor, filesize);
+					bytes += qq_put32(raw_data + bytes, filesize);
 					/* Number of fragments */
-					bytes += create_packet_dw(raw_data, &cursor, info->fragment_num);
+					bytes += qq_put32(raw_data + bytes, 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);
+					bytes += qq_put32(raw_data + bytes, info->fragment_len);
+					bytes += qq_putdata(raw_data + bytes, file_md5, 16);
+					bytes += qq_putdata(raw_data + bytes, filename_md5, 16);
 					/* Length of filename */
-					bytes += create_packet_w(raw_data, &cursor, filename_len);
+					bytes += qq_put16(raw_data + bytes, filename_len);
 					/* 8 unknown bytes */
-					bytes += create_packet_dw(raw_data, &cursor, 0x00000000);
-					bytes += create_packet_dw(raw_data, &cursor, 0x00000000);
+					bytes += qq_put32(raw_data + bytes, 0x00000000);
+					bytes += qq_put32(raw_data + bytes, 0x00000000);
 					/* filename */
-					bytes += create_packet_data(raw_data, &cursor, (guint8 *) filename,
+					bytes += qq_putdata(raw_data + bytes, (guint8 *) filename,
 							filename_len);
 					break;
 				case QQ_FILE_DATA_INFO:
 					purple_debug(PURPLE_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);
+					/* bytes += qq_put16(raw_data + bytes, ++(qd->send_seq)); */
+					bytes += qq_put16(raw_data + bytes, info->send_seq);
+					bytes += qq_put8(raw_data + bytes, sub_type);
+					/* bytes += qq_put32(raw_data + bytes, fragment_index); */
+					bytes += qq_put32(raw_data + bytes, fragment_index - 1);
+					bytes += qq_put32(raw_data + bytes, (fragment_index - 1) * fragment_size);
+					bytes += qq_put16(raw_data + bytes, len);
+					bytes += qq_putdata(raw_data + bytes, data, len);
 					break;
 				case QQ_FILE_EOF:
 					purple_debug(PURPLE_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);
+					/* bytes += qq_put16(raw_data + bytes, info->fragment_num + 1); */
+					bytes += qq_put16(raw_data + bytes, info->fragment_num);
+					bytes += qq_put8(raw_data + bytes, sub_type);
 					/* purple_xfer_set_completed(qd->xfer, TRUE); */
 			}
 			break;
@@ -482,18 +460,18 @@
 			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);
+					bytes += qq_put16(raw_data + bytes, 0x0000);
+					bytes += qq_put8(raw_data + bytes, sub_type);
+					bytes += qq_put32(raw_data + bytes, 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);
+					bytes += qq_put16(raw_data + bytes, seq);
+					bytes += qq_put8(raw_data + bytes, sub_type);
+					bytes += qq_put32(raw_data + bytes, 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);
+					bytes += qq_put16(raw_data + bytes, filesize / QQ_FILE_FRAGMENT_MAXLEN + 2);
+					bytes += qq_put8(raw_data + bytes, sub_type);
 					break;
 			}
 	}
@@ -520,9 +498,11 @@
  */
 
 
-static void _qq_process_recv_file_ctl_packet(PurpleConnection *gc, guint8 *data, guint8 *cursor,
-		gint len, qq_file_header *fh)
+static void _qq_process_recv_file_ctl_packet(PurpleConnection *gc, guint8 *data, gint len)
 {
+	gint bytes ;
+	gint decryped_bytes;
+	qq_file_header fh;
 	guint8 *decrypted_data;
 	gint decrypted_len;
 	qq_data *qd = (qq_data *) gc->proto_data;
@@ -531,60 +511,65 @@
 	guint8 hellobyte;
 	ft_info *info = (ft_info *) qd->xfer->data;
 
+	bytes = 0;
+	bytes += _qq_get_file_header(&fh, data + bytes);
+
 	decrypted_data = g_newa(guint8, len);
 	decrypted_len = len;
 
-	if (qq_decrypt(cursor, len - (cursor - data), qd->session_md5, decrypted_data, &decrypted_len)) {
-		gchar *hex_dump;
-		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;
-		purple_debug(PURPLE_DEBUG_INFO, "QQ", "==> [%d] receive %s packet\n", seq, qq_get_file_cmd_desc(packet_type));
-		hex_dump = hex_dump_to_str(decrypted_data, decrypted_len);
-		purple_debug(PURPLE_DEBUG_INFO, "QQ", "decrypted control packet received: \n%s", hex_dump);
-		g_free(hex_dump);
-		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);
+	if ( !qq_decrypt(data, len, qd->session_md5, decrypted_data, &decrypted_len) ) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt rcv file ctrl packet\n");
+		return;
+	}
+
+	/* only for debug info */
+	decryped_bytes = 16;	/* skip md5 section */
+	decryped_bytes += qq_get16(&packet_type, decrypted_data + decryped_bytes);
+	decryped_bytes += qq_get16(&seq, decrypted_data + decryped_bytes);
+	decryped_bytes += 4+1+1+19+1;	/* skip something */
+
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "==> [%d] receive %s packet\n", seq, qq_get_file_cmd_desc(packet_type));
+	qq_hex_dump(PURPLE_DEBUG_INFO, "QQ",
+		decrypted_data, decrypted_len,
+		"decrypted control packet received:");
 
-				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:
-				purple_debug(PURPLE_DEBUG_INFO, "QQ", "unprocess file command %d\n", packet_type);
-		}
-	} 
+	switch (packet_type) {
+		case QQ_FILE_CMD_NOTIFY_IP_ACK:
+			decryped_bytes = 0;
+			qq_get_conn_info(info, decrypted_data + decryped_bytes);
+			/* 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 */
+			decryped_bytes += 47;
+			decryped_bytes += qq_get8(&hellobyte, decrypted_data + decryped_bytes);
+			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 */
+			decryped_bytes += 47;
+			decryped_bytes += qq_get8(&hellobyte, decrypted_data + decryped_bytes);
+			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:
+			purple_debug(PURPLE_DEBUG_INFO, "QQ", "unprocess file command %d\n", packet_type);
+	}
 }
 
 static void _qq_recv_file_progess(PurpleConnection *gc, guint8 *buffer, guint16 len, guint32 index, guint32 offset)
@@ -609,15 +594,15 @@
 		purple_debug(PURPLE_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;
 	purple_xfer_update_progress(xfer);
-	
+
 	mask = 0x1 << (info->max_fragment_index % sizeof(info->window));
 	while (info->window & mask)
 	{
@@ -639,7 +624,7 @@
 	guint8 *buffer;
 	guint i;
 	gint readbytes;
-	
+
 	if (purple_xfer_get_bytes_remaining(xfer) <= 0) return;
 	if (info->window == 0 && info->max_fragment_index == 0)
 	{
@@ -655,7 +640,7 @@
 			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);
+						info->max_fragment_index + i + 1, 0, buffer, readbytes);
 		}
 		if (mask & 0x8000) mask = 0x0001;
 		else mask = mask << 1;
@@ -706,8 +691,8 @@
 					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 + sizeof(info->window) + 1, 0, buffer, readbytes);
+
 			info->max_fragment_index ++;
 			if (mask & 0x8000) mask = 0x0001;
 			else mask = mask << 1;
@@ -718,9 +703,10 @@
 			fragment_index, info->window, info->max_fragment_index);
 }
 
-static void _qq_process_recv_file_data(PurpleConnection *gc, guint8 *data, guint8 *cursor,
-		gint len, guint32 to_uid)
+static void _qq_process_recv_file_data(PurpleConnection *gc, guint8 *data, gint len)
 {
+	gint bytes ;
+	qq_file_header fh;
 	guint16 packet_type;
 	guint16 packet_seq;
 	guint8 sub_type;
@@ -729,24 +715,27 @@
 	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);
+
+	bytes = 0;
+	bytes += _qq_get_file_header(&fh, data + bytes);
+
+	bytes += 1; /* skip an unknown byte */
+	bytes += qq_get16(&packet_type, data + bytes);
 	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);
+			bytes += qq_get16(&packet_seq, data + bytes);
+			bytes += qq_get8(&sub_type, data + bytes);
 			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);
+					bytes += 4;	/* file length, we have already known it from xfer */
+					bytes += qq_get32(&info->fragment_num, data + bytes);
+					bytes += qq_get32(&info->fragment_len, data + bytes);
 
-					/* FIXME: We must check the md5 here, if md5 doesn't match
-					 * we will ignore the packet or send sth as error number
-					 */
+					/* 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;
@@ -757,27 +746,27 @@
 							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);
+					bytes += qq_get32(&fragment_index, data + bytes);
+					bytes += qq_get32(&fragment_offset, data + bytes);
+					bytes += qq_get16(&fragment_len, data + bytes);
 					purple_debug(PURPLE_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);
+					_qq_recv_file_progess(gc, data + bytes, fragment_len, fragment_index, fragment_offset);
 					break;
 				case QQ_FILE_EOF:
 					purple_debug(PURPLE_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);
+							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);
+			bytes += qq_get16(&packet_seq, data + bytes);
+			bytes += qq_get8(&sub_type, data + bytes);
 			switch (sub_type)
 			{
 				case QQ_FILE_BASIC_INFO:
@@ -787,16 +776,16 @@
 					_qq_send_file_progess(gc);
 					break;
 				case QQ_FILE_DATA_INFO:
-					read_packet_dw(data, &cursor, len, &fragment_index);
+					bytes += qq_get32(&fragment_index, data + bytes);
 					_qq_update_send_progess(gc, fragment_index);
 					if (purple_xfer_is_completed(qd->xfer))
 						_qq_send_file_data_packet(gc, QQ_FILE_CMD_FILE_OP, QQ_FILE_EOF, 0, 0, NULL, 0);
-				/*	else
+					/*	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);
 					purple_xfer_set_completed(qd->xfer, TRUE);
 					break;
@@ -820,21 +809,21 @@
 
 void qq_process_recv_file(PurpleConnection *gc, guint8 *data, gint len)
 {
-	guint8 *cursor;
-	qq_file_header fh;
+	gint bytes;
+	guint8 tag;
 	qq_data *qd;
 
 	qd = (qq_data *) gc->proto_data;
 
-	cursor = data;
-	_qq_get_file_header(data, &cursor, len, &fh);
+	bytes = 0;
+	bytes += qq_get8(&tag, data + bytes);
 
-	switch (fh.tag) {
+	switch (tag) {
 		case QQ_FILE_CONTROL_PACKET_TAG:
-			_qq_process_recv_file_ctl_packet(gc, data, cursor, len, &fh);
+			_qq_process_recv_file_ctl_packet(gc, data + bytes, len - bytes);
 			break;
 		case QQ_FILE_DATA_PACKET_TAG:
-			_qq_process_recv_file_data(gc, data, cursor, len, fh.sender_uid);
+			_qq_process_recv_file_data(gc, data + bytes, len - bytes);
 			break;
 		default:
 			purple_debug(PURPLE_DEBUG_INFO, "QQ", "unknown packet tag");
--- a/libpurple/protocols/qq/group_conv.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/group_conv.c	Thu Nov 20 21:13:56 2008 +0000
@@ -27,8 +27,8 @@
 
 #include "conversation.h"
 
-#include "buddy_status.h"
 #include "group_conv.h"
+#include "buddy_list.h"
 #include "utils.h"
 
 /* show group conversation window */
@@ -99,7 +99,9 @@
 			list = list->next;
 		}
 
-		purple_conv_chat_add_users(PURPLE_CONV_CHAT(conv), names, NULL, flags, FALSE);
+		if (names != NULL && flags != NULL) {
+			purple_conv_chat_add_users(PURPLE_CONV_CHAT(conv), names, NULL, flags, FALSE);
+		}
 	}
 	/* clean up names */
 	while (names != NULL) {
--- a/libpurple/protocols/qq/group_find.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/group_find.c	Thu Nov 20 21:13:56 2008 +0000
@@ -138,6 +138,9 @@
 	group = NULL;
 	while (list != NULL) {
 		group = (qq_group *) list->data;
+		if (group->group_name_utf8 == NULL) {
+			continue;
+		}
 		if (!g_ascii_strcasecmp(purple_conversation_get_name(conv), group->group_name_utf8))
 			break;
 		list = list->next;
--- a/libpurple/protocols/qq/group_free.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/group_free.c	Thu Nov 20 21:13:56 2008 +0000
@@ -26,7 +26,7 @@
 
 #include "debug.h"
 
-#include "buddy_status.h"
+#include "buddy_list.h"
 #include "group_free.h"
 #include "group_network.h"
 
@@ -55,8 +55,10 @@
 {
 	g_return_if_fail(group != NULL);
 	qq_group_free_member(group);
+	g_free(group->my_status_desc);
 	g_free(group->group_name_utf8);
 	g_free(group->group_desc_utf8);
+	g_free(group->notice_utf8);
 	g_free(group);
 }
 
--- a/libpurple/protocols/qq/group_im.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/group_im.c	Thu Nov 20 21:13:56 2008 +0000
@@ -58,28 +58,28 @@
 void qq_send_packet_group_im(PurpleConnection *gc, qq_group *group, const gchar *msg)
 {
 	gint data_len, bytes;
-	guint8 *raw_data, *cursor, *send_im_tail;
+	guint8 *raw_data, *send_im_tail;
 	guint16 msg_len;
 	gchar *msg_filtered;
 
 	g_return_if_fail(group != NULL && msg != NULL);
 
 	msg_filtered = purple_markup_strip_html(msg);
-	purple_debug_info("QQ_MESG", "filterd qq qun mesg: %s\n", msg_filtered);
+	purple_debug_info("QQ_MESG", "Send qun mesg filterd: %s\n", msg_filtered);
 	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, (guint8 *) msg_filtered, msg_len);
+	bytes += qq_put8(raw_data + bytes, QQ_GROUP_CMD_SEND_MSG);
+	bytes += qq_put32(raw_data + bytes, group->internal_group_id);
+	bytes += qq_put16(raw_data + bytes, msg_len + QQ_SEND_IM_AFTER_MSG_LEN);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *) msg_filtered, msg_len);
 	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, send_im_tail, QQ_SEND_IM_AFTER_MSG_LEN);
+			FALSE, FALSE, FALSE,
+			QQ_SEND_IM_AFTER_MSG_LEN);
+	bytes += qq_putdata(raw_data + bytes, send_im_tail, QQ_SEND_IM_AFTER_MSG_LEN);
 	g_free(send_im_tail);
 	g_free(msg_filtered);
 
@@ -87,11 +87,11 @@
 		qq_send_group_cmd(gc, group, raw_data, data_len);
 	else
 		purple_debug(PURPLE_DEBUG_ERROR, "QQ",
-			   "Fail creating group_im packet, expect %d bytes, build %d bytes\n", data_len, bytes);
+				"Fail creating group_im packet, expect %d bytes, build %d bytes\n", data_len, bytes);
 }
 
 /* this is the ACK */
-void qq_process_group_cmd_im(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc) 
+void qq_process_group_cmd_im(guint8 *data, gint len, PurpleConnection *gc) 
 {
 	/* return should be the internal group id
 	 * but we have nothing to do with it */
@@ -99,29 +99,26 @@
 }
 
 /* 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, PurpleConnection *gc)
+void qq_process_recv_group_im_apply_join(guint8 *data, gint len, guint32 internal_group_id, PurpleConnection *gc)
 {
 	guint32 external_group_id, user_uid;
 	guint8 group_type;
 	gchar *reason_utf8, *msg, *reason;
 	group_member_opt *g;
 	gchar *nombre;
+	gint bytes = 0;
 
 	g_return_if_fail(internal_group_id > 0 && data != NULL && len > 0);
 
-	if (*cursor >= (data + len - 1)) {
-		purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Received group msg apply_join is empty\n");
-		return;
-	}
+	/* FIXME: check length here */
 
-	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);
+	bytes += qq_get32(&external_group_id, data + bytes);
+	bytes += qq_get8(&group_type, data + bytes);
+	bytes += qq_get32(&user_uid, data + bytes);
 
 	g_return_if_fail(external_group_id > 0 && user_uid > 0);
 
-	convert_as_pascal_string(*cursor, &reason_utf8, QQ_CHARSET_DEFAULT);
+	bytes += convert_as_pascal_string(data + bytes, &reason_utf8, QQ_CHARSET_DEFAULT);
 
 	msg = g_strdup_printf(_("User %d requested to join group %d"), user_uid, external_group_id);
 	reason = g_strdup_printf(_("Reason: %s"), reason_utf8);
@@ -134,17 +131,17 @@
 	nombre = uid_to_purple_name(user_uid);
 
 	purple_request_action(gc, _("QQ Qun Operation"),
-			    msg, reason,
-			    PURPLE_DEFAULT_ACTION_NONE,
-				purple_connection_get_account(gc), nombre, NULL,
-				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));
+			msg, reason,
+			PURPLE_DEFAULT_ACTION_NONE,
+			purple_connection_get_account(gc), nombre, NULL,
+			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(nombre);
 	g_free(reason);
@@ -153,31 +150,28 @@
 }
 
 /* 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, PurpleConnection *gc)
+void qq_process_recv_group_im_been_rejected(guint8 *data, gint len, guint32 internal_group_id, PurpleConnection *gc)
 {
 	guint32 external_group_id, admin_uid;
 	guint8 group_type;
 	gchar *reason_utf8, *msg, *reason;
 	qq_group *group;
+	gint bytes = 0;
 
 	g_return_if_fail(data != NULL && len > 0);
 
-	if (*cursor >= (data + len - 1)) {
-		purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Received group msg been_rejected is empty\n");
-		return;
-	}
+	/* FIXME: check length here */
 
-	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);
+	bytes += qq_get32(&external_group_id, data + bytes);
+	bytes += qq_get8(&group_type, data + bytes);
+	bytes += qq_get32(&admin_uid, data + bytes);
 
 	g_return_if_fail(external_group_id > 0 && admin_uid > 0);
 
-	convert_as_pascal_string(*cursor, &reason_utf8, QQ_CHARSET_DEFAULT);
+	bytes += convert_as_pascal_string(data + bytes, &reason_utf8, QQ_CHARSET_DEFAULT);
 
 	msg = g_strdup_printf
-	    (_("Your request to join group %d has been rejected by admin %d"), external_group_id, admin_uid);
+		(_("Your request to join group %d has been rejected by admin %d"), external_group_id, admin_uid);
 	reason = g_strdup_printf(_("Reason: %s"), reason_utf8);
 
 	purple_notify_warning(gc, _("QQ Qun Operation"), msg, reason);
@@ -194,31 +188,28 @@
 }
 
 /* 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, PurpleConnection *gc)
+void qq_process_recv_group_im_been_approved(guint8 *data, gint len, guint32 internal_group_id, PurpleConnection *gc)
 {
 	guint32 external_group_id, admin_uid;
 	guint8 group_type;
 	gchar *reason_utf8, *msg;
 	qq_group *group;
+	gint bytes = 0;
 
 	g_return_if_fail(data != NULL && len > 0);
 
-	if (*cursor >= (data + len - 1)) {
-		purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Received group msg been_approved is empty\n");
-		return;
-	}
+	/* FIXME: check length here */
 
-	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);
+	bytes += qq_get32(&external_group_id, data + bytes);
+	bytes += qq_get8(&group_type, data + bytes);
+	bytes += qq_get32(&admin_uid, data + bytes);
 
 	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);
+	bytes += convert_as_pascal_string(data + bytes, &reason_utf8, QQ_CHARSET_DEFAULT);
 
 	msg = g_strdup_printf
-	    (_("Your request to join group %d has been approved by admin %d"), external_group_id, admin_uid);
+		(_("Your request to join group %d has been approved by admin %d"), external_group_id, admin_uid);
 
 	purple_notify_warning(gc, _("QQ Qun Operation"), msg, NULL);
 
@@ -233,24 +224,21 @@
 }
 
 /* process the packet when removed from a group */
-void qq_process_recv_group_im_been_removed
-    (guint8 *data, guint8 **cursor, gint len, guint32 internal_group_id, PurpleConnection *gc)
+void qq_process_recv_group_im_been_removed(guint8 *data, gint len, guint32 internal_group_id, PurpleConnection *gc)
 {
 	guint32 external_group_id, uid;
 	guint8 group_type;
 	gchar *msg;
 	qq_group *group;
+	gint bytes = 0;
 
 	g_return_if_fail(data != NULL && len > 0);
 
-	if (*cursor >= (data + len - 1)) {
-		purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Received group msg been_removed is empty\n");
-		return;
-	}
+	/* FIXME: check length here */
 
-	read_packet_dw(data, cursor, len, &external_group_id);
-	read_packet_b(data, cursor, len, &group_type);
-	read_packet_dw(data, cursor, len, &uid);
+	bytes += qq_get32(&external_group_id, data + bytes);
+	bytes += qq_get8(&group_type, data + bytes);
+	bytes += qq_get32(&uid, data + bytes);
 
 	g_return_if_fail(external_group_id > 0 && uid > 0);
 
@@ -267,24 +255,21 @@
 }
 
 /* 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, PurpleConnection *gc)
+void qq_process_recv_group_im_been_added(guint8 *data, gint len, guint32 internal_group_id, PurpleConnection *gc)
 {
 	guint32 external_group_id, uid;
 	guint8 group_type;
 	qq_group *group;
 	gchar *msg;
+	gint bytes = 0;
 
 	g_return_if_fail(data != NULL && len > 0);
 
-	if (*cursor >= (data + len - 1)) {
-		purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Received group msg been_added is empty\n");
-		return;
-	}
+	/* FIXME: check length here */
 
-	read_packet_dw(data, cursor, len, &external_group_id);
-	read_packet_b(data, cursor, len, &group_type);
-	read_packet_dw(data, cursor, len, &uid);
+	bytes += qq_get32(&external_group_id, data + bytes);
+	bytes += qq_get8(&group_type, data + bytes);
+	bytes += qq_get32(&uid, data + bytes);
 
 	g_return_if_fail(external_group_id > 0 && uid > 0);
 
@@ -307,10 +292,9 @@
 }
 
 /* recv an IM from a group chat */
-void qq_process_recv_group_im(guint8 *data, guint8 **cursor, gint data_len, 
-		guint32 internal_group_id, PurpleConnection *gc, guint16 im_type)
+void qq_process_recv_group_im(guint8 *data, gint data_len, guint32 internal_group_id, PurpleConnection *gc, guint16 im_type)
 {
-	gchar *msg_with_purple_smiley, *msg_utf8_encoded, *im_src_name, *hex_dump;
+	gchar *msg_with_purple_smiley, *msg_utf8_encoded, *im_src_name;
 	guint16 unknown;
 	guint32 unknown4;
 	PurpleConversation *conv;
@@ -319,32 +303,30 @@
 	qq_group *group;
 	qq_recv_group_im *im_group;
 	gint skip_len;
+	gint bytes = 0;
 
 	g_return_if_fail(data != NULL && data_len > 0);
+
+	/* FIXME: check length here */
+
 	qd = (qq_data *) gc->proto_data;
 
-	hex_dump = hex_dump_to_str(*cursor, data_len - (*cursor - data));
-	purple_debug(PURPLE_DEBUG_INFO, "QQ", "group im hex dump\n%s\n", hex_dump);
-
-	if (*cursor >= (data + data_len - 1)) {
-		purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Received group im_group is empty\n");
-		return;
-	}
+	/* qq_hex_dump(PURPLE_DEBUG_INFO, "QQ", data, data_len, "group im hex dump"); */
 
 	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));
+	bytes += qq_get32(&(im_group->external_group_id), data + bytes);
+	bytes += qq_get8(&(im_group->group_type), data + bytes);
 
 	if(QQ_RECV_IM_TEMP_QUN_IM == im_type) {
-		read_packet_dw(data, cursor, data_len, &(internal_group_id));
+		bytes += qq_get32(&(internal_group_id), data + bytes);
 	}
 
-	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_time(data, cursor, data_len, &im_group->send_time);
-	read_packet_dw(data, cursor, data_len, &unknown4);	/* versionID */
+	bytes += qq_get32(&(im_group->member_uid), bytes + data);
+	bytes += qq_get16(&unknown, data + bytes);	/* 0x0001? */
+	bytes += qq_get16(&(im_group->msg_seq), data + bytes);
+	bytes += qq_getime(&im_group->send_time, data + bytes);
+	bytes += qq_get32(&unknown4, data + bytes);	/* versionID */
 	/*
 	 * length includes font_attr
 	 * this msg_len includes msg and font_attr
@@ -355,7 +337,7 @@
 	 * 3. font_attr
 	 */
 
-	read_packet_w(data, cursor, data_len, &(im_group->msg_len));
+	bytes += qq_get16(&(im_group->msg_len), data + bytes);
 	g_return_if_fail(im_group->msg_len > 0);
 
 	/*
@@ -371,14 +353,14 @@
 		skip_len = 10;
 	else
 		skip_len = 0;
-	*cursor += skip_len;
+	bytes += skip_len;
 
-	im_group->msg = g_strdup((gchar *) *cursor);
-	*cursor += strlen(im_group->msg) + 1;
+	im_group->msg = g_strdup((gchar *) data + bytes);
+	bytes += strlen(im_group->msg) + 1;
 	/* there might not be any font_attr, check it */
 	im_group->font_attr_len = im_group->msg_len - strlen(im_group->msg) - 1 - skip_len;
 	if (im_group->font_attr_len > 0)
-		im_group->font_attr = g_memdup(*cursor, im_group->font_attr_len);
+		im_group->font_attr = g_memdup(data + bytes, im_group->font_attr_len);
 	else
 		im_group->font_attr = NULL;
 
@@ -386,7 +368,7 @@
 	msg_with_purple_smiley = qq_smiley_to_purple(im_group->msg);
 	if (im_group->font_attr_len > 0)
 		msg_utf8_encoded = qq_encode_to_purple(im_group->font_attr,
-						     im_group->font_attr_len, msg_with_purple_smiley);
+				im_group->font_attr_len, msg_with_purple_smiley);
 	else
 		msg_utf8_encoded = qq_to_utf8(msg_with_purple_smiley, QQ_CHARSET_DEFAULT);
 
@@ -395,6 +377,9 @@
 
 	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, group->group_name_utf8, purple_connection_get_account(gc));
 	if (conv == NULL && purple_prefs_get_bool("/plugins/prpl/qq/prompt_group_msg_on_recv")) {
+		/* New conv should open, get group info*/
+		qq_send_cmd_group_get_group_info(gc, group);
+		
 		serv_got_joined_chat(gc, qd->channel++, group->group_name_utf8);
 		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, group->group_name_utf8, purple_connection_get_account(gc));
 	}
@@ -406,11 +391,10 @@
 		else
 			im_src_name = g_strdup(member->nickname);
 		serv_got_chat_in(gc,
-				 purple_conv_chat_get_id(PURPLE_CONV_CHAT
-						       (conv)), im_src_name, 0, msg_utf8_encoded, im_group->send_time);
+				purple_conv_chat_get_id(PURPLE_CONV_CHAT
+					(conv)), im_src_name, 0, msg_utf8_encoded, im_group->send_time);
 		g_free(im_src_name);
 	}
-	g_free(hex_dump);
 	g_free(msg_with_purple_smiley);
 	g_free(msg_utf8_encoded);
 	g_free(im_group->msg);
--- a/libpurple/protocols/qq/group_im.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/group_im.h	Thu Nov 20 21:13:56 2008 +0000
@@ -30,17 +30,31 @@
 #include "group.h"
 
 void qq_send_packet_group_im(PurpleConnection *gc, qq_group *group, const gchar *msg);
-void qq_process_group_cmd_im(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc);
-void qq_process_recv_group_im(guint8 *data, 
-		guint8 **cursor, gint data_len, guint32 internal_group_id, PurpleConnection *gc, guint16 im_type);
-void qq_process_recv_group_im_apply_join(guint8 *data,
-				    guint8 **cursor, gint len, guint32 internal_group_id, PurpleConnection *gc);
-void qq_process_recv_group_im_been_rejected(guint8 *data,
-				       guint8 **cursor, gint len, guint32 internal_group_id, PurpleConnection *gc);
-void qq_process_recv_group_im_been_approved(guint8 *data,
-				       guint8 **cursor, gint len, guint32 internal_group_id, PurpleConnection *gc);
-void qq_process_recv_group_im_been_removed(guint8 *data,
-				      guint8 **cursor, gint len, guint32 internal_group_id, PurpleConnection *gc);
-void qq_process_recv_group_im_been_added(guint8 *data,
-				    guint8 **cursor, gint len, guint32 internal_group_id, PurpleConnection *gc);
+
+/* void qq_process_group_cmd_im(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc); */
+void qq_process_group_cmd_im(guint8 *data, gint len, PurpleConnection *gc);
+
+/* void qq_process_recv_group_im(guint8 *data, guint8 **cursor, 
+ * gint data_len, guint32 internal_group_id, PurpleConnection *gc, guint16 im_type); */
+void qq_process_recv_group_im(guint8 *data, gint data_len, guint32 internal_group_id, PurpleConnection *gc, guint16 im_type);
+
+/* void qq_process_recv_group_im_apply_join(guint8 *data, guint8 **cursor, gint len, 
+ * guint32 internal_group_id, PurpleConnection *gc); */
+void qq_process_recv_group_im_apply_join(guint8 *data, gint len, guint32 internal_group_id, PurpleConnection *gc);
+
+/* void qq_process_recv_group_im_been_rejected(guint8 *data, guint8 **cursor, gint len, 
+ * guint32 internal_group_id, PurpleConnection *gc); */
+void qq_process_recv_group_im_been_rejected(guint8 *data, gint len, guint32 internal_group_id, PurpleConnection *gc);
+
+/* void qq_process_recv_group_im_been_approved(guint8 *data, guint8 **cursor, gint len, 
+ * guint32 internal_group_id, PurpleConnection *gc); */
+void qq_process_recv_group_im_been_approved(guint8 *data, gint len, guint32 internal_group_id, PurpleConnection *gc);
+
+/* void qq_process_recv_group_im_been_removed(guint8 *data, guint8 **cursor, gint len, 
+ * guint32 internal_group_id, PurpleConnection *gc); */
+void qq_process_recv_group_im_been_removed(guint8 *data, gint len, guint32 internal_group_id, PurpleConnection *gc);
+
+/* void qq_process_recv_group_im_been_added(guint8 *data,  guint8 **cursor, gint len, 
+ * guint32 internal_group_id, PurpleConnection *gc); */
+void qq_process_recv_group_im_been_added(guint8 *data, gint len, guint32 internal_group_id, PurpleConnection *gc);
 #endif
--- a/libpurple/protocols/qq/group_info.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/group_info.c	Thu Nov 20 21:13:56 2008 +0000
@@ -27,12 +27,11 @@
 #include "conversation.h"
 #include "debug.h"
 
-#include "buddy_status.h"
 #include "char_conv.h"
 #include "group_find.h"
 #include "group_internal.h"
 #include "group_info.h"
-#include "buddy_status.h"
+#include "buddy_list.h"
 #include "group_network.h"
 
 /* we check who needs to update member info every minutes
@@ -43,7 +42,7 @@
 {
 	g_return_val_if_fail(member != NULL, FALSE);
 	return (member->nickname == NULL) ||
-	    (time(NULL) - member->last_refresh) > QQ_GROUP_CHAT_REFRESH_NICKNAME_INTERNAL;
+		(time(NULL) - member->last_refresh) > QQ_GROUP_CHAT_REFRESH_NICKNAME_INTERNAL;
 }
 
 /* this is done when we receive the reply to get_online_members sub_cmd
@@ -65,100 +64,104 @@
 /* send packet to get detailed information of one group */
 void qq_send_cmd_group_get_group_info(PurpleConnection *gc, qq_group *group)
 {
-	guint8 *raw_data, *cursor;
-	gint bytes, data_len;
+	guint8 raw_data[16] = {0};
+	gint bytes = 0;
 
 	g_return_if_fail(group != NULL);
 
-	data_len = 5;
-	raw_data = g_newa(guint8, data_len);
-	cursor = raw_data;
+	bytes += qq_put8(raw_data + bytes, QQ_GROUP_CMD_GET_GROUP_INFO);
+	bytes += qq_put32(raw_data + bytes, group->internal_group_id);
 
-	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)
-		purple_debug(PURPLE_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_group_cmd(gc, group, raw_data, bytes);
 }
 
 /* send packet to get online group member, called by keep_alive */
+void qq_send_cmd_group_all_get_online_members(PurpleConnection *gc)
+{
+	qq_data *qd;
+	qq_group *group;
+	GList *list;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	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 */
+			qq_send_cmd_group_get_online_members(gc, group);
+
+		list = list->next;
+	}
+}
+
 void qq_send_cmd_group_get_online_members(PurpleConnection *gc, qq_group *group)
 {
-	guint8 *raw_data, *cursor;
-	gint bytes, data_len;
+	guint8 raw_data[16] = {0};
+	gint bytes = 0;
 
 	g_return_if_fail(group != NULL);
 
 	/* only get online members when conversation window is on */
 	if (NULL == purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,group->group_name_utf8, purple_connection_get_account(gc))) {
 		purple_debug(PURPLE_DEBUG_WARNING, "QQ",
-			   "Conv windows for \"%s\" is not on, do not get online members\n", group->group_name_utf8);
+				"Conversation \"%s\" is not open, ignore to get online members\n", group->group_name_utf8);
 		return;
 	}
 
-	data_len = 5;
-	raw_data = g_newa(guint8, data_len);
-	cursor = raw_data;
+	bytes += qq_put8(raw_data + bytes, QQ_GROUP_CMD_GET_ONLINE_MEMBER);
+	bytes += qq_put32(raw_data + bytes, group->internal_group_id);
 
-	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)
-		purple_debug(PURPLE_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_group_cmd(gc, group, raw_data, bytes);
 }
 
 /* send packet to get info for each group member */
 void qq_send_cmd_group_get_members_info(PurpleConnection *gc, qq_group *group)
 {
-	guint8 *raw_data, *cursor;
-	gint bytes, data_len, i;
+	guint8 *raw_data;
+	gint bytes, num, data_len;
 	GList *list;
 	qq_buddy *member;
 
 	g_return_if_fail(group != NULL);
-	for (i = 0, list = group->members; list != NULL; list = list->next) {
+	for (num = 0, list = group->members; list != NULL; list = list->next) {
 		member = (qq_buddy *) list->data;
 		if (_is_group_member_need_update_info(member))
-			i++;
+			num++;
 	}
 
-	if (i <= 0) {
-		purple_debug(PURPLE_DEBUG_INFO, "QQ", "No group member needs to to update info now.\n");
+	if (num <= 0) {
+		purple_debug(PURPLE_DEBUG_INFO, "QQ", "No group member info needs to be updated now.\n");
 		return;
 	}
 
-	data_len = 5 + 4 * i;
+	data_len = 5 + 4 * num;
 	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);
+	bytes += qq_put8(raw_data + bytes, QQ_GROUP_CMD_GET_MEMBER_INFO);
+	bytes += qq_put32(raw_data + bytes, 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);
+			bytes += qq_put32(raw_data + bytes, member->uid);
 		list = list->next;
 	}
 
-	if (bytes != data_len)
+	if (bytes != data_len) {
 		purple_debug(PURPLE_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);
+				"Fail create packet for %s\n", qq_group_cmd_get_desc(QQ_GROUP_CMD_GET_MEMBER_INFO));
+		return;
+	}
+
+	qq_send_group_cmd(gc, group, raw_data, bytes);
 }
 
-void qq_process_group_cmd_get_group_info(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc)
+void qq_process_group_cmd_get_group_info(guint8 *data, gint len, PurpleConnection *gc)
 {
 	qq_group *group;
 	qq_buddy *member;
@@ -168,17 +171,20 @@
 	guint16 unknown, max_members;
 	guint32 member_uid, internal_group_id, external_group_id;
 	GSList *pending_id;
-	gint pascal_len, i;
 	guint32 unknown4;
 	guint8 unknown1;
+	gint bytes, num;
+	gchar *notice;
 
 	g_return_if_fail(data != NULL && len > 0);
 	qd = (qq_data *) gc->proto_data;
 
-	read_packet_dw(data, cursor, len, &(internal_group_id));
+	bytes = 0;
+	bytes += qq_get32(&(internal_group_id), data + bytes);
 	g_return_if_fail(internal_group_id > 0);
-	read_packet_dw(data, cursor, len, &(external_group_id));
-	g_return_if_fail(internal_group_id > 0);
+
+	bytes += qq_get32(&(external_group_id), data + bytes);
+	g_return_if_fail(external_group_id > 0);
 
 	pending_id = qq_get_pending_id(qd->adding_groups_from_server, internal_group_id);
 	if (pending_id != NULL) {
@@ -189,45 +195,54 @@
 	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
 	g_return_if_fail(group != NULL);
 
-	read_packet_b(data, cursor, len, &(group->group_type));
-	read_packet_dw(data, cursor, len, &unknown4);	/* unknown 4 bytes */
-	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 */
-	read_packet_w(data, cursor, len, &unknown);	
-	read_packet_dw(data, cursor, len, &(group->group_category));
-	read_packet_w(data, cursor, len, &max_members);
-	read_packet_b(data, cursor, len, &unknown1);
-	read_packet_dw(data, cursor, len, &(unknown4));	/* versionID */
+	bytes += qq_get8(&(group->group_type), data + bytes);
+	bytes += qq_get32(&unknown4, data + bytes);	/* unknown 4 bytes */
+	bytes += qq_get32(&(group->creator_uid), data + bytes);
+	bytes += qq_get8(&(group->auth_type), data + bytes);
+	bytes += qq_get32(&unknown4, data + bytes);	/* oldCategory */
+	bytes += qq_get16(&unknown, data + bytes);	
+	bytes += qq_get32(&(group->group_category), data + bytes);
+	bytes += qq_get16(&max_members, data + bytes);
+	bytes += qq_get8(&unknown1, data + bytes);
+	/* the following, while Eva:
+	 * 4(unk), 4(verID), 1(nameLen), nameLen(qunNameContent), 1(0x00),
+	 * 2(qunNoticeLen), qunNoticeLen(qunNoticeContent, 1(qunDescLen),
+	 * qunDestLen(qunDestcontent)) */
+	bytes += qq_get8(&unknown1, data + bytes);
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "type=%u creatorid=%u category=%u maxmembers=%u\n",
+			group->group_type, group->creator_uid, group->group_category, max_members);
+	
+	/* strlen + <str content> */
+	bytes += convert_as_pascal_string(data + bytes, &(group->group_name_utf8), QQ_CHARSET_DEFAULT);
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "group \"%s\"\n", group->group_name_utf8); 
+	bytes += qq_get16(&unknown, data + bytes);	/* 0x0000 */
+	bytes += convert_as_pascal_string(data + bytes, &notice, QQ_CHARSET_DEFAULT);
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "notice \"%s\"\n", notice); 
+	bytes += convert_as_pascal_string(data + bytes, &(group->group_desc_utf8), QQ_CHARSET_DEFAULT);
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "group_desc \"%s\"\n", group->group_desc_utf8); 
 
-	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;
+	num = 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, &organization);
-		read_packet_b(data, cursor, len, &role);
+	while (bytes < len) {
+		bytes += qq_get32(&member_uid, data + bytes);
+		num++;
+		bytes += qq_get8(&organization, data + bytes);
+		bytes += qq_get8(&role, data + bytes);
 
+		/*
 		if(organization != 0 || role != 0) {
-			purple_debug(PURPLE_DEBUG_INFO, "QQ", "group member %d: organization=%d, role=%d\n", member_uid, organization, role);
+			purple_debug(PURPLE_DEBUG_INFO, "QQ_GRP", "%d, organization=%d, role=%d\n", member_uid, organization, role);
 		}
+		*/
 		member = qq_group_find_or_add_member(gc, group, member_uid);
 		if (member != NULL)
 			member->role = role;
 	}
-        if(*cursor > (data + len)) {
-                         purple_debug(PURPLE_DEBUG_ERROR, "QQ", "group_cmd_get_group_info: Dangerous error! maybe protocol changed, notify me!");
-        }
+	if(bytes > len) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "group_cmd_get_group_info: Dangerous error! maybe protocol changed, notify me!");
+	}
 
-	purple_debug(PURPLE_DEBUG_INFO, "QQ", "group \"%s\" has %d members\n", group->group_name_utf8, i);
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "group \"%s\" has %d members\n", group->group_name_utf8, num);
 
 	if (group->creator_uid == qd->uid)
 		group->my_status = QQ_GROUP_MEMBER_STATUS_IS_ADMIN;
@@ -237,33 +252,37 @@
 	purple_conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, 
 			group->group_name_utf8, purple_connection_get_account(gc));
 	if(NULL == purple_conv) {
-                purple_debug(PURPLE_DEBUG_WARNING, "QQ",
-                           "Conv windows for \"%s\" is not on, do not set topic\n", group->group_name_utf8);
+		purple_debug(PURPLE_DEBUG_WARNING, "QQ",
+				"Conversation \"%s\" is not open, do not set topic\n", group->group_name_utf8);
+		return;
 	}
-	else {
-		purple_conv_chat_set_topic(PURPLE_CONV_CHAT(purple_conv), NULL, group->notice_utf8);
-	}
+
+	/* filter \r\n in notice */
+	qq_filter_str(notice);
+	group->notice_utf8 = strdup(notice);
+	g_free(notice);
+	
+	purple_conv_chat_set_topic(PURPLE_CONV_CHAT(purple_conv), NULL, group->notice_utf8);
 }
 
-void qq_process_group_cmd_get_online_members(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc)
+void qq_process_group_cmd_get_online_members(guint8 *data, gint len, PurpleConnection *gc)
 {
 	guint32 internal_group_id, member_uid;
 	guint8 unknown;
-	gint bytes, i;
+	gint bytes, num;
 	qq_group *group;
 	qq_buddy *member;
 
 	g_return_if_fail(data != NULL && len > 0);
 
-	if (data + len - *cursor < 4) {
+	if (len <= 3) {
 		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Invalid group online member reply, discard it!\n");
 		return;
 	}
 
 	bytes = 0;
-	i = 0;
-	bytes += read_packet_dw(data, cursor, len, &internal_group_id);
-	bytes += read_packet_b(data, cursor, len, &unknown);	/* 0x3c ?? */
+	bytes += qq_get32(&internal_group_id, data + bytes);
+	bytes += qq_get8(&unknown, data + bytes);	/* 0x3c ?? */
 	g_return_if_fail(internal_group_id > 0);
 
 	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
@@ -275,61 +294,77 @@
 
 	/* 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++;
+	num = 0;
+	while (bytes < len) {
+		bytes += qq_get32(&member_uid, data + bytes);
+		num++;
 		member = qq_group_find_or_add_member(gc, group, member_uid);
 		if (member != NULL)
 			member->status = QQ_BUDDY_ONLINE_NORMAL;
 	}
-        if(*cursor > (data + len)) {
-                         purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
-					 "group_cmd_get_online_members: Dangerous error! maybe protocol changed, notify developers!");
-        }
+	if(bytes > len) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
+				"group_cmd_get_online_members: Dangerous error! maybe protocol changed, notify developers!");
+	}
 
-	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Group \"%s\" has %d online members\n", group->group_name_utf8, i);
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Group \"%s\" has %d online members\n", group->group_name_utf8, num);
 }
 
 /* process the reply to get_members_info packet */
-void qq_process_group_cmd_get_members_info(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc)
+void qq_process_group_cmd_get_members_info(guint8 *data, gint len, PurpleConnection *gc)
 {
+	gint bytes;
+	gint num;
 	guint32 internal_group_id, member_uid;
 	guint16 unknown;
-	gint pascal_len, i;
 	qq_group *group;
 	qq_buddy *member;
+	gchar *nick;
 
 	g_return_if_fail(data != NULL && len > 0);
 
-	read_packet_dw(data, cursor, len, &internal_group_id);
+	bytes = 0;
+	bytes += qq_get32(&internal_group_id, data + bytes);
 	g_return_if_fail(internal_group_id > 0);
 
 	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
 	g_return_if_fail(group != NULL);
 
-	i = 0;
+	num = 0;
 	/* now starts the member info, as get buddy list reply */
-	while (*cursor < data + len) {
-		read_packet_dw(data, cursor, len, &member_uid);
+	while (bytes < len) {
+		bytes += qq_get32(&member_uid, data + bytes);
 		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_w(data, cursor, len, &(member->face));
-		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));
+		num++;
+		bytes += qq_get16(&(member->face), data + bytes);
+		bytes += qq_get8(&(member->age), data + bytes);
+		bytes += qq_get8(&(member->gender), data + bytes);
+		bytes += convert_as_pascal_string(data + bytes, &nick, QQ_CHARSET_DEFAULT);
+		bytes += qq_get16(&unknown, data + bytes);
+		bytes += qq_get8(&(member->ext_flag), data + bytes);
+		bytes += qq_get8(&(member->comm_flag), data + bytes);
+
+		/* filter \r\n in nick */
+		qq_filter_str(nick);
+		member->nickname = g_strdup(nick);
+		g_free(nick);
+		
+		/*
+		if (QQ_DEBUG) {
+			purple_debug(PURPLE_DEBUG_INFO, "QQ",
+					"member [%09d]: ext_flag=0x%02x, comm_flag=0x%02x, nick=%s\n",
+					member_uid, member->ext_flag, member->comm_flag, member->nickname);
+		}
+		*/
 
 		member->last_refresh = time(NULL);
 	}
-        if(*cursor > (data + len)) {
-                         purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
-					 "group_cmd_get_members_info: Dangerous error! maybe protocol changed, notify developers!");
-        }
-	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Group \"%s\" obtained %d member info\n", group->group_name_utf8, i);
+	if(bytes > len) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
+				"group_cmd_get_members_info: Dangerous error! maybe protocol changed, notify developers!");
+	}
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Group \"%s\" obtained %d member info\n", group->group_name_utf8, num);
 }
--- a/libpurple/protocols/qq/group_info.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/group_info.h	Thu Nov 20 21:13:56 2008 +0000
@@ -31,9 +31,12 @@
 
 void qq_send_cmd_group_get_group_info(PurpleConnection *gc, qq_group *group);
 void qq_send_cmd_group_get_online_members(PurpleConnection *gc, qq_group *group);
+void qq_send_cmd_group_all_get_online_members(PurpleConnection *gc);
+
 void qq_send_cmd_group_get_members_info(PurpleConnection *gc, qq_group *group);
-void qq_process_group_cmd_get_group_info(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc);
-void qq_process_group_cmd_get_online_members(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc);
-void qq_process_group_cmd_get_members_info(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc);
+
+void qq_process_group_cmd_get_group_info(guint8 *data, gint len, PurpleConnection *gc);
+void qq_process_group_cmd_get_online_members(guint8 *data, gint len, PurpleConnection *gc);
+void qq_process_group_cmd_get_members_info(guint8 *data, gint len, PurpleConnection *gc);
 
 #endif
--- a/libpurple/protocols/qq/group_join.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/group_join.c	Thu Nov 20 21:13:56 2008 +0000
@@ -64,8 +64,8 @@
 /* send packet to join a group without auth */
 void qq_send_cmd_group_join_group(PurpleConnection *gc, qq_group *group)
 {
-	guint8 *raw_data, *cursor;
-	gint bytes, data_len;
+	guint8 raw_data[16] = {0};
+	gint bytes = 0;
 
 	g_return_if_fail(group != NULL);
 
@@ -86,19 +86,11 @@
 		break;
 	}
 
-	data_len = 5;
-	raw_data = g_newa(guint8, data_len);
-	cursor = raw_data;
+	bytes = 0;
+	bytes += qq_put8(raw_data + bytes, QQ_GROUP_CMD_JOIN_GROUP);
+	bytes += qq_put32(raw_data + bytes, group->internal_group_id);
 
-	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)
-		purple_debug(PURPLE_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_group_cmd(gc, group, raw_data, bytes);
 }
 
 static void _qq_group_join_auth_with_gc_and_id(gc_and_uid *g, const gchar *reason_utf8)
@@ -145,7 +137,7 @@
 
 void qq_send_cmd_group_auth(PurpleConnection *gc, qq_group *group, guint8 opt, guint32 uid, const gchar *reason_utf8)
 {
-	guint8 *raw_data, *cursor;
+	guint8 *raw_data;
 	gchar *reason_qq;
 	gint bytes, data_len;
 
@@ -164,50 +156,42 @@
 
 	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, (guint8 *) reason_qq, strlen(reason_qq));
+	bytes += qq_put8(raw_data + bytes, QQ_GROUP_CMD_JOIN_GROUP_AUTH);
+	bytes += qq_put32(raw_data + bytes, group->internal_group_id);
+	bytes += qq_put8(raw_data + bytes, opt);
+	bytes += qq_put32(raw_data + bytes, uid);
+	bytes += qq_put8(raw_data + bytes, strlen(reason_qq));
+	bytes += qq_putdata(raw_data + bytes, (guint8 *) reason_qq, strlen(reason_qq));
 
-	if (bytes != data_len)
+	if (bytes != data_len) {
 		purple_debug(PURPLE_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);
+		return;
+	}
+
+	qq_send_group_cmd(gc, group, raw_data, data_len);
 }
 
 /* send a packet to exit a group */
 void qq_send_cmd_group_exit_group(PurpleConnection *gc, qq_group *group)
 {
-	guint8 *raw_data, *cursor;
-	gint bytes, data_len;
+	guint8 raw_data[16] = {0};
+	gint bytes = 0;
 
 	g_return_if_fail(group != NULL);
 
-	data_len = 5;
-	raw_data = g_newa(guint8, data_len);
-	cursor = raw_data;
+	bytes += qq_put8(raw_data + bytes, QQ_GROUP_CMD_EXIT_GROUP);
+	bytes += qq_put32(raw_data + bytes, group->internal_group_id);
 
-	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)
-		purple_debug(PURPLE_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_group_cmd(gc, group, raw_data, bytes);
 }
 
 /* If comes here, cmd is OK already */
-void qq_process_group_cmd_exit_group(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc)
+void qq_process_group_cmd_exit_group(guint8 *data, gint len, PurpleConnection *gc)
 {
-	gint bytes, expected_bytes;
+	gint bytes;
 	guint32 internal_group_id;
 	PurpleChat *chat;
 	qq_group *group;
@@ -216,96 +200,94 @@
 	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 (len < 4) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ",
+			   "Invalid exit group reply, expect %d bytes, read %d bytes\n", 4, len);
+		return;
+	}
 
-	if (bytes == expected_bytes) {
-		group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
-		if (group != NULL) {
-			chat =
-			    purple_blist_find_chat
+	bytes = 0;
+	bytes += qq_get32(&internal_group_id, data + bytes);
+
+	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
+	if (group != NULL) {
+		chat = purple_blist_find_chat
 			    (purple_connection_get_account(gc), g_strdup_printf("%d", group->external_group_id));
-			if (chat != NULL)
-				purple_blist_remove_chat(chat);
-			qq_group_delete_internal_record(qd, internal_group_id);
-		}
-		purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully left the group"), NULL);
-	} else {
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ",
-			   "Invalid exit group reply, expect %d bytes, read %d bytes\n", expected_bytes, bytes);
+		if (chat != NULL)
+			purple_blist_remove_chat(chat);
+		qq_group_delete_internal_record(qd, internal_group_id);
 	}
+	purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully left the group"), NULL);
 }
 
 /* Process the reply to group_auth subcmd */
-void qq_process_group_cmd_join_group_auth(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc)
+void qq_process_group_cmd_join_group_auth(guint8 *data, gint len, PurpleConnection *gc)
 {
-	gint bytes, expected_bytes;
+	gint bytes;
 	guint32 internal_group_id;
 	qq_data *qd;
 
 	g_return_if_fail(data != NULL && len > 0);
 	qd = (qq_data *) gc->proto_data;
 
+	if (len < 4) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ",
+			   "Invalid join group reply, expect %d bytes, read %d bytes\n", 4, len);
+		return;
+	}
 	bytes = 0;
-	expected_bytes = 4;
-	bytes += read_packet_dw(data, cursor, len, &internal_group_id);
+	bytes += qq_get32(&internal_group_id, data + bytes);
 	g_return_if_fail(internal_group_id > 0);
 
-	if (bytes == expected_bytes)
-		purple_notify_info
-		    (gc, _("QQ Group Auth"),
+	purple_notify_info(gc, _("QQ Group Auth"),
 		     _("Your authorization request has been accepted by the QQ server"), NULL);
-	else
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ",
-			   "Invalid join group reply, expect %d bytes, read %d bytes\n", expected_bytes, bytes);
 }
 
 /* process group cmd reply "join group" */
-void qq_process_group_cmd_join_group(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc)
+void qq_process_group_cmd_join_group(guint8 *data, gint len, PurpleConnection *gc)
 {
-	gint bytes, expected_bytes;
+	gint bytes;
 	guint32 internal_group_id;
 	guint8 reply;
 	qq_group *group;
 
 	g_return_if_fail(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) {
+	if (len < 5) {
 		purple_debug(PURPLE_DEBUG_ERROR, "QQ",
-			   "Invalid join group reply, expect %d bytes, read %d bytes\n", expected_bytes, bytes);
+			   "Invalid join group reply, expect %d bytes, read %d bytes\n", 5, len);
 		return;
-	} else {		/* join group OK */
-		group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
-		/* need to check if group is NULL or not. */
-		g_return_if_fail(group != NULL);
-		switch (reply) {
-		case QQ_GROUP_JOIN_OK:
-			purple_debug(PURPLE_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 shown before getting online members */
-			qq_group_conv_show_window(gc, group);
-			qq_send_cmd_group_get_group_info(gc, group);
-			break;
-		case QQ_GROUP_JOIN_NEED_AUTH:
-			purple_debug(PURPLE_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:
-			purple_debug(PURPLE_DEBUG_INFO, "QQ",
-				   "Error joining group [%d] %s, unknown reply: 0x%02x\n",
-				   group->external_group_id, group->group_name_utf8, reply);
-		}
+	}
+	
+	bytes = 0;
+	bytes += qq_get32(&internal_group_id, data + bytes);
+	bytes += qq_get8(&reply, data + bytes);
+
+	/* join group OK */
+	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
+	/* need to check if group is NULL or not. */
+	g_return_if_fail(group != NULL);
+	switch (reply) {
+	case QQ_GROUP_JOIN_OK:
+		purple_debug(PURPLE_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 shown before getting online members */
+		qq_group_conv_show_window(gc, group);
+		qq_send_cmd_group_get_group_info(gc, group);
+		break;
+	case QQ_GROUP_JOIN_NEED_AUTH:
+		purple_debug(PURPLE_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:
+		purple_debug(PURPLE_DEBUG_INFO, "QQ",
+			   "Error joining group [%d] %s, unknown reply: 0x%02x\n",
+			   group->external_group_id, group->group_name_utf8, reply);
 	}
 }
 
--- a/libpurple/protocols/qq/group_join.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/group_join.h	Thu Nov 20 21:13:56 2008 +0000
@@ -46,8 +46,8 @@
 void qq_send_cmd_group_join_group(PurpleConnection *gc, qq_group *group);
 void qq_group_exit(PurpleConnection *gc, GHashTable *data);
 void qq_send_cmd_group_exit_group(PurpleConnection *gc, qq_group *group);
-void qq_process_group_cmd_exit_group(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc);
-void qq_process_group_cmd_join_group_auth(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc);
-void qq_process_group_cmd_join_group(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc);
+void qq_process_group_cmd_exit_group(guint8 *data, gint len, PurpleConnection *gc);
+void qq_process_group_cmd_join_group_auth(guint8 *data, gint len, PurpleConnection *gc);
+void qq_process_group_cmd_join_group(guint8 *data, gint len, PurpleConnection *gc);
 
 #endif
--- a/libpurple/protocols/qq/group_network.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/group_network.c	Thu Nov 20 21:13:56 2008 +0000
@@ -39,7 +39,7 @@
 #include "group_opt.h"
 #include "group_search.h"
 #include "header_info.h"
-#include "send_core.h"
+#include "qq_network.h"
 #include "utils.h"
 
 enum {
@@ -75,18 +75,42 @@
 		return "QQ_GROUP_CMD_GET_ONLINE_MEMBER";
 	case QQ_GROUP_CMD_GET_MEMBER_INFO:
 		return "QQ_GROUP_CMD_GET_MEMBER_INFO";
+	case QQ_GROUP_CMD_MODIFY_CARD:
+		return "QQ_GROUP_CMD_MODIFY_CARD";
+	case QQ_GROUP_CMD_REQUEST_ALL_REALNAMES:
+		return "QQ_GROUP_CMD_REQUEST_ALL_REALNAMES";
+	case QQ_GROUP_CMD_REQUEST_CARD:
+		return "QQ_GROUP_CMD_REQUEST_CARD";
+	case QQ_GROUP_CMD_SEND_IM_EX:
+		return "QQ_GROUP_CMD_SEND_IM_EX";
+	case QQ_GROUP_CMD_ADMIN:
+		return "QQ_GROUP_CMD_ADMIN";
+	case QQ_GROUP_CMD_TRANSFER:
+		return "QQ_GROUP_CMD_TRANSFER";
+	case QQ_GROUP_CMD_CREATE_TEMP_QUN:
+		return "QQ_GROUP_CMD_CREATE_TEMP_QUN";
+	case QQ_GROUP_CMD_MODIFY_TEMP_QUN_MEMBER:
+		return "QQ_GROUP_CMD_MODIFY_TEMP_QUN_MEMBER";
+	case QQ_GROUP_CMD_EXIT_TEMP_QUN:
+		return "QQ_GROUP_CMD_EXIT_TEMP_QUN";
+	case QQ_GROUP_CMD_GET_TEMP_QUN_INFO:
+		return "QQ_GROUP_CMD_GET_TEMP_QUN_INFO";
+	case QQ_GROUP_CMD_SEND_TEMP_QUN_IM:
+		return "QQ_GROUP_CMD_SEND_TEMP_QUN_IM";
+	case QQ_GROUP_CMD_GET_TEMP_QUN_MEMBERS:
+		return "QQ_GROUP_CMD_GET_TEMP_QUN_MEMBERS";
 	default:
 		return "Unknown QQ Group Command";
 	}
 }
 
 /* default process of reply error */
-static void _qq_process_group_cmd_reply_error_default(guint8 reply, guint8 *cursor, gint len, PurpleConnection *gc)
+static void _qq_process_group_cmd_reply_error_default(guint8 reply, guint8 *data, gint len, PurpleConnection *gc)
 {
 	gchar *msg, *msg_utf8;
-	g_return_if_fail(cursor != NULL && len > 0);
+	g_return_if_fail(data != NULL && len > 0);
 
-	msg = g_strndup((gchar *) cursor, len);	/* it will append 0x00 */
+	msg = g_strndup((gchar *) data, 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);
@@ -96,14 +120,13 @@
 }
 
 /* default process, dump only */
-static void _qq_process_group_cmd_reply_default(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc)
+static void _qq_process_group_cmd_reply_default(guint8 *data, gint len, PurpleConnection *gc)
 {
-	gchar *hex_dump;
 	g_return_if_fail(data != NULL && len > 0);
 
-	hex_dump = hex_dump_to_str(data, len);
-	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Dump unprocessed group cmd reply:\n%s", hex_dump);
-	g_free(hex_dump);
+	qq_hex_dump(PURPLE_DEBUG_INFO, "QQ",
+		data, len, 
+		"Dump unprocessed group cmd reply:");
 }
 
 /* The lower layer command of send group cmd */
@@ -116,7 +139,7 @@
 
 	qd = (qq_data *) gc->proto_data;
 
-	qq_send_cmd(gc, QQ_CMD_GROUP_CMD, TRUE, 0, TRUE, raw_data, data_len);
+	qq_send_cmd(qd, QQ_CMD_GROUP_CMD, raw_data, data_len);
 
 	p = g_new0(group_packet, 1);
 
@@ -136,7 +159,7 @@
 	qq_data *qd;
 	gint len, bytes;
 	guint32 internal_group_id;
-	guint8 *data, *cursor, sub_cmd, reply;
+	guint8 *data, sub_cmd, reply;
 
 	g_return_if_fail(buf != NULL && buf_len != 0);
 
@@ -149,102 +172,101 @@
 		return;
 	}
 
-	if (qq_decrypt(buf, buf_len, qd->session_key, data, &len)) {
-		if (len <= 2) {
-			purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Group cmd reply is too short, only %d bytes\n", len);
-			return;
-		}
+	if ( !qq_decrypt(buf, buf_len, qd->session_key, data, &len) ) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt group cmd reply\n");
+		return;
+	}
 
-		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_id(gc, internal_group_id, QQ_INTERNAL_ID);
+	if (len <= 2) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Group cmd reply is too short, only %d bytes\n", len);
+		return;
+	}
 
-		if (reply != QQ_GROUP_CMD_REPLY_OK) {
-			purple_debug(PURPLE_DEBUG_WARNING, "QQ",
-				   "Group cmd reply says cmd %s fails\n", qq_group_cmd_get_desc(sub_cmd));
+	bytes = 0;
+	bytes += qq_get8(&sub_cmd, data + bytes);
+	bytes += qq_get8(&reply, data + bytes);
 
-			if (group != NULL)
-				qq_set_pending_id(&qd->joining_groups, group->external_group_id, FALSE);
+	group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
 
-			switch (reply) {	/* this should be all errors */
-			case QQ_GROUP_CMD_REPLY_NOT_MEMBER:
-				if (group != NULL) {
-					purple_debug(PURPLE_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);
-				}
-				break;
-			case QQ_GROUP_CMD_REPLY_SEARCH_ERROR:
-				if (qd->roomlist != NULL) {
-					if (purple_roomlist_get_in_progress(qd->roomlist))
-						purple_roomlist_set_in_progress(qd->roomlist, FALSE);
-				}
-				_qq_process_group_cmd_reply_error_default(reply, cursor, len - bytes, gc);
-				break;
-			default:
-				_qq_process_group_cmd_reply_error_default(reply, cursor, len - bytes, gc);
-			}
-			return;
-		}
+	if (reply != QQ_GROUP_CMD_REPLY_OK) {
+		purple_debug(PURPLE_DEBUG_WARNING, "QQ",
+			   "Group cmd reply says cmd %s fails\n", qq_group_cmd_get_desc(sub_cmd));
+
+		if (group != NULL)
+			qq_set_pending_id(&qd->joining_groups, group->external_group_id, FALSE);
 
-		/* seems 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);
+		switch (reply) {	/* this should be all errors */
+		case QQ_GROUP_CMD_REPLY_NOT_MEMBER:
 			if (group != NULL) {
-				qq_send_cmd_group_get_members_info(gc, group);
-				qq_send_cmd_group_get_online_members(gc, group);
+				purple_debug(PURPLE_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);
 			}
 			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_members(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_members_info(data, &cursor, len, gc);
-			if (group != NULL)
-				qq_group_conv_refresh_online_member(gc, group);
+		case QQ_GROUP_CMD_REPLY_SEARCH_ERROR:
+			if (qd->roomlist != NULL) {
+				if (purple_roomlist_get_in_progress(qd->roomlist))
+					purple_roomlist_set_in_progress(qd->roomlist, FALSE);
+			}
+			_qq_process_group_cmd_reply_error_default(reply, data + bytes, len - bytes, gc);
 			break;
 		default:
-			purple_debug(PURPLE_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);
+			_qq_process_group_cmd_reply_error_default(reply, data + bytes, len - bytes, gc);
+		}
+		return;
+	}
+
+	/* seems 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 + bytes, len - bytes, gc);
+		if (group != NULL) {
+			qq_send_cmd_group_get_members_info(gc, group);
+			qq_send_cmd_group_get_online_members(gc, group);
 		}
-
-	} else {
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt group cmd reply\n");
+		break;
+	case QQ_GROUP_CMD_CREATE_GROUP:
+		qq_group_process_create_group_reply(data + bytes, len - bytes, gc);
+		break;
+	case QQ_GROUP_CMD_MODIFY_GROUP_INFO:
+		qq_group_process_modify_info_reply(data + bytes, len - bytes, gc);
+		break;
+	case QQ_GROUP_CMD_MEMBER_OPT:
+		qq_group_process_modify_members_reply(data + bytes, len - bytes, gc);
+		break;
+	case QQ_GROUP_CMD_ACTIVATE_GROUP:
+		qq_group_process_activate_group_reply(data + bytes, len - bytes, gc);
+		break;
+	case QQ_GROUP_CMD_SEARCH_GROUP:
+		qq_process_group_cmd_search_group(data + bytes, len - bytes, gc);
+		break;
+	case QQ_GROUP_CMD_JOIN_GROUP:
+		qq_process_group_cmd_join_group(data + bytes, len - bytes, gc);
+		break;
+	case QQ_GROUP_CMD_JOIN_GROUP_AUTH:
+		qq_process_group_cmd_join_group_auth(data + bytes, len - bytes, gc);
+		break;
+	case QQ_GROUP_CMD_EXIT_GROUP:
+		qq_process_group_cmd_exit_group(data + bytes, len - bytes, gc);
+		break;
+	case QQ_GROUP_CMD_SEND_MSG:
+		qq_process_group_cmd_im(data + bytes, len - bytes, gc);
+		break;
+	case QQ_GROUP_CMD_GET_ONLINE_MEMBER:
+		qq_process_group_cmd_get_online_members(data + bytes, len - bytes, 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_members_info(data + bytes, len - bytes, gc);
+		if (group != NULL)
+			qq_group_conv_refresh_online_member(gc, group);
+		break;
+	default:
+		purple_debug(PURPLE_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 + bytes, len, gc);
 	}
 }
--- a/libpurple/protocols/qq/group_network.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/group_network.h	Thu Nov 20 21:13:56 2008 +0000
@@ -42,7 +42,20 @@
 	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_GET_MEMBER_INFO = 0x0c,
+
+	QQ_GROUP_CMD_MODIFY_CARD = 0x0E,
+	QQ_GROUP_CMD_REQUEST_ALL_REALNAMES = 0x0F,
+	QQ_GROUP_CMD_REQUEST_CARD = 0x10,
+	QQ_GROUP_CMD_SEND_IM_EX = 0x1A,
+	QQ_GROUP_CMD_ADMIN = 0x1B,
+	QQ_GROUP_CMD_TRANSFER = 0x1C,
+	QQ_GROUP_CMD_CREATE_TEMP_QUN = 0x30,
+	QQ_GROUP_CMD_MODIFY_TEMP_QUN_MEMBER = 0x31,
+	QQ_GROUP_CMD_EXIT_TEMP_QUN = 0x32,
+	QQ_GROUP_CMD_GET_TEMP_QUN_INFO = 0x33,
+	QQ_GROUP_CMD_SEND_TEMP_QUN_IM = 0x35,
+	QQ_GROUP_CMD_GET_TEMP_QUN_MEMBERS = 0x37,
 } qq_group_cmd;
 
 typedef struct _group_packet {
--- a/libpurple/protocols/qq/group_opt.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/group_opt.c	Thu Nov 20 21:13:56 2008 +0000
@@ -57,22 +57,24 @@
 
 static void _qq_group_member_opt(PurpleConnection *gc, qq_group *group, gint operation, guint32 *members)
 {
-	guint8 *data, *cursor;
+	guint8 *data;
 	gint i, count, data_len;
+	gint bytes;
 	g_return_if_fail(members != NULL);
 
-	for (i = 0; members[i] != 0xffffffff; i++) {;
+	for (count = 0; members[count] != 0xffffffff; count++) {;
 	}
-	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);
+	
+	bytes = 0;
+	bytes += qq_put8(data + bytes, QQ_GROUP_CMD_MEMBER_OPT);
+	bytes += qq_put32(data + bytes, group->internal_group_id);
+	bytes += qq_put8(data + bytes, operation);
 	for (i = 0; i < count; i++)
-		create_packet_dw(data, &cursor, members[i]);
-	qq_send_group_cmd(gc, group, data, data_len);
+		bytes += qq_put32(data + bytes, members[i]);
+
+	qq_send_group_cmd(gc, group, data, bytes);
 }
 
 static void _qq_group_do_nothing_with_struct(group_member_opt *g)
@@ -97,11 +99,11 @@
 
 	qq_send_packet_get_info(g->gc, g->member, TRUE);	/* we want to see window */
 	purple_request_action(g->gc, NULL, _("Do you want to approve the request?"), "",
-					PURPLE_DEFAULT_ACTION_NONE,
-					purple_connection_get_account(g->gc), NULL, NULL,
-					g, 2,
-					_("Reject"), G_CALLBACK(qq_group_reject_application_with_struct),
-					_("Approve"), G_CALLBACK(qq_group_approve_application_with_struct));
+				PURPLE_DEFAULT_ACTION_NONE,
+				purple_connection_get_account(g->gc), NULL, NULL,
+				g, 2,
+				_("Reject"), G_CALLBACK(qq_group_reject_application_with_struct),
+				_("Approve"), G_CALLBACK(qq_group_approve_application_with_struct));
 }
 
 void qq_group_reject_application_with_struct(group_member_opt *g)
@@ -193,13 +195,15 @@
 		_qq_group_member_opt(gc, group, QQ_GROUP_MEMBER_ADD, add_members);
 }
 
-void qq_group_process_modify_members_reply(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc)
+void qq_group_process_modify_members_reply(guint8 *data, gint len, PurpleConnection *gc)
 {
+	gint bytes;
 	guint32 internal_group_id;
 	qq_group *group;
 	g_return_if_fail(data != NULL);
 
-	read_packet_dw(data, cursor, len, &internal_group_id);
+	bytes = 0;
+	bytes += qq_get32(&internal_group_id, data + bytes);
 	g_return_if_fail(internal_group_id > 0);
 
 	/* we should have its info locally */
@@ -213,8 +217,9 @@
 
 void qq_group_modify_info(PurpleConnection *gc, qq_group *group)
 {
-	gint data_len, data_written;
-	guint8 *data, *cursor;
+	guint8 *data;
+	gint data_len;
+	gint bytes;
 	gchar *group_name, *group_desc, *notice;
 
 	g_return_if_fail(group != NULL);
@@ -228,47 +233,50 @@
 	    + 1 + strlen(notice);
 
 	data = g_newa(guint8, data_len);
-	cursor = data;
-	data_written = 0;
+	bytes = 0;
 	/* 000-000 */
-	data_written += create_packet_b(data, &cursor, QQ_GROUP_CMD_MODIFY_GROUP_INFO);
+	bytes += qq_put8(data + bytes, QQ_GROUP_CMD_MODIFY_GROUP_INFO);
 	/* 001-004 */
-	data_written += create_packet_dw(data, &cursor, group->internal_group_id);
+	bytes += qq_put32(data + bytes, group->internal_group_id);
 	/* 005-005 */
-	data_written += create_packet_b(data, &cursor, 0x01);
+	bytes += qq_put8(data + bytes, 0x01);
 	/* 006-006 */
-	data_written += create_packet_b(data, &cursor, group->auth_type);
+	bytes += qq_put8(data + bytes, group->auth_type);
 	/* 007-008 */
-	data_written += create_packet_w(data, &cursor, 0x0000);
+	bytes += qq_put16(data + bytes, 0x0000);
 	/* 009-010 */
-	data_written += create_packet_w(data, &cursor, group->group_category);
+	bytes += qq_put16(data + bytes, group->group_category);
 
-	data_written += create_packet_b(data, &cursor, strlen(group_name));
-	data_written += create_packet_data(data, &cursor, (guint8 *) group_name, strlen(group_name));
+	bytes += qq_put8(data + bytes, strlen(group_name));
+	bytes += qq_putdata(data + bytes, (guint8 *) group_name, strlen(group_name));
 
-	data_written += create_packet_w(data, &cursor, 0x0000);
+	bytes += qq_put16(data + bytes, 0x0000);
 
-	data_written += create_packet_b(data, &cursor, strlen(notice));
-	data_written += create_packet_data(data, &cursor, (guint8 *) notice, strlen(notice));
+	bytes += qq_put8(data + bytes, strlen(notice));
+	bytes += qq_putdata(data+ bytes, (guint8 *) notice, strlen(notice));
 
-	data_written += create_packet_b(data, &cursor, strlen(group_desc));
-	data_written += create_packet_data(data, &cursor, (guint8 *) group_desc, strlen(group_desc));
+	bytes += qq_put8(data + bytes, strlen(group_desc));
+	bytes += qq_putdata(data + bytes, (guint8 *) group_desc, strlen(group_desc));
 
-	if (data_written != data_len)
+	if (bytes != data_len)	{
 		purple_debug(PURPLE_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);
+			   data_len, bytes);
+		return;
+	}
+
+	qq_send_group_cmd(gc, group, data, bytes);
 }
 
-void qq_group_process_modify_info_reply(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc)
+void qq_group_process_modify_info_reply(guint8 *data, gint len, PurpleConnection *gc)
 {
+	gint bytes;
 	guint32 internal_group_id;
 	qq_group *group;
 	g_return_if_fail(data != NULL);
 
-	read_packet_dw(data, cursor, len, &internal_group_id);
+	bytes = 0;
+	bytes += qq_get32(&internal_group_id, data + bytes);
 	g_return_if_fail(internal_group_id > 0);
 
 	/* we should have its info locally */
@@ -284,42 +292,44 @@
 /* we create a very simple group first, and then let the user to modify */
 void qq_group_create_with_name(PurpleConnection *gc, const gchar *name)
 {
-	gint data_len, data_written;
-	guint8 *data, *cursor;
+	gint data_len;
+	guint8 *data;
+	gint bytes;
 	qq_data *qd;
 	g_return_if_fail(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;
+	bytes = 0;
 	/* we create the simpleset group, only group name is given */
 	/* 000 */
-	data_written += create_packet_b(data, &cursor, QQ_GROUP_CMD_CREATE_GROUP);
+	bytes += qq_put8(data + bytes, QQ_GROUP_CMD_CREATE_GROUP);
 	/* 001 */
-	data_written += create_packet_b(data, &cursor, QQ_GROUP_TYPE_PERMANENT);
+	bytes += qq_put8(data + bytes, QQ_GROUP_TYPE_PERMANENT);
 	/* 002 */
-	data_written += create_packet_b(data, &cursor, QQ_GROUP_AUTH_TYPE_NEED_AUTH);
+	bytes += qq_put8(data + bytes, QQ_GROUP_AUTH_TYPE_NEED_AUTH);
 	/* 003-004 */
-	data_written += create_packet_w(data, &cursor, 0x0000);
+	bytes += qq_put16(data + bytes, 0x0000);
 	/* 005-006 */
-	data_written += create_packet_w(data, &cursor, 0x0003);
+	bytes += qq_put16(data + bytes, 0x0003);
 	/* 007 */
-	data_written += create_packet_b(data, &cursor, strlen(name));
-	data_written += create_packet_data(data, &cursor, (guint8 *) 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 */
+	bytes += qq_put8(data + bytes, strlen(name));
+	bytes += qq_putdata(data + bytes, (guint8 *) name, strlen(name));
+	bytes += qq_put16(data + bytes, 0x0000);
+	bytes += qq_put8(data + bytes, 0x00);	/* no group notice */
+	bytes += qq_put8(data + bytes, 0x00);	/* no group desc */
+	bytes += qq_put32(data + bytes, qd->uid);	/* I am member of coz */
 
-	if (data_written != data_len)
+	if (bytes != data_len) {
 		purple_debug(PURPLE_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);
+			   data_len, bytes);
+		return;
+	}
+
+	qq_send_group_cmd(gc, NULL, data, bytes);
 }
 
 static void qq_group_setup_with_gc_and_uid(gc_and_uid *g)
@@ -335,8 +345,9 @@
 	g_free(g);
 }
 
-void qq_group_process_create_group_reply(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc)
+void qq_group_process_create_group_reply(guint8 *data, gint len, PurpleConnection *gc)
 {
+	gint bytes;
 	guint32 internal_group_id, external_group_id;
 	qq_group *group;
 	gc_and_uid *g;
@@ -346,8 +357,9 @@
 	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);
+	bytes = 0;
+	bytes += qq_get32(&internal_group_id, data + bytes);
+	bytes += qq_get32(&external_group_id, data + bytes);
 	g_return_if_fail(internal_group_id > 0 && external_group_id);
 
 	group = qq_group_create_internal_record(gc, internal_group_id, external_group_id, NULL);
@@ -378,36 +390,29 @@
 /* we have to activate group after creation, otherwise the group can not be searched */
 void qq_group_activate_group(PurpleConnection *gc, guint32 internal_group_id)
 {
-	gint data_len, data_written;
-	guint8 *data, *cursor;
+	guint8 data[16] = {0};
+	gint bytes = 0;
 	g_return_if_fail(internal_group_id > 0);
 
-	data_len = 5;
-	data = g_newa(guint8, data_len);
-	cursor = data;
-
-	data_written = 0;
+	bytes = 0;
 	/* we create the simplest group, only group name is given */
 	/* 000 */
-	data_written += create_packet_b(data, &cursor, QQ_GROUP_CMD_ACTIVATE_GROUP);
+	bytes += qq_put8(data + bytes, QQ_GROUP_CMD_ACTIVATE_GROUP);
 	/* 001-005 */
-	data_written += create_packet_dw(data, &cursor, internal_group_id);
+	bytes += qq_put32(data + bytes, internal_group_id);
 
-	if (data_written != data_len)
-		purple_debug(PURPLE_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_send_group_cmd(gc, NULL, data, bytes);
 }
 
-void qq_group_process_activate_group_reply(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc)
+void qq_group_process_activate_group_reply(guint8 *data, gint len, PurpleConnection *gc)
 {
+	gint bytes;
 	guint32 internal_group_id;
 	qq_group *group;
 	g_return_if_fail(data != NULL);
 
-	read_packet_dw(data, cursor, len, &internal_group_id);
+	bytes = 0;
+	bytes += qq_get32(&internal_group_id, data + bytes);
 	g_return_if_fail(internal_group_id > 0);
 
 	/* we should have its info locally */
--- a/libpurple/protocols/qq/group_opt.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/group_opt.h	Thu Nov 20 21:13:56 2008 +0000
@@ -54,12 +54,12 @@
 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, PurpleConnection *gc);
-void qq_group_process_modify_members_reply(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc);
+void qq_group_process_modify_info_reply(guint8 *data, gint len, PurpleConnection *gc);
+void qq_group_process_modify_members_reply(guint8 *data, gint len, PurpleConnection *gc);
 void qq_group_manage_group(PurpleConnection *gc, GHashTable *data);
 void qq_group_create_with_name(PurpleConnection *gc, const gchar *name);
 void qq_group_activate_group(PurpleConnection *gc, guint32 internal_group_id);
-void qq_group_process_activate_group_reply(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc);
-void qq_group_process_create_group_reply(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc);
+void qq_group_process_activate_group_reply(guint8 *data, gint len, PurpleConnection *gc);
+void qq_group_process_create_group_reply(guint8 *data, gint len, PurpleConnection *gc);
 
 #endif
--- a/libpurple/protocols/qq/group_search.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/group_search.c	Thu Nov 20 21:13:56 2008 +0000
@@ -43,24 +43,18 @@
 /* send packet to search for qq_group */
 void qq_send_cmd_group_search_group(PurpleConnection *gc, guint32 external_group_id)
 {
-	guint8 *raw_data, *cursor, type;
-	gint bytes, data_len;
+	guint8 raw_data[16] = {0};
+	gint bytes = 0;
+	guint8 type;
 
-	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);
+	bytes += qq_put8(raw_data + bytes, QQ_GROUP_CMD_SEARCH_GROUP);
+	bytes += qq_put8(raw_data + bytes, type);
+	bytes += qq_put32(raw_data + bytes, external_group_id);
 
-	if (bytes != data_len)
-		purple_debug(PURPLE_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_group_cmd(gc, NULL, raw_data, bytes);
 }
 
 static void _qq_setup_roomlist(qq_data *qd, qq_group *group)
@@ -89,55 +83,50 @@
 }
 
 /* process group cmd reply "search group" */
-void qq_process_group_cmd_search_group(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc)
+void qq_process_group_cmd_search_group(guint8 *data, gint len, PurpleConnection *gc)
 {
+	gint bytes;
 	guint8 search_type;
 	guint16 unknown;
-	gint bytes, pascal_len;
+	qq_group group;
 	qq_data *qd;
-	qq_group *group;
 	GSList *pending_id;
 
 	g_return_if_fail(data != NULL && len > 0);
 	qd = (qq_data *) gc->proto_data;
 
-	read_packet_b(data, cursor, len, &search_type);
-	group = g_newa(qq_group, 1);
+	bytes = 0;
+	bytes += qq_get8(&search_type, data + bytes);
 
 	/* now it starts with group_info_entry */
-	bytes = 0;
-	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));
-	bytes += read_packet_w(data, cursor, len, &(unknown));
-	bytes += read_packet_dw(data, cursor, len, &(group->creator_uid));
-	bytes += read_packet_w(data, cursor, len, &(unknown));
-	bytes += read_packet_w(data, cursor, len, &(unknown));
-	bytes += read_packet_w(data, cursor, len, &(unknown));
-	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;
+	bytes += qq_get32(&(group.internal_group_id), data + bytes);
+	bytes += qq_get32(&(group.external_group_id), data + bytes);
+	bytes += qq_get8(&(group.group_type), data + bytes);
+	bytes += qq_get16(&(unknown), data + bytes);
+	bytes += qq_get16(&(unknown), data + bytes);
+	bytes += qq_get32(&(group.creator_uid), data + bytes);
+	bytes += qq_get16(&(unknown), data + bytes);
+	bytes += qq_get16(&(unknown), data + bytes);
+	bytes += qq_get16(&(unknown), data + bytes);
+	bytes += qq_get32(&(group.group_category), data + bytes);
+	bytes += convert_as_pascal_string(data + bytes, &(group.group_name_utf8), QQ_CHARSET_DEFAULT);
+	bytes += qq_get16(&(unknown), data + bytes);
+	bytes += qq_get8(&(group.auth_type), data + bytes);
+	bytes += convert_as_pascal_string(data + bytes, &(group.group_desc_utf8), QQ_CHARSET_DEFAULT);
 	/* end of one qq_group */
-        if(*cursor != (data + len)) {
-                         purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
-					 "group_cmd_search_group: Dangerous error! maybe protocol changed, notify developers!");
-        }
+	if(bytes != len) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
+			"group_cmd_search_group: Dangerous error! maybe protocol changed, notify developers!");
+	}
 
-	pending_id = qq_get_pending_id(qd->joining_groups, group->external_group_id);
+	pending_id = qq_get_pending_id(qd->joining_groups, group.external_group_id);
 	if (pending_id != NULL) {
-		qq_set_pending_id(&qd->joining_groups, group->external_group_id, FALSE);
-		if (qq_group_find_by_id(gc, group->internal_group_id, QQ_INTERNAL_ID) == NULL)
+		qq_set_pending_id(&qd->joining_groups, group.external_group_id, FALSE);
+		if (qq_group_find_by_id(gc, group.internal_group_id, QQ_INTERNAL_ID) == NULL)
 			qq_group_create_internal_record(gc, 
-					group->internal_group_id, group->external_group_id, group->group_name_utf8);
-		qq_send_cmd_group_join_group(gc, group);
+					group.internal_group_id, group.external_group_id, group.group_name_utf8);
+		qq_send_cmd_group_join_group(gc, &group);
 	} else {
-		_qq_setup_roomlist(qd, group);
+		_qq_setup_roomlist(qd, &group);
 	}
 }
--- a/libpurple/protocols/qq/group_search.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/group_search.h	Thu Nov 20 21:13:56 2008 +0000
@@ -29,6 +29,6 @@
 #include "connection.h"
 
 void qq_send_cmd_group_search_group(PurpleConnection *gc, guint32 external_group_id);
-void qq_process_group_cmd_search_group(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc);
+void qq_process_group_cmd_search_group(guint8 *data, gint len, PurpleConnection *gc);
 
 #endif
--- a/libpurple/protocols/qq/header_info.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/header_info.c	Thu Nov 20 21:13:56 2008 +0000
@@ -34,11 +34,31 @@
 #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_CLIENT_0E1B 0x0e1b	/* QQ2005? QQ2006? */
+#define QQ_CLIENT_0E1B 0x0e1b	/* QQ2005 ? */
 #define QQ_CLIENT_0E35 0x0e35	/* EN QQ2005 V05.0.200.020 */
 #define QQ_CLIENT_0F15 0x0f15	/* QQ2006 Spring Festival build */
 #define QQ_CLIENT_0F5F 0x0f5f	/* QQ2006 final build */
 
+#define QQ_CLIENT_0C0B 0x0C0B	/* QQ2004 */
+#define QQ_CLIENT_0C0D 0x0C0D	/* QQ2004 preview*/
+#define QQ_CLIENT_0C21 0x0C21	/* QQ2004 */
+#define QQ_CLIENT_0C49 0x0C49	/* QQ2004II */
+#define QQ_CLIENT_0D05 0x0D05	/* QQ2005 beta1 */
+#define QQ_CLIENT_0D51 0x0D51	/* QQ2005 beta2 */
+#define QQ_CLIENT_0D61 0x0D61	/* QQ2005 */
+#define QQ_CLIENT_05A5 0x05A5	/* ? */
+#define QQ_CLIENT_05F1 0x0F15	/* QQ2006 Spring Festival */
+#define QQ_CLIENT_0F4B 0x0F4B	/* QQ2006 Beta 3  */
+
+#define QQ_CLIENT_1105 0x1105	/* QQ2007 beta4*/
+#define QQ_CLIENT_111D 0x111D	/* QQ2007 */
+#define QQ_CLIENT_115B 0x115B	/* QQ2008 */
+#define QQ_CLIENT_1203 0x1203	/* QQ2008 */
+#define QQ_CLIENT_1205 0x1205	/* QQ2008 */
+#define QQ_CLIENT_120B 0x120B	/* QQ2008 July 8.0.978.400 */
+#define QQ_CLIENT_1412 0x1412	/* QQMac 1.0 preview1 build 670 */
+#define QQ_CLIENT_1441 0x1441	/* QQ2009 preview2 */
+
 #define QQ_SERVER_0100 0x0100	/* server */
 
 /* given command alias, return the command name accordingly */
@@ -55,10 +75,10 @@
 		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_ADD_BUDDY_WO_AUTH:
+		return "QQ_CMD_ADD_BUDDY_WO_AUTH";
+	case QQ_CMD_DEL_BUDDY:
+		return "QQ_CMD_DEL_BUDDY";
 	case QQ_CMD_BUDDY_AUTH:
 		return "QQ_CMD_BUDDY_AUTH";
 	case QQ_CMD_CHANGE_ONLINE_STATUS:
@@ -73,29 +93,29 @@
 		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_GET_BUDDIES_LIST:
+		return "QQ_CMD_GET_BUDDIES_LIST";
+	case QQ_CMD_GET_BUDDIES_ONLINE:
+		return "QQ_CMD_GET_BUDDIES_ONLINE";
 	case QQ_CMD_GROUP_CMD:
 		return "QQ_CMD_GROUP_CMD";
 	case QQ_CMD_GET_ALL_LIST_WITH_GROUP:
 		return "QQ_CMD_GET_ALL_LIST_WITH_GROUP";
 	case QQ_CMD_GET_LEVEL:
 		return "QQ_CMD_GET_LEVEL";
-	case QQ_CMD_REQUEST_LOGIN_TOKEN:
-		return "QQ_CMD_REQUEST_LOGIN_TOKEN";
+	case QQ_CMD_TOKEN:
+		return "QQ_CMD_TOKEN";
 	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";
+	case QQ_CMD_RECV_MSG_BUDDY_CHANGE_STATUS:
+		return "QQ_CMD_RECV_MSG_BUDDY_CHANGE_STATUS";
 	default:
-		return "UNKNOWN_TYPE";
+		return "Unknown";
 	}
 }
 
 /* given source tag, return its description accordingly */
-const gchar *qq_get_source_str(gint source)
+const gchar *qq_get_ver_desc(gint source)
 {
 	switch (source) {
 	case QQ_CLIENT_062E:
@@ -114,17 +134,46 @@
 		return "GB QQ2003iii build 0304";
 	case QQ_CLIENT_0B37:
 		return "GB QQ2003iii build 0304 (April 5 update)";
+	case QQ_CLIENT_0C0B:
+		return "QQ2004";
+	case QQ_CLIENT_0C0D:
+		return "QQ2004 preview";
+	case QQ_CLIENT_0C21:
+		return "QQ2004";
+	case QQ_CLIENT_0C49:
+		return "QQ2004II";
+	case QQ_CLIENT_0D05:
+		return "QQ2005 beta1";
+	case QQ_CLIENT_0D51:
+		return "QQ2005 beta2";
+	case QQ_CLIENT_0D61:
+		return "QQ2005";
 	case QQ_CLIENT_0E1B:
 		return "QQ2005 or QQ2006";
 	case QQ_CLIENT_0E35:
 		return "En QQ2005 V05.0.200.020";
 	case QQ_CLIENT_0F15:
-		return "QQ2006 Spring Festival build";
+		return "QQ2006 Spring Festival";
+	case QQ_CLIENT_0F4B:
+		return "QQ2006 beta3";
 	case QQ_CLIENT_0F5F:
 		return "QQ2006 final build";
+	case QQ_CLIENT_1105:
+		return "QQ2007 beta4";
+	case QQ_CLIENT_111D:
+		return "QQ2007";
+	case QQ_CLIENT_115B:
+	case QQ_CLIENT_1203:
+	case QQ_CLIENT_1205:
+	case QQ_CLIENT_120B:
+		return "QQ2008";
+	case QQ_CLIENT_1412:
+		return "QQMac 1.0 preview1 build 670";
+	case QQ_CLIENT_1441:
+		return "QQ2009 preview2";
 	case QQ_SERVER_0100:
 		return "QQ Server 0100";
 	default:
-		return "QQ unknown version";
+		return "Unknown";
 	}
 }
--- a/libpurple/protocols/qq/header_info.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/header_info.h	Thu Nov 20 21:13:56 2008 +0000
@@ -42,8 +42,8 @@
 	QQ_CMD_UPDATE_INFO = 0x0004,			/* update information */
 	QQ_CMD_SEARCH_USER = 0x0005,			/* search 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_ADD_BUDDY_WO_AUTH = 0x0009,		/* add buddy without auth */
+	QQ_CMD_DEL_BUDDY = 0x000a,			/* delete a buddy  */
 	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 */
@@ -53,19 +53,19 @@
 	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_GET_BUDDIES_LIST = 0x0026,		/* get buddies list */
+	QQ_CMD_GET_BUDDIES_ONLINE = 0x0027,		/* get online buddies list */
 	QQ_CMD_CELL_PHONE_2 = 0x0029,			/* cell phone 2 */
 	QQ_CMD_GROUP_CMD = 0x0030,			/* group command */
 	QQ_CMD_GET_ALL_LIST_WITH_GROUP = 0x0058,  
 	QQ_CMD_GET_LEVEL = 0x005C,			/* get level for one or more buddies */
-	QQ_CMD_REQUEST_LOGIN_TOKEN  = 0x0062, 		/* get login token */
+	QQ_CMD_TOKEN  = 0x0062, 		/* get login token */
 	QQ_CMD_RECV_MSG_SYS = 0x0080,			/* receive a system message */
-	QQ_CMD_RECV_MSG_FRIEND_CHANGE_STATUS = 0x0081,	/* friends change status */
+	QQ_CMD_RECV_MSG_BUDDY_CHANGE_STATUS = 0x0081,	/* buddy change status */
 };
 
 const gchar *qq_get_cmd_desc(gint type);
 
-const gchar *qq_get_source_str(gint source);
+const gchar *qq_get_ver_desc(gint source);
 
 #endif
--- a/libpurple/protocols/qq/im.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/im.c	Thu Nov 20 21:13:56 2008 +0000
@@ -40,19 +40,16 @@
 #include "header_info.h"
 #include "im.h"
 #include "packet_parse.h"
-#include "send_core.h"
+#include "qq_network.h"
 #include "send_file.h"
 #include "utils.h"
 
 #define QQ_SEND_IM_REPLY_OK       0x00
 #define DEFAULT_FONT_NAME_LEN 	  4
 
-/* a debug function */
-void _qq_show_packet(const gchar *desc, const guint8 *buf, gint len);
-
 enum
 {
-        QQ_NORMAL_IM_TEXT = 0x000b,
+	QQ_NORMAL_IM_TEXT = 0x000b,
 	QQ_NORMAL_IM_FILE_REQUEST_TCP = 0x0001,
 	QQ_NORMAL_IM_FILE_APPROVE_TCP = 0x0003,
 	QQ_NORMAL_IM_FILE_REJECT_TCP = 0x0005,
@@ -82,7 +79,7 @@
 	guint16 sender_ver;
 	guint32 sender_uid;
 	guint32 receiver_uid;
-	guint8 *session_md5;
+	guint8 session_md5[QQ_KEY_LENGTH];
 	guint16 normal_im_type;
 };
 
@@ -112,7 +109,7 @@
 	guint32 sender_uid;
 	guint32 receiver_uid;
 	guint32 server_im_seq;
-	guint8 sender_ip[4];
+	struct in_addr sender_ip;
 	guint16 sender_port;
 	guint16 im_type;
 };
@@ -121,9 +118,9 @@
 #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, gint tail_len)
+		const gchar *font_size,
+		const gchar *font_name,
+		gboolean is_bold, gboolean is_italic, gboolean is_underline, gint tail_len)
 {
 	gchar *s1;
 	unsigned char *rgb;
@@ -141,7 +138,7 @@
 	send_im_tail = g_new0(guint8, tail_len);
 
 	g_strlcpy((gchar *) (send_im_tail + QQ_SEND_IM_AFTER_MSG_HEADER_LEN),
-		  font_name, tail_len - QQ_SEND_IM_AFTER_MSG_HEADER_LEN);
+			font_name, tail_len - QQ_SEND_IM_AFTER_MSG_HEADER_LEN);
 	send_im_tail[tail_len - 1] = (guint8) tail_len;
 
 	send_im_tail[0] = 0x00;
@@ -182,39 +179,39 @@
 	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);
+	/* qq_show_packet("QQ_MESG", send_im_tail, tail_len); */
 	return (guint8 *) 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";
-	case QQ_RECV_IM_QUN_IM:
-		return "QQ_RECV_IM_QUN_IM";
-	default:
-		return "QQ_RECV_IM_UNKNOWN";
+		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";
+		case QQ_RECV_IM_QUN_IM:
+			return "QQ_RECV_IM_QUN_IM";
+		default:
+			return "QQ_RECV_IM_UNKNOWN";
 	}
 }
 
@@ -222,27 +219,26 @@
  * we send an ACK which is the first 16 bytes of incoming packet */
 static void _qq_send_packet_recv_im_ack(PurpleConnection *gc, guint16 seq, guint8 *data)
 {
-	qq_send_cmd(gc, QQ_CMD_RECV_IM, FALSE, seq, FALSE, data, 16);
+	qq_data *qd;
+
+	qd = (qq_data *) gc->proto_data;
+	qq_send_cmd_detail(qd, QQ_CMD_RECV_IM, seq, FALSE, data, 16);
 }
 
 /* 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)
+static gint _qq_normal_im_common_read(guint8 *data, 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));
+	bytes += qq_get16(&(common->sender_ver), data + bytes);
+	bytes += qq_get32(&(common->sender_uid), data + bytes);
+	bytes += qq_get32(&(common->receiver_uid), data + bytes);
+	bytes += qq_getdata(common->session_md5, QQ_KEY_LENGTH, data + bytes);
+	bytes += qq_get16(&(common->normal_im_type), data + bytes);
 
 	if (bytes != 28) {	/* read common place fail */
 		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Expect 28 bytes, read %d bytes\n", bytes);
@@ -253,8 +249,7 @@
 }
 
 /* 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, PurpleConnection *gc)
+static void _qq_process_recv_normal_im_text(guint8 *data, gint len, qq_recv_normal_im_common *common, PurpleConnection *gc)
 {
 	guint16 purple_msg_type;
 	gchar *name;
@@ -262,62 +257,73 @@
 	gchar *msg_utf8_encoded;
 	qq_data *qd;
 	qq_recv_normal_im_text *im_text;
+	gint bytes = 0;
+	PurpleBuddy *b;
+	qq_buddy *qq_b;
 
 	g_return_if_fail(common != NULL);
 	qd = (qq_data *) gc->proto_data;
 
 	/* now it is QQ_NORMAL_IM_TEXT */
-	if (*cursor >= (data + len - 1)) {
-		purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Received normal IM text is empty\n");
-		return;
-	} else
-		im_text = g_newa(qq_recv_normal_im_text, 1);
+	/*
+	   if (*cursor >= (data + len - 1)) {
+	   purple_debug(PURPLE_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_w(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));
+	bytes += qq_get16(&(im_text->msg_seq), data + bytes);
+	bytes += qq_get32(&(im_text->send_time), data + bytes);
+	bytes += qq_get16(&(im_text->sender_icon), data + bytes);
+	bytes += qq_getdata((guint8 *) & (im_text->unknown2), 3, data + bytes);
+	bytes += qq_get8(&(im_text->is_there_font_attr), data + bytes);
 	/**
 	 * from lumaqq	for unknown3
 	 *	totalFragments = buf.get() & 255;
-         *	fragmentSequence = buf.get() & 255;
-         *	messageId = buf.getChar();
+	 *	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));
+	bytes += qq_getdata((guint8 *) & (im_text->unknown3), 4, data + bytes);
+	bytes += qq_get8(&(im_text->msg_type), data + bytes);
 
 	/* 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(*(gchar **) cursor, data + len - *cursor);
+		im_text->msg = g_strndup((gchar *)(data + bytes), len - bytes);
 	} else {		/* it is normal mesasge */
 		if (im_text->is_there_font_attr) {
-			im_text->msg = g_strdup(*(gchar **) 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);
+			im_text->msg = g_strdup((gchar *)(data + bytes));
+			bytes += strlen(im_text->msg) + 1; /* length decided by strlen! will it cause a crash? */
+			im_text->font_attr_len = len - bytes;
+			im_text->font_attr = g_memdup(data + bytes, im_text->font_attr_len);
 		} else		/* not im_text->is_there_font_attr */
-			im_text->msg = g_strndup(*(gchar **) cursor, data + len - *cursor);
+			im_text->msg = g_strndup((gchar *)(data + bytes), len - bytes);
 	}			/* if im_text->msg_type */
-	_qq_show_packet("QQ_MESG recv", data, *cursor - data);
 
 	name = uid_to_purple_name(common->sender_uid);
-	if (purple_find_buddy(gc->account, name) == NULL)
+	b = purple_find_buddy(gc->account, name);
+	if (b == NULL) {
 		qq_add_buddy_by_recv_packet(gc, common->sender_uid, FALSE, TRUE);
-
+		b = purple_find_buddy(gc->account, name);
+	}
+	qq_b = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
+	if (qq_b != NULL) {
+		qq_b->client_version = common->sender_ver; 
+	}
+	
 	purple_msg_type = (im_text->msg_type == QQ_IM_AUTO_REPLY) ? PURPLE_MESSAGE_AUTO_RESP : 0;
 
 	msg_with_purple_smiley = qq_smiley_to_purple(im_text->msg);
 	msg_utf8_encoded = im_text->is_there_font_attr ?
-	    qq_encode_to_purple(im_text->font_attr,
-			      im_text->font_attr_len,
-			      msg_with_purple_smiley) : qq_to_utf8(msg_with_purple_smiley, QQ_CHARSET_DEFAULT);
+		qq_encode_to_purple(im_text->font_attr,
+				im_text->font_attr_len,
+				msg_with_purple_smiley) : qq_to_utf8(msg_with_purple_smiley, QQ_CHARSET_DEFAULT);
 
 	/* send encoded to purple, note that we use im_text->send_time,
 	 * not the time we receive the message
@@ -333,81 +339,66 @@
 }
 
 /* it is a normal IM, maybe text or video request */
-static void _qq_process_recv_normal_im(guint8 *data, guint8 **cursor, gint len, PurpleConnection *gc)
+static void _qq_process_recv_normal_im(guint8 *data, gint len, PurpleConnection *gc)
 {
-	gint bytes;
+	gint bytes = 0;
 	qq_recv_normal_im_common *common;
 	qq_recv_normal_im_unprocessed *im_unprocessed;
-	gchar *hex_dump;
 
 	g_return_if_fail (data != NULL && len != 0);
 
-	if (*cursor >= (data + len - 1)) {
-		purple_debug (PURPLE_DEBUG_WARNING, "QQ",
-			    "Received normal IM is empty\n");
-		return;
-	}
-	else
-		common = g_newa (qq_recv_normal_im_common, 1);
+	common = g_newa (qq_recv_normal_im_common, 1);
 
-	bytes = _qq_normal_im_common_read (data, cursor, len, common);
+	bytes = _qq_normal_im_common_read(data, len, common);
 	if (bytes < 0) {
 		purple_debug (PURPLE_DEBUG_ERROR, "QQ",
-			    "Fail read the common part of normal IM\n");
+				"Fail read the common part of normal IM\n");
 		return;
 	}
 
 	switch (common->normal_im_type) {
-	case QQ_NORMAL_IM_TEXT:
-		purple_debug (PURPLE_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 */
-		purple_debug (PURPLE_DEBUG_WARNING, "QQ",
-			    "Normal IM, unprocessed type [0x%04x]\n",
-			    common->normal_im_type);
-	       	hex_dump = hex_dump_to_str(im_unprocessed->unknown, im_unprocessed->length);
-		purple_debug (PURPLE_DEBUG_WARNING, "QQ", "Dump unknown part.\n%s", hex_dump);
-		g_free(hex_dump);
-		g_free (common->session_md5);
-		return;
+		case QQ_NORMAL_IM_TEXT:
+			purple_debug (PURPLE_DEBUG_INFO, "QQ",
+					"Normal IM, text type:\n [%d] => [%d], src: %s (%04X)\n",
+					common->sender_uid, common->receiver_uid,
+					qq_get_ver_desc (common->sender_ver), common->sender_ver);
+			if (bytes >= len - 1) {
+				purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Received normal IM text is empty\n");
+				return;
+			}
+			_qq_process_recv_normal_im_text(data + bytes, len - bytes, common, gc);
+			break;
+		case QQ_NORMAL_IM_FILE_REJECT_UDP:
+			qq_process_recv_file_reject(data + bytes, len - bytes, common->sender_uid, gc);
+			break;
+		case QQ_NORMAL_IM_FILE_APPROVE_UDP:
+			qq_process_recv_file_accept(data + bytes, len - bytes, common->sender_uid, gc);
+			break;
+		case QQ_NORMAL_IM_FILE_REQUEST_UDP:
+			qq_process_recv_file_request(data + bytes, len - bytes, common->sender_uid, gc);
+			break;
+		case QQ_NORMAL_IM_FILE_CANCEL:
+			qq_process_recv_file_cancel(data + bytes, len - bytes, common->sender_uid, gc);
+			break;
+		case QQ_NORMAL_IM_FILE_NOTIFY:
+			qq_process_recv_file_notify(data + bytes, len - bytes, common->sender_uid, gc);
+			break;
+		default:
+			im_unprocessed = g_newa (qq_recv_normal_im_unprocessed, 1);
+			im_unprocessed->common = common;
+			im_unprocessed->unknown = data + bytes;
+			im_unprocessed->length = len - bytes;
+			/* a simple process here, maybe more later */
+			purple_debug (PURPLE_DEBUG_WARNING, "QQ",
+					"Normal IM, unprocessed type [0x%04x], len %d\n",
+					common->normal_im_type, im_unprocessed->length);
+			qq_show_packet ("QQ unk-im", im_unprocessed->unknown, im_unprocessed->length);
+			return;
 	}
-
-	g_free (common->session_md5);
 }
 
 /* process im from system administrator */
-static void _qq_process_recv_sys_im(guint8 *data, guint8 **cursor, gint data_len, PurpleConnection *gc)
+static void _qq_process_recv_sys_im(guint8 *data, gint data_len, PurpleConnection *gc)
 {
 	gint len;
 	guint8 reply;
@@ -415,14 +406,9 @@
 
 	g_return_if_fail(data != NULL && data_len != 0);
 
-	if (*cursor >= (data + data_len - 1)) {
-		purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Received sys IM is empty\n");
-		return;
-	}
+	len = data_len;
 
-	len = data + data_len - *cursor;
-
-	if (NULL == (segments = split_data(*cursor, len, "\x2f", 2)))
+	if (NULL == (segments = split_data(data, len, "\x2f", 2)))
 		return;
 
 	reply = strtol(segments[0], NULL, 10);
@@ -436,7 +422,7 @@
 void qq_send_packet_im(PurpleConnection *gc, guint32 to_uid, gchar *msg, gint type)
 {
 	qq_data *qd;
-	guint8 *cursor, *raw_data, *send_im_tail;
+	guint8 *raw_data, *send_im_tail;
 	guint16 client_tag, normal_im_type;
 	gint msg_len, raw_len, font_name_len, tail_len, bytes;
 	time_t now;
@@ -500,52 +486,51 @@
 
 	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);
+	bytes += qq_put32(raw_data + bytes, qd->uid);
 	/* 004-007: sender uid */
-	bytes += create_packet_dw(raw_data, &cursor, to_uid);
+	bytes += qq_put32(raw_data + bytes, to_uid);
 	/* 008-009: sender client version */
-	bytes += create_packet_w(raw_data, &cursor, client_tag);
+	bytes += qq_put16(raw_data + bytes, client_tag);
 	/* 010-013: receiver uid */
-	bytes += create_packet_dw(raw_data, &cursor, qd->uid);
+	bytes += qq_put32(raw_data + bytes, qd->uid);
 	/* 014-017: sender uid */
-	bytes += create_packet_dw(raw_data, &cursor, to_uid);
+	bytes += qq_put32(raw_data + bytes, to_uid);
 	/* 018-033: md5 of (uid+session_key) */
-	bytes += create_packet_data(raw_data, &cursor, qd->session_md5, 16);
+	bytes += qq_putdata(raw_data + bytes, qd->session_md5, 16);
 	/* 034-035: message type */
-	bytes += create_packet_w(raw_data, &cursor, normal_im_type);
+	bytes += qq_put16(raw_data + bytes, normal_im_type);
 	/* 036-037: sequence number */
-	bytes += create_packet_w(raw_data, &cursor, qd->send_seq);
+	bytes += qq_put16(raw_data + bytes, qd->send_seq);
 	/* 038-041: send time */
-	bytes += create_packet_dw(raw_data, &cursor, (guint32) now);
+	bytes += qq_put32(raw_data + bytes, (guint32) now);
 	/* 042-043: sender icon */
-	bytes += create_packet_w(raw_data, &cursor, qd->my_icon);
+	bytes += qq_put16(raw_data + bytes, qd->my_icon);
 	/* 044-046: always 0x00 */
-	bytes += create_packet_w(raw_data, &cursor, 0x0000);
-	bytes += create_packet_b(raw_data, &cursor, 0x00);
+	bytes += qq_put16(raw_data + bytes, 0x0000);
+	bytes += qq_put8(raw_data + bytes, 0x00);
 	/* 047-047: we use font attr */
-	bytes += create_packet_b(raw_data, &cursor, 0x01);
+	bytes += qq_put8(raw_data + bytes, 0x01);
 	/* 048-051: always 0x00 */
-	bytes += create_packet_dw(raw_data, &cursor, 0x00000000);
+	bytes += qq_put32(raw_data + bytes, 0x00000000);
 	/* 052-052: text message type (normal/auto-reply) */
-	bytes += create_packet_b(raw_data, &cursor, type);
+	bytes += qq_put8(raw_data + bytes, type);
 	/* 053-   : msg ends with 0x00 */
-	bytes += create_packet_data(raw_data, &cursor, (guint8 *) msg_filtered, msg_len);
+	bytes += qq_putdata(raw_data + bytes, (guint8 *) msg_filtered, msg_len);
 	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, send_im_tail, tail_len);
+			is_italic, is_underline, tail_len);
+	qq_show_packet("QQ_send_im_tail debug", send_im_tail, tail_len);
+	bytes += qq_putdata(raw_data + bytes, send_im_tail, tail_len);
 
-	_qq_show_packet("QQ_MESG raw", raw_data, cursor - raw_data);
+	qq_show_packet("QQ_raw_data debug", raw_data, bytes);
 
 	if (bytes == raw_len)	/* create packet OK */
-		qq_send_cmd(gc, QQ_CMD_SEND_IM, TRUE, 0, TRUE, raw_data, cursor - raw_data);
+		qq_send_cmd(qd, QQ_CMD_SEND_IM, raw_data, bytes);
 	else
 		purple_debug(PURPLE_DEBUG_ERROR, "QQ",
-			   "Fail creating send_im packet, expect %d bytes, build %d bytes\n", raw_len, bytes);
+				"Fail creating send_im packet, expect %d bytes, build %d bytes\n", raw_len, bytes);
 
 	if (font_color)
 		g_free(font_color);
@@ -560,7 +545,8 @@
 {
 	qq_data *qd;
 	gint len;
-	guint8 *data, *cursor, reply;
+	guint8 *data, reply;
+	gint bytes = 0;
 
 	g_return_if_fail(buf != NULL && buf_len != 0);
 
@@ -569,8 +555,7 @@
 	data = g_newa(guint8, len);
 
 	if (qq_decrypt(buf, buf_len, qd->session_key, data, &len)) {
-		cursor = data;
-		read_packet_b(data, &cursor, len, &reply);
+		bytes += qq_get8(&reply, data + bytes);
 		if (reply != QQ_SEND_IM_REPLY_OK) {
 			purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Send IM fail\n");
 			purple_notify_error(gc, _("Error"), _("Failed to send IM."), NULL);
@@ -588,7 +573,7 @@
 {
 	qq_data *qd;
 	gint len, bytes;
-	guint8 *data, *cursor;
+	guint8 *data;
 	qq_recv_im_header *im_header;
 
 	g_return_if_fail(buf != NULL && buf_len != 0);
@@ -597,98 +582,107 @@
 	len = buf_len;
 	data = g_newa(guint8, len);
 
-	if (qq_decrypt(buf, buf_len, qd->session_key, data, &len)) {
-		if (len < 16) {	/* we need to ack with the first 16 bytes */
-			purple_debug(PURPLE_DEBUG_ERROR, "QQ", "IM is too short\n");
-			return;
-		} else
-			_qq_send_packet_recv_im_ack(gc, seq, data);
+	if (!qq_decrypt(buf, buf_len, qd->session_key, data, &len)) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt rev im\n");
+	}
+
+	if (len < 16) {	/* we need to ack with the first 16 bytes */
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "IM is too short\n");
+		return;
+	} else {
+		_qq_send_packet_recv_im_ack(gc, seq, data);
+	}
+
+	/* check len first */
+	if (len < 20) {	/* length of im_header */
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ",
+				"Fail read recv IM header, len should longer than 20 bytes, read %d bytes\n", len);
+		return;
+	}
 
-		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));
+	bytes = 0;
+	im_header = g_newa(qq_recv_im_header, 1);
+	bytes += qq_get32(&(im_header->sender_uid), data + bytes);
+	bytes += qq_get32(&(im_header->receiver_uid), data + bytes);
+	bytes += qq_get32(&(im_header->server_im_seq), data + bytes);
+	/* if the message is delivered via server, it is server IP/port */
+	bytes += qq_getIP(&(im_header->sender_ip), data + bytes);
+	bytes += qq_get16(&(im_header->sender_port), data + bytes);
+	bytes += qq_get16(&(im_header->im_type), data + bytes);
+	/* im_header prepared */
 
-		if (bytes != 20) {	/* length of im_header */
-			purple_debug(PURPLE_DEBUG_ERROR, "QQ",
-				   "Fail read recv IM header, expect 20 bytes, read %d bytes\n", bytes);
-			return;
-		}
+	if (im_header->receiver_uid != qd->uid) {	/* should not happen */
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "IM to [%d], NOT me\n", im_header->receiver_uid);
+		return;
+	}
 
-		if (im_header->receiver_uid != qd->uid) {	/* should not happen */
-			purple_debug(PURPLE_DEBUG_ERROR, "QQ", "IM to [%d], NOT me\n", im_header->receiver_uid);
-			return;
-		}
+	/* check bytes */
+	if (bytes >= len - 1) {
+		purple_debug (PURPLE_DEBUG_WARNING, "QQ", "Received IM is empty\n");
+		return;
+	}
 
-		switch (im_header->im_type) {
+	switch (im_header->im_type) {
 		case QQ_RECV_IM_TO_BUDDY:
 			purple_debug(PURPLE_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);
+					"IM from buddy [%d], I am in his/her buddy list\n", im_header->sender_uid);
+			_qq_process_recv_normal_im(data + bytes, len - bytes, gc); /* position and rest length */
 			break;
 		case QQ_RECV_IM_TO_UNKNOWN:
 			purple_debug(PURPLE_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);
+					"IM from buddy [%d], I am a stranger to him/her\n", im_header->sender_uid);
+			_qq_process_recv_normal_im(data + bytes, len - bytes, gc);
 			break;
 		case QQ_RECV_IM_UNKNOWN_QUN_IM:
 		case QQ_RECV_IM_TEMP_QUN_IM:
 		case QQ_RECV_IM_QUN_IM:
 			purple_debug(PURPLE_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);
+			qq_process_recv_group_im(data + bytes, len - bytes, im_header->sender_uid, gc, im_header->im_type);
 			break;
 		case QQ_RECV_IM_ADD_TO_QUN:
 			purple_debug(PURPLE_DEBUG_INFO, "QQ",
-				   "IM from group, added by group internal_id [%d]\n", im_header->sender_uid);
+					"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);
+			qq_process_recv_group_im_been_added(data + bytes, len - bytes, im_header->sender_uid, gc);
 			break;
 		case QQ_RECV_IM_DEL_FROM_QUN:
 			purple_debug(PURPLE_DEBUG_INFO, "QQ",
-				   "IM from group, removed by group internal_ID [%d]\n", im_header->sender_uid);
+					"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);
+			qq_process_recv_group_im_been_removed(data + bytes, len - bytes, im_header->sender_uid, gc);
 			break;
 		case QQ_RECV_IM_APPLY_ADD_TO_QUN:
 			purple_debug(PURPLE_DEBUG_INFO, "QQ",
-				   "IM from group, apply to join group internal_ID [%d]\n", im_header->sender_uid);
+					"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);
+			qq_process_recv_group_im_apply_join(data + bytes, len - bytes, im_header->sender_uid, gc);
 			break;
 		case QQ_RECV_IM_APPROVE_APPLY_ADD_TO_QUN:
 			purple_debug(PURPLE_DEBUG_INFO, "QQ",
-				   "IM for group system info, approved by group internal_id [%d]\n",
-				   im_header->sender_uid);
+					"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);
+			qq_process_recv_group_im_been_approved(data + bytes, len - bytes, im_header->sender_uid, gc);
 			break;
 		case QQ_RECV_IM_REJCT_APPLY_ADD_TO_QUN:
 			purple_debug(PURPLE_DEBUG_INFO, "QQ",
-				   "IM for group system info, rejected by group internal_id [%d]\n",
-				   im_header->sender_uid);
+					"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);
+			qq_process_recv_group_im_been_rejected(data + bytes, len - bytes, im_header->sender_uid, gc);
 			break;
 		case QQ_RECV_IM_SYS_NOTIFICATION:
 			purple_debug(PURPLE_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);
+					"IM from [%d], should be a system administrator\n", im_header->sender_uid);
+			_qq_process_recv_sys_im(data + bytes, len - bytes, gc);
 			break;
 		default:
 			purple_debug(PURPLE_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));
-		}
-	} else {
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt rev im\n");
+					"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));
 	}
 }
+
--- a/libpurple/protocols/qq/keep_alive.c	Mon Aug 18 17:08:01 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,181 +0,0 @@
-/**
- * @file keep_alive.c
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- *
- *
- * OICQ encryption algorithm
- * Convert from ASM code provided by PerlOICQ
- * 
- * Puzzlebird, Nov-Dec 2002
- */
-
-#include "internal.h"
-
-#include "debug.h"
-#include "server.h"
-
-#include "buddy_info.h"
-#include "buddy_list.h"
-#include "buddy_status.h"
-#include "crypt.h"
-#include "header_info.h"
-#include "keep_alive.h"
-#include "packet_parse.h"
-#include "send_core.h"
-#include "utils.h"
-
-#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(PurpleConnection *gc)
-{
-	qq_data *qd;
-	guint8 *raw_data, *cursor;
-
-	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);
-}
-
-/* parse the return of keep-alive packet, it includes some system information */
-void qq_process_keep_alive_reply(guint8 *buf, gint buf_len, PurpleConnection *gc) 
-{
-	qq_data *qd;
-	gint len;
-	gchar **segments;
-	guint8 *data;
-
-	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_decrypt(buf, buf_len, qd->session_key, data, &len)) {
-		/* the last one is 60, don't know what it is */
-		if (NULL == (segments = split_data(data, len, "\x1f", 6)))
-			return;
-		/* segments[0] and segment[1] are all 0x30 ("0") */
-		qd->all_online = strtol(segments[2], NULL, 10);
-		if(0 == qd->all_online)
-			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-					_("Keep alive error"));
-		g_free(qd->my_ip);
-		qd->my_ip = g_strdup(segments[3]);
-		qd->my_port = strtol(segments[4], NULL, 10);
-		g_strfreev(segments);
-	} else
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt keep alive reply\n");
-
-	/* we refresh buddies's online status periodically */
-	/* qd->last_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);
-}
-
-/* refresh all buddies online/offline,
- * after receiving reply for get_buddies_online packet */
-void qq_refresh_all_buddy_status(PurpleConnection *gc)
-{
-	time_t now;
-	GList *list;
-	qq_data *qd;
-	qq_buddy *q_bud;
-
-	qd = (qq_data *) (gc->proto_data);
-	now = time(NULL);
-	list = qd->buddies;
-
-	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) {
-			q_bud->status = QQ_BUDDY_ONLINE_OFFLINE;
-			qq_update_buddy_contact(gc, q_bud);
-		}
-		list = list->next;
-	}
-}
-
-/*TODO: maybe this should be qq_update_buddy_status() ?*/
-void qq_update_buddy_contact(PurpleConnection *gc, qq_buddy *q_bud)
-{
-	gchar *name;
-	PurpleBuddy *bud;
-	gchar *status_id;
-	
-	g_return_if_fail(q_bud != NULL);
-
-	name = uid_to_purple_name(q_bud->uid);
-	bud = purple_find_buddy(gc->account, name);
-	g_return_if_fail(bud != NULL);
-
-	if (bud != NULL) {
-		purple_blist_server_alias_buddy(bud, q_bud->nickname); /* server */
-		q_bud->last_refresh = time(NULL);
-
-		/* purple supports signon and idle time
-		 * but it is not much use for QQ, I do not use them */
-		/* serv_got_update(gc, name, online, 0, q_bud->signon, q_bud->idle, bud->uc); */
-		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";
-			purple_debug(PURPLE_DEBUG_ERROR, "QQ", "unknown status: %x\n", q_bud->status);
-			break;
-		}
-		purple_debug(PURPLE_DEBUG_INFO, "QQ", "set buddy %d to %s\n", q_bud->uid, status_id);
-		purple_prpl_got_user_status(gc->account, name, status_id, NULL);
-
-		if (q_bud->comm_flag & QQ_COMM_FLAG_BIND_MOBILE && q_bud->status != QQ_BUDDY_OFFLINE)
-			purple_prpl_got_user_status(gc->account, name, "mobile", NULL);
-		else
-			purple_prpl_got_user_status_deactive(gc->account, name, "mobile");
-	} else {
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "unknown buddy: %d\n", q_bud->uid);
-	}
-
-	purple_debug(PURPLE_DEBUG_INFO, "QQ", "qq_update_buddy_contact, client=%04x\n", q_bud->client_version);
-	g_free(name);
-}
--- a/libpurple/protocols/qq/keep_alive.h	Mon Aug 18 17:08:01 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-/**
- * @file keep_alive.h
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- *
- */
-
-#ifndef _QQ_KEEP_ALIVE_H_
-#define _QQ_KEEP_ALIVE_H_
-
-#include <glib.h>
-#include "connection.h"
-#include "qq.h"
-
-void qq_send_packet_keep_alive(PurpleConnection *gc);
-
-void qq_process_keep_alive_reply(guint8 *buf, gint buf_len, PurpleConnection *gc);
-void qq_refresh_all_buddy_status(PurpleConnection *gc);
-
-void qq_update_buddy_contact(PurpleConnection *gc, qq_buddy *q_bud);
-
-#endif
--- a/libpurple/protocols/qq/login_logout.c	Mon Aug 18 17:08:01 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,504 +0,0 @@
-/**
- * @file login_logout.c
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#include "debug.h"
-#include "internal.h"
-#include "server.h"
-
-#include "buddy_info.h"
-#include "buddy_list.h"
-#include "buddy_status.h"
-#include "char_conv.h"
-#include "crypt.h"
-#include "group.h"
-#include "header_info.h"
-#include "login_logout.h"
-#include "packet_parse.h"
-#include "qq.h"
-#include "qq_proxy.h"
-#include "send_core.h"
-#include "utils.h"
-
-#define QQ_LOGIN_DATA_LENGTH		    416
-#define QQ_LOGIN_REPLY_OK_PACKET_LEN        139
-#define QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN  11
-
-#define QQ_REQUEST_LOGIN_TOKEN_REPLY_OK 	0x00
-
-#define QQ_LOGIN_REPLY_OK                   0x00
-#define QQ_LOGIN_REPLY_REDIRECT             0x01
-#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 lumaqq */
-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, PurpleConnection *gc, guint16 cmd, gboolean is_auto_seq, guint16 *seq);
-extern gint			/* defined in send_core.c */
- _qq_send_packet(PurpleConnection *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 guint8 *_gen_login_key(void)
-{
-	return (guint8 *) g_strnfill(QQ_KEY_LENGTH, 0x01);
-}
-
-/* process login reply which says OK */
-static gint _qq_process_login_ok(PurpleConnection *gc, guint8 *data, gint len)
-{
-	gint bytes;
-	guint8 *cursor;
-	qq_data *qd;
-	qq_login_reply_ok_packet lrop;
-
-	qd = (qq_data *) gc->proto_data;
-	cursor = data;
-	bytes = 0;
-
-	/* 000-000: reply code */
-	bytes += read_packet_b(data, &cursor, len, &lrop.result);
-	/* 001-016: session key */
-	lrop.session_key = g_memdup(cursor, QQ_KEY_LENGTH);
-	cursor += QQ_KEY_LENGTH;
-	bytes += QQ_KEY_LENGTH;
-	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Get session_key done\n");
-	/* 017-020: login uid */
-	bytes += read_packet_dw(data, &cursor, len, &lrop.uid);
-	/* 021-024: server detected user public IP */
-	bytes += read_packet_data(data, &cursor, len, (guint8 *) &lrop.client_ip, 4);
-	/* 025-026: server detected user port */
-	bytes += read_packet_w(data, &cursor, len, &lrop.client_port);
-	/* 027-030: server detected itself ip 127.0.0.1 ? */
-	bytes += read_packet_data(data, &cursor, len, (guint8 *) &lrop.server_ip, 4);
-	/* 031-032: server listening port */
-	bytes += read_packet_w(data, &cursor, len, &lrop.server_port);
-	/* 033-036: login time for current session */
-	bytes += read_packet_time(data, &cursor, len, &lrop.login_time);
-	/* 037-062: 26 bytes, unknown */
-	bytes += read_packet_data(data, &cursor, len, (guint8 *) &lrop.unknown1, 26);
-	/* 063-066: unknown server1 ip address */
-	bytes += read_packet_data(data, &cursor, len, (guint8 *) &lrop.unknown_server1_ip, 4);
-	/* 067-068: unknown server1 port */
-	bytes += read_packet_w(data, &cursor, len, &lrop.unknown_server1_port);
-	/* 069-072: unknown server2 ip address */
-	bytes += read_packet_data(data, &cursor, len, (guint8 *) &lrop.unknown_server2_ip, 4);
-	/* 073-074: unknown server2 port */
-	bytes += read_packet_w(data, &cursor, len, &lrop.unknown_server2_port);
-	/* 075-076: 2 bytes unknown */
-	bytes += read_packet_w(data, &cursor, len, &lrop.unknown2);
-	/* 077-078: 2 bytes unknown */
-	bytes += read_packet_w(data, &cursor, len, &lrop.unknown3);
-	/* 079-110: 32 bytes unknown */
-	bytes += read_packet_data(data, &cursor, len, (guint8 *) &lrop.unknown4, 32);
-	/* 111-122: 12 bytes unknown */
-	bytes += read_packet_data(data, &cursor, len, (guint8 *) &lrop.unknown5, 12);
-	/* 123-126: login IP of last session */
-	bytes += read_packet_data(data, &cursor, len, (guint8 *) &lrop.last_client_ip, 4);
-	/* 127-130: login time of last session */
-	bytes += read_packet_time(data, &cursor, len, &lrop.last_login_time);
-	/* 131-138: 8 bytes unknown */
-	bytes += read_packet_data(data, &cursor, len, (guint8 *) &lrop.unknown6, 8);
-
-	if (bytes != QQ_LOGIN_REPLY_OK_PACKET_LEN) {	/* fail parsing login info */
-		purple_debug(PURPLE_DEBUG_WARNING, "QQ",
-			   "Fail parsing login info, expect %d bytes, read %d bytes\n",
-			   QQ_LOGIN_REPLY_OK_PACKET_LEN, bytes);
-	}			/* but we still go on as login OK */
-
-	qd->session_key = lrop.session_key;
-	qd->session_md5 = _gen_session_md5(qd->uid, qd->session_key);
-	qd->my_ip = gen_ip_str(lrop.client_ip);
-	qd->my_port = lrop.client_port;
-	qd->login_time = lrop.login_time;
-	qd->last_login_time = lrop.last_login_time;
-	qd->last_login_ip = gen_ip_str(lrop.last_client_ip);
-
-	purple_connection_set_state(gc, PURPLE_CONNECTED);
-	qd->logged_in = TRUE;	/* must be defined after sev_finish_login */
-
-	/* now initiate QQ Qun, do it first as it may take longer to finish */
-	qq_group_init(gc);
-
-	/* Now goes on updating my icon/nickname, not showing info_window */
-	qd->modifying_face = FALSE;
-	qq_send_packet_get_info(gc, qd->uid, FALSE);
-	/* grab my level */
-	qq_send_packet_get_level(gc, qd->uid);
-
-	qq_send_packet_change_status(gc);
-
-	/* refresh buddies */
-	qq_send_packet_get_buddies_list(gc, QQ_FRIENDS_LIST_POSITION_START);
-	/* refresh groups */
-	qq_send_packet_get_all_list_with_group(gc, QQ_FRIENDS_LIST_POSITION_START);
-
-	return QQ_LOGIN_REPLY_OK;
-}
-
-/* process login reply packet which includes redirected new server address */
-static gint _qq_process_login_redirect(PurpleConnection *gc, guint8 *data, gint len)
-{
-	gint bytes, ret;
-	guint8 *cursor;
-	gchar *new_server_str;
-	qq_data *qd;
-	qq_login_reply_redirect_packet lrrp;
-
-	qd = (qq_data *) gc->proto_data;
-	cursor = data;
-	bytes = 0;
-	/* 000-000: reply code */
-	bytes += read_packet_b(data, &cursor, len, &lrrp.result);
-	/* 001-004: login uid */
-	bytes += read_packet_dw(data, &cursor, len, &lrrp.uid);
-	/* 005-008: redirected new server IP */
-	bytes += read_packet_data(data, &cursor, len, lrrp.new_server_ip, 4);
-	/* 009-010: redirected new server port */
-	bytes += read_packet_w(data, &cursor, len, &lrrp.new_server_port);
-
-	if (bytes != QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN) {
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ",
-			   "Fail parsing login redirect packet, expect %d bytes, read %d bytes\n",
-			   QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN, bytes);
-		ret = QQ_LOGIN_REPLY_MISC_ERROR;
-	} else {		/* start new connection */
-		new_server_str = gen_ip_str(lrrp.new_server_ip);
-		purple_debug(PURPLE_DEBUG_WARNING, "QQ",
-			   "Redirected to new server: %s:%d\n", new_server_str, lrrp.new_server_port);
-		qq_connect(gc->account, new_server_str, lrrp.new_server_port, qd->use_tcp, TRUE);
-		g_free(new_server_str);
-		ret = QQ_LOGIN_REPLY_REDIRECT;
-	}
-
-	return ret;
-}
-
-/* process login reply which says wrong password */
-static gint _qq_process_login_wrong_pwd(PurpleConnection *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);
-	purple_debug(PURPLE_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;
-}
-
-/* request before login */
-void qq_send_packet_request_login_token(PurpleConnection *gc)
-{
-	qq_data *qd;
-	guint8 *buf, *cursor;
-	guint16 seq_ret;
-	gint bytes;
-
-	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
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Fail create request login token packet\n");
-}
-
-/* send login packet to QQ server */
-static void qq_send_packet_login(PurpleConnection *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;
-
-	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_encrypt((guint8 *) "", 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 */
-	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_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
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Fail create login packet\n");
-}
-
-void qq_process_request_login_token_reply(guint8 *buf, gint buf_len, PurpleConnection *gc)
-{
-	qq_data *qd;
-	gchar *hex_dump;
-
-	g_return_if_fail(buf != NULL && buf_len != 0);
-
-	qd = (qq_data *) gc->proto_data;
-
-	if (buf[0] == QQ_REQUEST_LOGIN_TOKEN_REPLY_OK) {
-		if (buf[1] != buf_len-2) {
-			purple_debug(PURPLE_DEBUG_INFO, "QQ",
-					"Malformed login token reply packet. Packet specifies length of %d, actual length is %d\n", buf[1], buf_len-2);
-			purple_debug(PURPLE_DEBUG_INFO, "QQ",
-					"Attempting to proceed with the actual packet length.\n");
-		}
-		hex_dump = hex_dump_to_str(buf+2, buf_len-2);
-		purple_debug(PURPLE_DEBUG_INFO, "QQ",
-				"<<< got a token with %d bytes -> [default] decrypt and dump\n%s", buf_len-2, hex_dump);
-		qq_send_packet_login(gc, buf_len-2, buf+2);
-	} else {
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Unknown request login token reply code : %d\n", buf[0]);
-		hex_dump = hex_dump_to_str(buf, buf_len);
-		purple_debug(PURPLE_DEBUG_WARNING, "QQ",
-				">>> %d bytes -> [default] decrypt and dump\n%s",
-				buf_len, hex_dump);
-		try_dump_as_gbk(buf, buf_len);
-		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Error requesting login token"));
-	}
-	g_free(hex_dump);
-}
-
-/* send logout packets to QQ server */
-void qq_send_packet_logout(PurpleConnection *gc)
-{
-	gint i;
-	qq_data *qd;
-
-	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 */
-}
-
-/* process the login reply packet */
-void qq_process_login_reply(guint8 *buf, gint buf_len, PurpleConnection *gc)
-{
-	gint len, ret, bytes;
-	guint8 *data;
-	qq_data *qd;
-	gchar *hex_dump;
-
-	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_decrypt(buf, buf_len, qd->pwkey, data, &len)) {
-		/* should be able to decrypt with pwkey */
-		purple_debug(PURPLE_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 {
-			purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Unknown login reply code : %d\n", data[0]);
-			ret = QQ_LOGIN_REPLY_MISC_ERROR;
-		}
-	} else {		/* decrypt with pwkey error */
-		len = buf_len;	/* reset len, decrypt will fail if len is too short */
-		if (qq_decrypt(buf, buf_len, qd->inikey, data, &len)) {
-			/* decrypt ok with inipwd, it might be password error */
-			purple_debug(PURPLE_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:
-				purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Unknown reply code: %d\n", data[0]);
-				hex_dump = hex_dump_to_str(data, len);
-				purple_debug(PURPLE_DEBUG_WARNING, "QQ",
-						">>> %d bytes -> [default] decrypt and dump\n%s",
-						buf_len, hex_dump);
-				g_free(hex_dump);
-				try_dump_as_gbk(data, len);
-
-				ret = QQ_LOGIN_REPLY_MISC_ERROR;
-			}
-		} else {	/* no idea how to decrypt */
-			purple_debug(PURPLE_DEBUG_ERROR, "QQ", "No idea how to decrypt login reply\n");
-			ret = QQ_LOGIN_REPLY_MISC_ERROR;
-		}
-	}
-
-	switch (ret) {
-	case QQ_LOGIN_REPLY_PWD_ERROR:
-		if (!purple_account_get_remember_password(gc->account))
-			purple_account_set_password(gc->account, NULL);
-		purple_connection_error_reason(gc,
-			PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Incorrect password."));
-		break;
-	case QQ_LOGIN_REPLY_MISC_ERROR:
-			if (purple_debug_is_enabled())
-				purple_connection_error_reason(gc,
-					PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to login. Check debug log."));
-			else
-				purple_connection_error_reason(gc,
-					PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to login"));				
-		break;
-	case QQ_LOGIN_REPLY_OK:
-		purple_debug(PURPLE_DEBUG_INFO, "QQ", "Login repliess OK; everything is fine\n");
-		break;
-	case QQ_LOGIN_REPLY_REDIRECT:
-		/* the redirect has been done in _qq_process_login_reply */
-		break;
-	default:{;
-		}
-	}
-}
--- a/libpurple/protocols/qq/login_logout.h	Mon Aug 18 17:08:01 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-/**
- * file login_logout.h
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#ifndef _QQ_LOGIN_LOGOUT_H_
-#define _QQ_LOGIN_LOGOUT_H_
-
-#include <glib.h>
-#include "connection.h"
-
-#define QQ_LOGIN_MODE_NORMAL        0x0a
-#define QQ_LOGIN_MODE_AWAY	    0x1e
-#define QQ_LOGIN_MODE_HIDDEN        0x28
-
-void qq_send_packet_request_login_token(PurpleConnection *gc);
-void qq_process_request_login_token_reply(guint8 *buf, gint buf_len, PurpleConnection *gc);
-void qq_process_login_reply(guint8 *buf, gint buf_len, PurpleConnection *gc);
-void qq_send_packet_logout(PurpleConnection *gc);
-
-#endif
--- a/libpurple/protocols/qq/packet_parse.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/packet_parse.c	Thu Nov 20 21:13:56 2008 +0000
@@ -25,119 +25,155 @@
 #include <string.h>
 
 #include "packet_parse.h"
+#include "debug.h"
+
+/*------------------------------------------------PUT------------------------------------------------*/
+
+/* note:
+ * 1, in these functions, 'b' stands for byte, 'w' stands for word, 'dw' stands for double word.
+ * 2, we use '*cursor' and 'buf' as two addresses to calculate the length.
+ * 3, change '0' to '1', if want to get more info about the packet parsing. */
+
+#if 0
+#define PARSER_DEBUG
+#endif
 
 /* 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)
+gint qq_get8(guint8 *b, guint8 *buf)
 {
-	if (*cursor <= buf + buflen - sizeof(*b)) {
-		*b = **(guint8 **) cursor;
-		*cursor += sizeof(*b);
-		return sizeof(*b);
-	} else {
-		return -1;
-	}
+	guint8 b_dest;
+	memcpy(&b_dest, buf, sizeof(b_dest));
+	*b = b_dest;
+#ifdef PARSER_DEBUG
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "[DBG][get8] buf %p\n", (void *)buf);
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "[DBG][get8] b_dest 0x%2x, *b 0x%02x\n", b_dest, *b);
+#endif
+	return sizeof(b_dest);
 }
 
+
 /* 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)
+gint qq_get16(guint16 *w, guint8 *buf)
 {
-	if (*cursor <= buf + buflen - sizeof(*w)) {
-		*w = g_ntohs(**(guint16 **) cursor);
-		*cursor += sizeof(*w);
-		return sizeof(*w);
-	} else {
-		return -1;
-	}
+	guint16 w_dest;
+	memcpy(&w_dest, buf, sizeof(w_dest));
+	*w = g_ntohs(w_dest);
+#ifdef PARSER_DEBUG
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "[DBG][get16] buf %p\n", (void *)buf);
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "[DBG][get16] w_dest 0x%04x, *w 0x%04x\n", w_dest, *w);
+#endif
+	return sizeof(w_dest);
 }
 
 /* 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)
+gint qq_get32(guint32 *dw, guint8 *buf)
 {
-	if (*cursor <= buf + buflen - sizeof(*dw)) {
-		*dw = g_ntohl(**(guint32 **) cursor);
-		*cursor += sizeof(*dw);
-		return sizeof(*dw);
-	} else {
-		return -1;
-	}
+	guint32 dw_dest;
+	memcpy(&dw_dest, buf, sizeof(dw_dest));
+	*dw = g_ntohl(dw_dest);
+#ifdef PARSER_DEBUG
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "[DBG][get32] buf %p\n", (void *)buf);
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "[DBG][get32] dw_dest 0x%08x, *dw 0x%08x\n", dw_dest, *dw);
+#endif
+	return sizeof(dw_dest);
 }
 
-/* read four bytes as "time_t" from buf,
- * return the number of bytes read if succeeds, otherwise return -1
- * This function is a wrapper around read_packet_dw() to avoid casting. */
-gint read_packet_time(guint8 *buf, guint8 **cursor, gint buflen, time_t *t)
+gint qq_getIP(struct in_addr *ip, guint8 *buf)
 {
-	guint32 time;
-	gint ret = read_packet_dw(buf, cursor, buflen, &time);
-	if (ret != -1 ) {
-		*t = time;
-	}
-	return ret;
+	memcpy(ip, buf, sizeof(struct in_addr));
+	return sizeof(struct in_addr);
 }
 
 /* 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;
-	}
+gint qq_getdata(guint8 *data, gint datalen, guint8 *buf)
+{
+    memcpy(data, buf, datalen);
+#ifdef PARSER_DEBUG
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "[DBG][getdata] buf %p\n", (void *)buf);
+#endif
+    return datalen;
 }
 
+
+/* read four bytes as "time_t" from buf,
+ * return the number of bytes read if succeeds, otherwise return -1
+ * This function is a wrapper around read_packet_dw() to avoid casting. */
+gint qq_getime(time_t *t, guint8 *buf)
+{
+	guint32 dw_dest;
+	memcpy(&dw_dest, buf, sizeof(dw_dest));
+#ifdef PARSER_DEBUG
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "[DBG][getime] buf %p\n", (void *)buf);
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "[DBG][getime] dw_dest before 0x%08x\n", dw_dest);
+#endif
+	dw_dest = g_ntohl(dw_dest);
+#ifdef PARSER_DEBUG
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "[DBG][getime] dw_dest after 0x%08x\n", dw_dest);
+#endif
+	memcpy(t, &dw_dest, sizeof(dw_dest));
+	return sizeof(dw_dest);
+}
+
+/*------------------------------------------------PUT------------------------------------------------*/
 /* pack one byte into buf
  * return the number of bytes packed, otherwise return -1 */
-gint create_packet_b(guint8 *buf, guint8 **cursor, guint8 b)
+gint qq_put8(guint8 *buf, guint8 b)
 {
-	if (*cursor <= buf + MAX_PACKET_SIZE - sizeof(guint8)) {
-		**(guint8 **) cursor = b;
-		*cursor += sizeof(guint8);
-		return sizeof(guint8);
-	} else {
-		return -1;
-	}
+    memcpy(buf, &b, sizeof(b));
+#ifdef PARSER_DEBUG
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "[DBG][put8] buf %p\n", (void *)buf);
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "[DBG][put8] b 0x%02x\n", b);
+#endif
+    return sizeof(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)
+gint qq_put16(guint8 *buf, guint16 w)
 {
-	if (*cursor <= buf + MAX_PACKET_SIZE - sizeof(guint16)) {
-		**(guint16 **) cursor = g_htons(w);
-		*cursor += sizeof(guint16);
-		return sizeof(guint16);
-	} else {
-		return -1;
-	}
+    guint16 w_porter;
+    w_porter = g_htons(w);
+#ifdef PARSER_DEBUG
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "[DBG][put16] buf %p\n", (void *)buf);
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "[DBG][put16] w 0x%04x, w_porter 0x%04x\n", w, w_porter);
+#endif
+    memcpy(buf, &w_porter, sizeof(w_porter));
+    return sizeof(w_porter);
 }
 
+
 /* 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)
+gint qq_put32(guint8 *buf, guint32 dw)
 {
-	if (*cursor <= buf + MAX_PACKET_SIZE - sizeof(guint32)) {
-		**(guint32 **) cursor = g_htonl(dw);
-		*cursor += sizeof(guint32);
-		return sizeof(guint32);
-	} else {
-		return -1;
-	}
+    guint32 dw_porter;
+    dw_porter = g_htonl(dw);
+#ifdef PARSER_DEBUG
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "[DBG][put32] buf %p\n", (void *)buf);
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "[DBG][put32] dw 0x%08x, dw_porter 0x%08x\n", dw, dw_porter);
+#endif
+    memcpy(buf, &dw_porter, sizeof(dw_porter));
+    return sizeof(dw_porter);
+}
+
+gint qq_putIP(guint8* buf, struct in_addr *ip)
+{
+    memcpy(buf, ip, sizeof(struct in_addr));
+    return sizeof(struct in_addr);
 }
 
 /* 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)
+gint qq_putdata(guint8 *buf, const guint8 *data, const int datalen)
 {
-	if (*cursor <= buf + MAX_PACKET_SIZE - datalen) {
-		g_memmove(*cursor, data, datalen);
-		*cursor += datalen;
-		return datalen;
-	} else {
-		return -1;
-	}
+    memcpy(buf, data, datalen);
+#ifdef PARSER_DEBUG
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "[DBG][putdata] buf %p\n", (void *)buf);
+#endif
+    return datalen;
 }
--- a/libpurple/protocols/qq/packet_parse.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/packet_parse.h	Thu Nov 20 21:13:56 2008 +0000
@@ -37,14 +37,32 @@
  */
 #define MAX_PACKET_SIZE 65535
 
+#include <netinet/in.h>
+
+gint qq_get8(guint8 *b, guint8 *buf);
+gint qq_get16(guint16 *w, guint8 *buf);
+gint qq_get32(guint32 *dw,  guint8 *buf);
+gint qq_getIP(struct in_addr *ip, guint8 *buf);
+gint qq_getime(time_t *t, guint8 *buf);
+gint qq_getdata(guint8 *data, gint datalen, guint8 *buf);
+
+gint qq_put8(guint8 *buf, guint8 b);
+gint qq_put16(guint8 *buf, guint16 w);
+gint qq_put32(guint8 *buf, guint32 dw);
+gint qq_putIP(guint8* buf, struct in_addr *ip);
+gint qq_putdata(guint8 *buf, const guint8 *data, const int datalen);
+
+/*
 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_time(guint8 *buf, guint8 **cursor, gint buflen, time_t *t);
 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
--- a/libpurple/protocols/qq/qq.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/qq.c	Thu Nov 20 21:13:56 2008 +0000
@@ -40,7 +40,7 @@
 
 #include "buddy_info.h"
 #include "buddy_opt.h"
-#include "buddy_status.h"
+#include "buddy_list.h"
 #include "char_conv.h"
 #include "crypt.h"
 #include "group.h"
@@ -51,52 +51,98 @@
 #include "group_opt.h"
 #include "header_info.h"
 #include "im.h"
-#include "keep_alive.h"
-#include "login_logout.h"
+#include "qq_process.h"
+#include "qq_base.h"
 #include "packet_parse.h"
 #include "qq.h"
-#include "qq_proxy.h"
-#include "send_core.h"
+#include "qq_network.h"
 #include "send_file.h"
 #include "utils.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"
+
+#define QQ_TCP_PORT       		8000
+#define QQ_UDP_PORT             	8000
+
+static void server_list_create(PurpleAccount *account) {
+	PurpleConnection *gc;
+	qq_data *qd;
+	const gchar *user_server;
+	int port;
+
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Create server list\n");
+	gc = purple_account_get_connection(account);
+	g_return_if_fail(gc != NULL  && gc->proto_data != NULL);
+	qd = gc->proto_data;
 
-const gchar *udp_server_list[] = {
-	"sz.tencent.com",
-	"sz2.tencent.com",
-	"sz3.tencent.com",
-	"sz4.tencent.com",
-	"sz5.tencent.com",
-	"sz6.tencent.com",
-	"sz7.tencent.com",
-	"sz8.tencent.com",
-	"sz9.tencent.com"
-};
-const gint udp_server_amount = (sizeof(udp_server_list) / sizeof(udp_server_list[0]));
+	qd->use_tcp = purple_account_get_bool(account, "use_tcp", TRUE);
+	port = purple_account_get_int(account, "port", 0);
+	if (port == 0) {
+		if (qd->use_tcp) {
+			port = QQ_TCP_PORT;
+		} else {
+			port = QQ_UDP_PORT;
+		}
+	}
+	qd->user_port = port;
 
+ 	g_return_if_fail(qd->user_server == NULL);
+	user_server = purple_account_get_string(account, "server", NULL);
+	if (user_server != NULL && strlen(user_server) > 0) {
+		qd->user_server = g_strdup(user_server);
+	}
 
-const gchar *tcp_server_list[] = {
-	"tcpconn.tencent.com",
-	"tcpconn2.tencent.com",
-	"tcpconn3.tencent.com",
-	"tcpconn4.tencent.com",
-	"tcpconn5.tencent.com",
-	"tcpconn6.tencent.com"
-};
-const gint tcp_server_amount = (sizeof(tcp_server_list) / sizeof(tcp_server_list[0]));
+	if (qd->user_server != NULL) {
+		qd->servers = g_list_append(qd->servers, qd->user_server);
+		return;
+	}
+	if (qd->use_tcp) {
+		qd->servers = g_list_append(qd->servers, "tcpconn.tencent.com");
+		qd->servers = g_list_append(qd->servers, "tcpconn2.tencent.com");
+		qd->servers = g_list_append(qd->servers, "tcpconn3.tencent.com");
+		qd->servers = g_list_append(qd->servers, "tcpconn4.tencent.com");
+		qd->servers = g_list_append(qd->servers, "tcpconn5.tencent.com");
+		qd->servers = g_list_append(qd->servers, "tcpconn6.tencent.com");
+		return;
+    }
+    
+	qd->servers = g_list_append(qd->servers, "sz.tencent.com");
+	qd->servers = g_list_append(qd->servers, "sz2.tencent.com");
+	qd->servers = g_list_append(qd->servers, "sz3.tencent.com");
+	qd->servers = g_list_append(qd->servers, "sz4.tencent.com");
+	qd->servers = g_list_append(qd->servers, "sz5.tencent.com");
+	qd->servers = g_list_append(qd->servers, "sz6.tencent.com");
+	qd->servers = g_list_append(qd->servers, "sz7.tencent.com");
+	qd->servers = g_list_append(qd->servers, "sz8.tencent.com");
+	qd->servers = g_list_append(qd->servers, "sz9.tencent.com");
+}
 
-static void _qq_login(PurpleAccount *account)
+static void server_list_remove_all(qq_data *qd) {
+ 	g_return_if_fail(qd != NULL);
+
+	if (qd->real_hostname) {
+		purple_debug(PURPLE_DEBUG_INFO, "QQ", "free real_hostname\n");
+		g_free(qd->real_hostname);
+		qd->real_hostname = NULL;
+	}
+	
+	if (qd->user_server != NULL) {
+		purple_debug(PURPLE_DEBUG_INFO, "QQ", "free user_server\n");
+		g_free(qd->user_server);
+		qd->user_server = NULL;
+	}
+
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "free server list\n");
+ 	g_list_free(qd->servers);
+}
+
+static void qq_login(PurpleAccount *account)
 {
-	const gchar *qq_server, *qq_port;
+	PurpleConnection *gc;
 	qq_data *qd;
-	PurpleConnection *gc;
 	PurplePresence *presence;
-	gboolean use_tcp;
 
 	g_return_if_fail(account != NULL);
 
@@ -109,13 +155,7 @@
 	qd->gc = gc;
 	gc->proto_data = qd;
 
-	qq_server = purple_account_get_string(account, "server", NULL);
-	qq_port = purple_account_get_string(account, "port", NULL);
-	use_tcp = purple_account_get_bool(account, "use_tcp", FALSE);
 	presence = purple_account_get_presence(account);
-
-	qd->use_tcp = use_tcp;
-
 	if(purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_INVISIBLE)) {
 		qd->login_mode = QQ_LOGIN_MODE_HIDDEN;
 	} else if(purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_AWAY)
@@ -125,26 +165,28 @@
 		qd->login_mode = QQ_LOGIN_MODE_NORMAL;
 	}
 
-	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];
+	server_list_create(account);
+	purple_debug(PURPLE_DEBUG_INFO, "QQ",
+		"Server list has %d\n", g_list_length(qd->servers));
 
-	if (qq_port == NULL || strtol(qq_port, NULL, 10) == 0)
-		qq_port = use_tcp ? QQ_TCP_QUERY_PORT : QQ_UDP_PORT;
-
-	purple_connection_update_progress(gc, _("Connecting"), 0, QQ_CONNECT_STEPS);
-
-	if (qq_connect(account, qq_server, strtol(qq_port, NULL, 10), use_tcp, FALSE) < 0)
-		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-			_("Unable to connect."));
+	qq_connect(account);
 }
 
-/* directly goes for qq_disconnect */
-static void _qq_close(PurpleConnection *gc)
+/* clean up the given QQ connection and free all resources */
+static void qq_close(PurpleConnection *gc)
 {
-	g_return_if_fail(gc != NULL);
+	qq_data *qd;
+
+	g_return_if_fail(gc != NULL  && gc->proto_data);
+	qd = gc->proto_data;
+
 	qq_disconnect(gc);
+
+	server_list_remove_all(qd);
+	
+	g_free(qd);
+
+	gc->proto_data = NULL;
 }
 
 /* returns the icon name for a buddy or protocol */
@@ -195,78 +237,110 @@
 static void _qq_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full)
 {
 	qq_buddy *q_bud;
-	gchar *ip_str;
-	char *tmp;
-	const char *tmp2;
+	gchar *tmp;
+	GString *str;
 
 	g_return_if_fail(b != NULL);
 
 	q_bud = (qq_buddy *) b->proto_data;
-	g_return_if_fail(q_bud != NULL);
+	if (q_bud == NULL)
+		return;
+
+	/* if (PURPLE_BUDDY_IS_ONLINE(b) && q_bud != NULL) */
+	if (q_bud->ip.s_addr != 0) {
+		str = g_string_new(NULL);
+		g_string_printf(str, "%s:%d", inet_ntoa(q_bud->ip), q_bud->port);
+		if (q_bud->comm_flag & QQ_COMM_FLAG_TCP_MODE) {
+			g_string_append(str, " TCP");
+		} else {
+			g_string_append(str, " UDP");
+		}
+		g_string_free(str, TRUE);
+	}
 
-	if (PURPLE_BUDDY_IS_ONLINE(b) && q_bud != NULL)
-	{
-		ip_str = gen_ip_str(q_bud->ip);
-		if (strlen(ip_str) != 0) {
-			if (q_bud->comm_flag & QQ_COMM_FLAG_TCP_MODE)
-				tmp2 = _("TCP Address");
-			else
-				tmp2 = _("UDP Address");
-			tmp = g_strdup_printf("%s:%d", ip_str, q_bud->port);
-			purple_notify_user_info_add_pair(user_info, tmp2, tmp);
-			g_free(tmp);
-		}
-		g_free(ip_str);
+	tmp = g_strdup_printf("%d", q_bud->age);
+	purple_notify_user_info_add_pair(user_info, _("Age"), tmp);
+	g_free(tmp);
 
-		tmp = g_strdup_printf("%d", q_bud->age);
-		purple_notify_user_info_add_pair(user_info, _("Age"), tmp);
+	switch (q_bud->gender) {
+	case QQ_BUDDY_GENDER_GG:
+		purple_notify_user_info_add_pair(user_info, _("Gender"), _("Male"));
+		break;
+	case QQ_BUDDY_GENDER_MM:
+		purple_notify_user_info_add_pair(user_info, _("Gender"), _("Female"));
+		break;
+	case QQ_BUDDY_GENDER_UNKNOWN:
+		purple_notify_user_info_add_pair(user_info, _("Gender"), _("Unknown"));
+		break;
+	default:
+		tmp = g_strdup_printf("Error (%d)", q_bud->gender);
+		purple_notify_user_info_add_pair(user_info, _("Gender"), tmp);
 		g_free(tmp);
+	}
 
-		switch (q_bud->gender) {
-		case QQ_BUDDY_GENDER_GG:
-			purple_notify_user_info_add_pair(user_info, _("Gender"), _("Male"));
-			break;
-		case QQ_BUDDY_GENDER_MM:
-			purple_notify_user_info_add_pair(user_info, _("Gender"), _("Female"));
-			break;
-		case QQ_BUDDY_GENDER_UNKNOWN:
-			purple_notify_user_info_add_pair(user_info, _("Gender"), _("Unknown"));
-			break;
-		default:
-			tmp = g_strdup_printf("Error (%d)", q_bud->gender);
-			purple_notify_user_info_add_pair(user_info, _("Gender"), tmp);
-			g_free(tmp);
-		}
+	if (q_bud->level) {
+		tmp = g_strdup_printf("%d", q_bud->level);
+		purple_notify_user_info_add_pair(user_info, _("Level"), tmp);
+		g_free(tmp);
+	}
 
-		if (q_bud->level) {
-			tmp = g_strdup_printf("%d", q_bud->level);
-			purple_notify_user_info_add_pair(user_info, _("Level"), tmp);
-			g_free(tmp);
-		}
-		/* For debugging */
-		/*
-		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);
-		*/
+	str = g_string_new(NULL);
+	if (q_bud->comm_flag & QQ_COMM_FLAG_QQ_MEMBER) {
+		g_string_append( str, _("Member") );
+	}
+	if (q_bud->comm_flag & QQ_COMM_FLAG_QQ_VIP) {
+		g_string_append( str, _(" VIP") );
+	}
+	if (q_bud->comm_flag & QQ_COMM_FLAG_TCP_MODE) {
+		g_string_append( str, _(" TCP") );
+	}
+	if (q_bud->comm_flag & QQ_COMM_FLAG_MOBILE) {
+		g_string_append( str, _(" FromMobile") );
+	}
+	if (q_bud->comm_flag & QQ_COMM_FLAG_BIND_MOBILE) {
+		g_string_append( str, _(" BindMobile") );
+	}
+	if (q_bud->comm_flag & QQ_COMM_FLAG_VIDEO) {
+		g_string_append( str, _(" Video") );
 	}
+
+	if (q_bud->ext_flag & QQ_EXT_FLAG_SPACE) {
+		g_string_append( str, _(" Space") );
+	}
+	purple_notify_user_info_add_pair(user_info, _("Flag"), str->str);
+
+	g_string_free(str, TRUE);
+
+#ifdef DEBUG
+	tmp = g_strdup_printf( "%s (%04X)",
+										qq_get_ver_desc(q_bud->client_version),
+										q_bud->client_version );
+	purple_notify_user_info_add_pair(user_info, _("Ver"), tmp);
+	g_free(tmp);
+
+	tmp = g_strdup_printf( "Ext 0x%X, Comm 0x%X",
+												q_bud->ext_flag, q_bud->comm_flag );
+	purple_notify_user_info_add_pair(user_info, _("Flag"), tmp);
+	g_free(tmp);
+#endif
 }
 
 /* we can show tiny icons on the four corners of buddy icon, */
 static const char *_qq_list_emblem(PurpleBuddy *b)
 {
 	/* each char** are refering to a filename in pixmaps/purple/status/default/ */
-
-	qq_buddy *q_bud = b->proto_data;
+	qq_buddy *q_bud;
+	
+	if (!b || !(q_bud = b->proto_data)) {
+		return NULL;
+	}
 
-	if (q_bud) {
-		if (q_bud->comm_flag & QQ_COMM_FLAG_QQ_MEMBER)
-			return "qq_member";
-		/*
-		if (q_bud->comm_flag & QQ_COMM_FLAG_VIDEO)
-			return "video";
-		*/
-	}
+	if (q_bud->comm_flag & QQ_COMM_FLAG_MOBILE)
+		return "mobile";
+	if (q_bud->comm_flag & QQ_COMM_FLAG_VIDEO)
+		return "video";
+	if (q_bud->comm_flag & QQ_COMM_FLAG_QQ_MEMBER)
+		return "qq_member";
 
 	return NULL;
 }
@@ -351,6 +425,7 @@
 	group = qq_group_find_by_channel(gc, channel);
 	g_return_val_if_fail(group != NULL, -1);
 
+	purple_debug_info("QQ_MESG", "Send qun mesg in utf8: %s\n", message);
 	msg = utf8_to_qq(message, QQ_CHARSET_DEFAULT);
 	msg_with_qq_smiley = purple_smiley_to_qq(msg);
 	qq_send_packet_group_im(gc, group, msg_with_qq_smiley);
@@ -437,14 +512,15 @@
 	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>Current Online</b>: %d<br>\n"), qd->total_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>Server</b>: %s: %d<br>\n"), qd->server_name, qd->real_port);
 	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_printf(info, _("<b>Real hostname</b>: %s: %d<br>\n"), qd->real_hostname, qd->real_port);
+	g_string_append_printf(info, _("<b>My Public IP</b>: %s<br>\n"), inet_ntoa(qd->my_ip));
 
 	g_string_append(info, "<hr>\n");
 	g_string_append(info, "<i>Information below may not be accurate</i><br>\n");
@@ -593,30 +669,6 @@
 	return m;
 }
 
-
-static void _qq_keep_alive(PurpleConnection *gc)
-{
-	qq_group *group;
-	qq_data *qd;
-	GList *list;
-
-	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 */
-			qq_send_cmd_group_get_online_members(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(PurpleConnection *gc, gint channel, const gchar *who)
@@ -651,8 +703,8 @@
 	_qq_buddy_menu,						/* blist_node_menu */
 	qq_chat_info,						/* chat_info */
 	qq_chat_info_defaults,					/* chat_info_defaults */
-	_qq_login,						/* login */
-	_qq_close,						/* close */
+	qq_login,							/* open */
+	qq_close,						/* close */
 	_qq_send_im,						/* send_im */
 	NULL,							/* set_info */
 	NULL,							/* send_typing	*/
@@ -675,8 +727,8 @@
 	NULL,							/* chat_invite	*/
 	NULL,							/* chat_leave */
 	NULL,							/* chat_whisper */
-	_qq_chat_send,						/* chat_send */
-	_qq_keep_alive,						/* keepalive */
+	_qq_chat_send,			/* chat_send */
+	NULL,							/* keepalive */
 	NULL,							/* register_user */
 	_qq_get_chat_buddy_info,				/* get_cb_info	*/
 	NULL,							/* get_cb_away	*/
@@ -695,7 +747,7 @@
 	qq_roomlist_cancel,					/* roomlist_cancel */
 	NULL,							/* roomlist_expand_category */
 	NULL,							/* can_receive_file */
-	qq_send_file,						/* send_file */
+	NULL,							/* qq_send_file send_file */
 	NULL,							/* new xfer */
 	NULL,							/* offline_message */
 	NULL,							/* PurpleWhiteboardPrplOps */
@@ -706,7 +758,6 @@
 	NULL,
 	NULL,
 	NULL,
-	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
@@ -751,13 +802,22 @@
 {
 	PurpleAccountOption *option;
 
-	option = purple_account_option_bool_new(_("Connect using TCP"), "use_tcp", FALSE);
-	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
-
 	option = purple_account_option_string_new(_("Server"), "server", NULL);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 
-	option = purple_account_option_string_new(_("Port"), "port", NULL);
+	option = purple_account_option_int_new(_("Port"), "port", 0);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	option = purple_account_option_bool_new(_("Connect using TCP"), "use_tcp", TRUE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	option = purple_account_option_int_new(_("resend interval(s)"), "resend_interval", 10);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	option = purple_account_option_int_new(_("Keep alive interval(s)"), "keep_alive_interval", 60);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	option = purple_account_option_int_new(_("Update interval(s)"), "update_interval", 300);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 
 	my_protocol = plugin;
--- a/libpurple/protocols/qq/qq.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/qq.h	Thu Nov 20 21:13:56 2008 +0000
@@ -28,10 +28,12 @@
 #include <glib.h>
 #include "internal.h"
 #include "ft.h"
+#include "circbuffer.h"
+#include "dnsquery.h"
+#include "dnssrv.h"
 #include "proxy.h"
 #include "roomlist.h"
 
-#define QQ_FACES	    100
 #define QQ_KEY_LENGTH       16
 #define QQ_DEBUG            1	/* whether we are doing DEBUG */
 
@@ -42,6 +44,13 @@
 
 typedef struct _qq_data qq_data;
 typedef struct _qq_buddy qq_buddy;
+typedef struct _qq_interval qq_interval;
+
+struct _qq_interval {
+	gint resend;
+	gint keep_alive;
+	gint update; 
+};
 
 struct _qq_buddy {
 	guint32 uid;
@@ -49,10 +58,10 @@
 	guint8 age;
 	guint8 gender;
 	gchar *nickname;
-	guint8 ip[4];
+	struct in_addr ip;
 	guint16 port;
 	guint8 status;
-	guint8 flag1;
+	guint8 ext_flag;
 	guint8 comm_flag;	/* details in qq_buddy_list.c */
 	guint16 client_version;
 	guint8 onlineTime;
@@ -66,42 +75,65 @@
 };
 
 struct _qq_data {
-	gint fd;			/* socket file handler */
+	PurpleConnection *gc;
+
+	/* common network resource */
+	GList *servers;
+	gchar *user_server;
+	gint user_port;
+	gboolean use_tcp;		/* network in tcp or udp */
+	
+	gchar *server_name;
+	gboolean is_redirect;
+	gchar *real_hostname;	/* from real connction */
+	guint16 real_port;
+	guint reconnect_timeout;
+	gint reconnect_times;
+
+	PurpleProxyConnectData *connect_data;
+	gint fd;				/* socket file handler */
+	gint tx_handler; 	/* socket can_write handle, use in udp connecting and tcp send out */
+
+	qq_interval itv_config;
+	qq_interval itv_count;
+	guint network_timeout;
+	
+	GList *transactions;	/* check ack packet and resend */
+
+	/* tcp related */
+	PurpleCircBuffer *tcp_txbuf;
+	guint8 *tcp_rxqueue;
+	int tcp_rxlen;
+	
+	/* udp related */
+	PurpleDnsQueryData *udp_query_data;
+
 	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 */
-	guint8 *session_md5;		/* concatenate my uid with session_key and md5 it */
+	guint8 *token;		/* get from server*/
+	int token_len;
+	guint8 inikey[QQ_KEY_LENGTH];			/* initial key to encrypt login packet */
+	guint8 password_twice_md5[QQ_KEY_LENGTH];			/* password in md5 (or md5' md5) */
+	guint8 session_key[QQ_KEY_LENGTH];		/* later use this as key in this session */
+	guint8 session_md5[QQ_KEY_LENGTH];		/* concatenate my uid with session_key and md5 it */
 
 	guint16 send_seq;		/* send sequence number */
 	guint8 login_mode;		/* online of invisible */
 	gboolean logged_in;		/* used by qq-add_buddy */
-	gboolean use_tcp;		/* network in tcp or udp */
-
-	PurpleProxyType proxy_type;
-	PurpleConnection *gc;
 
 	PurpleXfer *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 */
+	struct in_addr my_ip;			/* my ip address detected by server */
 	guint16 my_port;		/* my port detected by server */
 	guint16 my_icon;		/* my icon index */
 	guint16 my_level;		/* my level */
-	guint32 all_online;		/* the number of online QQ users */
+	guint32 total_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;
-
 	PurpleRoomlist *roomlist;
 	gint channel;			/* the id for opened chat conversation */
 
@@ -112,16 +144,12 @@
 	GList *buddies;
 	GList *contact_info_window;
 	GList *group_info_window;
-	GList *sendqueue;
 	GList *info_query;
 	GList *add_buddy_request;
-	GQueue *before_login_packets;
 
 	/* TODO pass qq_send_packet_get_info() a callback and use signals to get rid of these */
 	gboolean modifying_info;
 	gboolean modifying_face;
 };
 
-void qq_function_not_implemented(PurpleConnection *gc);
-
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/qq/qq_base.c	Thu Nov 20 21:13:56 2008 +0000
@@ -0,0 +1,523 @@
+/**
+ * @file qq_base.c
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#include "debug.h"
+#include "internal.h"
+#include "server.h"
+#include "cipher.h"
+
+#include "buddy_info.h"
+#include "buddy_list.h"
+#include "char_conv.h"
+#include "crypt.h"
+#include "group.h"
+#include "header_info.h"
+#include "qq_base.h"
+#include "packet_parse.h"
+#include "qq.h"
+#include "qq_network.h"
+#include "utils.h"
+
+#define QQ_LOGIN_DATA_LENGTH		    416
+#define QQ_LOGIN_REPLY_OK_PACKET_LEN        139
+#define QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN  11
+
+/* 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 lumaqq */
+/* FIXME: change to guint8 */
+static const guint8 login_23_51[29] = {
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x86, 0xcc, 0x4c, 0x35,
+	0x2c, 0xd3, 0x73, 0x6c, 0x14, 0xf6, 0xf6, 0xaf,
+	0xc3, 0xfa, 0x33, 0xa4, 0x01
+};
+
+static const guint8 login_53_68[16] = {
+ 	0x8D, 0x8B, 0xFA, 0xEC, 0xD5, 0x52, 0x17, 0x4A,
+ 	0x86, 0xF9, 0xA7, 0x75, 0xE6, 0x32, 0xD1, 0x6D
+};
+
+static const guint8 login_100_bytes[100] = {
+	0x40, 0x0B, 0x04, 0x02, 0x00, 0x01, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x03, 0x09, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x01, 0xE9, 0x03, 0x01,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF3, 0x03,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xED,
+	0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+	0xEC, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x03, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x03, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x01, 0xEE, 0x03, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x01, 0xEF, 0x03, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x01, 0xEB, 0x03, 0x00,
+	0x00, 0x00, 0x00, 0x00
+};
+
+
+/* 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[QQ_KEY_LENGTH];
+	guint32 uid;
+	struct in_addr client_ip;	/* those detected by server */
+	guint16 client_port;
+	struct in_addr server_ip;
+	guint16 server_port;
+	time_t login_time;
+	guint8 unknown1[26];
+	struct in_addr unknown_server1_ip;
+	guint16 unknown_server1_port;
+	struct in_addr unknown_server2_ip;
+	guint16 unknown_server2_port;
+	guint16 unknown2;	/* 0x0001 */
+	guint16 unknown3;	/* 0x0000 */
+	guint8 unknown4[32];
+	guint8 unknown5[12];
+	struct in_addr last_client_ip;
+	time_t last_login_time;
+	guint8 unknown6[8];
+};
+
+struct _qq_login_reply_redirect {
+	guint8 result;
+	guint32 uid;
+	struct in_addr new_server_ip;
+	guint16 new_server_port;
+};
+
+/* generate a md5 key using uid and session_key */
+static void get_session_md5(guint8 *session_md5, guint32 uid, guint8 *session_key)
+{
+	guint8 src[QQ_KEY_LENGTH + QQ_KEY_LENGTH];
+	gint bytes = 0;
+	
+	bytes += qq_put32(src + bytes, uid);
+	bytes += qq_putdata(src + bytes, session_key, QQ_KEY_LENGTH);
+
+	qq_get_md5(session_md5, QQ_KEY_LENGTH, src, bytes);
+}
+
+/* process login reply which says OK */
+static gint8 process_login_ok(PurpleConnection *gc, guint8 *data, gint len)
+{
+	gint bytes;
+	qq_data *qd;
+	qq_login_reply_ok_packet lrop;
+
+	qd = (qq_data *) gc->proto_data;
+	/* FIXME, check QQ_LOGIN_REPLY_OK_PACKET_LEN here */
+	bytes = 0;
+
+	/* 000-000: reply code */
+	bytes += qq_get8(&lrop.result, data + bytes);
+	/* 001-016: session key */
+	bytes += qq_getdata(lrop.session_key, sizeof(lrop.session_key), data + bytes);
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Get session_key done\n");
+	/* 017-020: login uid */
+	bytes += qq_get32(&lrop.uid, data + bytes);
+	/* 021-024: server detected user public IP */
+	bytes += qq_getIP(&lrop.client_ip, data + bytes);
+	/* 025-026: server detected user port */
+	bytes += qq_get16(&lrop.client_port, data + bytes);
+	/* 027-030: server detected itself ip 127.0.0.1 ? */
+	bytes += qq_getIP(&lrop.server_ip, data + bytes);
+	/* 031-032: server listening port */
+	bytes += qq_get16(&lrop.server_port, data + bytes);
+	/* 033-036: login time for current session */
+	bytes += qq_getime(&lrop.login_time, data + bytes);
+	/* 037-062: 26 bytes, unknown */
+	bytes += qq_getdata((guint8 *) &lrop.unknown1, 26, data + bytes);
+	/* 063-066: unknown server1 ip address */
+	bytes += qq_getIP(&lrop.unknown_server1_ip, data + bytes);
+	/* 067-068: unknown server1 port */
+	bytes += qq_get16(&lrop.unknown_server1_port, data + bytes);
+	/* 069-072: unknown server2 ip address */
+	bytes += qq_getIP(&lrop.unknown_server2_ip, data + bytes);
+	/* 073-074: unknown server2 port */
+	bytes += qq_get16(&lrop.unknown_server2_port, data + bytes);
+	/* 075-076: 2 bytes unknown */
+	bytes += qq_get16(&lrop.unknown2, data + bytes);
+	/* 077-078: 2 bytes unknown */
+	bytes += qq_get16(&lrop.unknown3, data + bytes);
+	/* 079-110: 32 bytes unknown */
+	bytes += qq_getdata((guint8 *) &lrop.unknown4, 32, data + bytes);
+	/* 111-122: 12 bytes unknown */
+	bytes += qq_getdata((guint8 *) &lrop.unknown5, 12, data + bytes);
+	/* 123-126: login IP of last session */
+	bytes += qq_getIP(&lrop.last_client_ip, data + bytes);
+	/* 127-130: login time of last session */
+	bytes += qq_getime(&lrop.last_login_time, data + bytes);
+	/* 131-138: 8 bytes unknown */
+	bytes += qq_getdata((guint8 *) &lrop.unknown6, 8, data + bytes);
+
+	if (bytes != QQ_LOGIN_REPLY_OK_PACKET_LEN) {	/* fail parsing login info */
+		purple_debug(PURPLE_DEBUG_WARNING, "QQ",
+			   "Fail parsing login info, expect %d bytes, read %d bytes\n",
+			   QQ_LOGIN_REPLY_OK_PACKET_LEN, bytes);
+	}			/* but we still go on as login OK */
+
+	memcpy(qd->session_key, lrop.session_key, sizeof(qd->session_key));
+	get_session_md5(qd->session_md5, qd->uid, qd->session_key);
+	
+	qd->my_ip.s_addr = lrop.client_ip.s_addr;
+	
+	qd->my_port = lrop.client_port;
+	qd->login_time = lrop.login_time;
+	qd->last_login_time = lrop.last_login_time;
+	qd->last_login_ip = g_strdup( inet_ntoa(lrop.last_client_ip) );
+
+	return QQ_LOGIN_REPLY_OK;
+}
+
+/* process login reply packet which includes redirected new server address */
+static gint8 process_login_redirect(PurpleConnection *gc, guint8 *data, gint len)
+{
+	qq_data *qd;
+	gint bytes;
+	qq_login_reply_redirect_packet lrrp;
+
+	qd = (qq_data *) gc->proto_data;
+	bytes = 0;
+	/* 000-000: reply code */
+	bytes += qq_get8(&lrrp.result, data + bytes);
+	/* 001-004: login uid */
+	bytes += qq_get32(&lrrp.uid, data + bytes);
+	/* 005-008: redirected new server IP */
+	bytes += qq_getIP(&lrrp.new_server_ip, data + bytes);
+	/* 009-010: redirected new server port */
+	bytes += qq_get16(&lrrp.new_server_port, data + bytes);
+
+	if (bytes != QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ",
+			   "Fail parsing login redirect packet, expect %d bytes, read %d bytes\n",
+			   QQ_LOGIN_REPLY_REDIRECT_PACKET_LEN, bytes);
+		return QQ_LOGIN_REPLY_ERR_MISC;
+	}
+	
+	/* redirect to new server, do not disconnect or connect here
+	 * those connect should be called at packet_process */
+	if (qd->real_hostname) {
+		purple_debug(PURPLE_DEBUG_INFO, "QQ", "free real_hostname\n");
+		g_free(qd->real_hostname);
+		qd->real_hostname = NULL;
+	}
+	qd->real_hostname = g_strdup( inet_ntoa(lrrp.new_server_ip) );
+	qd->real_port = lrrp.new_server_port;
+
+	return QQ_LOGIN_REPLY_REDIRECT;
+}
+
+/* process login reply which says wrong password */
+static gint8 process_login_wrong_pwd(PurpleConnection *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);
+	purple_debug(PURPLE_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_ERR_PWD;
+}
+
+/* request before login */
+void qq_send_packet_token(PurpleConnection *gc)
+{
+	qq_data *qd;
+	guint8 buf[16] = {0};
+	gint bytes = 0;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	bytes += qq_put8(buf + bytes, 0);
+	
+	qd->send_seq++;
+	qq_send_data(qd, QQ_CMD_TOKEN, qd->send_seq, TRUE, buf, bytes);
+}
+
+/* send login packet to QQ server */
+void qq_send_packet_login(PurpleConnection *gc)
+{
+	qq_data *qd;
+	guint8 *buf, *raw_data;
+	gint bytes;
+	guint8 *encrypted_data;
+	gint encrypted_len;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	g_return_if_fail(qd->token != NULL && qd->token_len > 0);
+
+	raw_data = g_newa(guint8, QQ_LOGIN_DATA_LENGTH);
+	memset(raw_data, 0, QQ_LOGIN_DATA_LENGTH);
+
+	encrypted_data = g_newa(guint8, QQ_LOGIN_DATA_LENGTH + 16);	/* 16 bytes more */
+#ifdef DEBUG
+	memset(qd->inikey, 0x01, sizeof(qd->inikey));
+#else
+	for (bytes = 0; bytes < sizeof(qd->inikey); bytes++)	{
+		qd->inikey[bytes] = (guint8) (g_random_int_range(0, 255) % 256);
+	}
+#endif
+
+	bytes = 0;
+	/* now generate the encrypted data
+	 * 000-015 use password_twice_md5 as key to encrypt empty string */
+	qq_encrypt((guint8 *) "", 0, qd->password_twice_md5, raw_data + bytes, &encrypted_len);
+	bytes += 16;
+	/* 016-016 */
+	bytes += qq_put8(raw_data + bytes, 0x00);
+	/* 017-020, used to be IP, now zero */
+	bytes += qq_put32(raw_data + bytes, 0x00000000);
+	/* 021-022, used to be port, now zero */
+	bytes += qq_put16(raw_data + bytes, 0x0000);
+	/* 023-051, fixed value, unknown */
+	bytes += qq_putdata(raw_data + bytes, login_23_51, 29);
+	/* 052-052, login mode */
+	bytes += qq_put8(raw_data + bytes, qd->login_mode);
+	/* 053-068, fixed value, maybe related to per machine */
+	bytes += qq_putdata(raw_data + bytes, login_53_68, 16);
+	/* 069, login token length */
+	bytes += qq_put8(raw_data + bytes, qd->token_len);
+	/* 070-093, login token, normally 24 bytes */
+	bytes += qq_putdata(raw_data + bytes, qd->token, qd->token_len);
+	/* 100 bytes unknown */
+	bytes += qq_putdata(raw_data + bytes, login_100_bytes, 100);
+	/* all zero left */
+
+	qq_encrypt(raw_data, QQ_LOGIN_DATA_LENGTH, qd->inikey, encrypted_data, &encrypted_len);
+
+	buf = g_newa(guint8, MAX_PACKET_SIZE);
+	memset(buf, 0, MAX_PACKET_SIZE);
+	bytes = 0;
+	bytes += qq_putdata(buf + bytes, qd->inikey, QQ_KEY_LENGTH);
+	bytes += qq_putdata(buf + bytes, encrypted_data, encrypted_len);
+
+	qd->send_seq++;
+	qq_send_data(qd, QQ_CMD_LOGIN, qd->send_seq, TRUE, buf, bytes);
+}
+
+guint8 qq_process_token_reply(PurpleConnection *gc, gchar *error_msg, guint8 *buf, gint buf_len)
+{
+	qq_data *qd;
+	guint8 ret;
+	int token_len;
+
+	g_return_val_if_fail(buf != NULL && buf_len != 0, -1);
+
+	g_return_val_if_fail(gc != NULL  && gc->proto_data != NULL, -1);
+	qd = (qq_data *) gc->proto_data;
+
+	ret = buf[0];
+	
+	if (ret != QQ_TOKEN_REPLY_OK) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Unknown request login token reply code : %d\n", buf[0]);
+		qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ",
+				buf, buf_len,
+				">>> [default] decrypt and dump");
+		error_msg = try_dump_as_gbk(buf, buf_len);
+		return ret;
+	}
+	
+	token_len = buf_len-2;
+	if (token_len <= 0) {
+		error_msg = g_strdup_printf( _("Invalid token len, %d"), token_len);
+		return -1;
+	}
+	
+	if (buf[1] != token_len) {
+		purple_debug(PURPLE_DEBUG_INFO, "QQ",
+				"Invalid token len. Packet specifies length of %d, actual length is %d\n", buf[1], buf_len-2);
+	}
+	qq_hex_dump(PURPLE_DEBUG_INFO, "QQ",
+			buf+2, token_len,
+			"<<< got a token -> [default] decrypt and dump");
+			
+	qd->token = g_new0(guint8, token_len);
+	qd->token_len = token_len;
+	g_memmove(qd->token, buf + 2, qd->token_len);
+	return ret;
+}
+
+/* send logout packets to QQ server */
+void qq_send_packet_logout(PurpleConnection *gc)
+{
+	gint i;
+	qq_data *qd;
+
+	qd = (qq_data *) gc->proto_data;
+	for (i = 0; i < 4; i++)
+		qq_send_cmd_detail(qd, QQ_CMD_LOGOUT, 0xffff, FALSE, qd->password_twice_md5, QQ_KEY_LENGTH);
+
+	qd->logged_in = FALSE;	/* update login status AFTER sending logout packets */
+}
+
+/* process the login reply packet */
+guint8 qq_process_login_reply(guint8 *buf, gint buf_len, PurpleConnection *gc)
+{
+	qq_data *qd;
+	guint8 *data;
+	gint data_len;
+	gchar* error_msg;
+
+	g_return_val_if_fail(buf != NULL && buf_len != 0, QQ_LOGIN_REPLY_ERR_MISC);
+
+	qd = (qq_data *) gc->proto_data;
+
+	data_len = buf_len;
+	data = g_newa(guint8, data_len);
+
+	if (qq_decrypt(buf, buf_len, qd->inikey, data, &data_len)) {
+		purple_debug(PURPLE_DEBUG_WARNING, "QQ", 
+				"Decrypt login reply packet with inikey, %d bytes\n", data_len);
+	} else {
+		/* reset data_len since it may changed */
+		data_len = buf_len;
+		if (qq_decrypt(buf, buf_len, qd->password_twice_md5, data, &data_len)) {
+			purple_debug(PURPLE_DEBUG_INFO, "QQ",
+				"Decrypt login reply packet with password_twice_md5, %d bytes\n", data_len);
+		} else {
+			purple_debug(PURPLE_DEBUG_ERROR, "QQ",
+					"No idea how to decrypt login reply\n");
+			return QQ_LOGIN_REPLY_ERR_MISC;
+		}
+	}
+	
+	switch (data[0]) {
+		case QQ_LOGIN_REPLY_OK:
+			purple_debug(PURPLE_DEBUG_INFO, "QQ", "Login reply is OK\n");
+			return process_login_ok(gc, data, data_len);
+		case QQ_LOGIN_REPLY_REDIRECT:
+			purple_debug(PURPLE_DEBUG_INFO, "QQ", "Login reply is redirect\n");
+			return process_login_redirect(gc, data, data_len);
+		case QQ_LOGIN_REPLY_ERR_PWD:
+			purple_debug(PURPLE_DEBUG_INFO, "QQ", "Login reply is error password\n");
+			return process_login_wrong_pwd(gc, data, data_len);
+		case QQ_LOGIN_REPLY_NEED_REACTIVE:
+		case QQ_LOGIN_REPLY_REDIRECT_EX:
+			purple_debug(PURPLE_DEBUG_INFO, "QQ", "Login reply is not actived or redirect extend\n");
+		default:
+		break;
+	}
+
+	purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Unknown reply code: %d\n", data[0]);
+			qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ",
+			data, data_len,
+			">>> [default] decrypt and dump");
+	error_msg = try_dump_as_gbk(data, data_len);
+	if (error_msg)	{
+			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_msg);
+			g_free(error_msg);
+	}
+	return QQ_LOGIN_REPLY_ERR_MISC;
+}
+
+/* send keep-alive packet to QQ server (it is a heart-beat) */
+void qq_send_packet_keep_alive(PurpleConnection *gc)
+{
+	qq_data *qd;
+	guint8 raw_data[16] = {0};
+	gint bytes= 0;
+
+	qd = (qq_data *) gc->proto_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 */
+	bytes += qq_put32(raw_data + bytes, qd->uid);
+
+	qq_send_cmd(qd, QQ_CMD_KEEP_ALIVE, raw_data, 4);
+}
+
+/* parse the return of keep-alive packet, it includes some system information */
+gboolean qq_process_keep_alive(guint8 *buf, gint buf_len, PurpleConnection *gc) 
+{
+	qq_data *qd;
+	gint len;
+	gchar **segments;
+	guint8 *data;
+
+	g_return_val_if_fail(buf != NULL && buf_len != 0, FALSE);
+
+	qd = (qq_data *) gc->proto_data;
+	len = buf_len;
+	data = g_newa(guint8, len);
+
+	if ( !qq_decrypt(buf, buf_len, qd->session_key, data, &len) ) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt keep alive reply\n");
+		return FALSE;
+	}
+
+	/* qq_show_packet("Keep alive reply packet", data, len); */
+
+	/* the last one is 60, don't know what it is */
+	if (NULL == (segments = split_data(data, len, "\x1f", 6)))
+			return TRUE;
+			
+	/* segments[0] and segment[1] are all 0x30 ("0") */
+	qd->total_online = strtol(segments[2], NULL, 10);
+	if(0 == qd->total_online) {
+		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Keep alive error"));
+	}
+	qd->my_ip.s_addr = inet_addr(segments[3]);
+	qd->my_port = strtol(segments[4], NULL, 10);
+
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "keep alive, %s:%d\n",
+		inet_ntoa(qd->my_ip), qd->my_port);
+	
+	g_strfreev(segments);
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/qq/qq_base.h	Thu Nov 20 21:13:56 2008 +0000
@@ -0,0 +1,56 @@
+/**
+ * file qq_base.h
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#ifndef _QQ_BASE_H_
+#define _QQ_BASE_H_
+
+#include <glib.h>
+#include "connection.h"
+
+#define QQ_TOKEN_REPLY_OK 	0x00
+
+#define QQ_LOGIN_REPLY_OK							0x00
+#define QQ_LOGIN_REPLY_REDIRECT				0x01
+#define QQ_LOGIN_REPLY_ERR_PWD					0x05
+#define QQ_LOGIN_REPLY_NEED_REACTIVE		0x06
+#define QQ_LOGIN_REPLY_REDIRECT_EX			0x0A
+#define QQ_LOGIN_REPLY_ERR_MISC				0xff	/* defined by myself */
+
+#define QQ_LOGIN_MODE_NORMAL        0x0a
+#define QQ_LOGIN_MODE_AWAY	    0x1e
+#define QQ_LOGIN_MODE_HIDDEN        0x28
+
+#define QQ_UPDATE_ONLINE_INTERVAL   300	/* in sec */
+
+void qq_send_packet_token(PurpleConnection *gc);
+guint8 qq_process_token_reply(PurpleConnection *gc, gchar *error_msg, guint8 *buf, gint buf_len);
+
+void qq_send_packet_login(PurpleConnection *gc);
+guint8 qq_process_login_reply(guint8 *buf, gint buf_len, PurpleConnection *gc);
+
+void qq_send_packet_logout(PurpleConnection *gc);
+
+void qq_send_packet_keep_alive(PurpleConnection *gc);
+gboolean qq_process_keep_alive(guint8 *buf, gint buf_len, PurpleConnection *gc);
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/qq/qq_network.c	Thu Nov 20 21:13:56 2008 +0000
@@ -0,0 +1,1045 @@
+/**
+ * @file qq_network.c
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#include "cipher.h"
+#include "debug.h"
+#include "internal.h"
+
+#ifdef _WIN32
+#define random rand
+#define srandom srand
+#endif
+
+#include "buddy_info.h"
+#include "group_info.h"
+#include "group_free.h"
+#include "crypt.h"
+#include "header_info.h"
+#include "qq_base.h"
+#include "buddy_list.h"
+#include "packet_parse.h"
+#include "qq_network.h"
+#include "qq_trans.h"
+#include "utils.h"
+#include "qq_process.h"
+
+/* set QQ_RECONNECT_MAX to 1, when test reconnecting */
+#define QQ_RECONNECT_MAX					4
+#define QQ_RECONNECT_INTERVAL		5000
+#define QQ_KEEP_ALIVE_INTERVAL		60000
+#define QQ_TRANS_INTERVAL				10000
+
+static gboolean set_new_server(qq_data *qd)
+{
+	gint count;
+	gint index;
+	GList *it = NULL;
+	
+ 	g_return_val_if_fail(qd != NULL, FALSE);
+
+	if (qd->servers == NULL) {
+		purple_debug(PURPLE_DEBUG_INFO, "QQ", "Server list is NULL\n");
+		return FALSE;
+	}
+
+	if (qd->real_hostname) {
+		purple_debug(PURPLE_DEBUG_INFO, "QQ", "free real_hostname\n");
+		g_free(qd->real_hostname);
+		qd->real_hostname = NULL;
+	}
+
+	/* remove server used before */
+	if (qd->server_name != NULL) {
+		purple_debug(PURPLE_DEBUG_INFO, "QQ",
+			"Remove previous server [%s]\n", qd->server_name);
+   		qd->servers = g_list_remove(qd->servers, qd->server_name);
+   		qd->server_name = NULL;
+    }
+	
+	count = g_list_length(qd->servers);
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Server list has %d\n", count);
+	if (count <= 0) {
+		/* no server left, disconnect when result is false */
+		qd->servers = NULL;
+		return FALSE;
+	}
+	
+	/* get new server */
+	index  = random() % count;
+	it = g_list_nth(qd->servers, index);
+    qd->server_name = it->data;		/* do not free server_name */
+    if (qd->server_name == NULL || strlen(qd->server_name) <= 0 ) {
+		purple_debug(PURPLE_DEBUG_INFO, "QQ", "Server name at %d is empty\n", index);
+		return FALSE;
+	}
+
+	qd->real_hostname = g_strdup(qd->server_name);
+	qd->real_port = qd->user_port;
+	
+ 	qd->reconnect_times = QQ_RECONNECT_MAX;
+
+	purple_debug(PURPLE_DEBUG_INFO, "QQ",
+		"set new server to %s:%d\n", qd->real_hostname, qd->real_port);
+	return TRUE;
+}
+
+static gint packet_get_header(guint8 *header_tag,  guint16 *source_tag,
+	guint16 *cmd, guint16 *seq, guint8 *buf)
+{
+	gint bytes = 0;
+	bytes += qq_get8(header_tag, buf + bytes);
+	bytes += qq_get16(source_tag, buf + bytes);
+	bytes += qq_get16(cmd, buf + bytes);
+	bytes += qq_get16(seq, buf + bytes);
+	return bytes;
+}
+
+static gboolean reconnect_later_cb(gpointer data)
+{
+	PurpleConnection *gc;
+	qq_data *qd;
+
+	gc = (PurpleConnection *) data;
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, FALSE);
+	qd = (qq_data *) gc->proto_data;
+
+	qd->reconnect_timeout = 0;
+
+	qq_connect(gc->account);
+	return FALSE;	/* timeout callback stops */
+}
+
+static void reconnect_later(PurpleConnection *gc)
+{
+	qq_data *qd;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	qd->reconnect_times--;
+	if (qd->reconnect_times < 0) {
+		if ( set_new_server(qd) != TRUE) {
+			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+					_("Failed to connect server"));
+			return;
+		}
+	}
+
+	purple_debug(PURPLE_DEBUG_INFO, "QQ",
+		"Reconnect to server %s:%d next retries %d in %d ms\n",
+		qd->real_hostname, qd->real_port,
+		qd->reconnect_times, QQ_RECONNECT_INTERVAL);
+
+	qd->reconnect_timeout = purple_timeout_add(QQ_RECONNECT_INTERVAL,
+		reconnect_later_cb, gc);
+}
+
+/* process the incoming packet from qq_pending */
+static void packet_process(PurpleConnection *gc, guint8 *buf, gint buf_len)
+{
+	qq_data *qd;
+	gint bytes, bytes_not_read;
+
+	gboolean prev_login_status;
+	
+	guint8 header_tag;
+	guint16 source_tag;
+	guint16 cmd;
+	guint16 seq;		/* May be ack_seq or send_seq, depends on cmd */
+
+	qq_transaction *trans;
+
+	g_return_if_fail(buf != NULL && buf_len > 0);
+
+	qd = (qq_data *) gc->proto_data;
+
+	prev_login_status = qd->logged_in;
+
+	/* Len, header and tail tag have been checked before */
+	bytes = 0;
+	bytes += packet_get_header(&header_tag, &source_tag, &cmd, &seq, buf + bytes);
+
+	if (QQ_DEBUG) {
+		purple_debug(PURPLE_DEBUG_INFO, "QQ",
+				"==> [%05d] 0x%04X %s, from (0x%04X %s)\n",
+				seq, cmd, qq_get_cmd_desc(cmd), source_tag, qq_get_ver_desc(source_tag));
+	}
+	
+	bytes_not_read = buf_len - bytes - 1;
+
+	/* ack packet, we need to update send tranactions */
+	/* we do not check duplication for server ack */
+	trans = qq_trans_find_rcved(qd, cmd, seq);
+	if (trans == NULL) {
+		/* new server command */
+		qq_trans_add_server_cmd(qd, cmd, seq, buf + bytes, bytes_not_read);
+		if ( qd->logged_in ) {
+			qq_proc_cmd_server(gc, cmd, seq, buf + bytes, bytes_not_read);
+		}
+		return;
+	}
+
+	if (qq_trans_is_dup(trans)) {
+		purple_debug(PURPLE_DEBUG_WARNING,
+				"QQ", "dup [%05d] %s, discard...\n", seq, qq_get_cmd_desc(cmd));
+		return;
+	}
+
+	if (qq_trans_is_server(trans)) {
+		if ( qd->logged_in ) {
+			qq_proc_cmd_server(gc, cmd, seq, buf + bytes, bytes_not_read);
+		}
+		return;
+	}
+
+	/* this is the length of all the encrypted data (also remove tail tag */
+	qq_proc_cmd_reply(gc, cmd, seq, buf + bytes, bytes_not_read);
+
+	/* check is redirect or not, and do it now */
+	if (qd->is_redirect) {
+	 	/* free resource except real_hostname and port */
+		qq_disconnect(gc);
+	 	qd->reconnect_times = QQ_RECONNECT_MAX;
+		reconnect_later(gc);
+		return;
+	}
+
+	if (prev_login_status != qd->logged_in && qd->logged_in == TRUE) {
+		/* logged_in, but we have packets before login */
+		qq_trans_process_before_login(qd);
+	}
+}
+
+static void tcp_pending(gpointer data, gint source, PurpleInputCondition cond)
+{
+	PurpleConnection *gc;
+	qq_data *qd;
+	guint8 buf[1024];		/* set to 16 when test  tcp_rxqueue */
+	gint buf_len;
+	gint bytes;
+	
+	guint8 *pkt;
+	guint16 pkt_len;
+	
+	gchar *error_msg;
+	guint8 *jump;
+	gint jump_len;
+
+	gc = (PurpleConnection *) data;
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	if(cond != PURPLE_INPUT_READ) {
+		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Socket error"));
+		return;
+	}
+
+	qd = (qq_data *) gc->proto_data;
+	
+	/* test code, not using tcp_rxqueue
+	memset(pkt,0, sizeof(pkt));
+	buf_len = read(qd->fd, pkt, sizeof(pkt));
+	if (buf_len > 2) {
+		packet_process(gc, pkt + 2, buf_len - 2);
+	}
+	return;
+	*/
+	
+	buf_len = read(qd->fd, buf, sizeof(buf));
+	if (buf_len < 0) {
+		if (errno == EAGAIN)
+			/* No worries */
+			return;
+
+		error_msg = g_strdup_printf(_("Lost connection with server:\n%d, %s"), errno, g_strerror(errno));
+		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_msg);
+		g_free(error_msg);
+		return;
+	} else if (buf_len == 0) {
+		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Server closed the connection."));
+		return;
+	}
+
+	/* keep alive will be sent in 30 seconds since last_receive
+	 *  QQ need a keep alive packet in every 60 seconds
+	 gc->last_received = time(NULL);
+	*/
+	/*
+	purple_debug(PURPLE_DEBUG_INFO, "TCP_PENDING",
+			   "Read %d bytes from socket, rxlen is %d\n", buf_len, qd->tcp_rxlen);
+	*/
+	qd->tcp_rxqueue = g_realloc(qd->tcp_rxqueue, buf_len + qd->tcp_rxlen);
+	memcpy(qd->tcp_rxqueue + qd->tcp_rxlen, buf, buf_len);
+	qd->tcp_rxlen += buf_len;
+	
+	pkt = g_newa(guint8, MAX_PACKET_SIZE);
+	while (1) {
+		if (qd->tcp_rxlen < QQ_TCP_HEADER_LENGTH) {
+			break;
+		}
+		
+		bytes = 0;
+		bytes += qq_get16(&pkt_len, qd->tcp_rxqueue + bytes);
+		if (qd->tcp_rxlen < pkt_len) {
+			break;
+		}
+
+		/* 
+		purple_debug(PURPLE_DEBUG_INFO, "TCP_PENDING",
+				   "Packet len is %d bytes, rxlen is %d\n", pkt_len, qd->tcp_rxlen);
+		*/
+		if ( pkt_len < QQ_TCP_HEADER_LENGTH
+		    || *(qd->tcp_rxqueue + bytes) != QQ_PACKET_TAG
+			|| *(qd->tcp_rxqueue + pkt_len - 1) != QQ_PACKET_TAIL) {
+			/* HEY! This isn't even a QQ. What are you trying to pull? */
+
+			purple_debug(PURPLE_DEBUG_ERROR, "TCP_PENDING",
+				 "Packet error, failed to check header and tail tag\n");
+
+			jump = memchr(qd->tcp_rxqueue + 1, QQ_PACKET_TAIL, qd->tcp_rxlen - 1);
+			if ( !jump ) {
+				purple_debug(PURPLE_DEBUG_INFO, "TCP_PENDING",
+				 	"Failed to find next QQ_PACKET_TAIL, clear receive buffer\n");
+				g_free(qd->tcp_rxqueue);
+				qd->tcp_rxqueue = NULL;
+				qd->tcp_rxlen = 0;
+				return;
+			}
+
+			/* jump and over QQ_PACKET_TAIL */
+			jump_len = (jump - qd->tcp_rxqueue) + 1;
+			purple_debug(PURPLE_DEBUG_INFO, "TCP_PENDING",
+				"Find next QQ_PACKET_TAIL at %d, jump %d bytes\n", jump_len, jump_len + 1);
+			g_memmove(qd->tcp_rxqueue, jump, qd->tcp_rxlen - jump_len);
+			qd->tcp_rxlen -= jump_len;
+			continue;
+		}
+
+		memset(pkt, 0, MAX_PACKET_SIZE);
+		g_memmove(pkt, qd->tcp_rxqueue + bytes, pkt_len - bytes);
+		
+		/* jump to next packet */
+		qd->tcp_rxlen -= pkt_len;
+		if (qd->tcp_rxlen) {
+			/*
+			purple_debug(PURPLE_DEBUG_ERROR, "TCP_PENDING", "shrink tcp_rxqueue to %d\n", qd->tcp_rxlen);		
+			*/
+			jump = g_memdup(qd->tcp_rxqueue + pkt_len, qd->tcp_rxlen);
+			g_free(qd->tcp_rxqueue);
+			qd->tcp_rxqueue = jump;
+		} else {
+			/* purple_debug(PURPLE_DEBUG_ERROR, "TCP_PENDING", "free tcp_rxqueue\n"); */
+			g_free(qd->tcp_rxqueue);
+			qd->tcp_rxqueue = NULL;
+		}
+
+		if (pkt == NULL) {
+			continue;
+		}
+		/* do not call packet_process before jump 
+		 * packet_process may call disconnect and destory tcp_rxqueue */
+		packet_process(gc, pkt, pkt_len - bytes);
+	}
+}
+
+static void udp_pending(gpointer data, gint source, PurpleInputCondition cond)
+{
+	PurpleConnection *gc;
+	qq_data *qd;
+	guint8 *buf;
+	gint buf_len;
+
+	gc = (PurpleConnection *) data;
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	if(cond != PURPLE_INPUT_READ) {
+		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Socket error"));
+		return;
+	}
+
+	qd = (qq_data *) gc->proto_data;
+	g_return_if_fail(qd->fd >= 0);
+	
+	buf = g_newa(guint8, MAX_PACKET_SIZE);
+
+	/* here we have UDP proxy suppport */
+	buf_len = read(qd->fd, buf, MAX_PACKET_SIZE);
+	if (buf_len <= 0) {
+		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Unable to read from socket"));
+		return;
+	}
+
+	/* keep alive will be sent in 30 seconds since last_receive
+	 *  QQ need a keep alive packet in every 60 seconds
+	 gc->last_received = time(NULL);
+	*/
+
+	if (buf_len < QQ_UDP_HEADER_LENGTH) {
+		if (buf[0] != QQ_PACKET_TAG || buf[buf_len - 1] != QQ_PACKET_TAIL) {
+			qq_hex_dump(PURPLE_DEBUG_ERROR, "UDP_PENDING",
+					buf, buf_len,
+					"Received packet is too short, or no header and tail tag");
+			return;
+		}
+	}
+	
+	packet_process(gc, buf, buf_len);
+}
+
+static gint udp_send_out(qq_data *qd, guint8 *data, gint data_len)
+{
+	gint ret;
+
+	g_return_val_if_fail(qd != NULL && qd->fd >= 0 && data != NULL && data_len > 0, -1);
+
+	/*
+	purple_debug(PURPLE_DEBUG_INFO, "UDP_SEND_OUT", "Send %d bytes to socket %d\n", data_len, qd->fd);
+	*/
+	
+	errno = 0;
+	ret = send(qd->fd, data, data_len, 0);
+	if (ret < 0 && errno == EAGAIN) {
+		return ret;
+	}
+	
+	if (ret < 0) {
+		/* TODO: what to do here - do we really have to disconnect? */
+		purple_debug(PURPLE_DEBUG_ERROR, "UDP_SEND_OUT", "Send failed: %d, %s\n", errno, g_strerror(errno));
+		purple_connection_error_reason(qd->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, g_strerror(errno));
+	}
+	return ret;
+}
+
+static void tcp_can_write(gpointer data, gint source, PurpleInputCondition cond)
+{
+	qq_data *qd = data;
+	int ret, writelen;
+
+	writelen = purple_circ_buffer_get_max_read(qd->tcp_txbuf);
+	if (writelen == 0) {
+		purple_input_remove(qd->tx_handler);
+		qd->tx_handler = 0;
+		return;
+	}
+
+	ret = write(qd->fd, qd->tcp_txbuf->outptr, writelen);
+	purple_debug(PURPLE_DEBUG_ERROR, "TCP_CAN_WRITE",
+		"total %d bytes is sent %d\n", writelen, ret);
+
+	if (ret < 0 && errno == EAGAIN)
+		return;
+	else if (ret < 0) {
+		/* TODO: what to do here - do we really have to disconnect? */
+		purple_connection_error_reason(qd->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+		                               _("Write Error"));
+		return;
+	}
+
+	purple_circ_buffer_mark_read(qd->tcp_txbuf, ret);
+}
+
+static gint tcp_send_out(qq_data *qd, guint8 *data, gint data_len)
+{
+	gint ret;
+
+	g_return_val_if_fail(qd != NULL && qd->fd >= 0 && data != NULL && data_len > 0, -1);
+
+	/*
+	purple_debug(PURPLE_DEBUG_INFO, "TCP_SEND_OUT", "Send %d bytes to socket %d\n", data_len, qd->fd);
+	 */
+
+	if (qd->tx_handler == 0) {
+		ret = write(qd->fd, data, data_len);
+	} else {
+		ret = -1;
+		errno = EAGAIN;
+	}
+
+	/*
+	purple_debug(PURPLE_DEBUG_INFO, "TCP_SEND_OUT",
+		"Socket %d, total %d bytes is sent %d\n", qd->fd, data_len, ret);
+	*/
+	if (ret < 0 && errno == EAGAIN) {
+		/* socket is busy, send later */
+		purple_debug(PURPLE_DEBUG_INFO, "TCP_SEND_OUT", "Socket is busy and send later\n");
+		ret = 0;
+	} else if (ret <= 0) {
+		/* TODO: what to do here - do we really have to disconnect? */
+		purple_debug(PURPLE_DEBUG_ERROR, "TCP_SEND_OUT",
+			"Send to socket %d failed: %d, %s\n", qd->fd, errno, g_strerror(errno));
+		purple_connection_error_reason(qd->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, g_strerror(errno));
+		return ret;
+	}
+
+	if (ret < data_len) {
+		purple_debug(PURPLE_DEBUG_INFO, "TCP_SEND_OUT",
+			"Add %d bytes to buffer\n", data_len - ret);
+		if (qd->tx_handler == 0) {
+			qd->tx_handler = purple_input_add(qd->fd, PURPLE_INPUT_WRITE, tcp_can_write, qd);
+		}
+		purple_circ_buffer_append(qd->tcp_txbuf, data + ret, data_len - ret);
+	}
+	return ret;
+}
+
+static gboolean network_timeout(gpointer data)
+{
+	PurpleConnection *gc = (PurpleConnection *) data;
+	qq_data *qd;
+	gboolean is_lost_conn;
+
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, TRUE);
+	qd = (qq_data *) gc->proto_data;
+
+	is_lost_conn = qq_trans_scan(qd);
+	if (is_lost_conn) {
+		purple_connection_error_reason(gc,
+			PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Connection lost"));
+		return TRUE;
+	}
+
+	if ( !qd->logged_in ) {
+		return TRUE;
+	}
+	
+	qd->itv_count.keep_alive--;
+	if (qd->itv_count.keep_alive <= 0) {
+		qd->itv_count.keep_alive = qd->itv_config.keep_alive;
+		qq_send_packet_keep_alive(gc);
+		return TRUE;
+	}
+
+	if (qd->itv_config.update <= 0) {
+		return TRUE;
+	}
+
+	qd->itv_count.update--;
+	if (qd->itv_count.update <= 0) {
+		qd->itv_count.update = qd->itv_config.update;
+		qq_send_packet_get_buddies_online(gc, 0);
+
+		qq_send_cmd_group_all_get_online_members(gc);
+		return TRUE;
+	}
+
+	return TRUE;		/* if return FALSE, timeout callback stops */
+}
+
+/* the callback function after socket is built
+ * we setup the qq protocol related configuration here */
+static void qq_connect_cb(gpointer data, gint source, const gchar *error_message)
+{
+	qq_data *qd;
+	PurpleConnection *gc;
+	gchar *conn_msg;
+	const gchar *passwd;
+	PurpleAccount *account ;
+
+	gc = (PurpleConnection *) data;
+
+	if (!PURPLE_CONNECTION_IS_VALID(gc)) {
+		purple_debug(PURPLE_DEBUG_INFO, "QQ_CONN", "Invalid connection\n");
+		close(source);
+		return;
+	}
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+	account = purple_connection_get_account(gc);
+
+	/* Connect is now complete; clear the PurpleProxyConnectData */
+	qd->connect_data = NULL;
+
+	if (source < 0) {	/* socket returns -1 */
+		purple_debug(PURPLE_DEBUG_INFO, "QQ_CONN", "Invalid connection, source is < 0\n");
+		qq_disconnect(gc);
+		reconnect_later(gc);
+		return;
+	}
+
+	/* _qq_show_socket("Got login socket", source); */
+
+	/* 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(purple_account_get_username(purple_connection_get_account(gc)), NULL, 10);
+
+	/* now generate md5 processed passwd */
+	passwd = purple_account_get_password(purple_connection_get_account(gc));
+
+	/* use twice-md5 of user password as session key since QQ 2003iii */
+	qq_get_md5(qd->password_twice_md5, sizeof(qd->password_twice_md5),
+		(guint8 *)passwd, strlen(passwd));
+	qq_get_md5(qd->password_twice_md5, sizeof(qd->password_twice_md5),
+		qd->password_twice_md5, sizeof(qd->password_twice_md5));
+
+	g_return_if_fail(qd->network_timeout == 0);
+	qd->itv_config.resend = purple_account_get_int(account, "resend_interval", 10);
+	if (qd->itv_config.resend <= 0) qd->itv_config.resend = 10;
+
+	qd->itv_config.keep_alive = purple_account_get_int(account, "keep_alive_interval", 60);
+	if (qd->itv_config.keep_alive < 30) qd->itv_config.keep_alive = 30;
+	qd->itv_config.keep_alive /= qd->itv_config.resend;
+	qd->itv_count.keep_alive = qd->itv_config.keep_alive;
+
+	qd->itv_config.update = purple_account_get_int(account, "update_interval", 300);
+	if (qd->itv_config.update > 0) {
+		if (qd->itv_config.update < qd->itv_config.keep_alive) {
+			qd->itv_config.update = qd->itv_config.keep_alive;
+		}
+		qd->itv_config.update /= qd->itv_config.resend;
+		qd->itv_count.update = qd->itv_config.update;
+	} else {
+		qd->itv_config.update = 0;
+	}
+
+	qd->network_timeout = purple_timeout_add(qd->itv_config.resend *1000, network_timeout, gc);
+	
+	if (qd->use_tcp)
+		gc->inpa = purple_input_add(qd->fd, PURPLE_INPUT_READ, tcp_pending, gc);
+	else
+		gc->inpa = purple_input_add(qd->fd, PURPLE_INPUT_READ, udp_pending, gc);
+
+	/* Update the login progress status display */
+	conn_msg = g_strdup_printf("Login as %d", qd->uid);
+	purple_connection_update_progress(gc, conn_msg, QQ_CONNECT_STEPS - 1, QQ_CONNECT_STEPS);
+	g_free(conn_msg);
+
+	qq_send_packet_token(gc);
+}
+
+static void udp_can_write(gpointer data, gint source, PurpleInputCondition cond)
+{
+	PurpleConnection *gc;
+	qq_data *qd;
+	socklen_t len;
+	int error=0, ret;
+
+	gc = (PurpleConnection *) data;
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+
+
+	purple_debug_info("proxy", "Connected.\n");
+
+	/*
+	 * getsockopt after a non-blocking connect returns -1 if something is
+	 * really messed up (bad descriptor, usually). Otherwise, it returns 0 and
+	 * error holds what connect would have returned if it blocked until now.
+	 * Thus, error == 0 is success, error == EINPROGRESS means "try again",
+	 * and anything else is a real error.
+	 *
+	 * (error == EINPROGRESS can happen after a select because the kernel can
+	 * be overly optimistic sometimes. select is just a hint that you might be
+	 * able to do something.)
+	 */
+	len = sizeof(error);
+	ret = getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len);
+	if (ret == 0 && error == EINPROGRESS)
+		return; /* we'll be called again later */
+		
+	purple_input_remove(qd->tx_handler);
+	qd->tx_handler = 0;
+	if (ret < 0 || error != 0) {
+		if(ret != 0) 
+			error = errno;
+
+		close(source);
+
+		purple_debug_error("proxy", "getsockopt SO_ERROR check: %s\n", g_strerror(error));
+
+		qq_connect_cb(gc, -1, _("Unable to connect"));
+		return;
+	}
+
+	qq_connect_cb(gc, source, NULL);
+}
+
+static void udp_host_resolved(GSList *hosts, gpointer data, const char *error_message) {
+	PurpleConnection *gc;
+	qq_data *qd;
+	struct sockaddr server_addr;
+	int addr_size;
+	gint fd = -1;
+	int flags;
+
+	gc = (PurpleConnection *) data;
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+
+	/* udp_query_data must be set as NULL.
+	 * Otherwise purple_dnsquery_destroy in qq_disconnect cause glib double free error */
+	qd->udp_query_data = NULL;
+
+	if (!hosts || !hosts->data) {
+		purple_connection_error_reason(gc,
+			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+			_("Couldn't resolve host"));
+		return;
+	}
+
+	addr_size = GPOINTER_TO_INT(hosts->data);
+	hosts = g_slist_remove(hosts, hosts->data);
+	memcpy(&server_addr, hosts->data, addr_size);
+	g_free(hosts->data);
+	
+	hosts = g_slist_remove(hosts, hosts->data);
+	while(hosts) {
+		hosts = g_slist_remove(hosts, hosts->data);
+		g_free(hosts->data);
+		hosts = g_slist_remove(hosts, hosts->data);
+	}
+
+	fd = socket(PF_INET, SOCK_DGRAM, 0);
+	if (fd < 0) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
+				"Unable to create socket: %s\n", g_strerror(errno));
+		return;
+	}
+
+	/* we use non-blocking mode to speed up connection */
+	flags = fcntl(fd, F_GETFL);
+	fcntl(fd, F_SETFL, flags | 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, &server_addr, addr_size) >= 0) {
+		purple_debug(PURPLE_DEBUG_INFO, "QQ", "Connected.\n");
+		flags = fcntl(fd, F_GETFL);
+		fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
+		qq_connect_cb(gc, fd, NULL);
+		return;
+	}
+	
+	/* [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)) {
+			purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Connect in asynchronous mode.\n");
+			qd->tx_handler = purple_input_add(fd, PURPLE_INPUT_WRITE, udp_can_write, gc);
+			return;
+		}
+
+	purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Connection failed: %s\n", g_strerror(errno));
+	close(fd);
+}
+
+/* establish a generic QQ connection 
+ * TCP/UDP, and direct/redirected */
+void qq_connect(PurpleAccount *account)
+{
+	PurpleConnection *gc;
+	qq_data *qd;
+	gchar *conn_msg;
+
+	gc = purple_account_get_connection(account);
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+
+	qd = (qq_data *) gc->proto_data;
+
+
+	/* test set_new_server
+	while (set_new_server(qd)) {
+   		purple_debug(PURPLE_DEBUG_INFO, "QQ_TEST",
+   			"New server %s:%d  Real server %s:%d\n",
+   			qd->server_name, qd->user_port, qd->real_hostname, qd->real_port);
+	}
+	purple_debug(PURPLE_DEBUG_INFO, "QQ_TEST", "qd->servers %lu\n",
+ 			qd->servers);
+ 	exit(1);
+	*/
+	if (qd->server_name == NULL) {
+		/* must be first call this function */
+		if ( set_new_server(qd) != TRUE) {
+			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+					_("Failed to connect server"));
+			return;
+		}
+	}
+
+	if (qd->real_hostname == NULL || qd->real_port == 0) {
+		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("hostname is NULL or port is 0"));
+		return;
+	}
+
+	conn_msg = g_strdup_printf( _("Connecting server %s, retries %d"),
+		qd->real_hostname, qd->reconnect_times);
+	purple_connection_update_progress(gc, conn_msg, 1, QQ_CONNECT_STEPS);
+	g_free(conn_msg);
+
+	if (qd->is_redirect) {
+   		purple_debug(PURPLE_DEBUG_INFO, "QQ", "Redirect to %s:%d\n",
+   			qd->real_hostname, qd->real_port);
+   	}
+	qd->is_redirect = FALSE;
+
+	qd->fd = -1;
+	qd->tx_handler = 0;
+	
+	/* QQ connection via UDP/TCP. 
+	* Now use Purple proxy function to provide TCP proxy support,
+	* and qq_udp_proxy.c to add UDP proxy support (thanks henry) */
+	if(qd->use_tcp) {
+   		purple_debug(PURPLE_DEBUG_INFO, "QQ", "TCP Connect to %s:%d\n",
+   			qd->real_hostname, qd->real_port);
+
+		/* TODO: is there a good default grow size? */
+		purple_debug(PURPLE_DEBUG_INFO, "QQ", "Create tcp_txbuf\n");
+		qd->tcp_txbuf = purple_circ_buffer_new(0);
+
+		qd->connect_data = purple_proxy_connect(NULL, account,
+				qd->real_hostname, qd->real_port, qq_connect_cb, gc);
+		if (qd->connect_data == NULL) {
+			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Unable to connect."));
+		}
+		return;
+	}
+	
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "UDP Connect to %s:%d\n",
+		qd->real_hostname, qd->real_port);
+
+	g_return_if_fail(qd->udp_query_data == NULL);
+	qd->udp_query_data = purple_dnsquery_a(qd->real_hostname, qd->real_port,
+		udp_host_resolved, gc);
+	if (qd->udp_query_data == NULL) {
+		purple_connection_error_reason(qd->gc,
+			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+			_("Could not resolve hostname"));
+	}
+}
+
+/* clean up qq_data structure and all its components
+ * always used before a redirectly connection */
+void qq_disconnect(PurpleConnection *gc)
+{
+	qq_data *qd;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Disconnecting ...\n");
+
+	if (qd->network_timeout > 0) {
+		purple_timeout_remove(qd->network_timeout);
+		qd->network_timeout = 0;
+	}
+
+	/* finish  all I/O */
+	if (qd->fd >= 0 && qd->logged_in) {
+		qq_send_packet_logout(gc);
+	}
+
+	if (gc->inpa > 0) {
+		purple_input_remove(gc->inpa);
+		gc->inpa = 0;
+	}
+
+	if (qd->fd >= 0) {
+		close(qd->fd);
+		qd->fd = -1;
+	}
+
+	if (qd->reconnect_timeout > 0) {
+		purple_timeout_remove(qd->reconnect_timeout);
+		qd->reconnect_timeout = 0;
+	}
+
+	if (qd->connect_data != NULL) {
+		purple_debug(PURPLE_DEBUG_INFO, "QQ", "Cancel connect_data\n");
+		purple_proxy_connect_cancel(qd->connect_data);
+	}
+	
+	if(qd->tcp_txbuf != NULL) {
+		purple_debug(PURPLE_DEBUG_INFO, "QQ", "destroy tcp_txbuf\n");
+		purple_circ_buffer_destroy(qd->tcp_txbuf);
+		qd->tcp_txbuf = NULL;
+	}
+	
+	if (qd->tx_handler) {
+		purple_input_remove(qd->tx_handler);
+		qd->tx_handler = 0;
+	}
+	if (qd->tcp_rxqueue != NULL) {
+		purple_debug(PURPLE_DEBUG_INFO, "QQ", "destroy tcp_rxqueue\n");
+		g_free(qd->tcp_rxqueue);
+		qd->tcp_rxqueue = NULL;
+		qd->tcp_rxlen = 0;
+	}
+	
+	if (qd->udp_query_data != NULL) {
+		purple_debug(PURPLE_DEBUG_INFO, "QQ", "destroy udp_query_data\n");
+		purple_dnsquery_destroy(qd->udp_query_data);
+		qd->udp_query_data = NULL;
+	}
+
+	qq_trans_remove_all(qd);
+	
+	if (qd->token) {
+		purple_debug(PURPLE_DEBUG_INFO, "QQ", "free token\n");
+		g_free(qd->token);
+		qd->token = NULL;
+		qd->token_len = 0;
+	}
+	memset(qd->inikey, 0, sizeof(qd->inikey));
+	memset(qd->password_twice_md5, 0, sizeof(qd->password_twice_md5));
+	memset(qd->session_key, 0, sizeof(qd->session_key));
+	memset(qd->session_md5, 0, sizeof(qd->session_md5));
+
+	qd->my_ip.s_addr = 0;
+
+	qq_group_packets_free(qd);
+	qq_group_free_all(qd);
+	qq_add_buddy_request_free(qd);
+	qq_info_query_free(qd);
+	qq_buddies_list_free(gc->account, qd);
+}
+
+static gint encap(qq_data *qd, guint8 *buf, gint maxlen, guint16 cmd, guint16 seq, 
+	guint8 *data, gint data_len)
+{
+	gint bytes = 0;
+	g_return_val_if_fail(qd != NULL && buf != NULL && maxlen > 0, -1);
+	
+	if (data == NULL) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Fail encap packet, data is NULL\n");
+		return -1;
+	}
+	if (data_len <= 0) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Fail encap packet, data len <= 0\n");
+		return -1;
+	}
+
+	/* QQ TCP packet has two bytes in the begining defines packet length
+	 * so leave room here to store packet size */
+	if (qd->use_tcp) {
+		bytes += qq_put16(buf + bytes, 0x0000);
+	}
+	/* now comes the normal QQ packet as UDP */
+	bytes += qq_put8(buf + bytes, QQ_PACKET_TAG);
+	bytes += qq_put16(buf + bytes, QQ_CLIENT);
+	bytes += qq_put16(buf + bytes, cmd);
+	
+	bytes += qq_put16(buf + bytes, seq);
+
+	bytes += qq_put32(buf + bytes, qd->uid);
+	bytes += qq_putdata(buf + bytes, data, data_len);
+	bytes += qq_put8(buf + bytes, QQ_PACKET_TAIL);
+
+	/* set TCP packet length at begin of the packet */
+	if (qd->use_tcp) {
+		qq_put16(buf, bytes);
+	}
+
+	return bytes;
+}
+
+/* data has been encrypted before */
+gint qq_send_data(qq_data *qd, guint16 cmd, guint16 seq, gboolean need_ack,
+	guint8 *data, gint data_len)
+{
+	guint8 *buf;
+	gint buf_len;
+	gint bytes_sent;
+
+	g_return_val_if_fail(qd != NULL, -1);
+	g_return_val_if_fail(data != NULL && data_len > 0, -1);
+
+	buf = g_newa(guint8, MAX_PACKET_SIZE);
+	memset(buf, 0, MAX_PACKET_SIZE);
+	buf_len = encap(qd, buf, MAX_PACKET_SIZE, cmd, seq, data, data_len);
+	if (buf_len <= 0) {
+		return -1;
+	}
+
+	if (qd->use_tcp) {
+		bytes_sent = tcp_send_out(qd, buf, buf_len);
+	} else {
+		bytes_sent = udp_send_out(qd, buf, buf_len);
+	}
+
+	if (need_ack)  {
+		qq_trans_add_client_cmd(qd, cmd, seq, data, data_len);
+	}
+	
+	if (QQ_DEBUG) {
+		/* qq_show_packet("QQ_SEND_DATA", buf, buf_len); */
+		purple_debug(PURPLE_DEBUG_INFO, "QQ",
+				"<== [%05d], %s, total %d bytes is sent %d\n", 
+				seq, qq_get_cmd_desc(cmd), buf_len, bytes_sent);
+	}
+	return bytes_sent;
+}
+
+/* Encrypt data with session_key, then call qq_send_data */
+gint qq_send_cmd_detail(qq_data *qd, guint16 cmd, guint16 seq, gboolean need_ack,
+	guint8 *data, gint data_len)
+{
+	guint8 *encrypted_data;
+	gint encrypted_len;
+
+	g_return_val_if_fail(qd != NULL, -1);
+	g_return_val_if_fail(data != NULL && data_len > 0, -1);
+
+	encrypted_len = data_len + 16;	/* at most 16 bytes more */
+	encrypted_data = g_newa(guint8, encrypted_len);
+
+	qq_encrypt(data, data_len, qd->session_key, encrypted_data, &encrypted_len);
+
+	return qq_send_data(qd, cmd, seq, need_ack, encrypted_data, encrypted_len);
+}
+
+/* set seq and need_ack, then call qq_send_cmd_detail */
+gint qq_send_cmd(qq_data *qd, guint16 cmd, guint8 *data, gint data_len)
+{
+	g_return_val_if_fail(qd != NULL, -1);
+	g_return_val_if_fail(data != NULL && data_len > 0, -1);
+
+	qd->send_seq++;
+	return qq_send_cmd_detail(qd, cmd, qd->send_seq, TRUE, data, data_len);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/qq/qq_network.h	Thu Nov 20 21:13:56 2008 +0000
@@ -0,0 +1,45 @@
+/**
+ * @file qq_network.h
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#ifndef _QQ_NETWORK_H
+#define _QQ_NETWORK_H
+
+#include <glib.h>
+#include "connection.h"
+
+#include "qq.h"
+
+#define QQ_CONNECT_STEPS    3	/* steps in connection */
+
+void qq_connect(PurpleAccount *account);
+void qq_disconnect(PurpleConnection *gc);
+void qq_connect_later(PurpleConnection *gc);
+
+gint qq_send_cmd(qq_data *qd, guint16 cmd, guint8 *data, gint datalen);
+gint qq_send_data(qq_data *qd, guint16 cmd, guint16 seq, gboolean need_ack,
+	guint8 *data, gint data_len);
+gint qq_send_cmd_detail(qq_data *qd, guint16 cmd, guint16 seq, gboolean need_ack,
+	guint8 *data, gint data_len);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/qq/qq_process.c	Thu Nov 20 21:13:56 2008 +0000
@@ -0,0 +1,267 @@
+/**
+ * @file qq_network.c
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#include "cipher.h"
+#include "debug.h"
+#include "internal.h"
+
+#ifdef _WIN32
+#define random rand
+#define srandom srand
+#endif
+
+#include "buddy_info.h"
+#include "buddy_list.h"
+#include "buddy_opt.h"
+#include "group_info.h"
+#include "group_free.h"
+#include "char_conv.h"
+#include "crypt.h"
+#include "group_network.h"
+#include "header_info.h"
+#include "qq_base.h"
+#include "im.h"
+#include "qq_process.h"
+#include "packet_parse.h"
+#include "qq_network.h"
+#include "qq_trans.h"
+#include "sys_msg.h"
+#include "utils.h"
+
+/* default process, decrypt and dump */
+static void process_cmd_unknow(PurpleConnection *gc,gchar *title, guint8 *buf, gint buf_len, guint16 cmd, guint16 seq)
+{
+	qq_data *qd;
+	guint8 *data;
+	gint data_len;
+	gchar *msg_utf8 = NULL;
+
+	g_return_if_fail(buf != NULL && buf_len != 0);
+
+	qq_show_packet(title, buf, buf_len);
+
+	qd = (qq_data *) gc->proto_data;
+
+	data_len = buf_len;
+	data = g_newa(guint8, data_len);
+	memset(data, 0, data_len);
+	if ( !qq_decrypt(buf, buf_len, qd->session_key, data, &data_len )) {
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Fail decrypt packet with default process\n");
+		return;
+	}
+	
+	qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ",
+			data, data_len,
+			">>> [%d] %s -> [default] decrypt and dump",
+			seq, qq_get_cmd_desc(cmd));
+
+	msg_utf8 = try_dump_as_gbk(data, data_len);
+	if (msg_utf8) {
+		g_free(msg_utf8);
+	}
+}
+
+void qq_proc_cmd_server(PurpleConnection *gc,
+	guint16 cmd, guint16 seq, guint8 *data, gint data_len)
+{
+	/* now process the packet */
+	switch (cmd) {
+		case QQ_CMD_RECV_IM:
+			qq_process_recv_im(data, data_len, seq, gc);
+			break;
+		case QQ_CMD_RECV_MSG_SYS:
+			qq_process_msg_sys(data, data_len, seq, gc);
+			break;
+		case QQ_CMD_RECV_MSG_BUDDY_CHANGE_STATUS:
+			qq_process_buddy_change_status(data, data_len, gc);
+			break;
+		default:
+			process_cmd_unknow(gc, "Unknow SERVER CMD", data, data_len, cmd, seq);
+			break;
+	}
+}
+
+static void process_cmd_login(PurpleConnection *gc, guint8 *data, gint data_len)
+{
+	qq_data *qd;
+	guint ret_8;
+
+	g_return_if_fail (gc != NULL && gc->proto_data != NULL);
+	
+	qd = (qq_data *) gc->proto_data;
+
+	ret_8 = qq_process_login_reply(data, data_len, gc);
+	if (ret_8 == QQ_LOGIN_REPLY_OK) {
+		purple_debug(PURPLE_DEBUG_INFO, "QQ", "Login repliess OK; everything is fine\n");
+
+		purple_connection_set_state(gc, PURPLE_CONNECTED);
+		qd->logged_in = TRUE;	/* must be defined after sev_finish_login */
+
+		/* now initiate QQ Qun, do it first as it may take longer to finish */
+		qq_group_init(gc);
+
+		/* Now goes on updating my icon/nickname, not showing info_window */
+		qd->modifying_face = FALSE;
+
+		qq_send_packet_get_info(gc, qd->uid, FALSE);
+		/* grab my level */
+		qq_send_packet_get_level(gc, qd->uid);
+
+		qq_send_packet_change_status(gc);
+
+		/* refresh buddies */
+		qq_send_packet_get_buddies_list(gc, 0);
+
+		/* refresh groups */
+		qq_send_packet_get_all_list_with_group(gc, 0);
+
+		return;
+	}
+
+	if (ret_8 == QQ_LOGIN_REPLY_REDIRECT) {
+		qd->is_redirect = TRUE;
+		/*
+		purple_debug(PURPLE_DEBUG_WARNING, "QQ",
+			"Redirected to new server: %s:%d\n", qd->real_hostname, qd->real_port);
+		*/
+		return;
+	}
+
+	if (ret_8 == QQ_LOGIN_REPLY_ERR_PWD) {
+		if (!purple_account_get_remember_password(gc->account)) {
+			purple_account_set_password(gc->account, NULL);
+		}
+		purple_connection_error_reason(gc,
+			PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Incorrect password."));
+		return;
+	}
+
+	if (ret_8 == QQ_LOGIN_REPLY_ERR_MISC) {
+		if (purple_debug_is_enabled())
+			purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to login. Check debug log."));
+		else
+			purple_connection_error_reason(gc,
+				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to login"));
+		return;
+	}
+}
+
+void qq_proc_cmd_reply(PurpleConnection *gc,
+	guint16 cmd, guint16 seq, guint8 *data, gint data_len)
+{
+	gboolean ret_bool = FALSE;
+	guint8 ret_8 = 0;
+	guint16 ret_16 = 0;
+	guint32 ret_32 = 0;
+	gchar *error_msg = NULL;
+
+	switch (cmd) {
+		case QQ_CMD_TOKEN:
+			ret_8 = qq_process_token_reply(gc, error_msg, data, data_len);
+			if (ret_8 != QQ_TOKEN_REPLY_OK) {
+				if (error_msg == NULL) {
+					error_msg = g_strdup_printf( _("Invalid token reply code, 0x%02X"), ret_8);
+				}
+				purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_msg);
+				g_free(error_msg);
+				return;
+			}
+			
+			qq_send_packet_login(gc);
+			break;
+		case QQ_CMD_LOGIN:
+			process_cmd_login(gc, data, data_len);
+			break;
+		case QQ_CMD_UPDATE_INFO:
+			qq_process_modify_info_reply(data, data_len, gc);
+			break;
+		case QQ_CMD_ADD_BUDDY_WO_AUTH:
+			qq_process_add_buddy_reply(data, data_len, seq, gc);
+			break;
+		case QQ_CMD_DEL_BUDDY:
+			qq_process_remove_buddy_reply(data, data_len, gc);
+			break;
+		case QQ_CMD_REMOVE_SELF:
+			qq_process_remove_self_reply(data, data_len, gc);
+			break;
+		case QQ_CMD_BUDDY_AUTH:
+			qq_process_add_buddy_auth_reply(data, data_len, gc);
+			break;
+		case QQ_CMD_GET_USER_INFO:
+			qq_process_get_info_reply(data, data_len, gc);
+			break;
+		case QQ_CMD_CHANGE_ONLINE_STATUS:
+			qq_process_change_status_reply(data, data_len, gc);
+			break;
+		case QQ_CMD_SEND_IM:
+			qq_process_send_im_reply(data, data_len, gc);
+			break;
+		case QQ_CMD_KEEP_ALIVE:
+			qq_process_keep_alive(data, data_len, gc);
+			break;
+		case QQ_CMD_GET_BUDDIES_ONLINE:
+			ret_8 = qq_process_get_buddies_online_reply(data, data_len, gc);
+			if (ret_8  > 0 && ret_8 < 0xff) {
+				purple_debug(PURPLE_DEBUG_INFO, "QQ", "Requesting for more online buddies\n"); 
+				qq_send_packet_get_buddies_online(gc, ret_8);
+			} else {
+				purple_debug(PURPLE_DEBUG_INFO, "QQ", "All online buddies received\n"); 
+				/* Fixme: this should not be called once*/
+				qq_send_packet_get_buddies_levels(gc);
+
+				qq_refresh_all_buddy_status(gc);
+			}
+			break;
+		case QQ_CMD_GET_LEVEL:
+			qq_process_get_level_reply(data, data_len, gc);
+			break;
+		case QQ_CMD_GET_BUDDIES_LIST:
+			ret_16 = qq_process_get_buddies_list_reply(data, data_len, gc);
+			if (ret_16 > 0	&& ret_16 < 0xffff) { 
+				purple_debug(PURPLE_DEBUG_INFO, "QQ", "Requesting for more buddies\n"); 
+				qq_send_packet_get_buddies_list(gc, ret_16);
+			} else {
+				purple_debug(PURPLE_DEBUG_INFO, "QQ", "All buddies received. Requesting buddies' levels\n");
+				qq_send_packet_get_buddies_online(gc, 0);
+			}
+			break;
+		case QQ_CMD_GROUP_CMD:
+			qq_process_group_cmd_reply(data, data_len, seq, gc);
+			break;
+		case QQ_CMD_GET_ALL_LIST_WITH_GROUP:
+			ret_32 = qq_process_get_all_list_with_group_reply(data, data_len, gc);
+			if (ret_32 > 0 && ret_32 < 0xffffffff) {
+				purple_debug(PURPLE_DEBUG_INFO, "QQ", "Requesting for more buddies and groups\n");
+				qq_send_packet_get_all_list_with_group(gc, ret_32);
+			} else {
+				purple_debug(PURPLE_DEBUG_INFO, "QQ", "All buddies and groups received\n"); 
+			}
+			break;
+		default:
+			process_cmd_unknow(gc, "Unknow reply CMD", data, data_len, cmd, seq);
+			break;
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/qq/qq_process.h	Thu Nov 20 21:13:56 2008 +0000
@@ -0,0 +1,38 @@
+/**
+ * @file qq_process.h
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#ifndef _QQ_PROCESS_H
+#define _QQ_PROCESS_H
+
+#include <glib.h>
+#include "connection.h"
+
+#include "qq.h"
+
+void qq_proc_cmd_reply(PurpleConnection *gc,
+		guint16 cmd, guint16 seq, guint8 *data, gint data_len);
+void qq_proc_cmd_server(PurpleConnection *gc,
+	guint16 cmd, guint16 seq, guint8 *data, gint data_len);
+#endif
+
--- a/libpurple/protocols/qq/qq_proxy.c	Mon Aug 18 17:08:01 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,529 +0,0 @@
-/**
- * @file qq_proxy.c
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#include "cipher.h"
-#include "debug.h"
-#include "internal.h"
-
-#ifdef _WIN32
-#define random rand
-#define srandom srand
-#endif
-
-#include "packet_parse.h"
-#include "buddy_info.h"
-#include "buddy_opt.h"
-#include "char_conv.h"
-#include "group_free.h"
-#include "login_logout.h"
-#include "qq_proxy.h"
-#include "recv_core.h"
-#include "send_core.h"
-#include "sendqueue.h"
-#include "udp_proxy_s5.h"
-#include "utils.h"
-
-/* These functions are used only in development phase */
-/*
-static void _qq_show_socket(gchar *desc, gint fd) {
-	struct sockaddr_in sin;
-	socklen_t len = sizeof(sin);
-	getsockname(fd, (struct sockaddr *)&sin, &len);
-	purple_debug(PURPLE_DEBUG_INFO, desc, "%s:%d\n",
-            inet_ntoa(sin.sin_addr), g_ntohs(sin.sin_port));
-}
-*/
-
-void _qq_show_packet(const gchar *desc, const guint8 *buf, gint len)
-{
-	char buf1[8*len+2], 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");
-	purple_debug(PURPLE_DEBUG_INFO, desc, "%s", buf1);
-}
-
-/* QQ 2003iii uses double MD5 for the pwkey to get the session key */
-static guint8 *_gen_pwkey(const gchar *pwd)
-{
-        PurpleCipher *cipher;
-        PurpleCipherContext *context;
-
-	guchar pwkey_tmp[QQ_KEY_LENGTH];
-
-	cipher = purple_ciphers_find_cipher("md5");
-	context = purple_cipher_context_new(cipher, NULL);
-	purple_cipher_context_append(context, (guchar *) pwd, strlen(pwd));
-	purple_cipher_context_digest(context, sizeof(pwkey_tmp), pwkey_tmp, NULL);
-	purple_cipher_context_destroy(context);
-	context = purple_cipher_context_new(cipher, NULL);
-	purple_cipher_context_append(context, pwkey_tmp, QQ_KEY_LENGTH);
-	purple_cipher_context_digest(context, sizeof(pwkey_tmp), pwkey_tmp, NULL);
-	purple_cipher_context_destroy(context);
-
-	return g_memdup(pwkey_tmp, QQ_KEY_LENGTH);
-}
-
-static gboolean _qq_fill_host(GSList *hosts, struct sockaddr_in *addr, gint *addr_size)
-{
-	if (!hosts || !hosts->data)
-		return FALSE;
-
-	*addr_size = GPOINTER_TO_INT(hosts->data);
-
-	hosts = g_slist_remove(hosts, hosts->data);
-	memcpy(addr, hosts->data, *addr_size);
-	g_free(hosts->data);
-	hosts = g_slist_remove(hosts, hosts->data);
-	while(hosts) {
-		hosts = g_slist_remove(hosts, hosts->data);
-		g_free(hosts->data);
-		hosts = g_slist_remove(hosts, hosts->data);
-	}
-
-	return TRUE;
-}
-
-/* set up any finalizing start-up stuff */
-static void _qq_start_services(PurpleConnection *gc)
-{
-	/* start watching for IMs about to be sent */
-	/*
-	purple_signal_connect(purple_conversations_get_handle(),
-			"sending-im-msg", gc,
-			PURPLE_CALLBACK(qq_sending_im_msg_cb), NULL);
-			*/
-}
-
-/* the callback function after socket is built
- * we setup the qq protocol related configuration here */
-static void _qq_got_login(gpointer data, gint source, const gchar *error_message)
-{
-	qq_data *qd;
-	PurpleConnection *gc;
-	gchar *buf;
-	const gchar *passwd;
-
-	gc = (PurpleConnection *) data;
-
-	if (!PURPLE_CONNECTION_IS_VALID(gc)) {
-		close(source);
-		return;
-	}
-
-	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
-
-	if (source < 0) {	/* socket returns -1 */
-		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_message);
-		return;
-	}
-
-	qd = (qq_data *) gc->proto_data;
-
-	/*
-	_qq_show_socket("Got login socket", source);
-	*/
-
-	/* 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(purple_account_get_username(purple_connection_get_account(gc)), NULL, 10);
-
-	/* now generate md5 processed passwd */
-	passwd = purple_account_get_password(purple_connection_get_account(gc));
-	qd->pwkey = _gen_pwkey(passwd);
-
-	qd->sendqueue_timeout = purple_timeout_add(QQ_SENDQUEUE_TIMEOUT, qq_sendqueue_timeout_callback, gc);
-	gc->inpa = purple_input_add(qd->fd, PURPLE_INPUT_READ, qq_input_pending, gc);
-
-	/* Update the login progress status display */
-	buf = g_strdup_printf("Login as %d", qd->uid);
-	purple_connection_update_progress(gc, buf, 1, QQ_CONNECT_STEPS);
-	g_free(buf);
-
-	_qq_start_services(gc);
-
-	qq_send_packet_request_login_token(gc);
-}
-
-/* clean up qq_data structure and all its components
- * always used before a redirectly connection */
-static void _qq_common_clean(PurpleConnection *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) {
-		purple_timeout_remove(qd->sendqueue_timeout);
-		qd->sendqueue_timeout = 0;
-	}
-
-	if (gc->inpa > 0) {
-		purple_input_remove(gc->inpa);
-		gc->inpa = 0;
-	}
-
-	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_buddies_list_free(gc->account, qd);
-}
-
-static void no_one_calls(gpointer data, gint source, PurpleInputCondition cond)
-{
-        struct PHB *phb = data;
-	socklen_t len;
-	int error=0, ret;
-
-	purple_debug_info("proxy", "Connected.\n");
-
-	len = sizeof(error);
-
-	/*
-	* getsockopt after a non-blocking connect returns -1 if something is
-	* really messed up (bad descriptor, usually). Otherwise, it returns 0 and
-	* error holds what connect would have returned if it blocked until now.
-	* Thus, error == 0 is success, error == EINPROGRESS means "try again",
-	* and anything else is a real error.
-	*
-	* (error == EINPROGRESS can happen after a select because the kernel can
-	* be overly optimistic sometimes. select is just a hint that you might be
-	* able to do something.)
-	*/
-	ret = getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len);
-	if (ret == 0 && error == EINPROGRESS)
-		return; /* we'll be called again later */
-	if (ret < 0 || error != 0) {
-		if(ret!=0) 
-			error = errno;
-		close(source);
-		purple_input_remove(phb->inpa);
-
-		purple_debug_error("proxy", "getsockopt SO_ERROR check: %s\n", g_strerror(error));
-
-		phb->func(phb->data, -1, _("Unable to connect"));
-		return;
-	}
-
-	purple_input_remove(phb->inpa);
-
-	if (phb->account == NULL || purple_account_get_connection(phb->account) != NULL) {
-
-		phb->func(phb->data, source, NULL);
-	}
-
-	g_free(phb->host);
-	g_free(phb);
-}
-
-/* returns -1 if fails, otherwise returns the file handle */
-static gint _qq_proxy_none(struct PHB *phb, struct sockaddr *addr, socklen_t addrlen)
-{
-	gint fd = -1;
-	int flags;
-
-	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Using UDP without proxy\n");
-	fd = socket(PF_INET, SOCK_DGRAM, 0);
-
-	if (fd < 0) {
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ Redirect", 
-			"Unable to create socket: %s\n", g_strerror(errno));
-		return -1;
-	}
-
-	/* we use non-blocking mode to speed up connection */
-	flags = fcntl(fd, F_GETFL);
-	fcntl(fd, F_SETFL, flags | 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)) {
-			purple_debug_warning("QQ", "Connect in asynchronous mode.\n");
-			phb->inpa = purple_input_add(fd, PURPLE_INPUT_WRITE, no_one_calls, phb);
-		} else {
-			purple_debug_error("QQ", "Connection failed: %s\n", g_strerror(errno));
-			close(fd);
-			return -1;
-		}		/* if errno */
-	} else {		/* connect returns 0 */
-		purple_debug(PURPLE_DEBUG_INFO, "QQ", "Connected.\n");
-		flags = fcntl(fd, F_GETFL);
-		fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
-		phb->func(phb->data, fd, NULL);
-	}
-
-	return fd;
-}
-
-static void _qq_proxy_resolved(GSList *hosts, gpointer data, const char *error_message)
-{
-	struct PHB *phb = (struct PHB *) data;
-	struct sockaddr_in addr;
-	gint addr_size, ret = -1;
-
-	if(_qq_fill_host(hosts, &addr, &addr_size))
-		ret = qq_proxy_socks5(phb, (struct sockaddr *) &addr, addr_size);
-
-	if (ret < 0) {
-		phb->func(phb->data, -1, _("Unable to connect"));
-		g_free(phb->host);
-		g_free(phb);
-	}
-}
-
-static void _qq_server_resolved(GSList *hosts, gpointer data, const char *error_message)
-{
-	struct PHB *phb = (struct PHB *) data;
-	PurpleConnection *gc = (PurpleConnection *) phb->data;
-	qq_data *qd = (qq_data *) gc->proto_data;
-	struct sockaddr_in addr;
-	gint addr_size, ret = -1;
-
-	if(_qq_fill_host(hosts, &addr, &addr_size)) {
-		switch (purple_proxy_info_get_type(phb->gpi)) {
-			case PURPLE_PROXY_NONE:
-				ret = _qq_proxy_none(phb, (struct sockaddr *) &addr, addr_size);
-				break;
-			case PURPLE_PROXY_SOCKS5:
-				ret = 0;
-				if (purple_proxy_info_get_host(phb->gpi) == NULL || 
-						purple_proxy_info_get_port(phb->gpi) == 0) {
-					purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
-							"Use of socks5 proxy selected but host or port info doesn't exist.\n");
-					ret = -1;
-				} else {
-					/* as the destination is always QQ server during the session, 
-				 	* we can set dest_sin here, instead of _qq_s5_canread_again */
-					memcpy(&qd->dest_sin, &addr, addr_size);
-					if (purple_dnsquery_a(purple_proxy_info_get_host(phb->gpi),
-							purple_proxy_info_get_port(phb->gpi),
-							_qq_proxy_resolved, phb) == NULL)
-						ret = -1;
-				}
-				break;
-			default:
-				purple_debug(PURPLE_DEBUG_WARNING, "QQ", 
-						"Proxy type %i is unsupported, not using a proxy.\n",
-						purple_proxy_info_get_type(phb->gpi));
-				ret = _qq_proxy_none(phb, (struct sockaddr *) &addr, addr_size);
-		}
-	}
-
-	if (ret < 0) {
-		phb->func(gc, -1, _("Unable to connect"));
-		g_free(phb->host);
-		g_free(phb);
-	}
-}
-
-/* returns -1 if dns lookup fails, otherwise returns 0 */
-static gint _qq_udp_proxy_connect(PurpleAccount *account,
-			   const gchar *server, guint16 port, 
-			   void callback(gpointer, gint, const gchar *error_message), 
-			   PurpleConnection *gc)
-{
-	PurpleProxyInfo *info;
-	struct PHB *phb;
-	qq_data *qd = (qq_data *) gc->proto_data;
-
-	g_return_val_if_fail(gc != NULL && qd != NULL, -1);
-
-	info = purple_proxy_get_setup(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;
-	qd->proxy_type = purple_proxy_info_get_type(phb->gpi);
-
-	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Choosing proxy type %d\n", 
-			purple_proxy_info_get_type(phb->gpi));
-
-	if (purple_dnsquery_a(server, port, _qq_server_resolved, phb) == NULL) {
-		phb->func(gc, -1, _("Unable to connect"));
-		g_free(phb->host);
-		g_free(phb);
-		return -1;
-	} else {
-		return 0;
-	}
-}
-
-/* QQ connection via UDP/TCP. 
- * I use Purple proxy function to provide TCP proxy support,
- * and qq_udp_proxy.c to add UDP proxy support (thanks henry) */
-static gint _proxy_connect_full (PurpleAccount *account, const gchar *host, guint16 port, 
-		PurpleProxyConnectFunction func, gpointer data, gboolean use_tcp)
-{
-	PurpleConnection *gc;
-	qq_data *qd;
-
-	gc = purple_account_get_connection(account);
-	qd = (qq_data *) gc->proto_data;
-	qd->server_ip = g_strdup(host);
-	qd->server_port = port;
-
-	if(use_tcp)
-		return (purple_proxy_connect(NULL, account, host, port, func, data) == NULL);
-	else
-		return _qq_udp_proxy_connect(account, host, port, func, data);
-}
-
-/* establish a generic QQ connection 
- * TCP/UDP, and direct/redirected */
-gint qq_connect(PurpleAccount *account, const gchar *host, guint16 port, 
-		gboolean use_tcp, gboolean is_redirect)
-{
-	PurpleConnection *gc;
-	qq_data *qd;
-
-	g_return_val_if_fail(host != NULL, -1);
-	g_return_val_if_fail(port > 0, -1);
-
-	gc = purple_account_get_connection(account);
-	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, -1);
-
-	if (is_redirect)
-		_qq_common_clean(gc);
-
-	qd = (qq_data *) gc->proto_data;
-	qd->before_login_packets = g_queue_new();
-
-	return _proxy_connect_full(account, host, port, _qq_got_login, gc, use_tcp);
-}
-
-/* clean up the given QQ connection and free all resources */
-void qq_disconnect(PurpleConnection *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->session_md5);
-	g_free(qd->my_ip);
-	g_free(qd);
-
-	gc->proto_data = NULL;
-}
-
-/* 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 == PURPLE_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);
-		errno = 0;
-		ret = send(qd->fd, buf, len + 10, 0);
-	} else {
-		errno = 0;
-		ret = send(qd->fd, data, len, 0);
-	}
-	if (ret == -1)
-		purple_connection_error_reason(qd->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, g_strerror(errno));
-
-	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 == PURPLE_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;
-}
--- a/libpurple/protocols/qq/qq_proxy.h	Mon Aug 18 17:08:01 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-/**
- * @file qq_proxy.h
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#ifndef _QQ_PROXY_H
-#define _QQ_PROXY_H
-
-#include <glib.h>
-#include "dnsquery.h"
-#include "proxy.h"
-
-#include "qq.h"
-
-#define QQ_CONNECT_STEPS    2	/* steps in connection */
-
-struct PHB {
-	PurpleProxyConnectFunction func;
-	gpointer data;
-	gchar *host;
-	gint port;
-	gint inpa;
-	PurpleProxyInfo *gpi;
-	PurpleAccount *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(PurpleAccount *account, const gchar *host, guint16 port, gboolean use_tcp, gboolean is_redirect);
-void qq_disconnect(PurpleConnection *gc);
-
-void _qq_show_packet(const gchar *desc, const guint8 *buf, gint len);
-
-#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/qq/qq_trans.c	Thu Nov 20 21:13:56 2008 +0000
@@ -0,0 +1,290 @@
+/**
+ * @file qq_trans.c
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#include "internal.h"
+
+#include "connection.h"
+#include "debug.h"
+#include "notify.h"
+#include "prefs.h"
+#include "request.h"
+
+#include "header_info.h"
+#include "qq_network.h"
+#include "qq_process.h"
+#include "qq_trans.h"
+
+#define QQ_RESEND_MAX               3	/* max resend per packet */
+
+qq_transaction *qq_trans_find_rcved(qq_data *qd, guint16 cmd, guint16 seq)
+{
+	GList *curr;
+	GList *next;
+	qq_transaction *trans;
+
+	if (qd->transactions == NULL) {
+		return NULL;
+	}
+
+	next = qd->transactions;
+	while( (curr = next) ) {
+		next = curr->next;
+		
+		trans = (qq_transaction *) (curr->data);
+		if(trans->cmd == cmd && trans->seq == seq) {
+			if (trans->rcved_times == 0) {
+				trans->scan_times = 0;
+			}
+			trans->rcved_times++;
+			if (qq_trans_is_server(trans) && qq_trans_is_dup(trans)) {
+				/* server may not get our confirm reply before, send reply again*/
+				if (trans->data != NULL && trans->data_len > 0) {
+					qq_send_data(qd, trans->cmd, trans->seq, FALSE, trans->data, trans->data_len);
+				}
+			}
+			return trans;
+		}
+	}
+
+	return NULL;
+}
+
+gboolean qq_trans_is_server(qq_transaction *trans) 
+{
+	g_return_val_if_fail(trans != NULL, FALSE);
+	
+	if (trans->flag & QQ_TRANS_IS_SERVER)
+		return TRUE;
+	else
+		return FALSE;
+}
+
+gboolean qq_trans_is_dup(qq_transaction *trans) 
+{
+	g_return_val_if_fail(trans != NULL, TRUE);
+	
+	if (trans->rcved_times > 1)
+		return TRUE;
+	else
+		return FALSE;
+}
+
+/* Remove a packet with seq from send trans */
+static void trans_remove(qq_data *qd, qq_transaction *trans) 
+{
+	g_return_if_fail(qd != NULL && trans != NULL);
+	
+	purple_debug(PURPLE_DEBUG_INFO, "QQ_TRANS",
+				"Remove [%s%05d] retry %d rcved %d scan %d %s\n",
+				(trans->flag & QQ_TRANS_IS_SERVER) ? "SRV-" : "",
+				trans->seq,
+				trans->send_retries, trans->rcved_times, trans->scan_times,
+				qq_get_cmd_desc(trans->cmd));
+
+	if (trans->data)	g_free(trans->data);
+	qd->transactions = g_list_remove(qd->transactions, trans);
+	g_free(trans);
+}
+
+void qq_trans_add_client_cmd(qq_data *qd, guint16 cmd, guint16 seq, guint8 *data, gint data_len)
+{
+	qq_transaction *trans = g_new0(qq_transaction, 1);
+
+	g_return_if_fail(trans != NULL);
+
+	trans->flag = 0;
+	if (cmd == QQ_CMD_TOKEN || cmd == QQ_CMD_LOGIN || cmd == QQ_CMD_KEEP_ALIVE) {
+		trans->flag |= QQ_TRANS_CLI_IMPORT;
+	}
+	trans->fd = qd->fd;
+	trans->cmd = cmd;
+	trans->seq = seq;
+	trans->send_retries = QQ_RESEND_MAX;
+	trans->rcved_times = 0;
+	trans->scan_times = 0;
+	trans->data = NULL;
+	trans->data_len = 0;
+	if (data != NULL && data_len > 0) {
+		trans->data = g_memdup(data, data_len);	/* don't use g_strdup, may have 0x00 */
+		trans->data_len = data_len;
+	}
+	purple_debug(PURPLE_DEBUG_ERROR, "QQ_TRANS",
+			"Add client cmd, seq = %d, data = %p, len = %d\n",
+			trans->seq, trans->data, trans->data_len);
+	qd->transactions = g_list_append(qd->transactions, trans);
+}
+
+void qq_trans_add_server_cmd(qq_data *qd, guint16 cmd, guint16 seq, guint8 *data, gint data_len)
+{
+	qq_transaction *trans = g_new0(qq_transaction, 1);
+
+	g_return_if_fail(trans != NULL);
+
+	trans->flag = QQ_TRANS_IS_SERVER;
+	if ( !qd->logged_in ) {
+		trans->flag |= QQ_TRANS_BEFORE_LOGIN;
+	}
+	trans->fd = qd->fd;
+	trans->cmd = cmd;
+	trans->seq = seq;
+	trans->send_retries = 0;
+	trans->rcved_times = 1;
+	trans->scan_times = 0;
+	trans->data = NULL;
+	trans->data_len = 0;
+	if (data != NULL && data_len > 0) {
+		trans->data = g_memdup(data, data_len);	/* don't use g_strdup, may have 0x00 */
+		trans->data_len = data_len;
+	}
+	purple_debug(PURPLE_DEBUG_ERROR, "QQ_TRANS",
+			"Add server cmd, seq = %d, data = %p, len = %d\n",
+			trans->seq, trans->data, trans->data_len);
+	qd->transactions = g_list_append(qd->transactions, trans);
+}
+
+void qq_trans_process_before_login(qq_data *qd)
+{
+	GList *curr;
+	GList *next;
+	qq_transaction *trans;
+
+	g_return_if_fail(qd != NULL);
+
+	next = qd->transactions;
+	while( (curr = next) ) {
+		next = curr->next;
+		trans = (qq_transaction *) (curr->data);
+		/* purple_debug(PURPLE_DEBUG_ERROR, "QQ_TRANS", "Scan [%d]\n", trans->seq); */
+		
+		if ( !(trans->flag & QQ_TRANS_IS_SERVER) ) {
+			continue;
+		}
+		if ( !(trans->flag & QQ_TRANS_BEFORE_LOGIN) ) {
+			continue;
+		}
+		// set QQ_TRANS_BEFORE_LOGIN off
+		trans->flag &= ~QQ_TRANS_BEFORE_LOGIN;
+
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ_TRANS",
+				"Process server cmd before login, seq %d, data %p, len %d, send_retries %d\n",
+				trans->seq, trans->data, trans->data_len, trans->send_retries);
+
+		qq_proc_cmd_reply(qd->gc, trans->seq, trans->cmd, trans->data, trans->data_len);
+	}
+
+	/* purple_debug(PURPLE_DEBUG_INFO, "QQ_TRANS", "Scan finished\n"); */
+	return;
+}
+
+gboolean qq_trans_scan(qq_data *qd)
+{
+	GList *curr;
+	GList *next;
+	qq_transaction *trans;
+
+	g_return_val_if_fail(qd != NULL, FALSE);
+	
+	next = qd->transactions;
+	while( (curr = next) ) {
+		next = curr->next;
+		trans = (qq_transaction *) (curr->data);
+		/* purple_debug(PURPLE_DEBUG_INFO, "QQ_TRANS", "Scan [%d]\n", trans->seq); */
+		
+		if (trans->flag & QQ_TRANS_BEFORE_LOGIN) {
+			/* keep server cmd before login*/
+			continue;
+		}
+
+		trans->scan_times++;
+		if (trans->scan_times <= 1) {
+			/* skip in 10 seconds */
+			continue;
+		}
+
+		if (trans->rcved_times > 0) {
+			/* Has been received */
+			trans_remove(qd, trans);
+			continue;
+		}
+
+		if (trans->flag & QQ_TRANS_IS_SERVER) {
+			continue;
+		}
+		
+		/* Never get reply */
+		trans->send_retries--;
+		if (trans->send_retries <= 0) {
+			purple_debug(PURPLE_DEBUG_WARNING, "QQ_TRANS",
+				"[%d] %s is lost.\n",
+				trans->seq, qq_get_cmd_desc(trans->cmd));
+			if (trans->flag & QQ_TRANS_CLI_IMPORT) {
+				return TRUE;
+			}
+
+			purple_debug(PURPLE_DEBUG_ERROR, "QQ_TRANS",
+				"Lost [%d] %s, data %p, len %d, retries %d\n",
+				trans->seq, qq_get_cmd_desc(trans->cmd),
+				trans->data, trans->data_len, trans->send_retries);
+			trans_remove(qd, trans);
+			continue;
+		}
+
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ_TRANS",
+				"Resend [%d] %s data %p, len %d, send_retries %d\n",
+				trans->seq, qq_get_cmd_desc(trans->cmd),
+				trans->data, trans->data_len, trans->send_retries);
+		qq_send_data(qd, trans->cmd, trans->seq, FALSE, trans->data, trans->data_len);
+	}
+
+	/* purple_debug(PURPLE_DEBUG_INFO, "QQ_TRANS", "Scan finished\n"); */
+	return FALSE;
+}
+
+/* clean up send trans and free all contents */
+void qq_trans_remove_all(qq_data *qd)
+{
+	GList *curr;
+	GList *next;
+	qq_transaction *trans;
+	gint count = 0;
+
+	curr = qd->transactions;
+	while(curr) {
+		next = curr->next;
+		
+		trans = (qq_transaction *) (curr->data);
+		/*
+		purple_debug(PURPLE_DEBUG_ERROR, "QQ_TRANS",
+			"Remove to transaction, seq = %d, buf = %p, len = %d\n",
+			trans->seq, trans->buf, trans->len);
+		*/
+		trans_remove(qd, trans);
+
+		count++;
+		curr = next;
+	}
+	g_list_free(qd->transactions);
+
+	purple_debug(PURPLE_DEBUG_INFO, "QQ_TRANS", "Free all %d packets\n", count);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/qq/qq_trans.h	Thu Nov 20 21:13:56 2008 +0000
@@ -0,0 +1,61 @@
+/**
+ * @file qq_trans.h
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#ifndef _QQ_SEND_QUEUE_H_
+#define _QQ_SEND_QUEUE_H_
+
+#include <glib.h>
+#include "qq.h"
+
+enum {
+	QQ_TRANS_IS_SERVER = 0x01,			/* Is server command or client command */
+	/* prefix QQ_TRANS_CLI is for client command*/
+	QQ_TRANS_CLI_EMERGE = 0x02,		/* send at once; or may wait for next reply*/
+	QQ_TRANS_CLI_IMPORT = 0x04,		/* Only notice if not get reply; or resend, disconn if reties get 0*/
+	QQ_TRANS_BEFORE_LOGIN = 0x08,	/* server command before login*/
+};
+
+typedef struct _qq_transaction {
+	guint8 flag;
+	guint16 seq;
+	guint16 cmd;
+	guint8 *data;
+	gint data_len;
+
+	gint fd;
+	gint send_retries;
+	gint rcved_times;
+	gint scan_times;
+} qq_transaction;
+
+qq_transaction *qq_trans_find_rcved(qq_data *qd, guint16 cmd, guint16 seq);
+gboolean qq_trans_is_server(qq_transaction *trans) ;
+gboolean qq_trans_is_dup(qq_transaction *trans);
+void qq_trans_add_client_cmd(qq_data *qd, guint16 cmd, guint16 seq, guint8 *data, gint data_len);
+void qq_trans_add_server_cmd(qq_data *qd, guint16 cmd, guint16 seq, guint8 *data, gint data_len);
+void qq_trans_process_before_login(qq_data *qd);
+gboolean qq_trans_scan(qq_data *qd);
+void qq_trans_remove_all(qq_data *qd);
+
+#endif
--- a/libpurple/protocols/qq/recv_core.c	Mon Aug 18 17:08:01 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,326 +0,0 @@
-/**
- * @file recv_core.c
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#include "debug.h"
-#include "internal.h"
-
-#include "buddy_info.h"
-#include "buddy_list.h"
-#include "buddy_opt.h"
-#include "buddy_status.h"
-#include "char_conv.h"
-#include "crypt.h"
-#include "group_network.h"
-#include "header_info.h"
-#include "keep_alive.h"
-#include "im.h"
-#include "login_logout.h"
-#include "packet_parse.h"
-#include "qq_proxy.h"
-#include "recv_core.h"
-#include "sendqueue.h"
-#include "sys_msg.h"
-#include "utils.h"
-
-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, PurpleConnection *gc)
-{
-	qq_data *qd;
-	guint8 *byte, mask;
-
-	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 */
-}
-
-/* default process, decrypt and dump */
-static void _qq_process_packet_default(guint8 *buf, gint buf_len, guint16 cmd, guint16 seq, PurpleConnection *gc)
-{
-	qq_data *qd;
-	guint8 *data;
-	gchar *msg_utf8;
-	gint len;
-
-	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;
-
-	_qq_show_packet("Processing unknown packet", buf, len);
-	if (qq_decrypt(buf, buf_len, qd->session_key, data, &len)) {
-		gchar *hex_dump = hex_dump_to_str(data, len);
-		purple_debug(PURPLE_DEBUG_WARNING, "QQ",
-			   ">>> [%d] %s, %d bytes -> [default] decrypt and dump\n%s",
-			   seq, qq_get_cmd_desc(cmd), buf_len, hex_dump);
-		g_free(hex_dump);
-		try_dump_as_gbk(data, len);
-	} else {
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Fail decrypt packet with default process\n");
-	}
-}
-
-/* process the incoming packet from qq_pending */
-static void _qq_packet_process(guint8 *buf, gint buf_len, PurpleConnection *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(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) {
-		gchar *hex_dump = hex_dump_to_str(buf, buf_len);
-		purple_debug(PURPLE_DEBUG_ERROR,
-			   "QQ", "Received packet is too short, dump and drop\n%s", hex_dump);
-		g_free(hex_dump);
-		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 */
-			purple_debug
-			    (PURPLE_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 */
-		}
-	}
-
-	/* 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 */
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ",
-			   "Fail reading packet header, expect %d bytes, read %d bytes\n", 
-			   bytes_expected, bytes_read);
-		return;
-	}
-
-	if ((buf[buf_len - 1] != QQ_PACKET_TAIL) || (header.header_tag != QQ_PACKET_TAG)) {
-		gchar *hex_dump = hex_dump_to_str(buf, buf_len);
-		purple_debug(PURPLE_DEBUG_ERROR,
-			   "QQ", "Unknown QQ proctocol, dump and drop\n%s", hex_dump);
-		g_free(hex_dump);
-		return;
-	}
-
-	if (QQ_DEBUG)
-		purple_debug(PURPLE_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) {
-		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);
-		}
-	}
-
-	/* 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)) {
-			purple_debug(PURPLE_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)
-				purple_debug(PURPLE_DEBUG_INFO, "QQ",
-					   "ack [%05d] %s, remove from sendqueue\n",
-					   header.seq, qq_get_cmd_desc(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:
-		qq_process_get_all_list_with_group_reply(cursor, len, gc);
-		break;
-	case QQ_CMD_GET_LEVEL:
-		qq_process_get_level_reply(cursor, len, gc);
-		break;
-	case QQ_CMD_REQUEST_LOGIN_TOKEN:
-		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;
-	}
-}
-
-/* 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);
-	}
-}
-
-void qq_input_pending(gpointer data, gint source, PurpleInputCondition cond)
-{
-	PurpleConnection *gc;
-	qq_data *qd;
-	guint8 *buf;
-	gint len;
-
-	gc = (PurpleConnection *) data;
-
-	if(cond != PURPLE_INPUT_READ) {
-		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-			_("Socket error"));
-		return;
-	}
-
-	qd = (qq_data *) gc->proto_data;
-	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) {
-		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-			_("Unable to read from socket"));
-		return;
-	} else {
-		_qq_packet_process(buf, len, gc);
-	}
-}
--- a/libpurple/protocols/qq/recv_core.h	Mon Aug 18 17:08:01 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-/**
- * @file recv_core.h
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#ifndef _QQ_RECV_CORE_H_
-#define _QQ_RECV_CORE_H_
-
-#include <glib.h>
-#include "connection.h"
-#include "qq.h"
-
-void qq_b4_packets_free(qq_data *qd);
-
-void qq_input_pending(gpointer data, gint source, PurpleInputCondition cond);
-
-#endif
--- a/libpurple/protocols/qq/send_core.c	Mon Aug 18 17:08:01 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,163 +0,0 @@
-/**
- * @file send_core.c
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#include "debug.h"
-#include "internal.h"
-
-#include "crypt.h"
-#include "header_info.h"
-#include "packet_parse.h"
-#include "qq.h"
-#include "qq_proxy.h"
-#include "send_core.h"
-#include "sendqueue.h"
-
-/* create qq packet header with given sequence
- * return the number of bytes in header if succeeds
- * return -1 if there is any error */
-gint _create_packet_head_seq(guint8 *buf, guint8 **cursor,
-			     PurpleConnection *gc, guint16 cmd, gboolean is_auto_seq, guint16 *seq)
-{
-	qq_data *qd;
-	gint bytes_expected, bytes_written;
-
-	g_return_val_if_fail(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) {
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ",
-			   "Fail create qq header, expect %d bytes, written %d bytes\n", bytes_expected, bytes_written);
-		bytes_written = -1;
-	}
-	return bytes_written;
-}
-
-/* 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 */
-gint _qq_send_packet(PurpleConnection *gc, guint8 *buf, gint len, guint16 cmd)
-{
-	qq_data *qd;
-	qq_sendpacket *p;
-	gint bytes_sent;
-	guint8 *cursor;
-
-	qd = (qq_data *) gc->proto_data;
-
-	if (qd->use_tcp) {
-		if (len > MAX_PACKET_SIZE) {
-			purple_debug(PURPLE_DEBUG_ERROR, "QQ",
-				   "xxx [%05d] %s, %d bytes is too large, do not send\n",
-				   qd->send_seq, qq_get_cmd_desc(cmd), len);
-			return -1;
-		} else {	/* I update the len for TCP packet */
-			cursor = buf;
-			create_packet_w(buf, &cursor, len);
-		}
-	}
-
-	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);
-	}
-
-	return bytes_sent;
-}
-
-/* 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(PurpleConnection *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;
-
-	qd = (qq_data *) gc->proto_data;
-	g_return_val_if_fail(qd->session_key != NULL, -1);
-
-	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_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)
-				purple_debug(PURPLE_DEBUG_INFO, "QQ",
-					   "<== [%05d] %s, %d bytes\n", seq_ret, qq_get_cmd_desc(cmd), bytes_sent);
-			return bytes_sent;
-		} else {	/* bad packet */
-			purple_debug(PURPLE_DEBUG_ERROR, "QQ",
-				   "Fail creating packet, expect %d bytes, written %d bytes\n",
-				   bytes_expected, bytes_written);
-			return -1;
-		}
-	}
-
-	return -1;
-}
--- a/libpurple/protocols/qq/send_core.h	Mon Aug 18 17:08:01 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-/**
- * @file send_core.h
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#ifndef _QQ_SEND_CORE_H_
-#define _QQ_SEND_CORE_H_
-
-#include <glib.h>
-#include "connection.h"
-
-gint qq_send_cmd(PurpleConnection *gc, guint16 cmd, gboolean is_auto_seq, guint16 seq, 
-		gboolean need_ack, guint8 *data, gint len);
-gint _qq_send_packet(PurpleConnection * gc, guint8 *buf, gint len, guint16 cmd);
-gint _create_packet_head_seq(guint8 *buf, guint8 **cursor,
-		PurpleConnection *gc, guint16 cmd, gboolean is_auto_seq, guint16 *seq);
-
-#endif
--- a/libpurple/protocols/qq/send_file.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/send_file.c	Thu Nov 20 21:13:56 2008 +0000
@@ -29,14 +29,14 @@
 #include "network.h"
 #include "notify.h"
 
-#include "buddy_status.h"
+#include "buddy_list.h"
 #include "crypt.h"
 #include "file_trans.h"
 #include "header_info.h"
 #include "im.h"
-#include "keep_alive.h"
+#include "qq_base.h"
 #include "packet_parse.h"
-#include "send_core.h"
+#include "qq_network.h"
 #include "utils.h"
 
 enum
@@ -103,6 +103,7 @@
 	return send(info->sender_fd, buf, len, 0);
 }
 */
+
 static ssize_t _qq_xfer_udp_send(const guint8 *buf, size_t len, PurpleXfer *xfer)
 {
 	struct sockaddr_in sin;
@@ -243,42 +244,45 @@
 	g_free(internet_ip_str);
 }
 
-void qq_get_conn_info(guint8 *data, guint8 **cursor, gint data_len, ft_info *info)
+#define QQ_CONN_INFO_LEN	61
+gint qq_get_conn_info(ft_info *info, guint8 *data)
 {
-	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);
+	gint bytes = 0;
+	/* 16 + 30 + 1 + 4 + 2 + 2 + 4 + 2 = 61 */
+	bytes += qq_getdata(info->file_session_key, 16, data + bytes);
+	bytes += 30;	/* skip 30 bytes */
+	bytes += qq_get8(&info->conn_method, data + bytes);
+	bytes += qq_get32(&info->remote_internet_ip, data + bytes);
+	bytes += qq_get16(&info->remote_internet_port, data + bytes);
+	bytes += qq_get16(&info->remote_major_port, data + bytes);
+	bytes += qq_get32(&info->remote_real_ip, data + bytes);
+	bytes += qq_get16(&info->remote_minor_port, data + bytes);
 	qq_show_conn_info(info);
+	return bytes;
 }
 
-gint qq_fill_conn_info(guint8 *raw_data, guint8 **cursor, ft_info *info)
+gint qq_fill_conn_info(guint8 *raw_data, ft_info *info)
 {
-	gint bytes;
-	bytes = 0;
+	gint bytes = 0;
 	/* 064: connection method, UDP 0x00, TCP 0x03 */
-	bytes += create_packet_b (raw_data, cursor, info->conn_method);
+	bytes += qq_put8 (raw_data + bytes, info->conn_method);
 	/* 065-068: outer ip address of sender (proxy address) */
-	bytes += create_packet_dw (raw_data, cursor, info->local_internet_ip);
+	bytes += qq_put32 (raw_data + bytes, info->local_internet_ip);
 	/* 069-070: sender port */
-	bytes += create_packet_w (raw_data, cursor, info->local_internet_port);
+	bytes += qq_put16 (raw_data + bytes, 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);
+	bytes += qq_put16 (raw_data + bytes, info->local_major_port);
 	/* 073-076: real ip */
-	bytes += create_packet_dw (raw_data, cursor, info->local_real_ip);
+	bytes += qq_put32 (raw_data + bytes, info->local_real_ip);
 	/* 077-078: the second listening port */
-	bytes += create_packet_w (raw_data, cursor, info->local_minor_port);
+	bytes += qq_put16 (raw_data + bytes, info->local_minor_port);
 	return bytes;
 }
 
 
 /* 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)
+(guint8 *raw_data, guint32 to_uid, guint16 message_type, qq_data *qd, gboolean seq_ack)
 {
 	gint bytes;
 	time_t now;
@@ -294,42 +298,42 @@
 	}
 
 	/* 000-003: receiver uid */
-	bytes += create_packet_dw (raw_data, cursor, qd->uid);
+	bytes += qq_put32 (raw_data + bytes, qd->uid);
 	/* 004-007: sender uid */
-	bytes += create_packet_dw (raw_data, cursor, to_uid);
+	bytes += qq_put32 (raw_data + bytes, to_uid);
 	/* 008-009: sender client version */
-	bytes += create_packet_w (raw_data, cursor, QQ_CLIENT);
+	bytes += qq_put16 (raw_data + bytes, QQ_CLIENT);
 	/* 010-013: receiver uid */
-	bytes += create_packet_dw (raw_data, cursor, qd->uid);
+	bytes += qq_put32 (raw_data + bytes, qd->uid);
 	/* 014-017: sender uid */
-	bytes += create_packet_dw (raw_data, cursor, to_uid);
+	bytes += qq_put32 (raw_data + bytes, to_uid);
 	/* 018-033: md5 of (uid+session_key) */
-	bytes += create_packet_data (raw_data, cursor, qd->session_md5, 16);
+	bytes += qq_putdata (raw_data + bytes, qd->session_md5, 16);
 	/* 034-035: message type */
-	bytes += create_packet_w (raw_data, cursor, message_type);
+	bytes += qq_put16 (raw_data + bytes, message_type);
 	/* 036-037: sequence number */
-	bytes += create_packet_w (raw_data, cursor, seq);
+	bytes += qq_put16 (raw_data + bytes, seq);
 	/* 038-041: send time */
-	bytes += create_packet_dw (raw_data, cursor, (guint32) now);
+	bytes += qq_put32 (raw_data + bytes, (guint32) now);
 	/* 042-042: always 0x00 */
-	bytes += create_packet_b (raw_data, cursor, 0x00);
+	bytes += qq_put8 (raw_data + bytes, 0x00);
 	/* 043-043: sender icon */
-	bytes += create_packet_b (raw_data, cursor, qd->my_icon);
+	bytes += qq_put8 (raw_data + bytes, qd->my_icon);
 	/* 044-046: always 0x00 */
-	bytes += create_packet_w (raw_data, cursor, 0x0000);
-	bytes += create_packet_b (raw_data, cursor, 0x00);
+	bytes += qq_put16 (raw_data + bytes, 0x0000);
+	bytes += qq_put8 (raw_data + bytes, 0x00);
 	/* 047-047: we use font attr */
-	bytes += create_packet_b (raw_data, cursor, 0x01);
+	bytes += qq_put8 (raw_data + bytes, 0x01);
 	/* 048-051: always 0x00 */
-	bytes += create_packet_dw (raw_data, cursor, 0x00000000);
+	bytes += qq_put32 (raw_data + bytes, 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);
+	bytes += qq_put32 (raw_data + bytes, 0x00000000);
+	bytes += qq_put32 (raw_data + bytes, 0x00000000);
+	bytes += qq_put16 (raw_data + bytes, 0x0000);
+	bytes += qq_put8 (raw_data + bytes, 0x00);
 	/* 063: transfer_type,  0x65: FILE 0x6b: FACE */
-	bytes += create_packet_b (raw_data, cursor, QQ_FILE_TRANSFER_FILE); /* FIXME */
+	bytes += qq_put8 (raw_data + bytes, QQ_FILE_TRANSFER_FILE); /* FIXME */
 
 	return bytes;
 }
@@ -433,7 +437,7 @@
 static void _qq_send_packet_file_request (PurpleConnection *gc, guint32 to_uid, gchar *filename, gint filesize)
 {
 	qq_data *qd;
-	guint8 *cursor, *raw_data;
+	guint8 *raw_data;
 	gchar *filelen_str;
 	gint filename_len, filelen_strlen, packet_len, bytes;
 	ft_info *info;
@@ -443,7 +447,7 @@
 	info = g_new0(ft_info, 1);
 	info->to_uid = to_uid;
 	info->send_seq = qd->send_seq;
-	info->local_internet_ip = g_ntohl(inet_addr(qd->my_ip));
+	info->local_internet_ip = qd->my_ip.s_addr;
 	info->local_internet_port = qd->my_port;
 	info->local_real_ip = 0x00000000;
 	info->conn_method = 0x00;
@@ -455,27 +459,24 @@
 
 	packet_len = 82 + filename_len + filelen_strlen;
 	raw_data = g_newa(guint8, packet_len);
-	cursor = raw_data;
+	bytes = 0;
 
-	bytes = _qq_create_packet_file_header(raw_data, &cursor, to_uid, 
+	bytes += _qq_create_packet_file_header(raw_data + bytes, to_uid, 
 			QQ_FILE_TRANS_REQ, qd, FALSE);
-	bytes += qq_fill_conn_info(raw_data, &cursor, info);
+	bytes += qq_fill_conn_info(raw_data + bytes, info);
 	/* 079: 0x20 */
-	bytes += create_packet_b (raw_data, &cursor, 0x20);
+	bytes += qq_put8 (raw_data + bytes, 0x20);
 	/* 080: 0x1f */
-	bytes += create_packet_b (raw_data, &cursor, 0x1f);
+	bytes += qq_put8 (raw_data + bytes, 0x1f);
 	/* undetermined len: filename */
-	bytes += create_packet_data (raw_data, &cursor, (guint8 *) filename,
-				     filename_len);
+	bytes += qq_putdata (raw_data + bytes, (guint8 *) filename, filename_len);
 	/* 0x1f */
-	bytes += create_packet_b (raw_data, &cursor, 0x1f);
+	bytes += qq_put8 (raw_data + bytes, 0x1f);
 	/* file length */
-	bytes += create_packet_data (raw_data, &cursor, (guint8 *) filelen_str,
-				     filelen_strlen);
+	bytes += qq_putdata (raw_data + bytes, (guint8 *) filelen_str, filelen_strlen);
 
 	if (packet_len == bytes)
-		qq_send_cmd (gc, QQ_CMD_SEND_IM, TRUE, 0, TRUE, raw_data,
-			     cursor - raw_data);
+		qq_send_cmd (qd, QQ_CMD_SEND_IM, raw_data, bytes);
 	else
 		purple_debug (PURPLE_DEBUG_INFO, "qq_send_packet_file_request",
 			    "%d bytes expected but got %d bytes\n",
@@ -488,7 +489,7 @@
 static void _qq_send_packet_file_accept(PurpleConnection *gc, guint32 to_uid)
 {
 	qq_data *qd;
-	guint8 *cursor, *raw_data;
+	guint8 *raw_data;
 	guint16 minor_port;
 	guint32 real_ip;
 	gint packet_len, bytes;
@@ -502,22 +503,21 @@
 
 	packet_len = 79;
 	raw_data = g_newa (guint8, packet_len);
-	cursor = raw_data;
+	bytes = 0;
 
 	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);
+	bytes += _qq_create_packet_file_header(raw_data + bytes, to_uid, QQ_FILE_TRANS_ACC_UDP, qd, TRUE);
+	bytes += qq_fill_conn_info(raw_data + bytes, 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);
+		qq_send_cmd (qd, QQ_CMD_SEND_IM, raw_data, bytes);
 	else
 		purple_debug (PURPLE_DEBUG_INFO, "qq_send_packet_file_accept",
 			    "%d bytes expected but got %d bytes\n",
@@ -529,7 +529,7 @@
 	PurpleXfer *xfer;
 	ft_info *info;
 	qq_data *qd;
-	guint8 *cursor, *raw_data;
+	guint8 *raw_data;
 	gint packet_len, bytes;
 
 	qd = (qq_data *) gc->proto_data;
@@ -538,14 +538,13 @@
 
 	packet_len = 79;
 	raw_data = g_newa (guint8, packet_len);
-	cursor = raw_data;
+	bytes = 0;
 
 	purple_debug(PURPLE_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);
+	bytes += _qq_create_packet_file_header(raw_data + bytes, to_uid, QQ_FILE_TRANS_NOTIFY, qd, TRUE);
+	bytes += qq_fill_conn_info(raw_data + bytes, info);
 	if (packet_len == bytes)
-		qq_send_cmd (gc, QQ_CMD_SEND_IM, TRUE, 0, TRUE, raw_data,
-			     cursor - raw_data);
+		qq_send_cmd (qd, QQ_CMD_SEND_IM, raw_data, bytes);
 	else
 		purple_debug (PURPLE_DEBUG_INFO, "qq_send_packet_file_notify",
 			    "%d bytes expected but got %d bytes\n",
@@ -560,7 +559,7 @@
 static void _qq_send_packet_file_reject (PurpleConnection *gc, guint32 to_uid)
 {
 	qq_data *qd;
-	guint8 *cursor, *raw_data;
+	guint8 *raw_data;
 	gint packet_len, bytes;
 
 	purple_debug(PURPLE_DEBUG_INFO, "_qq_send_packet_file_reject", "start");
@@ -568,14 +567,12 @@
 
 	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);
+	bytes += _qq_create_packet_file_header(raw_data + bytes, 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);
+		qq_send_cmd (qd, QQ_CMD_SEND_IM, raw_data, bytes);
 	else
 		purple_debug (PURPLE_DEBUG_INFO, "qq_send_packet_file",
 			    "%d bytes expected but got %d bytes\n",
@@ -586,7 +583,7 @@
 static void _qq_send_packet_file_cancel (PurpleConnection *gc, guint32 to_uid)
 {
 	qq_data *qd;
-	guint8 *cursor, *raw_data;
+	guint8 *raw_data;
 	gint packet_len, bytes;
 
 	purple_debug(PURPLE_DEBUG_INFO, "_qq_send_packet_file_cancel", "start\n");
@@ -594,17 +591,15 @@
 
 	packet_len = 64;
 	raw_data = g_newa (guint8, packet_len);
-	cursor = raw_data;
 	bytes = 0;
 
 	purple_debug(PURPLE_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);
+	bytes += _qq_create_packet_file_header(raw_data + bytes, to_uid, QQ_FILE_TRANS_CANCEL, qd, TRUE);
 	purple_debug(PURPLE_DEBUG_INFO, "_qq_send_packet_file_cancel", "end create header\n");
 
 	if (packet_len == bytes) {
 		purple_debug(PURPLE_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);
+		qq_send_cmd (qd, QQ_CMD_SEND_IM, raw_data, bytes);
 	}
 	else
 		purple_debug (PURPLE_DEBUG_INFO, "qq_send_packet_file",
@@ -688,7 +683,7 @@
 }
 
 /* process reject im for file transfer request */
-void qq_process_recv_file_reject (guint8 *data, guint8 **cursor, gint data_len, 
+void qq_process_recv_file_reject (guint8 *data, gint data_len, 
 		guint32 sender_uid, PurpleConnection *gc)
 {
 	gchar *msg, *filename;
@@ -698,11 +693,13 @@
 	qd = (qq_data *) gc->proto_data;
 	g_return_if_fail (qd->xfer != NULL);
 
+	/*	border has been checked before
 	if (*cursor >= (data + data_len - 1)) {
 		purple_debug (PURPLE_DEBUG_WARNING, "QQ",
 			    "Received file reject message is empty\n");
 		return;
 	}
+	*/
 	filename = strrchr(purple_xfer_get_local_filename(qd->xfer), '/') + 1;
 	msg = g_strdup_printf(_("%d has declined the file %s"),
 		 sender_uid, filename);
@@ -715,7 +712,7 @@
 }
 
 /* process cancel im for file transfer request */
-void qq_process_recv_file_cancel (guint8 *data, guint8 **cursor, gint data_len, 
+void qq_process_recv_file_cancel (guint8 *data, gint data_len, 
 		guint32 sender_uid, PurpleConnection *gc)
 {
 	gchar *msg, *filename;
@@ -726,11 +723,13 @@
 	g_return_if_fail (qd->xfer != NULL
 			&& purple_xfer_get_filename(qd->xfer) != NULL);
 
+	/*	border has been checked before
 	if (*cursor >= (data + data_len - 1)) {
 		purple_debug (PURPLE_DEBUG_WARNING, "QQ",
 			    "Received file reject message is empty\n");
 		return;
 	}
+	*/
 	filename = strrchr(purple_xfer_get_local_filename(qd->xfer), '/') + 1;
 	msg = g_strdup_printf
 		(_("%d canceled the transfer of %s"),
@@ -744,27 +743,26 @@
 }
 
 /* process accept im for file transfer request */
-void qq_process_recv_file_accept(guint8 *data, guint8 **cursor, gint data_len, 
-		guint32 sender_uid, PurpleConnection *gc)
+void qq_process_recv_file_accept(guint8 *data, gint data_len, guint32 sender_uid, PurpleConnection *gc)
 {
 	qq_data *qd;
+	gint bytes;
 	ft_info *info;
 	PurpleXfer *xfer;
 
 	g_return_if_fail (data != NULL && data_len != 0);
 	qd = (qq_data *) gc->proto_data;
 	xfer = qd->xfer;
+	info = (ft_info *) qd->xfer->data;
 
-	if (*cursor >= (data + data_len - 1)) {
+	if (data_len <= 30 + QQ_CONN_INFO_LEN) {
 		purple_debug (PURPLE_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);
+	bytes = 18 + 12;	/* skip 30 bytes */
+	qq_get_conn_info(info, data + bytes);
 	_qq_xfer_init_socket(qd->xfer);
 
 	_qq_xfer_init_udp_channel(info);
@@ -772,8 +770,7 @@
 }
 
 /* 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, PurpleConnection * gc)
+void qq_process_recv_file_request(guint8 *data, gint data_len, guint32 sender_uid, PurpleConnection * gc)
 {
 	qq_data *qd;
 	PurpleXfer *xfer;
@@ -781,25 +778,27 @@
 	ft_info *info;
 	PurpleBuddy *b;
 	qq_buddy *q_bud;
+	gint bytes;
 
 	g_return_if_fail (data != NULL && data_len != 0);
 	qd = (qq_data *) gc->proto_data;
 
-	if (*cursor >= (data + data_len - 1)) {
-		purple_debug (PURPLE_DEBUG_WARNING, "QQ",
-			    "Received file reject message is empty\n");
-		return;
-	}
-
-	info = g_new0(ft_info, 1);
-	info->local_internet_ip = g_ntohl(inet_addr(qd->my_ip));
+	info = g_newa(ft_info, 1);
+	info->local_internet_ip = qd->my_ip.s_addr;
 	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));
+	
+	if (data_len <= 2 + 30 + QQ_CONN_INFO_LEN) {
+		purple_debug (PURPLE_DEBUG_WARNING, "QQ",
+			    "Received file request message is empty\n");
+		return;
+	}
+	bytes = 0;
+	bytes += qq_get16(&(info->send_seq), data + bytes);
 
-	*cursor = data + 18 + 12;
-	qq_get_conn_info(data, cursor, data_len, info);
+	bytes += 18 + 12;	/* skip 30 bytes */
+	bytes += qq_get_conn_info(info, data + bytes);
 
 	fileinfo = g_strsplit((gchar *) (data + 81 + 12), "\x1f", 2);
 	g_return_if_fail (fileinfo != NULL && fileinfo[0] != NULL && fileinfo[1] != NULL);
@@ -815,11 +814,11 @@
 		q_bud = (b == NULL) ? NULL : (qq_buddy *) b->proto_data;
 		if (q_bud) {
 			if(0 != info->remote_real_ip) {
-				g_memmove(q_bud->ip, &info->remote_real_ip, 4);
+				g_memmove(&(q_bud->ip), &info->remote_real_ip, sizeof(q_bud->ip));
 				q_bud->port = info->remote_minor_port;
 			}
 			else if (0 != info->remote_internet_ip) {
-				g_memmove(q_bud->ip, &info->remote_internet_ip, 4);
+				g_memmove(&(q_bud->ip), &info->remote_internet_ip, sizeof(q_bud->ip));
 				q_bud->port = info->remote_major_port;
 			}
 
@@ -832,7 +831,7 @@
 
 		}
 		else 
-			purple_debug(PURPLE_DEBUG_WARNING, "QQ", "buddy %d is not in my friendlist\n", sender_uid);
+			purple_debug(PURPLE_DEBUG_WARNING, "QQ", "buddy %d is not in list\n", sender_uid);
 
 		g_free(sender_name);	    
 		g_strfreev(fileinfo);
@@ -880,9 +879,10 @@
 	*/
 }
 
-void qq_process_recv_file_notify(guint8 *data, guint8 **cursor, gint data_len, 
+void qq_process_recv_file_notify(guint8 *data, gint data_len, 
 		guint32 sender_uid, PurpleConnection *gc)
 {
+	gint bytes;
 	qq_data *qd;
 	ft_info *info;
 	PurpleXfer *xfer;
@@ -890,19 +890,19 @@
 	g_return_if_fail (data != NULL && data_len != 0);
 	qd = (qq_data *) gc->proto_data;
 
-	if (*cursor >= (data + data_len - 1)) {
+	xfer = qd->xfer;
+	info = (ft_info *) qd->xfer->data;
+	if (data_len <= 2 + 30 + QQ_CONN_INFO_LEN) {
 		purple_debug (PURPLE_DEBUG_WARNING, "QQ",
 			    "Received file notify message is empty\n");
 		return;
 	}
+	
+	bytes = 0;
+	bytes += qq_get16(&(info->send_seq), data + bytes);
 
-	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);
+	bytes += 18 + 12;
+	bytes += qq_get_conn_info(info, data + bytes);
 
 	_qq_xfer_init_udp_channel(info);
 
@@ -938,7 +938,7 @@
 /*
 static void qq_send_packet_request_key(PurpleConnection *gc, guint8 key)
 {
-	qq_send_cmd(gc, QQ_CMD_REQUEST_KEY, TRUE, 0, TRUE, &key, 1);
+	qq_send_cmd(gc, QQ_CMD_REQUEST_KEY, &key, 1);
 }
 
 static void qq_process_recv_request_key(PurpleConnection *gc)
--- a/libpurple/protocols/qq/send_file.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/send_file.h	Thu Nov 20 21:13:56 2008 +0000
@@ -26,11 +26,12 @@
 #define _QQ_QQ_SEND_FILE_H_
 
 #include "ft.h"
+#include "qq.h"
 
 typedef struct _ft_info {
 	guint32 to_uid;
 	guint16 send_seq;
-	guint8 file_session_key[16];
+	guint8 file_session_key[QQ_KEY_LENGTH];
 	guint8 conn_method;
 	guint32 remote_internet_ip;
 	guint16 remote_internet_port;
@@ -66,20 +67,15 @@
 	gboolean use_major;
 } ft_info;
 
-void qq_process_recv_file_accept(guint8 *data, guint8 **cursor, gint data_len, 
-		guint32 sender_uid, PurpleConnection *gc);
-void qq_process_recv_file_reject(guint8 *data, guint8 **cursor, gint data_len, 
-		guint32 sender_uid, PurpleConnection *gc);
-void qq_process_recv_file_cancel(guint8 *data, guint8 **cursor, gint data_len, 
-		guint32 sender_uid, PurpleConnection *gc);
-void qq_process_recv_file_request(guint8 *data, guint8 **cursor, gint data_len, 
-		guint32 sender_uid, PurpleConnection *gc);
-void qq_process_recv_file_notify(guint8 *data, guint8 **cursor, gint data_len, 
-		guint32 sender_uid, PurpleConnection *gc);
+void qq_process_recv_file_accept(guint8 *data, gint data_len, guint32 sender_uid, PurpleConnection *gc);
+void qq_process_recv_file_reject(guint8 *data, gint data_len, guint32 sender_uid, PurpleConnection *gc);
+void qq_process_recv_file_cancel(guint8 *data, gint data_len, guint32 sender_uid, PurpleConnection *gc);
+void qq_process_recv_file_request(guint8 *data, gint data_len, guint32 sender_uid, PurpleConnection *gc);
+void qq_process_recv_file_notify(guint8 *data, gint data_len, guint32 sender_uid, PurpleConnection *gc);
 gboolean qq_can_receive_file(PurpleConnection *gc, const char *who);
 void qq_send_file(PurpleConnection *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);
+gint qq_get_conn_info(ft_info *info, guint8 *data);
+gint qq_fill_conn_info(guint8 *data, ft_info *info);
 gssize _qq_xfer_write(const guint8 *buf, size_t len, PurpleXfer *xfer);
 
 #endif
--- a/libpurple/protocols/qq/sendqueue.c	Mon Aug 18 17:08:01 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,156 +0,0 @@
-/**
- * @file sendqueue.c
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#include "internal.h"
-
-#include "connection.h"
-#include "debug.h"
-#include "notify.h"
-#include "prefs.h"
-#include "request.h"
-
-#include "header_info.h"
-#include "qq_proxy.h"
-#include "sendqueue.h"
-
-#define QQ_RESEND_MAX               8	/* max resend per packet */
-
-typedef struct _gc_and_packet gc_and_packet;
-
-struct _gc_and_packet {
-	PurpleConnection *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;
-
-	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;
-	}
-}
-		
-/* 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++;
-	}
-	purple_debug(PURPLE_DEBUG_INFO, "QQ", "%d packets in sendqueue are freed!\n", i);
-}
-
-/* FIXME We shouldn't be dropping packets, but for now we have to because
- * somewhere we're generating invalid packets that the server won't ack.
- * Given enough time, a buildup of those packets would crash the client. */
-gboolean qq_sendqueue_timeout_callback(gpointer data)
-{
-	PurpleConnection *gc;
-	qq_data *qd;
-	GList *list;
-	qq_sendpacket *p;
-	time_t now;
-	gint wait_time;
-
-	gc = (PurpleConnection *) 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;
-		}
-	}
-
-	list = qd->sendqueue;
-	while (list != NULL) {
-		p = (qq_sendpacket *) list->data;
-		if (p->resend_times == QQ_RESEND_MAX) {	/* reach max */
-			switch (p->cmd) {
-			case QQ_CMD_KEEP_ALIVE:
-				if (qd->logged_in) {
-					purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Connection lost!\n");
-					purple_connection_error_reason(gc,
-						PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Connection lost"));
-					qd->logged_in = FALSE;
-				}
-				p->resend_times = -1;
-				break;
-			case QQ_CMD_LOGIN:
-			case QQ_CMD_REQUEST_LOGIN_TOKEN:
-				if (!qd->logged_in)	/* cancel login progress */
-					purple_connection_error_reason(gc,
-						PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Login failed, no reply"));
-				p->resend_times = -1;
-				break;
-			default:{
-				purple_debug(PURPLE_DEBUG_WARNING, "QQ", 
-					"%s packet sent %d times but not acked. Not resending it.\n", 
-					qq_get_cmd_desc(p->cmd), QQ_RESEND_MAX);
-				}
-				p->resend_times = -1;
-			}
-		} 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++;
-				purple_debug(PURPLE_DEBUG_INFO,
-					   "QQ", "<<< [%05d] send again for %d times!\n", 
-					   p->send_seq, p->resend_times);
-			}
-		}
-		list = list->next;
-	}
-	return TRUE;		/* if we return FALSE, the timeout callback stops functioning */
-}
--- a/libpurple/protocols/qq/sendqueue.h	Mon Aug 18 17:08:01 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/**
- * @file sendqueue.h
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#ifndef _QQ_SEND_QUEUE_H_
-#define _QQ_SEND_QUEUE_H_
-
-#include <glib.h>
-#include "qq.h"
-
-#define QQ_SENDQUEUE_TIMEOUT 			5000	/* in 1/1000 sec */
-
-typedef struct _qq_sendpacket qq_sendpacket;
-
-struct _qq_sendpacket {
-	gint fd;
-	gint len;
-	guint8 *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
--- a/libpurple/protocols/qq/sys_msg.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/sys_msg.c	Thu Nov 20 21:13:56 2008 +0000
@@ -35,7 +35,7 @@
 #include "header_info.h"
 #include "packet_parse.h"
 #include "qq.h"
-#include "send_core.h"
+#include "qq_network.h"
 #include "sys_msg.h"
 #include "utils.h"
 
@@ -44,6 +44,7 @@
 	QQ_MSG_SYS_ADD_CONTACT_REQUEST = 0x02,
 	QQ_MSG_SYS_ADD_CONTACT_APPROVED = 0x03,
 	QQ_MSG_SYS_ADD_CONTACT_REJECTED = 0x04,
+	QQ_MSG_SYS_NOTICE= 0x06,
 	QQ_MSG_SYS_NEW_VERSION = 0x09
 };
 
@@ -120,27 +121,29 @@
 /* Send ACK if the sys message needs an ACK */
 static void _qq_send_packet_ack_msg_sys(PurpleConnection *gc, guint8 code, guint32 from, guint16 seq)
 {
-	guint8 bar, *ack, *cursor;
+	qq_data *qd;
+	guint8 bar, *ack;
 	gchar *str;
 	gint ack_len, bytes;
 
+	qd = (qq_data *) gc->proto_data;
+	
 	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, (guint8 *) str, strlen(str));
-	bytes += create_packet_b(ack, &cursor, bar);
-	bytes += create_packet_w(ack, &cursor, seq);
+	bytes += qq_put8(ack + bytes, code);
+	bytes += qq_put8(ack + bytes, bar);
+	bytes += qq_putdata(ack + bytes, (guint8 *) str, strlen(str));
+	bytes += qq_put8(ack + bytes, bar);
+	bytes += qq_put16(ack + bytes, 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);
+		qq_send_cmd_detail(qd, QQ_CMD_ACK_SYS_MSG, 0, FALSE, ack, ack_len);
 	else
 		purple_debug(PURPLE_DEBUG_ERROR, "QQ",
 			   "Fail creating sys msg ACK, expect %d bytes, build %d bytes\n", ack_len, bytes);
@@ -275,6 +278,20 @@
 	g_free(name);
 }
 
+static void _qq_process_msg_sys_notice(PurpleConnection *gc, gchar *from, gchar *to, gchar *msg_utf8)
+{
+	gchar *title, *content;
+
+	g_return_if_fail(from != NULL && to != NULL);
+
+	title = g_strdup_printf(_("Notice from: %s"), from);
+	content = g_strdup_printf(_("%s"), msg_utf8);
+
+	purple_notify_info(gc, NULL, title, content);
+	g_free(title);
+	g_free(content);
+}
+
 void qq_process_msg_sys(guint8 *buf, gint buf_len, guint16 seq, PurpleConnection *gc)
 {
 	qq_data *qd;
@@ -318,9 +335,12 @@
 		case QQ_MSG_SYS_ADD_CONTACT_REJECTED:
 			_qq_process_msg_sys_add_contact_rejected(gc, from, to, msg_utf8);
 			break;
+		case QQ_MSG_SYS_NOTICE:
+			_qq_process_msg_sys_notice(gc, from, to, msg_utf8);
+			break;
 		case QQ_MSG_SYS_NEW_VERSION:
 			purple_debug(PURPLE_DEBUG_WARNING, "QQ",
-				   "QQ server says there is newer version than %s\n", qq_get_source_str(QQ_CLIENT));
+				   "QQ server says there is newer version than %s\n", qq_get_ver_desc(QQ_CLIENT));
 			break;
 		default:
 			purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Recv unknown sys msg code: %s\n", code);
--- a/libpurple/protocols/qq/udp_proxy_s5.c	Mon Aug 18 17:08:01 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,381 +0,0 @@
-/**
- * @file udp_proxy_s5.c
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#include "debug.h"
-
-#include "udp_proxy_s5.h"
-
-static void _qq_s5_canread_again(gpointer data, gint source, PurpleInputCondition cond)
-{
-	unsigned char buf[512];
-	struct PHB *phb = data;
-	struct sockaddr_in sin;
-	int len, error;
-	socklen_t errlen;
-	int flags;
-
-	purple_input_remove(phb->inpa);
-	purple_debug(PURPLE_DEBUG_INFO, "socks5 proxy", "Able to read again.\n");
-
-	len = read(source, buf, 10);
-	if (len < 10) {
-		purple_debug(PURPLE_DEBUG_WARNING, "socks5 proxy", "or not...\n");
-		close(source);
-
-		if (phb->account == NULL || purple_account_get_connection(phb->account) != NULL) {
-
-			phb->func(phb->data, source, NULL);
-		}
-
-		g_free(phb->host);
-		g_free(phb);
-		return;
-	}
-	if ((buf[0] != 0x05) || (buf[1] != 0x00)) {
-		if ((buf[0] == 0x05) && (buf[1] < 0x09))
-			purple_debug(PURPLE_DEBUG_ERROR, "socks5 proxy", "socks5 error: %x\n", buf[1]);
-		else
-			purple_debug(PURPLE_DEBUG_ERROR, "socks5 proxy", "Bad data.\n");
-		close(source);
-
-		if (phb->account == NULL || purple_account_get_connection(phb->account) != NULL) {
-
-			phb->func(phb->data, -1, _("Unable to connect"));
-		}
-
-		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) {
-		purple_debug(PURPLE_DEBUG_INFO, "s5_canread_again", "connect failed: %s\n", g_strerror(errno));
-		close(phb->udpsock);
-		close(source);
-		g_free(phb->host);
-		g_free(phb);
-		return;
-	}
-
-	error = ETIMEDOUT;
-	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Connect didn't block\n");
-	errlen = sizeof(error);
-	if (getsockopt(phb->udpsock, SOL_SOCKET, SO_ERROR, &error, &errlen) < 0) {
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ", "getsockopt failed.\n");
-		close(phb->udpsock);
-		return;
-	}
-	flags = fcntl(phb->udpsock, F_GETFL);
-	fcntl(phb->udpsock, F_SETFL, flags & ~O_NONBLOCK);
-
-	if (phb->account == NULL || purple_account_get_connection(phb->account) != NULL) {
-		phb->func(phb->data, phb->udpsock, NULL);
-	}
-
-	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; 
-	socklen_t ctllen;
-	int flags;
-
-	purple_debug(PURPLE_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) {
-		purple_debug(PURPLE_DEBUG_INFO, "QQ", "getsockname: %s\n", g_strerror(errno));
-		close(source);
-		g_free(phb->host);
-		g_free(phb);
-		return;
-	}
-
-	phb->udpsock = socket(PF_INET, SOCK_DGRAM, 0);
-	purple_debug(PURPLE_DEBUG_INFO, "s5_sendconnect", "UDP socket=%d\n", phb->udpsock);
-	if (phb->udpsock < 0) {
-		close(source);
-		g_free(phb->host);
-		g_free(phb);
-		return;
-	}
-
-	flags = fcntl(phb->udpsock, F_GETFL);
-	fcntl(phb->udpsock, F_SETFL, flags | O_NONBLOCK);
-
-	port = g_ntohs(ctlsin.sin_port) + 1;
-	while (1) {
-		inet_aton("0.0.0.0", &(sin.sin_addr));
-		sin.sin_family = AF_INET;
-		sin.sin_port = g_htons(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);
-		purple_debug(PURPLE_DEBUG_INFO, "s5_sendconnect", "packet too small\n");
-
-		if (phb->account == NULL || purple_account_get_connection(phb->account) != NULL) {
-			phb->func(phb->data, -1, _("Unable to connect"));
-		}
-
-		g_free(phb->host);
-		g_free(phb);
-		return;
-	}
-
-	phb->inpa = purple_input_add(source, PURPLE_INPUT_READ, _qq_s5_canread_again, phb);
-}
-
-static void _qq_s5_readauth(gpointer data, gint source, PurpleInputCondition cond)
-{
-	unsigned char buf[512];
-	struct PHB *phb = data;
-
-	purple_input_remove(phb->inpa);
-	purple_debug(PURPLE_DEBUG_INFO, "socks5 proxy", "Got auth response.\n");
-
-	if (read(source, buf, 2) < 2) {
-		close(source);
-
-		if (phb->account == NULL || purple_account_get_connection(phb->account) != NULL) {
-
-			phb->func(phb->data, -1, _("Unable to connect"));
-		}
-
-		g_free(phb->host);
-		g_free(phb);
-		return;
-	}
-
-	if ((buf[0] != 0x01) || (buf[1] != 0x00)) {
-		close(source);
-
-		if (phb->account == NULL || purple_account_get_connection(phb->account) != NULL) {
-
-			phb->func(phb->data, -1, _("Unable to connect"));
-		}
-
-		g_free(phb->host);
-		g_free(phb);
-		return;
-	}
-
-	_qq_s5_sendconnect(phb, source);
-}
-
-static void _qq_s5_canread(gpointer data, gint source, PurpleInputCondition cond)
-{
-	unsigned char buf[512];
-	struct PHB *phb;
-	int ret;
-
-	phb = data;
-
-	purple_input_remove(phb->inpa);
-	purple_debug(PURPLE_DEBUG_INFO, "socks5 proxy", "Able to read.\n");
-
-	ret = read(source, buf, 2);
-	if (ret < 2) {
-		purple_debug(PURPLE_DEBUG_INFO, "s5_canread", "packet smaller than 2 octet\n");
-		close(source);
-
-		if (phb->account == NULL || purple_account_get_connection(phb->account) != NULL) {
-
-			phb->func(phb->data, -1, _("Unable to connect"));
-		}
-
-		g_free(phb->host);
-		g_free(phb);
-		return;
-	}
-
-	if ((buf[0] != 0x05) || (buf[1] == 0xff)) {
-		purple_debug(PURPLE_DEBUG_INFO, "s5_canread", "unsupport\n");
-		close(source);
-
-		if (phb->account == NULL || purple_account_get_connection(phb->account) != NULL) {
-
-			phb->func(phb->data, -1, _("Unable to connect"));
-		}
-
-		g_free(phb->host);
-		g_free(phb);
-		return;
-	}
-
-	if (buf[1] == 0x02) {
-		unsigned int i, j;
-
-		i = strlen(purple_proxy_info_get_username(phb->gpi));
-		j = strlen(purple_proxy_info_get_password(phb->gpi));
-
-		buf[0] = 0x01;	/* version 1 */
-		buf[1] = i;
-		memcpy(buf + 2, purple_proxy_info_get_username(phb->gpi), i);
-		buf[2 + i] = j;
-		memcpy(buf + 2 + i + 1, purple_proxy_info_get_password(phb->gpi), j);
-
-		if (write(source, buf, 3 + i + j) < 3 + i + j) {
-			close(source);
-
-			if (phb->account == NULL || purple_account_get_connection(phb->account) != NULL) {
-
-				phb->func(phb->data, -1, _("Unable to connect"));
-			}
-
-			g_free(phb->host);
-			g_free(phb);
-			return;
-		}
-
-		phb->inpa = purple_input_add(source, PURPLE_INPUT_READ, _qq_s5_readauth, phb);
-	} else {
-		purple_debug(PURPLE_DEBUG_INFO, "s5_canread", "calling s5_sendconnect\n");
-		_qq_s5_sendconnect(phb, source);
-	}
-}
-
-static void _qq_s5_canwrite(gpointer data, gint source, PurpleInputCondition cond)
-{
-	unsigned char buf[512];
-	int i;
-	struct PHB *phb = data;
-	socklen_t len;
-	int error = ETIMEDOUT;
-	int flags;
-
-	purple_debug(PURPLE_DEBUG_INFO, "socks5 proxy", "Connected.\n");
-
-	if (phb->inpa > 0)
-		purple_input_remove(phb->inpa);
-
-	len = sizeof(error);
-	if (getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
-		purple_debug(PURPLE_DEBUG_INFO, "getsockopt", "%s\n", g_strerror(errno));
-		close(source);
-		if (phb->account == NULL || purple_account_get_connection(phb->account) != NULL) {
-
-			phb->func(phb->data, -1, _("Unable to connect"));
-		}
-
-		g_free(phb->host);
-		g_free(phb);
-		return;
-	}
-	flags = fcntl(source, F_GETFL);
-	fcntl(source, F_SETFL, flags & ~O_NONBLOCK);
-
-	i = 0;
-	buf[0] = 0x05;		/* SOCKS version 5 */
-
-	if (purple_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) {
-		purple_debug(PURPLE_DEBUG_INFO, "write", "%s\n", g_strerror(errno));
-		purple_debug(PURPLE_DEBUG_ERROR, "socks5 proxy", "Unable to write\n");
-		close(source);
-
-		if (phb->account == NULL || purple_account_get_connection(phb->account) != NULL) {
-
-			phb->func(phb->data, -1, _("Unable to connect"));
-		}
-
-		g_free(phb->host);
-		g_free(phb);
-		return;
-	}
-
-	phb->inpa = purple_input_add(source, PURPLE_INPUT_READ, _qq_s5_canread, phb);
-}
-
-gint qq_proxy_socks5(struct PHB *phb, struct sockaddr *addr, socklen_t addrlen)
-{
-	gint fd;
-	int flags;
-
-	purple_debug(PURPLE_DEBUG_INFO, "QQ",
-		   "Connecting to %s:%d via %s:%d using SOCKS5\n",
-		   phb->host, phb->port, purple_proxy_info_get_host(phb->gpi), purple_proxy_info_get_port(phb->gpi));
-
-	if ((fd = socket(addr->sa_family, SOCK_STREAM, 0)) < 0)
-		return -1;
-
-	purple_debug(PURPLE_DEBUG_INFO, "QQ", "proxy_sock5 return fd=%d\n", fd);
-
-	flags = fcntl(fd, F_GETFL);
-	fcntl(fd, F_SETFL, flags | O_NONBLOCK);
-	if (connect(fd, addr, addrlen) < 0) {
-		if ((errno == EINPROGRESS) || (errno == EINTR)) {
-			purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Connect in asynchronous mode.\n");
-			phb->inpa = purple_input_add(fd, PURPLE_INPUT_WRITE, _qq_s5_canwrite, phb);
-		} else {
-			close(fd);
-			return -1;
-		}
-	} else {
-		purple_debug(PURPLE_DEBUG_MISC, "QQ", "Connect in blocking mode.\n");
-		flags = fcntl(fd, F_GETFL);
-		fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
-		_qq_s5_canwrite(phb, fd, PURPLE_INPUT_WRITE);
-	}
-
-	return fd;
-}
--- a/libpurple/protocols/qq/udp_proxy_s5.h	Mon Aug 18 17:08:01 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-/**
- * @file udp_proxy_s5.h
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#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
--- a/libpurple/protocols/qq/utils.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/utils.c	Thu Nov 20 21:13:56 2008 +0000
@@ -22,7 +22,6 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
-#include "cipher.h"
 #include "limits.h"
 #include "stdlib.h"
 #include "string.h"
@@ -31,6 +30,8 @@
 #include "win32dep.h"
 #endif
 
+#include "cipher.h"
+
 #include "char_conv.h"
 #include "debug.h"
 #include "prefs.h"
@@ -40,6 +41,32 @@
 
 #define QQ_NAME_FORMAT    "%d"
 
+/* These functions are used only in development phase */
+/*
+   static void _qq_show_socket(gchar *desc, gint fd) {
+   struct sockaddr_in sin;
+   socklen_t len = sizeof(sin);
+   getsockname(fd, (struct sockaddr *)&sin, &len);
+   purple_debug(PURPLE_DEBUG_INFO, desc, "%s:%d\n",
+   inet_ntoa(sin.sin_addr), g_ntohs(sin.sin_port));
+   }
+   */
+
+void qq_get_md5(guint8 *md5, gint md5_len, const guint8* const src, gint src_len)
+{
+	PurpleCipher *cipher;
+	PurpleCipherContext *context;
+
+	g_return_if_fail(md5 != NULL && md5_len > 0);
+	g_return_if_fail(src != NULL && src_len > 0);
+
+	cipher = purple_ciphers_find_cipher("md5");
+	context = purple_cipher_context_new(cipher, NULL);
+	purple_cipher_context_append(context, src, src_len);
+	purple_cipher_context_digest(context, md5_len, md5, NULL);
+	purple_cipher_context_destroy(context);
+}
+
 gchar *get_name_by_index_str(gchar **array, const gchar *index_str, gint amount)
 {
 	gint index;
@@ -87,7 +114,7 @@
 	g_memmove(input, data, len);
 	input[len] = 0x00;
 
-	segments = g_strsplit((gchar *) input, delimit, 0);
+	segments = g_strsplit_set((gchar *) input, delimit, 0);
 	if (expected_fields <= 0)
 		return segments;
 
@@ -113,30 +140,20 @@
 	return segments;
 }
 
-/* generate a md5 key using uid and session_key */
-guint8 *_gen_session_md5(gint uid, guint8 *session_key)
+/* convert Purple name to original QQ UID */
+guint32 purple_name_to_uid(const gchar *const name)
 {
-	guint8 *src, md5_str[QQ_KEY_LENGTH];
-	PurpleCipher *cipher;
-	PurpleCipherContext *context;
+	guint32 ret;
+	g_return_val_if_fail(name != NULL, 0);
 
-	src = g_newa(guint8, 20);
-	memcpy(src, &uid, 4);
-	memcpy(src, session_key, QQ_KEY_LENGTH);
-
-	cipher = purple_ciphers_find_cipher("md5");
-	context = purple_cipher_context_new(cipher, NULL);
-	purple_cipher_context_append(context, src, 20);
-	purple_cipher_context_digest(context, sizeof(md5_str), md5_str, NULL);
-	purple_cipher_context_destroy(context);
-
-	return g_memdup(md5_str, QQ_KEY_LENGTH);
+	ret = strtol(name, NULL, 10);
+	if (errno == ERANGE)
+		return 0;
+	else
+		return ret;
 }
 
-/* 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)
-{
+gchar *gen_ip_str(guint8 *ip) {
 	gchar *ret;
 	if (ip == NULL || ip[0] == 0) {
 		ret = g_new(gchar, 1);
@@ -159,19 +176,6 @@
 	return ip;
 }
 
-/* convert Purple name to original QQ UID */
-guint32 purple_name_to_uid(const gchar *const name)
-{
-	guint32 ret;
-	g_return_val_if_fail(name != NULL, 0);
-
-	ret = strtol(name, NULL, 10);
-	if (errno == ERANGE)
-		return 0;
-	else
-		return ret;
-}
-
 /* convert a QQ UID to a unique name of Purple
  * the return needs to be freed */
 gchar *uid_to_purple_name(guint32 uid)
@@ -194,7 +198,7 @@
 }
 
 /* try to dump the data as GBK */
-void try_dump_as_gbk(const guint8 *const data, gint len)
+gchar* try_dump_as_gbk(const guint8 *const data, gint len)
 {
 	gint i;
 	guint8 *incoming;
@@ -215,8 +219,8 @@
 
 	if (msg_utf8 != NULL) {
 		purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Try extract GB msg: %s\n", msg_utf8);
-		g_free(msg_utf8);
 	}
+	return msg_utf8;
 }
 
 /* strips whitespace */
@@ -294,7 +298,7 @@
 
 /* Dumps a chunk of raw data into an ASCII hex string.
  * The return should be freed later. */
-gchar *hex_dump_to_str(const guint8 *const buffer, gint bytes)
+static gchar *hex_dump_to_str(const guint8 *const buffer, gint bytes)
 {
 	GString *str;
 	gchar *ret;
@@ -303,12 +307,12 @@
 	str = g_string_new("");
 	for (i = 0; i < bytes; i += 16) {
 		/* length label */
-		g_string_append_printf(str, "%04d: ", i);
+		g_string_append_printf(str, "%07x: ", i);
 
 		/* dump hex value */
 		for (j = 0; j < 16; j++)
 			if ((i + j) < bytes)
-				g_string_append_printf(str, " %02X", buffer[i + j]);
+				g_string_append_printf(str, " %02x", buffer[i + j]);
 			else
 				g_string_append(str, "   ");
 		g_string_append(str, "  ");
@@ -331,6 +335,51 @@
 	return ret;
 }
 
+void qq_hex_dump(PurpleDebugLevel level, const char *category,
+		const guint8 *pdata, gint bytes,	
+		const char *format, ...)
+{
+	va_list args;
+	char *arg_s = NULL;
+	gchar *phex = NULL;
+
+	g_return_if_fail(level != PURPLE_DEBUG_ALL);
+	g_return_if_fail(format != NULL);
+
+	va_start(args, format);
+	arg_s = g_strdup_vprintf(format, args);
+	va_end(args);
+
+	if (bytes <= 0) {
+		purple_debug(level, category, arg_s);
+		return;
+	}
+
+	phex = hex_dump_to_str(pdata, bytes);
+	purple_debug(level, category, "%s - (len %d)\n%s", arg_s, bytes, phex);
+	g_free(phex);
+}
+
+void qq_show_packet(const gchar *desc, const guint8 *buf, gint len)
+{
+	/*
+	   char buf1[8*len+2], 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");
+	   purple_debug(PURPLE_DEBUG_INFO, desc, "%s", buf1);
+	   */
+
+	/* modified by s3e, 20080424 */
+	qq_hex_dump(PURPLE_DEBUG_INFO, desc,
+		buf, len,
+		"");
+}
+
 /* convert face num from packet (0-299) to local face (1-100) */
 gchar *face_to_icon_str(gint face)
 {
--- a/libpurple/protocols/qq/utils.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/qq/utils.h	Thu Nov 20 21:13:56 2008 +0000
@@ -28,12 +28,15 @@
 #include <stdio.h>
 #include <glib.h>
 
+#include "debug.h"
+
+void qq_get_md5(guint8 *md5, gint md5_len, const guint8* const src, gint src_len);
+
 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);
-guint8 *_gen_session_md5(gint uid, guint8 *session_key);
 
 gchar *gen_ip_str(guint8 *ip);
 guint8 *str_ip_gen(gchar *str);
@@ -44,10 +47,13 @@
 
 gchar *face_to_icon_str(gint face);
 
-void try_dump_as_gbk(const guint8 *const data, gint len);
+gchar *try_dump_as_gbk(const guint8 *const data, gint len);
 
+void qq_show_packet(const gchar *desc, const guint8 *buf, gint len);
+void qq_hex_dump(PurpleDebugLevel level, const char *category,
+		const guint8 *pdata, gint bytes,	
+		const char *format, ...);
 guint8 *hex_str_to_bytes(const gchar *buf, gint *out_len);
-gchar *hex_dump_to_str(const guint8 *buf, gint buf_len);
 
 const gchar *qq_buddy_icon_dir(void);
 const gchar *qq_win32_buddy_icon_dir(void);
--- a/libpurple/protocols/silc/buddy.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/silc/buddy.c	Thu Nov 20 21:13:56 2008 +0000
@@ -1434,13 +1434,25 @@
 void silcpurple_idle_set(PurpleConnection *gc, int idle)
 
 {
-	SilcPurple sg = gc->proto_data;
-	SilcClient client = sg->client;
-	SilcClientConnection conn = sg->conn;
+	SilcPurple sg;
+	SilcClient client;
+	SilcClientConnection conn;
 	SilcAttributeObjService service;
 	const char *server;
 	int port;
 
+	sg = gc->proto_data;
+	if (sg == NULL)
+		return;
+
+	client = sg->client;
+	if (client == NULL)
+		return;
+
+	conn = sg->conn;
+	if (conn == NULL)
+		return;
+
 	server = purple_account_get_string(sg->account, "server",
 					 "silc.silcnet.org");
 	port = purple_account_get_int(sg->account, "port", 706),
--- a/libpurple/protocols/silc/silc.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/silc/silc.c	Thu Nov 20 21:13:56 2008 +0000
@@ -22,6 +22,7 @@
 #include "silcpurple.h"
 #include "version.h"
 #include "wb.h"
+#include "core.h"
 
 extern SilcClientOperations ops;
 static PurplePlugin *silc_plugin = NULL;
@@ -669,13 +670,31 @@
 #if __SILC_TOOLKIT_VERSION >= SILC_VERSION(1,1,1)
 	SilcPurpleTask task;
 #endif /* __SILC_TOOLKIT_VERSION */
+	GHashTable *ui_info;
+	const char *ui_name = NULL, *ui_website = NULL;
+	char *quit_msg;
 
 	g_return_if_fail(sg != NULL);
 
+	ui_info = purple_core_get_ui_info();
+	
+	if(ui_info) {
+		ui_name = g_hash_table_lookup(ui_info, "name");
+		ui_website = g_hash_table_lookup(ui_info, "website");
+	}
+	
+	if(!ui_name || !ui_website) {
+		ui_name = "Pidgin";
+		ui_website = PURPLE_WEBSITE;
+	}
+	quit_msg = g_strdup_printf(_("Download %s: %s"),
+							   ui_name, ui_website);
+
 	/* Send QUIT */
 	silc_client_command_call(sg->client, sg->conn, NULL,
-				 "QUIT", "Download Pidgin: " PURPLE_WEBSITE,
+				 "QUIT", quit_msg,
 				 NULL);
+	g_free(quit_msg);
 
 	if (sg->conn)
 		silc_client_close_connection(sg->client, sg->conn);
@@ -1816,7 +1835,10 @@
 {
 	PurpleConnection *gc;
 	SilcPurple sg;
-
+	GHashTable *ui_info;
+	const char *ui_name = NULL, *ui_website = NULL;
+	char *quit_msg;
+								   
 	gc = purple_conversation_get_gc(conv);
 
 	if (gc == NULL)
@@ -1827,8 +1849,23 @@
 	if (sg == NULL)
 		return PURPLE_CMD_RET_FAILED;
 
+	ui_info = purple_core_get_ui_info();
+	
+	if(ui_info) {
+		ui_name = g_hash_table_lookup(ui_info, "name");
+		ui_website = g_hash_table_lookup(ui_info, "website");
+	}
+	
+	if(!ui_name || !ui_website) {
+		ui_name = "Pidgin";
+		ui_website = PURPLE_WEBSITE;
+	}
+	quit_msg = g_strdup_printf(_("Download %s: %s"),
+							   ui_name, ui_website);
+
 	silc_client_command_call(sg->client, sg->conn, NULL,
-				 "QUIT", (args && args[0]) ? args[0] : "Download Pidgin: " PURPLE_WEBSITE, NULL);
+				 "QUIT", (args && args[0]) ? args[0] : quit_msg, NULL);
+	g_free(quit_msg);
 
 	return PURPLE_CMD_RET_OK;
 }
--- a/libpurple/protocols/silc10/buddy.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/silc10/buddy.c	Thu Nov 20 21:13:56 2008 +0000
@@ -1434,13 +1434,25 @@
 void silcpurple_idle_set(PurpleConnection *gc, int idle)
 
 {
-	SilcPurple sg = gc->proto_data;
-	SilcClient client = sg->client;
-	SilcClientConnection conn = sg->conn;
+	SilcPurple sg;
+	SilcClient client;
+	SilcClientConnection conn;
 	SilcAttributeObjService service;
 	const char *server;
 	int port;
 
+	sg = gc->proto_data;
+	if (sg == NULL)
+		return;
+
+	client = sg->client;
+	if (client == NULL)
+		return;
+
+	conn = sg->conn;
+	if (conn == NULL)
+		return;
+
 	server = purple_account_get_string(sg->account, "server",
 					 "silc.silcnet.org");
 	port = purple_account_get_int(sg->account, "port", 706),
--- a/libpurple/protocols/silc10/silc.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/silc10/silc.c	Thu Nov 20 21:13:56 2008 +0000
@@ -22,6 +22,7 @@
 #include "silcpurple.h"
 #include "version.h"
 #include "wb.h"
+#include "core.h"
 
 extern SilcClientOperations ops;
 static PurplePlugin *silc_plugin = NULL;
@@ -384,12 +385,30 @@
 silcpurple_close(PurpleConnection *gc)
 {
 	SilcPurple sg = gc->proto_data;
+	GHashTable *ui_info;
+	const char *ui_name = NULL, *ui_website = NULL;
+	char *quit_msg;
+	
+	g_return_if_fail(sg != NULL);
 
-	g_return_if_fail(sg != NULL);
+	ui_info = purple_core_get_ui_info();
+
+	if(ui_info) {
+		ui_name = g_hash_table_lookup(ui_info, "name");
+		ui_website = g_hash_table_lookup(ui_info, "website");
+	}
+
+	if(!ui_name || !ui_website) {
+		ui_name = "Pidgin";
+		ui_website = PURPLE_WEBSITE;
+	}
+	quit_msg = g_strdup_printf(_("Download %s: %s"),
+							   ui_name, ui_website);
 
 	/* Send QUIT */
 	silc_client_command_call(sg->client, sg->conn, NULL,
-				 "QUIT", "Download this: " PURPLE_WEBSITE, NULL);
+				 "QUIT", quit_msg, NULL);
+	g_free(quit_msg);
 
 	if (sg->conn)
 		silc_client_close_connection(sg->client, sg->conn);
@@ -1535,7 +1554,10 @@
 {
 	PurpleConnection *gc;
 	SilcPurple sg;
-
+	GHashTable *ui_info;
+	const char *ui_name = NULL, *ui_website = NULL;
+	char *quit_msg;
+	
 	gc = purple_conversation_get_gc(conv);
 
 	if (gc == NULL)
@@ -1546,8 +1568,23 @@
 	if (sg == NULL)
 		return PURPLE_CMD_RET_FAILED;
 
+	ui_info = purple_core_get_ui_info();
+
+	if(ui_info) {
+		ui_name = g_hash_table_lookup(ui_info, "name");
+		ui_website = g_hash_table_lookup(ui_info, "website");
+	}
+
+	if(!ui_name || !ui_website) {
+		ui_name = "Pidgin";
+		ui_website = PURPLE_WEBSITE;
+	}
+	quit_msg = g_strdup_printf(_("Download %s: %s"),
+							   ui_name, ui_website);
+
 	silc_client_command_call(sg->client, sg->conn, NULL,
-				 "QUIT", (args && args[0]) ? args[0] : "Download this: " PURPLE_WEBSITE, NULL);
+				 "QUIT", (args && args[0]) ? args[0] : quit_msg, NULL);
+	g_free(quit_msg);
 
 	return PURPLE_CMD_RET_OK;
 }
--- a/libpurple/protocols/simple/simple.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/simple/simple.c	Thu Nov 20 21:13:56 2008 +0000
@@ -1898,7 +1898,7 @@
 	PurpleConnection *gc;
 	struct simple_account_data *sip;
 	gchar **userserver;
-	gchar *hosttoconnect;
+	const gchar *hosttoconnect;
 
 	const char *username = purple_account_get_username(account);
 	gc = purple_account_get_connection(account);
@@ -1934,14 +1934,13 @@
 	sip->status = g_strdup("available");
 
 	if(!purple_account_get_bool(account, "useproxy", FALSE)) {
-		hosttoconnect = g_strdup(sip->servername);
+		hosttoconnect = sip->servername;
 	} else {
-		hosttoconnect = g_strdup(purple_account_get_string(account, "proxy", sip->servername));
+		hosttoconnect = purple_account_get_string(account, "proxy", sip->servername);
 	}
 
 	sip->srv_query_data = purple_srv_resolve("sip",
 			sip->udp ? "udp" : "tcp", hosttoconnect, srvresolved, sip);
-	g_free(hosttoconnect);
 }
 
 static void simple_close(PurpleConnection *gc)
--- a/libpurple/protocols/yahoo/yahoo.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Thu Nov 20 21:13:56 2008 +0000
@@ -1995,13 +1995,19 @@
 			yahoo_process_auth_new(gc, seed);
 			break;
 		default:
-			buf = g_strdup_printf(_("The Yahoo server has requested the use of an unrecognized "
-						"authentication method.  You will probably not be able "
-						"to successfully sign on to Yahoo.  Check %s for updates."), PURPLE_WEBSITE);
-			purple_notify_error(gc, "", _("Failed Yahoo! Authentication"),
-					  buf);
-			g_free(buf);
-			yahoo_process_auth_new(gc, seed); /* Can't hurt to try it anyway. */
+			{
+				GHashTable *ui_info = purple_core_get_ui_info();
+
+				buf = g_strdup_printf(_("The Yahoo server has requested the use of an unrecognized "
+							"authentication method.  You will probably not be able "
+							"to successfully sign on to Yahoo.  Check %s for updates."),
+							((ui_info && g_hash_table_lookup(ui_info, "website")) ? (char *)g_hash_table_lookup(ui_info, "website") : PURPLE_WEBSITE));
+				purple_notify_error(gc, "", _("Failed Yahoo! Authentication"),
+							buf);
+				g_free(buf);
+				yahoo_process_auth_new(gc, seed); /* Can't hurt to try it anyway. */
+				break;
+			}
 		}
 	}
 }
--- a/libpurple/protocols/yahoo/yahoo_profile.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoo_profile.c	Thu Nov 20 21:13:56 2008 +0000
@@ -1282,6 +1282,10 @@
 	url_data = purple_util_fetch_url(url, TRUE, NULL, FALSE, yahoo_got_info, data);
 	if (url_data != NULL)
 		yd->url_datas = g_slist_prepend(yd->url_datas, url_data);
+	else {
+		g_free(data->name);
+		g_free(data);
+	}
 
 	g_free(url);
 }
--- a/libpurple/prpl.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/prpl.c	Thu Nov 20 21:13:56 2008 +0000
@@ -389,6 +389,110 @@
 	return statuses;
 }
 
+void
+purple_prpl_send_attention(PurpleConnection *gc, const char *who, guint type_code)
+{
+	PurpleAttentionType *attn;
+	PurpleMessageFlags flags;
+	PurplePlugin *prpl;
+	PurpleConversation *conv;
+	gboolean (*send_attention)(PurpleConnection *, const char *, guint);
+	PurpleBuddy *buddy;
+	const char *alias;	
+	gchar *description;
+	time_t mtime;
+
+	g_return_if_fail(gc != NULL);
+	g_return_if_fail(who != NULL);
+
+	prpl = purple_find_prpl(purple_account_get_protocol_id(gc->account));
+	send_attention = PURPLE_PLUGIN_PROTOCOL_INFO(prpl)->send_attention;
+	g_return_if_fail(send_attention != NULL);
+
+	mtime = time(NULL);
+
+	attn = purple_get_attention_type_from_code(gc->account, type_code);
+
+	if ((buddy = purple_find_buddy(purple_connection_get_account(gc), who)) != NULL)
+		alias = purple_buddy_get_contact_alias(buddy);
+	else
+		alias = who;
+
+	if (attn && purple_attention_type_get_outgoing_desc(attn)) {
+		description = g_strdup_printf(purple_attention_type_get_outgoing_desc(attn), alias);
+	} else {
+		description = g_strdup_printf(_("Requesting %s's attention..."), alias);
+	}
+	
+	flags = PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_NOTIFY | PURPLE_MESSAGE_SYSTEM;
+
+	purple_debug_info("server", "serv_send_attention: sending '%s' to %s\n",
+			description, who);
+
+	if (!send_attention(gc, who, type_code))
+		return;
+
+	conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, gc->account, who);
+	purple_conv_im_write(PURPLE_CONV_IM(conv), NULL, description, flags, mtime);
+
+	g_free(description);
+}
+
+static void
+got_attention(PurpleConnection *gc, int id, const char *who, guint type_code)
+{
+	PurpleMessageFlags flags;
+	PurpleAttentionType *attn;
+	PurpleBuddy *buddy;
+	const char *alias;
+	gchar *description;
+	time_t mtime;
+
+	mtime = time(NULL);
+
+	attn = purple_get_attention_type_from_code(gc->account, type_code);
+
+	/* PURPLE_MESSAGE_NOTIFY is for attention messages. */
+	flags = PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NOTIFY | PURPLE_MESSAGE_RECV;
+
+	/* TODO: if (attn->icon_name) is non-null, use it to lookup an emoticon and display
+	 * it next to the attention command. And if it is null, display a generic icon. */
+
+	if ((buddy = purple_find_buddy(purple_connection_get_account(gc), who)) != NULL)
+		alias = purple_buddy_get_contact_alias(buddy);
+	else
+		alias = who;
+
+	if (attn && purple_attention_type_get_incoming_desc(attn)) {
+		description = g_strdup_printf(purple_attention_type_get_incoming_desc(attn), alias);
+	} else {
+		description = g_strdup_printf(_("%s has requested your attention!"), alias);
+	}
+
+	purple_debug_info("server", "got_attention: got '%s' from %s\n",
+			description, who);
+
+	if (id == -1)
+		serv_got_im(gc, who, description, flags, mtime);
+	else
+		serv_got_chat_in(gc, id, who, flags, description, mtime);
+
+	/* TODO: sounds (depending on PurpleAttentionType), shaking, etc. */
+
+	g_free(description);
+}
+
+void
+purple_prpl_got_attention(PurpleConnection *gc, const char *who, guint type_code)
+{
+	got_attention(gc, -1, who, type_code);
+}
+
+void
+purple_prpl_got_attention_in_chat(PurpleConnection *gc, int id, const char *who, guint type_code)
+{
+	got_attention(gc, id, who, type_code);
+}
 
 /**************************************************************************
  * Protocol Plugin Subsystem API
--- a/libpurple/prpl.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/prpl.h	Thu Nov 20 21:13:56 2008 +0000
@@ -31,6 +31,7 @@
 #define _PURPLE_PRPL_H_
 
 typedef struct _PurplePluginProtocolInfo PurplePluginProtocolInfo;
+/** @copydoc _PurpleAttentionType */
 typedef struct _PurpleAttentionType PurpleAttentionType;
 
 /**************************************************************************/
@@ -99,6 +100,9 @@
 	gboolean secret;
 };
 
+/** Represents "nudges" and "buzzes" that you may send to a buddy to attract
+ *  their attention (or vice-versa).
+ */
 struct _PurpleAttentionType
 {
 	const char *name;                  /**< Shown in GUI elements */
@@ -689,6 +693,41 @@
  */
 GList *purple_prpl_get_statuses(PurpleAccount *account, PurplePresence *presence);
 
+/** Send an attention request message.
+ *
+ * @param gc The connection to send the message on.
+ * @param who Whose attention to request.
+ * @param type_code An index into the prpl's attention_types list determining the type
+ * 	of the attention request command to send. 0 if prpl only defines one
+ * 	(for example, Yahoo and MSN), but some protocols define more (MySpaceIM).
+ *
+ * Note that you can't send arbitrary PurpleAttentionType's, because there is
+ * only a fixed set of attention commands.
+ * @since 2.5.0
+ */
+void purple_prpl_send_attention(PurpleConnection *gc, const char *who, guint type_code);
+
+/** Process an incoming attention message. 
+ *
+ * @param gc The connection that received the attention message.
+ * @param who Who requested your attention.
+ * @param type_code An index into the prpl's attention_types list determining the type
+ * 	of the attention request command to send.
+ * @since 2.5.0
+ */
+void purple_prpl_got_attention(PurpleConnection *gc, const char *who, guint type_code);
+
+/** Process an incoming attention message in a chat. 
+ *
+ * @param gc The connection that received the attention message.
+ * @param id The chat id.
+ * @param who Who requested your attention.
+ * @param type_code An index into the prpl's attention_types list determining the type
+ * 	of the attention request command to send. 
+ * @since 2.5.0
+ */
+void purple_prpl_got_attention_in_chat(PurpleConnection *gc, int id, const char *who, guint type_code);
+
 /*@}*/
 
 /**************************************************************************/
--- a/libpurple/purple-url-handler	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/purple-url-handler	Thu Nov 20 21:13:56 2008 +0000
@@ -6,7 +6,15 @@
 import time
 import urllib
 
-obj = dbus.SessionBus().get_object("im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject")
+bus = dbus.SessionBus()
+obj = None
+try:
+    obj = bus.get_object("im.pidgin.purple.PurpleService",
+                         "/im/pidgin/purple/PurpleObject")
+except dbus.DBusException, e:
+    if e._dbus_error_name == "org.freedesktop.DBus.Error.ServiceUnknown":
+        print "Error: no libpurple-powered client is running. Try starting Pidgin or Finch."
+        sys.exit(1)
 purple = dbus.Interface(obj, "im.pidgin.purple.PurpleInterface")
 
 class CheckedObject:
@@ -50,20 +58,39 @@
     except:
         return value
 
-def findaccount(protocolname, accountname=""):
+def account_not_found():
+    print "No matching account found."
+    sys.exit(1)
+
+def bring_account_online(account):
+    if not cpurple.PurpleAccountIsConnected(account):
+        # The last argument is meant to be a GList * but the D-Bus binding
+        # generator thing just wants a UInt32, which is pretty failing.
+        # Happily, passing a 0 to mean an empty list turns out to work anyway.
+        purple.PurpleAccountSetStatusList(account, "online", 1, 0)
+        purple.PurpleAccountConnect(account)
+
+def findaccount(protocolname, accountname="", matcher=None):
+    if matcher:
+        for account in cpurple.PurpleAccountsGetAll():
+            if accountname != "" and accountname != cpurple.PurpleAccountGetUsername(a):
+                continue
+            if matcher(account):
+                bring_account_online(account)
+                return account
+        account_not_found()
+
     # prefer connected accounts
     account = cpurple.PurpleAccountsFindConnected(accountname, protocolname)
     if (account != 0):
-	return account
+        return account
 
     # try to get any account and connect it
     account = cpurple.PurpleAccountsFindAny(accountname, protocolname)
     if (account == 0):
-        print "No matching account found."
-	sys.exit(1)
+        account_not_found()
 
-    purple.PurpleAccountSetStatusVargs(account, "online", 1)
-    purple.PurpleAccountConnect(account)
+    bring_account_online(account)
     return account
 
 def goim(account, screenname, message=None):
@@ -178,12 +205,16 @@
             key, value = extendlist(param.split("=", 1), 2, "")
             params[key] = urllib.unquote_plus(value)
 
-    account = findaccount(protocol)
+    def correct_server(account):
+        username = cpurple.PurpleAccountGetUsername(account)
+        return (server == (username.split("@"))[1])
+
+    account = findaccount(protocol, matcher=correct_server)
 
     if (target != ""):
         if (isnick):
             goim(account, urllib.unquote_plus(target.split(",")[0]), params.get("msg"))
-	else:
+        else:
             channel = urllib.unquote_plus(target.split(",")[0])
             if channel[0] != "#":
                 channel = "#" + channel
@@ -213,9 +244,9 @@
         addbuddy(account, screenname)
 
 def myim(uri):
-	protocol = "prpl-myspace"
-	print "TODO: send uri: ", uri
-	assert False, "Not implemented"
+        protocol = "prpl-myspace"
+        print "TODO: send uri: ", uri
+        assert False, "Not implemented"
 
 def sip(uri):
     protocol = "prpl-simple"
@@ -328,9 +359,9 @@
             ymsgr(uri)
         else:
             print "Unknown protocol: %s" % type
-    except dbus.dbus_bindings.DBusException:
-        print "ERROR: Is there a libpurple-powered client (e.g. Pidgin or Finch) running?"
-
+    except dbus.DBusException, e:
+        print "Error: %s" % (e.message)
+        sys.exit(1)
 
 if __name__ == "__main__":
     main()
--- a/libpurple/roomlist.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/roomlist.h	Thu Nov 20 21:13:56 2008 +0000
@@ -30,6 +30,7 @@
 typedef struct _PurpleRoomlist PurpleRoomlist;
 typedef struct _PurpleRoomlistRoom PurpleRoomlistRoom;
 typedef struct _PurpleRoomlistField PurpleRoomlistField;
+/** @copydoc _PurpleRoomlistUiOps */
 typedef struct _PurpleRoomlistUiOps PurpleRoomlistUiOps;
 
 /**
--- a/libpurple/server.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/server.c	Thu Nov 20 21:13:56 2008 +0000
@@ -323,91 +323,13 @@
 void
 serv_send_attention(PurpleConnection *gc, const char *who, guint type_code)
 {
-	PurpleAttentionType *attn;
-	PurpleMessageFlags flags;
-	PurplePlugin *prpl;
-	PurpleConversation *conv;
-	gboolean (*send_attention)(PurpleConnection *, const char *, guint);
-	PurpleBuddy *buddy;
-	const char *alias;	
-	gchar *description;
-	time_t mtime;
-
-	g_return_if_fail(gc != NULL);
-	g_return_if_fail(who != NULL);
-
-	prpl = purple_find_prpl(purple_account_get_protocol_id(gc->account));
-	send_attention = PURPLE_PLUGIN_PROTOCOL_INFO(prpl)->send_attention;
-	g_return_if_fail(send_attention != NULL);
-
-	mtime = time(NULL);
-
-	attn = purple_get_attention_type_from_code(gc->account, type_code);
-
-	if ((buddy = purple_find_buddy(purple_connection_get_account(gc), who)) != NULL)
-		alias = purple_buddy_get_contact_alias(buddy);
-	else
-		alias = who;
-
-	if (attn && purple_attention_type_get_outgoing_desc(attn)) {
-		description = g_strdup_printf(purple_attention_type_get_outgoing_desc(attn), alias);
-	} else {
-		description = g_strdup_printf(_("Requesting %s's attention..."), alias);
-	}
-	
-	flags = PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_NOTIFY | PURPLE_MESSAGE_SYSTEM;
-
-	purple_debug_info("server", "serv_send_attention: sending '%s' to %s\n",
-			description, who);
-
-	if (!send_attention(gc, who, type_code))
-		return;
-
-	conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, gc->account, who);
-	purple_conv_im_write(PURPLE_CONV_IM(conv), NULL, description, flags, mtime);
-
-	g_free(description);
+	purple_prpl_send_attention(gc, who, type_code);
 }
 
 void
 serv_got_attention(PurpleConnection *gc, const char *who, guint type_code)
 {
-	PurpleMessageFlags flags;
-	PurpleAttentionType *attn;
-	PurpleBuddy *buddy;
-	const char *alias;
-	gchar *description;
-	time_t mtime;
-
-	mtime = time(NULL);
-
-	attn = purple_get_attention_type_from_code(gc->account, type_code);
-
-	/* PURPLE_MESSAGE_NOTIFY is for attention messages. */
-	flags = PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NOTIFY | PURPLE_MESSAGE_RECV;
-
-	/* TODO: if (attn->icon_name) is non-null, use it to lookup an emoticon and display
-	 * it next to the attention command. And if it is null, display a generic icon. */
-
-	if ((buddy = purple_find_buddy(purple_connection_get_account(gc), who)) != NULL)
-		alias = purple_buddy_get_contact_alias(buddy);
-	else
-		alias = who;
-
-	if (attn && purple_attention_type_get_incoming_desc(attn)) {
-		description = g_strdup_printf(purple_attention_type_get_incoming_desc(attn), alias);
-	} else {
-		description = g_strdup_printf(_("%s has requested your attention!"), alias);
-	}
-
-	purple_debug_info("server", "serv_got_attention: got '%s' from %s\n",
-			description, who);
-
-	serv_got_im(gc, who, description, flags, mtime);
-	
-	/* TODO: sounds (depending on PurpleAttentionType), shaking, etc. */
-
-	g_free(description);
+	purple_prpl_got_attention(gc, who, type_code);
 }
 
 
@@ -727,6 +649,7 @@
 		PurpleStatusPrimitive primitive;
 		const gchar *auto_reply_pref;
 		const char *away_msg = NULL;
+		gboolean mobile = FALSE;
 
 		auto_reply_pref = purple_prefs_get_string("/purple/away/auto_reply");
 
@@ -734,9 +657,10 @@
 		status = purple_presence_get_active_status(presence);
 		status_type = purple_status_get_type(status);
 		primitive = purple_status_type_get_primitive(status_type);
+		mobile = purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_MOBILE);
 		if ((primitive == PURPLE_STATUS_AVAILABLE) ||
 			(primitive == PURPLE_STATUS_INVISIBLE) ||
-			(primitive == PURPLE_STATUS_MOBILE) ||
+			mobile ||
 		    !strcmp(auto_reply_pref, "never") ||
 		    (!purple_presence_is_idle(presence) && !strcmp(auto_reply_pref, "awayidle")))
 		{
--- a/libpurple/server.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/server.h	Thu Nov 20 21:13:56 2008 +0000
@@ -63,6 +63,8 @@
 
 /** Send an attention request message.
  *
+ * @deprecated Use purple_prpl_send_attention() instead.
+ *
  * @param gc The connection to send the message on.
  * @param who Whose attention to request.
  * @param type_code An index into the prpl's attention_types list determining the type
@@ -76,6 +78,8 @@
 
 /** Process an incoming attention message. 
  *
+ * @deprecated Use purple_prpl_got_attention() instead.
+ *
  * @param gc The connection that received the attention message.
  * @param who Who requested your attention.
  * @param type_code An index into the prpl's attention_types list determining the type
--- a/libpurple/sound.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/sound.h	Thu Nov 20 21:13:56 2008 +0000
@@ -55,6 +55,9 @@
 
 } PurpleSoundEventID;
 
+/** Operations used by the core to request that particular sound files, or the
+ *  sound associated with a particular event, should be played.
+ */
 typedef struct _PurpleSoundUiOps
 {
 	void (*init)(void);
--- a/libpurple/util.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/util.c	Thu Nov 20 21:13:56 2008 +0000
@@ -832,6 +832,11 @@
 				if (offset_positive)
 					tzoff *= -1;
 			}
+			else if ((*c == 'Z') && (c = c + 1))
+			{
+				/* 'Z' = Zulu = UTC */
+				tzoff = 0;
+			}
 			else if (utc)
 			{
 				static struct tm tmptm;
@@ -3594,7 +3599,7 @@
 static void url_fetch_connect_cb(gpointer url_data, gint source, const gchar *error_message);
 
 static gboolean
-parse_redirect(const char *data, size_t data_len, gint sock,
+parse_redirect(const char *data, size_t data_len,
 			   PurpleUtilFetchUrlData *gfud)
 {
 	gchar *s;
@@ -3768,7 +3773,7 @@
 					header_len, gfud->webdata);
 
 				/* See if we can find a redirect. */
-				if(parse_redirect(gfud->webdata, header_len, source, gfud))
+				if(parse_redirect(gfud->webdata, header_len, gfud))
 					return;
 
 				gfud->got_headers = TRUE;
@@ -4718,3 +4723,21 @@
 	return g_string_free(string, FALSE);
 }
 
+const gchar *
+purple_get_host_name(void)
+{
+#if GLIB_CHECK_VERSION(2,8,0)
+	return g_get_host_name();
+#else
+	static char hostname[256];
+	int ret = gethostname(hostname, sizeof(hostname));
+	hostname[sizeof(hostname) - 1] = '\0';
+
+	if (ret == -1 || hostname[0] == '\0') {
+		purple_debug_info("purple_get_host_name: ", "could not find host name");
+		return "localhost";
+	} else {
+		return hostname;
+	}
+#endif
+}
--- a/libpurple/util.h	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/util.h	Thu Nov 20 21:13:56 2008 +0000
@@ -1271,6 +1271,14 @@
  */
 void purple_restore_default_signal_handlers(void);
 
+/**
+ * Gets the host name of the machine. If it not possible to determine the
+ * host name, "localhost" is returned
+ *
+ * @constreturn The hostname
+ */
+const gchar *purple_get_host_name(void);
+
 #ifdef __cplusplus
 }
 #endif
--- a/libpurple/win32/global.mak	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/win32/global.mak	Thu Nov 20 21:13:56 2008 +0000
@@ -19,8 +19,8 @@
 MEANWHILE_TOP ?= $(WIN32_DEV_TOP)/meanwhile-1.0.2_daa1
 NSPR_TOP ?= $(WIN32_DEV_TOP)/nspr-4.6.4
 NSS_TOP ?= $(WIN32_DEV_TOP)/nss-3.11.4
-PERL_LIB_TOP ?= $(WIN32_DEV_TOP)/perl58
-SILC_TOOLKIT ?= $(WIN32_DEV_TOP)/silc-toolkit-1.1.5
+PERL_LIB_TOP ?= $(WIN32_DEV_TOP)/perl-5.10.0
+SILC_TOOLKIT ?= $(WIN32_DEV_TOP)/silc-toolkit-1.1.7
 TCL_LIB_TOP ?= $(WIN32_DEV_TOP)/tcl-8.4.5
 GSTREAMER_TOP ?= $(WIN32_DEV_TOP)/gstreamer-0.10.13
 
@@ -56,7 +56,7 @@
 PIDGIN_EXE := $(PIDGIN_TOP)/pidgin.exe
 PIDGIN_PORTABLE_EXE := $(PIDGIN_TOP)/pidgin-portable.exe
 
-GCCWARNINGS := -Waggregate-return -Wcast-align -Wdeclaration-after-statement -Werror-implicit-function-declaration -Wextra -Wno-sign-compare -Wno-unused-parameter -Winit-self -Wmissing-declarations -Wmissing-prototypes -Wnested-externs -Wpointer-arith -Wundef
+GCCWARNINGS ?= -Waggregate-return -Wcast-align -Wdeclaration-after-statement -Werror-implicit-function-declaration -Wextra -Wno-sign-compare -Wno-unused-parameter -Winit-self -Wmissing-declarations -Wmissing-prototypes -Wnested-externs -Wpointer-arith -Wundef
 
 # parse the version number from the configure.ac file if it is newer
 #m4_define([purple_major_version], [2])
--- a/libpurple/win32/libc_interface.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/win32/libc_interface.c	Thu Nov 20 21:13:56 2008 +0000
@@ -327,9 +327,12 @@
 			case WSAETIMEDOUT: /* 10060 */
 				g_snprintf(errbuf, sizeof(errbuf), _("Connection timed out."));
 				break;
-			case WSAECONNREFUSED: /*10061 */
+			case WSAECONNREFUSED: /* 10061 */
 				g_snprintf(errbuf, sizeof(errbuf), _("Connection refused."));
 				break;
+			case WSAEADDRINUSE: /* 10048 */
+				g_snprintf(errbuf, sizeof(errbuf), _("Address already in use."));
+				break;
 			default:
 				g_snprintf(errbuf, sizeof(errbuf), "Windows socket error #%d", errornum);
 		}
--- a/libpurple/xmlnode.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/libpurple/xmlnode.c	Thu Nov 20 21:13:56 2008 +0000
@@ -728,6 +728,13 @@
 	return ret;
 }
 
+static void
+xmlnode_copy_foreach_ns(gpointer key, gpointer value, gpointer user_data)
+{
+	GHashTable *ret = (GHashTable *)user_data;
+	g_hash_table_insert(ret, g_strdup(key), g_strdup(value));
+}
+
 xmlnode *
 xmlnode_copy(const xmlnode *src)
 {
@@ -739,17 +746,23 @@
 
 	ret = new_node(src->name, src->type);
 	ret->xmlns = g_strdup(src->xmlns);
-	if(src->data) {
-		if(src->data_sz) {
+	if (src->data) {
+		if (src->data_sz) {
 			ret->data = g_memdup(src->data, src->data_sz);
 			ret->data_sz = src->data_sz;
 		} else {
 			ret->data = g_strdup(src->data);
 		}
 	}
+	ret->prefix = g_strdup(src->prefix);
+	if (src->namespace_map) {
+		ret->namespace_map = g_hash_table_new_full(g_str_hash, g_str_equal,
+		                                           g_free, g_free);
+		g_hash_table_foreach(src->namespace_map, xmlnode_copy_foreach_ns, ret->namespace_map);
+	}
 
-	for(child = src->child; child; child = child->next) {
-		if(sibling) {
+	for (child = src->child; child; child = child->next) {
+		if (sibling) {
 			sibling->next = xmlnode_copy(child);
 			sibling = sibling->next;
 		} else {
--- a/pidgin/gtkaccount.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/pidgin/gtkaccount.c	Thu Nov 20 21:13:56 2008 +0000
@@ -1186,7 +1186,6 @@
 	char *tmp;
 	gboolean new_acct = FALSE, icon_change = FALSE;
 	PurpleAccount *account;
-	PurplePluginProtocolInfo *prpl_info;
 
 	/* Build the username string. */
 	username = g_strdup(gtk_entry_get_text(GTK_ENTRY(dialog->screenname_entry)));
@@ -1254,8 +1253,7 @@
 		purple_account_set_alias(account, NULL);
 
 	/* Buddy Icon */
-	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(dialog->plugin);
-	if (prpl_info != NULL && prpl_info->icon_spec.format != NULL)
+	if (dialog->prpl_info != NULL && dialog->prpl_info->icon_spec.format != NULL)
 	{
 		const char *filename;
 
--- a/pidgin/gtkblist.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/pidgin/gtkblist.c	Thu Nov 20 21:13:56 2008 +0000
@@ -130,6 +130,7 @@
 
 static guint visibility_manager_count = 0;
 static GdkVisibilityState gtk_blist_visibility = GDK_VISIBILITY_UNOBSCURED;
+static gboolean gtk_blist_focused = FALSE;
 static gboolean editing_blist = FALSE;
 
 static GList *pidgin_blist_sort_methods = NULL;
@@ -5166,9 +5167,14 @@
 /******************************************/
 
 static int
-blist_focus_cb(GtkWidget *widget, gpointer data, PidginBuddyList *gtkblist)
-{
-	pidgin_set_urgent(GTK_WINDOW(gtkblist->window), FALSE);
+blist_focus_cb(GtkWidget *widget, GdkEventFocus *event, PidginBuddyList *gtkblist)
+{
+	if(event->in) {
+		gtk_blist_focused = TRUE;
+		pidgin_set_urgent(GTK_WINDOW(gtkblist->window), FALSE);
+	} else {
+		gtk_blist_focused = FALSE;
+	}
 	return 0;
 }
 
@@ -5255,6 +5261,8 @@
 	gtkblist->window = pidgin_create_window(_("Buddy List"), 0, "buddy_list", TRUE);
 	g_signal_connect(G_OBJECT(gtkblist->window), "focus-in-event",
 			 G_CALLBACK(blist_focus_cb), gtkblist);
+	g_signal_connect(G_OBJECT(gtkblist->window), "focus-out-event",
+			 G_CALLBACK(blist_focus_cb), gtkblist);
 	GTK_WINDOW(gtkblist->window)->allow_shrink = TRUE;
 
 	gtkblist->main_vbox = gtk_vbox_new(FALSE, 0);
@@ -6988,8 +6996,15 @@
 {
 	if (gtkblist && gtkblist->window) {
 		if (GTK_WIDGET_VISIBLE(gtkblist->window)) {
+			/* make the buddy list visible if it is iconified or if it is
+			 * obscured and not currently focused (the focus part ensures
+			 * that we do something reasonable if the buddy list is obscured
+			 * by a window set to always be on top), otherwise hide the
+			 * buddy list
+			 */
 			purple_blist_set_visible(PIDGIN_WINDOW_ICONIFIED(gtkblist->window) ||
-					gtk_blist_visibility != GDK_VISIBILITY_UNOBSCURED);
+					((gtk_blist_visibility != GDK_VISIBILITY_UNOBSCURED) &&
+					!gtk_blist_focused));
 		} else {
 			purple_blist_set_visible(TRUE);
 		}
--- a/pidgin/gtkconv.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/pidgin/gtkconv.c	Thu Nov 20 21:13:56 2008 +0000
@@ -1481,7 +1481,7 @@
 	PurpleAccount *account;
 	PurpleConnection *gc;
 	PurplePluginProtocolInfo *prpl_info = NULL;
-	char *real_who;
+	gchar *real_who = NULL;
 
 	account = purple_conversation_get_account(conv);
 	g_return_if_fail(account != NULL);
@@ -1494,13 +1494,11 @@
 	if (prpl_info && prpl_info->get_cb_real_name)
 		real_who = prpl_info->get_cb_real_name(gc,
 				purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)), who);
-	else
-		real_who = g_strdup(who);
-
-	if(!real_who)
+
+	if(!who && !real_who)
 		return;
 
-	pidgin_dialogs_im_with_user(account, real_who);
+	pidgin_dialogs_im_with_user(account, real_who ? real_who : who);
 
 	g_free(real_who);
 }
@@ -1539,11 +1537,22 @@
 static void
 menu_chat_send_file_cb(GtkWidget *w, PidginConversation *gtkconv)
 {
+	PurplePluginProtocolInfo *prpl_info;
 	PurpleConversation *conv = gtkconv->active_conv;
 	const char *who = g_object_get_data(G_OBJECT(w), "user_data");
 	PurpleConnection *gc  = purple_conversation_get_gc(conv);
-
-	serv_send_file(gc, who, NULL);
+	gchar *real_who = NULL;
+
+	g_return_if_fail(gc != NULL);
+
+	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
+
+	if (prpl_info && prpl_info->get_cb_real_name)
+		real_who = prpl_info->get_cb_real_name(gc,
+				purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)), who);
+
+	serv_send_file(gc, real_who ? real_who : who, NULL);
+	g_free(real_who);
 }
 
 static void
@@ -1659,23 +1668,34 @@
 
 		if (gc == NULL)
 			gtk_widget_set_sensitive(button, FALSE);
-
-		g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free);
+		else
+			g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free);
 
 
 		if (prpl_info && prpl_info->send_file)
 		{
+			gboolean can_receive_file = TRUE;
+
 			button = pidgin_new_item_from_stock(menu, _("Send File"),
 				PIDGIN_STOCK_TOOLBAR_SEND_FILE, G_CALLBACK(menu_chat_send_file_cb),
 				PIDGIN_CONVERSATION(conv), 0, 0, NULL);
 
-			if (gc == NULL || prpl_info == NULL ||
-			    !(!prpl_info->can_receive_file || prpl_info->can_receive_file(gc, who)))
-			{
+			if (gc == NULL || prpl_info == NULL)
+				can_receive_file = FALSE;
+			else {
+				gchar *real_who = NULL;
+				if (prpl_info->get_cb_real_name)
+					real_who = prpl_info->get_cb_real_name(gc,
+						purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)), who);
+				if (!(!prpl_info->can_receive_file || prpl_info->can_receive_file(gc, real_who ? real_who : who)))
+					can_receive_file = FALSE;
+				g_free(real_who);
+			}
+
+			if (!can_receive_file)
 				gtk_widget_set_sensitive(button, FALSE);
-			}
-
-			g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free);
+			else
+				g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free);
 		}
 
 
@@ -1688,8 +1708,8 @@
 
 		if (gc == NULL)
 			gtk_widget_set_sensitive(button, FALSE);
-
-		g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free);
+		else
+			g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free);
 	}
 
 	if (prpl_info && (prpl_info->get_info || prpl_info->get_cb_info)) {
@@ -1698,8 +1718,8 @@
 
 		if (gc == NULL)
 			gtk_widget_set_sensitive(button, FALSE);
-
-		g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free);
+		else
+			g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free);
 	}
 
 	if (prpl_info && prpl_info->get_cb_away) {
@@ -1708,8 +1728,8 @@
 
 		if (gc == NULL)
 			gtk_widget_set_sensitive(button, FALSE);
-
-		g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free);
+		else
+			g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free);
 	}
 
 	if (!is_me && prpl_info && !(prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME)) {
@@ -1722,8 +1742,8 @@
 
 		if (gc == NULL)
 			gtk_widget_set_sensitive(button, FALSE);
-
-		g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free);
+		else
+			g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free);
 	}
 
 	button = pidgin_new_item_from_stock(menu, _("Last said"), GTK_STOCK_INDEX,
--- a/pidgin/gtkdialogs.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/pidgin/gtkdialogs.c	Thu Nov 20 21:13:56 2008 +0000
@@ -73,7 +73,8 @@
 	{"John 'rekkanoryo' Bailey",	N_("developer"), NULL},
 	{"Ethan 'Paco-Paco' Blanton",	N_("developer"), NULL},
 	{"Thomas Butter",				N_("developer"), NULL},
-	{"Ka-Hing Cheung",				N_("developer"), NULL},
+	/* feel free to not translate this */
+	{N_("Ka-Hing Cheung"),			N_("developer"), NULL},
 	{"Sadrul Habib Chowdhury",		N_("developer"), NULL},
 	{"Mark 'KingAnt' Doliner",		N_("developer"), "mark@kingant.net"},
 	{"Sean Egan",					N_("developer"), "sean.egan@gmail.com"},
@@ -85,6 +86,7 @@
 	{"Bartosz Oler",		N_("developer"), NULL},
 	{"Etan 'deryni' Reisner",       N_("developer"), NULL},
 	{"Tim 'marv' Ringenbach",		N_("developer"), NULL},
+	{"Elliott 'QuLogic' Sales de Andrade",	N_("developer"),	NULL},
 	{"Luke 'LSchiere' Schierer",	N_("support"), "lschiere@users.sf.net"},
 	{"Megan 'Cae' Schneider",       N_("support/QA"), NULL},
 	{"Evan Schoenberg",		N_("developer"), NULL},
@@ -100,7 +102,6 @@
 	{"Felipe 'shx' Contreras",		NULL,	NULL},
 	{"Dennis 'EvilDennisR' Ristuccia",	N_("Senior Contributor/QA"),	NULL},
 	{"Peter 'Fmoo' Ruibal",		NULL,	NULL},
-	{"Elliott 'QuLogic' Sales de Andrade",	NULL,	NULL},
 	{"Gabriel 'Nix' Schulhof", 	NULL, 	NULL},
 	{"Jorge 'Masca' Villaseñor", 	NULL, 	NULL},
 	{NULL, NULL, NULL}
@@ -424,11 +425,11 @@
 	for (i = 0; developers[i].name != NULL; i++) {
 		if (developers[i].email != NULL) {
 			g_string_append_printf(str, "  %s (%s) &lt;<a href=\"mailto:%s\">%s</a>&gt;<br/>",
-					developers[i].name, _(developers[i].role),
+					_(developers[i].name), _(developers[i].role),
 					developers[i].email, developers[i].email);
 		} else {
 			g_string_append_printf(str, "  %s (%s)<br/>",
-					developers[i].name, _(developers[i].role));
+					_(developers[i].name), _(developers[i].role));
 		}
 	}
 	g_string_append(str, "<BR/>");
--- a/pidgin/gtkdocklet.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/pidgin/gtkdocklet.c	Thu Nov 20 21:13:56 2008 +0000
@@ -708,6 +708,11 @@
 	if (status == PURPLE_STATUS_OFFLINE)
 		gtk_widget_set_sensitive(menuitem, FALSE);
 
+	menuitem = pidgin_new_item_from_stock(menu, _("Join Chat..."), PIDGIN_STOCK_CHAT,
+			G_CALLBACK(pidgin_blist_joinchat_show), NULL, 0, 0, NULL);
+	if (status == PURPLE_STATUS_OFFLINE)
+		gtk_widget_set_sensitive(menuitem, FALSE);
+
 	menuitem = docklet_status_submenu();
 	gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
 
--- a/pidgin/gtkimhtml.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/pidgin/gtkimhtml.c	Thu Nov 20 21:13:56 2008 +0000
@@ -768,7 +768,7 @@
 	gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), &end,
 	                                   buf_x + event->area.width, buf_y + event->area.height);
 
-
+	gtk_text_iter_order(&start, &end);
 
 	cur = start;
 
--- a/pidgin/gtkmain.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/pidgin/gtkmain.c	Thu Nov 20 21:13:56 2008 +0000
@@ -350,6 +350,8 @@
 
 		g_hash_table_insert(ui_info, "name", (char*)PIDGIN_NAME);
 		g_hash_table_insert(ui_info, "version", VERSION);
+		g_hash_table_insert(ui_info, "website", "http://pidgin.im");
+		g_hash_table_insert(ui_info, "dev_website", "http://developer.pidgin.im");
 	}
 
 	return ui_info;
--- a/pidgin/gtkmenutray.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/pidgin/gtkmenutray.c	Thu Nov 20 21:13:56 2008 +0000
@@ -84,19 +84,24 @@
 }
 
 static void
-pidgin_menu_tray_finalize(GObject *obj) {
+pidgin_menu_tray_finalize(GObject *obj)
+{
+	PidginMenuTray *tray = PIDGIN_MENU_TRAY(obj);
 #if 0
 	/* This _might_ be leaking, but I have a sneaking suspicion that the widget is
 	 * getting destroyed in GtkContainer's finalize function.  But if were are
 	 * leaking here, be sure to figure out why this causes a crash.
 	 *	-- Gary
 	 */
-	PidginMenuTray *tray = PIDGIN_MENU_TRAY(obj);
 
 	if(GTK_IS_WIDGET(tray->tray))
 		gtk_widget_destroy(GTK_WIDGET(tray->tray));
 #endif
 
+	if (tray->tooltips) {
+		gtk_object_sink(GTK_OBJECT(tray->tooltips));
+	}
+
 	G_OBJECT_CLASS(parent_class)->finalize(obj);
 }
 
--- a/pidgin/gtkroomlist.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/pidgin/gtkroomlist.c	Thu Nov 20 21:13:56 2008 +0000
@@ -53,7 +53,6 @@
 	PurpleRoomlist *roomlist;
 
 	gboolean pg_needs_pulse;
-	gboolean pg_to_active;
 	guint pg_update_to;
 } PidginRoomlistDialog;
 
@@ -84,32 +83,34 @@
 
 static gint delete_win_cb(GtkWidget *w, GdkEventAny *e, gpointer d)
 {
-	PidginRoomlistDialog *dialog;
-
-	dialog = (PidginRoomlistDialog *) d;
+	PidginRoomlistDialog *dialog = d;
 
 	if (dialog->roomlist && purple_roomlist_get_in_progress(dialog->roomlist))
 		purple_roomlist_cancel_get_list(dialog->roomlist);
 
+	if (dialog->pg_update_to > 0)
+		purple_timeout_remove(dialog->pg_update_to);
+
 	if (dialog->roomlist) {
-		if (dialog->pg_to_active) {
-			purple_timeout_remove(dialog->pg_update_to);
-			dialog->pg_to_active = FALSE;
+		PidginRoomlist *rl = dialog->roomlist->ui_data;
+
+		if (dialog->pg_update_to > 0)
 			/* yes, that's right, unref it twice. */
 			purple_roomlist_unref(dialog->roomlist);
-		}
+
+		if (rl)
+			rl->dialog = NULL;
+		purple_roomlist_unref(dialog->roomlist);
 	}
 
-	/* free stuff here */
-	if (dialog->roomlist)
-		purple_roomlist_unref(dialog->roomlist);
+	dialog->progress = NULL;
 	g_free(dialog);
 
 	return FALSE;
 }
 
 static void dialog_select_account_cb(GObject *w, PurpleAccount *account,
-                                     PidginRoomlistDialog *dialog)
+				     PidginRoomlistDialog *dialog)
 {
 	dialog->account = account;
 }
@@ -186,9 +187,7 @@
 	GValue val;
 	PurpleRoomlistRoom *room;
 	static struct _menu_cb_info *info;
-	PidginRoomlistDialog *dialog;
-
-	dialog = grl->dialog;
+	PidginRoomlistDialog *dialog = grl->dialog;
 
 	if (gtk_tree_selection_get_selected(selection, NULL, &iter)) {
 		val.g_type = 0;
@@ -239,9 +238,7 @@
 {
 	PurpleRoomlist *rl = dialog->roomlist;
 	PidginRoomlist *grl = rl->ui_data;
-	struct _menu_cb_info *info;
-
-	info = (struct _menu_cb_info*)g_object_get_data(G_OBJECT(button), "room-info");
+	struct _menu_cb_info *info = g_object_get_data(G_OBJECT(button), "room-info");
 
 	if(info != NULL)
 		do_add_room_cb(grl->tree, info);
@@ -256,9 +253,7 @@
 {
 	PurpleRoomlist *rl = dialog->roomlist;
 	PidginRoomlist *grl = rl->ui_data;
-	struct _menu_cb_info *info;
-
-	info = (struct _menu_cb_info*)g_object_get_data(G_OBJECT(button), "room-info");
+	struct _menu_cb_info *info = g_object_get_data(G_OBJECT(button), "room-info");
 
 	if(info != NULL)
 		do_join_cb(grl->tree, info);
@@ -490,12 +485,13 @@
 
 static gboolean account_filter_func(PurpleAccount *account)
 {
-	PurpleConnection *gc = purple_account_get_connection(account);
+	PurpleConnection *conn = purple_account_get_connection(account);
 	PurplePluginProtocolInfo *prpl_info = NULL;
 
-	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
+	if (conn)
+		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(conn->prpl);
 
-	return (prpl_info->roomlist_get_list != NULL);
+	return (prpl_info && prpl_info->roomlist_get_list != NULL);
 }
 
 gboolean
@@ -518,10 +514,7 @@
 pidgin_roomlist_dialog_new_with_account(PurpleAccount *account)
 {
 	PidginRoomlistDialog *dialog;
-	GtkWidget *window;
-	GtkWidget *vbox;
-	GtkWidget *vbox2;
-	GtkWidget *bbox;
+	GtkWidget *window, *vbox, *vbox2, *bbox;
 
 	dialog = g_new0(PidginRoomlistDialog, 1);
 	dialog->account = account;
@@ -611,9 +604,8 @@
 
 void pidgin_roomlist_dialog_show_with_account(PurpleAccount *account)
 {
-	PidginRoomlistDialog *dialog;
+	PidginRoomlistDialog *dialog = pidgin_roomlist_dialog_new_with_account(account);
 
-	dialog = pidgin_roomlist_dialog_new_with_account(account);
 	if (!dialog)
 		return;
 
@@ -627,9 +619,7 @@
 
 static void pidgin_roomlist_new(PurpleRoomlist *list)
 {
-	PidginRoomlist *rl;
-
-	rl = g_new0(PidginRoomlist, 1);
+	PidginRoomlist *rl = g_new0(PidginRoomlist, 1);
 
 	list->ui_data = rl;
 
@@ -802,7 +792,7 @@
 
 	if (!rl || !rl->dialog || !rl->dialog->pg_needs_pulse) {
 		if (rl && rl->dialog)
-			rl->dialog->pg_to_active = FALSE;
+			rl->dialog->pg_update_to = 0;
 		purple_roomlist_unref(list);
 		return FALSE;
 	}
@@ -827,15 +817,14 @@
 		rl->num_rooms++;
 
 	if (rl->dialog) {
-		if (!rl->dialog->pg_to_active) {
-			rl->dialog->pg_to_active = TRUE;
+		if (rl->dialog->pg_update_to == 0) {
 			purple_roomlist_ref(list);
 			rl->dialog->pg_update_to = g_timeout_add(100, pidgin_progress_bar_pulse, list);
 			gtk_progress_bar_pulse(GTK_PROGRESS_BAR(rl->dialog->progress));
-		} else {
+		} else
 			rl->dialog->pg_needs_pulse = TRUE;
-		}
 	}
+
 	if (room->parent) {
 		parentrr = g_hash_table_lookup(rl->cats, room->parent);
 		path = gtk_tree_row_reference_get_path(parentrr);
@@ -881,14 +870,14 @@
 	}
 }
 
-static void pidgin_roomlist_in_progress(PurpleRoomlist *list, gboolean flag)
+static void pidgin_roomlist_in_progress(PurpleRoomlist *list, gboolean in_progress)
 {
 	PidginRoomlist *rl = list->ui_data;
 
 	if (!rl || !rl->dialog)
 		return;
 
-	if (flag) {
+	if (in_progress) {
 		if (rl->dialog->account_widget)
 			gtk_widget_set_sensitive(rl->dialog->account_widget, FALSE);
 		gtk_widget_set_sensitive(rl->dialog->stop_button, TRUE);
@@ -905,12 +894,10 @@
 
 static void pidgin_roomlist_destroy(PurpleRoomlist *list)
 {
-	PidginRoomlist *rl;
+	PidginRoomlist *rl = list->ui_data;
 
 	roomlists = g_list_remove(roomlists, list);
 
-	rl = list->ui_data;
-
 	g_return_if_fail(rl != NULL);
 
 	g_hash_table_destroy(rl->cats);
--- a/pidgin/gtksmiley.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/pidgin/gtksmiley.c	Thu Nov 20 21:13:56 2008 +0000
@@ -275,7 +275,8 @@
 			g_free(buffer);
 		}
 		emoticon = purple_smiley_new_from_file(entry, s->filename);
-		pidgin_smiley_add_to_list(emoticon);
+		if (emoticon)
+			pidgin_smiley_add_to_list(emoticon);
 	}
 
 	if (smiley_manager != NULL)
--- a/pidgin/gtkutils.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/pidgin/gtkutils.c	Thu Nov 20 21:13:56 2008 +0000
@@ -1001,13 +1001,14 @@
 	}
 
 	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(conn->prpl);
+	if (prpl_info != NULL && prpl_info->get_cb_real_name)
+		who = prpl_info->get_cb_real_name(conn, chat, name);
 	if (prpl_info == NULL || prpl_info->get_cb_info == NULL) {
-		pidgin_retrieve_user_info(conn, name);
+		pidgin_retrieve_user_info(conn, who ? who : name);
+		g_free(who);
 		return;
 	}
 
-	if (prpl_info->get_cb_real_name)
-		who = prpl_info->get_cb_real_name(conn, chat, name);
 	show_retrieveing_info(conn, who ? who : name);
 	prpl_info->get_cb_info(conn, chat, name);
 	g_free(who);
--- a/pidgin/pidgintooltip.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/pidgin/pidgintooltip.c	Thu Nov 20 21:13:56 2008 +0000
@@ -59,6 +59,7 @@
 destroy_tooltip_data(PidginTooltipData *data)
 {
 	gtk_tree_path_free(data->common.treeview.path);
+	pidgin_tooltip_destroy();
 	g_free(data);
 }
 
Binary file pidgin/pixmaps/emblems/16/birthday.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pixmaps/emblems/16/scalable/birthday.svg	Thu Nov 20 21:13:56 2008 +0000
@@ -0,0 +1,629 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="16px"
+   height="16px"
+   id="svg8140"
+   sodipodi:version="0.32"
+   inkscape:version="0.46"
+   sodipodi:docname="birthday.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape">
+  <defs
+     id="defs8142">
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4232"
+       id="linearGradient3007"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,0.7499999,14.982194,1.1250003)"
+       x1="7.5089025"
+       y1="2.218369"
+       x2="7.5089025"
+       y2="4.8258252" />
+    <linearGradient
+       id="linearGradient4380"
+       inkscape:collect="always">
+      <stop
+         id="stop4382"
+         offset="0"
+         style="stop-color:#fcaf3e;stop-opacity:1;" />
+      <stop
+         id="stop4384"
+         offset="1"
+         style="stop-color:#ffffff;stop-opacity:1" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4380"
+       id="linearGradient3005"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,0.7499999,-2.9821948,0.3750003)"
+       x1="15.491097"
+       y1="4.2733984"
+       x2="15.491097"
+       y2="2.7707961" />
+    <linearGradient
+       id="linearGradient4142"
+       inkscape:collect="always">
+      <stop
+         id="stop4144"
+         offset="0"
+         style="stop-color:#5c3566;stop-opacity:1;" />
+      <stop
+         id="stop4146"
+         offset="1"
+         style="stop-color:#9253a2;stop-opacity:1" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4142"
+       id="linearGradient3029"
+       gradientUnits="userSpaceOnUse"
+       x1="15.5"
+       y1="10.635184"
+       x2="15.5"
+       y2="7.1438446"
+       gradientTransform="matrix(1,0,0,0.8000001,-2.9821944,0.2999994)" />
+    <linearGradient
+       id="linearGradient4150"
+       inkscape:collect="always">
+      <stop
+         id="stop4152"
+         offset="0"
+         style="stop-color:#3c7704;stop-opacity:1" />
+      <stop
+         id="stop4154"
+         offset="1"
+         style="stop-color:#59b106;stop-opacity:1" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4150"
+       id="linearGradient3034"
+       gradientUnits="userSpaceOnUse"
+       x1="7.5"
+       y1="9.4861355"
+       x2="7.5"
+       y2="7.0554562"
+       gradientTransform="matrix(1,0,0,0.8000001,-3,0.2999994)" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4232"
+       id="linearGradient3048"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,0.7499999,-3.0178049,0.3750005)"
+       x1="7.5089025"
+       y1="2.218369"
+       x2="7.5089025"
+       y2="4.8258252" />
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4374">
+      <stop
+         style="stop-color:#fcaf3e;stop-opacity:1;"
+         offset="0"
+         id="stop4376" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1"
+         offset="1"
+         id="stop4378" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4374"
+       id="linearGradient3046"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.463041,0,0,1.1368063,-10.850902,0.678176)"
+       x1="6.3242626"
+       y1="2.3645318"
+       x2="6.3242626"
+       y2="1.6300712" />
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4232">
+      <stop
+         style="stop-color:#f57900;stop-opacity:1;"
+         offset="0"
+         id="stop4234" />
+      <stop
+         style="stop-color:#b25800;stop-opacity:1"
+         offset="1"
+         id="stop4236" />
+    </linearGradient>
+    <linearGradient
+       y2="4.8258252"
+       x2="7.5089025"
+       y1="2.218369"
+       x1="7.5089025"
+       gradientTransform="matrix(1,0,0,0.7499999,1.0000002,-0.6249996)"
+       gradientUnits="userSpaceOnUse"
+       id="linearGradient4308"
+       xlink:href="#linearGradient4232"
+       inkscape:collect="always" />
+    <linearGradient
+       id="linearGradient4368"
+       inkscape:collect="always">
+      <stop
+         id="stop4370"
+         offset="0"
+         style="stop-color:#fcaf3e;stop-opacity:1;" />
+      <stop
+         id="stop4372"
+         offset="1"
+         style="stop-color:#ffffff;stop-opacity:1" />
+    </linearGradient>
+    <linearGradient
+       y2="1.6300712"
+       x2="6.3242626"
+       y1="2.3645318"
+       x1="6.3242626"
+       gradientTransform="matrix(2.463041,0,0,1.1368063,-6.8330964,-0.3218242)"
+       gradientUnits="userSpaceOnUse"
+       id="linearGradient4306"
+       xlink:href="#linearGradient4368"
+       inkscape:collect="always" />
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient3425">
+      <stop
+         style="stop-color:#204a87;stop-opacity:1;"
+         offset="0"
+         id="stop3427" />
+      <stop
+         style="stop-color:#2e69c2;stop-opacity:1"
+         offset="1"
+         id="stop3429" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3425"
+       id="linearGradient3431"
+       x1="11.5"
+       y1="9.961833"
+       x2="11.241222"
+       y2="6.6366434"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-2.9999997,-2.0000001)" />
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4388">
+      <stop
+         style="stop-color:#d3d7cf;stop-opacity:1;"
+         offset="0"
+         id="stop4390" />
+      <stop
+         style="stop-color:#d3d7cf;stop-opacity:0;"
+         offset="1"
+         id="stop4392" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4388"
+       id="linearGradient4394"
+       x1="2.9999998"
+       y1="11.5"
+       x2="21"
+       y2="11.5"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4158">
+      <stop
+         style="stop-color:#9a9c98;stop-opacity:1"
+         offset="0"
+         id="stop4160" />
+      <stop
+         style="stop-color:#666763;stop-opacity:1"
+         offset="1"
+         id="stop4162" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4158"
+       id="linearGradient4164"
+       x1="16.274719"
+       y1="9.7764273"
+       x2="17.448"
+       y2="13.753902"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.7894737,0,0,0.6666667,-1.4736843,1.8333322)" />
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4048">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop4050" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop4052" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4048"
+       id="linearGradient4054"
+       x1="-10.516191"
+       y1="10.124428"
+       x2="36.795452"
+       y2="19.026175"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient3941">
+      <stop
+         style="stop-color:#af6d02;stop-opacity:1"
+         offset="0"
+         id="stop3943" />
+      <stop
+         style="stop-color:#5f3b00;stop-opacity:1"
+         offset="1"
+         id="stop3945" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3941"
+       id="linearGradient3947"
+       x1="15.917198"
+       y1="16.659033"
+       x2="16.463091"
+       y2="20.489477"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.7894737,0,0,0.7,-1.473684,0.4500012)" />
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4003">
+      <stop
+         style="stop-color:#c17d11;stop-opacity:1"
+         offset="0"
+         id="stop4005" />
+      <stop
+         style="stop-color:#e9b96e;stop-opacity:1"
+         offset="1"
+         id="stop4007" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4003"
+       id="linearGradient4009"
+       x1="16.815628"
+       y1="16.941942"
+       x2="10.718681"
+       y2="16.941942"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.7894737,0,0,0.7,-1.473684,0.4500012)" />
+    <filter
+       inkscape:collect="always"
+       id="filter4540"
+       x="-0.087152615"
+       width="1.1743052"
+       y="-0.21174857"
+       height="1.4234971">
+      <feGaussianBlur
+         inkscape:collect="always"
+         stdDeviation="0.37221428"
+         id="feGaussianBlur4542" />
+    </filter>
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 8 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="16 : 8 : 1"
+       inkscape:persp3d-origin="8 : 5.3333333 : 1"
+       id="perspective8148" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="22.197802"
+     inkscape:cx="8"
+     inkscape:cy="8"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="641"
+     inkscape:window-height="669"
+     inkscape:window-x="0"
+     inkscape:window-y="22">
+    <inkscape:grid
+       type="xygrid"
+       id="grid8150" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata8145">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer">
+    <path
+       sodipodi:type="arc"
+       style="opacity:0.47499999999999998;fill:#edd400;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;filter:url(#filter4540);enable-background:accumulate"
+       id="path4466"
+       sodipodi:cx="12.03125"
+       sodipodi:cy="4.265625"
+       sodipodi:rx="5.125"
+       sodipodi:ry="2.109375"
+       d="M 17.15625,4.265625 A 5.125,2.109375 0 1 1 6.90625,4.265625 A 5.125,2.109375 0 1 1 17.15625,4.265625 z"
+       transform="matrix(1.3658536,0,0,1.1656218,-8.4329267,-2.4721054)" />
+    <path
+       style="fill:url(#linearGradient4009);fill-opacity:1;stroke:url(#linearGradient3947);stroke-width:1.00000072000000007;stroke-miterlimit:4;stroke-opacity:1"
+       d="m 0.50000029,9.8437508 0,3.5562497 C 0.50000029,14.5592 3.8600006,15.5 8.0000002,15.5 12.14,15.5 15.5,14.559199 15.5,13.4 l 0,-3.5562497 z"
+       id="path3936"
+       sodipodi:nodetypes="ccsccc" />
+    <rect
+       style="opacity:0.26499999000000002;color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect4624"
+       width="2"
+       height="3"
+       x="3"
+       y="11" />
+    <path
+       sodipodi:type="inkscape:offset"
+       inkscape:radius="-1.0054175"
+       inkscape:original="M 2.5 11.5 L 2.5 18.5 C 2.5 20.155999 6.7560004 21.5 12 21.5 C 17.243999 21.5 21.5 20.155998 21.5 18.5 L 21.5 11.5 L 2.5 11.5 z "
+       style="fill:none;stroke:url(#linearGradient4054);stroke-width:1.44648218000000006;stroke-miterlimit:4;stroke-opacity:1"
+       id="path3983"
+       d="m 3.5,12.5 0,6 c 0,-0.016662 0.0034472,0.121066 0.34375,0.375 0.3403028,0.253934 0.9602178,0.531845 1.75,0.78125 C 7.1733144,20.15506 9.4638941,20.5 12,20.5 c 2.536106,0 4.826685,-0.34494 6.40625,-0.84375 0.789782,-0.249405 1.409697,-0.527316 1.75,-0.78125 C 20.496553,18.621066 20.5,18.483337 20.5,18.5 l 0,-6 z"
+       transform="matrix(0.7647058,0,0,0.6249999,-1.1764702,1.6875019)" />
+    <rect
+       style="opacity:0.83499995000000005;color:#000000;fill:#8f5902;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect4056"
+       width="1"
+       height="3"
+       x="6"
+       y="11"
+       rx="0.5"
+       ry="0.5" />
+    <rect
+       style="opacity:0.83499995000000005;color:#000000;fill:#8f5902;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect4078"
+       width="1"
+       height="3"
+       x="3"
+       y="10"
+       rx="0.5"
+       ry="0.5" />
+    <rect
+       style="opacity:0.83499995000000005;color:#000000;fill:#8f5902;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect4131"
+       width="1"
+       height="3"
+       x="12"
+       y="10"
+       rx="0.5"
+       ry="0.5" />
+    <rect
+       style="opacity:0.26499999000000002;color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect2655"
+       width="2"
+       height="3"
+       x="10"
+       y="11" />
+    <rect
+       style="opacity:0.26499999000000002;color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect2664"
+       width="2"
+       height="2"
+       x="13"
+       y="11" />
+    <rect
+       style="opacity:0.26499999000000002;color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect2646"
+       width="2"
+       height="3"
+       x="7"
+       y="12" />
+    <rect
+       style="opacity:0.83499995000000005;color:#000000;fill:#8f5902;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect4140"
+       width="1"
+       height="3"
+       x="8"
+       y="10"
+       rx="0.5"
+       ry="0.5" />
+    <path
+       style="fill:#eeeeec;fill-opacity:1;stroke:url(#linearGradient4164);stroke-width:1.00000059999999991;stroke-miterlimit:4;stroke-opacity:1"
+       d="M 15.500001,9.5000002 C 15.500001,10.604 12.139999,11.5 8,11.5 3.8600003,11.5 0.5000003,10.604 0.5000003,9.5000004 0.5000003,8.3960006 3.8600004,7.5000001 8,7.5000001 c 4.140001,0 7.500001,0.89600032 7.500001,2.0000001 z"
+       id="path1307" />
+    <path
+       sodipodi:type="inkscape:offset"
+       inkscape:radius="-1.0051613"
+       inkscape:original="M 12 8.5 C 6.7560004 8.5 2.5000001 9.844 2.5 11.5 C 2.5 13.155999 6.7560004 14.5 12 14.5 C 17.243999 14.5 21.5 13.155999 21.5 11.5 C 21.5 9.844 17.244 8.5000008 12 8.5 z "
+       xlink:href="#path4198"
+       style="fill:url(#linearGradient4394);fill-opacity:1;stroke:#ffffff;stroke-width:1.61721622999999992;stroke-miterlimit:4;stroke-opacity:1"
+       id="path4200"
+       d="M 12,9.5 C 9.4638721,9.5 7.1733501,9.8449289 5.59375,10.34375 4.80395,10.593161 4.1840886,10.87104 3.84375,11.125 3.5034114,11.37896 3.5,11.516552 3.5,11.5 c 0,-0.016553 0.0034113,0.12104 0.34375,0.375 0.3403387,0.25396 0.9601999,0.531839 1.75,0.78125 C 7.1733501,13.155071 9.4638722,13.5 12,13.5 c 2.536128,0 4.82665,-0.344929 6.40625,-0.84375 0.7898,-0.249411 1.409661,-0.52729 1.75,-0.78125 C 20.496589,11.62104 20.5,11.483447 20.5,11.5 c 0,0.016552 -0.003411,-0.12104 -0.34375,-0.375 -0.340339,-0.25396 -0.9602,-0.53184 -1.75,-0.78125 C 16.82665,9.844929 14.536128,9.5000004 12,9.5 z"
+       transform="matrix(0.7647058,0,0,0.4999999,-1.1764702,3.7500016)" />
+    <rect
+       style="opacity:1;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient3431);stroke-width:1.00000011999999994;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3304"
+       width="2"
+       height="4.999999"
+       x="7.5"
+       y="4.500001"
+       rx="1"
+       ry="1" />
+    <rect
+       style="opacity:0.55500033999999998;color:#000000;fill:#c6d7ed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3308"
+       width="1"
+       height="1"
+       x="8"
+       y="6" />
+    <rect
+       style="opacity:0.55500033999999998;color:#000000;fill:#c6d7ed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3330"
+       width="1"
+       height="1"
+       x="8"
+       y="8" />
+    <rect
+       style="opacity:0.31000000999999999;color:#000000;fill:#c6d7ed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3608"
+       width="1"
+       height="1"
+       x="7"
+       y="6" />
+    <rect
+       style="opacity:0.31000000999999999;color:#000000;fill:#c6d7ed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3617"
+       width="1"
+       height="1"
+       x="7"
+       y="8" />
+    <rect
+       style="opacity:0.31000000999999999;color:#000000;fill:#c6d7ed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3626"
+       width="1"
+       height="1"
+       x="9"
+       y="8" />
+    <rect
+       style="opacity:0.31000000999999999;color:#000000;fill:#c6d7ed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3635"
+       width="1"
+       height="1"
+       x="9"
+       y="6" />
+    <path
+       style="fill:url(#linearGradient4306);fill-opacity:1;stroke:url(#linearGradient4308);stroke-width:1.00000119000000010;stroke-miterlimit:4;stroke-opacity:1"
+       d="m 8.9999763,3.4999993 c -0.827986,0 -1.499975,-0.6719998 -1.499975,-1.4999993 0,-0.82799966 0.671989,-1.4999994 1.4999755,-1.4999994 0.5378079,0 0.8279864,2.9999986 0,2.9999987 z"
+       id="path4284"
+       sodipodi:nodetypes="csss" />
+    <path
+       style="fill:url(#linearGradient3046);fill-opacity:1;stroke:url(#linearGradient3048);stroke-width:1.00000119000000010;stroke-miterlimit:4;stroke-opacity:1"
+       d="M 4.9821711,4.4999994 C 4.1541851,4.4999994 3.4821961,3.8279997 3.4821961,3.0000001 C 3.4821961,2.1720004 4.1541851,1.5000007 4.9821711,1.5000007 C 5.5199791,1.5000007 5.8101571,4.4999993 4.9821711,4.4999994 L 4.9821711,4.4999994 z"
+       id="path2289"
+       sodipodi:nodetypes="csss" />
+    <rect
+       style="fill:#6ec31b;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient3034);stroke-width:1.00000024000000010;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3469"
+       width="2"
+       height="4"
+       x="3.5"
+       y="5.5"
+       rx="1"
+       ry="1" />
+    <rect
+       style="opacity:0.55500033999999998;fill:#c6d7ed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3471"
+       width="1"
+       height="1"
+       x="4"
+       y="5.9999995" />
+    <rect
+       style="opacity:0.55500033999999998;fill:#c6d7ed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3473"
+       width="1"
+       height="1"
+       x="4"
+       y="7.9999995" />
+    <rect
+       style="fill:#ad7fa8;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient3029);stroke-width:1.00000024000000010;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3533"
+       width="2"
+       height="4"
+       x="11.517806"
+       y="5.5"
+       rx="1"
+       ry="1" />
+    <rect
+       style="opacity:0.55500033999999998;fill:#c6d7ed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3535"
+       width="1"
+       height="1"
+       x="12.017806"
+       y="5.9999995" />
+    <rect
+       style="opacity:0.55500033999999998;fill:#c6d7ed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3537"
+       width="1"
+       height="1"
+       x="12.017806"
+       y="7.9999995" />
+    <rect
+       style="opacity:0.31000000999999999;fill:#c6d7ed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3559"
+       width="1"
+       height="1"
+       x="3"
+       y="6" />
+    <rect
+       style="opacity:0.31000000999999999;fill:#c6d7ed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3581"
+       width="1"
+       height="1"
+       x="3"
+       y="8" />
+    <rect
+       style="opacity:0.31000000999999999;fill:#c6d7ed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3590"
+       width="1"
+       height="1"
+       x="5"
+       y="8" />
+    <rect
+       style="opacity:0.31000000999999999;fill:#c6d7ed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3599"
+       width="1"
+       height="1"
+       x="5"
+       y="6" />
+    <rect
+       style="opacity:0.31000000999999999;fill:#c6d7ed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3644"
+       width="1"
+       height="1"
+       x="11.017806"
+       y="6" />
+    <rect
+       style="opacity:0.31000000999999999;fill:#c6d7ed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3653"
+       width="1"
+       height="1"
+       x="13.017806"
+       y="6" />
+    <rect
+       style="opacity:0.31000000999999999;fill:#c6d7ed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3662"
+       width="1"
+       height="1"
+       x="13.017806"
+       y="8" />
+    <rect
+       style="opacity:0.31000000999999999;fill:#c6d7ed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="rect3671"
+       width="1"
+       height="1"
+       x="11.017806"
+       y="8" />
+    <path
+       style="fill:url(#linearGradient3005);fill-opacity:1;stroke:url(#linearGradient3007);stroke-width:1.00000119000000010;stroke-miterlimit:4;stroke-opacity:1"
+       d="M 12.999976,4.4999993 C 12.17199,4.4999993 11.500001,3.8279995 11.500001,3 C 11.500001,2.1720003 12.17199,1.5000006 12.999977,1.5000006 C 13.537784,1.5000006 13.827963,4.4999992 12.999977,4.4999993 L 12.999976,4.4999993 z"
+       id="path4348"
+       sodipodi:nodetypes="csss" />
+  </g>
+</svg>
--- a/pidgin/pixmaps/emotes/default/24/Makefile.am	Mon Aug 18 17:08:01 2008 +0000
+++ b/pidgin/pixmaps/emotes/default/24/Makefile.am	Thu Nov 20 21:13:56 2008 +0000
@@ -16,6 +16,7 @@
     boy.png \
     brb.png \
     bulgy-eyes.png \
+    bunny.png \
     bye.png \
     cake.png \
     call-me.png \
Binary file pidgin/pixmaps/emotes/default/24/airplane.png has changed
Binary file pidgin/pixmaps/emotes/default/24/bad.png has changed
Binary file pidgin/pixmaps/emotes/default/24/beer.png has changed
Binary file pidgin/pixmaps/emotes/default/24/bomb.png has changed
Binary file pidgin/pixmaps/emotes/default/24/bowl.png has changed
Binary file pidgin/pixmaps/emotes/default/24/boy.png has changed
Binary file pidgin/pixmaps/emotes/default/24/brb.png has changed
Binary file pidgin/pixmaps/emotes/default/24/bunny.png has changed
Binary file pidgin/pixmaps/emotes/default/24/cake.png has changed
Binary file pidgin/pixmaps/emotes/default/24/camera.png has changed
Binary file pidgin/pixmaps/emotes/default/24/car.png has changed
Binary file pidgin/pixmaps/emotes/default/24/cat.png has changed
Binary file pidgin/pixmaps/emotes/default/24/clock.png has changed
Binary file pidgin/pixmaps/emotes/default/24/coffee.png has changed
Binary file pidgin/pixmaps/emotes/default/24/coins.png has changed
Binary file pidgin/pixmaps/emotes/default/24/console.png has changed
--- a/pidgin/pixmaps/emotes/default/24/default.theme.in	Mon Aug 18 17:08:01 2008 +0000
+++ b/pidgin/pixmaps/emotes/default/24/default.theme.in	Thu Nov 20 21:13:56 2008 +0000
@@ -3,7 +3,6 @@
 Icon=wink.png
 Author=Hylke Bons
 
-
 # Default smileys
 [default]
 smile.png           :)      :-)
@@ -125,6 +124,7 @@
 party.png           <:o)
 eyeroll.png         8-)
 yawn.png            |-) 
+bunny.png           ('.')
 ! skywalker.png     C:-)    c:-)    C:)     c:)
 ! monkey.png        :-(|)  :(|)
 
Binary file pidgin/pixmaps/emotes/default/24/dog.png has changed
Binary file pidgin/pixmaps/emotes/default/24/drink.png has changed
Binary file pidgin/pixmaps/emotes/default/24/film.png has changed
Binary file pidgin/pixmaps/emotes/default/24/girl.png has changed
Binary file pidgin/pixmaps/emotes/default/24/goat.png has changed
Binary file pidgin/pixmaps/emotes/default/24/good.png has changed
Binary file pidgin/pixmaps/emotes/default/24/handcuffs.png has changed
Binary file pidgin/pixmaps/emotes/default/24/lamp.png has changed
Binary file pidgin/pixmaps/emotes/default/24/love-over.png has changed
Binary file pidgin/pixmaps/emotes/default/24/love.png has changed
Binary file pidgin/pixmaps/emotes/default/24/mail.png has changed
Binary file pidgin/pixmaps/emotes/default/24/mobile.png has changed
Binary file pidgin/pixmaps/emotes/default/24/moon.png has changed
Binary file pidgin/pixmaps/emotes/default/24/msn.png has changed
Binary file pidgin/pixmaps/emotes/default/24/musical-note.png has changed
Binary file pidgin/pixmaps/emotes/default/24/phone.png has changed
Binary file pidgin/pixmaps/emotes/default/24/pizza.png has changed
Binary file pidgin/pixmaps/emotes/default/24/plate.png has changed
Binary file pidgin/pixmaps/emotes/default/24/present.png has changed
Binary file pidgin/pixmaps/emotes/default/24/rain.png has changed
Binary file pidgin/pixmaps/emotes/default/24/rose-dead.png has changed
Binary file pidgin/pixmaps/emotes/default/24/rose.png has changed
Binary file pidgin/pixmaps/emotes/default/24/sheep.png has changed
Binary file pidgin/pixmaps/emotes/default/24/soccerball.png has changed
Binary file pidgin/pixmaps/emotes/default/24/star.png has changed
Binary file pidgin/pixmaps/emotes/default/24/sun.png has changed
Binary file pidgin/pixmaps/emotes/default/24/thunder.png has changed
Binary file pidgin/pixmaps/emotes/default/24/turtle.png has changed
Binary file pidgin/pixmaps/emotes/default/24/umbrella.png has changed
Binary file pidgin/pixmaps/toolbar/16/emote-select.png has changed
--- a/pidgin/pixmaps/toolbar/16/scalable/emote-select.svg	Mon Aug 18 17:08:01 2008 +0000
+++ b/pidgin/pixmaps/toolbar/16/scalable/emote-select.svg	Thu Nov 20 21:13:56 2008 +0000
@@ -2,113 +2,26 @@
 <!-- Created with Inkscape (http://www.inkscape.org/) -->
 <svg
    xmlns:dc="http://purl.org/dc/elements/1.1/"
-   xmlns:cc="http://web.resource.org/cc/"
+   xmlns:cc="http://creativecommons.org/ns#"
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:svg="http://www.w3.org/2000/svg"
    xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink"
    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   width="16px"
-   height="16px"
-   id="svg4346"
+   id="svg2"
    sodipodi:version="0.32"
-   inkscape:version="0.44.1"
-   sodipodi:docbase="/home/hbons/Desktop/pidgin improvements"
-   sodipodi:docname="inser-emote.svg"
-   inkscape:export-filename="/home/hbons/Desktop/pidgin improvements/insert-emote.png"
+   inkscape:version="0.46"
+   width="16"
+   height="16"
+   version="1.0"
+   sodipodi:docname="emote-select.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:export-filename="/home/hbons/Pidgin objects refresh/emote-select.png"
    inkscape:export-xdpi="90"
    inkscape:export-ydpi="90">
-  <defs
-     id="defs4348">
-    <linearGradient
-       inkscape:collect="always"
-       id="linearGradient5269">
-      <stop
-         style="stop-color:#fcaf3e;stop-opacity:1;"
-         offset="0"
-         id="stop5271" />
-      <stop
-         style="stop-color:#fcaf3e;stop-opacity:0;"
-         offset="1"
-         id="stop5273" />
-    </linearGradient>
-    <linearGradient
-       inkscape:collect="always"
-       id="linearGradient3104">
-      <stop
-         style="stop-color:#eeeeec;stop-opacity:1;"
-         offset="0"
-         id="stop3106" />
-      <stop
-         style="stop-color:#eeeeec;stop-opacity:0;"
-         offset="1"
-         id="stop3108" />
-    </linearGradient>
-    <radialGradient
-       inkscape:collect="always"
-       xlink:href="#linearGradient3104"
-       id="radialGradient3114"
-       cx="5.7434092"
-       cy="16.737026"
-       fx="5.7434092"
-       fy="16.737026"
-       r="9.975256"
-       gradientUnits="userSpaceOnUse"
-       gradientTransform="matrix(-1.30241,1.304442,-1.325199,-1.323009,41.46631,16.11711)" />
-    <linearGradient
-       inkscape:collect="always"
-       id="linearGradient3150">
-      <stop
-         style="stop-color:#2e3436;stop-opacity:1;"
-         offset="0"
-         id="stop3152" />
-      <stop
-         style="stop-color:#2e3436;stop-opacity:0;"
-         offset="1"
-         id="stop3154" />
-    </linearGradient>
-    <radialGradient
-       inkscape:collect="always"
-       xlink:href="#linearGradient3150"
-       id="radialGradient3156"
-       cx="10.748654"
-       cy="10.457643"
-       fx="10.748654"
-       fy="10.457643"
-       r="6.6449099"
-       gradientTransform="matrix(-0.842757,0,0,-0.35721,19.80716,14.19321)"
-       gradientUnits="userSpaceOnUse" />
-    <linearGradient
-       inkscape:collect="always"
-       xlink:href="#linearGradient5269"
-       id="linearGradient5275"
-       x1="26.243328"
-       y1="13.001364"
-       x2="26.243328"
-       y2="10.507664"
-       gradientUnits="userSpaceOnUse" />
-  </defs>
-  <sodipodi:namedview
-     id="base"
-     pagecolor="#ffffff"
-     bordercolor="#666666"
-     borderopacity="1.0"
-     inkscape:pageopacity="0.0"
-     inkscape:pageshadow="2"
-     inkscape:zoom="31.392433"
-     inkscape:cx="14.469085"
-     inkscape:cy="9.6077349"
-     inkscape:current-layer="layer1"
-     showgrid="true"
-     inkscape:grid-bbox="true"
-     inkscape:document-units="px"
-     inkscape:window-width="1274"
-     inkscape:window-height="966"
-     inkscape:window-x="3"
-     inkscape:window-y="25" />
   <metadata
-     id="metadata4351">
+     id="metadata7">
     <rdf:RDF>
       <cc:Work
          rdf:about="">
@@ -118,84 +31,573 @@
       </cc:Work>
     </rdf:RDF>
   </metadata>
-  <g
-     id="layer1"
-     inkscape:label="Layer 1"
-     inkscape:groupmode="layer">
-    <path
-       sodipodi:type="arc"
-       style="opacity:1;fill:#fce94f;fill-opacity:1;stroke:#f57900;stroke-width:1.53872597;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
-       id="path1307"
-       sodipodi:cx="11.806158"
-       sodipodi:cy="10.983024"
-       sodipodi:rx="9.975256"
-       sodipodi:ry="9.975256"
-       d="M 21.781414 10.983024 A 9.975256 9.975256 0 1 1  1.8309021,10.983024 A 9.975256 9.975256 0 1 1  21.781414 10.983024 z"
-       transform="matrix(0.651503,0,0,0.651499,-0.691896,-0.15554)" />
-    <path
-       sodipodi:type="arc"
-       style="opacity:0.79545456;fill:url(#radialGradient3114);fill-opacity:1;stroke:none;stroke-width:1.05274069;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
-       id="path3102"
-       sodipodi:cx="11.806158"
-       sodipodi:cy="10.983024"
-       sodipodi:rx="9.975256"
-       sodipodi:ry="9.975256"
-       d="M 21.781414 10.983024 A 9.975256 9.975256 0 1 1  1.8309021,10.983024 A 9.975256 9.975256 0 1 1  21.781414 10.983024 z"
-       transform="matrix(0.601488,0,0,0.601488,-0.101266,0.39384)" />
-    <path
-       sodipodi:type="arc"
-       style="opacity:0.5;fill:none;fill-opacity:1;stroke:white;stroke-width:1.81368434;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
-       id="path2184"
-       sodipodi:cx="11.806158"
-       sodipodi:cy="10.983024"
-       sodipodi:rx="9.975256"
-       sodipodi:ry="9.975256"
-       d="M 21.781414 10.983024 A 9.975256 9.975256 0 1 1  1.8309021,10.983024 A 9.975256 9.975256 0 1 1  21.781414 10.983024 z"
-       transform="matrix(0.551364,0,0,0.551364,0.490507,0.944359)" />
-    <path
-       style="opacity:1;fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
-       d="M 15.725806,10.961292 C 15.725806,15.032532 14.403819,17 12,17 C 9.596182,17 8.1945569,15.02924 8.1945569,10.961292 C 9.1602707,14.990359 9.904443,15.152467 12,15.152467 C 14.095556,15.152468 14.914933,15.003949 15.725806,10.961292 z "
-       id="path2186"
-       sodipodi:nodetypes="cscsc"
-       transform="matrix(1.062241,0,0,0.5,-5.704588,1.519354)" />
-    <path
-       sodipodi:type="arc"
-       style="opacity:1;fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
-       id="path2191"
-       sodipodi:cx="9.0598059"
-       sodipodi:cy="8.7845774"
-       sodipodi:rx="1.1679889"
-       sodipodi:ry="1.4520943"
-       d="M 10.227795 8.7845774 A 1.1679889 1.4520943 0 1 1  7.891817,8.7845774 A 1.1679889 1.4520943 0 1 1  10.227795 8.7845774 z"
-       transform="matrix(0.856175,0,0,1.032991,-2.756776,-3.574387)" />
-    <path
-       sodipodi:type="arc"
-       style="opacity:1;fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
-       id="path2193"
-       sodipodi:cx="9.0598059"
-       sodipodi:cy="8.7845774"
-       sodipodi:rx="1.1679889"
-       sodipodi:ry="1.4520943"
-       d="M 10.227795 8.7845774 A 1.1679889 1.4520943 0 1 1  7.891817,8.7845774 A 1.1679889 1.4520943 0 1 1  10.227795 8.7845774 z"
-       transform="matrix(0.85617,0,0,1.032991,1.243263,-3.574387)" />
-    <image
-       id="image4376"
-       height="16"
-       width="16"
-       sodipodi:absref="/home/hbons/Desktop/pidgin improvements/insert-image.png"
-       xlink:href="insert-image.png"
-       transform="translate(17,-1)" />
-    <path
-       transform="matrix(1.046767,0,0,0.836179,-16.0259,3.13821)"
-       style="fill:#eeeeec;fill-opacity:1;stroke:none;stroke-width:1.06887114;stroke-miterlimit:4;stroke-opacity:1"
-       d="M 22,10 L 29.642574,10 L 25.830275,14.783663 L 22,10 z "
-       id="path4382"
-       sodipodi:nodetypes="cccc" />
-    <path
-       transform="matrix(1.046767,0,0,0.836179,-16.52887,3.138211)"
-       style="fill:url(#linearGradient5275);fill-opacity:1;stroke:#d5680b;stroke-width:1.06887114;stroke-miterlimit:4;stroke-opacity:1"
-       d="M 22,10 L 29.642574,10 L 25.830275,14.783663 L 22,10 z "
-       id="rect4379"
-       sodipodi:nodetypes="cccc" />
-  </g>
+  <defs
+     id="defs5">
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient3398">
+      <stop
+         style="stop-color:#fe9906;stop-opacity:1;"
+         offset="0"
+         id="stop3400" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1"
+         offset="1"
+         id="stop3402" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient3390">
+      <stop
+         style="stop-color:#fe9906;stop-opacity:1"
+         offset="0"
+         id="stop3392" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1"
+         offset="1"
+         id="stop3394" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient3378">
+      <stop
+         style="stop-color:#a64a00;stop-opacity:1;"
+         offset="0"
+         id="stop3380" />
+      <stop
+         style="stop-color:#a64a00;stop-opacity:0;"
+         offset="1"
+         id="stop3382" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient3357">
+      <stop
+         style="stop-color:#dd6c00;stop-opacity:1"
+         offset="0"
+         id="stop3359" />
+      <stop
+         style="stop-color:#7c3c00;stop-opacity:1"
+         offset="1"
+         id="stop3361" />
+    </linearGradient>
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective9" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="10.507664"
+       x2="26.243328"
+       y1="13.001364"
+       x1="26.243328"
+       id="linearGradient5275"
+       xlink:href="#linearGradient5269"
+       inkscape:collect="always" />
+    <radialGradient
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-0.842757,0,0,-0.35721,19.80716,14.19321)"
+       r="6.6449099"
+       fy="10.457643"
+       fx="10.748654"
+       cy="10.457643"
+       cx="10.748654"
+       id="radialGradient3156"
+       xlink:href="#linearGradient3150"
+       inkscape:collect="always" />
+    <linearGradient
+       id="linearGradient3150"
+       inkscape:collect="always">
+      <stop
+         id="stop3152"
+         offset="0"
+         style="stop-color:#2e3436;stop-opacity:1;" />
+      <stop
+         id="stop3154"
+         offset="1"
+         style="stop-color:#2e3436;stop-opacity:0;" />
+    </linearGradient>
+    <radialGradient
+       gradientTransform="matrix(-1.30241,1.304442,-1.325199,-1.323009,41.46631,16.11711)"
+       gradientUnits="userSpaceOnUse"
+       r="9.975256"
+       fy="16.737026"
+       fx="5.7434092"
+       cy="16.737026"
+       cx="5.7434092"
+       id="radialGradient3114"
+       xlink:href="#linearGradient3104"
+       inkscape:collect="always" />
+    <linearGradient
+       id="linearGradient3104"
+       inkscape:collect="always">
+      <stop
+         id="stop3106"
+         offset="0"
+         style="stop-color:#eeeeec;stop-opacity:1;" />
+      <stop
+         id="stop3108"
+         offset="1"
+         style="stop-color:#eeeeec;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient5269"
+       inkscape:collect="always">
+      <stop
+         id="stop5271"
+         offset="0"
+         style="stop-color:#fcaf3e;stop-opacity:1;" />
+      <stop
+         id="stop5273"
+         offset="1"
+         style="stop-color:#fcaf3e;stop-opacity:0;" />
+    </linearGradient>
+    <inkscape:perspective
+       id="perspective3186"
+       inkscape:persp3d-origin="8 : 5.3333333 : 1"
+       inkscape:vp_z="16 : 8 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 8 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <radialGradient
+       r="9.975256"
+       fy="14.186539"
+       fx="8.3343515"
+       cy="14.186539"
+       cx="8.3343515"
+       gradientTransform="matrix(-0.9327,0.932656,-0.947494,-0.947449,33.02126,11.96667)"
+       gradientUnits="userSpaceOnUse"
+       id="radialGradient2264"
+       xlink:href="#linearGradient3104"
+       inkscape:collect="always" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3104"
+       id="radialGradient2233"
+       cx="8.3343515"
+       cy="14.186539"
+       fx="8.3343515"
+       fy="14.186539"
+       r="9.975256"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-0.9327,0.932656,-0.947494,-0.947449,33.02126,11.96667)" />
+    <radialGradient
+       r="9.975256"
+       fy="14.186539"
+       fx="8.3343515"
+       cy="14.186539"
+       cx="8.3343515"
+       gradientTransform="matrix(-0.9327,0.932656,-0.947494,-0.947449,33.02126,11.96667)"
+       gradientUnits="userSpaceOnUse"
+       id="radialGradient2349"
+       xlink:href="#linearGradient3104"
+       inkscape:collect="always" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3104"
+       id="radialGradient2303"
+       cx="8.3343515"
+       cy="14.186539"
+       fx="8.3343515"
+       fy="14.186539"
+       r="9.975256"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-0.9327,0.932656,-0.947494,-0.947449,33.02126,11.96667)" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="17.682426"
+       x2="12.720216"
+       y1="20.952612"
+       x1="12.720216"
+       id="linearGradient2873"
+       xlink:href="#linearGradient2867"
+       inkscape:collect="always" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="20.761486"
+       x2="12.746171"
+       y1="18.202251"
+       x1="12.5"
+       id="linearGradient2853"
+       xlink:href="#linearGradient2847"
+       inkscape:collect="always" />
+    <radialGradient
+       gradientTransform="matrix(-0.9327,0.932656,-0.947494,-0.947449,33.02126,11.96667)"
+       gradientUnits="userSpaceOnUse"
+       r="9.975256"
+       fy="14.186539"
+       fx="8.3343515"
+       cy="14.186539"
+       cx="8.3343515"
+       id="radialGradient2230"
+       xlink:href="#linearGradient3104"
+       inkscape:collect="always" />
+    <linearGradient
+       id="linearGradient2382"
+       inkscape:collect="always">
+      <stop
+         id="stop2384"
+         offset="0"
+         style="stop-color:#d3d7cf;stop-opacity:1;" />
+      <stop
+         id="stop2386"
+         offset="1"
+         style="stop-color:#d3d7cf;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient2847"
+       inkscape:collect="always">
+      <stop
+         id="stop2849"
+         offset="0"
+         style="stop-color:#888a85;stop-opacity:1;" />
+      <stop
+         id="stop2851"
+         offset="1"
+         style="stop-color:#888a85;stop-opacity:0;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient2867"
+       inkscape:collect="always">
+      <stop
+         id="stop2869"
+         offset="0"
+         style="stop-color:#d3d7cf;stop-opacity:1;" />
+      <stop
+         id="stop2871"
+         offset="1"
+         style="stop-color:#d3d7cf;stop-opacity:0;" />
+    </linearGradient>
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2382"
+       id="radialGradient2259"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-1.591138,1.574803,-1.783257,-1.76495,68.854751,-2.8442229)"
+       cx="17.911736"
+       cy="11.083743"
+       fx="17.911736"
+       fy="11.083743"
+       r="2.5781252" />
+    <radialGradient
+       gradientTransform="matrix(-0.9327,0.932656,-0.947494,-0.947449,33.02126,11.96667)"
+       gradientUnits="userSpaceOnUse"
+       r="9.975256"
+       fy="14.186539"
+       fx="8.3343515"
+       cy="14.186539"
+       cx="8.3343515"
+       id="radialGradient3313"
+       xlink:href="#linearGradient3104"
+       inkscape:collect="always" />
+    <radialGradient
+       r="9.975256"
+       fy="14.186539"
+       fx="8.3343515"
+       cy="14.186539"
+       cx="8.3343515"
+       gradientTransform="matrix(-0.9327,0.932656,-0.947494,-0.947449,33.02126,11.96667)"
+       gradientUnits="userSpaceOnUse"
+       id="radialGradient2255"
+       xlink:href="#linearGradient3104"
+       inkscape:collect="always" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3104"
+       id="radialGradient2214"
+       cx="8.7359829"
+       cy="18.005522"
+       fx="8.7359829"
+       fy="18.005522"
+       r="9.975256"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-1.3308754,1.3308124,-1.4391023,-1.4390341,45.391773,16.51952)" />
+    <radialGradient
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-0.842757,0,0,-0.35721,19.80716,14.19321)"
+       r="6.6449099"
+       fy="10.457643"
+       fx="10.748654"
+       cy="10.457643"
+       cx="10.748654"
+       id="radialGradient3292"
+       xlink:href="#linearGradient3150"
+       inkscape:collect="always" />
+    <radialGradient
+       gradientTransform="matrix(-0.9327,0.932656,-0.947494,-0.947449,33.02126,11.96667)"
+       gradientUnits="userSpaceOnUse"
+       r="9.975256"
+       fy="14.186539"
+       fx="8.3343515"
+       cy="14.186539"
+       cx="8.3343515"
+       id="radialGradient3175"
+       xlink:href="#linearGradient3104"
+       inkscape:collect="always" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3104"
+       id="radialGradient3191"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-1.3308754,1.3308124,-1.4391023,-1.4390341,45.391773,16.51952)"
+       cx="8.7359829"
+       cy="18.005522"
+       fx="8.7359829"
+       fy="18.005522"
+       r="9.975256" />
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       y2="15.449714"
+       x2="10.698112"
+       y1="16.037401"
+       x1="12.845698"
+       id="linearGradient3269"
+       xlink:href="#linearGradient3263"
+       inkscape:collect="always" />
+    <radialGradient
+       gradientTransform="matrix(-0.9327,0.932656,-0.947494,-0.947449,33.02126,11.96667)"
+       gradientUnits="userSpaceOnUse"
+       r="9.975256"
+       fy="14.186539"
+       fx="8.3343515"
+       cy="14.186539"
+       cx="8.3343515"
+       id="radialGradient2216"
+       xlink:href="#linearGradient3104"
+       inkscape:collect="always" />
+    <linearGradient
+       id="linearGradient3263"
+       inkscape:collect="always">
+      <stop
+         id="stop3265"
+         offset="0"
+         style="stop-color:#555753;stop-opacity:1;" />
+      <stop
+         id="stop3267"
+         offset="1"
+         style="stop-color:#555753;stop-opacity:0;" />
+    </linearGradient>
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3104"
+       id="radialGradient2247"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-1.3308754,1.3308124,-1.4391023,-1.4390341,45.391773,16.51952)"
+       cx="8.7359829"
+       cy="18.005522"
+       fx="8.7359829"
+       fy="18.005522"
+       r="9.975256" />
+    <radialGradient
+       gradientTransform="matrix(-0.9327,0.932656,-0.947494,-0.947449,33.02126,11.96667)"
+       gradientUnits="userSpaceOnUse"
+       r="9.975256"
+       fy="14.186539"
+       fx="8.3343515"
+       cy="14.186539"
+       cx="8.3343515"
+       id="radialGradient3274"
+       xlink:href="#linearGradient3104"
+       inkscape:collect="always" />
+    <inkscape:perspective
+       id="perspective3265"
+       inkscape:persp3d-origin="12 : 8 : 1"
+       inkscape:vp_z="24 : 12 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 12 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3104"
+       id="radialGradient3331"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-1.30241,1.304442,-1.325199,-1.323009,41.46631,16.11711)"
+       cx="5.7434092"
+       cy="16.737026"
+       fx="5.7434092"
+       fy="16.737026"
+       r="9.975256" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3104"
+       id="radialGradient3344"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-1.30241,1.304442,-1.325199,-1.323009,41.46631,16.11711)"
+       cx="5.7434092"
+       cy="16.737026"
+       fx="5.7434092"
+       fy="16.737026"
+       r="9.975256" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3357"
+       id="linearGradient3363"
+       x1="12.549859"
+       y1="12.357164"
+       x2="13.745301"
+       y2="19.751348"
+       gradientUnits="userSpaceOnUse" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3378"
+       id="radialGradient3384"
+       cx="11.76924"
+       cy="25.560368"
+       fx="11.76924"
+       fy="25.560368"
+       r="9.8412299"
+       gradientTransform="matrix(1.6444693,0,0,1.4596828,-7.5849142,-16.236248)"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3390"
+       id="linearGradient3396"
+       x1="5.0167913"
+       y1="6.0080996"
+       x2="5.9832082"
+       y2="6.9919"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3398"
+       id="linearGradient3404"
+       x1="9.9935493"
+       y1="9.1751938"
+       x2="9.4590645"
+       y2="10.073978"
+       gradientUnits="userSpaceOnUse" />
+  </defs>
+  <sodipodi:namedview
+     inkscape:window-height="849"
+     inkscape:window-width="1440"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     guidetolerance="10.0"
+     gridtolerance="10.0"
+     objecttolerance="10.0"
+     borderopacity="1.0"
+     bordercolor="#666666"
+     pagecolor="#ffffff"
+     id="base"
+     showgrid="true"
+     inkscape:snap-bbox="true"
+     inkscape:snap-nodes="false"
+     inkscape:zoom="20.32932"
+     inkscape:cx="16.928433"
+     inkscape:cy="11.656784"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:current-layer="svg2">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3156"
+       visible="true"
+       enabled="true" />
+  </sodipodi:namedview>
+  <path
+     sodipodi:type="arc"
+     style="fill:#edd400;fill-opacity:1;stroke:url(#linearGradient3363);stroke-width:1.33355486000000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path3317"
+     sodipodi:cx="11.806158"
+     sodipodi:cy="10.983024"
+     sodipodi:rx="9.975256"
+     sodipodi:ry="9.975256"
+     d="M 21.781414,10.983024 A 9.975256,9.975256 0 1 1 1.8309021,10.983024 A 9.975256,9.975256 0 1 1 21.781414,10.983024 z"
+     transform="matrix(0.751736,0,0,0.751736,-0.8751143,-0.2563346)" />
+  <path
+     sodipodi:type="arc"
+     style="opacity:0.64044948999999995;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1.53465486000000007;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path3321"
+     sodipodi:cx="11.806158"
+     sodipodi:cy="10.983024"
+     sodipodi:rx="9.975256"
+     sodipodi:ry="9.975256"
+     d="M 21.781414,10.983024 A 9.975256,9.975256 0 1 1 1.8309021,10.983024 A 9.975256,9.975256 0 1 1 21.781414,10.983024 z"
+     transform="matrix(0.6516124,0,0,0.6516124,0.3069616,0.8433262)" />
+  <path
+     sodipodi:type="arc"
+     style="opacity:0.79545456;fill:url(#radialGradient3344);fill-opacity:1;stroke:none;stroke-width:1.05274069;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path3319"
+     sodipodi:cx="11.806158"
+     sodipodi:cy="10.983024"
+     sodipodi:rx="9.975256"
+     sodipodi:ry="9.975256"
+     d="M 21.781414,10.983024 A 9.975256,9.975256 0 1 1 1.8309021,10.983024 A 9.975256,9.975256 0 1 1 21.781414,10.983024 z"
+     transform="matrix(0.7017364,0,0,0.7017364,-0.2848107,0.2928127)" />
+  <path
+     style="fill:#eeeeec;fill-opacity:1;stroke:url(#linearGradient3396);stroke-width:0.99999987999999995;stroke-miterlimit:4;stroke-opacity:1"
+     d="M 7.5,6.5000001 C 7.5,7.6040001 6.6040006,8.5000002 5.5000006,8.5000002 C 4.3960003,8.5000002 3.4999999,7.6040001 3.4999999,6.5000001 C 3.4999999,5.3959999 4.3960003,4.5 5.5000006,4.5 C 6.6040006,4.5 7.5,5.3959999 7.5,6.5000001 z"
+     id="path3154" />
+  <path
+     sodipodi:type="arc"
+     style="fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:0.98640186;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path2172"
+     sodipodi:cx="9.7069349"
+     sodipodi:cy="9.6526775"
+     sodipodi:rx="1.0259361"
+     sodipodi:ry="1.9413869"
+     d="M 10.732871,9.6526775 A 1.0259361,1.9413869 0 1 1 8.6809988,9.6526775 A 1.0259361,1.9413869 0 1 1 10.732871,9.6526775 z"
+     transform="matrix(0.9747194,0,0,0.5150957,-3.4615376,2.0279472)" />
+  <path
+     sodipodi:type="arc"
+     style="fill:#eeeeec;fill-opacity:1;stroke:url(#linearGradient3404);stroke-width:0.70564497000000004;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path3152"
+     sodipodi:cx="9.7069349"
+     sodipodi:cy="9.6526775"
+     sodipodi:rx="1.0259361"
+     sodipodi:ry="1.9413869"
+     d="M 10.732871,9.6526775 A 1.0259361,1.9413869 0 1 1 8.6809988,9.6526775 A 1.0259361,1.9413869 0 1 1 10.732871,9.6526775 z"
+     transform="matrix(1.9494389,0,0,1.0301914,-8.4230764,-3.4441052)" />
+  <path
+     sodipodi:type="arc"
+     style="opacity:0.75;fill:none;fill-opacity:1;stroke:#ce5c00;stroke-width:0.85073602000000004;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path2259"
+     sodipodi:cx="8.3258924"
+     sodipodi:cy="9.2232141"
+     sodipodi:rx="1.2276785"
+     sodipodi:ry="1.7410715"
+     d="M 7.2133909,8.4869402 A 1.2276785,1.7410715 0 0 1 9.288462,8.1425499"
+     transform="matrix(1.4017776,0.3494647,-0.2648498,0.9196421,1.6361947,-7.2342213)"
+     sodipodi:start="3.5782199"
+     sodipodi:end="5.6135639"
+     sodipodi:open="true" />
+  <path
+     sodipodi:type="arc"
+     style="opacity:0.75;fill:none;fill-opacity:1;stroke:#ce5c00;stroke-width:0.85073620000000005;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path2261"
+     sodipodi:cx="8.3258924"
+     sodipodi:cy="9.2232141"
+     sodipodi:rx="1.2276785"
+     sodipodi:ry="1.7410715"
+     d="M 7.2133909,8.4869402 A 1.2276785,1.7410715 0 0 1 9.288462,8.1425499"
+     transform="matrix(-1.4017777,0.3494645,0.2648498,0.9196416,14.363806,-7.2342154)"
+     sodipodi:start="3.5782199"
+     sodipodi:end="5.6135639"
+     sodipodi:open="true" />
+  <path
+     sodipodi:type="arc"
+     style="fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:0.98640186;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path3346"
+     sodipodi:cx="9.7069349"
+     sodipodi:cy="9.6526775"
+     sodipodi:rx="1.0259361"
+     sodipodi:ry="1.9413869"
+     d="M 10.732871,9.6526775 A 1.0259361,1.9413869 0 1 1 8.6809988,9.6526775 A 1.0259361,1.9413869 0 1 1 10.732871,9.6526775 z"
+     transform="matrix(0.9747195,0,0,0.5150957,0.5384613,2.0279474)" />
+  <path
+     sodipodi:type="arc"
+     style="opacity:1;fill:none;fill-opacity:1;stroke:url(#radialGradient3384);stroke-width:2.21672367999999986;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="path3367"
+     sodipodi:cx="11.806158"
+     sodipodi:cy="10.983024"
+     sodipodi:rx="9.975256"
+     sodipodi:ry="9.975256"
+     d="M 20.502108,15.870372 A 9.975256,9.975256 0 0 1 3.0363724,15.736611"
+     transform="matrix(0.4511163,0,0,0.4511162,2.6740503,3.0453797)"
+     sodipodi:start="0.51202919"
+     sodipodi:end="2.6448802"
+     sodipodi:open="true" />
 </svg>
Binary file pidgin/pixmaps/tray/22/tray-away.png has changed
Binary file pidgin/pixmaps/tray/22/tray-busy.png has changed
Binary file pidgin/pixmaps/tray/22/tray-online.png has changed
--- a/pidgin/plugins/cap/cap.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/pidgin/plugins/cap/cap.c	Thu Nov 20 21:13:56 2008 +0000
@@ -333,7 +333,7 @@
 
 static gboolean max_message_difference_cb(gpointer data) {
 	CapStatistics *stats = data;
-	purple_debug_info("cap", "Max Message Difference timeout occured\n");
+	purple_debug_info("cap", "Max Message Difference timeout occurred\n");
 	insert_cap_failure(stats);
 	stats->timeout_source_id = 0;
 	return FALSE;
--- a/pidgin/plugins/perl/common/Makefile.mingw	Mon Aug 18 17:08:01 2008 +0000
+++ b/pidgin/plugins/perl/common/Makefile.mingw	Thu Nov 20 21:13:56 2008 +0000
@@ -5,13 +5,12 @@
 #
 
 PIDGIN_TREE_TOP := ../../../..
+GCCWARNINGS := -Wno-comment -Waggregate-return -Wcast-align -Wdeclaration-after-statement -Werror-implicit-function-declaration -Wextra -Wno-sign-compare -Wno-unused-parameter -Winit-self -Wmissing-declarations -Wmissing-prototypes -Wpointer-arith -Wundef -Wno-unused
 include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
 
 TARGET = Pidgin
 EXTUTILS ?= C:/perl/lib/ExtUtils
 
-CFLAGS += -Wno-comment -Wno-unused
-
 ##
 ## INCLUDE PATHS
 ##
@@ -72,7 +71,7 @@
 ##
 ## LIBRARIES
 ##
-LIBS =			-lperl58 \
+LIBS =			-lperl510 \
 			-lperl \
 			-lpurple \
 			-lpidgin \
--- a/pidgin/win32/nsis/pidgin-installer.nsi	Mon Aug 18 17:08:01 2008 +0000
+++ b/pidgin/win32/nsis/pidgin-installer.nsi	Thu Nov 20 21:13:56 2008 +0000
@@ -72,7 +72,7 @@
 !define GTK_MIN_VERSION				"2.6.10"
 !define GTK_REG_KEY				"SOFTWARE\GTK\2.0"
 !define PERL_REG_KEY				"SOFTWARE\Perl"
-!define PERL_DLL				"perl58.dll"
+!define PERL_DLL				"perl510.dll"
 !define GTK_DEFAULT_INSTALL_PATH		"$COMMONFILES\GTK\2.0"
 !define GTK_RUNTIME_INSTALLER			"..\..\..\..\gtk_installer\gtk-runtime*.exe"
 
@@ -376,7 +376,7 @@
     StrCmp $R0 "2" +2 ; Upgrade isn't optional
     MessageBox MB_YESNO $(GTK_UPGRADE_PROMPT) /SD IDYES IDNO done
     ClearErrors
-    ExecWait '"$TEMP\gtk-runtime.exe" /L=$LANGUAGE /S /D=$GTK_FOLDER'
+    ExecWait '"$TEMP\gtk-runtime.exe" /L=$LANGUAGE $ISSILENT /D=$GTK_FOLDER'
     IfErrors gtk_install_error done
 
     gtk_install_error:
@@ -505,11 +505,16 @@
     ; If this is under NT4, delete the SILC support stuff
     ; there is a bug that will prevent any account from connecting
     ; See https://lists.silcnet.org/pipermail/silc-devel/2005-January/001588.html
+    ; Also, remove the GSSAPI SASL plugin and associated files as they aren't
+    ; compatible with NT4.
     ${If} ${IsNT}
     ${AndIf} ${IsWinNT4}
+      ;SILC
       Delete "$INSTDIR\plugins\libsilc.dll"
       Delete "$INSTDIR\libsilcclient-1-1-2.dll"
       Delete "$INSTDIR\libsilc-1-1-2.dll"
+      ;GSSAPI
+      Delete "$INSTDIR\sasl2\saslGSSAPI.dll"
     ${EndIf}
 
     SetOutPath "$INSTDIR"
@@ -704,6 +709,8 @@
     Push "ymsgr"
     Call un.UnregisterURIHandler
 
+    Delete "$INSTDIR\ca-certs\CAcert_Class3.pem"
+    Delete "$INSTDIR\ca-certs\CAcert_Root.pem"
     Delete "$INSTDIR\ca-certs\Equifax_Secure_CA.pem"
     Delete "$INSTDIR\ca-certs\GTE_CyberTrust_Global_Root.pem"
     Delete "$INSTDIR\ca-certs\Microsoft_Secure_Server_Authority.pem"
@@ -1304,12 +1311,12 @@
   ;Reset ShellVarContext because we may have changed it
   SetShellVarContext "current"
 
-  StrCpy $ISSILENT "/NOUI"
+  StrCpy $ISSILENT "/S"
 
   ; GTK installer has two silent states.. one with Message boxes, one without
   ; If pidgin installer was run silently, we want to supress gtk installer msg boxes.
   IfSilent 0 set_gtk_normal
-      StrCpy $ISSILENT "/S"
+      StrCpy $ISSILENT "/NOUI"
   set_gtk_normal:
 
   ${GetParameters} $R0
@@ -1388,67 +1395,6 @@
 
 FunctionEnd
 
-; This is a modified StartRadioButtons (from Sections.nsh)
-; The only difference is that it allows for nothing in the group to be selected
-; In that case, the default variable should be set to ""
-!macro StartRadioButtonsUnselectable var
-
-  !define StartRadioButtons_Var "${var}"
-
-  Push $R0
-  Push $R1
-
-   ;If we have no selection, don't try to unselect it
-   StrCmp "${StartRadioButtons_Var}" "" +4
-   SectionGetFlags "${StartRadioButtons_Var}" $R0
-   IntOp $R1 $R0 & ${SF_SELECTED}
-   IntOp $R0 $R0 & ${SECTION_OFF}
-   SectionSetFlags "${StartRadioButtons_Var}" $R0
-
-   ; If the previous value isn't currently selected,
-   ; we don't want to select it at the end
-   IntCmp $R1 ${SF_SELECTED} +2
-   StrCpy "${StartRadioButtons_Var}" ""
-
-   StrCpy $R1 "${StartRadioButtons_Var}"
-
-!macroend
-
-Function .onSelChange
-  Push $0
-  Push $1
-  Push $2
-
-  ; Check that at most one of the non-readonly spelling dictionaries are selected
-  ; We can't use $R0 or $R1 in this block since they're used in the macros
-  !insertmacro StartRadioButtonsUnselectable $SPELLCHECK_SEL
-    ; Start with the first language dictionary
-    IntOp $2 ${SecSpellCheck} + 1
-
-    start_spellcheck_radio:
-    SectionGetFlags $2 $0
-
-    IntOp $1 $0 & ${SF_SECGRPEND}
-    ; If it is the end of the section group, stop
-    IntCmp $1 ${SF_SECGRPEND} end_spellcheck_radio
-
-    IntOp $0 $0 & ${SF_RO}
-    IntCmp $0 ${SF_RO} after_button_insert
-    ; If !readonly, then it is part of the radiobutton group
-    !insertmacro RadioButton $2
-    after_button_insert:
-
-    IntOp $2 $2 + 1 ;Advance to the next section
-    Goto start_spellcheck_radio
-
-    end_spellcheck_radio:
-  !insertmacro EndRadioButtons
-
-  Pop $2
-  Pop $1
-  Pop $0
-FunctionEnd
-
 ; Page enter and exit functions..
 
 Function preWelcomePage
--- a/pidgin/win32/winpidgin.c	Mon Aug 18 17:08:01 2008 +0000
+++ b/pidgin/win32/winpidgin.c	Thu Nov 20 21:13:56 2008 +0000
@@ -89,9 +89,8 @@
 			const char *err_msg = get_win32_error_message(retv);
 
 			printf("Could not read reg key '%s' subkey '%s' value: '%s'.\nMessage: (%ld) %s\n",
-					((key == HKEY_LOCAL_MACHINE) ? "HKLM" :
-					 (key == HKEY_CURRENT_USER) ? "HKCU" :
-					 "???"),
+					(key == HKEY_LOCAL_MACHINE) ? "HKLM"
+					 : ((key == HKEY_CURRENT_USER) ? "HKCU" : "???"),
 					sub_key, val_name, retv, err_msg);
 		}
 		RegCloseKey(hkey);
@@ -216,13 +215,13 @@
 
 	/* Set up the settings dir base to be \\path\to
 	 * The actual settings dir will be \\path\to\.purple */
-	snprintf(path2, sizeof(path2), "PURPLEHOME=%s", path);
+	_snprintf(path2, sizeof(path2), "PURPLEHOME=%s", path);
 	printf("Setting settings dir: %s\n", path2);
-	putenv(path2);
+	_putenv(path2);
 
-	snprintf(path2, sizeof(path2), "PIDGIN_ASPELL_DIR=%s\\Aspell\\bin", path);
+	_snprintf(path2, sizeof(path2), "PIDGIN_ASPELL_DIR=%s\\Aspell\\bin", path);
 	printf("%s\n", path2);
-	putenv(path2);
+	_putenv(path2);
 
 	/* set the GTK+ path to be \\path\to\GTK\bin */
 	strcat(path, "\\GTK\\bin");
@@ -437,9 +436,82 @@
 
 	locale = winpidgin_get_locale();
 
-	snprintf(envstr, 25, "LANG=%s", locale);
+	_snprintf(envstr, 25, "LANG=%s", locale);
 	printf("Setting locale: %s\n", envstr);
-	putenv(envstr);
+	_putenv(envstr);
+}
+
+
+static void winpidgin_add_stuff_to_path() {
+	char perl_path[MAX_PATH + 1];
+	char *ppath = NULL;
+	char mit_kerberos_path[MAX_PATH + 1];
+	char *mpath = NULL;
+	DWORD plen;
+
+	printf("%s", "Looking for Perl... ");
+
+	plen = sizeof(perl_path);
+	if (read_reg_string(HKEY_LOCAL_MACHINE, "SOFTWARE\\Perl", "",
+			    (LPBYTE) &perl_path, &plen)) {
+		/* We *could* check for perl510.dll, but it seems unnecessary. */
+		printf("found in '%s'.\n", perl_path);
+
+		if (perl_path[strlen(perl_path) - 1] != '\\')
+			strcat(perl_path, "\\");
+		strcat(perl_path, "bin");
+
+		ppath = perl_path;
+	} else
+		printf("%s", "not found.\n");
+
+	printf("%s", "Looking for MIT Kerberos... ");
+
+	plen = sizeof(mit_kerberos_path);
+	if (read_reg_string(HKEY_LOCAL_MACHINE, "SOFTWARE\\MIT\\Kerberos", "InstallDir",
+			    (LPBYTE) &mit_kerberos_path, &plen)) {
+		/* We *could* check for gssapi32.dll */
+		printf("found in '%s'.\n", mit_kerberos_path);
+
+		if (mit_kerberos_path[strlen(mit_kerberos_path) - 1] != '\\')
+			strcat(mit_kerberos_path, "\\");
+		strcat(mit_kerberos_path, "bin");
+
+		mpath = mit_kerberos_path;
+	} else
+		printf("%s", "not found.\n");
+
+	if (ppath != NULL || mpath != NULL) {
+		const char *path = getenv("PATH");
+		BOOL add_ppath = ppath != NULL && (path == NULL || !strstr(path, ppath));
+		BOOL add_mpath = mpath != NULL && (path == NULL || !strstr(path, mpath));
+		char *newpath;
+		int newlen;
+
+		if (add_ppath || add_mpath) {
+			/* Enough to add "PATH=" + path + ";"  + ppath + ";" + mpath + \0 */
+			newlen = 6 + (path ? strlen(path) + 1 : 0);
+			if (add_ppath)
+				newlen += strlen(ppath) + 1;
+			if (add_mpath)
+				newlen += strlen(mpath) + 1;
+			newpath = malloc(newlen);
+			*newpath = '\0';
+
+			_snprintf(newpath, newlen, "PATH=%s%s%s%s%s%s",
+				  path ? path : "",
+				  path ? ";" : "",
+				  add_ppath ? ppath : "",
+				  add_ppath ? ";" : "",
+				  add_mpath ? mpath : "",
+				  add_mpath ? ";" : "");
+
+			printf("New PATH: %s\n", newpath);
+
+			_putenv(newpath);
+			free(newpath);
+		}
+	}
 }
 
 #define PIDGIN_WM_FOCUS_REQUEST (WM_APP + 13)
@@ -598,10 +670,10 @@
 	} else {
 		DWORD dw = GetLastError();
 		const char *err_msg = get_win32_error_message(dw);
-		snprintf(errbuf, 512,
+		_snprintf(errbuf, 512,
 			"Error getting module filename.\nError: (%u) %s",
 			(UINT) dw, err_msg);
-		printf("%s", errbuf);
+		printf("%s\n", errbuf);
 		MessageBox(NULL, errbuf, NULL, MB_OK | MB_TOPMOST);
 		pidgin_dir[0] = '\0';
 	}
@@ -631,6 +703,9 @@
 		dll_prep();
 
 	winpidgin_set_locale();
+
+	winpidgin_add_stuff_to_path();
+
 	/* If help, version or multiple flag used, do not check Mutex */
 	if (!strstr(lpszCmdLine, "-h") && !strstr(lpszCmdLine, "-v"))
 		if (!winpidgin_set_running(getenv("PIDGIN_MULTI_INST") == NULL && strstr(lpszCmdLine, "-m") == NULL))
@@ -645,11 +720,11 @@
 		BOOL mod_not_found = (dw == ERROR_MOD_NOT_FOUND || dw == ERROR_DLL_NOT_FOUND);
 		const char *err_msg = get_win32_error_message(dw);
 
-		snprintf(errbuf, 512, "Error loading pidgin.dll.\nError: (%u) %s%s%s",
+		_snprintf(errbuf, 512, "Error loading pidgin.dll.\nError: (%u) %s%s%s",
 			(UINT) dw, err_msg,
 			mod_not_found ? "\n" : "",
 			mod_not_found ? "This probably means that GTK+ can't be found." : "");
-		printf("%s", errbuf);
+		printf("%s\n", errbuf);
 		MessageBox(NULL, errbuf, TEXT("Error"), MB_OK | MB_TOPMOST);
 
 		return 0;
--- a/po/POTFILES.in	Mon Aug 18 17:08:01 2008 +0000
+++ b/po/POTFILES.in	Thu Nov 20 21:13:56 2008 +0000
@@ -143,12 +143,9 @@
 libpurple/protocols/qq/keep_alive.c
 libpurple/protocols/qq/login_logout.c
 libpurple/protocols/qq/qq.c
-libpurple/protocols/qq/qq_proxy.c
-libpurple/protocols/qq/recv_core.c
+libpurple/protocols/qq/qq_network.c
 libpurple/protocols/qq/send_file.c
-libpurple/protocols/qq/sendqueue.c
 libpurple/protocols/qq/sys_msg.c
-libpurple/protocols/qq/udp_proxy_s5.c
 libpurple/protocols/sametime/sametime.c
 libpurple/protocols/silc/buddy.c
 libpurple/protocols/silc/chat.c
--- a/po/de.po	Mon Aug 18 17:08:01 2008 +0000
+++ b/po/de.po	Thu Nov 20 21:13:56 2008 +0000
@@ -7,13 +7,12 @@
 #
 # This file is distributed under the same license as the Pidgin package.
 #
-# Bjoern Voigt <bjoern@cs.tu-berlin.de>, 2008.
 msgid ""
 msgstr ""
 "Project-Id-Version: de\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-06-09 20:56+0200\n"
-"PO-Revision-Date: 2008-06-09 20:55+0200\n"
+"POT-Creation-Date: 2008-07-11 17:26+0200\n"
+"PO-Revision-Date: 2008-07-11 17:25+0200\n"
 "Last-Translator: Jochen Kemnade <jochenkemnade@web.de>\n"
 "Language-Team: Deutsch <de@li.org>\n"
 "MIME-Version: 1.0\n"
@@ -622,7 +621,6 @@
 msgid "Invite"
 msgstr "Einladen"
 
-#, fuzzy
 msgid ""
 "Please enter the name of the user you wish to invite,\n"
 "along with an optional invite message."
@@ -3234,10 +3232,8 @@
 msgstr "Falscher Modus"
 
 #, c-format
-msgid "Ban on %s by %s, set %ld second ago"
-msgid_plural "Ban on %s by %s, set %ld seconds ago"
-msgstr[0] "Verbot zu %s von %s, gesetzt vor %ld Sekunde"
-msgstr[1] "Verbot zu %s von %s, gesetzt vor %ld Sekunden"
+msgid "Ban on %s by %s, set %s ago"
+msgstr "Verbot zu %s von %s, gesetzt vor %s"
 
 #, c-format
 msgid "Ban on %s"
@@ -6174,11 +6170,11 @@
 msgid "In local permit/deny"
 msgstr "In lokaler erlaubt/verboten-Liste"
 
-msgid "Too evil (sender)"
-msgstr "Zu boshaft (Sender)"
-
-msgid "Too evil (receiver)"
-msgstr "Zu boshaft (Empfänger)"
+msgid "Warning level too high (sender)"
+msgstr "Warnstufe zu hoch (Absender)"
+
+msgid "Warning level too high (receiver)"
+msgstr "Warnstufe zu hoch (Empfänger)"
 
 msgid "User temporarily unavailable"
 msgstr "Benutzer ist temporär nicht verfügbar"
@@ -6282,9 +6278,8 @@
 msgid "Camera"
 msgstr "Kamera"
 
-#, fuzzy
 msgid "Screen Sharing"
-msgstr "Screen Sharing"
+msgstr "Gemeinsamer Bildschirm"
 
 msgid "Free For Chat"
 msgstr "Bereit zum Chatten"
@@ -6372,7 +6367,6 @@
 "versuchen Sie es noch einmal. Wenn Sie es weiterversuchen, müssen Sie sogar "
 "noch länger warten."
 
-#. client too old
 #, c-format
 msgid "The client version you are using is too old. Please upgrade at %s"
 msgstr ""
@@ -6538,20 +6532,26 @@
 "überschritten wurde."
 
 #, c-format
-msgid "You missed %hu message from %s because he/she was too evil."
-msgid_plural "You missed %hu messages from %s because he/she was too evil."
+msgid ""
+"You missed %hu message from %s because his/her warning level is too high."
+msgid_plural ""
+"You missed %hu messages from %s because his/her warning level is too high."
 msgstr[0] ""
-"Sie haben %hu Nachricht von %s nicht erhalten, da er/sie zu boshaft war."
+"Sie haben %hu Nachricht von %s nicht erhalten, da seine/ihre Warnstufe zu "
+"hoch ist."
 msgstr[1] ""
-"Sie haben %hu Nachrichten von %s nicht erhalten,/sie zu boshaft war."
-
-#, c-format
-msgid "You missed %hu message from %s because you are too evil."
-msgid_plural "You missed %hu messages from %s because you are too evil."
+"Sie haben %hu Nachrichten von %s nicht erhalten, da seine/ihre Warnstufe zu "
+"hoch ist."
+
+#, c-format
+msgid "You missed %hu message from %s because your warning level is too high."
+msgid_plural ""
+"You missed %hu messages from %s because your warning level is too high."
 msgstr[0] ""
-"Sie haben %hu Nachricht von %s nicht erhalten, da Sie zu boshaft sind."
+"Sie haben %hu Nachricht von %s nicht erhalten, da Ihre Warnstufe zu hoch ist."
 msgstr[1] ""
-"Sie haben %hu Nachrichten von %s nicht erhalten, da Sie zu boshaft sind."
+"Sie haben %hu Nachrichten von %s nicht erhalten, da Ihre Warnstufe zu hoch "
+"ist."
 
 #, c-format
 msgid "You missed %hu message from %s for an unknown reason."
@@ -7333,10 +7333,6 @@
 msgid "Unable to login"
 msgstr "Anmeldung fehlgeschlagen"
 
-#. we didn't successfully connect. tdt->toc_fd is valid here
-msgid "Unable to connect."
-msgstr "Verbindung nicht möglich."
-
 #, c-format
 msgid "Unknown-%d"
 msgstr "Unbekannt-%d"
@@ -7362,12 +7358,16 @@
 msgstr "<b>Letzte Aktualisierung</b>: %s<br>\n"
 
 #, c-format
+msgid "<b>Server</b>: %s: %d<br>\n"
+msgstr "<b>Server</b>: %s: %d<br>\n"
+
+#, c-format
 msgid "<b>Connection Mode</b>: %s<br>\n"
 msgstr "<b>Verbindungsmodus</b>: %s<br>\n"
 
 #, c-format
-msgid "<b>Server IP</b>: %s: %d<br>\n"
-msgstr "<b>Server-IP</b>: %s: %d<br>\n"
+msgid "<b>Real hostname</b>: %s: %d<br>\n"
+msgstr "<b>Wirklicher Hostname</b>: %s: %d<br>\n"
 
 #, c-format
 msgid "<b>My Public IP</b>: %s<br>\n"
@@ -7419,12 +7419,49 @@
 msgid "Connect using TCP"
 msgstr "Über TCP verbinden"
 
+msgid "Failed to connect server"
+msgstr "Verbinden zum Server fehlgeschlagen"
+
 msgid "Socket error"
 msgstr "Socket-Fehler"
 
+#, c-format
+msgid ""
+"Lost connection with server:\n"
+"%d, %s"
+msgstr ""
+"Verbindung zum Server verloren:\n"
+"%d, %s"
+
 msgid "Unable to read from socket"
 msgstr "Socket kann nicht gelesen werden"
 
+msgid "Write Error"
+msgstr "Schreibfehler"
+
+msgid "Connection lost"
+msgstr "Verbindung verloren"
+
+msgid "Login failed, no reply"
+msgstr "Anmeldung fehlgeschlagen, keine Antwort"
+
+msgid "Couldn't resolve host"
+msgstr "Kann den Hostnamen nicht auflösen"
+
+msgid "hostname is NULL or port is 0"
+msgstr "Hostname ist NULL oder Port ist 0"
+
+#, c-format
+msgid "Connecting server %s, retries %d"
+msgstr "Verbinde zu Server %s, %d Wiederholungen"
+
+#. we didn't successfully connect. tdt->toc_fd is valid here
+msgid "Unable to connect."
+msgstr "Verbindung nicht möglich."
+
+msgid "Could not resolve hostname"
+msgstr "Konnte den Hostnamen nicht auflösen"
+
 #, c-format
 msgid "%d has declined the file %s"
 msgstr "%d hat die Datei %s abgelehnt"
@@ -7436,12 +7473,6 @@
 msgid "%d canceled the transfer of %s"
 msgstr "%d hat die Übertragung von %s abgebrochen"
 
-msgid "Connection lost"
-msgstr "Verbindung verloren"
-
-msgid "Login failed, no reply"
-msgstr "Anmeldung fehlgeschlagen, keine Antwort"
-
 msgid "Do you want to add this buddy?"
 msgstr "Möchten Sie diesen Buddy hinzufügen?"
 
@@ -8499,6 +8530,10 @@
 msgid "Error loading SILC key pair"
 msgstr "Fehler beim Laden des SILC-Schlüsselpaares"
 
+#, c-format
+msgid "Download %s: %s"
+msgstr "Download %s: %s"
+
 msgid "Your Current Mood"
 msgstr "Ihre momentane Stimmung"
 
@@ -8943,12 +8978,6 @@
 msgid "Could not create listen socket"
 msgstr "Kann Listen-Socket nicht erstellen"
 
-msgid "Couldn't resolve host"
-msgstr "Kann den Hostnamen nicht auflösen"
-
-msgid "Could not resolve hostname"
-msgstr "Konnte den Hostnamen nicht auflösen"
-
 msgid "SIP usernames may not contain whitespaces or @ symbols"
 msgstr "SIP-Benutzernamen dürfen keine Leerzeichen oder @-Symbole enthalten"
 
@@ -9395,9 +9424,6 @@
 msgid "%s is trying to send you a group of %d files.\n"
 msgstr "%s versucht, Ihnen eine Gruppe von %d Dateien zu senden.\n"
 
-msgid "Write Error"
-msgstr "Schreibfehler"
-
 msgid "Yahoo! Japan Profile"
 msgstr "Yahoo!-Japan-Profil"
 
@@ -9760,7 +9786,6 @@
 msgid "Stored Image"
 msgstr "Gespeichertes Bild"
 
-#, fuzzy
 msgid "Stored Image. (that'll have to do for now)"
 msgstr "Gespeichertes Bild. (Das muss erstmal reichen)"
 
@@ -10263,9 +10288,6 @@
 msgid "/Tools/_Certificates"
 msgstr "/Werkzeuge/_Zertifikate"
 
-msgid "/Tools/Smile_y"
-msgstr "/Werkzeuge/Smile_y"
-
 msgid "/Tools/Plu_gins"
 msgstr "/Werkzeuge/Plu_gins"
 
@@ -10275,6 +10297,9 @@
 msgid "/Tools/Pr_ivacy"
 msgstr "/Werkzeuge/Pri_vatsphäre"
 
+msgid "/Tools/Smile_y"
+msgstr "/Werkzeuge/Smile_y"
+
 msgid "/Tools/_File Transfers"
 msgstr "/Werkzeuge/_Dateiübertragungen"
 
@@ -10508,6 +10533,12 @@
 msgid "Please enter the name of the group to be added."
 msgstr "Bitte geben Sie den Namen der Gruppe ein, die hinzugefügt werden soll."
 
+msgid "Enable Account"
+msgstr "Konten aktivieren"
+
+msgid "<PurpleMain>/Accounts/Enable Account"
+msgstr "<PurpleMain>/Konten/Konto aktivieren"
+
 msgid "<PurpleMain>/Accounts/"
 msgstr "<PurpleMain>/Konten/"
 
@@ -10520,12 +10551,6 @@
 msgid "_Disable"
 msgstr "_Deaktivieren"
 
-msgid "Enable Account"
-msgstr "Konten aktivieren"
-
-msgid "<PurpleMain>/Accounts/Enable Account"
-msgstr "<PurpleMain>/Konten/Konto aktivieren"
-
 msgid "/Tools"
 msgstr "/Werkzeuge"
 
@@ -11440,9 +11465,8 @@
 msgid "Color to draw the name of an action message."
 msgstr "Farbe, mit der der Name in einer Aktions-Nachricht dargestellt wird."
 
-#, fuzzy
 msgid "Action Message Name Color for Whispered Message"
-msgstr "Farbe des Absendernamens für Aktions-Nachrichten"
+msgstr "Farbe des Absendernamens für geflüsterte Aktions-Nachrichten"
 
 msgid "Whisper Message Name Color"
 msgstr "Farbe des Absendernamens für Flüster-Nachrichten"
@@ -11517,7 +11541,6 @@
 msgid "_Save Image..."
 msgstr "Bild _speichern..."
 
-#, fuzzy
 msgid "_Add Custom Smiley..."
 msgstr "Benutzerdefinierten Smiley _hinzufügen..."
 
@@ -11559,16 +11582,18 @@
 msgid "Insert Image"
 msgstr "Bild einfügen"
 
-msgid ""
-"This smiley is disabled because a custom smiley exists for this shortcut."
+#, c-format
+msgid ""
+"This smiley is disabled because a custom smiley exists for this shortcut:\n"
+" %s"
 msgstr ""
 "Dieser Smiley ist deaktiviert, da ein benutzerdefinierter Smiley für diese "
-"Tastenkombination existiert."
+"Tastenkombination existiert:\n"
+" %s"
 
 msgid "Smile!"
 msgstr "Lächeln!"
 
-#, fuzzy
 msgid "_Manage custom smileys"
 msgstr "Benutzerdefinierte Smileys _verwalten"
 
--- a/po/en_GB.po	Mon Aug 18 17:08:01 2008 +0000
+++ b/po/en_GB.po	Thu Nov 20 21:13:56 2008 +0000
@@ -2911,7 +2911,7 @@
 msgstr "Your current password is different from the one that you specified."
 
 msgid "Unable to change password. Error occurred.\n"
-msgstr "Unable to change password. An error occured.\n"
+msgstr "Unable to change password. An error occurred.\n"
 
 msgid "Change password for the Gadu-Gadu account"
 msgstr "Change password for the Gadu-Gadu account"
@@ -5270,7 +5270,7 @@
 msgstr "Message could not be sent because the user is offline:"
 
 msgid "Message could not be sent because a connection error occurred:"
-msgstr "Message could not be sent because a connection error occured:"
+msgstr "Message could not be sent because a connection error occurred:"
 
 msgid "Message could not be sent because we are sending too quickly:"
 msgstr "Message could not be sent because we are sending too quickly:"
--- a/po/it.po	Mon Aug 18 17:08:01 2008 +0000
+++ b/po/it.po	Thu Nov 20 21:13:56 2008 +0000
@@ -3833,8 +3833,8 @@
 msgstr "Impossibile trovare un'installazione di ActiveTCL. Se vuoi usare i plugin TCL, installa ActiveTCL da http://www.activestate.com\n"
 
 #: ../libpurple/protocols/bonjour/bonjour.c:107
-msgid "The Apple Bonjour For Windows toolkit wasn't found, see the FAQ at: http://developer.pidgin.im/wiki/Using%20Pidgin#CanIusePidginforBonjourLink-LocalMessaging for more information."
-msgstr "Il toolkit Apple Bonjour per Windows non è stato trovato. Leggi le FAQ su: http://developer.pidgin.im/wiki/Using%20Pidgin#CanIusePidginforBonjourLink-LocalMessaging per maggiori informazioni."
+msgid "The Apple Bonjour For Windows toolkit wasn't found, see the FAQ at: http://d.pidgin.im/BonjourWindows for more information."
+msgstr "Il toolkit Apple Bonjour per Windows non è stato trovato. Leggi le FAQ su: http://d.pidgin.im/BonjourWindows per maggiori informazioni."
 
 #: ../libpurple/protocols/bonjour/bonjour.c:126
 msgid "Unable to listen for incoming IM connections\n"
--- a/po/nb.po	Mon Aug 18 17:08:01 2008 +0000
+++ b/po/nb.po	Thu Nov 20 21:13:56 2008 +0000
@@ -3877,7 +3877,7 @@
 msgstr "Kunne ikke finne en ActiveTCL installasjon. Om du ønsker å bruke TCL tillegg, installer ActiveTCL fra http://www.activestate.com\n"
 
 #: ../libpurple/protocols/bonjour/bonjour.c:101
-msgid "The Apple Bonjour For Windows toolkit wasn't found, see the FAQ at: http://developer.pidgin.im/wiki/Using%20Pidgin#CanIusePidginforBonjourLink-LocalMessaging for more information."
+msgid "The Apple Bonjour For Windows toolkit wasn't found, see the FAQ at: http://d.pidgin.im/BonjourWindows for more information."
 msgstr ""
 
 #: ../libpurple/protocols/bonjour/bonjour.c:120
--- a/po/te.po	Mon Aug 18 17:08:01 2008 +0000
+++ b/po/te.po	Thu Nov 20 21:13:56 2008 +0000
@@ -4059,7 +4059,7 @@
 msgstr "ActiveTCL ఇన్స్టాలేషన్ ను కనుగొనడంలో అశక్తత. మీరు TCL ప్లగ్ ఇన్లను ఉపయోగించాలనుకుంటే http://www.activestate.com\n"
 
 #: ../libpurple/protocols/bonjour/bonjour.c:108
-msgid "The Apple Bonjour For Windows toolkit wasn't found, see the FAQ at: http://developer.pidgin.im/wiki/Using%20Pidgin#CanIusePidginforBonjourLink-LocalMessaging for more information."
+msgid "The Apple Bonjour For Windows toolkit wasn't found, see the FAQ at: http://d.pidgin.im/BonjourWindows for more information."
 msgstr ""
 
 #: ../libpurple/protocols/bonjour/bonjour.c:127
--- a/po/ur.po	Mon Aug 18 17:08:01 2008 +0000
+++ b/po/ur.po	Thu Nov 20 21:13:56 2008 +0000
@@ -3891,7 +3891,7 @@
 msgstr "ایكٹیو TCL ا نسٹالیشن كی  جانچ  نا ممكن۔ اگر آپ TCL پلگ انس  استعمال كرنا چاہتے ہیں ، ایكٹیوTCL انسٹال كروfrom http://www.activestate.com\n"
 
 #: ../libpurple/protocols/bonjour/bonjour.c:108
-msgid "The Apple Bonjour For Windows toolkit wasn't found, see the FAQ at: http://developer.pidgin.im/wiki/Using%20Pidgin#CanIusePidginforBonjourLink-LocalMessaging for more information."
+msgid "The Apple Bonjour For Windows toolkit wasn't found, see the FAQ at: http://d.pidgin.im/BonjourWindows for more information."
 msgstr ""
 
 #: ../libpurple/protocols/bonjour/bonjour.c:127
--- a/po/vi.po	Mon Aug 18 17:08:01 2008 +0000
+++ b/po/vi.po	Thu Nov 20 21:13:56 2008 +0000
@@ -3916,7 +3916,7 @@
 "The Apple Bonjour For Windows toolkit wasn't found, see the FAQ at: http://"
 "developer.pidgin.im/wiki/Using%20Pidgin#CanIusePidginforBonjourLink-"
 "LocalMessaging for more information."
-msgstr "Không tìm thấy bộ công cụ Apple Bonjour For Windows, xem FAQ (Hỏi Đáp) ở địa chỉ « http://developer.pidgin.im/wiki/Using%20Pidgin#CanIusePidginforBonjourLink-LocalMessaging » để tìm chi tiết."
+msgstr "Không tìm thấy bộ công cụ Apple Bonjour For Windows, xem FAQ (Hỏi Đáp) ở địa chỉ « http://d.pidgin.im/BonjourWindows » để tìm chi tiết."
 
 #: ../libpurple/protocols/bonjour/bonjour.c:120
 msgid "Unable to listen for incoming IM connections\n"
--- a/po/zh_CN.po	Mon Aug 18 17:08:01 2008 +0000
+++ b/po/zh_CN.po	Thu Nov 20 21:13:56 2008 +0000
@@ -17454,3 +17454,8 @@
 msgid "This plugin is useful for debbuging XMPP servers or clients."
 msgstr "此插件用于调试 XMPP 服务器或客户端。"
 
+#. feel free to not translate this
+#: ../pidgin/gtkdialogs.c:77
+msgid "Ka-Hing Cheung"
+msgstr "张家兴"
+
--- a/po/zh_TW.po	Mon Aug 18 17:08:01 2008 +0000
+++ b/po/zh_TW.po	Thu Nov 20 21:13:56 2008 +0000
@@ -13684,6 +13684,11 @@
 msgid "This plugin is useful for debbuging XMPP servers or clients."
 msgstr "幫助為 XMPP 伺服器或客戶端進行除錯。"
 
+#. feel free to not translate this
+#: ../pidgin/gtkdialogs.c:77
+msgid "Ka-Hing Cheung"
+msgstr "張家興"
+
 #~ msgid "_Resume"
 #~ msgstr "恢復(_R)"
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/ca-certs/CAcert_Class3.pem	Thu Nov 20 21:13:56 2008 +0000
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIGCDCCA/CgAwIBAgIBATANBgkqhkiG9w0BAQQFADB5MRAwDgYDVQQKEwdSb290
+IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB
+IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA
+Y2FjZXJ0Lm9yZzAeFw0wNTEwMTQwNzM2NTVaFw0zMzAzMjgwNzM2NTVaMFQxFDAS
+BgNVBAoTC0NBY2VydCBJbmMuMR4wHAYDVQQLExVodHRwOi8vd3d3LkNBY2VydC5v
+cmcxHDAaBgNVBAMTE0NBY2VydCBDbGFzcyAzIFJvb3QwggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQCrSTURSHzSJn5TlM9Dqd0o10Iqi/OHeBlYfA+e2ol9
+4fvrcpANdKGWZKufoCSZc9riVXbHF3v1BKxGuMO+f2SNEGwk82GcwPKQ+lHm9WkB
+Y8MPVuJKQs/iRIwlKKjFeQl9RrmK8+nzNCkIReQcn8uUBByBqBSzmGXEQ+xOgo0J
+0b2qW42S0OzekMV/CsLj6+YxWl50PpczWejDAz1gM7/30W9HxM3uYoNSbi4ImqTZ
+FRiRpoWSR7CuSOtttyHshRpocjWr//AQXcD0lKdq1TuSfkyQBX6TwSyLpI5idBVx
+bgtxA+qvFTia1NIFcm+M+SvrWnIl+TlG43IbPgTDZCciECqKT1inA62+tC4T7V2q
+SNfVfdQqe1z6RgRQ5MwOQluM7dvyz/yWk+DbETZUYjQ4jwxgmzuXVjit89Jbi6Bb
+6k6WuHzX1aCGcEDTkSm3ojyt9Yy7zxqSiuQ0e8DYbF/pCsLDpyCaWt8sXVJcukfV
+m+8kKHA4IC/VfynAskEDaJLM4JzMl0tF7zoQCqtwOpiVcK01seqFK6QcgCExqa5g
+eoAmSAC4AcCTY1UikTxW56/bOiXzjzFU6iaLgVn5odFTEcV7nQP2dBHgbbEsPyyG
+kZlxmqZ3izRg0RS0LKydr4wQ05/EavhvE/xzWfdmQnQeiuP43NJvmJzLR5iVQAX7
+6QIDAQABo4G/MIG8MA8GA1UdEwEB/wQFMAMBAf8wXQYIKwYBBQUHAQEEUTBPMCMG
+CCsGAQUFBzABhhdodHRwOi8vb2NzcC5DQWNlcnQub3JnLzAoBggrBgEFBQcwAoYc
+aHR0cDovL3d3dy5DQWNlcnQub3JnL2NhLmNydDBKBgNVHSAEQzBBMD8GCCsGAQQB
+gZBKMDMwMQYIKwYBBQUHAgEWJWh0dHA6Ly93d3cuQ0FjZXJ0Lm9yZy9pbmRleC5w
+aHA/aWQ9MTAwDQYJKoZIhvcNAQEEBQADggIBAH8IiKHaGlBJ2on7oQhy84r3HsQ6
+tHlbIDCxRd7CXdNlafHCXVRUPIVfuXtCkcKZ/RtRm6tGpaEQU55tiKxzbiwzpvD0
+nuB1wT6IRanhZkP+VlrRekF490DaSjrxC1uluxYG5sLnk7mFTZdPsR44Q4Dvmw2M
+77inYACHV30eRBzLI++bPJmdr7UpHEV5FpZNJ23xHGzDwlVks7wU4vOkHx4y/CcV
+Bc/dLq4+gmF78CEQGPZE6lM5+dzQmiDgxrvgu1pPxJnIB721vaLbLmINQjRBvP+L
+ivVRIqqIMADisNS8vmW61QNXeZvo3MhN+FDtkaVSKKKs+zZYPumUK5FQhxvWXtaM
+zPcPEAxSTtAWYeXlCmy/F8dyRlecmPVsYGN6b165Ti/Iubm7aoW8mA3t+T6XhDSU
+rgCvoeXnkm5OvfPi2RSLXNLrAWygF6UtEOucekq9ve7O/e0iQKtwOIj1CodqwqsF
+YMlIBdpTwd5Ed2qz8zw87YC8pjhKKSRf/lk7myV6VmMAZLldpGJ9VzZPrYPvH5JT
+oI53V93lYRE9IwCQTDz6o2CTBKOvNfYOao9PSmCnhQVsRqGP9Md246FZV/dxssRu
+FFxtbUFm3xuTsdQAw+7Lzzw9IYCpX2Nl/N3gX6T0K/CFcUHUZyX7GrGXrtaZghNB
+0m6lG5kngOcLqagA
+-----END CERTIFICATE-----
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/ca-certs/CAcert_Root.pem	Thu Nov 20 21:13:56 2008 +0000
@@ -0,0 +1,41 @@
+-----BEGIN CERTIFICATE-----
+MIIHPTCCBSWgAwIBAgIBADANBgkqhkiG9w0BAQQFADB5MRAwDgYDVQQKEwdSb290
+IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB
+IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA
+Y2FjZXJ0Lm9yZzAeFw0wMzAzMzAxMjI5NDlaFw0zMzAzMjkxMjI5NDlaMHkxEDAO
+BgNVBAoTB1Jvb3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEi
+MCAGA1UEAxMZQ0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJ
+ARYSc3VwcG9ydEBjYWNlcnQub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
+CgKCAgEAziLA4kZ97DYoB1CW8qAzQIxL8TtmPzHlawI229Z89vGIj053NgVBlfkJ
+8BLPRoZzYLdufujAWGSuzbCtRRcMY/pnCujW0r8+55jE8Ez64AO7NV1sId6eINm6
+zWYyN3L69wj1x81YyY7nDl7qPv4coRQKFWyGhFtkZip6qUtTefWIonvuLwphK42y
+fk1WpRPs6tqSnqxEQR5YYGUFZvjARL3LlPdCfgv3ZWiYUQXw8wWRBB0bF4LsyFe7
+w2t6iPGwcswlWyCR7BYCEo8y6RcYSNDHBS4CMEK4JZwFaz+qOqfrU0j36NK2B5jc
+G8Y0f3/JHIJ6BVgrCFvzOKKrF11myZjXnhCLotLddJr3cQxyYN/Nb5gznZY0dj4k
+epKwDpUeb+agRThHqtdB7Uq3EvbXG4OKDy7YCbZZ16oE/9KTfWgu3YtLq1i6L43q
+laegw1SJpfvbi1EinbLDvhG+LJGGi5Z4rSDTii8aP8bQUWWHIbEZAWV/RRyH9XzQ
+QUxPKZgh/TMfdQwEUfoZd9vUFBzugcMd9Zi3aQaRIt0AUMyBMawSB3s42mhb5ivU
+fslfrejrckzzAeVLIL+aplfKkQABi6F1ITe1Yw1nPkZPcCBnzsXWWdsC4PDSy826
+YreQQejdIOQpvGQpQsgi3Hia/0PsmBsJUUtaWsJx8cTLc6nloQsCAwEAAaOCAc4w
+ggHKMB0GA1UdDgQWBBQWtTIb1Mfz4OaO873SsDrusjkY0TCBowYDVR0jBIGbMIGY
+gBQWtTIb1Mfz4OaO873SsDrusjkY0aF9pHsweTEQMA4GA1UEChMHUm9vdCBDQTEe
+MBwGA1UECxMVaHR0cDovL3d3dy5jYWNlcnQub3JnMSIwIAYDVQQDExlDQSBDZXJ0
+IFNpZ25pbmcgQXV0aG9yaXR5MSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0QGNhY2Vy
+dC5vcmeCAQAwDwYDVR0TAQH/BAUwAwEB/zAyBgNVHR8EKzApMCegJaAjhiFodHRw
+czovL3d3dy5jYWNlcnQub3JnL3Jldm9rZS5jcmwwMAYJYIZIAYb4QgEEBCMWIWh0
+dHBzOi8vd3d3LmNhY2VydC5vcmcvcmV2b2tlLmNybDA0BglghkgBhvhCAQgEJxYl
+aHR0cDovL3d3dy5jYWNlcnQub3JnL2luZGV4LnBocD9pZD0xMDBWBglghkgBhvhC
+AQ0ESRZHVG8gZ2V0IHlvdXIgb3duIGNlcnRpZmljYXRlIGZvciBGUkVFIGhlYWQg
+b3ZlciB0byBodHRwOi8vd3d3LmNhY2VydC5vcmcwDQYJKoZIhvcNAQEEBQADggIB
+ACjH7pyCArpcgBLKNQodgW+JapnM8mgPf6fhjViVPr3yBsOQWqy1YPaZQwGjiHCc
+nWKdpIevZ1gNMDY75q1I08t0AoZxPuIrA2jxNGJARjtT6ij0rPtmlVOKTV39O9lg
+18p5aTuxZZKmxoGCXJzN600BiqXfEVWqFcofN8CCmHBh22p8lqOOLlQ+TyGpkO/c
+gr/c6EWtTZBzCDyUZbAEmXZ/4rzCahWqlwQ3JNgelE5tDlG+1sSPypZt90Pf6DBl
+Jzt7u0NDY8RD97LsaMzhGY4i+5jhe1o+ATc7iwiwovOVThrLm82asduycPAtStvY
+sONvRUgzEv/+PDIqVPfE94rwiCPCR/5kenHA0R6mY7AHfqQv0wGP3J8rtsYIqQ+T
+SCX8Ev2fQtzzxD72V7DX3WnRBnc0CkvSyqD/HMaMyRa+xMwyN2hzXwj7UfdJUzYF
+CpUCTPJ5GhD22Dp1nPMd8aINcGeGG7MW9S/lpOt5hvk9C8JzC6WZrG/8Z7jlLwum
+GCSNe9FINSkYQKyTYOGWhlC0elnYjyELn8+CkcY7v2vcB5G5l1YjqrZslMZIBjzk
+zk6q5PYvCdxTby78dOs6Y5nCpqyJvKeyRKANihDjbPIky/qbn3BHLt4Ui9SyIAmW
+omTxJBzcoTWcFbLUvFUufQb1nA5V9FrWk9p2rSVzTMVD
+-----END CERTIFICATE-----
--- a/share/ca-certs/Makefile.am	Mon Aug 18 17:08:01 2008 +0000
+++ b/share/ca-certs/Makefile.am	Thu Nov 20 21:13:56 2008 +0000
@@ -1,4 +1,6 @@
 CERTIFICATES = \
+		CAcert_Root.pem \
+		CAcert_Class3.pem \
 		Equifax_Secure_CA.pem \
 		GTE_CyberTrust_Global_Root.pem \
 		Microsoft_Secure_Server_Authority.pem \