Reconnect-to-sync handler

This commit is contained in:
Adrian Bergqvist 2022-10-19 23:12:36 +02:00
parent 81d22900fa
commit b021bed467
No known key found for this signature in database
GPG Key ID: FAE7D8EDE225E686
7 changed files with 136 additions and 63 deletions

View File

@ -30,21 +30,17 @@ import java.util.NoSuchElementException;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class FML2CRPMClientConnectionPhase implements VelocityForgeClientConnectionPhase { public class FML2CRPMClientConnectionPhase extends VelocityForgeClientConnectionPhase {
private static String OUTBOUND_CATCHER_NAME = "ambassador-catcher"; private static String OUTBOUND_CATCHER_NAME = "ambassador-catcher";
//TODO: Use modData inside ConnectedPlayer instead //TODO: Use modData inside ConnectedPlayer instead
public byte[] modListData; public byte[] modListData;
private RegisteredServer backupServer; private RegisteredServer backupServer;
private VelocityLoginPayloadManager payloadManager;
public ClientPhase clientPhase = ClientPhase.HANDSHAKE;
@Override @Override
public void handleLogin(ConnectedPlayer player, VelocityServer server, Continuation continuation) { public void handleLogin(ConnectedPlayer player, VelocityServer server, Continuation continuation) {
final MinecraftConnection connection = player.getConnection(); final MinecraftConnection connection = player.getConnection();
payloadManager = new VelocityLoginPayloadManager(connection);
VelocityForgeHandshakeSessionHandler sessionHandler = new VelocityForgeHandshakeSessionHandler(connection.getSessionHandler(), player); 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) if (modListData == null)
modListData = ByteBufUtil.getBytes(data); modListData = ByteBufUtil.getBytes(data);
this.clientPhase = ClientPhase.MODDED; this.clientPhase = ClientPhase.MODDED;
@ -54,11 +50,7 @@ public class FML2CRPMClientConnectionPhase implements VelocityForgeClientConnect
connection.flush(); connection.flush();
} }
@Override public void reset(VelocityServerConnection serverConnection, ConnectedPlayer player, Runnable whenComplete) {
public boolean handle(ConnectedPlayer player, LoginPluginResponse packet) {
return true;
}
public void reset(ConnectedPlayer player, Runnable whenComplete) {
if (player.getConnectedServer() != null) { if (player.getConnectedServer() != null) {
backupServer = player.getConnectedServer().getServer(); backupServer = player.getConnectedServer().getServer();
player.getConnectedServer().disconnect(); player.getConnectedServer().disconnect();
@ -70,12 +62,12 @@ public class FML2CRPMClientConnectionPhase implements VelocityForgeClientConnect
if (connection.getState() == StateRegistry.LOGIN) { if (connection.getState() == StateRegistry.LOGIN) {
payloadManager.sendPayload("fml:loginwrapper", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generateResetPacket())); getPayloadManager().sendPayload("fml:loginwrapper", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generateResetPacket()));
} else { } else {
connection.write(new PluginMessage("fml:handshake",Unpooled.wrappedBuffer(ForgeHandshakeUtils.generatePluginResetPacket()))); connection.write(new PluginMessage("fml:handshake",Unpooled.wrappedBuffer(ForgeHandshakeUtils.generatePluginResetPacket())));
connection.setState(StateRegistry.LOGIN); connection.setState(StateRegistry.LOGIN);
} }
payloadManager.listenFor(98).thenAccept((ignored) -> { getPayloadManager().listenFor(98).thenAccept((ignored) -> {
this.clientPhase = ClientPhase.HANDSHAKE; this.clientPhase = ClientPhase.HANDSHAKE;
whenComplete.run(); 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
}
} }

View File

@ -1,42 +1,109 @@
package org.adde0109.ambassador.forge; package org.adde0109.ambassador.forge;
import com.velocitypowered.api.event.Continuation; 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.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; 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.ConnectedPlayer;
import com.velocitypowered.proxy.connection.client.LoginSessionHandler; import com.velocitypowered.proxy.connection.client.LoginSessionHandler;
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; 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.VelocityForgeClientConnectionPhase;
import org.adde0109.ambassador.velocity.VelocityLoginPayloadManager; 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.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<String,RegisteredServer> TEMPORARY_FORCED = new PassiveExpiringMap<>(120, TimeUnit.SECONDS);
private Throwable throwable;
private RegisteredServer triedServer;
private Continuation continuation;
@Override @Override
public void handleLogin(ConnectedPlayer player, VelocityServer server, Continuation continuation) { public void handleLogin(ConnectedPlayer player, VelocityServer server, Continuation continuation) {
MinecraftSessionHandler sessionHandler = player.getConnection().getSessionHandler(); this.continuation = continuation;
if (player.getConnection().getSessionHandler() == null) { final MinecraftConnection connection = player.getConnection();
continuation.resumeWithException(new Exception("No current player session handler"));
return; final Runnable defaultTask = () -> {
} Optional<RegisteredServer> initialFromConfig = player.getNextServerToTry();
if (!(player.getConnection().getSessionHandler() instanceof LoginSessionHandler)) { PlayerChooseInitialServerEvent event = new PlayerChooseInitialServerEvent(player,
continuation.resumeWithException(new Exception("Invalid current player session handler:" + player.getConnection().getSessionHandler().getClass().getName())); initialFromConfig.orElse(null));
return; server.getEventManager().fire(event)
} .thenRun(() -> {
try { Optional<RegisteredServer> toTry = event.getInitialServer();
Method connectToInitalServer = LoginSessionHandler.class.getDeclaredMethod("connectToInitialServer"); tryServer(player, toTry.orElse(null));
connectToInitalServer.setAccessible(true); });
connectToInitalServer.invoke(sessionHandler); };
} catch (ReflectiveOperationException e) {
continuation.resumeWithException(e); 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 @Override
public boolean handle(ConnectedPlayer player, LoginPluginResponse packet) { public void reset(VelocityServerConnection serverConnection, ConnectedPlayer player, Runnable whenComplete) {
return VelocityForgeClientConnectionPhase.super.handle(player, packet); 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();
} }
} }

View File

@ -13,7 +13,7 @@ public class ForgeFML2ConnectionType implements ConnectionType {
@Override @Override
public ClientConnectionPhase getInitialClientPhase() { public ClientConnectionPhase getInitialClientPhase() {
return new FML2CRPMClientConnectionPhase(); return new FML2ClientConnectionPhase();
} }
@Override @Override

View File

@ -30,7 +30,7 @@ public class VelocityEventHandler {
continuation.resume(); continuation.resume();
return; 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) @Subscribe(order = PostOrder.LAST)

View File

@ -3,26 +3,57 @@ package org.adde0109.ambassador.velocity;
import com.velocitypowered.api.event.Continuation; import com.velocitypowered.api.event.Continuation;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection; 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.ClientConnectionPhase;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
import org.adde0109.ambassador.forge.FML2CRPMClientConnectionPhase;
import org.adde0109.ambassador.forge.ForgeHandshakeUtils; import org.adde0109.ambassador.forge.ForgeHandshakeUtils;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
public interface VelocityForgeClientConnectionPhase extends ClientConnectionPhase { public abstract class VelocityForgeClientConnectionPhase implements ClientConnectionPhase {
//TODO:Make class when PCF is done //TODO:Make class when PCF is done
default void handleLogin(ConnectedPlayer player, VelocityServer server, Continuation continuation) { VelocityLoginPayloadManager payloadManager;
public FML2CRPMClientConnectionPhase.ClientPhase clientPhase = ClientPhase.HANDSHAKE;
} public void handleLogin(ConnectedPlayer player, VelocityServer server, Continuation continuation) {
default boolean handle(ConnectedPlayer player,LoginPluginResponse packet) {
return false;
} }
default VelocityLoginPayloadManager getPayloadManager() { public void reset(VelocityServerConnection serverConnection,ConnectedPlayer player, Runnable whenComplete) {
return null; }
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
} }
} }

View File

@ -6,6 +6,7 @@ import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
import org.adde0109.ambassador.forge.FML2CRPMClientConnectionPhase; import org.adde0109.ambassador.forge.FML2CRPMClientConnectionPhase;
import org.adde0109.ambassador.velocity.VelocityForgeClientConnectionPhase;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -18,17 +19,17 @@ public class VelocityForgeBackendConnectionPhase implements BackendConnectionPha
} }
public void handleSuccess(VelocityServerConnection serverCon, VelocityServer server) { public void handleSuccess(VelocityServerConnection serverCon, VelocityServer server) {
FML2CRPMClientConnectionPhase clientPhase = ((FML2CRPMClientConnectionPhase) serverCon.getPlayer().getPhase()); VelocityForgeClientConnectionPhase clientPhase = ((VelocityForgeClientConnectionPhase) serverCon.getPlayer().getPhase());
if (clientPhase.clientPhase == FML2CRPMClientConnectionPhase.ClientPhase.HANDSHAKE || clientPhase.clientPhase == FML2CRPMClientConnectionPhase.ClientPhase.MODLIST) { if (clientPhase.clientPhase == VelocityForgeClientConnectionPhase.ClientPhase.HANDSHAKE || clientPhase.clientPhase == VelocityForgeClientConnectionPhase.ClientPhase.MODLIST) {
clientPhase.complete((VelocityServer) server,serverCon.getPlayer(),serverCon.getPlayer().getConnection()); clientPhase.complete((VelocityServer) server,serverCon.getPlayer(),serverCon.getPlayer().getConnection());
} }
} }
public boolean handle(VelocityServerConnection server, ConnectedPlayer player, LoginPluginMessage message) throws Exception { public boolean handle(VelocityServerConnection server, ConnectedPlayer player, LoginPluginMessage message) throws Exception {
FML2CRPMClientConnectionPhase clientPhase = ((FML2CRPMClientConnectionPhase) player.getPhase()); VelocityForgeClientConnectionPhase clientPhase = ((FML2CRPMClientConnectionPhase) player.getPhase());
message.retain(); message.retain();
if (clientPhase.clientPhase == FML2CRPMClientConnectionPhase.ClientPhase.VANILLA) { if (clientPhase.clientPhase == FML2CRPMClientConnectionPhase.ClientPhase.VANILLA) {
clientPhase.reset(player, () -> { clientPhase.reset(server,player, () -> {
for (LoginPluginMessage msg: queuedHandshakePackets) { for (LoginPluginMessage msg: queuedHandshakePackets) {
clientPhase.forwardPayload(server,msg); clientPhase.forwardPayload(server,msg);
} }

View File

@ -33,7 +33,7 @@ public class VelocityForgeBackendHandshakeHandler extends ChannelDuplexHandler {
if (serverConnection.getPlayer().getPhase() instanceof FML2CRPMClientConnectionPhase phase) { if (serverConnection.getPlayer().getPhase() instanceof FML2CRPMClientConnectionPhase phase) {
init(connection,serverConnection); init(connection,serverConnection);
if (phase.clientPhase == FML2CRPMClientConnectionPhase.ClientPhase.MODDED) { if (phase.clientPhase == FML2CRPMClientConnectionPhase.ClientPhase.MODDED) {
phase.reset(serverConnection.getPlayer(), () -> { phase.reset(serverConnection ,serverConnection.getPlayer(), () -> {
ctx.flush(); ctx.flush();
}); });
} else { } else {