diff --git a/build.gradle b/build.gradle index f16bb74..a685d0f 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { } group 'org.adde0109' -version '0.4.0' +version '0.5.0' repositories { maven { @@ -21,6 +21,8 @@ dependencies { implementation 'com.electronwill.night-config:toml:3.6.5' implementation 'org.bstats:bstats-velocity:3.0.0' implementation 'org.apache.commons:commons-collections4:4.4' + implementation 'io.netty:netty-buffer:4.1.79.Final' + implementation 'io.netty:netty-transport:4.1.79.Final' } shadowJar { diff --git a/src/main/java/org/adde0109/ambassador/forge/ForgeHandshakeUtils.java b/src/main/java/org/adde0109/ambassador/forge/ForgeHandshakeUtils.java index 93fbe26..1500e75 100644 --- a/src/main/java/org/adde0109/ambassador/forge/ForgeHandshakeUtils.java +++ b/src/main/java/org/adde0109/ambassador/forge/ForgeHandshakeUtils.java @@ -7,6 +7,8 @@ import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerPing; import com.velocitypowered.api.util.ModInfo; import java.util.*; + +import io.netty.buffer.ByteBuf; import org.slf4j.Logger; import java.nio.charset.StandardCharsets; @@ -60,6 +62,12 @@ public class ForgeHandshakeUtils { return stream.toByteArray(); } + public static byte[] generateResetPacket() { + ByteArrayDataOutput dataAndPacketIdStream = ByteStreams.newDataOutput(); + writeVarInt(dataAndPacketIdStream,98); + return dataAndPacketIdStream.toByteArray(); + } + public static class HandshakeReceiver { private int partLength; diff --git a/src/main/java/org/adde0109/ambassador/forge/ForgeServerSwitchHandler.java b/src/main/java/org/adde0109/ambassador/forge/ForgeServerSwitchHandler.java index 6e6c264..417ccb4 100644 --- a/src/main/java/org/adde0109/ambassador/forge/ForgeServerSwitchHandler.java +++ b/src/main/java/org/adde0109/ambassador/forge/ForgeServerSwitchHandler.java @@ -3,17 +3,38 @@ package org.adde0109.ambassador.forge; import com.velocitypowered.api.event.Continuation; import com.velocitypowered.api.event.PostOrder; import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.PluginMessageEvent; +import com.velocitypowered.api.event.player.ServerConnectedEvent; import com.velocitypowered.api.event.player.ServerPreConnectEvent; import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.messages.ChannelIdentifier; +import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; import com.velocitypowered.api.util.GameProfile; +import com.velocitypowered.api.util.UuidUtils; +import com.velocitypowered.proxy.config.PlayerInfoForwarding; +import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.MinecraftConnection; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; +import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.StateRegistry; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; import java.util.*; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; +import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; +import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; +import com.velocitypowered.proxy.protocol.packet.PluginMessage; +import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.EventLoop; +import io.netty.util.concurrent.GenericFutureListener; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.adde0109.ambassador.Ambassador; @@ -40,6 +61,11 @@ public class ForgeServerSwitchHandler { Optional forgeServerConnectionOptional = ambassador.forgeHandshakeHandler.getForgeServerConnection(event.getOriginalServer()); Optional forgeConnection = ambassador.forgeHandshakeHandler.getForgeConnection(event.getPlayer()); if (forgeConnection.isPresent()) { + //TODO: If the client can resync without kick, we don't need to check the server. + if (true) { + continuation.resume(); + return; + } ForgeServerConnection forgeServerConnection = forgeServerConnectionOptional.orElseGet(() -> new ForgeServerConnection(event.getOriginalServer())); forgeServerConnection.getHandshake().whenComplete((msg, ex) -> { if (ex != null) { @@ -62,11 +88,7 @@ public class ForgeServerSwitchHandler { if (ambassador.config.reSyncOptionForge() != AmbassadorConfig.reSyncOption.NEVER) { if (forgeConnection.get().getTransmittedHandshake().isEmpty() || !msg.equals(forgeConnection.get().getTransmittedHandshake().get())) { event.setResult(ServerPreConnectEvent.ServerResult.denied()); - try { - reSync(event.getPlayer(),forgeServerConnection); - } catch (ReflectiveOperationException e) { - continuation.resumeWithException(e); - } + kickReSync(event.getPlayer(), forgeServerConnection); } } } @@ -82,9 +104,94 @@ public class ForgeServerSwitchHandler { continuation.resume(); } } - private void reSync(Player player, ForgeServerConnection forgeServerConnection) throws ReflectiveOperationException { + private void kickReSync(Player player, ForgeServerConnection forgeServerConnection){ ambassador.logger.info("Kicking {} because of re-sync needed", player); player.disconnect(Component.text("Please reconnect")); reSyncMap.put(player.getUsername(),forgeServerConnection); } + + @Subscribe + public void onServerConnectedEvent(ServerConnectedEvent event, Continuation continuation) { + ConnectedPlayer player = ((ConnectedPlayer) event.getPlayer()); + Optional forgeServerConnection = ambassador.forgeHandshakeHandler.getForgeServerConnection(event.getServer()); + Optional forgeConnection = ambassador.forgeHandshakeHandler.getForgeConnection(player); + if (forgeConnection.isPresent() && forgeServerConnection.isPresent() && event.getPreviousServer().isPresent()) { + Future handshakeFuture = forgeServerConnection.get().getHandshake(); + player.getConnection().eventLoop().submit(() -> { + reSync(player,handshakeFuture,continuation); + }); + } else { + continuation.resume(); + } + } + + private void reSync(ConnectedPlayer player, Future handshakeFuture, Continuation continuation) { + MinecraftConnection connection = player.getConnection(); + connection.setSessionHandler(new ReSyncHandler(player,handshakeFuture,continuation)); + connection.write(new PluginMessage("fml:handshake", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generateResetPacket()))); + connection.setState(StateRegistry.LOGIN); + } + private class ReSyncHandler implements MinecraftSessionHandler { + + + private final Player player; + + private final MinecraftConnection connection; + private final Future handshakeFuture; + private final MinecraftSessionHandler originalHandler; + private final Continuation continuation; + private int sent = 0; + + ReSyncHandler(ConnectedPlayer player, Future handshakeFuture, Continuation continuation) { + this.player = player; + this.connection = player.getConnection(); + this.handshakeFuture = handshakeFuture; + this.originalHandler = this.connection.getSessionHandler(); + this.continuation = continuation; + } + + @Override + public boolean handle(LoginPluginResponse packet) { + if (sent == 0) { + ForgeHandshakeUtils.CachedServerHandshake handshake; + try { + handshake = handshakeFuture.get(); + } catch (Exception e) { + return true; + } + sent = sendHandshake(connection, handshake); + } else { + if (sent == 1) { + complete(); + } + sent--; + } + return true; + } + + private int sendHandshake(MinecraftConnection connection, ForgeHandshakeUtils.CachedServerHandshake handshake) { + int transactionId = 1; + connection.delayedWrite(new LoginPluginMessage(transactionId, "fml:loginwrapper", Unpooled.wrappedBuffer(handshake.modListPacket))); + for (byte[] data : handshake.otherPackets) { + transactionId++; + connection.delayedWrite(new LoginPluginMessage(transactionId, "fml:loginwrapper", Unpooled.wrappedBuffer(data))); + } + connection.flush(); + return transactionId; + } + private void complete() { + VelocityConfiguration configuration = (VelocityConfiguration) ambassador.server.getConfiguration(); + UUID playerUniqueId = player.getUniqueId(); + if (configuration.getPlayerInfoForwardingMode() == PlayerInfoForwarding.NONE) { + playerUniqueId = UuidUtils.generateOfflinePlayerUuid(player.getUsername()); + } + ServerLoginSuccess success = new ServerLoginSuccess(); + success.setUsername(player.getUsername()); + success.setUuid(playerUniqueId); + connection.write(success); + connection.setState(StateRegistry.PLAY); + connection.setSessionHandler(originalHandler); + continuation.resume(); + } + } }