WIP: Almost done

This commit is contained in:
Adrian Bergqvist 2022-09-27 00:14:33 +02:00
parent 2ab257ffe7
commit 007ff8511a
No known key found for this signature in database
GPG Key ID: FAE7D8EDE225E686
9 changed files with 129 additions and 98 deletions

View File

@ -4,7 +4,7 @@ plugins {
}
group 'org.adde0109'
version '1.0.0-alpha'
version '1.0.2-alpha'
repositories {
maven {

View File

@ -20,6 +20,7 @@ import org.adde0109.ambassador.forge.ForgeConnection;
import org.adde0109.ambassador.forge.ForgeHandshakeHandler;
import org.adde0109.ambassador.forge.ForgeHandshakeUtils;
import org.adde0109.ambassador.forge.ForgeServerSwitchHandler;
import org.adde0109.ambassador.velocity.VelocityBackendChannelInitializer;
import org.adde0109.ambassador.velocity.VelocityServerChannelInitializer;
import org.adde0109.ambassador.velocity.VelocityEventHandler;
import org.bstats.charts.SingleLineChart;
@ -28,7 +29,7 @@ import org.slf4j.Logger;
import java.nio.file.Path;
@Plugin(id = "ambassador", name = "Ambassador", version = "1.0.0-alpha", authors = {"adde0109"})
@Plugin(id = "ambassador", name = "Ambassador", version = "1.0.2-alpha", authors = {"adde0109"})
public class Ambassador {
public ProxyServer server;
@ -72,11 +73,12 @@ public class Ambassador {
private void inject() throws ReflectiveOperationException {
Field cmField = VelocityServer.class.getDeclaredField("cm");
cmField.setAccessible(true);
Field endpointMap = ConnectionManager.class.getDeclaredField("endpoints");
endpointMap.setAccessible(true);
ChannelInitializer<?> original = ((ConnectionManager) cmField.get(server)).serverChannelInitializer.get();
((ConnectionManager) cmField.get(server)).serverChannelInitializer.set(new VelocityServerChannelInitializer(original));
ChannelInitializer<?> originalBackend = ((ConnectionManager) cmField.get(server)).backendChannelInitializer.get();
((ConnectionManager) cmField.get(server)).backendChannelInitializer.set(new VelocityBackendChannelInitializer(originalBackend,(VelocityServer) server));
}
@Subscribe

View File

@ -2,4 +2,5 @@ package org.adde0109.ambassador.forge;
public class ForgeConstants {
public static final String HANDLER = "Modern Forge handler";
public static final ForgeFML2ConnectionType ForgeFML2 = new ForgeFML2ConnectionType();
}

View File

@ -25,13 +25,12 @@ import org.checkerframework.checker.units.qual.A;
import org.checkerframework.checker.units.qual.C;
import javax.annotation.Nullable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.*;
public class ForgeFML2ClientConnectionPhase implements VelocityForgeClientConnectionPhase {
private boolean isResettable;
@ -40,24 +39,30 @@ public class ForgeFML2ClientConnectionPhase implements VelocityForgeClientConnec
public byte[] modListData;
private final ArrayList<Integer> listenerList = new ArrayList();
public ArrayList<MinecraftPacket> packagesToSendAfterReset = new ArrayList<>();
private Runnable whenComplete;
public boolean isReady = false;
public ClientPhase clientPhase = ClientPhase.HANDSHAKE;
@Override
public void handleLogin(ConnectedPlayer player,ForgeHandshakeUtils.CachedServerHandshake handshake, Continuation continuation) {
this.whenComplete = continuation::resume;
final MinecraftConnection connection = player.getConnection();
VelocityForgeHandshakeSessionHandler sessionHandler = new VelocityForgeHandshakeSessionHandler(connection.getSessionHandler(),player);
if(handshake == null) {
connection.delayedWrite(new LoginPluginMessage(0,"fml:loginwrapper", Unpooled.wrappedBuffer(ForgeHandshakeUtils.emptyModlist)));
listenerList.add(0);
VelocityForgeHandshakeSessionHandler sessionHandler = new VelocityForgeHandshakeSessionHandler(connection.getSessionHandler(), player);
if (handshake == null) {
connection.delayedWrite(new LoginPluginMessage(98, "fml:loginwrapper", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generateResetPacket())));
listenerList.add(98);
this.whenComplete = () -> {
this.clientPhase = ClientPhase.VANILLA;
continuation.resume();
};
} else {
connection.delayedWrite(new LoginPluginMessage(0,"fml:loginwrapper", Unpooled.wrappedBuffer(handshake.modListPacket)));
connection.delayedWrite(new LoginPluginMessage(0, "fml:loginwrapper", Unpooled.wrappedBuffer(handshake.modListPacket)));
listenerList.add(0);
for (int i = 0;i<handshake.otherPackets.size();i++) {
connection.delayedWrite(new LoginPluginMessage(i+1,"fml:loginwrapper", Unpooled.wrappedBuffer(handshake.otherPackets.get(i))));
listenerList.add(i+1);
for (int i = 0; i < handshake.otherPackets.size(); i++) {
connection.delayedWrite(new LoginPluginMessage(i + 1, "fml:loginwrapper", Unpooled.wrappedBuffer(handshake.otherPackets.get(i))));
listenerList.add(i + 1);
}
this.whenComplete = () -> {
this.clientPhase = ClientPhase.MODDED;
continuation.resume();
};
}
connection.setSessionHandler(sessionHandler);
connection.flush();
@ -65,40 +70,35 @@ public class ForgeFML2ClientConnectionPhase implements VelocityForgeClientConnec
@Override
public boolean handle(ConnectedPlayer player, LoginPluginResponse packet) {
if (packet.getId() == 98) {
this.clientPhase = ClientPhase.HANDSHAKE;
this.isResettable = packet.isSuccess();
} else if (packet.getId() == 0) {
this.clientPhase = ClientPhase.MODLIST;
}
if (!listenerList.removeIf(id -> id.equals(packet.getId()))) {
player.getConnectionInFlight().getConnection().write(packet.retain());
return true;
}
if (packet.getId() == 98) {
isReady = true;
for (MinecraftPacket packet1 : packagesToSendAfterReset) {
player.getConnection().delayedWrite(packet1);
}
packagesToSendAfterReset = new ArrayList<>();
player.getConnection().flush();
} else if (packet.getId() == 0) {
if (!packet.isSuccess()) {
//TODO: Write disconnect message to end user
player.getConnection().close();
return true;
}
modListData = ByteBufUtil.getBytes(packet.content());
}
if (listenerList.isEmpty() && whenComplete != null) {
} else if (listenerList.isEmpty() && whenComplete != null) {
whenComplete.run();
whenComplete = null;
}
return true;
}
public void reset(ConnectedPlayer player,MinecraftConnection connection) {
isReady = false;
public void reset(ConnectedPlayer player, Runnable whenComplete) {
this.whenComplete = whenComplete;
if (player.getConnectedServer() != null) {
player.getConnectedServer().disconnect();
}
MinecraftConnection connection = player.getConnection();
connection.setSessionHandler(new VelocityForgeHandshakeSessionHandler(connection.getSessionHandler(),player));
connection.write(new PluginMessage("fml:handshake",Unpooled.wrappedBuffer(ForgeHandshakeUtils.generatePluginResetPacket())));
if (connection.getState() == StateRegistry.LOGIN) {
connection.write(new LoginPluginMessage(98,"fml:loginwrapper", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generateResetPacket())));
} else {
connection.write(new PluginMessage("fml:handshake",Unpooled.wrappedBuffer(ForgeHandshakeUtils.generatePluginResetPacket())));
connection.setState(StateRegistry.LOGIN);
}
listenerList.add(98);
connection.setState(StateRegistry.LOGIN);
this.clientPhase = null;
}
public void complete(VelocityServer server, ConnectedPlayer player, MinecraftConnection connection) {
VelocityConfiguration configuration = (VelocityConfiguration) server.getConfiguration();
@ -109,16 +109,21 @@ public class ForgeFML2ClientConnectionPhase implements VelocityForgeClientConnec
ServerLoginSuccess success = new ServerLoginSuccess();
success.setUsername(player.getUsername());
success.setUuid(playerUniqueId);
send(player,success);
connection.write(success);
this.clientPhase = this.clientPhase == ClientPhase.MODLIST ? ClientPhase.MODDED : ClientPhase.VANILLA;
connection.setState(StateRegistry.PLAY);
connection.setSessionHandler(((VelocityForgeHandshakeSessionHandler) connection.getSessionHandler()).getOriginal());
}
public void send(ConnectedPlayer player, MinecraftPacket message) {
if (isReady) {
player.getConnection().write(message);
} else {
packagesToSendAfterReset.add(message);
}
}
public enum ClientPhase {
VANILLA,
HANDSHAKE,
MODLIST,
MODDED
}
}

View File

@ -20,7 +20,7 @@ public class ForgeFML2ConnectionType implements ConnectionType {
@Override
public BackendConnectionPhase getInitialBackendPhase() {
return BackendConnectionPhases.UNKNOWN;
return new VelocityForgeBackendConnectionPhase();
}
@Override

View File

@ -1,16 +1,11 @@
package org.adde0109.ambassador.velocity;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import org.adde0109.ambassador.forge.ForgeConstants;
import org.adde0109.ambassador.forge.ForgeFML2ClientConnectionPhase;
import org.adde0109.ambassador.forge.ForgeFML2ConnectionType;
import org.adde0109.ambassador.velocity.backend.VelocityForgeBackendHandshakeHandler;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Method;
@ -36,20 +31,8 @@ public class VelocityBackendChannelInitializer extends ChannelInitializer<Channe
}
@Override
protected void initChannel(@NotNull Channel ch) throws Exception {
INIT_CHANNEL.invoke(original);
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
if (ctx.handler() instanceof MinecraftConnection connection) {
if (connection.getAssociation() instanceof VelocityServerConnection serverConnection) {
if (serverConnection.getPlayer().getPhase() instanceof ForgeFML2ClientConnectionPhase) {
connection.setType(new ForgeFML2ConnectionType());
ctx.pipeline().addBefore(ctx.name(), ForgeConstants.HANDLER, new VelocityForgeBackendHandshakeHandler(connection, serverConnection, server));
}
}
}
super.handlerAdded(ctx);
protected void initChannel(Channel ch) throws Exception {
INIT_CHANNEL.invoke(original,ch);
ch.pipeline().addLast(ForgeConstants.HANDLER, new VelocityForgeBackendHandshakeHandler(server));
}
}

View File

@ -8,7 +8,7 @@ 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.ForgeFML2ConnectionType;
import org.adde0109.ambassador.forge.ForgeConstants;
public class VelocityHandshakeSessionHandler implements MinecraftSessionHandler {
private final HandshakeSessionHandler original;
@ -24,7 +24,7 @@ public class VelocityHandshakeSessionHandler implements MinecraftSessionHandler
handshake.handle(original);
if (connection.getType() == ConnectionTypes.VANILLA && connection.getState() == StateRegistry.LOGIN) {
if (handshake.getServerAddress().split("\0")[1].equals("FML2")) {
connection.setType(new ForgeFML2ConnectionType());
connection.setType(ForgeConstants.ForgeFML2);
}
}
return true;

View File

@ -1,45 +1,46 @@
package org.adde0109.ambassador.velocity.backend;
import com.velocitypowered.proxy.Velocity;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
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.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.adde0109.ambassador.Ambassador;
import org.adde0109.ambassador.forge.ForgeFML2ClientConnectionPhase;
import org.adde0109.ambassador.forge.ForgeHandshakeUtils;
import org.adde0109.ambassador.velocity.VelocityForgeClientConnectionPhase;
import java.util.ArrayList;
import java.util.List;
public class VelocityForgeBackendConnectionPhase implements BackendConnectionPhase {
private final Ambassador ambassador;
private boolean vanilla = true;
private final List<LoginPluginMessage> queuedHandshakePackets = new ArrayList<>();
public VelocityForgeBackendConnectionPhase(Ambassador ambassador) {
this.ambassador = ambassador;
public VelocityForgeBackendConnectionPhase() {
}
public void handleSuccess(VelocityServerConnection serverCon, VelocityServer server) {
ForgeFML2ClientConnectionPhase clientPhase = ((ForgeFML2ClientConnectionPhase) serverCon.getPlayer().getPhase());
if (vanilla) {
clientPhase.reset(serverCon.getPlayer(),serverCon.getPlayer().getConnection());
if (clientPhase.clientPhase == ForgeFML2ClientConnectionPhase.ClientPhase.HANDSHAKE || clientPhase.clientPhase == ForgeFML2ClientConnectionPhase.ClientPhase.MODLIST) {
clientPhase.complete((VelocityServer) server,serverCon.getPlayer(),serverCon.getPlayer().getConnection());
}
clientPhase.complete((VelocityServer) ambassador.server,serverCon.getPlayer(),serverCon.getPlayer().getConnection());
}
public boolean handle(VelocityServerConnection server, ConnectedPlayer player, LoginPluginMessage message) {
vanilla = false;
public boolean handle(VelocityServerConnection server, ConnectedPlayer player, LoginPluginMessage message) throws Exception {
ForgeFML2ClientConnectionPhase clientPhase = ((ForgeFML2ClientConnectionPhase) player.getPhase());
message.retain();
((ForgeFML2ClientConnectionPhase) player.getPhase()).send(player,message);
if (clientPhase.clientPhase == ForgeFML2ClientConnectionPhase.ClientPhase.VANILLA) {
clientPhase.reset(player, () -> {
for (LoginPluginMessage msg: queuedHandshakePackets) {
player.getConnection().delayedWrite(msg);
}
player.getConnection().flush();
});
queuedHandshakePackets.add(message);
} else if (clientPhase.clientPhase != null) {
player.getConnection().write(message);
} else {
queuedHandshakePackets.add(message);
}
return true;
}

View File

@ -3,34 +3,73 @@ package org.adde0109.ambassador.velocity.backend;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.network.Connections;
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
import com.velocitypowered.proxy.protocol.packet.ServerLogin;
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.*;
import io.netty.util.ReferenceCountUtil;
import org.adde0109.ambassador.forge.ForgeConstants;
import org.adde0109.ambassador.forge.ForgeFML2ClientConnectionPhase;
public class VelocityForgeBackendHandshakeHandler extends ChannelInboundHandlerAdapter {
import java.util.concurrent.CountDownLatch;
private final MinecraftConnection connection;
private final VelocityServerConnection serverConnection;
public class VelocityForgeBackendHandshakeHandler extends ChannelDuplexHandler {
private VelocityServerConnection serverConnection;
private final VelocityServer server;
public VelocityForgeBackendHandshakeHandler(MinecraftConnection connection, VelocityServerConnection serverConnection, VelocityServer server) {
this.connection = connection;
this.serverConnection = serverConnection;
public VelocityForgeBackendHandshakeHandler(VelocityServer server) {
this.server = server;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if ((msg instanceof ServerLogin)) {
ChannelHandler handler = ctx.pipeline().get(Connections.HANDLER);
if (handler instanceof MinecraftConnection connection) {
if (connection.getAssociation() instanceof VelocityServerConnection serverConnection) {
if (serverConnection.getPlayer().getPhase() instanceof ForgeFML2ClientConnectionPhase phase) {
init(connection,serverConnection);
if (phase.clientPhase == ForgeFML2ClientConnectionPhase.ClientPhase.MODDED) {
phase.reset(serverConnection.getPlayer(), () -> {
ctx.write(msg, promise);
ctx.flush();
});
} else {
ctx.write(msg,promise);
}
} else {
ctx.pipeline().remove(this);
ctx.write(msg,promise);
}
} else {
throw new Exception("Connection not associated with a server connection");
}
} else {
throw new Exception("Default minecraft packet handler not found");
}
} else {
ctx.write(msg,promise);
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof LoginPluginMessage message && message.getChannel().equals("fml:loginwrapper")) {
((VelocityForgeBackendConnectionPhase) serverConnection.getPhase()).handle(serverConnection, serverConnection.getPlayer(), message);
ReferenceCountUtil.release(msg);
} else if (msg instanceof ServerLoginSuccess) {
((VelocityForgeBackendConnectionPhase) serverConnection.getPhase()).handleSuccess(serverConnection,server);
ReferenceCountUtil.release(msg);
ctx.fireChannelRead(msg);
} else {
ctx.fireChannelRead(msg);
}
}
private void init(MinecraftConnection connection, VelocityServerConnection serverConnection) {
this.serverConnection = serverConnection;
connection.setType(ForgeConstants.ForgeFML2);
serverConnection.setConnectionPhase(connection.getType().getInitialBackendPhase());
}
}