diff --git a/ambassador-bungeecord/build.gradle.kts b/ambassador-bungeecord/build.gradle.kts new file mode 100644 index 0000000..154d891 --- /dev/null +++ b/ambassador-bungeecord/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + java + id("com.github.johnrengelman.shadow") version "7.1.2" +} + +group = "org.adde0109" +version = "1.1.7-alpha-bungeecord" \ No newline at end of file diff --git a/ambassador-bungeecord/build/libs/ambassador-bungeecord-1.1.7-alpha.jar b/ambassador-bungeecord/build/libs/ambassador-bungeecord-1.1.7-alpha.jar new file mode 100644 index 0000000..7bd1f42 Binary files /dev/null and b/ambassador-bungeecord/build/libs/ambassador-bungeecord-1.1.7-alpha.jar differ diff --git a/ambassador-bungeecord/build/tmp/jar/MANIFEST.MF b/ambassador-bungeecord/build/tmp/jar/MANIFEST.MF new file mode 100644 index 0000000..59499bc --- /dev/null +++ b/ambassador-bungeecord/build/tmp/jar/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/build.gradle.kts b/ambassador-velocity/build.gradle.kts similarity index 99% rename from build.gradle.kts rename to ambassador-velocity/build.gradle.kts index 09f2b68..d26898a 100644 --- a/build.gradle.kts +++ b/ambassador-velocity/build.gradle.kts @@ -1,6 +1,5 @@ plugins { java - idea id("com.github.johnrengelman.shadow") version "7.1.2" } diff --git a/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/Ambassador$1.class b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/Ambassador$1.class new file mode 100644 index 0000000..9565442 Binary files /dev/null and b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/Ambassador$1.class differ diff --git a/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/Ambassador.class b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/Ambassador.class new file mode 100644 index 0000000..69c4663 Binary files /dev/null and b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/Ambassador.class differ diff --git a/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/forge/FML2CRPMClientConnectionPhase.class b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/forge/FML2CRPMClientConnectionPhase.class new file mode 100644 index 0000000..eb18d45 Binary files /dev/null and b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/forge/FML2CRPMClientConnectionPhase.class differ diff --git a/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/forge/FML2CRPMResetCompleteDecoder.class b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/forge/FML2CRPMResetCompleteDecoder.class new file mode 100644 index 0000000..6d43cd6 Binary files /dev/null and b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/forge/FML2CRPMResetCompleteDecoder.class differ diff --git a/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/forge/FML2ClientConnectionPhase.class b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/forge/FML2ClientConnectionPhase.class new file mode 100644 index 0000000..16502b0 Binary files /dev/null and b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/forge/FML2ClientConnectionPhase.class differ diff --git a/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/forge/ForgeConstants.class b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/forge/ForgeConstants.class new file mode 100644 index 0000000..6e0c3ce Binary files /dev/null and b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/forge/ForgeConstants.class differ diff --git a/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/forge/ForgeFMLConnectionType.class b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/forge/ForgeFMLConnectionType.class new file mode 100644 index 0000000..3d14b3a Binary files /dev/null and b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/forge/ForgeFMLConnectionType.class differ diff --git a/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/forge/ForgeHandshakeUtils.class b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/forge/ForgeHandshakeUtils.class new file mode 100644 index 0000000..abbc07a Binary files /dev/null and b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/forge/ForgeHandshakeUtils.class differ diff --git a/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityBackendChannelInitializer.class b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityBackendChannelInitializer.class new file mode 100644 index 0000000..0eb346c Binary files /dev/null and b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityBackendChannelInitializer.class differ diff --git a/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityEventHandler.class b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityEventHandler.class new file mode 100644 index 0000000..4ad2d61 Binary files /dev/null and b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityEventHandler.class differ diff --git a/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityForgeClientConnectionPhase$ClientPhase.class b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityForgeClientConnectionPhase$ClientPhase.class new file mode 100644 index 0000000..f8681a6 Binary files /dev/null and b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityForgeClientConnectionPhase$ClientPhase.class differ diff --git a/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityForgeClientConnectionPhase.class b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityForgeClientConnectionPhase.class new file mode 100644 index 0000000..c09355a Binary files /dev/null and b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityForgeClientConnectionPhase.class differ diff --git a/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityForgeHandshakeSessionHandler.class b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityForgeHandshakeSessionHandler.class new file mode 100644 index 0000000..08f7b14 Binary files /dev/null and b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityForgeHandshakeSessionHandler.class differ diff --git a/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityHandshakeSessionHandler.class b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityHandshakeSessionHandler.class new file mode 100644 index 0000000..3e8d87a Binary files /dev/null and b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityHandshakeSessionHandler.class differ diff --git a/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityLoginPayloadManager.class b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityLoginPayloadManager.class new file mode 100644 index 0000000..eb60d26 Binary files /dev/null and b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityLoginPayloadManager.class differ diff --git a/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityServerChannelInitializer.class b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityServerChannelInitializer.class new file mode 100644 index 0000000..45c304d Binary files /dev/null and b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/VelocityServerChannelInitializer.class differ diff --git a/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/backend/ForgeHandshakeSessionHandler.class b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/backend/ForgeHandshakeSessionHandler.class new file mode 100644 index 0000000..7a31df2 Binary files /dev/null and b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/backend/ForgeHandshakeSessionHandler.class differ diff --git a/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/backend/VelocityForgeBackendConnectionPhase.class b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/backend/VelocityForgeBackendConnectionPhase.class new file mode 100644 index 0000000..9f76280 Binary files /dev/null and b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/backend/VelocityForgeBackendConnectionPhase.class differ diff --git a/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/backend/VelocityForgeBackendHandshakeHandler.class b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/backend/VelocityForgeBackendHandshakeHandler.class new file mode 100644 index 0000000..e558ae6 Binary files /dev/null and b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/backend/VelocityForgeBackendHandshakeHandler.class differ diff --git a/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/commands/EnumArgumentProperty.class b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/commands/EnumArgumentProperty.class new file mode 100644 index 0000000..d7f7ceb Binary files /dev/null and b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/commands/EnumArgumentProperty.class differ diff --git a/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/commands/EnumArgumentPropertySerializer.class b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/commands/EnumArgumentPropertySerializer.class new file mode 100644 index 0000000..bae1c58 Binary files /dev/null and b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/commands/EnumArgumentPropertySerializer.class differ diff --git a/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/commands/ModIdArgumentProperty.class b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/commands/ModIdArgumentProperty.class new file mode 100644 index 0000000..36f7907 Binary files /dev/null and b/ambassador-velocity/build/classes/java/main/org/adde0109/ambassador/velocity/commands/ModIdArgumentProperty.class differ diff --git a/ambassador-velocity/build/classes/java/main/velocity-plugin.json b/ambassador-velocity/build/classes/java/main/velocity-plugin.json new file mode 100644 index 0000000..1b6d80a --- /dev/null +++ b/ambassador-velocity/build/classes/java/main/velocity-plugin.json @@ -0,0 +1 @@ +{"id":"ambassador","name":"Ambassador","version":"1.1.7-alpha","authors":["adde0109"],"dependencies":[],"main":"org.adde0109.ambassador.Ambassador"} \ No newline at end of file diff --git a/ambassador-velocity/build/libs/Ambassador-Velocity-1.1.7-alpha-all.jar b/ambassador-velocity/build/libs/Ambassador-Velocity-1.1.7-alpha-all.jar new file mode 100644 index 0000000..df95964 Binary files /dev/null and b/ambassador-velocity/build/libs/Ambassador-Velocity-1.1.7-alpha-all.jar differ diff --git a/ambassador-velocity/build/tmp/compileJava/previous-compilation-data.bin b/ambassador-velocity/build/tmp/compileJava/previous-compilation-data.bin new file mode 100644 index 0000000..7a1ec12 Binary files /dev/null and b/ambassador-velocity/build/tmp/compileJava/previous-compilation-data.bin differ diff --git a/ambassador-velocity/build/tmp/shadowJar/MANIFEST.MF b/ambassador-velocity/build/tmp/shadowJar/MANIFEST.MF new file mode 100644 index 0000000..59499bc --- /dev/null +++ b/ambassador-velocity/build/tmp/shadowJar/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/ambassador-velocity/src/main/java/org/adde0109/ambassador/Ambassador.java b/ambassador-velocity/src/main/java/org/adde0109/ambassador/Ambassador.java new file mode 100644 index 0000000..2cd91a9 --- /dev/null +++ b/ambassador-velocity/src/main/java/org/adde0109/ambassador/Ambassador.java @@ -0,0 +1,96 @@ +package org.adde0109.ambassador; + +import com.google.inject.Inject; +import com.velocitypowered.api.event.PostOrder; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.plugin.annotation.DataDirectory; +import com.velocitypowered.api.proxy.ProxyServer; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.network.ConnectionManager; +import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier; +import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentPropertyRegistry; +import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentPropertySerializer; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelInitializer; +import org.adde0109.ambassador.velocity.VelocityBackendChannelInitializer; +import org.adde0109.ambassador.velocity.VelocityServerChannelInitializer; +import org.adde0109.ambassador.velocity.VelocityEventHandler; +import org.adde0109.ambassador.velocity.commands.EnumArgumentProperty; +import org.adde0109.ambassador.velocity.commands.EnumArgumentPropertySerializer; +import org.adde0109.ambassador.velocity.commands.ModIdArgumentProperty; +import org.bstats.velocity.Metrics; +import org.slf4j.Logger; + +import java.nio.file.Path; + +@Plugin(id = "ambassador", name = "Ambassador", version = "1.1.7-alpha", authors = {"adde0109"}) +public class Ambassador { + + public ProxyServer server; + public final Logger logger; + private final Metrics.Factory metricsFactory; + private final Path dataDirectory; + + private static Ambassador instance; + public static Ambassador getInstance() { + return instance; + } + + + @Inject + public Ambassador(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory, Metrics.Factory metricsFactory) { + this.server = server; + this.logger = logger; + this.dataDirectory = dataDirectory; + this.metricsFactory = metricsFactory; + Ambassador.instance = this; + } + + @Subscribe(order = PostOrder.LAST) + public void onProxyInitialization(ProxyInitializeEvent event) throws ReflectiveOperationException { + initMetrics(); + + server.getEventManager().register(this, new VelocityEventHandler(this)); + + inject(); + } + + private void inject() throws ReflectiveOperationException { + Field cmField = VelocityServer.class.getDeclaredField("cm"); + cmField.setAccessible(true); + + ChannelInitializer original = ((ConnectionManager) cmField.get(server)).serverChannelInitializer.get(); + ((ConnectionManager) cmField.get(server)).serverChannelInitializer.set(new VelocityServerChannelInitializer(original,(VelocityServer) server)); + + ChannelInitializer originalBackend = ((ConnectionManager) cmField.get(server)).backendChannelInitializer.get(); + ((ConnectionManager) cmField.get(server)).backendChannelInitializer.set(new VelocityBackendChannelInitializer(originalBackend,(VelocityServer) server)); + + Method argumentRegistry = ArgumentPropertyRegistry.class.getDeclaredMethod("register", ArgumentIdentifier.class, Class.class, ArgumentPropertySerializer.class); + argumentRegistry.setAccessible(true); + argumentRegistry.invoke(null,ArgumentIdentifier.id("forge:enum"), EnumArgumentProperty.class, EnumArgumentPropertySerializer.ENUM); + argumentRegistry.invoke(null,ArgumentIdentifier.id("forge:modid"), ModIdArgumentProperty.class, + new ArgumentPropertySerializer<>() { + @Override + public ModIdArgumentProperty deserialize(ByteBuf buf, ProtocolVersion protocolVersion) { + return new ModIdArgumentProperty(); + } + + @Override + public void serialize(Object object, ByteBuf buf, ProtocolVersion protocolVersion) { + + } + }); + + } + + private void initMetrics() { + Metrics metrics = metricsFactory.make(this, 15655); + } +} diff --git a/ambassador-velocity/src/main/java/org/adde0109/ambassador/forge/FML2CRPMClientConnectionPhase.java b/ambassador-velocity/src/main/java/org/adde0109/ambassador/forge/FML2CRPMClientConnectionPhase.java new file mode 100644 index 0000000..c1a1ddf --- /dev/null +++ b/ambassador-velocity/src/main/java/org/adde0109/ambassador/forge/FML2CRPMClientConnectionPhase.java @@ -0,0 +1,95 @@ +package org.adde0109.ambassador.forge; + +import com.velocitypowered.api.event.player.KickedFromServerEvent; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import com.velocitypowered.api.util.UuidUtils; +import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.config.PlayerInfoForwarding; +import com.velocitypowered.proxy.config.VelocityConfiguration; +import com.velocitypowered.proxy.connection.MinecraftConnection; +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.StateRegistry; +import com.velocitypowered.proxy.protocol.packet.PluginMessage; +import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess; +import io.netty.buffer.Unpooled; +import org.adde0109.ambassador.Ambassador; +import org.adde0109.ambassador.velocity.VelocityForgeClientConnectionPhase; +import org.adde0109.ambassador.velocity.VelocityForgeHandshakeSessionHandler; +import org.adde0109.ambassador.velocity.VelocityLoginPayloadManager; + +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class FML2CRPMClientConnectionPhase extends VelocityForgeClientConnectionPhase { + + //TODO: Use modData inside ConnectedPlayer instead + public byte[] modListData; + private RegisteredServer backupServer; + + public FML2CRPMClientConnectionPhase(VelocityForgeClientConnectionPhase.ClientPhase clientPhase, VelocityLoginPayloadManager payloadManager) { + super(clientPhase,payloadManager); + } + + public CompletableFuture reset(RegisteredServer server, ConnectedPlayer player) { + CompletableFuture future = new CompletableFuture<>(); + if (player.getConnectedServer() != null) { + backupServer = player.getConnectedServer().getServer(); + player.getConnectedServer().disconnect(); + player.setConnectedServer(null); + } + + MinecraftConnection connection = player.getConnection(); + connection.setSessionHandler(new VelocityForgeHandshakeSessionHandler(connection.getSessionHandler(),this)); + + ((VelocityServer) Ambassador.getInstance().server).unregisterConnection(player); + this.clientPhase = null; + + ScheduledFuture scheduledFuture = connection.eventLoop().schedule(()-> { + connection.getChannel().pipeline().remove(ForgeConstants.RESET_LISTENER); + future.complete(false); + },5, TimeUnit.SECONDS); + connection.getChannel().pipeline().addBefore(Connections.MINECRAFT_DECODER, ForgeConstants.RESET_LISTENER,new FML2CRPMResetCompleteDecoder()); + getPayloadManager().listenFor(98).thenAccept(ignore -> { + if (scheduledFuture.cancel(false)) { + connection.getChannel().pipeline().remove(ForgeConstants.RESET_LISTENER); + connection.setState(StateRegistry.LOGIN); + this.clientPhase = ClientPhase.HANDSHAKE; + future.complete(true); + } + }); + connection.write(new PluginMessage("fml:handshake",Unpooled.wrappedBuffer(ForgeHandshakeUtils.generatePluginResetPacket()))); + return future; + } + public void complete(VelocityServer server, ConnectedPlayer player, MinecraftConnection connection) { + VelocityConfiguration configuration = (VelocityConfiguration) 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); + + this.clientPhase = this.clientPhase == ClientPhase.MODLIST ? ClientPhase.MODDED : ClientPhase.VANILLA; + + connection.setState(StateRegistry.PLAY); + connection.setSessionHandler(((VelocityForgeHandshakeSessionHandler) connection.getSessionHandler()).getOriginal()); + ((VelocityServer) Ambassador.getInstance().server).registerConnection(player); + + backupServer = null; + } + + public void handleKick(KickedFromServerEvent event) { + if (backupServer != null && !(event.getResult() instanceof KickedFromServerEvent.RedirectPlayer)) { + net.kyori.adventure.text.Component reason = event.getServerKickReason().orElse(null); + event.setResult(KickedFromServerEvent.RedirectPlayer.create(backupServer,reason)); + backupServer = null; + } + } + +} diff --git a/ambassador-velocity/src/main/java/org/adde0109/ambassador/forge/FML2CRPMResetCompleteDecoder.java b/ambassador-velocity/src/main/java/org/adde0109/ambassador/forge/FML2CRPMResetCompleteDecoder.java new file mode 100644 index 0000000..6cee225 --- /dev/null +++ b/ambassador-velocity/src/main/java/org/adde0109/ambassador/forge/FML2CRPMResetCompleteDecoder.java @@ -0,0 +1,41 @@ +package org.adde0109.ambassador.forge; + +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +public class FML2CRPMResetCompleteDecoder extends ChannelInboundHandlerAdapter { + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + if (msg instanceof ByteBuf buf) { + if (!ctx.channel().isActive() || !buf.isReadable()) { + buf.release(); + return; + } + + int originalReaderIndex = buf.readerIndex(); + int packetId = ProtocolUtils.readVarInt(buf); + if (packetId == 0x02 && buf.readableBytes() > 1) { + try { + int id = ProtocolUtils.readVarInt(buf); + boolean success = buf.readBoolean(); + if (id == 98) { + try { + MinecraftPacket packet = new LoginPluginResponse(id,success,buf.readRetainedSlice(buf.readableBytes())); + ctx.fireChannelRead(packet); + } finally { + buf.release(); + } + return; + } + } catch (Exception ignored) {} + } + buf.readerIndex(originalReaderIndex); + } + ctx.fireChannelRead(msg); + } +} diff --git a/ambassador-velocity/src/main/java/org/adde0109/ambassador/forge/FML2ClientConnectionPhase.java b/ambassador-velocity/src/main/java/org/adde0109/ambassador/forge/FML2ClientConnectionPhase.java new file mode 100644 index 0000000..ae297a7 --- /dev/null +++ b/ambassador-velocity/src/main/java/org/adde0109/ambassador/forge/FML2ClientConnectionPhase.java @@ -0,0 +1,124 @@ +package org.adde0109.ambassador.forge; + +import com.velocitypowered.api.event.Continuation; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.connection.MinecraftConnection; +import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; +import com.velocitypowered.proxy.connection.client.ConnectedPlayer; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; +import io.netty.buffer.ByteBuf; +import io.netty.util.ReferenceCountUtil; +import net.kyori.adventure.text.Component; +import org.adde0109.ambassador.velocity.VelocityForgeClientConnectionPhase; +import org.apache.commons.collections4.map.PassiveExpiringMap; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +public class FML2ClientConnectionPhase extends VelocityForgeClientConnectionPhase { + + private static String OUTBOUND_CATCHER_NAME = "ambassador-catcher"; + + private static final PassiveExpiringMap TEMPORARY_FORCED = new PassiveExpiringMap<>(120, TimeUnit.SECONDS); + + private Throwable throwable; + private RegisteredServer triedServer; + private Continuation continuation; + private CompletableFuture onJoinGame; + + private static final Method CONNECT_TO_INITIAL_SERVER; + + static { + Class clazz; + try { + clazz = Class.forName("com.velocitypowered.proxy.connection.client.LoginSessionHandler"); + } catch (ClassNotFoundException ignored){ + try { + clazz = Class.forName("com.velocitypowered.proxy.connection.client.AuthSessionHandler"); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + try { + CONNECT_TO_INITIAL_SERVER = clazz.getDeclaredMethod("connectToInitialServer", ConnectedPlayer.class); + CONNECT_TO_INITIAL_SERVER.setAccessible(true); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + @Override + public void handleLogin(ConnectedPlayer player, VelocityServer server, Continuation continuation) { + this.continuation = continuation; + final MinecraftConnection connection = player.getConnection(); + + forced = TEMPORARY_FORCED.remove(player.getUsername()); + if (forced != null) { + player.createConnectionRequest(forced).fireAndForget(); + } else { + try { + CONNECT_TO_INITIAL_SERVER.invoke(player.getConnection().getSessionHandler(),player); + } catch (ReflectiveOperationException e) { + continuation.resumeWithException(e); + } + } + } + + @Override + public CompletableFuture reset(RegisteredServer server, ConnectedPlayer player) { + FML2CRPMClientConnectionPhase newPhase = new FML2CRPMClientConnectionPhase(clientPhase,getPayloadManager()); + player.setPhase(newPhase); + CompletableFuture future = newPhase.reset(server,player); + future.thenAccept(success -> { + if (!success) { + TEMPORARY_FORCED.put(player.getUsername(),server); + player.disconnect(Component.text("Please reconnect")); + } + }); + return future; + } + + @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()))); + clientPhase = clientPhase == ClientPhase.MODLIST ? ClientPhase.MODDED : ClientPhase.VANILLA; + internalServerConnection = player.getConnectionInFlight(); + player.resetInFlightConnection(); + this.onJoinGame = new CompletableFuture<>(); + continuation.resume(); + } + + public void handleJoinGame() { + this.onJoinGame.complete(null); + } + + public CompletableFuture awaitJoinGame() { + return this.onJoinGame; + } + + @Override + public void handleForward(VelocityServerConnection serverConnection, LoginPluginMessage payload) { + final ByteBuf buf = payload.content().duplicate(); + ProtocolUtils.readString(buf); //Channel + ProtocolUtils.readVarInt(buf); //Length + if (ProtocolUtils.readVarInt(buf) == 1) { + getPayloadManager().listenFor(payload.getId()).thenAccept(rawResponse -> { + final ByteBuf response = rawResponse.duplicate(); + ProtocolUtils.readString(response); //Channel + ProtocolUtils.readVarInt(response); //Length + if (ProtocolUtils.readVarInt(response) == 2) { + String[] mods = ProtocolUtils.readStringArray(response); + if (Arrays.stream(mods).anyMatch(s -> s.equals("clientresetpacket"))) { + serverConnection.getPlayer().setPhase(new FML2CRPMClientConnectionPhase(clientPhase,getPayloadManager())); + } + } + }); + } + } +} diff --git a/ambassador-velocity/src/main/java/org/adde0109/ambassador/forge/ForgeConstants.java b/ambassador-velocity/src/main/java/org/adde0109/ambassador/forge/ForgeConstants.java new file mode 100644 index 0000000..e2300e2 --- /dev/null +++ b/ambassador-velocity/src/main/java/org/adde0109/ambassador/forge/ForgeConstants.java @@ -0,0 +1,14 @@ +package org.adde0109.ambassador.forge; + +import com.velocitypowered.proxy.connection.ConnectionType; + +public class ForgeConstants { + public static final String HANDLER = "Modern Forge handler"; + public static final String OUTBOUND_CATCHER_NAME = "ambassador-catcher"; + public static final String RESET_LISTENER = "ambassador-reset-listener"; + + public static final String FML2Marker = "\0FML2\0"; + public static final String FML3Marker = "\0FML3\0"; + public static final ConnectionType ForgeFML2 = new ForgeFMLConnectionType(2); + public static final ConnectionType ForgeFML3 = new ForgeFMLConnectionType(3); +} diff --git a/ambassador-velocity/src/main/java/org/adde0109/ambassador/forge/ForgeFMLConnectionType.java b/ambassador-velocity/src/main/java/org/adde0109/ambassador/forge/ForgeFMLConnectionType.java new file mode 100644 index 0000000..8d6ffe1 --- /dev/null +++ b/ambassador-velocity/src/main/java/org/adde0109/ambassador/forge/ForgeFMLConnectionType.java @@ -0,0 +1,38 @@ +package org.adde0109.ambassador.forge; + +import com.velocitypowered.api.util.GameProfile; +import com.velocitypowered.proxy.config.PlayerInfoForwarding; +import com.velocitypowered.proxy.connection.ConnectionType; +import com.velocitypowered.proxy.connection.backend.BackendConnectionPhase; +import com.velocitypowered.proxy.connection.client.ClientConnectionPhase; +import org.adde0109.ambassador.velocity.backend.VelocityForgeBackendConnectionPhase; + +import java.util.Collections; + +public class ForgeFMLConnectionType implements ConnectionType { + + final int netVersion; + + public ForgeFMLConnectionType(int netVersion) { + this.netVersion = netVersion; + } + + @Override + public ClientConnectionPhase getInitialClientPhase() { + return new FML2ClientConnectionPhase(); + } + + @Override + public BackendConnectionPhase getInitialBackendPhase() { + return new VelocityForgeBackendConnectionPhase(); + } + + @Override + public GameProfile addGameProfileTokensIfRequired(GameProfile original, PlayerInfoForwarding forwardingType) { + if (forwardingType == PlayerInfoForwarding.LEGACY) { + return original.addProperties(Collections.singleton(new GameProfile.Property("extraData", "\1FML" + netVersion + "\1", ""))); + } else { + return original; + } + } +} diff --git a/ambassador-velocity/src/main/java/org/adde0109/ambassador/forge/ForgeHandshakeUtils.java b/ambassador-velocity/src/main/java/org/adde0109/ambassador/forge/ForgeHandshakeUtils.java new file mode 100644 index 0000000..a0395ca --- /dev/null +++ b/ambassador-velocity/src/main/java/org/adde0109/ambassador/forge/ForgeHandshakeUtils.java @@ -0,0 +1,103 @@ +package org.adde0109.ambassador.forge; + +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; + +import java.nio.charset.StandardCharsets; + +public class ForgeHandshakeUtils { + + public static int readVarInt(ByteArrayDataInput stream) { + int i = 0; + int j = 0; + + byte b0; + do { + b0 = stream.readByte(); + i |= (b0 & 127) << j++ * 7; + if (j > 5) { + throw new RuntimeException("VarInt too big"); + } + } while((b0 & 128) == 128); + + return i; + } + + public static void writeVarInt(ByteArrayDataOutput stream,int i) { + while((i & -128) != 0) { + stream.writeByte(i & 127 | 128); + i >>>= 7; + } + + stream.writeByte(i); + } + + public static void writeUtf(ByteArrayDataOutput stream,String p_211400_1_) { + byte[] abyte = p_211400_1_.getBytes(StandardCharsets.UTF_8); + writeVarInt(stream,abyte.length); + stream.write(abyte); + } + + public static byte[] generateTestPacket() { + ByteArrayDataOutput dataAndPacketIdStream = ByteStreams.newDataOutput(); + writeVarInt(dataAndPacketIdStream,4); + writeUtf(dataAndPacketIdStream,"ambassadortestpacket"); + writeVarInt(dataAndPacketIdStream,0); + + ByteArrayDataOutput stream = ByteStreams.newDataOutput(); + byte[] dataAndPacketId = dataAndPacketIdStream.toByteArray(); + writeUtf(stream,"fml:handshake"); + writeVarInt(stream,dataAndPacketId.length); + stream.write(dataAndPacketId); + + return stream.toByteArray(); + } + + public static byte[] generateResetPacket() { + ByteArrayDataOutput dataAndPacketIdStream = ByteStreams.newDataOutput(); + writeVarInt(dataAndPacketIdStream,98); + + ByteArrayDataOutput stream = ByteStreams.newDataOutput(); + byte[] dataAndPacketId = dataAndPacketIdStream.toByteArray(); + writeUtf(stream,"fml:handshake"); + writeVarInt(stream,dataAndPacketId.length); + stream.write(dataAndPacketId); + return stream.toByteArray(); + } + public static byte[] generatePluginResetPacket() { + ByteArrayDataOutput dataAndPacketIdStream = ByteStreams.newDataOutput(); + writeVarInt(dataAndPacketIdStream,98); + return dataAndPacketIdStream.toByteArray(); + } + + public static final byte[] emptyModlist = generateEmptyModlist(); + private static byte[] generateEmptyModlist() { + ByteArrayDataOutput dataAndPacketIdStream = ByteStreams.newDataOutput(); + writeVarInt(dataAndPacketIdStream,1); + writeVarInt(dataAndPacketIdStream,0); + writeVarInt(dataAndPacketIdStream,0); + writeVarInt(dataAndPacketIdStream,0); + + ByteArrayDataOutput stream = ByteStreams.newDataOutput(); + byte[] dataAndPacketId = dataAndPacketIdStream.toByteArray(); + writeUtf(stream,"fml:handshake"); + writeVarInt(stream,dataAndPacketId.length); + stream.write(dataAndPacketId); + return stream.toByteArray(); + } + + + public static final byte[] ACKPacket = generateACKPacket(); + private static byte[] generateACKPacket() { + ByteArrayDataOutput dataAndPacketIdStream = ByteStreams.newDataOutput(); + writeVarInt(dataAndPacketIdStream,99); + + ByteArrayDataOutput stream = ByteStreams.newDataOutput(); + byte[] dataAndPacketId = dataAndPacketIdStream.toByteArray(); + writeUtf(stream,"fml:handshake"); + writeVarInt(stream,dataAndPacketId.length); + stream.write(dataAndPacketId); + return stream.toByteArray(); + } +} diff --git a/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/VelocityBackendChannelInitializer.java b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/VelocityBackendChannelInitializer.java new file mode 100644 index 0000000..711acda --- /dev/null +++ b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/VelocityBackendChannelInitializer.java @@ -0,0 +1,44 @@ +package org.adde0109.ambassador.velocity; + +import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.network.BackendChannelInitializer; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import org.adde0109.ambassador.forge.ForgeConstants; +import org.adde0109.ambassador.velocity.backend.VelocityForgeBackendHandshakeHandler; + + +import java.lang.reflect.Method; + +public class VelocityBackendChannelInitializer extends BackendChannelInitializer { + + private static final Method INIT_CHANNEL; + + private final ChannelInitializer delegate; + private final VelocityServer server; + + static { + try { + INIT_CHANNEL = ChannelInitializer.class.getDeclaredMethod("initChannel", Channel.class); + INIT_CHANNEL.setAccessible(true); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + public VelocityBackendChannelInitializer(ChannelInitializer delegate, VelocityServer server) { + super(server); + this.delegate = delegate; + this.server = server; + } + + @Override + protected void initChannel(Channel ch) { + try { + INIT_CHANNEL.invoke(delegate,ch); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + ch.pipeline().addLast(ForgeConstants.HANDLER, new VelocityForgeBackendHandshakeHandler(server)); + } +} diff --git a/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/VelocityEventHandler.java b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/VelocityEventHandler.java new file mode 100644 index 0000000..fd9cfc4 --- /dev/null +++ b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/VelocityEventHandler.java @@ -0,0 +1,111 @@ +package org.adde0109.ambassador.velocity; + +import com.velocitypowered.api.event.Continuation; +import com.velocitypowered.api.event.PostOrder; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.PostLoginEvent; +import com.velocitypowered.api.event.permission.PermissionsSetupEvent; +import com.velocitypowered.api.event.player.*; +import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; +import com.velocitypowered.proxy.connection.client.ConnectedPlayer; +import com.velocitypowered.proxy.protocol.packet.ClientSettings; +import org.adde0109.ambassador.Ambassador; +import org.adde0109.ambassador.forge.FML2CRPMClientConnectionPhase; +import org.adde0109.ambassador.forge.FML2ClientConnectionPhase; +import org.adde0109.ambassador.forge.ForgeConstants; +import org.adde0109.ambassador.forge.ForgeFMLConnectionType; +import org.adde0109.ambassador.velocity.backend.VelocityForgeBackendConnectionPhase; + +public class VelocityEventHandler { + + private final Ambassador ambassador; + + public VelocityEventHandler(Ambassador ambassador) { + this.ambassador = ambassador; + } + + @Subscribe + public void onPermissionsSetupEvent(PermissionsSetupEvent event, Continuation continuation) { + if(!(event.getSubject() instanceof ConnectedPlayer player)) { + continuation.resume(); + return; + } + if (!(player.getPhase() instanceof VelocityForgeClientConnectionPhase phase)) { + continuation.resume(); + return; + } + player.getConnection().eventLoop().submit(() -> phase.fireLoginEvent(player, (VelocityServer) ambassador.server,continuation)); + } + + @Subscribe(order = PostOrder.LAST) + public void onKickedFromServerEvent(KickedFromServerEvent event, Continuation continuation) { + if (((ConnectedPlayer) event.getPlayer()).getPhase() instanceof FML2CRPMClientConnectionPhase phase) { + phase.handleKick(event); + } + continuation.resume(); + } + + @Subscribe(order = PostOrder.LAST) + public void onServerPreConnectEvent(ServerPreConnectEvent event, Continuation continuation) { + ConnectedPlayer player = (ConnectedPlayer) event.getPlayer(); + if (!(player.getPhase() instanceof VelocityForgeClientConnectionPhase phase)) { + continuation.resume(); + return; + } + if (phase.internalServerConnection != null) { + event.setResult(ServerPreConnectEvent.ServerResult.denied()); + player.setConnectedServer((VelocityServerConnection) phase.internalServerConnection); + phase.internalServerConnection = null; + } else if (phase.clientPhase == VelocityForgeClientConnectionPhase.ClientPhase.MODDED) { + player.getConnection().eventLoop().submit(() -> phase.reset(event.getOriginalServer(), (ConnectedPlayer) event.getPlayer()) + .thenAccept((ignored) -> continuation.resume())); + } else { + continuation.resume(); + } + } + + @Subscribe(order = PostOrder.LAST) + public void onPlayerChooseInitialServerEvent(PlayerChooseInitialServerEvent event, Continuation continuation) { + ConnectedPlayer player = (ConnectedPlayer) event.getPlayer(); + if (!(player.getPhase() instanceof VelocityForgeClientConnectionPhase phase)) { + continuation.resume(); + return; + } + if (event.getInitialServer().isEmpty()) + event.setInitialServer(phase.internalServerConnection.getServer()); + continuation.resume(); + } + + @Subscribe(order = PostOrder.LAST) + public void onServerPostConnectEvent(ServerPostConnectEvent event, Continuation continuation) { + ConnectedPlayer player = (ConnectedPlayer) event.getPlayer(); + if (!(player.getPhase() instanceof VelocityForgeClientConnectionPhase phase)) { + continuation.resume(); + return; + } + if (phase instanceof FML2ClientConnectionPhase specialPhase) { + specialPhase.handleJoinGame(); + } + //if (((ConnectedPlayer) event.getPlayer()).getConnectedServer() != null && ((ConnectedPlayer) event.getPlayer()).getConnectedServer().getConnection() != null) { + // ((ConnectedPlayer) event.getPlayer()).getConnectedServer().getConnection().write(new ClientSettings("en_GB", (byte) 10, 0, true, (short) 0xFF,1,false,true)); + //} + continuation.resume(); + } + + @Subscribe + public void onLoginEvent(PostLoginEvent event, Continuation continuation) { + ConnectedPlayer player = (ConnectedPlayer) event.getPlayer(); + if (!(player.getPhase() instanceof VelocityForgeClientConnectionPhase phase)) { + continuation.resume(); + return; + } + + if (phase instanceof FML2ClientConnectionPhase specialPhase) { + specialPhase.awaitJoinGame().thenAcceptAsync((ignored) -> { + player.setConnectedServer(null); + continuation.resume(); + },player.getConnection().eventLoop()); + } + } +} diff --git a/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/VelocityForgeClientConnectionPhase.java b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/VelocityForgeClientConnectionPhase.java new file mode 100644 index 0000000..a7046cd --- /dev/null +++ b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/VelocityForgeClientConnectionPhase.java @@ -0,0 +1,78 @@ +package org.adde0109.ambassador.velocity; + +import com.velocitypowered.api.event.Continuation; +import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.server.RegisteredServer; +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 java.util.concurrent.CompletableFuture; + +public abstract class VelocityForgeClientConnectionPhase implements ClientConnectionPhase { + //TODO:Make class when PCF is done + + + VelocityLoginPayloadManager payloadManager; + public VelocityForgeClientConnectionPhase.ClientPhase clientPhase = ClientPhase.HANDSHAKE; + + public ServerConnection internalServerConnection; + public RegisteredServer forced; + + protected VelocityForgeClientConnectionPhase(ClientPhase clientPhase, VelocityLoginPayloadManager payloadManager) { + this.clientPhase = clientPhase; + this.payloadManager = payloadManager; + } + protected VelocityForgeClientConnectionPhase() { + } + + public void handleLogin(ConnectedPlayer player, VelocityServer server, Continuation continuation) { + } + + public CompletableFuture reset(RegisteredServer server, ConnectedPlayer player) { + return CompletableFuture.completedFuture(false); + } + + 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); + + VelocityForgeHandshakeSessionHandler sessionHandler = new VelocityForgeHandshakeSessionHandler(player.getConnection().getSessionHandler(), this); + player.getConnection().setSessionHandler(sessionHandler); + } + + public void handleForward(VelocityServerConnection serverConnection, LoginPluginMessage payload) { + } + + final public void forwardPayload(VelocityServerConnection serverConnection, LoginPluginMessage payload) { + handleForward(serverConnection,payload); + if (payloadManager == null) { + payload.release(); + 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())); + }); + clientPhase = ClientPhase.MODLIST; + } + + public final VelocityLoginPayloadManager getPayloadManager() { + return payloadManager; + } + + public enum ClientPhase { + VANILLA, + HANDSHAKE, + MODLIST, + MODDED + } +} diff --git a/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/VelocityForgeHandshakeSessionHandler.java b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/VelocityForgeHandshakeSessionHandler.java new file mode 100644 index 0000000..2c65f0c --- /dev/null +++ b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/VelocityForgeHandshakeSessionHandler.java @@ -0,0 +1,38 @@ +package org.adde0109.ambassador.velocity; + +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.connection.client.ConnectedPlayer; +import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; +import io.netty.buffer.ByteBuf; + +public class VelocityForgeHandshakeSessionHandler implements MinecraftSessionHandler { + private final MinecraftSessionHandler original; + private final VelocityForgeClientConnectionPhase phase; + + public VelocityForgeHandshakeSessionHandler(MinecraftSessionHandler original, VelocityForgeClientConnectionPhase phase) { + this.original = original; + this.phase = phase; + } + + @Override + public boolean handle(LoginPluginResponse packet) { + if (phase.getPayloadManager().handlePayload(packet)) { + return true; + } else { + return original.handle(packet); + } + } + @Override + public void handleUnknown(ByteBuf buf) { + original.handleUnknown(buf); + } + + @Override + public void disconnected() { + original.disconnected(); + } + + public MinecraftSessionHandler getOriginal() { + return this.original; + } +} \ No newline at end of file diff --git a/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/VelocityHandshakeSessionHandler.java b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/VelocityHandshakeSessionHandler.java new file mode 100644 index 0000000..1bc208a --- /dev/null +++ b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/VelocityHandshakeSessionHandler.java @@ -0,0 +1,47 @@ +package org.adde0109.ambassador.velocity; + +import com.velocitypowered.proxy.connection.ConnectionTypes; +import com.velocitypowered.proxy.connection.MinecraftConnection; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.StateRegistry; +import com.velocitypowered.proxy.protocol.packet.Handshake; +import io.netty.buffer.ByteBuf; +import org.adde0109.ambassador.forge.ForgeConstants; + +public class VelocityHandshakeSessionHandler implements MinecraftSessionHandler { + private final HandshakeSessionHandler original; + private final MinecraftConnection connection; + + public VelocityHandshakeSessionHandler(HandshakeSessionHandler original, MinecraftConnection connection) { + this.original = original; + this.connection = connection; + } + + @Override + public boolean handle(Handshake handshake) { + handshake.handle(original); + if (connection.getType() == ConnectionTypes.VANILLA) { + final String[] markerSplit = handshake.getServerAddress().split("\0"); + if (connection.getState() == StateRegistry.LOGIN && markerSplit.length > 1) { + switch (markerSplit[1]) { + case "FML2" -> connection.setType(ForgeConstants.ForgeFML2); + case "FML3" -> connection.setType(ForgeConstants.ForgeFML3); + } + } + } + return true; + } + + + @Override + public void handleGeneric(MinecraftPacket packet) { + original.handleGeneric(packet); + } + + @Override + public void handleUnknown(ByteBuf buf) { + original.handleUnknown(buf); + } +} diff --git a/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/VelocityLoginPayloadManager.java b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/VelocityLoginPayloadManager.java new file mode 100644 index 0000000..2fe0461 --- /dev/null +++ b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/VelocityLoginPayloadManager.java @@ -0,0 +1,62 @@ +package org.adde0109.ambassador.velocity; + +import com.velocitypowered.proxy.connection.MinecraftConnection; +import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; +import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; +import io.netty.buffer.ByteBuf; + +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class VelocityLoginPayloadManager { + private final HashMap> listenerList = new HashMap<>(); + private int counter = 0; + private final MinecraftConnection connection; + + public VelocityLoginPayloadManager(MinecraftConnection connection) { + this.connection = connection; + } + + public CompletableFuture sendPayload(String channel, ByteBuf data) { + connection.write(new LoginPluginMessage(counter,channel,data)); + CompletableFuture future = listenFor(counter); + counter++; + return future; + } + + public CompletableFuture sendPayloads(String channel, List dataList) { + final CompletableFuture callback = new CompletableFuture<>(); + for (ByteBuf data : dataList) { + connection.delayedWrite(new LoginPluginMessage(counter, channel, data)); + listenerList.put(counter,callback); + counter++; + } + connection.flush(); + return callback; + } + + public CompletableFuture listenFor(int id) { + CompletableFuture value = listenerList.get(id); + if (value == null) { + CompletableFuture callback = new CompletableFuture<>(); + listenerList.put(id,callback); + return callback; + } else { + return value; + } + } + + boolean handlePayload(LoginPluginResponse response) { + final CompletableFuture callback = listenerList.get(response.getId()); + if (callback != null) { + listenerList.remove(response.getId()); + if (!listenerList.containsValue(callback)) { + callback.complete(response.content()); + } + return true; + } else { + return false; + } + } + } diff --git a/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/VelocityServerChannelInitializer.java b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/VelocityServerChannelInitializer.java new file mode 100644 index 0000000..825802a --- /dev/null +++ b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/VelocityServerChannelInitializer.java @@ -0,0 +1,46 @@ +package org.adde0109.ambassador.velocity; + +import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.connection.MinecraftConnection; +import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler; +import com.velocitypowered.proxy.network.Connections; +import com.velocitypowered.proxy.network.ServerChannelInitializer; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelInitializer; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Method; + +public class VelocityServerChannelInitializer extends ServerChannelInitializer { + private static final Method INIT_CHANNEL; + + private final ChannelInitializer delegate; + + static { + try { + INIT_CHANNEL = ChannelInitializer.class.getDeclaredMethod("initChannel", Channel.class); + INIT_CHANNEL.setAccessible(true); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + public VelocityServerChannelInitializer(ChannelInitializer delegate,VelocityServer server) { + super(server); + this.delegate = delegate; + } + + @Override + protected void initChannel(@NotNull Channel ch){ + try { + INIT_CHANNEL.invoke(delegate,ch); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + ChannelHandler handler = ch.pipeline().get(Connections.HANDLER); + if (!(handler instanceof final MinecraftConnection connection)) throw new RuntimeException("plugin conflict"); + HandshakeSessionHandler originalSessionHandler = (HandshakeSessionHandler) connection.getSessionHandler(); + connection.setSessionHandler(new VelocityHandshakeSessionHandler(originalSessionHandler, connection)); + } +} diff --git a/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/backend/ForgeHandshakeSessionHandler.java b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/backend/ForgeHandshakeSessionHandler.java new file mode 100644 index 0000000..5692f26 --- /dev/null +++ b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/backend/ForgeHandshakeSessionHandler.java @@ -0,0 +1,71 @@ +package org.adde0109.ambassador.velocity.backend; + +import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.connection.backend.LoginSessionHandler; +import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.packet.Disconnect; +import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; +import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess; +import io.netty.buffer.ByteBuf; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import org.adde0109.ambassador.forge.ForgeConstants; +import org.adde0109.ambassador.forge.ForgeFMLConnectionType; +import org.adde0109.ambassador.velocity.VelocityForgeClientConnectionPhase; + +public class ForgeHandshakeSessionHandler implements MinecraftSessionHandler { + + private final LoginSessionHandler original; + private final VelocityServerConnection serverConnection; + private final VelocityServer server; + + public ForgeHandshakeSessionHandler(LoginSessionHandler original, VelocityServerConnection serverConnection, VelocityServer server) { + this.original = original; + this.serverConnection = serverConnection; + this.server = server; + } + + @Override + public boolean handle(LoginPluginMessage packet) { + if (packet.getChannel().equals("fml:loginwrapper")) { + if (!(serverConnection.getConnection().getType() instanceof ForgeFMLConnectionType)) { + if (!(serverConnection.getPlayer().getConnection().getType() instanceof ForgeFMLConnectionType clientType)) { + final String reason = "This server has mods that require Forge to be installed on the client. Contact your server admin for more details."; + original.handle(Disconnect.create(Component.text(reason, NamedTextColor.RED),serverConnection.getPlayer().getProtocolVersion())); + return true; + } + serverConnection.getConnection().setType(clientType); + serverConnection.setConnectionPhase(clientType.getInitialBackendPhase()); + } + ((VelocityForgeBackendConnectionPhase) serverConnection.getPhase()).handle(serverConnection,serverConnection.getPlayer(),packet); + return true; + } + return original.handle(packet); + } + + @Override + public boolean handle(ServerLoginSuccess packet) { + if ((serverConnection.getPlayer().getPhase() instanceof VelocityForgeClientConnectionPhase phase)) { + phase.complete(server,serverConnection.getPlayer(),serverConnection.getPlayer().getConnection()); + } + return original.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/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/backend/VelocityForgeBackendConnectionPhase.java b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/backend/VelocityForgeBackendConnectionPhase.java new file mode 100644 index 0000000..939f474 --- /dev/null +++ b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/backend/VelocityForgeBackendConnectionPhase.java @@ -0,0 +1,41 @@ +package org.adde0109.ambassador.velocity.backend; + +import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.connection.backend.BackendConnectionPhase; +import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; +import com.velocitypowered.proxy.connection.client.ConnectedPlayer; +import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; +import io.netty.buffer.ByteBuf; +import io.netty.util.ReferenceCountUtil; +import org.adde0109.ambassador.velocity.VelocityForgeClientConnectionPhase; + +import java.util.ArrayList; +import java.util.List; + +public class VelocityForgeBackendConnectionPhase implements BackendConnectionPhase { + + + public VelocityForgeBackendConnectionPhase() { + } + + public void handle(VelocityServerConnection server, ConnectedPlayer player, LoginPluginMessage message) { + VelocityForgeClientConnectionPhase clientPhase = ((VelocityForgeClientConnectionPhase) player.getPhase()); + if (clientPhase.clientPhase == VelocityForgeClientConnectionPhase.ClientPhase.VANILLA) { + final LoginPluginMessage msg = message; + + msg.content().retain().discardSomeReadBytes(); + + server.getConnection().getChannel().config().setAutoRead(false); + clientPhase.reset(server.getServer(),player).thenAccept((success) -> { + if (success) { + clientPhase.forwardPayload(server,msg); + server.getConnection().getChannel().config().setAutoRead(true); + } else { + msg.release(); + } + }); + } else { + clientPhase.forwardPayload(server, (LoginPluginMessage) message.retain()); + } + } +} diff --git a/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/backend/VelocityForgeBackendHandshakeHandler.java b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/backend/VelocityForgeBackendHandshakeHandler.java new file mode 100644 index 0000000..06328bd --- /dev/null +++ b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/backend/VelocityForgeBackendHandshakeHandler.java @@ -0,0 +1,39 @@ +package org.adde0109.ambassador.velocity.backend; + +import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.config.PlayerInfoForwarding; +import com.velocitypowered.proxy.connection.MinecraftConnection; +import com.velocitypowered.proxy.connection.backend.LoginSessionHandler; +import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; +import com.velocitypowered.proxy.network.Connections; +import com.velocitypowered.proxy.protocol.packet.Handshake; +import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; +import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess; +import io.netty.channel.*; +import io.netty.util.ReferenceCountUtil; +import org.adde0109.ambassador.forge.ForgeConstants; +import org.adde0109.ambassador.forge.ForgeFMLConnectionType; +import org.adde0109.ambassador.velocity.VelocityForgeClientConnectionPhase; +import org.jetbrains.annotations.NotNull; + +public class VelocityForgeBackendHandshakeHandler extends ChannelInboundHandlerAdapter { + + private final VelocityServer server; + + public VelocityForgeBackendHandshakeHandler(VelocityServer server) { + this.server = server; + } + + @Override + public void channelActive(@NotNull ChannelHandlerContext ctx) throws Exception { + MinecraftConnection connection = (MinecraftConnection) ctx.pipeline().get(Connections.HANDLER); + VelocityServerConnection serverConnection = (VelocityServerConnection) connection.getAssociation(); + + if (serverConnection.getPlayer().getConnection().getType() instanceof ForgeFMLConnectionType) { + connection.setSessionHandler(new ForgeHandshakeSessionHandler((LoginSessionHandler) connection.getSessionHandler(),serverConnection,server)); + } + + ctx.pipeline().remove(this); + ctx.pipeline().fireChannelActive(); + } +} diff --git a/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/commands/EnumArgumentProperty.java b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/commands/EnumArgumentProperty.java new file mode 100644 index 0000000..a370ed3 --- /dev/null +++ b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/commands/EnumArgumentProperty.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.adde0109.ambassador.velocity.commands; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.exceptions.CommandSyntaxException; + +public class EnumArgumentProperty implements ArgumentType { + + private final String className; + + public EnumArgumentProperty(String className) { + this.className = className; + } + + public String getClassName() { + return this.className; + } + + @Override + public String parse(StringReader reader) throws CommandSyntaxException { + return reader.readUnquotedString(); + } +} diff --git a/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/commands/EnumArgumentPropertySerializer.java b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/commands/EnumArgumentPropertySerializer.java new file mode 100644 index 0000000..b39fb51 --- /dev/null +++ b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/commands/EnumArgumentPropertySerializer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2018 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.adde0109.ambassador.velocity.commands; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentPropertySerializer; +import io.netty.buffer.ByteBuf; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * An argument property serializer that will serialize and deserialize nothing. + */ +public class EnumArgumentPropertySerializer implements ArgumentPropertySerializer { + + public static final EnumArgumentPropertySerializer ENUM = new EnumArgumentPropertySerializer(); + + private EnumArgumentPropertySerializer() { + } + + @Override + public @Nullable EnumArgumentProperty deserialize(ByteBuf buf, ProtocolVersion protocolVersion) { + return new EnumArgumentProperty(ProtocolUtils.readString(buf)); + } + + @Override + public void serialize(EnumArgumentProperty object, ByteBuf buf, ProtocolVersion protocolVersion) { + ProtocolUtils.writeString(buf, object.getClassName()); + } +} \ No newline at end of file diff --git a/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/commands/ModIdArgumentProperty.java b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/commands/ModIdArgumentProperty.java new file mode 100644 index 0000000..0f0c83a --- /dev/null +++ b/ambassador-velocity/src/main/java/org/adde0109/ambassador/velocity/commands/ModIdArgumentProperty.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2018 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.adde0109.ambassador.velocity.commands; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.util.ModInfo; +import java.util.Collection; +import java.util.concurrent.CompletableFuture; + +public class ModIdArgumentProperty implements ArgumentType { + + public ModIdArgumentProperty() {} + + @Override + public String parse(StringReader reader) throws CommandSyntaxException { + return reader.readUnquotedString(); + } + + @Override + public CompletableFuture listSuggestions(CommandContext context, + SuggestionsBuilder builder) { + S source = context.getSource(); + + if (source instanceof Player) { + ModInfo modInfo = ((Player) source).getModInfo().orElse(null); + + if (modInfo != null) { + for (ModInfo.Mod mod : modInfo.getMods()) { + builder.suggest(mod.getId()); + } + + return builder.buildFuture(); + } + } + + return Suggestions.empty(); + } + + @Override + public Collection getExamples() { + throw new UnsupportedOperationException(); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 5de0c54..0e13ac7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,7 +1,15 @@ rootProject.name = "Ambassador" -includeBuild("Velocity") { - dependencySubstitution { - substitute(module("com.velocitypowered:velocity-api")).using(project(":velocity-api")) - substitute(module("com.velocitypowered:velocity-proxy")).using(project(":velocity-proxy")) + +val velocityPath = rootProject.projectDir.toPath().resolve("Velocity/"); + includeBuild("Velocity") { + dependencySubstitution { + if(java.nio.file.Files.isDirectory(velocityPath.resolve("proxy/"))) { + substitute(module("com.velocitypowered:velocity-proxy")).using(project(":velocity-proxy")) + substitute(module("com.velocitypowered:velocity-api")).using(project(":velocity-api")) + } else { + logger.warn("Git Submodule 'Velocity' not initialized!") + } + } } -} \ No newline at end of file +include("ambassador-velocity") +include("ambassador-bungeecord") \ No newline at end of file