From 43e97389afb08d5f22c5e796ba7072f15a09baed Mon Sep 17 00:00:00 2001 From: sruon Date: Tue, 27 Jan 2026 20:16:43 -0700 Subject: [PATCH] PacketCap module to save clients packets --- modules/tools/packetcap/packetcap.cpp | 144 ++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 modules/tools/packetcap/packetcap.cpp diff --git a/modules/tools/packetcap/packetcap.cpp b/modules/tools/packetcap/packetcap.cpp new file mode 100644 index 00000000000..044f936870f --- /dev/null +++ b/modules/tools/packetcap/packetcap.cpp @@ -0,0 +1,144 @@ +/************************************************************************ + * PacketCap + * + * Captures packets for opted-in characters to assist with debugging. + * Set charvar 'PacketCap' to 1 to enable capture for a character. + * + * File format: DD TT TT TT TT TT TT TT TT SS SS [PP PP PP PP ..] + * + * DD = direction + * TT = timestamp + * SS = packet size + * PP = packet payload + ************************************************************************/ + +#include "common/settings.h" +#include "map/entities/charentity.h" +#include "map/packets/basic.h" +#include "map/utils/moduleutils.h" + +#include +#include +#include +#include + +namespace +{ +constexpr uint8 DIRECTION_C2S = 0x00; // Client to Server (incoming) +constexpr uint8 DIRECTION_S2C = 0x01; // Server to Client (outgoing) +constexpr auto DEFAULT_CAPTURE_DIR = "./log/packets"; +} // namespace + +class PacketCapModule : public CPPModule +{ +public: + void OnInit() override + { + captureDir_ = settings::get("logging.PACKET_CAPTURE_DIR"); + if (captureDir_.empty()) + { + captureDir_ = DEFAULT_CAPTURE_DIR; + } + + std::error_code ec; + std::filesystem::create_directories(captureDir_, ec); + if (ec) + { + ShowErrorFmt("PacketCap: Failed to create directory {}: {}", captureDir_, ec.message()); + return; + } + + ShowInfoFmt("PacketCap module loaded (dir: {})", captureDir_); + } + + void OnCharZoneIn(CCharEntity* PChar) override + { + if (!PChar) + { + return; + } + + if (PChar->getCharVar("PacketCap") == 1) + { + auto now = std::chrono::floor(std::chrono::system_clock::now()); + auto filename = fmt::format("{}/{}_{:%Y%m%d_%H%M%S}.cap", captureDir_, PChar->id, now); + auto file = std::make_unique(filename, std::ios::binary); + + if (!file->is_open()) + { + ShowWarningFmt("PacketCap: Failed to open {} for {} ({})", filename, PChar->getName(), PChar->id); + return; + } + + captureFiles_[PChar->id] = { filename, std::move(file) }; + ShowInfoFmt("PacketCap: Enabled capture for {} ({}) -> {}", PChar->getName(), PChar->id, filename); + } + } + + void OnCharZoneOut(CCharEntity* PChar) override + { + if (!PChar) + { + return; + } + + if (const auto it = captureFiles_.find(PChar->id); it != captureFiles_.end()) + { + it->second.file->close(); + ShowInfoFmt("PacketCap: Disabled capture for {} ({}) -> {}", PChar->getName(), PChar->id, it->second.filename); + captureFiles_.erase(it); + } + } + + void OnPushPacket(CCharEntity* PChar, const std::unique_ptr& packet) override + { + if (!PChar) + { + return; + } + + if (const auto it = captureFiles_.find(PChar->id); it != captureFiles_.end()) + { + writePacket(*it->second.file, DIRECTION_S2C, *packet, packet->getSize()); + } + } + + auto OnIncomingPacket(MapSession* session, CCharEntity* PChar, CBasicPacket& packet) -> bool override + { + if (!PChar) + { + return false; + } + + if (const auto it = captureFiles_.find(PChar->id); it != captureFiles_.end()) + { + writePacket(*it->second.file, DIRECTION_C2S, packet, packet.getSize()); + } + + return false; + } + +private: + struct CaptureFile + { + std::string filename; + std::unique_ptr file; + }; + + std::string captureDir_; + std::unordered_map captureFiles_; + + static void writePacket(std::ofstream& file, const uint8 direction, const uint8* data, const std::size_t size) + { + const auto now = std::chrono::system_clock::now(); + const uint64 timestamp = std::chrono::duration_cast(now.time_since_epoch()).count(); + const uint16 packetSize = static_cast(size); + + file.write(reinterpret_cast(&direction), 1); + file.write(reinterpret_cast(×tamp), 8); + file.write(reinterpret_cast(&packetSize), 2); + file.write(reinterpret_cast(data), size); + } +}; + +REGISTER_CPP_MODULE(PacketCapModule);