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
, uint32 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), m_latency(0)
49 m_Address
= sock
->GetRemoteAddress ();
50 sock
->AddReference ();
54 /// WorldSession destructor
55 WorldSession::~WorldSession()
57 ///- unload player if not unloaded
61 /// - If have unclosed socket, close it
64 m_Socket
->CloseSocket ();
65 m_Socket
->RemoveReference ();
69 ///- empty incoming packet queue
70 while(!_recvQueue
.empty())
72 WorldPacket
*packet
= _recvQueue
.next ();
77 void WorldSession::SizeError(WorldPacket
const& packet
, uint32 size
) const
79 sLog
.outError("Client (account %u) send packet %s (%u) with size %u but expected %u (attempt crash server?), skipped",
80 GetAccountId(),LookupOpcodeName(packet
.GetOpcode()),packet
.GetOpcode(),packet
.size(),size
);
83 /// Get the player name
84 char const* WorldSession::GetPlayerName() const
86 return GetPlayer() ? GetPlayer()->GetName() : "<none>";
89 /// Send a packet to the client
90 void WorldSession::SendPacket(WorldPacket
const* packet
)
97 // Code for network use statistic
98 static uint64 sendPacketCount
= 0;
99 static uint64 sendPacketBytes
= 0;
101 static time_t firstTime
= time(NULL
);
102 static time_t lastTime
= firstTime
; // next 60 secs start time
104 static uint64 sendLastPacketCount
= 0;
105 static uint64 sendLastPacketBytes
= 0;
107 time_t cur_time
= time(NULL
);
109 if((cur_time
- lastTime
) < 60)
112 sendPacketBytes
+=packet
->size();
114 sendLastPacketCount
+=1;
115 sendLastPacketBytes
+=packet
->size();
119 uint64 minTime
= uint64(cur_time
- lastTime
);
120 uint64 fullTime
= uint64(lastTime
- firstTime
);
121 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
));
122 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
);
125 sendLastPacketCount
= 1;
126 sendLastPacketBytes
= packet
->wpos(); // wpos is real written size
129 #endif // !MANGOS_DEBUG
131 if (m_Socket
->SendPacket (*packet
) == -1)
132 m_Socket
->CloseSocket ();
135 /// Add an incoming packet to the queue
136 void WorldSession::QueuePacket(WorldPacket
* new_packet
)
138 _recvQueue
.add(new_packet
);
141 /// Logging helper for unexpected opcodes
142 void WorldSession::logUnexpectedOpcode(WorldPacket
* packet
, const char *reason
)
144 sLog
.outError( "SESSION: received unexpected opcode %s (0x%.4X) %s",
145 LookupOpcodeName(packet
->GetOpcode()),
150 /// Update the WorldSession (triggered by World update)
151 bool WorldSession::Update(uint32
/*diff*/)
153 ///- Retrieve packets from the receive queue and call the appropriate handlers
154 /// not proccess packets if socket already closed
155 while (!_recvQueue
.empty() && m_Socket
&& !m_Socket
->IsClosed ())
157 WorldPacket
*packet
= _recvQueue
.next();
160 sLog.outError( "MOEP: %s (0x%.4X)",
161 LookupOpcodeName(packet->GetOpcode()),
162 packet->GetOpcode());
165 if(packet
->GetOpcode() >= NUM_MSG_TYPES
)
167 sLog
.outError( "SESSION: received non-existed opcode %s (0x%.4X)",
168 LookupOpcodeName(packet
->GetOpcode()),
169 packet
->GetOpcode());
173 OpcodeHandler
& opHandle
= opcodeTable
[packet
->GetOpcode()];
174 switch (opHandle
.status
)
176 case STATUS_LOGGEDIN
:
179 // skip STATUS_LOGGEDIN opcode unexpected errors if player logout sometime ago - this can be network lag delayed packets
180 if(!m_playerRecentlyLogout
)
181 logUnexpectedOpcode(packet
, "the player has not logged in yet");
183 else if(_player
->IsInWorld())
184 (this->*opHandle
.handler
)(*packet
);
185 // lag can cause STATUS_LOGGEDIN opcodes to arrive after the player started a transfer
187 case STATUS_TRANSFER_PENDING
:
189 logUnexpectedOpcode(packet
, "the player has not logged in yet");
190 else if(_player
->IsInWorld())
191 logUnexpectedOpcode(packet
, "the player is still in world");
193 (this->*opHandle
.handler
)(*packet
);
196 // prevent cheating with skip queue wait
199 logUnexpectedOpcode(packet
, "the player not pass queue yet");
203 m_playerRecentlyLogout
= false;
204 (this->*opHandle
.handler
)(*packet
);
207 sLog
.outError( "SESSION: received not allowed opcode %s (0x%.4X)",
208 LookupOpcodeName(packet
->GetOpcode()),
209 packet
->GetOpcode());
217 ///- Cleanup socket pointer if need
218 if (m_Socket
&& m_Socket
->IsClosed ())
220 m_Socket
->RemoveReference ();
224 ///- If necessary, log the player out
225 time_t currTime
= time(NULL
);
226 if (!m_Socket
|| (ShouldLogOut(currTime
) && !m_playerLoading
))
230 return false; //Will remove this session from the world session map
235 /// %Log the player out
236 void WorldSession::LogoutPlayer(bool Save
)
238 // finish pending transfers before starting the logout
239 while(_player
&& _player
->IsBeingTeleportedFar())
240 HandleMoveWorldportAckOpcode();
242 m_playerLogout
= true;
246 if (uint64 lguid
= GetPlayer()->GetLootGUID())
247 DoLootRelease(lguid
);
249 ///- If the player just died before logging out, make him appear as a ghost
250 //FIXME: logout must be delayed in case lost connection with client in time of combat
251 if (_player
->GetDeathTimer())
253 _player
->getHostilRefManager().deleteReferences();
254 _player
->BuildPlayerRepop();
255 _player
->RepopAtGraveyard();
257 else if (!_player
->getAttackers().empty())
259 _player
->CombatStop();
260 _player
->getHostilRefManager().setOnlineOfflineState(false);
261 _player
->RemoveAllAurasOnDeath();
263 // build set of player who attack _player or who have pet attacking of _player
264 std::set
<Player
*> aset
;
265 for(Unit::AttackerSet::const_iterator itr
= _player
->getAttackers().begin(); itr
!= _player
->getAttackers().end(); ++itr
)
267 Unit
* owner
= (*itr
)->GetOwner(); // including player controlled case
270 if(owner
->GetTypeId()==TYPEID_PLAYER
)
271 aset
.insert((Player
*)owner
);
274 if((*itr
)->GetTypeId()==TYPEID_PLAYER
)
275 aset
.insert((Player
*)(*itr
));
278 _player
->SetPvPDeath(!aset
.empty());
279 _player
->KillPlayer();
280 _player
->BuildPlayerRepop();
281 _player
->RepopAtGraveyard();
283 // give honor to all attackers from set like group case
284 for(std::set
<Player
*>::const_iterator itr
= aset
.begin(); itr
!= aset
.end(); ++itr
)
285 (*itr
)->RewardHonor(_player
,aset
.size());
287 // give bg rewards and update counters like kill by first from attackers
288 // this can't be called for all attackers.
290 if(BattleGround
*bg
= _player
->GetBattleGround())
291 bg
->HandleKillPlayer(_player
,*aset
.begin());
293 else if(_player
->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION
))
295 // this will kill character by SPELL_AURA_SPIRIT_OF_REDEMPTION
296 _player
->RemoveSpellsCausingAura(SPELL_AURA_MOD_SHAPESHIFT
);
297 //_player->SetDeathPvP(*); set at SPELL_AURA_SPIRIT_OF_REDEMPTION apply time
298 _player
->KillPlayer();
299 _player
->BuildPlayerRepop();
300 _player
->RepopAtGraveyard();
302 //drop a flag if player is carrying it
303 if(BattleGround
*bg
= _player
->GetBattleGround())
304 bg
->EventPlayerLoggedOut(_player
);
306 ///- Teleport to home if the player is in an invalid instance
307 if(!_player
->m_InstanceValid
&& !_player
->isGameMaster())
308 _player
->TeleportTo(_player
->m_homebindMapId
, _player
->m_homebindX
, _player
->m_homebindY
, _player
->m_homebindZ
, _player
->GetOrientation());
310 for (int i
=0; i
< PLAYER_MAX_BATTLEGROUND_QUEUES
; i
++)
312 if(BattleGroundQueueTypeId bgQueueTypeId
= _player
->GetBattleGroundQueueTypeId(i
))
314 _player
->RemoveBattleGroundQueueId(bgQueueTypeId
);
315 sBattleGroundMgr
.m_BattleGroundQueues
[ bgQueueTypeId
].RemovePlayer(_player
->GetGUID(), true);
319 ///- Reset the online field in the account table
320 // 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
321 //No SQL injection as AccountID is uint32
322 loginDatabase
.PExecute("UPDATE account SET online = 0 WHERE id = '%u'", GetAccountId());
324 ///- If the player is in a guild, update the guild roster and broadcast a logout message to other guild members
325 Guild
*guild
= objmgr
.GetGuildById(_player
->GetGuildId());
328 guild
->LoadPlayerStatsByGuid(_player
->GetGUID());
329 guild
->UpdateLogoutTime(_player
->GetGUID());
331 WorldPacket
data(SMSG_GUILD_EVENT
, (1+1+12+8)); // name limited to 12 in character table.
332 data
<<(uint8
)GE_SIGNED_OFF
;
334 data
<<_player
->GetName();
335 data
<<_player
->GetGUID();
336 guild
->BroadcastPacket(&data
);
340 _player
->RemovePet(NULL
,PET_SAVE_AS_CURRENT
, true);
342 ///- empty buyback items and save the player in the database
343 // some save parts only correctly work in case player present in map/player_lists (pets, etc)
347 for(int j
= BUYBACK_SLOT_START
; j
< BUYBACK_SLOT_END
; j
++)
349 eslot
= j
- BUYBACK_SLOT_START
;
350 _player
->SetUInt64Value(PLAYER_FIELD_VENDORBUYBACK_SLOT_1
+eslot
*2,0);
351 _player
->SetUInt32Value(PLAYER_FIELD_BUYBACK_PRICE_1
+eslot
,0);
352 _player
->SetUInt32Value(PLAYER_FIELD_BUYBACK_TIMESTAMP_1
+eslot
,0);
357 ///- Leave all channels before player delete...
358 _player
->CleanupChannels();
360 ///- If the player is in a group (or invited), remove him. If the group if then only 1 person, disband the group.
361 _player
->UninviteFromGroup();
363 // remove player from the group if he is:
364 // a) in group; b) not in raid group; c) logging out normally (not being kicked or disconnected)
365 if(_player
->GetGroup() && !_player
->GetGroup()->isRaidGroup() && m_Socket
)
366 _player
->RemoveFromGroup();
368 ///- Remove the player from the world
369 // the player may not be in the world when logging out
370 // e.g if he got disconnected during a transfer to another map
371 // calls to GetMap in this case may cause crashes
372 if(_player
->IsInWorld()) _player
->GetMap()->Remove(_player
, false);
373 // RemoveFromWorld does cleanup that requires the player to be in the accessor
374 ObjectAccessor::Instance().RemoveObject(_player
);
376 ///- Send update to group
377 if(_player
->GetGroup())
378 _player
->GetGroup()->SendUpdate();
380 ///- Broadcast a logout message to the player's friends
381 sSocialMgr
.SendFriendStatus(_player
, FRIEND_OFFLINE
, _player
->GetGUIDLow(), true);
383 ///- Delete the player object
384 _player
->CleanupsBeforeDelete(); // do some cleanup before deleting to prevent crash at crossreferences to already deleted data
386 sSocialMgr
.RemovePlayerSocial (_player
->GetGUIDLow ());
390 ///- Send the 'logout complete' packet to the client
391 WorldPacket
data( SMSG_LOGOUT_COMPLETE
, 0 );
394 ///- Since each account can only have one online character at any given time, ensure all characters for active account are marked as offline
395 //No SQL injection as AccountId is uint32
396 CharacterDatabase
.PExecute("UPDATE characters SET online = 0 WHERE account = '%u'",
398 sLog
.outDebug( "SESSION: Sent SMSG_LOGOUT_COMPLETE Message" );
401 m_playerLogout
= false;
402 m_playerRecentlyLogout
= true;
406 /// Kick a player out of the World
407 void WorldSession::KickPlayer()
410 m_Socket
->CloseSocket ();
413 /// Cancel channeling handler
415 void WorldSession::SendAreaTriggerMessage(const char* Text
, ...)
422 vsnprintf( szStr
, 1024, Text
, ap
);
425 uint32 length
= strlen(szStr
)+1;
426 WorldPacket
data(SMSG_AREA_TRIGGER_MESSAGE
, 4+length
);
432 void WorldSession::SendNotification(const char *format
,...)
439 va_start(ap
, format
);
440 vsnprintf( szStr
, 1024, format
, ap
);
443 WorldPacket
data(SMSG_NOTIFICATION
, (strlen(szStr
)+1));
449 void WorldSession::SendNotification(int32 string_id
,...)
451 char const* format
= GetMangosString(string_id
);
457 va_start(ap
, string_id
);
458 vsnprintf( szStr
, 1024, format
, ap
);
461 WorldPacket
data(SMSG_NOTIFICATION
, (strlen(szStr
)+1));
467 void WorldSession::SendSetPhaseShift(uint32 PhaseShift
)
469 WorldPacket
data(SMSG_SET_PHASE_SHIFT
, 4);
470 data
<< uint32(PhaseShift
);
474 const char * WorldSession::GetMangosString( int32 entry
) const
476 return objmgr
.GetMangosString(entry
,GetSessionDbLocaleIndex());
479 void WorldSession::Handle_NULL( WorldPacket
& recvPacket
)
481 sLog
.outError( "SESSION: received unhandled opcode %s (0x%.4X)",
482 LookupOpcodeName(recvPacket
.GetOpcode()),
483 recvPacket
.GetOpcode());
486 void WorldSession::Handle_EarlyProccess( WorldPacket
& recvPacket
)
488 sLog
.outError( "SESSION: received opcode %s (0x%.4X) that must be processed in WorldSocket::OnRead",
489 LookupOpcodeName(recvPacket
.GetOpcode()),
490 recvPacket
.GetOpcode());
493 void WorldSession::Handle_ServerSide( WorldPacket
& recvPacket
)
495 sLog
.outError( "SESSION: received server-side opcode %s (0x%.4X)",
496 LookupOpcodeName(recvPacket
.GetOpcode()),
497 recvPacket
.GetOpcode());
500 void WorldSession::Handle_Deprecated( WorldPacket
& recvPacket
)
502 sLog
.outError( "SESSION: received deprecated opcode %s (0x%.4X)",
503 LookupOpcodeName(recvPacket
.GetOpcode()),
504 recvPacket
.GetOpcode());
507 void WorldSession::SendAuthWaitQue(uint32 position
)
511 WorldPacket
packet( SMSG_AUTH_RESPONSE
, 1 );
512 packet
<< uint8( AUTH_OK
);
517 WorldPacket
packet( SMSG_AUTH_RESPONSE
, 5 );
518 packet
<< uint8( AUTH_WAIT_QUEUE
);
519 packet
<< uint32 (position
);
524 void WorldSession::LoadAccountData()
526 for (uint32 i
= 0; i
< NUM_ACCOUNT_DATA_TYPES
; ++i
)
529 m_accountData
[i
] = data
;
532 QueryResult
*result
= CharacterDatabase
.PQuery("SELECT type, time, data FROM account_data WHERE account='%u'", GetAccountId());
539 Field
*fields
= result
->Fetch();
541 uint32 type
= fields
[0].GetUInt32();
542 if(type
< NUM_ACCOUNT_DATA_TYPES
)
544 m_accountData
[type
].Time
= fields
[1].GetUInt32();
545 m_accountData
[type
].Data
= fields
[2].GetCppString();
547 } while (result
->NextRow());
552 void WorldSession::SetAccountData(uint32 type
, time_t time_
, std::string data
)
554 m_accountData
[type
].Time
= time_
;
555 m_accountData
[type
].Data
= data
;
557 uint32 acc
= GetAccountId();
559 CharacterDatabase
.BeginTransaction ();
560 CharacterDatabase
.PExecute("DELETE FROM account_data WHERE account='%u' AND type='%u'", acc
, type
);
561 CharacterDatabase
.escape_string(data
);
562 CharacterDatabase
.PExecute("INSERT INTO account_data VALUES ('%u','%u','%u','%s')", acc
, type
, (uint32
)time_
, data
.c_str());
563 CharacterDatabase
.CommitTransaction ();
566 void WorldSession::ReadMovementInfo(WorldPacket
&data
, MovementInfo
*mi
)
568 CHECK_PACKET_SIZE(data
, data
.rpos()+4+2+4+4+4+4+4);
577 if(mi
->flags
& MOVEMENTFLAG_ONTRANSPORT
)
579 CHECK_PACKET_SIZE(data
, data
.rpos()+8+4+4+4+4+4+1);
589 if((mi
->flags
& (MOVEMENTFLAG_SWIMMING
| MOVEMENTFLAG_FLYING2
)) || (mi
->unk1
& 0x20))
591 CHECK_PACKET_SIZE(data
, data
.rpos()+4);
595 CHECK_PACKET_SIZE(data
, data
.rpos()+4);
596 data
>> mi
->fallTime
;
598 if(mi
->flags
& MOVEMENTFLAG_JUMPING
)
600 CHECK_PACKET_SIZE(data
, data
.rpos()+4+4+4+4);
602 data
>> mi
->j_sinAngle
;
603 data
>> mi
->j_cosAngle
;
604 data
>> mi
->j_xyspeed
;
607 if(mi
->flags
& MOVEMENTFLAG_SPLINE
)
609 CHECK_PACKET_SIZE(data
, data
.rpos()+4);
614 void WorldSession::ReadAddonsInfo(WorldPacket
&data
)
616 if (data
.rpos() + 4 > data
.size())
626 uint32 pos
= data
.rpos();
628 ByteBuffer addonInfo
;
629 addonInfo
.resize(size
);
631 if (uncompress(const_cast<uint8
*>(addonInfo
.contents()), &uSize
, const_cast<uint8
*>(data
.contents() + pos
), data
.size() - pos
) == Z_OK
)
634 addonInfo
>> addonsCount
; // addons count
636 for(uint32 i
= 0; i
< addonsCount
; ++i
)
638 std::string addonName
;
642 // check next addon data format correctness
643 if(addonInfo
.rpos()+1 > addonInfo
.size())
646 addonInfo
>> addonName
;
648 // recheck next addon data format correctness
649 if(addonInfo
.rpos()+1+4+4 > addonInfo
.size())
652 addonInfo
>> enabled
>> crc
>> unk1
;
654 sLog
.outDebug("ADDON: Name: %s, Enabled: 0x%x, CRC: 0x%x, Unknown2: 0x%x", addonName
.c_str(), enabled
, crc
, unk1
);
656 m_addonsList
.push_back(AddonInfo(addonName
, enabled
, crc
));
662 if(addonInfo
.rpos() != addonInfo
.size())
663 sLog
.outDebug("packet under read!");
666 sLog
.outError("Addon packet uncompress error!");
669 void WorldSession::SendAddonsInfo()
671 unsigned char tdata
[256] =
673 0xC3, 0x5B, 0x50, 0x84, 0xB9, 0x3E, 0x32, 0x42, 0x8C, 0xD0, 0xC7, 0x48, 0xFA, 0x0E, 0x5D, 0x54,
674 0x5A, 0xA3, 0x0E, 0x14, 0xBA, 0x9E, 0x0D, 0xB9, 0x5D, 0x8B, 0xEE, 0xB6, 0x84, 0x93, 0x45, 0x75,
675 0xFF, 0x31, 0xFE, 0x2F, 0x64, 0x3F, 0x3D, 0x6D, 0x07, 0xD9, 0x44, 0x9B, 0x40, 0x85, 0x59, 0x34,
676 0x4E, 0x10, 0xE1, 0xE7, 0x43, 0x69, 0xEF, 0x7C, 0x16, 0xFC, 0xB4, 0xED, 0x1B, 0x95, 0x28, 0xA8,
677 0x23, 0x76, 0x51, 0x31, 0x57, 0x30, 0x2B, 0x79, 0x08, 0x50, 0x10, 0x1C, 0x4A, 0x1A, 0x2C, 0xC8,
678 0x8B, 0x8F, 0x05, 0x2D, 0x22, 0x3D, 0xDB, 0x5A, 0x24, 0x7A, 0x0F, 0x13, 0x50, 0x37, 0x8F, 0x5A,
679 0xCC, 0x9E, 0x04, 0x44, 0x0E, 0x87, 0x01, 0xD4, 0xA3, 0x15, 0x94, 0x16, 0x34, 0xC6, 0xC2, 0xC3,
680 0xFB, 0x49, 0xFE, 0xE1, 0xF9, 0xDA, 0x8C, 0x50, 0x3C, 0xBE, 0x2C, 0xBB, 0x57, 0xED, 0x46, 0xB9,
681 0xAD, 0x8B, 0xC6, 0xDF, 0x0E, 0xD6, 0x0F, 0xBE, 0x80, 0xB3, 0x8B, 0x1E, 0x77, 0xCF, 0xAD, 0x22,
682 0xCF, 0xB7, 0x4B, 0xCF, 0xFB, 0xF0, 0x6B, 0x11, 0x45, 0x2D, 0x7A, 0x81, 0x18, 0xF2, 0x92, 0x7E,
683 0x98, 0x56, 0x5D, 0x5E, 0x69, 0x72, 0x0A, 0x0D, 0x03, 0x0A, 0x85, 0xA2, 0x85, 0x9C, 0xCB, 0xFB,
684 0x56, 0x6E, 0x8F, 0x44, 0xBB, 0x8F, 0x02, 0x22, 0x68, 0x63, 0x97, 0xBC, 0x85, 0xBA, 0xA8, 0xF7,
685 0xB5, 0x40, 0x68, 0x3C, 0x77, 0x86, 0x6F, 0x4B, 0xD7, 0x88, 0xCA, 0x8A, 0xD7, 0xCE, 0x36, 0xF0,
686 0x45, 0x6E, 0xD5, 0x64, 0x79, 0x0F, 0x17, 0xFC, 0x64, 0xDD, 0x10, 0x6F, 0xF3, 0xF5, 0xE0, 0xA6,
687 0xC3, 0xFB, 0x1B, 0x8C, 0x29, 0xEF, 0x8E, 0xE5, 0x34, 0xCB, 0xD1, 0x2A, 0xCE, 0x79, 0xC3, 0x9A,
688 0x0D, 0x36, 0xEA, 0x01, 0xE0, 0xAA, 0x91, 0x20, 0x54, 0xF0, 0x72, 0xD8, 0x1E, 0xC7, 0x89, 0xD2
691 WorldPacket
data(SMSG_ADDON_INFO
, 4);
693 for(AddonsList::iterator itr
= m_addonsList
.begin(); itr
!= m_addonsList
.end(); ++itr
)
695 uint8 state
= (itr
->Enabled
? 2 : 1);
696 data
<< uint8(state
);
698 uint8 unk1
= (itr
->Enabled
? 1 : 0);
702 uint8 unk2
= (itr
->CRC
!= 0x4c1c776d); // If addon is Standard addon CRC
705 data
.append(tdata
, sizeof(tdata
));
710 uint8 unk3
= (itr
->Enabled
? 0 : 1);
714 // String, 256 (null terminated?)
719 m_addonsList
.clear();
722 data
<< uint32(count
);
723 /*for(uint32 i = 0; i < count; ++i)