diff --git a/build.gradle b/build.gradle index 8496662..ea52581 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { } group 'org.adde0109' -version '1.0.0-alpha' +version '1.0.2-alpha' repositories { maven { diff --git a/src/main/java/org/adde0109/ambassador/Ambassador.java b/src/main/java/org/adde0109/ambassador/Ambassador.java index f97f796..1616806 100644 --- a/src/main/java/org/adde0109/ambassador/Ambassador.java +++ b/src/main/java/org/adde0109/ambassador/Ambassador.java @@ -20,6 +20,7 @@ import org.adde0109.ambassador.forge.ForgeConnection; import org.adde0109.ambassador.forge.ForgeHandshakeHandler; import org.adde0109.ambassador.forge.ForgeHandshakeUtils; import org.adde0109.ambassador.forge.ForgeServerSwitchHandler; +import org.adde0109.ambassador.velocity.VelocityBackendChannelInitializer; import org.adde0109.ambassador.velocity.VelocityServerChannelInitializer; import org.adde0109.ambassador.velocity.VelocityEventHandler; import org.bstats.charts.SingleLineChart; @@ -28,7 +29,7 @@ import org.slf4j.Logger; import java.nio.file.Path; -@Plugin(id = "ambassador", name = "Ambassador", version = "1.0.0-alpha", authors = {"adde0109"}) +@Plugin(id = "ambassador", name = "Ambassador", version = "1.0.2-alpha", authors = {"adde0109"}) public class Ambassador { public ProxyServer server; @@ -72,11 +73,12 @@ public class Ambassador { private void inject() throws ReflectiveOperationException { Field cmField = VelocityServer.class.getDeclaredField("cm"); cmField.setAccessible(true); - Field endpointMap = ConnectionManager.class.getDeclaredField("endpoints"); - endpointMap.setAccessible(true); ChannelInitializer original = ((ConnectionManager) cmField.get(server)).serverChannelInitializer.get(); ((ConnectionManager) cmField.get(server)).serverChannelInitializer.set(new VelocityServerChannelInitializer(original)); + + ChannelInitializer originalBackend = ((ConnectionManager) cmField.get(server)).backendChannelInitializer.get(); + ((ConnectionManager) cmField.get(server)).backendChannelInitializer.set(new VelocityBackendChannelInitializer(originalBackend,(VelocityServer) server)); } @Subscribe diff --git a/src/main/java/org/adde0109/ambassador/forge/ForgeConstants.java b/src/main/java/org/adde0109/ambassador/forge/ForgeConstants.java index eada8d1..0b670a5 100644 --- a/src/main/java/org/adde0109/ambassador/forge/ForgeConstants.java +++ b/src/main/java/org/adde0109/ambassador/forge/ForgeConstants.java @@ -2,4 +2,5 @@ package org.adde0109.ambassador.forge; public class ForgeConstants { public static final String HANDLER = "Modern Forge handler"; + public static final ForgeFML2ConnectionType ForgeFML2 = new ForgeFML2ConnectionType(); } diff --git a/src/main/java/org/adde0109/ambassador/forge/ForgeFML2ClientConnectionPhase.java b/src/main/java/org/adde0109/ambassador/forge/ForgeFML2ClientConnectionPhase.java index 7296ea3..4fabb3a 100644 --- a/src/main/java/org/adde0109/ambassador/forge/ForgeFML2ClientConnectionPhase.java +++ b/src/main/java/org/adde0109/ambassador/forge/ForgeFML2ClientConnectionPhase.java @@ -25,13 +25,12 @@ import org.checkerframework.checker.units.qual.A; import org.checkerframework.checker.units.qual.C; import javax.annotation.Nullable; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Future; +import java.util.concurrent.*; public class ForgeFML2ClientConnectionPhase implements VelocityForgeClientConnectionPhase { private boolean isResettable; @@ -40,24 +39,30 @@ public class ForgeFML2ClientConnectionPhase implements VelocityForgeClientConnec public byte[] modListData; private final ArrayList listenerList = new ArrayList(); - public ArrayList packagesToSendAfterReset = new ArrayList<>(); private Runnable whenComplete; - public boolean isReady = false; + public ClientPhase clientPhase = ClientPhase.HANDSHAKE; @Override public void handleLogin(ConnectedPlayer player,ForgeHandshakeUtils.CachedServerHandshake handshake, Continuation continuation) { - this.whenComplete = continuation::resume; final MinecraftConnection connection = player.getConnection(); - VelocityForgeHandshakeSessionHandler sessionHandler = new VelocityForgeHandshakeSessionHandler(connection.getSessionHandler(),player); - if(handshake == null) { - connection.delayedWrite(new LoginPluginMessage(0,"fml:loginwrapper", Unpooled.wrappedBuffer(ForgeHandshakeUtils.emptyModlist))); - listenerList.add(0); + VelocityForgeHandshakeSessionHandler sessionHandler = new VelocityForgeHandshakeSessionHandler(connection.getSessionHandler(), player); + if (handshake == null) { + connection.delayedWrite(new LoginPluginMessage(98, "fml:loginwrapper", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generateResetPacket()))); + listenerList.add(98); + this.whenComplete = () -> { + this.clientPhase = ClientPhase.VANILLA; + continuation.resume(); + }; } else { - connection.delayedWrite(new LoginPluginMessage(0,"fml:loginwrapper", Unpooled.wrappedBuffer(handshake.modListPacket))); + connection.delayedWrite(new LoginPluginMessage(0, "fml:loginwrapper", Unpooled.wrappedBuffer(handshake.modListPacket))); listenerList.add(0); - for (int i = 0;i { + this.clientPhase = ClientPhase.MODDED; + continuation.resume(); + }; } connection.setSessionHandler(sessionHandler); connection.flush(); @@ -65,40 +70,35 @@ public class ForgeFML2ClientConnectionPhase implements VelocityForgeClientConnec @Override public boolean handle(ConnectedPlayer player, LoginPluginResponse packet) { + if (packet.getId() == 98) { + this.clientPhase = ClientPhase.HANDSHAKE; + this.isResettable = packet.isSuccess(); + } else if (packet.getId() == 0) { + this.clientPhase = ClientPhase.MODLIST; + } if (!listenerList.removeIf(id -> id.equals(packet.getId()))) { player.getConnectionInFlight().getConnection().write(packet.retain()); - return true; - } - if (packet.getId() == 98) { - isReady = true; - for (MinecraftPacket packet1 : packagesToSendAfterReset) { - player.getConnection().delayedWrite(packet1); - } - packagesToSendAfterReset = new ArrayList<>(); - player.getConnection().flush(); - } else if (packet.getId() == 0) { - if (!packet.isSuccess()) { - //TODO: Write disconnect message to end user - player.getConnection().close(); - return true; - } - modListData = ByteBufUtil.getBytes(packet.content()); - } - if (listenerList.isEmpty() && whenComplete != null) { + } else if (listenerList.isEmpty() && whenComplete != null) { whenComplete.run(); whenComplete = null; } return true; } - public void reset(ConnectedPlayer player,MinecraftConnection connection) { - isReady = false; + public void reset(ConnectedPlayer player, Runnable whenComplete) { + this.whenComplete = whenComplete; if (player.getConnectedServer() != null) { player.getConnectedServer().disconnect(); } + MinecraftConnection connection = player.getConnection(); connection.setSessionHandler(new VelocityForgeHandshakeSessionHandler(connection.getSessionHandler(),player)); - connection.write(new PluginMessage("fml:handshake",Unpooled.wrappedBuffer(ForgeHandshakeUtils.generatePluginResetPacket()))); + if (connection.getState() == StateRegistry.LOGIN) { + connection.write(new LoginPluginMessage(98,"fml:loginwrapper", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generateResetPacket()))); + } else { + connection.write(new PluginMessage("fml:handshake",Unpooled.wrappedBuffer(ForgeHandshakeUtils.generatePluginResetPacket()))); + connection.setState(StateRegistry.LOGIN); + } listenerList.add(98); - connection.setState(StateRegistry.LOGIN); + this.clientPhase = null; } public void complete(VelocityServer server, ConnectedPlayer player, MinecraftConnection connection) { VelocityConfiguration configuration = (VelocityConfiguration) server.getConfiguration(); @@ -109,16 +109,21 @@ public class ForgeFML2ClientConnectionPhase implements VelocityForgeClientConnec ServerLoginSuccess success = new ServerLoginSuccess(); success.setUsername(player.getUsername()); success.setUuid(playerUniqueId); - send(player,success); + connection.write(success); + + this.clientPhase = this.clientPhase == ClientPhase.MODLIST ? ClientPhase.MODDED : ClientPhase.VANILLA; connection.setState(StateRegistry.PLAY); connection.setSessionHandler(((VelocityForgeHandshakeSessionHandler) connection.getSessionHandler()).getOriginal()); } public void send(ConnectedPlayer player, MinecraftPacket message) { - if (isReady) { player.getConnection().write(message); - } else { - packagesToSendAfterReset.add(message); - } + } + + public enum ClientPhase { + VANILLA, + HANDSHAKE, + MODLIST, + MODDED } } diff --git a/src/main/java/org/adde0109/ambassador/forge/ForgeFML2ConnectionType.java b/src/main/java/org/adde0109/ambassador/forge/ForgeFML2ConnectionType.java index fe2d34f..9a6aba7 100644 --- a/src/main/java/org/adde0109/ambassador/forge/ForgeFML2ConnectionType.java +++ b/src/main/java/org/adde0109/ambassador/forge/ForgeFML2ConnectionType.java @@ -20,7 +20,7 @@ public class ForgeFML2ConnectionType implements ConnectionType { @Override public BackendConnectionPhase getInitialBackendPhase() { - return BackendConnectionPhases.UNKNOWN; + return new VelocityForgeBackendConnectionPhase(); } @Override diff --git a/src/main/java/org/adde0109/ambassador/velocity/VelocityBackendChannelInitializer.java b/src/main/java/org/adde0109/ambassador/velocity/VelocityBackendChannelInitializer.java index 72e5ea6..3871195 100644 --- a/src/main/java/org/adde0109/ambassador/velocity/VelocityBackendChannelInitializer.java +++ b/src/main/java/org/adde0109/ambassador/velocity/VelocityBackendChannelInitializer.java @@ -1,16 +1,11 @@ package org.adde0109.ambassador.velocity; import com.velocitypowered.proxy.VelocityServer; -import com.velocitypowered.proxy.connection.MinecraftConnection; -import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import io.netty.channel.Channel; -import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; import org.adde0109.ambassador.forge.ForgeConstants; -import org.adde0109.ambassador.forge.ForgeFML2ClientConnectionPhase; -import org.adde0109.ambassador.forge.ForgeFML2ConnectionType; import org.adde0109.ambassador.velocity.backend.VelocityForgeBackendHandshakeHandler; -import org.jetbrains.annotations.NotNull; + import java.lang.reflect.Method; @@ -36,20 +31,8 @@ public class VelocityBackendChannelInitializer extends ChannelInitializer queuedHandshakePackets = new ArrayList<>(); - public VelocityForgeBackendConnectionPhase(Ambassador ambassador) { - this.ambassador = ambassador; + public VelocityForgeBackendConnectionPhase() { } public void handleSuccess(VelocityServerConnection serverCon, VelocityServer server) { ForgeFML2ClientConnectionPhase clientPhase = ((ForgeFML2ClientConnectionPhase) serverCon.getPlayer().getPhase()); - if (vanilla) { - clientPhase.reset(serverCon.getPlayer(),serverCon.getPlayer().getConnection()); + if (clientPhase.clientPhase == ForgeFML2ClientConnectionPhase.ClientPhase.HANDSHAKE || clientPhase.clientPhase == ForgeFML2ClientConnectionPhase.ClientPhase.MODLIST) { + clientPhase.complete((VelocityServer) server,serverCon.getPlayer(),serverCon.getPlayer().getConnection()); } - clientPhase.complete((VelocityServer) ambassador.server,serverCon.getPlayer(),serverCon.getPlayer().getConnection()); } - public boolean handle(VelocityServerConnection server, ConnectedPlayer player, LoginPluginMessage message) { - vanilla = false; + public boolean handle(VelocityServerConnection server, ConnectedPlayer player, LoginPluginMessage message) throws Exception { + ForgeFML2ClientConnectionPhase clientPhase = ((ForgeFML2ClientConnectionPhase) player.getPhase()); message.retain(); - ((ForgeFML2ClientConnectionPhase) player.getPhase()).send(player,message); + if (clientPhase.clientPhase == ForgeFML2ClientConnectionPhase.ClientPhase.VANILLA) { + clientPhase.reset(player, () -> { + for (LoginPluginMessage msg: queuedHandshakePackets) { + player.getConnection().delayedWrite(msg); + } + player.getConnection().flush(); + }); + queuedHandshakePackets.add(message); + } else if (clientPhase.clientPhase != null) { + player.getConnection().write(message); + } else { + queuedHandshakePackets.add(message); + } return true; } 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 7006efb..67e0b71 100644 --- a/src/main/java/org/adde0109/ambassador/velocity/backend/VelocityForgeBackendHandshakeHandler.java +++ b/src/main/java/org/adde0109/ambassador/velocity/backend/VelocityForgeBackendHandshakeHandler.java @@ -3,34 +3,73 @@ package org.adde0109.ambassador.velocity.backend; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; +import com.velocitypowered.proxy.network.Connections; import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; +import com.velocitypowered.proxy.protocol.packet.ServerLogin; import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.*; import io.netty.util.ReferenceCountUtil; +import org.adde0109.ambassador.forge.ForgeConstants; +import org.adde0109.ambassador.forge.ForgeFML2ClientConnectionPhase; -public class VelocityForgeBackendHandshakeHandler extends ChannelInboundHandlerAdapter { +import java.util.concurrent.CountDownLatch; - private final MinecraftConnection connection; - private final VelocityServerConnection serverConnection; +public class VelocityForgeBackendHandshakeHandler extends ChannelDuplexHandler { + + private VelocityServerConnection serverConnection; private final VelocityServer server; - public VelocityForgeBackendHandshakeHandler(MinecraftConnection connection, VelocityServerConnection serverConnection, VelocityServer server) { - this.connection = connection; - this.serverConnection = serverConnection; + public VelocityForgeBackendHandshakeHandler(VelocityServer server) { this.server = server; } @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) { + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + if ((msg instanceof ServerLogin)) { + ChannelHandler handler = ctx.pipeline().get(Connections.HANDLER); + if (handler instanceof MinecraftConnection connection) { + if (connection.getAssociation() instanceof VelocityServerConnection serverConnection) { + if (serverConnection.getPlayer().getPhase() instanceof ForgeFML2ClientConnectionPhase phase) { + init(connection,serverConnection); + if (phase.clientPhase == ForgeFML2ClientConnectionPhase.ClientPhase.MODDED) { + phase.reset(serverConnection.getPlayer(), () -> { + ctx.write(msg, promise); + ctx.flush(); + }); + } else { + ctx.write(msg,promise); + } + } else { + ctx.pipeline().remove(this); + ctx.write(msg,promise); + } + } else { + throw new Exception("Connection not associated with a server connection"); + } + } else { + throw new Exception("Default minecraft packet handler not found"); + } + } else { + ctx.write(msg,promise); + } + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof LoginPluginMessage message && message.getChannel().equals("fml:loginwrapper")) { ((VelocityForgeBackendConnectionPhase) serverConnection.getPhase()).handle(serverConnection, serverConnection.getPlayer(), message); ReferenceCountUtil.release(msg); } else if (msg instanceof ServerLoginSuccess) { ((VelocityForgeBackendConnectionPhase) serverConnection.getPhase()).handleSuccess(serverConnection,server); - ReferenceCountUtil.release(msg); + ctx.fireChannelRead(msg); } else { ctx.fireChannelRead(msg); } } + + private void init(MinecraftConnection connection, VelocityServerConnection serverConnection) { + this.serverConnection = serverConnection; + connection.setType(ForgeConstants.ForgeFML2); + serverConnection.setConnectionPhase(connection.getType().getInitialBackendPhase()); + } }