Restructured project

This commit is contained in:
Adrian Bergqvist 2023-02-04 16:44:27 +01:00
parent 6a541ff8ce
commit 2063b1364e
No known key found for this signature in database
GPG Key ID: FAE7D8EDE225E686
51 changed files with 1262 additions and 6 deletions

View File

@ -0,0 +1,7 @@
plugins {
java
id("com.github.johnrengelman.shadow") version "7.1.2"
}
group = "org.adde0109"
version = "1.1.7-alpha-bungeecord"

View File

@ -0,0 +1,2 @@
Manifest-Version: 1.0

View File

@ -1,6 +1,5 @@
plugins {
java
idea
id("com.github.johnrengelman.shadow") version "7.1.2"
}

View File

@ -0,0 +1 @@
{"id":"ambassador","name":"Ambassador","version":"1.1.7-alpha","authors":["adde0109"],"dependencies":[],"main":"org.adde0109.ambassador.Ambassador"}

View File

@ -0,0 +1,2 @@
Manifest-Version: 1.0

View File

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

View File

@ -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<Boolean> reset(RegisteredServer server, ConnectedPlayer player) {
CompletableFuture<Boolean> 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;
}
}
}

View File

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

View File

@ -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<String,RegisteredServer> TEMPORARY_FORCED = new PassiveExpiringMap<>(120, TimeUnit.SECONDS);
private Throwable throwable;
private RegisteredServer triedServer;
private Continuation continuation;
private CompletableFuture<Void> 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<Boolean> reset(RegisteredServer server, ConnectedPlayer player) {
FML2CRPMClientConnectionPhase newPhase = new FML2CRPMClientConnectionPhase(clientPhase,getPayloadManager());
player.setPhase(newPhase);
CompletableFuture<Boolean> 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<Void> 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()));
}
}
});
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<Integer, CompletableFuture<ByteBuf>> listenerList = new HashMap<>();
private int counter = 0;
private final MinecraftConnection connection;
public VelocityLoginPayloadManager(MinecraftConnection connection) {
this.connection = connection;
}
public CompletableFuture<ByteBuf> sendPayload(String channel, ByteBuf data) {
connection.write(new LoginPluginMessage(counter,channel,data));
CompletableFuture<ByteBuf> future = listenFor(counter);
counter++;
return future;
}
public CompletableFuture<ByteBuf> sendPayloads(String channel, List<ByteBuf> dataList) {
final CompletableFuture<ByteBuf> 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<ByteBuf> listenFor(int id) {
CompletableFuture<ByteBuf> value = listenerList.get(id);
if (value == null) {
CompletableFuture<ByteBuf> callback = new CompletableFuture<>();
listenerList.put(id,callback);
return callback;
} else {
return value;
}
}
boolean handlePayload(LoginPluginResponse response) {
final CompletableFuture<ByteBuf> 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;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String> {
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();
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<EnumArgumentProperty> {
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());
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String> {
public ModIdArgumentProperty() {}
@Override
public String parse(StringReader reader) throws CommandSyntaxException {
return reader.readUnquotedString();
}
@Override
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> 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<String> getExamples() {
throw new UnsupportedOperationException();
}
}

View File

@ -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!")
}
}
}
}
include("ambassador-velocity")
include("ambassador-bungeecord")