diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp index 31df240943f..b2e08bab7db 100644 --- a/src/server/game/Server/Protocol/Opcodes.cpp +++ b/src/server/game/Server/Protocol/Opcodes.cpp @@ -1741,7 +1741,11 @@ inline std::string GetOpcodeNameForLoggingImpl(T id) if (static_cast(id) < NUM_OPCODE_HANDLERS) { if (OpcodeHandler const* handler = opcodeTable[id]) + { ss << handler->Name; + if (opcode & COMPRESSED_OPCODE_MASK) + ss << "_COMPRESSED"; + } else ss << "UNKNOWN OPCODE"; } diff --git a/src/server/game/Server/Protocol/Opcodes.h b/src/server/game/Server/Protocol/Opcodes.h index 2fa0a51665c..edb8d127290 100644 --- a/src/server/game/Server/Protocol/Opcodes.h +++ b/src/server/game/Server/Protocol/Opcodes.h @@ -1380,8 +1380,11 @@ enum Opcodes : uint16 enum OpcodeMisc : uint16 { - NUM_OPCODE_HANDLERS = (0x7FFF + 1), - NULL_OPCODE = 0x0000 + MAX_OPCODE = 0x7FFF, + NUM_OPCODE_HANDLERS = (MAX_OPCODE + 1), + UNKNOWN_OPCODE = 0xFFFF, + NULL_OPCODE = 0, + COMPRESSED_OPCODE_MASK = 0x8000 }; typedef Opcodes OpcodeClient; diff --git a/src/server/game/Server/WorldPacket.cpp b/src/server/game/Server/WorldPacket.cpp new file mode 100644 index 00000000000..edc13bd5c73 --- /dev/null +++ b/src/server/game/Server/WorldPacket.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2008-2019 TrinityCore + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "WorldPacket.h" +#include "Errors.h" +#include "Log.h" +#include "World.h" +#include + +//! Compresses packet in place +void WorldPacket::Compress(z_stream* compressionStream) +{ + uint16 uncompressedOpcode = GetOpcode(); + if (uncompressedOpcode & COMPRESSED_OPCODE_MASK) + { + TC_LOG_ERROR("network", "Packet with opcode 0x%04X is already compressed!", uncompressedOpcode); + return; + } + + Opcodes opcode = Opcodes(uncompressedOpcode | COMPRESSED_OPCODE_MASK); + uint32 size = wpos(); + uint32 destsize = compressBound(size); + + std::vector storage(destsize); + + _compressionStream = compressionStream; + Compress(static_cast(&storage[0]), &destsize, static_cast(contents()), size); + if (destsize == 0) + return; + + clear(); + reserve(destsize + sizeof(uint32)); + *this << uint32(size); + append(&storage[0], destsize); + SetOpcode(opcode); + TC_LOG_INFO("network", "%s (len %u) successfully compressed to %04X (len %u)", GetOpcodeNameForLogging(Opcodes(uncompressedOpcode)).c_str(), size, opcode, destsize); +} + +//! Compresses another packet and stores it in self (source left intact) +void WorldPacket::Compress(z_stream* compressionStream, WorldPacket const* source) +{ + ASSERT(source != this); + + uint16 uncompressedOpcode = source->GetOpcode(); + if (uncompressedOpcode & COMPRESSED_OPCODE_MASK) + { + TC_LOG_ERROR("network", "Packet with opcode 0x%04X is already compressed!", uncompressedOpcode); + return; + } + + Opcodes opcode = Opcodes(uncompressedOpcode | COMPRESSED_OPCODE_MASK); + uint32 size = source->size(); + uint32 destsize = compressBound(size); + + size_t sizePos = 0; + resize(destsize + sizeof(uint32)); + + _compressionStream = compressionStream; + Compress(static_cast(&_storage[0] + sizeof(uint32)), &destsize, static_cast(source->contents()), size); + if (destsize == 0) + return; + + put(sizePos, size); + resize(destsize + sizeof(uint32)); + + SetOpcode(opcode); + + TC_LOG_INFO("network", "%s (len %u) successfully compressed to %04X (len %u)", GetOpcodeNameForLogging(Opcodes(uncompressedOpcode)).c_str(), size, opcode, destsize); +} + +void WorldPacket::Compress(void* dst, uint32 *dst_size, const void* src, int src_size) +{ + _compressionStream->next_out = (Bytef*)dst; + _compressionStream->avail_out = *dst_size; + _compressionStream->next_in = (Bytef*)src; + _compressionStream->avail_in = (uInt)src_size; + + int32 z_res = deflate(_compressionStream, Z_SYNC_FLUSH); + if (z_res != Z_OK) + { + TC_LOG_ERROR("network", "Can't compress packet (zlib: deflate) Error code: %i (%s, msg: %s)", z_res, zError(z_res), _compressionStream->msg); + *dst_size = 0; + return; + } + + if (_compressionStream->avail_in != 0) + { + TC_LOG_ERROR("network", "Can't compress packet (zlib: deflate not greedy)"); + *dst_size = 0; + return; + } + + *dst_size -= _compressionStream->avail_out; +} diff --git a/src/server/game/Server/WorldPacket.h b/src/server/game/Server/WorldPacket.h index 4cdb4c06a3f..0d753d18f9e 100644 --- a/src/server/game/Server/WorldPacket.h +++ b/src/server/game/Server/WorldPacket.h @@ -24,11 +24,13 @@ #include "ByteBuffer.h" #include +struct z_stream_s; + class WorldPacket : public ByteBuffer { public: // just container for later use - WorldPacket() : ByteBuffer(0), m_opcode(NULL_OPCODE) + WorldPacket() : ByteBuffer(0), m_opcode(UNKNOWN_OPCODE) { } @@ -80,11 +82,17 @@ class WorldPacket : public ByteBuffer uint16 GetOpcode() const { return m_opcode; } void SetOpcode(uint16 opcode) { m_opcode = opcode; } + bool IsCompressed() const { return (m_opcode & COMPRESSED_OPCODE_MASK) != 0; } + void Compress(z_stream_s* compressionStream); + void Compress(z_stream_s* compressionStream, WorldPacket const* source); + std::chrono::steady_clock::time_point GetReceivedTime() const { return m_receivedTime; } protected: uint16 m_opcode; + void Compress(void* dst, uint32 *dst_size, const void* src, int src_size); + z_stream_s* _compressionStream; std::chrono::steady_clock::time_point m_receivedTime; // only set for a specific set of opcodes, for performance reasons. }; diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index 96ddee8af69..1c88fff1ab6 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -222,10 +222,16 @@ void WorldSession::SendPacket(WorldPacket const* packet, bool forced /*= false*/ if (!m_Socket) return; - ASSERT(packet->GetOpcode() != NULL_OPCODE); - - if (!m_Socket) + if (packet->GetOpcode() == NULL_OPCODE) + { + TC_LOG_ERROR("network.opcode", "Prevented sending of NULL_OPCODE to %s", GetPlayerInfo().c_str()); return; + } + else if (packet->GetOpcode() == UNKNOWN_OPCODE) + { + TC_LOG_ERROR("network.opcode", "Prevented sending of UNKNOWN_OPCODE to %s", GetPlayerInfo().c_str()); + return; + } if (!forced) { diff --git a/src/server/game/Server/WorldSocket.cpp b/src/server/game/Server/WorldSocket.cpp index c23221bf08d..61a88bba301 100644 --- a/src/server/game/Server/WorldSocket.cpp +++ b/src/server/game/Server/WorldSocket.cpp @@ -104,6 +104,9 @@ bool WorldSocket::Update() MessageBuffer buffer(_sendBufferSize); while (_bufferQueue.Dequeue(queued)) { + if (_worldSession && queued->size() > 0x400 && !queued->IsCompressed()) + queued->Compress(_worldSession->GetCompressionStream()); + ServerPktHeader header(queued->size() + 2, queued->GetOpcode()); if (queued->NeedsEncryption()) _authCrypt.EncryptSend(header.header, header.getHeaderLength());