Skip to content

Commit

Permalink
State switching rework with other bugfixes (#128)
Browse files Browse the repository at this point in the history
* 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

Former-commit-id: 78231bd
  • Loading branch information
UserNugget authored Jan 14, 2024
1 parent 377376c commit 3e6d826
Show file tree
Hide file tree
Showing 15 changed files with 351 additions and 318 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
org.gradle.jvmargs=-Xmx4096m
fastPrepareVersion=1.0.8
fastPrepareVersion=1.0.9
velocityVersion=3.3.0-SNAPSHOT
nettyVersion=4.1.86.Final
fastutilVersion=8.5.11
Expand Down
37 changes: 36 additions & 1 deletion plugin/src/main/java/net/elytrium/limboapi/LimboAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.velocitypowered.natives.util.Natives;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.event.VelocityEventManager;
import com.velocitypowered.proxy.network.Connections;
Expand Down Expand Up @@ -61,6 +62,7 @@
import net.elytrium.commons.utils.reflection.ReflectionException;
import net.elytrium.commons.utils.updates.UpdatesChecker;
import net.elytrium.fastprepare.PreparedPacketFactory;
import net.elytrium.fastprepare.handler.PreparedPacketEncoder;
import net.elytrium.limboapi.api.Limbo;
import net.elytrium.limboapi.api.LimboFactory;
import net.elytrium.limboapi.api.chunk.BuiltInBiome;
Expand Down Expand Up @@ -414,7 +416,40 @@ public ByteBuf encodeSingleLoginUncompressed(MinecraftPacket packet, ProtocolVer
}

public void inject3rdParty(Player player, MinecraftConnection connection, ChannelPipeline pipeline) {
this.preparedPacketFactory.inject(player, connection, pipeline);
StateRegistry state = connection.getState();
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0
|| (state != StateRegistry.CONFIG && state != StateRegistry.LOGIN)) {
this.preparedPacketFactory.inject(player, connection, pipeline);
} else {
this.configPreparedPacketFactory.inject(player, connection, pipeline);
}
}

public void setState(MinecraftConnection connection, StateRegistry stateRegistry) {
connection.setState(stateRegistry);
this.setEncoderState(connection, stateRegistry);
}

public void setActiveSessionHandler(MinecraftConnection connection, StateRegistry stateRegistry,
MinecraftSessionHandler sessionHandler) {
connection.setActiveSessionHandler(stateRegistry, sessionHandler);
this.setEncoderState(connection, stateRegistry);
}

public void setEncoderState(MinecraftConnection connection, StateRegistry state) {
// As CONFIG state was added in 1.20.2, no need to track it for lower versions
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0) {
return;
}

PreparedPacketEncoder encoder = connection.getChannel().pipeline().get(PreparedPacketEncoder.class);
if (encoder != null) {
if (state != StateRegistry.CONFIG && state != StateRegistry.LOGIN) {
encoder.setFactory(this.preparedPacketFactory);
} else {
encoder.setFactory(this.configPreparedPacketFactory);
}
}
}

