|
34 | 34 |
|
35 | 35 | #include "Chat.h" |
36 | 36 | #include "ObjectMgr.h" |
| 37 | +#include "Opcodes.h" |
37 | 38 | #include "World.h" |
| 39 | +#include "WorldPacket.h" |
38 | 40 | #include "GMTicketMgr.h" |
39 | 41 | #include "Mail.h" |
40 | 42 |
|
| 43 | +#include <iomanip> |
| 44 | + |
41 | 45 | // show ticket (helper) |
42 | 46 | void ChatHandler::ShowTicket(GMTicket const* ticket) |
43 | 47 | { |
@@ -673,3 +677,211 @@ bool ChatHandler::HandleTickerSurveyClose(char* args) |
673 | 677 |
|
674 | 678 | return true; |
675 | 679 | } |
| 680 | + |
| 681 | +static std::string TicketEscapeField(const std::string& in) |
| 682 | +{ |
| 683 | + std::string out; |
| 684 | + out.reserve(in.size()); |
| 685 | + for (size_t i = 0; i < in.size(); ++i) |
| 686 | + { |
| 687 | + char c = in[i]; |
| 688 | + switch (c) |
| 689 | + { |
| 690 | + case '\\': out += "\\\\"; break; |
| 691 | + case '\t': out += "\\t"; break; |
| 692 | + case '\n': out += "\\n"; break; |
| 693 | + case '\r': out += "\\r"; break; |
| 694 | + default: out += c; |
| 695 | + } |
| 696 | + } |
| 697 | + return out; |
| 698 | +} |
| 699 | + |
| 700 | +void ChatHandler::SendTicketPayload(char kind, const std::string& body) |
| 701 | +{ |
| 702 | + if (!m_session || !m_session->GetPlayer()) |
| 703 | + { |
| 704 | + return; |
| 705 | + } |
| 706 | + |
| 707 | + const size_t CHUNK = 200; |
| 708 | + const size_t MAXCHUNKS = 16; |
| 709 | + |
| 710 | + std::string b = body; |
| 711 | + if (b.size() > CHUNK * MAXCHUNKS) |
| 712 | + { |
| 713 | + b.resize(CHUNK * MAXCHUNKS); |
| 714 | + } |
| 715 | + |
| 716 | + size_t total = b.empty() ? 1 : ((b.size() + CHUNK - 1) / CHUNK); |
| 717 | + ObjectGuid sender = m_session->GetPlayer()->GetObjectGuid(); |
| 718 | + |
| 719 | + for (size_t seq = 1; seq <= total; ++seq) |
| 720 | + { |
| 721 | + std::string chunk = b.empty() ? std::string() : b.substr((seq - 1) * CHUNK, CHUNK); |
| 722 | + std::ostringstream framed; |
| 723 | + framed << "ZGMTKT\t" << kind << "\t" << seq << "\t" << total << "\t" << chunk; |
| 724 | + std::string msg = framed.str(); |
| 725 | + |
| 726 | + WorldPacket data(SMSG_MESSAGECHAT, msg.size() + 32); |
| 727 | + data << uint8(CHAT_MSG_WHISPER); |
| 728 | + data << int32(LANG_ADDON); |
| 729 | + data << sender; |
| 730 | + data << uint32(msg.length() + 1); |
| 731 | + data << msg; |
| 732 | + data << uint8(0); |
| 733 | + m_session->SendPacket(&data); |
| 734 | + } |
| 735 | +} |
| 736 | + |
| 737 | +bool ChatHandler::HandleTicketPayloadPingCommand(char* /*args*/) |
| 738 | +{ |
| 739 | + // Stress the whisper channel exactly where real payloads are fragile: |
| 740 | + // a 2-chunk body whose 200-byte boundary lands on a space (chunk 1 ends in a |
| 741 | + // space) and whose last chunk ends in a trailing space, plus an empty-body 'L' |
| 742 | + // frame (empty final chunk). The spike confirms these survive intact. |
| 743 | + std::string body(199, 'a'); |
| 744 | + body += ' '; // byte 200 -> chunk 1 ends in a space |
| 745 | + body += std::string(199, 'b'); |
| 746 | + body += ' '; // trailing space on the final chunk |
| 747 | + SendTicketPayload('P', body); |
| 748 | + SendTicketPayload('L', ""); // empty-body frame (empty chunk, total=1) |
| 749 | + return true; |
| 750 | +} |
| 751 | + |
| 752 | +bool ChatHandler::HandleTicketPayloadShowCommand(char* args) |
| 753 | +{ |
| 754 | + char* px = ExtractLiteralArg(&args); |
| 755 | + if (!px) |
| 756 | + { |
| 757 | + return false; |
| 758 | + } |
| 759 | + |
| 760 | + GMTicket* ticket = NULL; |
| 761 | + uint32 num; |
| 762 | + if (ExtractUInt32(&px, num)) |
| 763 | + { |
| 764 | + if (num == 0) |
| 765 | + { |
| 766 | + return false; |
| 767 | + } |
| 768 | + ticket = sTicketMgr.GetGMTicket(num); |
| 769 | + if (!ticket) |
| 770 | + { |
| 771 | + PSendSysMessage(LANG_COMMAND_TICKETNOTEXIST, num); |
| 772 | + SetSentErrorMessage(true); |
| 773 | + return false; |
| 774 | + } |
| 775 | + } |
| 776 | + else |
| 777 | + { |
| 778 | + ObjectGuid target_guid; std::string target_name; |
| 779 | + if (!ExtractPlayerTarget(&px, NULL, &target_guid, &target_name)) |
| 780 | + { |
| 781 | + return false; |
| 782 | + } |
| 783 | + ticket = sTicketMgr.GetGMTicket(target_guid); |
| 784 | + if (!ticket) |
| 785 | + { |
| 786 | + PSendSysMessage(LANG_COMMAND_TICKETNOTEXIST_NAME, target_name.c_str()); |
| 787 | + SetSentErrorMessage(true); |
| 788 | + return false; |
| 789 | + } |
| 790 | + } |
| 791 | + |
| 792 | + std::string name; bool online; float x, y, z; uint32 mapId; |
| 793 | + bool posValid = ResolveTicketCreator(ticket->GetPlayerGuid(), name, online, x, y, z, mapId); |
| 794 | + |
| 795 | + std::string text = ticket->GetText(); |
| 796 | + if (text.size() > 500) |
| 797 | + { |
| 798 | + text.resize(500); |
| 799 | + } |
| 800 | + std::string resp = ticket->GetResponse(); |
| 801 | + if (resp.size() > 500) |
| 802 | + { |
| 803 | + resp.resize(500); |
| 804 | + } |
| 805 | + |
| 806 | + std::ostringstream b; |
| 807 | + b << ticket->GetId() << "\t" << TicketEscapeField(name) << "\t" << (online ? "1" : "0") |
| 808 | + << "\t" << (posValid ? "1" : "0") |
| 809 | + << "\t" << std::fixed << std::setprecision(2) << x << "\t" << y << "\t" << z |
| 810 | + << "\t" << mapId << "\t" << TicketEscapeField(text) << "\t" << TicketEscapeField(resp); |
| 811 | + SendTicketPayload('D', b.str()); |
| 812 | + return true; |
| 813 | +} |
| 814 | + |
| 815 | +bool ChatHandler::ResolveTicketCreator(ObjectGuid guid, std::string& name, bool& online, |
| 816 | + float& x, float& y, float& z, uint32& mapId) |
| 817 | +{ |
| 818 | + if (Player* p = sObjectMgr.GetPlayer(guid)) |
| 819 | + { |
| 820 | + online = true; |
| 821 | + name = p->GetName(); |
| 822 | + x = p->GetPositionX(); y = p->GetPositionY(); z = p->GetPositionZ(); |
| 823 | + mapId = p->GetMapId(); |
| 824 | + return true; |
| 825 | + } |
| 826 | + |
| 827 | + online = false; x = 0.0f; y = 0.0f; z = 0.0f; mapId = 0; name = "<offline>"; |
| 828 | + QueryResult* res = CharacterDatabase.PQuery( |
| 829 | + "SELECT `name`, `position_x`, `position_y`, `position_z`, `map` " |
| 830 | + "FROM `characters` WHERE `guid` = '%u'", guid.GetCounter()); |
| 831 | + if (res) |
| 832 | + { |
| 833 | + Field* fld = res->Fetch(); |
| 834 | + name = fld[0].GetCppString(); |
| 835 | + x = fld[1].GetFloat(); y = fld[2].GetFloat(); z = fld[3].GetFloat(); |
| 836 | + mapId = fld[4].GetUInt32(); |
| 837 | + delete res; |
| 838 | + return true; |
| 839 | + } |
| 840 | + return false; |
| 841 | +} |
| 842 | + |
| 843 | +bool ChatHandler::HandleTicketPayloadListCommand(char* /*args*/) |
| 844 | +{ |
| 845 | + std::string body; |
| 846 | + // Cap like HandleTicketListCommand so the joined body stays well under 200*16 bytes; |
| 847 | + // an uncapped body would hit the SendTicketPayload cap and truncate the last record. |
| 848 | + uint16 numToShow = std::min(uint16(sTicketMgr.GetTicketCount()), |
| 849 | + uint16(sWorld.getConfig(CONFIG_UINT32_GM_TICKET_LIST_SIZE))); |
| 850 | + for (uint16 i = 0; i < numToShow; ++i) |
| 851 | + { |
| 852 | + GMTicket* t = sTicketMgr.GetGMTicketByOrderPos(i); |
| 853 | + if (!t) |
| 854 | + { |
| 855 | + continue; |
| 856 | + } |
| 857 | + |
| 858 | + std::string name; bool online; float x, y, z; uint32 mapId; |
| 859 | + ResolveTicketCreator(t->GetPlayerGuid(), name, online, x, y, z, mapId); |
| 860 | + |
| 861 | + std::string snippet = t->GetText(); |
| 862 | + if (snippet.size() > 40) |
| 863 | + { |
| 864 | + snippet.resize(40); |
| 865 | + } |
| 866 | + |
| 867 | + uint32 age = uint32(time(NULL) - t->GetLastUpdate()); |
| 868 | + std::ostringstream rec; |
| 869 | + rec << t->GetId() << "\t" << TicketEscapeField(name) << "\t" << age << "\t" |
| 870 | + << (online ? "1" : "0") << "\t" << TicketEscapeField(snippet); |
| 871 | + std::string recStr = rec.str(); |
| 872 | + // Bound the joined body by BYTES, not only record count: SendTicketPayload caps at |
| 873 | + // 200*16=3200 bytes and would resize()-truncate the tail record into an unparseable |
| 874 | + // (<5-field) fragment that the addon drops. Stop cleanly before that can happen. |
| 875 | + if (!body.empty() && body.size() + 1 + recStr.size() > 3100) |
| 876 | + { |
| 877 | + break; |
| 878 | + } |
| 879 | + if (!body.empty()) |
| 880 | + { |
| 881 | + body += "\n"; |
| 882 | + } |
| 883 | + body += recStr; |
| 884 | + } |
| 885 | + SendTicketPayload('L', body); |
| 886 | + return true; |
| 887 | +} |
0 commit comments