Skip to content

Commit 78231bd

Browse files
authored
State switching rework with other bugfixes (#128)
* Properly check for pending disconnection * Force synchronize rejoin state switching * Deduplicate prepared config packets * Don't preserve ConfirmHandler and ClientPlaySessionHandler Preserving them will cause issues with server switching * Close "confirming" connection on spam * Speedup 1.20.3+ world loading by following Vanilla behavior * Synchronize LOGIN transition * Ensure that FastPrepareAPI encoder matches current state * Remove NbtUtils as Velocity now supports 1.20.2 NBT * Check for a custom PLAY state while sending UpsertPlayerInfo * Rollback CONFIG handler on server switch * Move the entire PLAY->CONFIG transition logic to the LimboSessionHandlerImpl * Fix invalid CONFIG packets being sent at PLAY state * Mitigate clientside race condition * Small improvements * Fix race condition mitigation then rejoin is disabled * Fix LoginEvent sending a LOGIN disconnect instead of CONFIG/PLAY one
1 parent 585a6fd commit 78231bd

File tree

15 files changed

+351
-318
lines changed

15 files changed

+351
-318
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
org.gradle.jvmargs=-Xmx4096m
2-
fastPrepareVersion=1.0.8
2+
fastPrepareVersion=1.0.9
33
velocityVersion=3.3.0-SNAPSHOT
44
nettyVersion=4.1.86.Final
55
fastutilVersion=8.5.11

plugin/src/main/java/net/elytrium/limboapi/LimboAPI.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import com.velocitypowered.natives.util.Natives;
3333
import com.velocitypowered.proxy.VelocityServer;
3434
import com.velocitypowered.proxy.connection.MinecraftConnection;
35+
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
3536
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
3637
import com.velocitypowered.proxy.event.VelocityEventManager;
3738
import com.velocitypowered.proxy.network.Connections;
@@ -61,6 +62,7 @@
6162
import net.elytrium.commons.utils.reflection.ReflectionException;
6263
import net.elytrium.commons.utils.updates.UpdatesChecker;
6364
import net.elytrium.fastprepare.PreparedPacketFactory;
65+
import net.elytrium.fastprepare.handler.PreparedPacketEncoder;
6466
import net.elytrium.limboapi.api.Limbo;
6567
import net.elytrium.limboapi.api.LimboFactory;
6668
import net.elytrium.limboapi.api.chunk.BuiltInBiome;
@@ -414,7 +416,40 @@ public ByteBuf encodeSingleLoginUncompressed(MinecraftPacket packet, ProtocolVer
414416
}
415417

416418
public void inject3rdParty(Player player, MinecraftConnection connection, ChannelPipeline pipeline) {
417-
this.preparedPacketFactory.inject(player, connection, pipeline);
419+
StateRegistry state = connection.getState();
420+
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0
421+
|| (state != StateRegistry.CONFIG && state != StateRegistry.LOGIN)) {
422+
this.preparedPacketFactory.inject(player, connection, pipeline);
423+
} else {
424+
this.configPreparedPacketFactory.inject(player, connection, pipeline);
425+
}
426+
}
427+
428+
public void setState(MinecraftConnection connection, StateRegistry stateRegistry) {
429+
connection.setState(stateRegistry);
430+
this.setEncoderState(connection, stateRegistry);
431+
}
432+
433+
public void setActiveSessionHandler(MinecraftConnection connection, StateRegistry stateRegistry,
434+
MinecraftSessionHandler sessionHandler) {
435+
connection.setActiveSessionHandler(stateRegistry, sessionHandler);
436+
this.setEncoderState(connection, stateRegistry);
437+
}
438+
439+
public void setEncoderState(MinecraftConnection connection, StateRegistry state) {
440+
// As CONFIG state was added in 1.20.2, no need to track it for lower versions
441+
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0) {
442+
return;
443+
}
444+
445+
PreparedPacketEncoder encoder = connection.getChannel().pipeline().get(PreparedPacketEncoder.class);
446+
if (encoder != null) {
447+
if (state != StateRegistry.CONFIG && state != StateRegistry.LOGIN) {
448+
encoder.setFactory(this.preparedPacketFactory);
449+
} else {
450+
encoder.setFactory(this.configPreparedPacketFactory);
451+
}
452+
}
418453
}
419454

420455
public void deject3rdParty(ChannelPipeline pipeline) {

plugin/src/main/java/net/elytrium/limboapi/Settings.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ public static class MESSAGES {
115115

116116
public String TOO_BIG_PACKET = "{PRFX}{NL}{NL}&cYour client sent too big packet!";
117117
public String INVALID_PING = "{PRFX}{NL}{NL}&cYour client sent invalid ping packet!";
118+
public String INVALID_SWITCH = "{PRFX}{NL}{NL}&cYour client sent an unexpected state switching packet!";
118119
public String TIME_OUT = "{PRFX}{NL}{NL}Timed out.";
119120
}
120121
}

