1 | #include "IRCClient.h" |
---|
2 | #include "IRCCmd.h" |
---|
3 | #include "IRCFunc.h" |
---|
4 | #include "ObjectAccessor.h" |
---|
5 | #include "ObjectMgr.h" |
---|
6 | #include "WorldPacket.h" |
---|
7 | #include "ChannelMgr.h" |
---|
8 | #include "Config/ConfigEnv.h" |
---|
9 | #include "Channel.h" |
---|
10 | #include "World.h" |
---|
11 | |
---|
12 | IRCCmd Command; |
---|
13 | void IRCClient::Handle_IRC(std::string sData) |
---|
14 | { |
---|
15 | sLog.outDebug(sData.c_str()); |
---|
16 | // If first 5 chars are ERROR then something is wrong |
---|
17 | // either link is being closed, nickserv ghost command, etc... |
---|
18 | if(sData.substr(0, 5) == "ERROR") |
---|
19 | { |
---|
20 | Disconnect(); |
---|
21 | return; |
---|
22 | } |
---|
23 | if(sData.substr(0, 4) == "PING") |
---|
24 | { |
---|
25 | // if the first 4 characters contain PING |
---|
26 | // the server is checking if we are still alive |
---|
27 | // sen back PONG back plus whatever the server send with it |
---|
28 | SendIRC("PONG " + sData.substr(4, sData.size() - 4)); |
---|
29 | } |
---|
30 | else |
---|
31 | { |
---|
32 | // if the first line contains : its an irc message |
---|
33 | // such as private messages channel join etc. |
---|
34 | if(sData.substr(0, 1) == ":") |
---|
35 | { |
---|
36 | // find the spaces in the receieved line |
---|
37 | size_t p1 = sData.find(" "); |
---|
38 | size_t p2 = sData.find(" ", p1 + 1); |
---|
39 | // because the irc protocol uses simple spaces |
---|
40 | // to seperate data we can easy pick them out |
---|
41 | // since we know the position of the spaces |
---|
42 | std::string USR = sData.substr(1, p1 - 1); |
---|
43 | std::string CMD = sData.substr(p1 + 1, p2 - p1 - 1); |
---|
44 | // trasform the commands to lowercase to make sure they always match |
---|
45 | std::transform(CMD.begin(), CMD.end(), CMD.begin(), towlower); |
---|
46 | // Extract the username from the first part |
---|
47 | std::string szUser = GetUser(USR); |
---|
48 | // if we receieved the internet connect code |
---|
49 | // we know for sure that were in and we can |
---|
50 | // authenticate ourself. |
---|
51 | if(CMD == sIRC._ICC) |
---|
52 | { |
---|
53 | // _Auth is defined in mangosd.conf (irc.auth) |
---|
54 | // 0 do not authenticate |
---|
55 | // 1 use nickserv |
---|
56 | // 2 use quakenet |
---|
57 | // aditionally you can provide you own authentication method here |
---|
58 | switch(sIRC._Auth) |
---|
59 | { |
---|
60 | case 1: |
---|
61 | SendIRC("PRIVMSG nickserv :IDENTIFY " + sIRC._Pass); |
---|
62 | break; |
---|
63 | case 2: |
---|
64 | SendIRC("PRIVMSG nickserv :IDENTIFY " + sIRC._Auth_Nick + " " + sIRC._Pass); |
---|
65 | break; |
---|
66 | case 3: |
---|
67 | SendIRC("PRIVMSG Q@CServe.quakenet.org :AUTH " + sIRC._Nick + " " + sIRC._Pass); |
---|
68 | break; |
---|
69 | case 4: |
---|
70 | SendIRC("PRIVMSG Q@CServe.quakenet.org :AUTH " + sIRC._Auth_Nick + " " + sIRC._Pass); |
---|
71 | break; |
---|
72 | } |
---|
73 | // if we join a default channel leave this now. |
---|
74 | if(sIRC._ldefc==1) |
---|
75 | SendIRC("PART #" + sIRC._defchan); |
---|
76 | // Loop thru the channel array and send a command to join them on IRC. |
---|
77 | for(int i=1;i < sIRC._chan_count + 1;i++) |
---|
78 | { |
---|
79 | SendIRC("JOIN #" + sIRC._irc_chan[i]); |
---|
80 | } |
---|
81 | } |
---|
82 | // someone joined the channel this could be the bot or another user |
---|
83 | if(CMD == "join") |
---|
84 | { |
---|
85 | size_t p = sData.find(":", p1); |
---|
86 | std::string CHAN = sData.substr(p + 1, sData.size() - p - 2); |
---|
87 | // if the user is us it means we join the channel |
---|
88 | if ((szUser == sIRC._Nick) ) |
---|
89 | { |
---|
90 | // its us that joined the channel |
---|
91 | Send_IRC_Channel(CHAN, MakeMsg(MakeMsg(sIRC.JoinMsg, "$Ver", sIRC._Mver.c_str()), "$Trigger", sIRC._cmd_prefx.c_str()), true); |
---|
92 | } |
---|
93 | else |
---|
94 | { |
---|
95 | // if the user is not us its someone else that joins |
---|
96 | // so we construct a message and send this to the clients. |
---|
97 | // MangChat now uses Send_WoW_Channel to send to the client |
---|
98 | // this makes MangChat handle the packets instead of previously the world. |
---|
99 | if((sIRC.BOTMASK & 2) != 0) |
---|
100 | Send_WoW_Channel(GetWoWChannel(CHAN).c_str(), IRCcol2WoW(MakeMsg(MakeMsg(GetChatLine(JOIN_IRC), "$Name", szUser), "$Channel", GetWoWChannel(CHAN)))); |
---|
101 | } |
---|
102 | } |
---|
103 | // someone on irc left or quit the channel |
---|
104 | if(CMD == "part" || CMD == "quit") |
---|
105 | { |
---|
106 | size_t p3 = sData.find(" ", p2 + 1); |
---|
107 | std::string CHAN = sData.substr(p2 + 1, p3 - p2 - 1); |
---|
108 | // Logout IRC Nick From MangChat If User Leaves Or Quits IRC. |
---|
109 | if(Command.IsLoggedIn(szUser)) |
---|
110 | { |
---|
111 | _CDATA CDATA; |
---|
112 | CDATA.USER = szUser; |
---|
113 | Command.Handle_Logout(&CDATA); |
---|
114 | } |
---|
115 | // Construct a message and inform the clients on the same channel. |
---|
116 | if((sIRC.BOTMASK & 2) != 0) |
---|
117 | Send_WoW_Channel(GetWoWChannel(CHAN).c_str(), IRCcol2WoW(MakeMsg(MakeMsg(GetChatLine(LEAVE_IRC), "$Name", szUser), "$Channel", GetWoWChannel(CHAN)))); |
---|
118 | } |
---|
119 | // someone changed their nick |
---|
120 | if (CMD == "nick" && (sIRC.BOTMASK & 128) != 0) |
---|
121 | { |
---|
122 | MakeMsg(MakeMsg(GetChatLine(CHANGE_NICK), "$Name", szUser), "$NewName", sData.substr(sData.find(":", p2) + 1)); |
---|
123 | // If the user is logged in and changes their nick |
---|
124 | // then we want to either log them out or update |
---|
125 | // their nick in the bot. I chose to update the bots user list. |
---|
126 | if(Command.IsLoggedIn(szUser)) |
---|
127 | { |
---|
128 | std::string NewNick = sData.substr(sData.find(":", p2) + 1); |
---|
129 | // On freenode I noticed the server sends an extra character |
---|
130 | // at the end of the string, so we need to erase the last |
---|
131 | // character of the string. if you have a problem with getting |
---|
132 | // the last letter of your nick erased, then remove the - 1. |
---|
133 | NewNick.erase(NewNick.length() - 1, 1); |
---|
134 | |
---|
135 | for(std::list<_client*>::iterator i=Command._CLIENTS.begin(); i!=Command._CLIENTS.end();i++) |
---|
136 | { |
---|
137 | if((*i)->Name == szUser) |
---|
138 | { |
---|
139 | (*i)->Name = NewNick; |
---|
140 | sIRC.Send_IRC_Channel(NewNick.c_str(), "I Noticed You Changed Your Nick, I Have Updated My Internal Database Accordingly.", true, "NOTICE"); |
---|
141 | |
---|
142 | // Figure why not output to the logfile, makes tracing problems easier. |
---|
143 | sIRC.iLog.WriteLog(" %s : %s Changed Nick To: %s", sIRC.iLog.GetLogDateTimeStr().c_str(), szUser.c_str(), NewNick.c_str()); |
---|
144 | } |
---|
145 | } |
---|
146 | } |
---|
147 | |
---|
148 | } |
---|
149 | // someone was kicked from irc |
---|
150 | if (CMD == "kick") |
---|
151 | { |
---|
152 | // extract the details |
---|
153 | size_t p3 = sData.find(" ", p2 + 1); |
---|
154 | size_t p4 = sData.find(" ", p3 + 1); |
---|
155 | size_t p5 = sData.find(":", p4); |
---|
156 | std::string CHAN = sData.substr(p2 + 1, p3 - p2 - 1); |
---|
157 | std::string WHO = sData.substr(p3 + 1, p4 - p3 - 1); |
---|
158 | std::string BY = sData.substr(p4 + 1, sData.size() - p4 - 1); |
---|
159 | // if the one kicked was us |
---|
160 | if(WHO == sIRC._Nick) |
---|
161 | { |
---|
162 | // and autojoin is enabled |
---|
163 | // return to the channel |
---|
164 | if(sIRC._autojoinkick == 1) |
---|
165 | { |
---|
166 | SendIRC("JOIN " + CHAN); |
---|
167 | Send_IRC_Channel(CHAN, sIRC.kikmsg, true); |
---|
168 | } |
---|
169 | } |
---|
170 | else |
---|
171 | { |
---|
172 | // if it is not us who was kicked we need to inform the clients someone |
---|
173 | // was removed from the channel |
---|
174 | // construct a message and send it to the players. |
---|
175 | Send_WoW_Channel(GetWoWChannel(CHAN).c_str(), "<IRC>[" + WHO + "]: Was Kicked From " + CHAN + " By: " + szUser); |
---|
176 | } |
---|
177 | } |
---|
178 | // a private chat message was receieved. |
---|
179 | if(CMD == "privmsg" || CMD == "notice") |
---|
180 | { |
---|
181 | // extract the values |
---|
182 | size_t p = sData.find(" ", p2 + 1); |
---|
183 | std::string FROM = sData.substr(p2 + 1, p - p2 - 1); |
---|
184 | std::string CHAT = sData.substr(p + 2, sData.size() - p - 3); |
---|
185 | // if this is our username it means we recieved a PM |
---|
186 | if(FROM == sIRC._Nick) |
---|
187 | { |
---|
188 | if(CHAT.find("\001VERSION\001") < CHAT.size()) |
---|
189 | { |
---|
190 | Send_IRC_Channel(szUser, MakeMsg("\001VERSION MangChat %s ©2008 |Death|\001", "%s" , sIRC._Mver.c_str()), true, "PRIVMSG"); |
---|
191 | } |
---|
192 | // a pm is required for certain commands |
---|
193 | // such as login. to validate the command |
---|
194 | // we send it to the command class wich handles |
---|
195 | // evrything else. |
---|
196 | Command.IsValid(szUser, FROM, CHAT, CMD); |
---|
197 | } |
---|
198 | else |
---|
199 | { |
---|
200 | // if our name is not in it, it means we receieved chat on one of the channels |
---|
201 | // magchat is in. the first thing we do is check if it is a command or not |
---|
202 | if(!Command.IsValid(szUser, FROM, CHAT, CMD)) |
---|
203 | { |
---|
204 | Send_WoW_Channel(GetWoWChannel(FROM).c_str(), IRCcol2WoW(MakeMsg(MakeMsg(GetChatLine(IRC_WOW), "$Name", szUser), "$Msg", CHAT))); |
---|
205 | } |
---|
206 | // if we indeed receieved a command we do not want to display this to the players |
---|
207 | // so only incanse the isvalid command returns false it will be sent to all player. |
---|
208 | // the isvalid function will automaitcly process the command on true. |
---|
209 | } |
---|
210 | } |
---|
211 | if(CMD == "mode") |
---|
212 | { |
---|
213 | // extract the mode details |
---|
214 | size_t p3 = sData.find(" ", p2 + 1); |
---|
215 | size_t p4 = sData.find(" ", p3 + 1); |
---|
216 | size_t p5 = sData.find(" ", p4 + 1); |
---|
217 | std::string CHAN = sData.substr(p2 + 1, p3 - p2 - 1); |
---|
218 | std::string MODE = sData.substr(p3 + 1, p4 - p3 - 1); |
---|
219 | std::string NICK = sData.substr(p4 + 1, p5 - p4 - 1); |
---|
220 | bool _AmiOp; |
---|
221 | _AmiOp = false; |
---|
222 | //A mode was changed on us |
---|
223 | if(NICK.c_str() == sIRC._Nick) |
---|
224 | _AmiOp = true; |
---|
225 | |
---|
226 | } |
---|
227 | } |
---|
228 | } |
---|
229 | } |
---|
230 | |
---|
231 | // This function is called in Channel.h |
---|
232 | // based on nAction it will inform the people on |
---|
233 | // irc when someone leaves one of the game channels. |
---|
234 | // nAction is based on the struct CACTION |
---|
235 | void IRCClient::Handle_WoW_Channel(std::string Channel, Player *plr, int nAction) |
---|
236 | { |
---|
237 | // make sure that we are connected |
---|
238 | if(sIRC.Connected && (sIRC.BOTMASK & 1)!= 0) |
---|
239 | { |
---|
240 | if(Channel_Valid(Channel)) |
---|
241 | { |
---|
242 | std::string GMRank = ""; |
---|
243 | std::string pname = plr->GetName(); |
---|
244 | bool DoGMAnnounce = false; |
---|
245 | if (plr->GetSession()->GetSecurity() > 0 && (sIRC.BOTMASK & 8)!= 0) |
---|
246 | DoGMAnnounce = true; |
---|
247 | if (plr->isGameMaster() && (sIRC.BOTMASK & 16)!= 0) |
---|
248 | DoGMAnnounce = true; |
---|
249 | if(DoGMAnnounce) |
---|
250 | { |
---|
251 | switch(plr->GetSession()->GetSecurity()) //switch case to determine what rank the "gm" is |
---|
252 | { |
---|
253 | case 0: GMRank = "";break; |
---|
254 | case 1: GMRank = "\0037"+sIRC.ojGM1;break; |
---|
255 | case 2: GMRank = "\0037"+sIRC.ojGM2;break; |
---|
256 | case 3: GMRank = "\0037"+sIRC.ojGM3;break; |
---|
257 | case 4: GMRank = "\0037"+sIRC.ojGM4;break; |
---|
258 | case 5: GMRank = "\0037"+sIRC.ojGM5;break; |
---|
259 | } |
---|
260 | } |
---|
261 | std::string ChatTag = ""; |
---|
262 | switch (plr->GetTeam()) |
---|
263 | { |
---|
264 | case 67:ChatTag.append("\0034");break; //horde |
---|
265 | case 469:ChatTag.append("\00312");break; //alliance |
---|
266 | } |
---|
267 | std::string query = "INSERT INTO `IRC_Inchan` VALUES (%d,'"+pname+"','"+Channel+"')"; |
---|
268 | std::string lchan = "DELETE FROM `IRC_Inchan` WHERE `guid` = %d AND `channel` = '"+Channel+"'"; |
---|
269 | switch(nAction) |
---|
270 | { |
---|
271 | case CHANNEL_JOIN: |
---|
272 | Send_IRC_Channel(GetIRCChannel(Channel), MakeMsg(MakeMsg(MakeMsg(GetChatLine(JOIN_WOW), "$Name", ChatTag + plr->GetName()), "$Channel", Channel), "$GM", GMRank)); |
---|
273 | WorldDatabase.PExecute(lchan.c_str(), plr->GetGUID()); |
---|
274 | WorldDatabase.PExecute(query.c_str(), plr->GetGUID()); |
---|
275 | break; |
---|
276 | case CHANNEL_LEAVE: |
---|
277 | Send_IRC_Channel(GetIRCChannel(Channel), MakeMsg(MakeMsg(MakeMsg(GetChatLine(LEAVE_WOW), "$Name", ChatTag + plr->GetName()), "$Channel", Channel), "$GM", GMRank)); |
---|
278 | WorldDatabase.PExecute(lchan.c_str(), plr->GetGUID()); |
---|
279 | break; |
---|
280 | } |
---|
281 | } |
---|
282 | } |
---|
283 | } |
---|
284 | |
---|
285 | // This function sends chat to a irc channel or user |
---|
286 | // to prevent the # beeing appended to send a msg to a user |
---|
287 | // set the NoPrefix to true |
---|
288 | void IRCClient::Send_IRC_Channel(std::string sChannel, std::string sMsg, bool NoPrefix, std::string nType) |
---|
289 | { |
---|
290 | std::string mType = "PRIVMSG"; |
---|
291 | if(Command.MakeUpper(nType.c_str()) == "NOTICE") |
---|
292 | mType = "NOTICE"; |
---|
293 | if(Command.MakeUpper(nType.c_str()) == "ERROR" && (sIRC.BOTMASK & 32)!= 0) |
---|
294 | mType = "NOTICE"; |
---|
295 | if(sIRC.Connected) |
---|
296 | { |
---|
297 | if(NoPrefix) |
---|
298 | SendIRC(mType + " " + sChannel + " :" + sMsg); |
---|
299 | else |
---|
300 | SendIRC(mType + " #" + sChannel + " :" + sMsg); |
---|
301 | } |
---|
302 | } |
---|
303 | |
---|
304 | // This function sends a message to all irc channels |
---|
305 | // that mangchat has in its configuration |
---|
306 | void IRCClient::Send_IRC_Channels(std::string sMsg) |
---|
307 | { |
---|
308 | for(int i=1;i < sIRC._chan_count + 1;i++) |
---|
309 | Send_IRC_Channel(sIRC._irc_chan[i], sMsg); |
---|
310 | } |
---|
311 | |
---|
312 | // This function is called in ChatHandler.cpp, any channel chat from wow will come |
---|
313 | // to this function, validates the channel and constructs a message that is send to IRC |
---|
314 | void IRCClient::Send_WoW_IRC(Player *plr, std::string Channel, std::string Msg) |
---|
315 | { |
---|
316 | // Check if the channel exist in our configuration |
---|
317 | if(Channel_Valid(Channel) && Msg.substr(0, 1) != ".") |
---|
318 | Send_IRC_Channel(GetIRCChannel(Channel), MakeMsgP(WOW_IRC, Msg, plr)); |
---|
319 | } |
---|
320 | |
---|
321 | void IRCClient::Send_WoW_Player(std::string sPlayer, std::string sMsg) |
---|
322 | { |
---|
323 | normalizePlayerName(sPlayer); |
---|
324 | if (Player* plr = ObjectAccessor::Instance().FindPlayerByName(sPlayer.c_str())) |
---|
325 | Send_WoW_Player(plr, sMsg); |
---|
326 | } |
---|
327 | |
---|
328 | void IRCClient::Send_WoW_Player(Player *plr, string sMsg) |
---|
329 | { |
---|
330 | WorldPacket data(SMSG_MESSAGECHAT, 200); |
---|
331 | data << (uint8)CHAT_MSG_SYSTEM; |
---|
332 | data << (uint32)LANG_UNIVERSAL; |
---|
333 | data << (uint64)plr->GetGUID(); |
---|
334 | data << (uint32)0; |
---|
335 | data << (uint64)plr->GetGUID(); |
---|
336 | data << (uint32)(sMsg.length()+1); |
---|
337 | data << sMsg; |
---|
338 | data << (uint8)0; |
---|
339 | plr->GetSession()->SendPacket(&data); |
---|
340 | } |
---|
341 | |
---|
342 | // This function will construct and send a packet to all players |
---|
343 | // on the given channel ingame. (previuosly found in world.cpp) |
---|
344 | // it loops thru all sessions and checks if they are on the channel |
---|
345 | // if so construct a packet and send it. |
---|
346 | void IRCClient::Send_WoW_Channel(const char *channel, std::string chat) |
---|
347 | { |
---|
348 | if(!(strlen(channel) > 0)) |
---|
349 | return; |
---|
350 | |
---|
351 | #ifdef USE_UTF8 |
---|
352 | std::string chat2 = chat; |
---|
353 | if(ConvertUTF8(chat2.c_str(), chat2)) |
---|
354 | chat = chat2; |
---|
355 | #endif |
---|
356 | |
---|
357 | HashMapHolder<Player>::MapType& m = ObjectAccessor::Instance().GetPlayers(); |
---|
358 | for(HashMapHolder<Player>::MapType::iterator itr = m.begin(); itr != m.end(); ++itr) |
---|
359 | { |
---|
360 | if (itr->second && itr->second->GetSession()->GetPlayer() && itr->second->GetSession()->GetPlayer()->IsInWorld()) |
---|
361 | { |
---|
362 | if(ChannelMgr* cMgr = channelMgr(itr->second->GetSession()->GetPlayer()->GetTeam())) |
---|
363 | { |
---|
364 | if(Channel *chn = cMgr->GetChannel(channel, itr->second->GetSession()->GetPlayer())) |
---|
365 | { |
---|
366 | WorldPacket data; |
---|
367 | data.Initialize(SMSG_MESSAGECHAT); |
---|
368 | data << (uint8)CHAT_MSG_CHANNEL; |
---|
369 | data << (uint32)LANG_UNIVERSAL; |
---|
370 | data << (uint64)0; |
---|
371 | data << (uint32)0; |
---|
372 | data << channel; |
---|
373 | data << (uint64)0; |
---|
374 | data << (uint32) (strlen(chat.c_str()) + 1); |
---|
375 | data << IRCcol2WoW(chat.c_str()); |
---|
376 | data << (uint8)0; |
---|
377 | itr->second->GetSession()->SendPacket(&data); |
---|
378 | } |
---|
379 | } |
---|
380 | } |
---|
381 | } |
---|
382 | } |
---|
383 | |
---|
384 | void IRCClient::Send_WoW_System(std::string Message) |
---|
385 | { |
---|
386 | HashMapHolder<Player>::MapType& m = ObjectAccessor::Instance().GetPlayers(); |
---|
387 | for(HashMapHolder<Player>::MapType::iterator itr = m.begin(); itr != m.end(); ++itr) |
---|
388 | { |
---|
389 | if (itr->second && itr->second->GetSession()->GetPlayer() && itr->second->GetSession()->GetPlayer()->IsInWorld()) |
---|
390 | { |
---|
391 | WorldPacket data; |
---|
392 | data.Initialize(CHAT_MSG_SYSTEM); |
---|
393 | data << (uint8)CHAT_MSG_SYSTEM; |
---|
394 | data << (uint32)LANG_UNIVERSAL; |
---|
395 | data << (uint64)0; |
---|
396 | data << (uint32)0; |
---|
397 | data << (uint64)0; |
---|
398 | data << (uint32) (strlen(Message.c_str()) + 1); |
---|
399 | data << Message.c_str(); |
---|
400 | data << (uint8)0; |
---|
401 | itr->second->GetSession()->SendPacket(&data); |
---|
402 | } |
---|
403 | } |
---|
404 | } |
---|
405 | void IRCClient::ResetIRC() |
---|
406 | { |
---|
407 | SendData("QUIT"); |
---|
408 | Disconnect(); |
---|
409 | } |
---|
410 | |
---|
411 | #define CHAT_INVITE_NOTICE 0x18 |
---|
412 | |
---|
413 | // this function should be called on player login Player::AddToWorld |
---|
414 | void IRCClient::AutoJoinChannel(Player *plr) |
---|
415 | { |
---|
416 | //this will work if at least 1 player is logged in regrdless if he is on the channel or not |
---|
417 | // the first person that login empty server is the one with bad luck and wont be invited, |
---|
418 | // if at least 1 player is online the player will be inited to the chanel |
---|
419 | |
---|
420 | std::string m_name = sIRC.ajchan; |
---|
421 | WorldPacket data; |
---|
422 | data.Initialize(SMSG_CHANNEL_NOTIFY, 1+m_name.size()+1); |
---|
423 | data << uint8(CHAT_INVITE_NOTICE); |
---|
424 | data << m_name.c_str(); |
---|
425 | |
---|
426 | HashMapHolder<Player>::MapType& m = ObjectAccessor::Instance().GetPlayers(); |
---|
427 | for(HashMapHolder<Player>::MapType::iterator itr = m.begin(); itr != m.end(); ++itr) |
---|
428 | { |
---|
429 | if (itr->second && itr->second->GetSession()->GetPlayer() && itr->second->GetSession()->GetPlayer()->IsInWorld()) |
---|
430 | { |
---|
431 | data << uint64(itr->second->GetGUID()); |
---|
432 | break; |
---|
433 | } |
---|
434 | } |
---|
435 | plr->GetSession()->SendPacket(&data); |
---|
436 | } |
---|