Repository navigation
fix(auth): late-bind FastLoginPlus injector during pre-login session verification #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,9 +35,28 @@ private TemporaryPlayerFactory() { | |
| } | ||
|
|
||
| public static Injector getInjectorFromPlayer(Player player) { | ||
| return player instanceof TemporaryPlayer | ||
| ? ((TemporaryPlayer) player).getInjector() | ||
| : null; | ||
| if (!(player instanceof TemporaryPlayer temporary)) return null; | ||
| Injector injector = temporary.getInjector(); | ||
| // FastLoginPlus retrieves this adapter at multiple asynchronous login | ||
| // phases. When LOGIN_START ran before the PacketEvents/native pipeline | ||
| // was fully initialized, retry on the same live User+Channel rather | ||
| // than permanently treating a legitimate connection as disconnected. | ||
| com.github.retrooper.packetevents.protocol.player.User user = | ||
| TemporaryPlayerAdapter.getUser(player); | ||
| if (user != null) { | ||
| org.bukkit.plugin.Plugin plugin = com.comphenix.protocol.ProtocolLibrary.getPlugin(); | ||
| if (plugin instanceof com.comphenix.protocol.ProtocolLib bridge) { | ||
| Injector resolved = bridge.getTemporaryInjector(user, player); | ||
| if (resolved != null) { | ||
| if (resolved != injector) temporary.setInjector(resolved); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the initial Useful? React with 👍 / 👎. |
||
| injector = resolved; | ||
| } | ||
| } | ||
| } | ||
| if (injector instanceof com.comphenix.protocol.injector.netty.channel.NettyChannelInjector netty) { | ||
| netty.refreshNativeConnection(); | ||
| } | ||
| return injector; | ||
| } | ||
|
|
||
| public static void setInjectorForPlayer(Player player, Injector injector) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package com.comphenix.protocol.injector.netty.manager; | ||
|
|
||
| import com.comphenix.protocol.injector.netty.channel.NettyChannelInjector; | ||
| import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory; | ||
| import com.comphenix.protocol.reflect.ObjectAllocator; | ||
| import com.github.retrooper.packetevents.protocol.player.User; | ||
| import io.netty.channel.embedded.EmbeddedChannel; | ||
| import java.lang.reflect.Field; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.*; | ||
|
|
||
| class PreLoginInjectorBindingTest { | ||
| @Test | ||
| void activeChannelBindsBeforeNativeConnectionAndCanRefreshLater() throws Exception { | ||
| EmbeddedChannel channel = new EmbeddedChannel(); | ||
| User user = ObjectAllocator.allocate(User.class); | ||
| Field field = User.class.getDeclaredField("channel"); | ||
| field.setAccessible(true); | ||
| field.set(user, channel); | ||
| var player = TemporaryPlayerFactory.createTemporaryPlayer(); | ||
| try (NetworkManagerInjector manager = new NetworkManagerInjector(null, null, null)) { | ||
| var early = manager.getTemporaryInjector(user, player); | ||
| assertInstanceOf(NettyChannelInjector.class, early, | ||
| "valid channel must bind before native Connection exists"); | ||
| assertFalse(((NettyChannelInjector) early).hasNativeConnection()); | ||
| TemporaryPlayerFactory.setInjectorForPlayer(player, early); | ||
| assertSame(early, TemporaryPlayerFactory.getInjectorFromPlayer(player)); | ||
| channel.pipeline().addLast("packet_handler", new net.minecraft.network.Connection()); | ||
| var late = manager.getTemporaryInjector(user, player); | ||
| assertSame(early, late, "must not replace channel injector mid-login"); | ||
| assertTrue(((NettyChannelInjector) late).hasNativeConnection()); | ||
| assertSame(late, TemporaryPlayerFactory.getInjectorFromPlayer(player)); | ||
| } finally { | ||
| channel.finishAndReleaseAll(); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a delayed asynchronous callback invokes this accessor after
onPlayerJoinhas promoted the channel injector to the real Bukkit player,bridge.getTemporaryInjector(...)reachesNetworkManagerInjector.getTemporaryInjector()and unconditionally callsnetty.setPlayer(temporary). The injector cached under the real player's UUID is then left pointing at the temporary facade becauseInjectionFactory.fromPlayer()returns UUID hits without restoring the player, causing subsequent post-login packet events and player-dependent lookups to use the wrong identity. Only retry the bridge binding while the temporary player has no injector; the existing injector can be refreshed directly.Useful? React with 👍 / 👎.