diff --git a/Velocity b/Velocity index eb594fc..c3583e1 160000 --- a/Velocity +++ b/Velocity @@ -1 +1 @@ -Subproject commit eb594fc799281ff418dc2c161c2d8a8eb0c89a19 +Subproject commit c3583e182ca6585e40d1eef0da8c18547c0b1bc1 diff --git a/src/main/java/org/adde0109/ambassador/forge/ShadowHandshakeReceiver.java b/src/main/java/org/adde0109/ambassador/forge/ShadowHandshakeReceiver.java new file mode 100644 index 0000000..32d4576 --- /dev/null +++ b/src/main/java/org/adde0109/ambassador/forge/ShadowHandshakeReceiver.java @@ -0,0 +1,70 @@ +package org.adde0109.ambassador.forge; + +import com.velocitypowered.proxy.connection.client.ConnectedPlayer; +import org.adde0109.ambassador.forge.packet.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.zip.Adler32; +import java.util.zip.Checksum; + +public class ShadowHandshakeReceiver { + + private final ConnectedPlayer player; + private final ModListReplyPacket modListReplyPacket; + private final Map registries; + + private ShadowHandshakeReceiver(ConnectedPlayer player, ModListReplyPacket modListReplyPacket, + Map registries) { + this.player = player; + this.modListReplyPacket = modListReplyPacket; + this.registries = registries; + } + + void handle(IForgeLoginWrapperPacket packet) throws IncompatibleHandshake { + + } + + void handle(ModListPacket packet) throws IncompatibleHandshake { + + //player.getConnection().write(); + } + + void handle(RegistryPacket packet) throws IncompatibleHandshake { + + player.getConnection().write(new ACKPacket(Context.fromContext(packet.getContext(), true))); + } + void handle(ConfigDataPacket packet) throws IncompatibleHandshake { + + player.getConnection().write(new ACKPacket(Context.fromContext(packet.getContext(), true))); + } + + static class Builder { + + ConnectedPlayer player; + ModListReplyPacket modListReplyPacket; + Map registries = new HashMap<>(); + + void addModListPacket(ModListPacket packet) { + + } + void setModListReplyPacket(ModListReplyPacket packet) { + this.modListReplyPacket = packet; + } + + void addRegistryPacket(RegistryPacket packet) { + Checksum registryChecksum = new Adler32(); + registryChecksum.update(packet.getSnapshot()); + registries.put(packet.getRegistryName(), registryChecksum.getValue()); + } + + ShadowHandshakeReceiver build() { + return new ShadowHandshakeReceiver(player, modListReplyPacket, registries); + } + } + + + class IncompatibleHandshake extends Throwable { + + } +} diff --git a/src/main/java/org/adde0109/ambassador/forge/VelocityForgeBackendConnectionPhase.java b/src/main/java/org/adde0109/ambassador/forge/VelocityForgeBackendConnectionPhase.java index 6143c77..245d490 100644 --- a/src/main/java/org/adde0109/ambassador/forge/VelocityForgeBackendConnectionPhase.java +++ b/src/main/java/org/adde0109/ambassador/forge/VelocityForgeBackendConnectionPhase.java @@ -6,6 +6,7 @@ import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.network.Connections; import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.packet.AvailableCommands; import com.velocitypowered.proxy.protocol.packet.PluginMessage; import net.kyori.adventure.identity.Identity; @@ -18,10 +19,10 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhase { - NOT_STARTED() { + NOT_STARTED { @Override VelocityForgeBackendConnectionPhase nextPhase() { - return WAITING_FOR_ACK; + return IN_PROGRESS; } @Override @@ -30,7 +31,7 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas return true; } }, - WAITING_FOR_ACK() { + IN_PROGRESS { @Override public void onLoginSuccess(VelocityServerConnection serverCon, ConnectedPlayer player) { serverCon.setConnectionPhase(VelocityForgeBackendConnectionPhase.COMPLETE); @@ -50,7 +51,7 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas } }, - COMPLETE() { + COMPLETE { @Override public boolean consideredComplete() { return true; @@ -68,15 +69,29 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas server.setConnectionPhase(newPhase); + if (player.getPhase() == VelocityForgeClientConnectionPhase.NOT_STARTED || + player.getPhase() == VelocityForgeClientConnectionPhase.IN_PROGRESS) { + //Initial Forge + player.getConnection().write(message); + return; + } + + //Forge -> Forge + //Reset client if not ready to receive new handshake VelocityForgeClientConnectionPhase clientPhase = (VelocityForgeClientConnectionPhase) player.getPhase(); - if (clientPhase == VelocityForgeClientConnectionPhase.RESETTABLE) { - //Initial Forge - //Forge -> Forge + if (clientPhase.getResetType() == VelocityForgeClientConnectionPhase.clientResetType.CRP || + clientPhase.getResetType() == VelocityForgeClientConnectionPhase.clientResetType.SR) { clientPhase.resetConnectionPhase(player); } - if (clientPhase != VelocityForgeClientConnectionPhase.COMPLETE) { + + //STILL WIP + if (!Ambassador.getInstance().config.isDebugMode()) { + return; + } + + if (clientPhase.getResetType() == VelocityForgeClientConnectionPhase.clientResetType.NONE) { if (message instanceof ModListPacket modListPacket) { clientPhase.forgeHandshake = new ForgeHandshake(); } @@ -118,15 +133,15 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas } }, server.ensureConnected().eventLoop()); } else if (message instanceof RegistryPacket registryPacket) { - server.getConnection().write(new ACKPacket(Context.createContext(message.getContext().getResponseID(), true))); + server.getConnection().write(new ACKPacket(Context.fromContext(message.getContext(), true))); handshake.addRegistry(registryPacket); remainingRegistries.countDown(); } else if (message instanceof ConfigDataPacket) { - server.getConnection().write(new ACKPacket(Context.createContext(message.getContext().getResponseID(), true))); + server.getConnection().write(new ACKPacket(Context.fromContext(message.getContext(), true))); } else if (message instanceof GenericForgeLoginWrapperPacket packet && ForgeHandshakeUtils.SilentGearUtils.isSilentGearPacket(packet.getContent())) { server.getConnection().write(new ForgeHandshakeUtils.SilentGearUtils.ACKPacket( - Context.createContext(message.getContext().getResponseID(), true))); + Context.fromContext(message.getContext(), true))); } } //Forge server diff --git a/src/main/java/org/adde0109/ambassador/forge/VelocityForgeClientConnectionPhase.java b/src/main/java/org/adde0109/ambassador/forge/VelocityForgeClientConnectionPhase.java index 8c62006..ae3beb4 100644 --- a/src/main/java/org/adde0109/ambassador/forge/VelocityForgeClientConnectionPhase.java +++ b/src/main/java/org/adde0109/ambassador/forge/VelocityForgeClientConnectionPhase.java @@ -10,7 +10,7 @@ import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.network.Connections; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.StateRegistry; -import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; +import com.velocitypowered.proxy.protocol.packet.LoginPluginMessagePacket; import com.velocitypowered.proxy.protocol.packet.PluginMessage; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; @@ -23,9 +23,7 @@ import org.adde0109.ambassador.velocity.client.FML2CRPMResetCompleteDecoder; import org.adde0109.ambassador.velocity.client.OutboundSuccessHolder; import org.adde0109.ambassador.velocity.client.ClientPacketQueue; -import javax.swing.*; import java.nio.charset.StandardCharsets; -import java.util.concurrent.TimeUnit; public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase { @@ -36,8 +34,10 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase } @Override - public void resetConnectionPhase(ConnectedPlayer player) { - RESETTABLE.resetConnectionPhase(player); + public void complete(ConnectedPlayer player) { + //When no handshake has taken place. + //Test if the client supports CRP. + clientResetType.CRP.doReset(player); } @Override @@ -47,47 +47,6 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase }, IN_PROGRESS { }, - RESETTABLE { - @Override - void onTransitionToNewPhase(ConnectedPlayer player) { - //Plugins may now send packets to client - player.getConnection().getChannel().pipeline().remove(ForgeConstants.PLUGIN_PACKET_QUEUE); - ((VelocityServer) Ambassador.getInstance().server).registerConnection(player); - } - - @Override - public void resetConnectionPhase(ConnectedPlayer player) { - MinecraftConnection connection = player.getConnection(); - - //There is no going back even if the handshake fails. No reason to still be connected. - if (player.getConnectedServer() != null) { - player.getConnectedServer().disconnect(); - player.setConnectedServer(null); - } - //Don't handle anything from the server until the reset has completed. - //player.getConnectionInFlight().getConnection().getChannel().config().setAutoRead(false); - - if (connection.getState() == StateRegistry.PLAY || connection.getState() == StateRegistry.CONFIG) { - connection.write(new PluginMessage("fml:handshake", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generatePluginResetPacket()))); - connection.setState(StateRegistry.LOGIN); - } else { - connection.write(new LoginPluginMessage(98,"fml:loginwrapper", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generateResetPacket()))); - } - - //Prepare to receive reset ACK - connection.getChannel().pipeline().addBefore(Connections.MINECRAFT_DECODER, - ForgeConstants.RESET_LISTENER, new FML2CRPMResetCompleteDecoder()); - - //Transition - player.setPhase(WAITING_RESET); - WAITING_RESET.onTransitionToNewPhase(player); - } - - @Override - public boolean consideredComplete() { - return true; - } - }, WAITING_RESET { @Override void onTransitionToNewPhase(ConnectedPlayer player) { @@ -103,6 +62,7 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase @Override public boolean handle(ConnectedPlayer player, IForgeLoginWrapperPacket msg, VelocityServerConnection server) { if (msg.getContext().getResponseID() == 98) { + //Reset complete player.getConnection().getChannel().pipeline().remove(ForgeConstants.RESET_LISTENER); player.setPhase(NOT_STARTED); @@ -110,7 +70,10 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase if (!(server.getConnection().getType() instanceof ForgeFMLConnectionType)) { // -> vanilla - complete(player, ((Context.ClientContext) msg.getContext()).success()); + complete(player, ((Context.ClientContext) msg.getContext()).success() ? clientResetType.CRP : clientResetType.UNKNOWN); + } + + if (player.getConnectionInFlight() != null) { player.getConnectionInFlight().getConnection().getChannel().config().setAutoRead(true); } @@ -130,19 +93,7 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase @Override public void resetConnectionPhase(ConnectedPlayer player) { - Ambassador.getTemporaryForced().put(player.getUsername(), player.getConnectionInFlight().getServer(), - Ambassador.getInstance().config.getServerSwitchCancellationTime(), TimeUnit.SECONDS); - //Disconnect - Reset - if(player.getModInfo().isPresent() - && player.getModInfo().get().getMods().stream().anyMatch((mod -> mod.getId().equals("serverredirect") - || mod.getId().equals("srvredirect:red"))) - && player.getVirtualHost().isPresent()) { - ByteBuf buf = Unpooled.buffer(); - ProtocolUtils.writeVarInt(buf, 0); - buf.writeBytes((player.getVirtualHost().get().getHostName() + ":" - + player.getVirtualHost().get().getPort()).getBytes(StandardCharsets.UTF_8)); - player.getConnection().write(new PluginMessage("srvredirect:red", buf)); - } + getResetType().doReset(player); } @Override @@ -154,8 +105,11 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase }; + //TODO: Make a new class that's linked to each player with these fields instead of having them in this phase class public ForgeHandshake forgeHandshake = new ForgeHandshake(); + private clientResetType resetType = clientResetType.UNKNOWN; + public boolean handle(ConnectedPlayer player, IForgeLoginWrapperPacket msg, VelocityServerConnection server) { if (msg instanceof ModListReplyPacket replyPacket) { @@ -180,39 +134,43 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase } public void complete(ConnectedPlayer player) { - complete(player, isResettable(player)); + complete(player, getResetType(player)); } - public void complete(ConnectedPlayer player, boolean resettable) { + public void complete(ConnectedPlayer player, clientResetType resetType) { MinecraftConnection connection = player.getConnection(); + //Send Login Success to client ((OutboundSuccessHolder) connection.getChannel().pipeline().get(ForgeConstants.SERVER_SUCCESS_LISTENER)) .sendPacket(); connection.setState(StateRegistry.PLAY); - if (resettable) { - player.setPhase(RESETTABLE); - RESETTABLE.onTransitionToNewPhase(player); - RESETTABLE.forgeHandshake = forgeHandshake; - } else { - player.setPhase(COMPLETE); - COMPLETE.onTransitionToNewPhase(player); - COMPLETE.forgeHandshake = forgeHandshake; - } + //Change phase to COMPLETE + player.setPhase(COMPLETE); + COMPLETE.resetType = resetType; + COMPLETE.onTransitionToNewPhase(player); + COMPLETE.forgeHandshake = forgeHandshake; if (Ambassador.getInstance().config.isDebugMode()) { player.sendMessage(Component.text("Forge handshake complete")); - player.sendMessage(Component.text(resettable ? "Resettable" : "Non-resettable")); + player.sendMessage(Component.text("Reset type: " + resetType.toString())); } } - private boolean isResettable(ConnectedPlayer player) { + private clientResetType getResetType(ConnectedPlayer player) { if (Ambassador.getInstance().config.isDebugMode()) { player.sendMessage(Component.text("Scanning modlist for client reset mods")); } if (player.getModInfo().isPresent()) { - return player.getModInfo().get().getMods().stream().anyMatch((mod -> mod.getId().equals("clientresetpacket"))); + if (player.getModInfo().get().getMods().stream().anyMatch((mod -> mod.getId().equals("clientresetpacket")))) { + return clientResetType.CRP; + } else if (Ambassador.getInstance().config.getServerSwitchCancellationTime() >= 0 && + player.getModInfo().get().getMods().stream().anyMatch((mod -> mod.getId().equals("serverredirect") + || mod.getId().equals("srvredirect:red"))) + && player.getVirtualHost().isPresent()) { + return clientResetType.SR; + } } - return false; + return clientResetType.NONE; } void onTransitionToNewPhase(ConnectedPlayer player) { @@ -228,5 +186,55 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase return false; } + public clientResetType getResetType() { + return resetType; + } + enum clientResetType { + UNKNOWN, + NONE, + CRP { + @Override + void doReset(ConnectedPlayer player) { + MinecraftConnection connection = player.getConnection(); + //There is no going back even if the handshake fails. No reason to still be connected. + if (player.getConnectedServer() != null) { + player.getConnectedServer().disconnect(); + player.setConnectedServer(null); + } + //Don't handle anything from the server until the reset has completed. + if (player.getConnectionInFlight() != null) { + player.getConnectionInFlight().getConnection().getChannel().config().setAutoRead(false); + } + + if (connection.getState() == StateRegistry.PLAY || connection.getState() == StateRegistry.CONFIG) { + connection.write(new PluginMessage("fml:handshake", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generatePluginResetPacket()))); + connection.setState(StateRegistry.LOGIN); + } else { + connection.write(new LoginPluginMessagePacket(98,"fml:loginwrapper", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generateResetPacket()))); + } + + //Prepare to receive reset ACK + connection.getChannel().pipeline().addBefore(Connections.MINECRAFT_DECODER, + ForgeConstants.RESET_LISTENER, new FML2CRPMResetCompleteDecoder()); + + //Transition + player.setPhase(WAITING_RESET); + WAITING_RESET.onTransitionToNewPhase(player); + } + }, + SR { + @Override + void doReset(ConnectedPlayer player) { + ByteBuf buf = Unpooled.buffer(); + ProtocolUtils.writeVarInt(buf, 0); + buf.writeBytes((player.getVirtualHost().get().getHostName() + ":" + + player.getVirtualHost().get().getPort()).getBytes(StandardCharsets.UTF_8)); + player.getConnection().write(new PluginMessage("srvredirect:red", buf)); + } + }; + + void doReset(ConnectedPlayer player) { + } + } } diff --git a/src/main/java/org/adde0109/ambassador/forge/packet/Context.java b/src/main/java/org/adde0109/ambassador/forge/packet/Context.java index 97ceb0b..8f39814 100644 --- a/src/main/java/org/adde0109/ambassador/forge/packet/Context.java +++ b/src/main/java/org/adde0109/ambassador/forge/packet/Context.java @@ -12,10 +12,14 @@ public class Context { return new Context(responseID); } - public static ClientContext createContext(int responseID, boolean clientSuccess) { + public static ClientContext createClientContext(int responseID, boolean clientSuccess) { return new ClientContext(responseID,clientSuccess); } + public static ClientContext fromContext(Context context, boolean clientSuccess) { + return new ClientContext(context.responseID,clientSuccess); + } + public int getResponseID() { return responseID; } diff --git a/src/main/java/org/adde0109/ambassador/forge/pipeline/ForgeLoginWrapperCodec.java b/src/main/java/org/adde0109/ambassador/forge/pipeline/ForgeLoginWrapperCodec.java index f800f2a..761ac0b 100644 --- a/src/main/java/org/adde0109/ambassador/forge/pipeline/ForgeLoginWrapperCodec.java +++ b/src/main/java/org/adde0109/ambassador/forge/pipeline/ForgeLoginWrapperCodec.java @@ -1,7 +1,7 @@ package org.adde0109.ambassador.forge.pipeline; import com.velocitypowered.proxy.protocol.ProtocolUtils; -import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; +import com.velocitypowered.proxy.protocol.packet.LoginPluginMessagePacket; import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder; import io.netty.buffer.ByteBuf; @@ -29,10 +29,10 @@ public class ForgeLoginWrapperCodec extends MessageToMessageCodec vanilla - player.getPhase().resetConnectionPhase(player); - player.getConnectionInFlight().getConnection().getChannel().config().setAutoRead(false); - } - } else { + + original.handle(packet); //Can lead to disconnect. + + //If we are still connected after handling that package. + if (serverConnection.getConnection() != null) { + ConnectedPlayer player = serverConnection.getPlayer(); + ((VelocityForgeClientConnectionPhase) player.getPhase()).complete(player); } + return true; } diff --git a/src/main/java/org/adde0109/ambassador/velocity/backend/ForgePlaySessionHandler.java b/src/main/java/org/adde0109/ambassador/velocity/backend/ForgePlaySessionHandler.java deleted file mode 100644 index 565d4b7..0000000 --- a/src/main/java/org/adde0109/ambassador/velocity/backend/ForgePlaySessionHandler.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.adde0109.ambassador.velocity.backend; - -import com.velocitypowered.proxy.connection.MinecraftSessionHandler; -import com.velocitypowered.proxy.connection.backend.TransitionSessionHandler; -import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; -import com.velocitypowered.proxy.protocol.MinecraftPacket; -import com.velocitypowered.proxy.protocol.packet.JoinGame; -import org.adde0109.ambassador.forge.VelocityForgeClientConnectionPhase; - -public class ForgePlaySessionHandler implements MinecraftSessionHandler { - - private final TransitionSessionHandler original; - private final VelocityServerConnection serverConnection; - - public ForgePlaySessionHandler(TransitionSessionHandler original, VelocityServerConnection serverConnection) { - this.original = original; - this.serverConnection = serverConnection; - } - - @Override - public boolean handle(JoinGame packet) { - if (serverConnection.getPlayer().getPhase() instanceof VelocityForgeClientConnectionPhase clientPhase) { - serverConnection.getPlayer().setPhase(VelocityForgeClientConnectionPhase.RESETTABLE); - } - return MinecraftSessionHandler.super.handle(packet); - } - - @Override - public void disconnected() { - original.disconnected(); - } - - public void handleGeneric(MinecraftPacket packet) { - if (!packet.handle(original)) - original.handleGeneric(packet); - } - - public MinecraftSessionHandler getOriginal() { - return this.original; - } -} diff --git a/src/main/java/org/adde0109/ambassador/velocity/client/FML2CRPMResetCompleteDecoder.java b/src/main/java/org/adde0109/ambassador/velocity/client/FML2CRPMResetCompleteDecoder.java index 959af1a..1fdc4d1 100644 --- a/src/main/java/org/adde0109/ambassador/velocity/client/FML2CRPMResetCompleteDecoder.java +++ b/src/main/java/org/adde0109/ambassador/velocity/client/FML2CRPMResetCompleteDecoder.java @@ -28,7 +28,7 @@ public class FML2CRPMResetCompleteDecoder extends ChannelInboundHandlerAdapter { if (id == 98) { try { ctx.fireChannelRead(GenericForgeLoginWrapperPacket.read( - Unpooled.EMPTY_BUFFER, Context.createContext(id, success))); + Unpooled.EMPTY_BUFFER, Context.createClientContext(id, success))); } finally { buf.release(); }