From b021bed4670911849f258d93c0bda6dcefb00920 Mon Sep 17 00:00:00 2001 From: Adrian Bergqvist Date: Wed, 19 Oct 2022 23:12:36 +0200 Subject: [PATCH] Reconnect-to-sync handler --- .../forge/FML2CRPMClientConnectionPhase.java | 36 +----- .../forge/FML2ClientConnectionPhase.java | 103 +++++++++++++++--- .../forge/ForgeFML2ConnectionType.java | 2 +- .../velocity/VelocityEventHandler.java | 2 +- .../VelocityForgeClientConnectionPhase.java | 45 ++++++-- .../VelocityForgeBackendConnectionPhase.java | 9 +- .../VelocityForgeBackendHandshakeHandler.java | 2 +- 7 files changed, 136 insertions(+), 63 deletions(-) diff --git a/src/main/java/org/adde0109/ambassador/forge/FML2CRPMClientConnectionPhase.java b/src/main/java/org/adde0109/ambassador/forge/FML2CRPMClientConnectionPhase.java index 37bac1c..9c31abe 100644 --- a/src/main/java/org/adde0109/ambassador/forge/FML2CRPMClientConnectionPhase.java +++ b/src/main/java/org/adde0109/ambassador/forge/FML2CRPMClientConnectionPhase.java @@ -30,21 +30,17 @@ import java.util.NoSuchElementException; import java.util.UUID; import java.util.concurrent.CompletableFuture; -public class FML2CRPMClientConnectionPhase implements VelocityForgeClientConnectionPhase { +public class FML2CRPMClientConnectionPhase extends VelocityForgeClientConnectionPhase { private static String OUTBOUND_CATCHER_NAME = "ambassador-catcher"; //TODO: Use modData inside ConnectedPlayer instead public byte[] modListData; private RegisteredServer backupServer; - - private VelocityLoginPayloadManager payloadManager; - public ClientPhase clientPhase = ClientPhase.HANDSHAKE; @Override public void handleLogin(ConnectedPlayer player, VelocityServer server, Continuation continuation) { final MinecraftConnection connection = player.getConnection(); - payloadManager = new VelocityLoginPayloadManager(connection); VelocityForgeHandshakeSessionHandler sessionHandler = new VelocityForgeHandshakeSessionHandler(connection.getSessionHandler(), player); - payloadManager.sendPayload("fml:loginwrapper",Unpooled.wrappedBuffer(ForgeHandshakeUtils.emptyModlist)).thenAccept((data) -> { + getPayloadManager().sendPayload("fml:loginwrapper",Unpooled.wrappedBuffer(ForgeHandshakeUtils.emptyModlist)).thenAccept((data) -> { if (modListData == null) modListData = ByteBufUtil.getBytes(data); this.clientPhase = ClientPhase.MODDED; @@ -54,11 +50,7 @@ public class FML2CRPMClientConnectionPhase implements VelocityForgeClientConnect connection.flush(); } - @Override - public boolean handle(ConnectedPlayer player, LoginPluginResponse packet) { - return true; - } - public void reset(ConnectedPlayer player, Runnable whenComplete) { + public void reset(VelocityServerConnection serverConnection, ConnectedPlayer player, Runnable whenComplete) { if (player.getConnectedServer() != null) { backupServer = player.getConnectedServer().getServer(); player.getConnectedServer().disconnect(); @@ -70,12 +62,12 @@ public class FML2CRPMClientConnectionPhase implements VelocityForgeClientConnect if (connection.getState() == StateRegistry.LOGIN) { - payloadManager.sendPayload("fml:loginwrapper", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generateResetPacket())); + getPayloadManager().sendPayload("fml:loginwrapper", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generateResetPacket())); } else { connection.write(new PluginMessage("fml:handshake",Unpooled.wrappedBuffer(ForgeHandshakeUtils.generatePluginResetPacket()))); connection.setState(StateRegistry.LOGIN); } - payloadManager.listenFor(98).thenAccept((ignored) -> { + getPayloadManager().listenFor(98).thenAccept((ignored) -> { this.clientPhase = ClientPhase.HANDSHAKE; whenComplete.run(); }); @@ -112,22 +104,4 @@ public class FML2CRPMClientConnectionPhase implements VelocityForgeClientConnect } } - public void forwardPayload(VelocityServerConnection serverConnection, LoginPluginMessage payload) { - payloadManager.sendPayload("fml:loginwrapper",payload.content()).thenAccept((responseData) -> { - //Move this to the backend. Backend should have its own forwarder. - serverConnection.getConnection().write(new LoginPluginResponse(payload.getId(),responseData.isReadable(),responseData.retain())); - }); - } - - @Override - public VelocityLoginPayloadManager getPayloadManager() { - return payloadManager; - } - - public enum ClientPhase { - VANILLA, - HANDSHAKE, - MODLIST, - MODDED - } } diff --git a/src/main/java/org/adde0109/ambassador/forge/FML2ClientConnectionPhase.java b/src/main/java/org/adde0109/ambassador/forge/FML2ClientConnectionPhase.java index 30a336a..7f60d27 100644 --- a/src/main/java/org/adde0109/ambassador/forge/FML2ClientConnectionPhase.java +++ b/src/main/java/org/adde0109/ambassador/forge/FML2ClientConnectionPhase.java @@ -1,42 +1,109 @@ package org.adde0109.ambassador.forge; import com.velocitypowered.api.event.Continuation; +import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import com.velocitypowered.api.proxy.server.ServerPing; +import com.velocitypowered.api.util.ModInfo; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.LoginSessionHandler; import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import org.adde0109.ambassador.velocity.VelocityForgeClientConnectionPhase; import org.adde0109.ambassador.velocity.VelocityLoginPayloadManager; +import org.apache.commons.collections4.map.PassiveExpiringMap; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.Optional; +import java.util.concurrent.TimeUnit; -public class FML2ClientConnectionPhase implements VelocityForgeClientConnectionPhase { +public class FML2ClientConnectionPhase extends VelocityForgeClientConnectionPhase { + + private static final PassiveExpiringMap TEMPORARY_FORCED = new PassiveExpiringMap<>(120, TimeUnit.SECONDS); + + private Throwable throwable; + private RegisteredServer triedServer; + private Continuation continuation; @Override public void handleLogin(ConnectedPlayer player, VelocityServer server, Continuation continuation) { - MinecraftSessionHandler sessionHandler = player.getConnection().getSessionHandler(); - if (player.getConnection().getSessionHandler() == null) { - continuation.resumeWithException(new Exception("No current player session handler")); - return; - } - if (!(player.getConnection().getSessionHandler() instanceof LoginSessionHandler)) { - continuation.resumeWithException(new Exception("Invalid current player session handler:" + player.getConnection().getSessionHandler().getClass().getName())); - return; - } - try { - Method connectToInitalServer = LoginSessionHandler.class.getDeclaredMethod("connectToInitialServer"); - connectToInitalServer.setAccessible(true); - connectToInitalServer.invoke(sessionHandler); - } catch (ReflectiveOperationException e) { - continuation.resumeWithException(e); + this.continuation = continuation; + final MinecraftConnection connection = player.getConnection(); + + final Runnable defaultTask = () -> { + Optional initialFromConfig = player.getNextServerToTry(); + PlayerChooseInitialServerEvent event = new PlayerChooseInitialServerEvent(player, + initialFromConfig.orElse(null)); + server.getEventManager().fire(event) + .thenRun(() -> { + Optional toTry = event.getInitialServer(); + tryServer(player, toTry.orElse(null)); + }); + }; + + final RegisteredServer forced = TEMPORARY_FORCED.remove(player.getUsername()); + if (forced != null) { + forced.ping().whenCompleteAsync((msg,ex) -> { + if (ex == null) { + if (throwable == null) + throwable = ex; + handlePingResponse(msg); + } else { + defaultTask.run(); + } + },connection.eventLoop()); + } else { + connection.eventLoop().submit(defaultTask); } } @Override - public boolean handle(ConnectedPlayer player, LoginPluginResponse packet) { - return VelocityForgeClientConnectionPhase.super.handle(player, packet); + public void reset(VelocityServerConnection serverConnection, ConnectedPlayer player, Runnable whenComplete) { + TEMPORARY_FORCED.put(player.getUsername(),serverConnection.getServer()); + player.disconnect(Component.text("Please reconnect")); + } + + @Override + public void complete(VelocityServer server, ConnectedPlayer player, MinecraftConnection connection) { + if (triedServer != null) + player.sendMessage(Component.translatable("velocity.error.connecting-server-error", + Component.text(triedServer.getServerInfo().getName()))); + } + + private void tryServer(ConnectedPlayer player, RegisteredServer server) { + if (server == null) { + player.disconnect0(Component.translatable("velocity.error.no-available-servers", + NamedTextColor.RED), true); + return; + } + server.ping().whenCompleteAsync((msg,ex) -> { + if (ex != null) { + if (throwable == null) + throwable = ex; + tryServer(player,player.getNextServerToTry().orElse(null)); + } else { + handlePingResponse(msg); + } + }, player.getConnection().eventLoop()); + } + + private void handlePingResponse(ServerPing ping) { + if (ping.getModinfo().isEmpty() || !ping.getModinfo().get().getType().equals("Ambassador")) { + continuation.resume(); + return; + } + ModInfo.Mod mod = ping.getModinfo().get().getMods().get(0); + String data = mod.getId(); + String markers = mod.getVersion(); + } } diff --git a/src/main/java/org/adde0109/ambassador/forge/ForgeFML2ConnectionType.java b/src/main/java/org/adde0109/ambassador/forge/ForgeFML2ConnectionType.java index c3aa079..0229aa8 100644 --- a/src/main/java/org/adde0109/ambassador/forge/ForgeFML2ConnectionType.java +++ b/src/main/java/org/adde0109/ambassador/forge/ForgeFML2ConnectionType.java @@ -13,7 +13,7 @@ public class ForgeFML2ConnectionType implements ConnectionType { @Override public ClientConnectionPhase getInitialClientPhase() { - return new FML2CRPMClientConnectionPhase(); + return new FML2ClientConnectionPhase(); } @Override diff --git a/src/main/java/org/adde0109/ambassador/velocity/VelocityEventHandler.java b/src/main/java/org/adde0109/ambassador/velocity/VelocityEventHandler.java index 59869ca..f9405d0 100644 --- a/src/main/java/org/adde0109/ambassador/velocity/VelocityEventHandler.java +++ b/src/main/java/org/adde0109/ambassador/velocity/VelocityEventHandler.java @@ -30,7 +30,7 @@ public class VelocityEventHandler { continuation.resume(); return; } - player.getConnection().eventLoop().submit(() -> phase.handleLogin(player, (VelocityServer) ambassador.server,continuation)); + player.getConnection().eventLoop().submit(() -> phase.fireLoginEvent(player, (VelocityServer) ambassador.server,continuation)); } @Subscribe(order = PostOrder.LAST) diff --git a/src/main/java/org/adde0109/ambassador/velocity/VelocityForgeClientConnectionPhase.java b/src/main/java/org/adde0109/ambassador/velocity/VelocityForgeClientConnectionPhase.java index 011d810..41e48f3 100644 --- a/src/main/java/org/adde0109/ambassador/velocity/VelocityForgeClientConnectionPhase.java +++ b/src/main/java/org/adde0109/ambassador/velocity/VelocityForgeClientConnectionPhase.java @@ -3,26 +3,57 @@ package org.adde0109.ambassador.velocity; import com.velocitypowered.api.event.Continuation; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.MinecraftConnection; +import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.client.ClientConnectionPhase; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; +import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; +import org.adde0109.ambassador.forge.FML2CRPMClientConnectionPhase; import org.adde0109.ambassador.forge.ForgeHandshakeUtils; import javax.annotation.Nullable; import java.util.ArrayList; -public interface VelocityForgeClientConnectionPhase extends ClientConnectionPhase { +public abstract class VelocityForgeClientConnectionPhase implements ClientConnectionPhase { //TODO:Make class when PCF is done - default void handleLogin(ConnectedPlayer player, VelocityServer server, Continuation continuation) { + VelocityLoginPayloadManager payloadManager; + public FML2CRPMClientConnectionPhase.ClientPhase clientPhase = ClientPhase.HANDSHAKE; - } - default boolean handle(ConnectedPlayer player,LoginPluginResponse packet) { - return false; + public void handleLogin(ConnectedPlayer player, VelocityServer server, Continuation continuation) { } - default VelocityLoginPayloadManager getPayloadManager() { - return null; + public void reset(VelocityServerConnection serverConnection,ConnectedPlayer player, Runnable whenComplete) { + } + + public void complete(VelocityServer server, ConnectedPlayer player, MinecraftConnection connection) { + + } + + final void fireLoginEvent(ConnectedPlayer player, VelocityServer server, Continuation continuation) { + payloadManager = new VelocityLoginPayloadManager(player.getConnection()); + handleLogin(player,server,continuation); + } + + public void forwardPayload(VelocityServerConnection serverConnection, LoginPluginMessage payload) { + if (payloadManager == null) { + return; + } + payloadManager.sendPayload("fml:loginwrapper",payload.content()).thenAccept((responseData) -> { + //Move this to the backend. Backend should have its own forwarder. + serverConnection.getConnection().write(new LoginPluginResponse(payload.getId(),responseData.isReadable(),responseData.retain())); + }); + } + + public final VelocityLoginPayloadManager getPayloadManager() { + return payloadManager; + } + + public enum ClientPhase { + VANILLA, + HANDSHAKE, + MODLIST, + MODDED } } diff --git a/src/main/java/org/adde0109/ambassador/velocity/backend/VelocityForgeBackendConnectionPhase.java b/src/main/java/org/adde0109/ambassador/velocity/backend/VelocityForgeBackendConnectionPhase.java index 9987e03..6989ece 100644 --- a/src/main/java/org/adde0109/ambassador/velocity/backend/VelocityForgeBackendConnectionPhase.java +++ b/src/main/java/org/adde0109/ambassador/velocity/backend/VelocityForgeBackendConnectionPhase.java @@ -6,6 +6,7 @@ import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; import org.adde0109.ambassador.forge.FML2CRPMClientConnectionPhase; +import org.adde0109.ambassador.velocity.VelocityForgeClientConnectionPhase; import java.util.ArrayList; import java.util.List; @@ -18,17 +19,17 @@ public class VelocityForgeBackendConnectionPhase implements BackendConnectionPha } public void handleSuccess(VelocityServerConnection serverCon, VelocityServer server) { - FML2CRPMClientConnectionPhase clientPhase = ((FML2CRPMClientConnectionPhase) serverCon.getPlayer().getPhase()); - if (clientPhase.clientPhase == FML2CRPMClientConnectionPhase.ClientPhase.HANDSHAKE || clientPhase.clientPhase == FML2CRPMClientConnectionPhase.ClientPhase.MODLIST) { + VelocityForgeClientConnectionPhase clientPhase = ((VelocityForgeClientConnectionPhase) serverCon.getPlayer().getPhase()); + if (clientPhase.clientPhase == VelocityForgeClientConnectionPhase.ClientPhase.HANDSHAKE || clientPhase.clientPhase == VelocityForgeClientConnectionPhase.ClientPhase.MODLIST) { clientPhase.complete((VelocityServer) server,serverCon.getPlayer(),serverCon.getPlayer().getConnection()); } } public boolean handle(VelocityServerConnection server, ConnectedPlayer player, LoginPluginMessage message) throws Exception { - FML2CRPMClientConnectionPhase clientPhase = ((FML2CRPMClientConnectionPhase) player.getPhase()); + VelocityForgeClientConnectionPhase clientPhase = ((FML2CRPMClientConnectionPhase) player.getPhase()); message.retain(); if (clientPhase.clientPhase == FML2CRPMClientConnectionPhase.ClientPhase.VANILLA) { - clientPhase.reset(player, () -> { + clientPhase.reset(server,player, () -> { for (LoginPluginMessage msg: queuedHandshakePackets) { clientPhase.forwardPayload(server,msg); } diff --git a/src/main/java/org/adde0109/ambassador/velocity/backend/VelocityForgeBackendHandshakeHandler.java b/src/main/java/org/adde0109/ambassador/velocity/backend/VelocityForgeBackendHandshakeHandler.java index c1f4aeb..227ab15 100644 --- a/src/main/java/org/adde0109/ambassador/velocity/backend/VelocityForgeBackendHandshakeHandler.java +++ b/src/main/java/org/adde0109/ambassador/velocity/backend/VelocityForgeBackendHandshakeHandler.java @@ -33,7 +33,7 @@ public class VelocityForgeBackendHandshakeHandler extends ChannelDuplexHandler { if (serverConnection.getPlayer().getPhase() instanceof FML2CRPMClientConnectionPhase phase) { init(connection,serverConnection); if (phase.clientPhase == FML2CRPMClientConnectionPhase.ClientPhase.MODDED) { - phase.reset(serverConnection.getPlayer(), () -> { + phase.reset(serverConnection ,serverConnection.getPlayer(), () -> { ctx.flush(); }); } else {