plugin/src/main/java/net/elytrium/limboapi/injection/login/LoginListener.java

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@
7676
import net.elytrium.limboapi.injection.dummy.ClosedChannel;
7777
import net.elytrium.limboapi.injection.dummy.ClosedMinecraftConnection;
7878
import net.elytrium.limboapi.injection.dummy.DummyEventPool;
79-
import net.elytrium.limboapi.injection.login.confirmation.ConfirmHandler;
8079
import net.elytrium.limboapi.injection.login.confirmation.LoginConfirmHandler;
8180
import net.elytrium.limboapi.injection.packet.ServerLoginSuccessHook;
8281
import net.kyori.adventure.text.Component;
@@ -136,7 +135,7 @@ public void hookLoginSession(GameProfileRequestEvent event) throws Throwable {
136135
MC_CONNECTION_FIELD.set(handler, CLOSED_MINECRAFT_CONNECTION);
137136

138137
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
139-
connection.setActiveSessionHandler(StateRegistry.LOGIN, new LoginConfirmHandler(connection));
138+
connection.setActiveSessionHandler(StateRegistry.LOGIN, new LoginConfirmHandler(this.plugin, connection));
140139
}
141140

142141
// From Velocity.
@@ -153,7 +152,7 @@ public void hookLoginSession(GameProfileRequestEvent event) throws Throwable {
153152
inboundConnection.getIdentifiedKey()
154153
);
155154
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
156-
((ConfirmHandler) connection.getActiveSessionHandler()).setPlayer(player);
155+
((LoginConfirmHandler) connection.getActiveSessionHandler()).setPlayer(player);
157156
}
158157
if (this.server.canRegisterConnection(player)) {
159158
if (!connection.isClosed()) {
@@ -204,19 +203,13 @@ public void hookLoginSession(GameProfileRequestEvent event) throws Throwable {
204203

205204
this.plugin.setInitialID(player, playerUniqueID);
206205

207-
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0) {
206+
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
207+
((LoginConfirmHandler) connection.getActiveSessionHandler())
208+
.thenRun(() -> this.fireRegisterEvent(player, connection, inbound, handler));
209+
} else {
208210
connection.setState(StateRegistry.PLAY);
211+
this.fireRegisterEvent(player, connection, inbound, handler);
209212
}
210-
211-
this.server.getEventManager().fire(new LoginLimboRegisterEvent(player)).thenAcceptAsync(limboRegisterEvent -> {
212-
LoginTasksQueue queue = new LoginTasksQueue(this.plugin, handler, this.server, player, inbound, limboRegisterEvent.getOnJoinCallbacks());
213-
this.plugin.addLoginQueue(player, queue);
214-
this.plugin.setKickCallback(player, limboRegisterEvent.getOnKickCallback());
215-
queue.next();
216-
}, connection.eventLoop()).exceptionally(t -> {
217-
LimboAPI.getLogger().error("Exception while registering LimboAPI login handlers for {}.", player, t);
218-
return null;
219-
});
220213
}
221214
} else {
222215
player.disconnect0(Component.translatable("velocity.error.already-connected-proxy", NamedTextColor.RED), true);
@@ -229,11 +222,29 @@ public void hookLoginSession(GameProfileRequestEvent event) throws Throwable {
229222
}
230223
}
231224

