Account Messaging Implementation menu

User Tag List

Results 1 to 4 of 4
  1. #1
    stoneharry's Avatar Moderator Harry

    Authenticator enabled
    Reputation
    1613
    Join Date
    Sep 2007
    Posts
    4,554
    Thanks G/R
    150/146
    Trade Feedback
    0 (0%)
    Mentioned
    3 Post(s)
    Tagged
    0 Thread(s)

    Account Messaging Implementation

    Account Messaging

    This entire thread is designed for the 3.3.5a WoW client, but it should work on any other client as well.

    Explanation

    The World of Warcraft client has had account messaging implemented for a very long time, but never actually used by Blizzard.

    Account messaging is the process of being able to send unique messages to certain accounts upon successfully authentication with the server. Messages can be clicked away or marked as read, and when marked as read they will no longer show up on login. Multiple messages can be queued at once.

    This is demonstrated in the following video: Account Messages - World of Warcraft - YouTube

    Implementation

    The main logical steps of account messages are as follows:
    • Player logs in
    • Server challenges player
    • Player challenges server
    • Server response has a flag that needs to be marked, to show that the client should check for account messages
    • Client goes to URL defined in strings to retrieve XML code to be displayed as message
    • Client displays message if retrieved successfully, other buttons can call different URLs to mark it read (etc)
    • When client visits URL, it parses a hash of its session key, the account name, and the message ID. The servers and clients session keys should match in theory.


    The Client Changes

    There is only one file that needs to be changed to get the client working with account messages.

    Find the gluestrings.lua file in the GlueXML folder of the interface. Note that changes to the glueXML files will not work unless the WoW executable is 'cracked'.

    There are some strings that need to be changed:

    Code:
    ACCOUNT_MESSAGE_BODY_NO_READ_URL = "http://www.blizzard.com/getMessageBodyUnread.xml";
    ACCOUNT_MESSAGE_BODY_URL = "http://www.blizzard.com/getMessageBody.xml";
    ACCOUNT_MESSAGE_BUTTON_READ = "Mark as read";
    ACCOUNT_MESSAGE_HEADERS_URL = "http://www.blizzard.com/getMessageHeaders.xml";
    ACCOUNT_MESSAGE_READ_URL = "http://www.blizzard.com/markMessageAsRead.xml";
    Note that the file extension makes no difference, as long as the text is formatted correctly.

    These need to be changed to the URL of your website so that it executes your scripts and not Blizzard's. I found that changes to the gluestrings.lua here was not always successful since apparently there is another file somewhere else that overwrites these strings (I forget where). To counter this, I set these strings again just before the client checks for account messages. This can be found on line 241 of GlueParent.lua.

    I set ACCOUNT_MESSAGE_BODY_URL to nil as I never see it used, and no error is ever spat it saying that it is nil when something tries to reference it, so I assume it was never implemented.

    The Emulator Changes

    In this document I am describing the changes relevant to ArcEmu. But obviously, TrinityCore uses exactly the same packets. It should be a simple matter to implement it in TrinityCore as well, or any other emulator for that matter.

    The function to take note of is in AuthSocket.cpp in the logonserver:

    Code:
    void AuthSocket::SendProofError(uint8 Error, uint8 * M2)
    {
    	uint8 buffer[32];
    	memset(buffer, 0, 32);
    
    	buffer[0] = 1;
    	buffer[1] = Error;
    	if(M2 == 0)
    	{
    		*(uint32*)&buffer[2] = 3;
    
    		Send(buffer, 6);
    		return;
    	}
    
    	memcpy(&buffer[2], M2, 20);
        buffer[22]= 0x01; //<-- ARENA TOURNAMENT ACC FLAG!
    	buffer[30]= 1; // Unread messages
    	Send(buffer, 32);
    }
    You can see that I flag that there are unread messages here:

    Code:
    buffer[30]= 1; // Unread messages
    Since the session key is generated upon login, account messages need to be updated with the most up to date session key so that when the client uploads their hashed session key, it is the same as the servers.

    The following algorithm is wrong. This is the hash that Schlumpf thought was correct, however upon testing we found it generated a completely different result to the client. The correct hash needs to be figured out.

    However, in:

    void AuthSocket::HandleProof()

    Of the AuthSocket.cpp, near the end:
    Code:
    m_account->SetSessionKey(m_sessionkey.AsByteArray());
    This sets the account to have that session key. Immediately after, I do the following to get a hashed session key and store it:
    Code:
    	// Disabled because the server algorithm seems to be unable to generate the same result as the client.
    
    	// Hash the session key so that the logon DB can know
    	static const unsigned short hash_constant (0x7a0b);
    	Sha1Hash newone;
    	newone.UpdateBigNumbers (&m_sessionkey, NULL);
    	newone.UpdateData ((uint8*)&hash_constant, sizeof (hash_constant));
    	newone.Finalize();
    	BigNumber hashed_session_key;
    	hashed_session_key.SetBinary (newone.GetDigest(), newone.GetLength());
    
    	// Let the DB know
    	sLogonSQL->Execute("UPDATE `account_messages` SET `hash` = '%s' WHERE `account` = '%s';", hashed_session_key.AsDecStr(), m_account->UsernamePtr);
    This is the first flaw with the current implementation.

    Website Additions

    Schlumpf has provided the following information that shows the data flow and operations in the processes:

    Code:
    $ curl ACCOUNT_MESSAGE_HEADERS_URL?accountName=$accountname&sessionKeyHash=$keyhash
    <xs:element name="headers">
      <xs:complexType>
        <xs:sequence>
          <xs:element minOccurs="0" maxOccurs="unbounded" name="header_entry">
            <xs:complexType>
              <xs:sequence>
                <xs:element name="subject" type="string"/>
              </xs:sequence>
              <xs:attribute name="id" type="integer"/>
              <xs:attribute name="priority" type="integer"/>
              <xs:attribute name="opened" type="integer"/>
            </xs:complexType>
          </xs:element>
        </xs:sequence>
      </xs:complexType>
    </xs:element>
    <!--
      <headers>
        <header_entry id="0" priority="0" opened="0">
          <subject>HeaderSubject 1</subject>
        </header_entry>
        <header_entry id="1" priority="0" opened="1">
          <subject>HeaderSubject 2</subject>
        </header_entry>
      </headers>
    -->
    
    $ curl ACCOUNT_MESSAGE_BODY_NO_READ_URL?accountName=$accountname&sessionKeyHash=$keyhash&messageId=$id
    <xs:element name="body" type="xs:string"/>
    <!--
      <body>Body</body>
    -->
    
    $ curl ACCOUNT_MESSAGE_READ_URL?accountName=$accountname&sessionKeyHash=$keyhash&messageId=$id
    
    
    sessionKeyHash:
    static const short hash_constant = 0x7A0B;
    SHA1_Init(&sha_context);
    SHA1_Update(&sha_context, ClientServices::Connection()->GetSessionKey(), 40);
    SHA1_Update(&sha_context, &hash_constant, sizeof (hash_constant));
    SHA1_Final(keyhash, &sha_context);
    
    
    local files:
    SFile::DisableSFileCheckDisk(); and SFile::EnableDirectAccess(0); ->
    nop(); and SFile::EnableDirectAccess(-1);.
    
    framexml/gluexml:
     FrameXML_CheckSignature() return 3;
               FrameXML_CreateFrames("Interface\\GlueXML\\GlueXML.toc", 0, &md5_ctx, &this._);
              MD5Final(md5_result, &md5_ctx);
              if ( !strncmp((const char *)md5_a, (const char *)md5_result, 16) )
              -> if (true)
                if ( SFile::FileExistsEx("Interface\\FrameXML\\Bindings.xml", 1) )
        CGUIBindings::Load(*CGUIBindings::s_bindings, "Interface\\FrameXML\\Bindings.xml", &md5_ctx, &status);
      MD5Final(md5_result, &md5_ctx);
      if ( strncmp(&v45, (const char *)md5_result, 16) )
        ClientPostClose(10);
         -> if(false)
    This allowed me to create three PHP scripts to handle each event. I am by no means an expert at PHP, so I am sure my scripts could easily be improved.

    First I have, getMessageHeaders.php:

    Code:
    <?php
    $con = mysql_connect('host', 'user', 'pass');
    
    $account = mysql_real_escape_string($_GET["accountName"]);
    $hash = mysql_real_escape_string($_GET["sessionKeyHash"]);
    
    $count = 0;
    
    mysql_select_db('logon', $con);
    
    $q = mysql_query("SELECT `heading`,`read`,`hash` FROM `account_messages` WHERE account = '{$account}'") or die(mysql_error());
    
    if (mysql_num_rows($q) != 0)
    {
    	echo "<headers>";
    	
    	while($row = mysql_fetch_array($q))
    	{
    		//if ($hash == $row['hash'])
    		//{
    			if ($row['read'] == 0)
    			{
    				$head = $row['heading'];
    				
    				echo "<header_entry id=\"{$count}\" priority=\"0\" opened=\"0\">";
    				echo 	"<subject>{$head}</subject>";
    				echo "</header_entry>";
    				
    				$count = $count + 1;
    			}
    		//}
    	}
    	
    	echo "</headers>";
    }
    
    mysql_close($con);
    
    /*
    echo  "<headers>";
    echo    "<header_entry id=\"0\" priority=\"0\" opened=\"0\">";
    echo      "<subject>{$account}! {$hash}</subject>";
    echo    "</header_entry>";
    echo    "<header_entry id=\"1\" priority=\"0\" opened=\"0\">";
    echo      "<subject>Message 2</subject>";
    echo    "</header_entry>";
    echo    "<header_entry id=\"2\" priority=\"0\" opened=\"0\">";
    echo      "<subject>Message 3</subject>";
    echo    "</header_entry>";
    echo  "</headers>";
    */
    ?>
    Things to take note of here is that the account name and hashed session key are passed in as parameters to the script. I store account messages in a database, hence why I select the necessary data from this database. I loop through each unread message and display XML code as I go along. The structure of the XML code can be seen commented out at the bottom.

    I have also commented out the comparison to see if the database hash is the same as the client hash, because of the incorrect hash I was generating server side. This makes it insecure.

    The next file is getMessageBody.php, which follows a similar structure:

    Code:
    <?php
    $con = mysql_connect('host', 'user', 'pass');
    
    $account = mysql_real_escape_string($_GET["accountName"]);
    $hash = mysql_real_escape_string($_GET["sessionKeyHash"]);
    $id = mysql_real_escape_string($_GET["messageId"]);
    
    $count = 0;
    
    mysql_select_db('logon', $con);
    
    $q = mysql_query("SELECT `id`,`message`,`read`,`hash` FROM `account_messages` WHERE account = '{$account}' ORDER BY `id`") or die(mysql_error());
    
    if (mysql_num_rows($q) != 0)
    {
    	while($row = mysql_fetch_array($q))
    	{
    		//if ($hash == $row['hash'])
    		//{
    			if ($row['read'] == 0)
    			{
    				if ($count == $id)
    				{
    					$body = $row['message'];
    					echo "<body>{$body}</body>";
    					break;
    				}
    				
    				$count = $count + 1;
    			}
    		//}
    	}
    }
    
    mysql_close($con);
    ?>
    This just gets the text of the message depending on it's ID.

    The next script is markRead.php, which is used to mark the message as read to stop it showing up:

    Code:
    <?php
    $con = mysql_connect('host', 'user', 'pass');
    
    $account = mysql_real_escape_string($_GET["accountName"]);
    $hash = mysql_real_escape_string($_GET["sessionKeyHash"]);
    $id = mysql_real_escape_string($_GET["messageId"]);
    
    $count = 0;
    $id_found = 0;
    
    mysql_select_db('zzlogon', $con);
    
    $q = mysql_query("SELECT `id`,`hash`,`read` FROM `account_messages` WHERE account = '{$account}' ORDER BY `id`") or die(mysql_error());
    
    if (mysql_num_rows($q) != 0)
    {
    	while($row = mysql_fetch_array($q))
    	{
    		//if ($hash == $row['hash'])
    		//{
    			if ($row['read'] == 0)
    			{
    				if ($count == $id)
    				{
    					$id_found = $row['id'];
    					break;
    				}
    				
    				$count = $count + 1;
    			}
    		//}
    	}
    }
    
    if ($id_found != 0)
    {
    	mysql_query("UPDATE `account_messages` SET `read` = '1' WHERE `id` = '{$id_found}'") or die(mysql_error());
    }
    
    mysql_close($con);
    ?>
    It follows a similar structure to the other two scripts. The ID of the message is a little hard to obtain, so the majority of the script is spent finding this ID. If the message ID is found, then a query is executed to update the account message and set it as read.

    The logon database new table I created used the following structure:

    Code:
    /*Table structure for table `account_messages` */
    
    CREATE TABLE `account_messages` (
      `id` int(20) NOT NULL AUTO_INCREMENT,
      `account` varchar(20) DEFAULT NULL,
      `heading` varchar(40) DEFAULT NULL,
      `message` text,
      `read` tinyint(1) NOT NULL DEFAULT '0',
      `hash` text,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=552 DEFAULT CHARSET=latin1;
    This is all that is needed web side.

    Current Bugs

    The hashed session key generated by the server is different to that of the client, making it very insecure. This needs to be fixed. This is described in more detail previously in the thread.

    Happy messaging, and please post if you think you can, or have, solved the issues described here.

    Thanks,
    Harry & Schumpf
    Last edited by stoneharry; 01-08-2020 at 01:46 PM. Reason: removed outdated info

    Account Messaging Implementation
  2. Thanks Maccer (1 members gave Thanks to stoneharry for this useful post)
  3. #2
    ZxOxZ21's Avatar Sergeant
    Reputation
    12
    Join Date
    Feb 2010
    Posts
    38
    Thanks G/R
    0/0
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I love your guides, Harry. Good job.

  4. #3
    Glusk's Avatar Contributor
    Reputation
    105
    Join Date
    Apr 2015
    Posts
    33
    Thanks G/R
    7/32
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Code:
    buffer[30]= 1; // Unread messages
    So at the end of the packet (Server Proof), there's room for 2 bytes. Does that "1" that you put there signal only that there are unread messages or does it also signal how many there are? If so, is there an upper limit?

  5. #4
    stoneharry's Avatar Moderator Harry

    Authenticator enabled
    Reputation
    1613
    Join Date
    Sep 2007
    Posts
    4,554
    Thanks G/R
    150/146
    Trade Feedback
    0 (0%)
    Mentioned
    3 Post(s)
    Tagged
    0 Thread(s)
    Originally Posted by Glusk View Post
    Code:
    buffer[30]= 1; // Unread messages
    So at the end of the packet (Server Proof), there's room for 2 bytes. Does that "1" that you put there signal only that there are unread messages or does it also signal how many there are? If so, is there an upper limit?
    Oh man, this is an old thread. Let me rack my brains.

    If I remember correctly, it's supposed to be the number of unread messages but when I tested higher values it wasn't working as intended somehow.

    The crashes I talk about in the original thread are not related to account messaging but some other modifications we had done to the client in the end. The system should work flawlessly but I advise writing your own implementation of those PHP scripts, it was a proof of concept.

  6. Thanks Glusk (1 members gave Thanks to stoneharry for this useful post)

Similar Threads

  1. [Buying] Want To Buy A Hearthstone Account for 15euros - Message Me for offers!
    By Michel026 in forum Hearthstone Buy Sell Trade
    Replies: 1
    Last Post: 12-28-2014, 03:58 PM
  2. [Selling] Level 30 (Unranked) Account. Tons of champs and skins. Message for info.
    By 70twinx in forum League of Legends Buy Sell Trade
    Replies: 0
    Last Post: 07-30-2014, 10:18 PM
  3. Banned account response message
    By Tgump in forum World of Warcraft General
    Replies: 5
    Last Post: 07-21-2011, 05:19 AM
  4. [ArcEmu] How to change the "World of warcraft account has been temporarily suspended" MESSAGE
    By darkmagishin in forum WoW EMU Questions & Requests
    Replies: 3
    Last Post: 12-25-2010, 01:11 AM
  5. Account Email 311 unread messages.
    By cXm0d in forum Members Only Accounts And CD Keys Buy Sell
    Replies: 13
    Last Post: 02-20-2009, 12:55 PM
All times are GMT -5. The time now is 02:12 AM. Powered by vBulletin® Version 4.2.3
Copyright © 2024 vBulletin Solutions, Inc. All rights reserved. User Alert System provided by Advanced User Tagging (Pro) - vBulletin Mods & Addons Copyright © 2024 DragonByte Technologies Ltd.
Digital Point modules: Sphinx-based search