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.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
}
}

View File

@ -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<String,RegisteredServer> 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<RegisteredServer> initialFromConfig = player.getNextServerToTry();
PlayerChooseInitialServerEvent event = new PlayerChooseInitialServerEvent(player,
initialFromConfig.orElse(null));
server.getEventManager().fire(event)
.thenRun(() -> {
Optional<RegisteredServer> 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();
}
}

View File

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

View File

@ -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)

View File

@ -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
}
}

View File

@ -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);
}

View File

@ -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 {