diff --git a/CHANGELOG.md b/CHANGELOG.md
index fbea6fe..c2eb23c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+## 1.0.7 — Deferred Pre-login Channel Fix
+
+- Fix deferred inbound `LOGIN.CLIENT.LOGIN_START` failure when ProtocolLib2PacketEvents had a generated `TemporaryPlayer` instead of a Bukkit `CraftPlayer`: use the already associated PacketEvents pre-login `User` and its live Netty Channel for silent transport instead of `PlayerManager.receivePacketSilently(Player, ...)`.
+- Also route pre-login clientbound packets through the same User to avoid the analogous invalid CraftPlayer cast.
+- Reject missing temporary User/channel rather than bypassing login authentication or swallowing the packet; ordinary CraftPlayer packet transport is unchanged.
+- Add focused offline transport-selection regression tests; Maven test suite passes.
+
# Changelog
All notable changes to ProtocolLib2PacketEvents (P2P) are documented here.
diff --git a/pom.xml b/pom.xml
index a24c168..b527563 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
com.comphenix.protocol
ProtocolLib2PacketEvents
- 1.0.6
+ 1.0.7
jar
ProtocolLib2PacketEvents
diff --git a/src/main/java/com/comphenix/protocol/internal/PacketEventsBackend.java b/src/main/java/com/comphenix/protocol/internal/PacketEventsBackend.java
index c5bce5f..adb0afa 100644
--- a/src/main/java/com/comphenix/protocol/internal/PacketEventsBackend.java
+++ b/src/main/java/com/comphenix/protocol/internal/PacketEventsBackend.java
@@ -2,6 +2,9 @@
import com.comphenix.protocol.events.PacketContainer;
import com.github.retrooper.packetevents.PacketEvents;
+import com.github.retrooper.packetevents.protocol.player.User;
+import com.comphenix.protocol.injector.temporary.TemporaryPlayer;
+import com.comphenix.protocol.injector.temporary.TemporaryPlayerAdapter;
import org.bukkit.entity.Player;
/** PacketEvents transport for structured packets. */
@@ -18,6 +21,15 @@ private static void requireStructured(PacketContainer packet, String action) {
public void send(Player player, PacketContainer packet, boolean filters) {
requireStructured(packet, "send");
Object wrapper = packet.getPacketWrapper();
+ User preLogin = temporaryTransport(player);
+ if (preLogin != null) {
+ if (wrapper != null) {
+ if (filters) preLogin.sendPacket(packet.getPacketWrapper());
+ else preLogin.sendPacketSilently(packet.getPacketWrapper());
+ } else if (filters) preLogin.sendPacket(packet.getHandle());
+ else preLogin.sendPacketSilently(packet.getHandle());
+ return;
+ }
if (wrapper != null) {
if (filters) PacketEvents.getAPI().getPlayerManager().sendPacket(player, packet.getPacketWrapper());
else PacketEvents.getAPI().getPlayerManager().sendPacketSilently(player, packet.getPacketWrapper());
@@ -28,10 +40,37 @@ public void send(Player player, PacketContainer packet, boolean filters) {
}
}
+ /**
+ * A LOGIN/CONFIGURATION packet may have a ProtocolLib temporary player,
+ * not a Bukkit CraftPlayer. The PacketEvents PlayerManager casts its input
+ * to CraftPlayer; for temporary players the underlying pre-login User owns
+ * the real channel and must be used for transport.
+ *
+ * Fail loudly if its channel is missing, instead of silently discarding a
+ * credential-bearing LOGIN_START or bypassing authentication listeners.
+ */
+ static User temporaryTransport(Player player) {
+ if (!(player instanceof TemporaryPlayer)) return null;
+ User user = TemporaryPlayerAdapter.getUser(player);
+ if (user == null || user.getChannel() == null) {
+ throw new IllegalStateException("Temporary ProtocolLib player has no PacketEvents login channel");
+ }
+ return user;
+ }
+
@Override
public void receive(Player player, PacketContainer packet, boolean filters) {
requireStructured(packet, "receive");
Object wrapper = packet.getPacketWrapper();
+ User preLogin = temporaryTransport(player);
+ if (preLogin != null) {
+ if (wrapper != null) {
+ if (filters) preLogin.receivePacket(packet.getPacketWrapper());
+ else preLogin.receivePacketSilently(packet.getPacketWrapper());
+ } else if (filters) preLogin.receivePacket(packet.getHandle());
+ else preLogin.receivePacketSilently(packet.getHandle());
+ return;
+ }
if (wrapper != null) {
if (filters) PacketEvents.getAPI().getPlayerManager().receivePacket(player, packet.getPacketWrapper());
else PacketEvents.getAPI().getPlayerManager().receivePacketSilently(player, packet.getPacketWrapper());
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index c4e1b60..65267ba 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -6,11 +6,11 @@ main: com.comphenix.protocol.ProtocolLib
# Bukkit.getPluginManager().getPlugin("ProtocolLib") - which resolves to this plugin
# through `provides` below - and refuse to enable any ProtocolLib older than v5.0.0,
# because ProtocolLib 4.x is genuinely incompatible with PacketEvents 2.x. P2P's real
-# release version (1.0.6) is carried by the jar name and the GitHub releases.
+# release version (1.0.7) is carried by the jar name and the GitHub releases.
version: 5.4.0
author: CyoriaSMP Team
description: A drop-in ProtocolLib compatibility layer powered by PacketEvents.
-p2p-version: 1.0.6
+p2p-version: 1.0.7
protocol-lib-api-version: 5.4.0
depend: [packetevents]
provides: [ProtocolLib]
diff --git a/src/test/java/com/comphenix/protocol/internal/PacketEventsBackendTemporaryUserTest.java b/src/test/java/com/comphenix/protocol/internal/PacketEventsBackendTemporaryUserTest.java
new file mode 100644
index 0000000..c62ec23
--- /dev/null
+++ b/src/test/java/com/comphenix/protocol/internal/PacketEventsBackendTemporaryUserTest.java
@@ -0,0 +1,32 @@
+package com.comphenix.protocol.internal;
+
+import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory;
+import org.bukkit.entity.Player;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class PacketEventsBackendTemporaryUserTest {
+ @Test
+ void fullyJoinedPlayerDoesNotUsePreLoginTransport() {
+ // The early guard is null-safe and the real-player path is untouched.
+ assertNull(PacketEventsBackend.temporaryTransport(null));
+ }
+
+ @Test
+ void anonymousTemporaryPlayerWithoutUserFailsClosed() {
+ Player facade = TemporaryPlayerFactory.createTemporaryPlayer();
+ IllegalStateException error = assertThrows(IllegalStateException.class,
+ () -> PacketEventsBackend.temporaryTransport(facade));
+ assertTrue(error.getMessage().contains("no PacketEvents login channel"));
+ }
+
+ @Test
+ void temporaryTransportRejectsUnmappedFacadeWithoutTryingCraftPlayerCast() {
+ // This regression guard tests the pre-login failure mode without a
+ // running PacketEvents API (which User's constructor requires).
+ Player facade = TemporaryPlayerFactory.createTemporaryPlayer();
+ assertThrows(IllegalStateException.class,
+ () -> PacketEventsBackend.temporaryTransport(facade));
+ }
+}