public void deject3rdParty(ChannelPipeline pipeline) {
Expand Down
1 change: 1 addition & 0 deletions plugin/src/main/java/net/elytrium/limboapi/Settings.java
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ public static class MESSAGES {

public String TOO_BIG_PACKET = "{PRFX}{NL}{NL}&cYour client sent too big packet!";
public String INVALID_PING = "{PRFX}{NL}{NL}&cYour client sent invalid ping packet!";
public String INVALID_SWITCH = "{PRFX}{NL}{NL}&cYour client sent an unexpected state switching packet!";
public String TIME_OUT = "{PRFX}{NL}{NL}Timed out.";
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@
import net.elytrium.limboapi.injection.dummy.ClosedChannel;
import net.elytrium.limboapi.injection.dummy.ClosedMinecraftConnection;
import net.elytrium.limboapi.injection.dummy.DummyEventPool;
import net.elytrium.limboapi.injection.login.confirmation.ConfirmHandler;
import net.elytrium.limboapi.injection.login.confirmation.LoginConfirmHandler;
import net.elytrium.limboapi.injection.packet.ServerLoginSuccessHook;
import net.kyori.adventure.text.Component;
Expand Down Expand Up @@ -136,7 +135,7 @@ public void hookLoginSession(GameProfileRequestEvent event) throws Throwable {
MC_CONNECTION_FIELD.set(handler, CLOSED_MINECRAFT_CONNECTION);

if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
connection.setActiveSessionHandler(StateRegistry.LOGIN, new LoginConfirmHandler(connection));
connection.setActiveSessionHandler(StateRegistry.LOGIN, new LoginConfirmHandler(this.plugin, connection));
}

// From Velocity.
Expand All @@ -153,7 +152,7 @@ public void hookLoginSession(GameProfileRequestEvent event) throws Throwable {
inboundConnection.getIdentifiedKey()
);
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
((ConfirmHandler) connection.getActiveSessionHandler()).setPlayer(player);
((LoginConfirmHandler) connection.getActiveSessionHandler()).setPlayer(player);
}
if (this.server.canRegisterConnection(player)) {
if (!connection.isClosed()) {
Expand Down Expand Up @@ -204,19 +203,13 @@ public void hookLoginSession(GameProfileRequestEvent event) throws Throwable {

this.plugin.setInitialID(player, playerUniqueID);

if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0) {
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
((LoginConfirmHandler) connection.getActiveSessionHandler())
.thenRun(() -> this.fireRegisterEvent(player, connection, inbound, handler));
} else {
connection.setState(StateRegistry.PLAY);
this.fireRegisterEvent(player, connection, inbound, handler);
}

this.server.getEventManager().fire(new LoginLimboRegisterEvent(player)).thenAcceptAsync(limboRegisterEvent -> {
LoginTasksQueue queue = new LoginTasksQueue(this.plugin, handler, this.server, player, inbound, limboRegisterEvent.getOnJoinCallbacks());
this.plugin.addLoginQueue(player, queue);
this.plugin.setKickCallback(player, limboRegisterEvent.getOnKickCallback());
queue.next();
}, connection.eventLoop()).exceptionally(t -> {
LimboAPI.getLogger().error("Exception while registering LimboAPI login handlers for {}.", player, t);
return null;
});
}
} else {
player.disconnect0(Component.translatable("velocity.error.already-connected-proxy", NamedTextColor.RED), true);
Expand All @@ -229,11 +222,29 @@ public void hookLoginSession(GameProfileRequestEvent event) throws Throwable {
}
}

private void fireRegisterEvent(ConnectedPlayer player, MinecraftConnection connection,
InitialInboundConnection inbound, Object handler) {
this.server.getEventManager().fire(new LoginLimboRegisterEvent(player)).thenAcceptAsync(limboRegisterEvent -> {
LoginTasksQueue queue = new LoginTasksQueue(this.plugin, handler, this.server, player, inbound, limboRegisterEvent.getOnJoinCallbacks());
this.plugin.addLoginQueue(player, queue);
this.plugin.setKickCallback(player, limboRegisterEvent.getOnKickCallback());
queue.next();
}, connection.eventLoop()).exceptionally(t -> {
LimboAPI.getLogger().error("Exception while registering LimboAPI login handlers for {}.", player, t);
return null;
});
}

@Subscribe
public void hookPlaySession(ServerConnectedEvent event) {
ConnectedPlayer player = (ConnectedPlayer) event.getPlayer();
MinecraftConnection connection = player.getConnection();

// 1.20.2+ can ignore this, as it should be despawned by default
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
return;
}

connection.eventLoop().execute(() -> {
if (!(connection.getActiveSessionHandler() instanceof ClientPlaySessionHandler)) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import com.velocitypowered.proxy.protocol.packet.config.StartUpdate;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoop;
Expand All @@ -74,8 +73,8 @@
import net.elytrium.commons.utils.reflection.ReflectionException;
import net.elytrium.limboapi.LimboAPI;
import net.elytrium.limboapi.injection.event.EventManagerHook;
import net.elytrium.limboapi.injection.login.confirmation.ConfirmHandler;
import net.elytrium.limboapi.injection.login.confirmation.TransitionConfirmHandler;
import net.elytrium.limboapi.injection.login.confirmation.LoginConfirmHandler;
import net.elytrium.limboapi.server.LimboSessionHandlerImpl;
import net.kyori.adventure.text.Component;
import org.slf4j.Logger;

Expand Down Expand Up @@ -146,7 +145,7 @@ private void finish() {
.setProperties(gameProfile.getGameProfile().getProperties())
)
));
} else if (connection.getState() == StateRegistry.PLAY) {
} else if (connection.getState() != StateRegistry.CONFIG) {
UpsertPlayerInfo.Entry playerInfoEntry = new UpsertPlayerInfo.Entry(this.player.getUniqueId());
playerInfoEntry.setDisplayName(new ComponentHolder(this.player.getProtocolVersion(), Component.text(gameProfile.getUsername())));
playerInfoEntry.setProfile(gameProfile.getGameProfile());
Expand Down Expand Up @@ -200,8 +199,8 @@ private void finish() {
private void initialize(MinecraftConnection connection) throws Throwable {
connection.setAssociation(this.player);
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0
|| (connection.getState() != StateRegistry.LOGIN && connection.getState() != StateRegistry.CONFIG)) {
connection.setState(StateRegistry.PLAY);
|| connection.getState() != StateRegistry.CONFIG) {
this.plugin.setState(connection, StateRegistry.PLAY);
}

ChannelPipeline pipeline = connection.getChannel().pipeline();
Expand Down Expand Up @@ -235,16 +234,16 @@ private void initialize(MinecraftConnection connection) throws Throwable {
} else {
Optional<Component> reason = event.getResult().getReasonComponent();
if (reason.isPresent()) {
this.player.disconnect0(reason.get(), true);
this.player.disconnect0(reason.get(), false);
} else {
if (this.server.registerConnection(this.player)) {
if (connection.getActiveSessionHandler() instanceof ConfirmHandler confirm) {
if (connection.getActiveSessionHandler() instanceof LoginConfirmHandler confirm) {
confirm.waitForConfirmation(() -> this.connectToServer(logger, this.player, connection));
} else {
this.connectToServer(logger, this.player, connection);
}
} else {
this.player.disconnect0(Component.translatable("velocity.error.already-connected-proxy"), true);
this.player.disconnect0(Component.translatable("velocity.error.already-connected-proxy"), false);
}
}
}
Expand All @@ -254,6 +253,7 @@ private void initialize(MinecraftConnection connection) throws Throwable {
});
}

@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
private void connectToServer(Logger logger, ConnectedPlayer player, MinecraftConnection connection) {
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0) {
try {
Expand All @@ -262,20 +262,14 @@ private void connectToServer(Logger logger, ConnectedPlayer player, MinecraftCon
} catch (Throwable e) {
throw new ReflectionException(e);
}
} else {
if (connection.getState() == StateRegistry.PLAY) {
connection.write(new StartUpdate());

TransitionConfirmHandler confirm = new TransitionConfirmHandler(connection);
confirm.setPlayer(player);

connection.setActiveSessionHandler(StateRegistry.PLAY, confirm);
confirm.waitForConfirmation(() -> this.connectToServer(logger, player, connection));
} else if (connection.getState() == StateRegistry.PLAY) {
// Synchronize with the client to ensure that it will not corrupt CONFIG state with PLAY packets
((LimboSessionHandlerImpl) connection.getActiveSessionHandler())
.disconnectToConfig(() -> this.connectToServer(logger, player, connection));

return;
}

connection.setActiveSessionHandler(StateRegistry.CONFIG,
return; // Re-running this method due to synchronization with the client
} else {
this.plugin.setActiveSessionHandler(connection, StateRegistry.CONFIG,
new ClientConfigSessionHandler(this.server, this.player));
}

Expand Down

This file was deleted.

Loading

0 comments on commit 3e6d826

Please sign in to comment.