A comprehensive packet handling library for PocketMine-MP that provides interception, monitoring, filtering, rewriting, profiling, throttling, recording, and testing utilities for network packets.
- Packet Interception: Register handlers to intercept and cancel or modify incoming and outgoing packets at configurable event priorities.
- Packet Monitoring: Observe packet flow at MONITOR priority without altering packets, ideal for logging and debugging.
- Declarative Filtering: Compose chainable conditions to allow or deny packets based on player state, packet data, or custom logic.
- Packet Rewriting: Modify packet fields in-flight using a fluent builder API with optional conditions.
- Rate Limiting: Throttle packets per type per player with configurable actions (drop, kick, or log) on violation.
- Network Profiling: Track real-time per-player and per-type bandwidth and packet volume statistics.
- Session Recording: Record and replay packet sessions for debugging, saved as JSON files with full timing data.
- Packet Mocking: Simulate incoming packets and capture outgoing packets for unit testing without a live connection.
- Batched Delivery: Queue multiple packets for batched delivery on the next tick, reducing per-packet overhead.
- Statistics Collection: Collect per-type and per-player packet counts with top-N queries and packets-per-second metrics.
Include or autoload LibPacket in your PocketMine-MP plugin. No external dependencies are required. Use the LibPacket entry point to create interceptors and monitors:
use imperazim\packet\LibPacket;
// Create an interceptor for modifying/blocking packets
$interceptor = LibPacket::createInterceptor($this);
// Create a monitor for observing packets (read-only)
$monitor = LibPacket::createMonitor($this);- Added
LibPacketclass inimperazim\packetnamespace.
The main entry point provides factory methods for creating interceptors and monitors:
use imperazim\packet\LibPacket;
use pocketmine\event\EventPriority;
// Default priority (NORMAL), does not handle cancelled events
$interceptor = LibPacket::createInterceptor($this);
// Custom priority and cancelled-event handling
$interceptor = LibPacket::createInterceptor($this, EventPriority::HIGH, handleCancelled: true);
// Monitor (always MONITOR priority)
$monitor = LibPacket::createMonitor($this, handleCancelled: false);- Added
PacketHandlerInterfaceinterface inimperazim\packet\handlernamespace. - Added
PacketMonitorHandlerInterfaceinterface inimperazim\packet\handlernamespace. - Added
PacketHandlerabstract class inimperazim\packet\handlernamespace. - Added
PacketMonitorHandlerabstract class inimperazim\packet\handlernamespace. - Added
PacketHandlerTraittrait inimperazim\packet\handlernamespace.
Extend the abstract classes or implement the interfaces to create packet handlers:
use imperazim\packet\handler\PacketHandler;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\TextPacket;
class ChatInterceptor extends PacketHandler {
public function __construct() {
parent::__construct([TextPacket::class]);
}
public function handle($packet, NetworkSession $session): bool {
if ($packet instanceof TextPacket && str_contains($packet->message, 'spam')) {
return false; // Cancel the packet
}
return true; // Allow the packet
}
}
$interceptor = LibPacket::createInterceptor($this);
$interceptor->registerIncoming(new ChatInterceptor());For monitoring (read-only observation):
use imperazim\packet\handler\PacketMonitorHandler;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
class MovementLogger extends PacketMonitorHandler {
public function __construct() {
parent::__construct([MovePlayerPacket::class]);
}
public function handle($packet, NetworkSession $session): void {
$player = $session->getPlayer();
if ($player !== null) {
$this->plugin->getLogger()->debug($player->getName() . " moved");
}
}
}
$monitor = LibPacket::createMonitor($this);
$monitor->registerIncoming(new MovementLogger());- Added
PacketInterceptorInterfaceinterface inimperazim\packet\interceptornamespace. - Added
PacketInterceptorclass inimperazim\packet\interceptornamespace.
The interceptor manages incoming and outgoing packet handlers. Handlers returning false cancel the packet:
use imperazim\packet\LibPacket;
use imperazim\packet\handler\PacketHandler;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\AvailableCommandsPacket;
class CommandFilter extends PacketHandler {
public function __construct() {
parent::__construct([AvailableCommandsPacket::class]);
}
public function handle($packet, NetworkSession $session): bool {
// Modify or filter the outgoing command list
return true;
}
}
$interceptor = LibPacket::createInterceptor($this);
$interceptor->registerOutgoing(new CommandFilter());
// Later, unregister the handler
$interceptor->unregisterOutgoing($handler);- Added
PacketMonitorInterfaceinterface inimperazim\packet\monitornamespace. - Added
PacketMonitorclass inimperazim\packet\monitornamespace.
Monitors observe packets at MONITOR priority without modifying them:
use imperazim\packet\LibPacket;
$monitor = LibPacket::createMonitor($this);
$monitor->registerIncoming($movementLogger);
$monitor->registerOutgoing($outgoingObserver);
// Unregister when no longer needed
$monitor->unregisterIncoming($movementLogger);- Added
PacketFilterclass inimperazim\packet\utilsnamespace. - Added
PacketRewriterclass inimperazim\packet\utilsnamespace. - Added
PacketQueueclass inimperazim\packet\utilsnamespace. - Added
PacketLoggerclass inimperazim\packet\utilsnamespace. - Added
PacketStatisticsclass inimperazim\packet\utilsnamespace. - Added
PacketUtilsclass inimperazim\packet\utilsnamespace. - Added
TypeValidatorclass inimperazim\packet\utilsnamespace.
PacketFilter -- Declarative packet filtering with composable conditions:
use imperazim\packet\utils\PacketFilter;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\world\GameMode;
// Block movement packets from creative-mode players
$filter = PacketFilter::create([MovePlayerPacket::class])
->where(fn($pkt, $session) => $session->getPlayer()?->getGamemode() === GameMode::CREATIVE)
->deny();
$interceptor->registerIncoming($filter);
// Allow only packets matching a condition
$filter = PacketFilter::create([TextPacket::class])
->where(fn($pkt, $session) => !str_contains($pkt->message, 'spam'))
->allow();
$interceptor->registerIncoming($filter);PacketRewriter -- Modify packet fields before delivery:
use imperazim\packet\utils\PacketRewriter;
use pocketmine\network\mcpe\protocol\TextPacket;
use pocketmine\network\mcpe\protocol\SetTimePacket;
// Censor bad words in chat packets
$rewriter = PacketRewriter::create([TextPacket::class])
->rewrite(function(TextPacket $packet, $session): void {
$packet->message = str_replace('bad', '***', $packet->message);
});
$interceptor->registerIncoming($rewriter);
// Conditional rewriting: force daytime in lobby world
$rewriter = PacketRewriter::create([SetTimePacket::class])
->when(fn($pkt, $s) => $s->getPlayer()?->getWorld()->getFolderName() === 'lobby')
->rewrite(fn(SetTimePacket $pkt, $s) => $pkt->time = 6000);
$interceptor->registerOutgoing($rewriter);PacketQueue -- Batched packet delivery on the next tick:
use imperazim\packet\utils\PacketQueue;
$queue = new PacketQueue($this);
$queue->enqueue($player, $packet1);
$queue->enqueue($player, $packet2);
// Both packets are sent together on the next tick
// Broadcast to multiple players
$queue->broadcast([$player1, $player2], $packet);
// Check pending count
$count = $queue->getPendingCount();
// Flush immediately or clear without sending
$queue->flush();
$queue->clear();PacketLogger -- Rate-limited packet logging:
use imperazim\packet\utils\PacketLogger;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\TextPacket;
$logger = new PacketLogger(
[MovePlayerPacket::class, TextPacket::class],
fn(string $line) => $this->getLogger()->debug($line),
maxPerSecond: 10,
includeData: true
);
$monitor->registerIncoming($logger);
// Adjust settings at runtime
$logger->setMaxPerSecond(50);
$logger->setIncludeData(false);PacketStatistics -- Per-type and per-player packet counting:
use imperazim\packet\utils\PacketStatistics;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\TextPacket;
$stats = new PacketStatistics([MovePlayerPacket::class, TextPacket::class]);
$monitor->registerIncoming($stats);
// Query statistics
$totals = $stats->getTotals(); // ['MovePlayerPacket' => 1523, ...]
$perPlayer = $stats->getByPlayer('Steve'); // ['MovePlayerPacket' => 42, ...]
$topTypes = $stats->getTopTypes(5); // Top 5 by count
$pps = $stats->getPacketsPerSecond(); // Packets per second
$grand = $stats->getGrandTotal(); // Total across all types
// Cleanup on player quit
$stats->cleanupPlayer('Steve');
// Reset all data
$stats->reset();PacketUtils -- Closure signature validation and type reflection:
use imperazim\packet\utils\PacketUtils;
use pocketmine\network\mcpe\protocol\Packet;
use pocketmine\network\mcpe\NetworkSession;
// Validate that a closure has the expected signature
$parsed = PacketUtils::parseClosureSignature(
function(Packet $packet, NetworkSession $session): bool {
return true;
},
[Packet::class, NetworkSession::class],
'bool'
);TypeValidator -- Validate packet classes and resolve their network IDs:
use imperazim\packet\utils\TypeValidator;
use pocketmine\network\mcpe\protocol\TextPacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
$ids = TypeValidator::validatePacketClasses([
TextPacket::class,
MovePlayerPacket::class,
]);
// Returns array of unique network IDs- Added
NetworkProfilerclass inimperazim\packet\profilernamespace. - Added
PacketThrottlerclass inimperazim\packet\profilernamespace.
NetworkProfiler -- Real-time bandwidth profiling per player and packet type:
use imperazim\packet\profiler\NetworkProfiler;
// Enable profiling
NetworkProfiler::enable();
// Record packets (call from your interceptor/monitor)
NetworkProfiler::recordOutgoing($player, $clientboundPacket);
NetworkProfiler::recordIncoming($player, $serverboundPacket);
// Query stats
$playerStats = NetworkProfiler::getPlayerStats($player); // ['in' => bytes, 'out' => bytes, 'packets' => [...]]
$topPackets = NetworkProfiler::getTopPackets(10); // Top 10 by volume
NetworkProfiler::isEnabled(); // bool
// Disable and get summary
$summary = NetworkProfiler::disable();
// $summary = ['duration' => float, 'totalIn' => int, 'totalOut' => int, 'topPackets' => [...]]
// Cleanup on player quit
NetworkProfiler::cleanup($player);PacketThrottler -- Rate-limit packets per type per player to prevent spam/abuse:
use imperazim\packet\profiler\PacketThrottler;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\TextPacket;
// Configure limits
PacketThrottler::setLimit(MovePlayerPacket::class, maxPerSecond: 30, action: PacketThrottler::ACTION_DROP);
PacketThrottler::setLimit(TextPacket::class, maxPerSecond: 5, action: PacketThrottler::ACTION_KICK);
// Check in your packet handler
if (!PacketThrottler::allow($player, $serverboundPacket)) {
return; // Packet was dropped or player was kicked
}
// Query violations and limits
$violations = PacketThrottler::getViolations($player, MovePlayerPacket::class);
$allLimits = PacketThrottler::getLimits();
// Remove a limit
PacketThrottler::removeLimit(TextPacket::class);
// Cleanup on player quit
PacketThrottler::cleanup($player);- Added
PacketRecorderclass inimperazim\packet\recordernamespace.
Record packets from a session for replay and debug analysis:
use imperazim\packet\recorder\PacketRecorder;
// Start recording
PacketRecorder::start($player, "debug_session_1");
// Record packets (call from your interceptor/monitor)
PacketRecorder::recordIncoming($player, $serverboundPacket);
PacketRecorder::recordOutgoing($player, $clientboundPacket);
// Check recording state
PacketRecorder::isRecording($player); // true
// Stop and get recording data
$recording = PacketRecorder::stop($player);
// Save to disk
$path = PacketRecorder::save($recording, $this->getDataFolder() . "recordings/");
// Load a recording
$loaded = PacketRecorder::load($this->getDataFolder() . "recordings/debug_session_1.json");
// Get a summary
$summary = PacketRecorder::summarize($loaded);
// $summary = ['duration' => float, 'packetCount' => int, 'inbound' => int, 'outbound' => int, 'topPackets' => [...]]
// Cleanup on player quit
PacketRecorder::cleanup($player);- Added
PacketMockerclass inimperazim\packet\mocknamespace.
Simulate packets for testing without a real player connection:
use imperazim\packet\mock\PacketMocker;
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
// Create a mock packet with custom properties
$packet = PacketMocker::create(MovePlayerPacket::class, [
'actorRuntimeId' => 1,
'mode' => MovePlayerPacket::MODE_NORMAL,
]);
// Simulate handling an incoming packet
$result = PacketMocker::simulateIncoming($packet, $handler);
// Capture outgoing packets during a callback
$captured = PacketMocker::capture(function() use ($someObject) {
$someObject->doSomethingThatSendsPackets();
});
// $captured = [ClientboundPacket, ...]
// Manual capture flow
PacketMocker::startCapture();
PacketMocker::record($clientboundPacket);
$packets = PacketMocker::stopCapture();This project is licensed under MIT. Please see the LICENSE file for details.