225+
private void fireRegisterEvent(ConnectedPlayer player, MinecraftConnection connection,
226+
InitialInboundConnection inbound, Object handler) {
227+
this.server.getEventManager().fire(new LoginLimboRegisterEvent(player)).thenAcceptAsync(limboRegisterEvent -> {
228+
LoginTasksQueue queue = new LoginTasksQueue(this.plugin, handler, this.server, player, inbound, limboRegisterEvent.getOnJoinCallbacks());
229+
this.plugin.addLoginQueue(player, queue);
230+
this.plugin.setKickCallback(player, limboRegisterEvent.getOnKickCallback());
231+
queue.next();
232+
}, connection.eventLoop()).exceptionally(t -> {
233+
LimboAPI.getLogger().error("Exception while registering LimboAPI login handlers for {}.", player, t);
234+
return null;
235+
});
236+
}
237+
232238
@Subscribe
233239
public void hookPlaySession(ServerConnectedEvent event) {
234240
ConnectedPlayer player = (ConnectedPlayer) event.getPlayer();
235241
MinecraftConnection connection = player.getConnection();
236242

243+
// 1.20.2+ can ignore this, as it should be despawned by default
244+
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
245+
return;
246+
}
247+
237248
connection.eventLoop().execute(() -> {
238249
if (!(connection.getActiveSessionHandler() instanceof ClientPlaySessionHandler)) {
239250
try {

plugin/src/main/java/net/elytrium/limboapi/injection/login/LoginTasksQueue.java

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@
5757
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
5858
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
5959
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
60-
import com.velocitypowered.proxy.protocol.packet.config.StartUpdate;
6160
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
6261
import io.netty.channel.ChannelPipeline;
6362
import io.netty.channel.EventLoop;
@@ -74,8 +73,8 @@
7473
import net.elytrium.commons.utils.reflection.ReflectionException;
7574
import net.elytrium.limboapi.LimboAPI;
7675
import net.elytrium.limboapi.injection.event.EventManagerHook;
77-
import net.elytrium.limboapi.injection.login.confirmation.ConfirmHandler;
78-
import net.elytrium.limboapi.injection.login.confirmation.TransitionConfirmHandler;
76+
import net.elytrium.limboapi.injection.login.confirmation.LoginConfirmHandler;
77+
import net.elytrium.limboapi.server.LimboSessionHandlerImpl;
7978
import net.kyori.adventure.text.Component;
8079
import org.slf4j.Logger;
8180

@@ -146,7 +145,7 @@ private void finish() {
146145
.setProperties(gameProfile.getGameProfile().getProperties())
147146
)
148147
));
149-
} else if (connection.getState() == StateRegistry.PLAY) {
148+
} else if (connection.getState() != StateRegistry.CONFIG) {
150149
UpsertPlayerInfo.Entry playerInfoEntry = new UpsertPlayerInfo.Entry(this.player.getUniqueId());
151150
playerInfoEntry.setDisplayName(new ComponentHolder(this.player.getProtocolVersion(), Component.text(gameProfile.getUsername())));
152151
playerInfoEntry.setProfile(gameProfile.getGameProfile());
@@ -200,8 +199,8 @@ private void finish() {
200199
private void initialize(MinecraftConnection connection) throws Throwable {
201200
connection.setAssociation(this.player);
202201
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0
203-
|| (connection.getState() != StateRegistry.LOGIN && connection.getState() != StateRegistry.CONFIG)) {
204-
connection.setState(StateRegistry.PLAY);
202+
|| connection.getState() != StateRegistry.CONFIG) {
203+
this.plugin.setState(connection, StateRegistry.PLAY);
205204
}
206205

207206
ChannelPipeline pipeline = connection.getChannel().pipeline();
@@ -235,16 +234,16 @@ private void initialize(MinecraftConnection connection) throws Throwable {
235234
} else {
236235
Optional<Component> reason = event.getResult().getReasonComponent();
237236
if (reason.isPresent()) {
238-
this.player.disconnect0(reason.get(), true);
237+
this.player.disconnect0(reason.get(), false);
239238
} else {
240239
if (this.server.registerConnection(this.player)) {
241-
if (connection.getActiveSessionHandler() instanceof ConfirmHandler confirm) {
240+
if (connection.getActiveSessionHandler() instanceof LoginConfirmHandler confirm) {
242241
confirm.waitForConfirmation(() -> this.connectToServer(logger, this.player, connection));
243242
} else {
244243
this.connectToServer(logger, this.player, connection);
245244
}
246245
} else {
247-
this.player.disconnect0(Component.translatable("velocity.error.already-connected-proxy"), true);
246+
this.player.disconnect0(Component.translatable("velocity.error.already-connected-proxy"), false);
248247
}
249248
}
250249
}
@@ -254,6 +253,7 @@ private void initialize(MinecraftConnection connection) throws Throwable {
254253
});
255254
}
256255

256+
@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
257257
private void connectToServer(Logger logger, ConnectedPlayer player, MinecraftConnection connection) {
258258
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0) {
259259
try {
@@ -262,20 +262,14 @@ private void connectToServer(Logger logger, ConnectedPlayer player, MinecraftCon
262262
} catch (Throwable e) {
263263
throw new ReflectionException(e);
264264
}
265-
} else {
266-
if (connection.getState() == StateRegistry.PLAY) {
267-
connection.write(new StartUpdate());
268-
269-
TransitionConfirmHandler confirm = new TransitionConfirmHandler(connection);
270-
confirm.setPlayer(player);
271-
272-
connection.setActiveSessionHandler(StateRegistry.PLAY, confirm);
273-
confirm.waitForConfirmation(() -> this.connectToServer(logger, player, connection));
265+
} else if (connection.getState() == StateRegistry.PLAY) {
266+
// Synchronize with the client to ensure that it will not corrupt CONFIG state with PLAY packets
267+
((LimboSessionHandlerImpl) connection.getActiveSessionHandler())
268+
.disconnectToConfig(() -> this.connectToServer(logger, player, connection));
274269

275-
return;
276-
}
277-
278-
connection.setActiveSessionHandler(StateRegistry.CONFIG,
270+
return; // Re-running this method due to synchronization with the client
271+
} else {
272+
this.plugin.setActiveSessionHandler(connection, StateRegistry.CONFIG,
279273
new ClientConfigSessionHandler(this.server, this.player));
280274
}
281275

plugin/src/main/java/net/elytrium/limboapi/injection/login/confirmation/ConfirmHandler.java

Lines changed: 0 additions & 120 deletions
This file was deleted.

0 commit comments

Comments
 (0)