2 * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 #include "WorldSocket.h" // must be first to make ACE happy with ACE includes in it
25 #include "Database/DatabaseEnv.h"
28 #include "WorldPacket.h"
29 #include "WorldSession.h"
31 #include "ObjectMgr.h"
35 #include "ObjectAccessor.h"
36 #include "BattleGroundMgr.h"
37 #include "SocialMgr.h"
38 #include "zlib/zlib.h"
40 /// WorldSession constructor
41 WorldSession::WorldSession(uint32 id
, WorldSocket
*sock
, AccountTypes sec
, uint8 expansion
, time_t mute_time
, LocaleConstant locale
) :
42 LookingForGroup_auto_join(false), LookingForGroup_auto_add(false), m_muteTime(mute_time
),
43 _player(NULL
), m_Socket(sock
),_security(sec
), _accountId(id
), m_expansion(expansion
),
44 m_sessionDbcLocale(sWorld
.GetAvailableDbcLocale(locale
)), m_sessionDbLocaleIndex(objmgr
.GetIndexForLocale(locale
)),
45 _logoutTime(0), m_inQueue(false), m_playerLoading(false), m_playerLogout(false), m_playerRecentlyLogout(false),
46 m_latency(0), m_TutorialsChanged(false)
50 m_Address
= sock
->GetRemoteAddress ();
51 sock
->AddReference ();
55 /// WorldSession destructor
56 WorldSession::~WorldSession()
58 ///- unload player if not unloaded
62 /// - If have unclosed socket, close it
65 m_Socket
->CloseSocket ();
66 m_Socket
->RemoveReference ();
70 ///- empty incoming packet queue
71 while(!_recvQueue
.empty())
73 WorldPacket
*packet
= _recvQueue
.next ();
78 void WorldSession::SizeError(WorldPacket
const& packet
, uint32 size
) const
80 sLog
.outError("Client (account %u) send packet %s (%u) with size %u but expected %u (attempt crash server?), skipped",
81 GetAccountId(),LookupOpcodeName(packet
.GetOpcode()),packet
.GetOpcode(),packet
.size(),size
);
84 /// Get the player name
85 char const* WorldSession::GetPlayerName() const
87 return GetPlayer() ? GetPlayer()->GetName() : "<none>";
90 /// Send a packet to the client
91 void WorldSession::SendPacket(WorldPacket
const* packet
)
98 // Code for network use statistic
99 static uint64 sendPacketCount
= 0;
100 static uint64 sendPacketBytes
= 0;
102 static time_t firstTime
= time(NULL
);
103 static time_t lastTime
= firstTime
; // next 60 secs start time
105 static uint64 sendLastPacketCount
= 0;
106 static uint64 sendLastPacketBytes
= 0;
108 time_t cur_time
= time(NULL
);
110 if((cur_time
- lastTime
) < 60)
113 sendPacketBytes
+=packet
->size();
115 sendLastPacketCount
+=1;
116 sendLastPacketBytes
+=packet
->size();
120 uint64 minTime
= uint64(cur_time
- lastTime
);
121 uint64 fullTime
= uint64(lastTime
- firstTime
);
122 sLog
.outDetail("Send all time packets count: " I64FMTD
" bytes: " I64FMTD
" avr.count/sec: %f avr.bytes/sec: %f time: %u",sendPacketCount
,sendPacketBytes
,float(sendPacketCount
)/fullTime
,float(sendPacketBytes
)/fullTime
,uint32(fullTime
));
123 sLog
.outDetail("Send last min packets count: " I64FMTD
" bytes: " I64FMTD
" avr.count/sec: %f avr.bytes/sec: %f",sendLastPacketCount
,sendLastPacketBytes
,float(sendLastPacketCount
)/minTime
,float(sendLastPacketBytes
)/minTime
);
126 sendLastPacketCount
= 1;
127 sendLastPacketBytes
= packet
->wpos(); // wpos is real written size
130 #endif // !MANGOS_DEBUG
132 if (m_Socket
->SendPacket (*packet
) == -1)
133 m_Socket
->CloseSocket ();
136 /// Add an incoming packet to the queue
137 void WorldSession::QueuePacket(WorldPacket
* new_packet
)
139 _recvQueue
.add(new_packet
);
142 /// Logging helper for unexpected opcodes
143 void WorldSession::logUnexpectedOpcode(WorldPacket
* packet
, const char *reason
)
145 sLog
.outError( "SESSION: received unexpected opcode %s (0x%.4X) %s",
146 LookupOpcodeName(packet
->GetOpcode()),
151 /// Update the WorldSession (triggered by World update)
152 bool WorldSession::Update(uint32
/*diff*/)
154 ///- Retrieve packets from the receive queue and call the appropriate handlers
155 /// not proccess packets if socket already closed
156 while (!_recvQueue
.empty() && m_Socket
&& !m_Socket
->IsClosed ())
158 WorldPacket
*packet
= _recvQueue
.next();
161 sLog.outError( "MOEP: %s (0x%.4X)",
162 LookupOpcodeName(packet->GetOpcode()),
163 packet->GetOpcode());
166 if(packet
->GetOpcode() >= NUM_MSG_TYPES
)
168 sLog
.outError( "SESSION: received non-existed opcode %s (0x%.4X)",
169 LookupOpcodeName(packet
->GetOpcode()),
170 packet
->GetOpcode());
174 OpcodeHandler
& opHandle
= opcodeTable
[packet
->GetOpcode()];
175 switch (opHandle
.status
)
177 case STATUS_LOGGEDIN
:
180 // skip STATUS_LOGGEDIN opcode unexpected errors if player logout sometime ago - this can be network lag delayed packets
181 if(!m_playerRecentlyLogout
)
182 logUnexpectedOpcode(packet
, "the player has not logged in yet");
184 else if(_player
->IsInWorld())
185 (this->*opHandle
.handler
)(*packet
);
186 // lag can cause STATUS_LOGGEDIN opcodes to arrive after the player started a transfer
188 case STATUS_TRANSFER
:
190 logUnexpectedOpcode(packet
, "the player has not logged in yet");
191 else if(_player
->IsInWorld())
192 logUnexpectedOpcode(packet
, "the player is still in world");
194 (this->*opHandle
.handler
)(*packet
);
197 // prevent cheating with skip queue wait
200 logUnexpectedOpcode(packet
, "the player not pass queue yet");
204 m_playerRecentlyLogout
= false;
205 (this->*opHandle
.handler
)(*packet
);
208 sLog
.outError( "SESSION: received not allowed opcode %s (0x%.4X)",
209 LookupOpcodeName(packet
->GetOpcode()),
210 packet
->GetOpcode());
218 ///- Cleanup socket pointer if need
219 if (m_Socket
&& m_Socket
->IsClosed ())
221 m_Socket
->RemoveReference ();
225 ///- If necessary, log the player out
226 time_t currTime
= time(NULL
);
227 if (!m_Socket
|| (ShouldLogOut(currTime
) && !m_playerLoading
))
231 return false; //Will remove this session from the world session map
236 /// %Log the player out
237 void WorldSession::LogoutPlayer(bool Save
)
239 // finish pending transfers before starting the logout
240 while(_player
&& _player
->IsBeingTeleportedFar())
241 HandleMoveWorldportAckOpcode();
243 m_playerLogout
= true;
247 if (uint64 lguid
= GetPlayer()->GetLootGUID())
248 DoLootRelease(lguid
);
250 ///- If the player just died before logging out, make him appear as a ghost
251 //FIXME: logout must be delayed in case lost connection with client in time of combat
252 if (_player
->GetDeathTimer())
254 _player
->getHostilRefManager().deleteReferences();
255 _player
->BuildPlayerRepop();
256 _player
->RepopAtGraveyard();
258 else if (!_player
->getAttackers().empty())
260 _player
->CombatStop();
261 _player
->getHostilRefManager().setOnlineOfflineState(false);
262 _player
->RemoveAllAurasOnDeath();
264 // build set of player who attack _player or who have pet attacking of _player
265 std::set
<Player
*> aset
;
266 for(Unit::AttackerSet::const_iterator itr
= _player
->getAttackers().begin(); itr
!= _player
->getAttackers().end(); ++itr
)
268 Unit
* owner
= (*itr
)->GetOwner(); // including player controlled case
271 if(owner
->GetTypeId()==TYPEID_PLAYER
)
272 aset
.insert((Player
*)owner
);
275 if((*itr
)->GetTypeId()==TYPEID_PLAYER
)
276 aset
.insert((Player
*)(*itr
));
279 _player
->SetPvPDeath(!aset
.empty());
280 _player
->KillPlayer();
281 _player
->BuildPlayerRepop();
282 _player
->RepopAtGraveyard();
284 // give honor to all attackers from set like group case
285 for(std::set
<Player
*>::const_iterator itr
= aset
.begin(); itr
!= aset
.end(); ++itr
)
286 (*itr
)->RewardHonor(_player
,aset
.size());
288 // give bg rewards and update counters like kill by first from attackers
289 // this can't be called for all attackers.
291 if(BattleGround
*bg
= _player
->GetBattleGround())
292 bg
->HandleKillPlayer(_player
,*aset
.begin());
294 else if(_player
->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION
))
296 // this will kill character by SPELL_AURA_SPIRIT_OF_REDEMPTION
297 _player
->RemoveSpellsCausingAura(SPELL_AURA_MOD_SHAPESHIFT
);
298 //_player->SetDeathPvP(*); set at SPELL_AURA_SPIRIT_OF_REDEMPTION apply time
299 _player
->KillPlayer();
300 _player
->BuildPlayerRepop();
301 _player
->RepopAtGraveyard();
303 //drop a flag if player is carrying it
304 if(BattleGround
*bg
= _player
->GetBattleGround())
305 bg
->EventPlayerLoggedOut(_player
);
307 ///- Teleport to home if the player is in an invalid instance
308 if(!_player
->m_InstanceValid
&& !_player
->isGameMaster())
309 _player
->TeleportTo(_player
->m_homebindMapId
, _player
->m_homebindX
, _player
->m_homebindY
, _player
->m_homebindZ
, _player
->GetOrientation());
311 for (int i
=0; i
< PLAYER_MAX_BATTLEGROUND_QUEUES
; ++i
)
313 if(BattleGroundQueueTypeId bgQueueTypeId
= _player
->GetBattleGroundQueueTypeId(i
))
315 _player
->RemoveBattleGroundQueueId(bgQueueTypeId
);
316 sBattleGroundMgr
.m_BattleGroundQueues
[ bgQueueTypeId
].RemovePlayer(_player
->GetGUID(), true);
320 ///- Reset the online field in the account table
321 // no point resetting online in character table here as Player::SaveToDB() will set it to 1 since player has not been removed from world at this stage
322 //No SQL injection as AccountID is uint32
323 loginDatabase
.PExecute("UPDATE account SET online = 0 WHERE id = '%u'", GetAccountId());
325 ///- If the player is in a guild, update the guild roster and broadcast a logout message to other guild members
326 Guild
*guild
= objmgr
.GetGuildById(_player
->GetGuildId());
329 guild
->LoadPlayerStatsByGuid(_player
->GetGUID());
330 guild
->UpdateLogoutTime(_player
->GetGUID());
332 WorldPacket
data(SMSG_GUILD_EVENT
, (1+1+12+8)); // name limited to 12 in character table.
333 data
<<(uint8
)GE_SIGNED_OFF
;
335 data
<<_player
->GetName();
336 data
<<_player
->GetGUID();
337 guild
->BroadcastPacket(&data
);
341 _player
->RemovePet(NULL
,PET_SAVE_AS_CURRENT
, true);
343 ///- empty buyback items and save the player in the database
344 // some save parts only correctly work in case player present in map/player_lists (pets, etc)
348 for(int j
= BUYBACK_SLOT_START
; j
< BUYBACK_SLOT_END
; ++j
)
350 eslot
= j
- BUYBACK_SLOT_START
;
351 _player
->SetUInt64Value(PLAYER_FIELD_VENDORBUYBACK_SLOT_1
+ (eslot
* 2), 0);
352 _player
->SetUInt32Value(PLAYER_FIELD_BUYBACK_PRICE_1
+ eslot
, 0);
353 _player
->SetUInt32Value(PLAYER_FIELD_BUYBACK_TIMESTAMP_1
+ eslot
, 0);
358 ///- Leave all channels before player delete...
359 _player
->CleanupChannels();
361 ///- If the player is in a group (or invited), remove him. If the group if then only 1 person, disband the group.
362 _player
->UninviteFromGroup();
364 // remove player from the group if he is:
365 // a) in group; b) not in raid group; c) logging out normally (not being kicked or disconnected)
366 if(_player
->GetGroup() && !_player
->GetGroup()->isRaidGroup() && m_Socket
)
367 _player
->RemoveFromGroup();
369 ///- Remove the player from the world
370 // the player may not be in the world when logging out
371 // e.g if he got disconnected during a transfer to another map
372 // calls to GetMap in this case may cause crashes
373 if(_player
->IsInWorld()) _player
->GetMap()->Remove(_player
, false);
374 // RemoveFromWorld does cleanup that requires the player to be in the accessor
375 ObjectAccessor::Instance().RemoveObject(_player
);
377 ///- Send update to group
378 if(_player
->GetGroup())
379 _player
->GetGroup()->SendUpdate();
381 ///- Broadcast a logout message to the player's friends
382 sSocialMgr
.SendFriendStatus(_player
, FRIEND_OFFLINE
, _player
->GetGUIDLow(), true);
384 ///- Delete the player object
385 _player
->CleanupsBeforeDelete(); // do some cleanup before deleting to prevent crash at crossreferences to already deleted data
387 sSocialMgr
.RemovePlayerSocial (_player
->GetGUIDLow ());
391 ///- Send the 'logout complete' packet to the client
392 WorldPacket
data( SMSG_LOGOUT_COMPLETE
, 0 );
395 ///- Since each account can only have one online character at any given time, ensure all characters for active account are marked as offline
396 //No SQL injection as AccountId is uint32
397 CharacterDatabase
.PExecute("UPDATE characters SET online = 0 WHERE account = '%u'",
399 sLog
.outDebug( "SESSION: Sent SMSG_LOGOUT_COMPLETE Message" );
402 m_playerLogout
= false;
403 m_playerRecentlyLogout
= true;
407 /// Kick a player out of the World
408 void WorldSession::KickPlayer()
411 m_Socket
->CloseSocket ();
414 /// Cancel channeling handler
416 void WorldSession::SendAreaTriggerMessage(const char* Text
, ...)
423 vsnprintf( szStr
, 1024, Text
, ap
);
426 uint32 length
= strlen(szStr
)+1;
427 WorldPacket
data(SMSG_AREA_TRIGGER_MESSAGE
, 4+length
);
433 void WorldSession::SendNotification(const char *format
,...)
440 va_start(ap
, format
);
441 vsnprintf( szStr
, 1024, format
, ap
);
444 WorldPacket
data(SMSG_NOTIFICATION
, (strlen(szStr
)+1));
450 void WorldSession::SendNotification(int32 string_id
,...)
452 char const* format
= GetMangosString(string_id
);
458 va_start(ap
, string_id
);
459 vsnprintf( szStr
, 1024, format
, ap
);
462 WorldPacket
data(SMSG_NOTIFICATION
, (strlen(szStr
)+1));
468 void WorldSession::SendSetPhaseShift(uint32 PhaseShift
)
470 WorldPacket
data(SMSG_SET_PHASE_SHIFT
, 4);
471 data
<< uint32(PhaseShift
);
475 const char * WorldSession::GetMangosString( int32 entry
) const
477 return objmgr
.GetMangosString(entry
,GetSessionDbLocaleIndex());
480 void WorldSession::Handle_NULL( WorldPacket
& recvPacket
)
482 sLog
.outError( "SESSION: received unhandled opcode %s (0x%.4X)",
483 LookupOpcodeName(recvPacket
.GetOpcode()),
484 recvPacket
.GetOpcode());
487 void WorldSession::Handle_EarlyProccess( WorldPacket
& recvPacket
)
489 sLog
.outError( "SESSION: received opcode %s (0x%.4X) that must be processed in WorldSocket::OnRead",
490 LookupOpcodeName(recvPacket
.GetOpcode()),
491 recvPacket
.GetOpcode());
494 void WorldSession::Handle_ServerSide( WorldPacket
& recvPacket
)
496 sLog
.outError( "SESSION: received server-side opcode %s (0x%.4X)",
497 LookupOpcodeName(recvPacket
.GetOpcode()),
498 recvPacket
.GetOpcode());
501 void WorldSession::Handle_Deprecated( WorldPacket
& recvPacket
)
503 sLog
.outError( "SESSION: received deprecated opcode %s (0x%.4X)",
504 LookupOpcodeName(recvPacket
.GetOpcode()),
505 recvPacket
.GetOpcode());
508 void WorldSession::SendAuthWaitQue(uint32 position
)
512 WorldPacket
packet( SMSG_AUTH_RESPONSE
, 1 );
513 packet
<< uint8( AUTH_OK
);
518 WorldPacket
packet( SMSG_AUTH_RESPONSE
, 5 );
519 packet
<< uint8( AUTH_WAIT_QUEUE
);
520 packet
<< uint32 (position
);
525 void WorldSession::LoadAccountData()
527 for (uint32 i
= 0; i
< NUM_ACCOUNT_DATA_TYPES
; ++i
)
530 m_accountData
[i
] = data
;
533 QueryResult
*result
= CharacterDatabase
.PQuery("SELECT type, time, data FROM account_data WHERE account='%u'", GetAccountId());
540 Field
*fields
= result
->Fetch();
542 uint32 type
= fields
[0].GetUInt32();
543 if(type
< NUM_ACCOUNT_DATA_TYPES
)
545 m_accountData
[type
].Time
= fields
[1].GetUInt32();
546 m_accountData
[type
].Data
= fields
[2].GetCppString();
548 } while (result
->NextRow());
553 void WorldSession::SetAccountData(uint32 type
, time_t time_
, std::string data
)
555 m_accountData
[type
].Time
= time_
;
556 m_accountData
[type
].Data
= data
;
558 uint32 acc
= GetAccountId();
560 CharacterDatabase
.BeginTransaction ();
561 CharacterDatabase
.PExecute("DELETE FROM account_data WHERE account='%u' AND type='%u'", acc
, type
);
562 CharacterDatabase
.escape_string(data
);
563 CharacterDatabase
.PExecute("INSERT INTO account_data VALUES ('%u','%u','%u','%s')", acc
, type
, (uint32
)time_
, data
.c_str());
564 CharacterDatabase
.CommitTransaction ();
567 void WorldSession::LoadTutorialsData()
569 for ( int aX
= 0 ; aX
< 8 ; ++aX
)
570 m_Tutorials
[ aX
] = 0;
572 QueryResult
*result
= CharacterDatabase
.PQuery("SELECT tut0,tut1,tut2,tut3,tut4,tut5,tut6,tut7 FROM character_tutorial WHERE account = '%u'", GetAccountId());
578 Field
*fields
= result
->Fetch();
580 for (int iI
= 0; iI
< 8; ++iI
)
581 m_Tutorials
[iI
] = fields
[iI
].GetUInt32();
583 while( result
->NextRow() );
588 m_TutorialsChanged
= false;
591 void WorldSession::SendTutorialsData()
593 WorldPacket
data(SMSG_TUTORIAL_FLAGS
, 4*8);
594 for(uint32 i
= 0; i
< 8; ++i
)
595 data
<< m_Tutorials
[i
];
599 void WorldSession::SaveTutorialsData()
601 if(!m_TutorialsChanged
)
605 // it's better than rebuilding indexes multiple times
606 QueryResult
*result
= CharacterDatabase
.PQuery("SELECT count(*) AS r FROM character_tutorial WHERE account = '%u'", GetAccountId());
609 Rows
= result
->Fetch()[0].GetUInt32();
615 CharacterDatabase
.PExecute("UPDATE character_tutorial SET tut0='%u', tut1='%u', tut2='%u', tut3='%u', tut4='%u', tut5='%u', tut6='%u', tut7='%u' WHERE account = '%u'",
616 m_Tutorials
[0], m_Tutorials
[1], m_Tutorials
[2], m_Tutorials
[3], m_Tutorials
[4], m_Tutorials
[5], m_Tutorials
[6], m_Tutorials
[7], GetAccountId());
620 CharacterDatabase
.PExecute("INSERT INTO character_tutorial (account,tut0,tut1,tut2,tut3,tut4,tut5,tut6,tut7) VALUES ('%u', '%u', '%u', '%u', '%u', '%u', '%u', '%u', '%u')", GetAccountId(), m_Tutorials
[0], m_Tutorials
[1], m_Tutorials
[2], m_Tutorials
[3], m_Tutorials
[4], m_Tutorials
[5], m_Tutorials
[6], m_Tutorials
[7]);
623 m_TutorialsChanged
= false;
626 void WorldSession::ReadMovementInfo(WorldPacket
&data
, MovementInfo
*mi
)
628 CHECK_PACKET_SIZE(data
, data
.rpos()+4+2+4+4+4+4+4);
637 if(mi
->flags
& MOVEMENTFLAG_ONTRANSPORT
)
639 if(!data
.readPackGUID(mi
->t_guid
))
642 CHECK_PACKET_SIZE(data
, data
.rpos()+4+4+4+4+4+1);
651 if((mi
->flags
& (MOVEMENTFLAG_SWIMMING
| MOVEMENTFLAG_FLYING2
)) || (mi
->unk1
& 0x20))
653 CHECK_PACKET_SIZE(data
, data
.rpos()+4);
657 CHECK_PACKET_SIZE(data
, data
.rpos()+4);
658 data
>> mi
->fallTime
;
660 if(mi
->flags
& MOVEMENTFLAG_JUMPING
)
662 CHECK_PACKET_SIZE(data
, data
.rpos()+4+4+4+4);
664 data
>> mi
->j_sinAngle
;
665 data
>> mi
->j_cosAngle
;
666 data
>> mi
->j_xyspeed
;
669 if(mi
->flags
& MOVEMENTFLAG_SPLINE
)
671 CHECK_PACKET_SIZE(data
, data
.rpos()+4);
676 void WorldSession::ReadAddonsInfo(WorldPacket
&data
)
678 if (data
.rpos() + 4 > data
.size())
688 uint32 pos
= data
.rpos();
690 ByteBuffer addonInfo
;
691 addonInfo
.resize(size
);
693 if (uncompress(const_cast<uint8
*>(addonInfo
.contents()), &uSize
, const_cast<uint8
*>(data
.contents() + pos
), data
.size() - pos
) == Z_OK
)
696 addonInfo
>> addonsCount
; // addons count
698 for(uint32 i
= 0; i
< addonsCount
; ++i
)
700 std::string addonName
;
704 // check next addon data format correctness
705 if(addonInfo
.rpos()+1 > addonInfo
.size())
708 addonInfo
>> addonName
;
710 // recheck next addon data format correctness
711 if(addonInfo
.rpos()+1+4+4 > addonInfo
.size())
714 addonInfo
>> enabled
>> crc
>> unk1
;
716 sLog
.outDebug("ADDON: Name: %s, Enabled: 0x%x, CRC: 0x%x, Unknown2: 0x%x", addonName
.c_str(), enabled
, crc
, unk1
);
718 m_addonsList
.push_back(AddonInfo(addonName
, enabled
, crc
));
724 if(addonInfo
.rpos() != addonInfo
.size())
725 sLog
.outDebug("packet under read!");
728 sLog
.outError("Addon packet uncompress error!");
731 void WorldSession::SendAddonsInfo()
733 unsigned char tdata
[256] =
735 0xC3, 0x5B, 0x50, 0x84, 0xB9, 0x3E, 0x32, 0x42, 0x8C, 0xD0, 0xC7, 0x48, 0xFA, 0x0E, 0x5D, 0x54,
736 0x5A, 0xA3, 0x0E, 0x14, 0xBA, 0x9E, 0x0D, 0xB9, 0x5D, 0x8B, 0xEE, 0xB6, 0x84, 0x93, 0x45, 0x75,
737 0xFF, 0x31, 0xFE, 0x2F, 0x64, 0x3F, 0x3D, 0x6D, 0x07, 0xD9, 0x44, 0x9B, 0x40, 0x85, 0x59, 0x34,
738 0x4E, 0x10, 0xE1, 0xE7, 0x43, 0x69, 0xEF, 0x7C, 0x16, 0xFC, 0xB4, 0xED, 0x1B, 0x95, 0x28, 0xA8,
739 0x23, 0x76, 0x51, 0x31, 0x57, 0x30, 0x2B, 0x79, 0x08, 0x50, 0x10, 0x1C, 0x4A, 0x1A, 0x2C, 0xC8,
740 0x8B, 0x8F, 0x05, 0x2D, 0x22, 0x3D, 0xDB, 0x5A, 0x24, 0x7A, 0x0F, 0x13, 0x50, 0x37, 0x8F, 0x5A,
741 0xCC, 0x9E, 0x04, 0x44, 0x0E, 0x87, 0x01, 0xD4, 0xA3, 0x15, 0x94, 0x16, 0x34, 0xC6, 0xC2, 0xC3,
742 0xFB, 0x49, 0xFE, 0xE1, 0xF9, 0xDA, 0x8C, 0x50, 0x3C, 0xBE, 0x2C, 0xBB, 0x57, 0xED, 0x46, 0xB9,
743 0xAD, 0x8B, 0xC6, 0xDF, 0x0E, 0xD6, 0x0F, 0xBE, 0x80, 0xB3, 0x8B, 0x1E, 0x77, 0xCF, 0xAD, 0x22,
744 0xCF, 0xB7, 0x4B, 0xCF, 0xFB, 0xF0, 0x6B, 0x11, 0x45, 0x2D, 0x7A, 0x81, 0x18, 0xF2, 0x92, 0x7E,
745 0x98, 0x56, 0x5D, 0x5E, 0x69, 0x72, 0x0A, 0x0D, 0x03, 0x0A, 0x85, 0xA2, 0x85, 0x9C, 0xCB, 0xFB,
746 0x56, 0x6E, 0x8F, 0x44, 0xBB, 0x8F, 0x02, 0x22, 0x68, 0x63, 0x97, 0xBC, 0x85, 0xBA, 0xA8, 0xF7,
747 0xB5, 0x40, 0x68, 0x3C, 0x77, 0x86, 0x6F, 0x4B, 0xD7, 0x88, 0xCA, 0x8A, 0xD7, 0xCE, 0x36, 0xF0,
748 0x45, 0x6E, 0xD5, 0x64, 0x79, 0x0F, 0x17, 0xFC, 0x64, 0xDD, 0x10, 0x6F, 0xF3, 0xF5, 0xE0, 0xA6,
749 0xC3, 0xFB, 0x1B, 0x8C, 0x29, 0xEF, 0x8E, 0xE5, 0x34, 0xCB, 0xD1, 0x2A, 0xCE, 0x79, 0xC3, 0x9A,
750 0x0D, 0x36, 0xEA, 0x01, 0xE0, 0xAA, 0x91, 0x20, 0x54, 0xF0, 0x72, 0xD8, 0x1E, 0xC7, 0x89, 0xD2
753 WorldPacket
data(SMSG_ADDON_INFO
, 4);
755 for(AddonsList::iterator itr
= m_addonsList
.begin(); itr
!= m_addonsList
.end(); ++itr
)
757 uint8 state
= 2; // 2 is sent here
758 data
<< uint8(state
);
760 uint8 unk1
= 1; // 1 is sent here
764 uint8 unk2
= (itr
->CRC
!= 0x4c1c776d); // If addon is Standard addon CRC
766 if (unk2
) // if CRC is wrong, add public key (client need it)
767 data
.append(tdata
, sizeof(tdata
));
772 uint8 unk3
= 0; // 0 is sent here
776 // String, 256 (null terminated?)
781 m_addonsList
.clear();
784 data
<< uint32(count
);
785 /*for(uint32 i = 0; i < count; ++i)