Changed reset mechanism

This commit is contained in:
Adrian Bergqvist 2023-05-06 16:00:30 +02:00
parent f1b1cc463c
commit a0b38b7595
No known key found for this signature in database
GPG Key ID: 3B3DA43224B79417
15 changed files with 210 additions and 159 deletions

View File

@ -5,7 +5,7 @@ plugins {
}
group = "org.adde0109"
version = "1.3.4-beta"
version = "1.4.0-beta"
repositories {
mavenCentral()

View File

@ -40,7 +40,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_3;
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet;
@Plugin(id = "ambassador", name = "Ambassador", version = "1.3.4-beta", authors = {"adde0109"})
@Plugin(id = "ambassador", name = "Ambassador", version = "1.4.0-beta", authors = {"adde0109"})
public class Ambassador {
public ProxyServer server;

View File

@ -8,6 +8,7 @@ public class ForgeConstants {
public static final String RESET_LISTENER = "ambassador-reset-listener";
public static final String SERVER_SUCCESS_LISTENER = "ambassador-server-success-listener";
public static final String PLUGIN_PACKET_QUEUE = "ambassador-plugin-generated-packet-queue";
public static final String LOGIN_PACKET_QUEUE = "ambassador-login-packet-queue";
public static final String FORGE_HANDSHAKE_DECODER = "ambassador-forge-decoder";
public static final String FORGE_HANDSHAKE_HANDLER = "ambassador-forge-handler";
public static final String COMMAND_ERROR_CATCHER = "ambassador-command-catcher";

View File

@ -65,8 +65,9 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas
//Reset client if not ready to receive new handshake
VelocityForgeClientConnectionPhase clientPhase = (VelocityForgeClientConnectionPhase) player.getPhase();
clientPhase.resetConnectionPhase(player);
message.retain();
clientPhase.resetAndWrite(player, message);
player.getConnection().write(message);
//Forge server
//To avoid unnecessary resets, we wait until we get the handshake even if we know that we should
//reset because that the previous server was Forge.

View File

@ -1,7 +1,6 @@
package org.adde0109.ambassador.forge;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
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;
@ -12,19 +11,16 @@ import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import io.netty.buffer.Unpooled;
import net.kyori.adventure.text.Component;
import org.adde0109.ambassador.Ambassador;
import org.adde0109.ambassador.forge.packet.GenericForgeLoginWrapperPacket;
import org.adde0109.ambassador.forge.packet.IForgeLoginWrapperPacket;
import org.adde0109.ambassador.forge.packet.ModListReplyPacket;
import org.adde0109.ambassador.forge.pipeline.ForgeLoginWrapperDecoder;
import org.adde0109.ambassador.velocity.client.FML2CRPMResetCompleteDecoder;
import org.adde0109.ambassador.velocity.client.OutboundSuccessHolder;
import org.adde0109.ambassador.velocity.client.PluginLoginPacketQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ScheduledFuture;
import org.adde0109.ambassador.velocity.client.ClientPacketQueue;
public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase {
//TODO:Make class when PCF is done
NOT_STARTED {
@Override
@ -33,42 +29,16 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase
}
},
IN_PROGRESS {
},
COMPLETE {
@Override
public void resetAndWrite(ConnectedPlayer player, LoginPluginMessage message) {
resetConnectionPhase(player);
pending = message;
}
@Override
public void resetConnectionPhase(ConnectedPlayer player) {
MinecraftConnection connection = player.getConnection();
//There is no going back even if the handshake fails. No reason to still be connected.
if (player.getConnectedServer() != null) {
player.getConnectedServer().disconnect();
player.setConnectedServer(null);
}
//Don't handle anything from the server until the reset has completed.
player.getConnectionInFlight().getConnection().getChannel().config().setAutoRead(false);
//Prepare to receive reset ACK
connection.getChannel().pipeline().addBefore(Connections.MINECRAFT_DECODER, ForgeConstants.RESET_LISTENER,new FML2CRPMResetCompleteDecoder());
((ForgeLoginWrapperDecoder) connection.getChannel().pipeline().get(ForgeConstants.FORGE_HANDSHAKE_DECODER)).registerLoginWrapperID(98);
//No more PLAY packets past this point should be sent to the client in case the reset works.
connection.write(new PluginMessage("fml:handshake", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generatePluginResetPacket())));
//We unregister so no plugin sees this client while the client is being reset.
((VelocityServer) Ambassador.getInstance().server).unregisterConnection(player);
connection.getChannel().pipeline().addAfter(Connections.MINECRAFT_ENCODER,ForgeConstants.PLUGIN_PACKET_QUEUE, new PluginLoginPacketQueue());
//Transition
player.setPhase(WAITING_RESET);
WAITING_RESET.onTransitionToNewPhase(player);
}
},
RESETTABLE {
@Override
void onTransitionToNewPhase(ConnectedPlayer player) {
//Plugins may now send packets to client
player.getConnection().getChannel().pipeline().remove(ForgeConstants.PLUGIN_PACKET_QUEUE);
((VelocityServer) Ambassador.getInstance().server).registerConnection(player);
}
@Override
@ -77,50 +47,55 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase
}
},
WAITING_RESET {
ScheduledFuture<?> scheduledFuture;
@Override
void onTransitionToNewPhase(ConnectedPlayer player) {
scheduledFuture = player.getConnection().eventLoop().schedule(()-> {
player.getConnection().getChannel().pipeline().remove(ForgeConstants.RESET_LISTENER);
Ambassador.getTemporaryForced().put(player.getUsername(), player.getConnectionInFlight().getServer(),
Ambassador.getInstance().config.getServerSwitchCancellationTime(), TimeUnit.SECONDS);
//Disconnect - Reset Timeout
player.disconnect(Ambassador.getInstance().config.getDisconnectResetMessage());
}, Ambassador.getInstance().config.getResetTimeout(), TimeUnit.MILLISECONDS);
//We unregister so no plugin sees this client while the client is being reset.
((VelocityServer) Ambassador.getInstance().server).unregisterConnection(player);
player.getConnection().getChannel().pipeline().addAfter(Connections.MINECRAFT_ENCODER,
ForgeConstants.LOGIN_PACKET_QUEUE, new ClientPacketQueue(StateRegistry.LOGIN));
if (player.getConnection().getChannel().pipeline().get(ForgeConstants.PLUGIN_PACKET_QUEUE) == null)
player.getConnection().getChannel().pipeline().addAfter(Connections.MINECRAFT_ENCODER,
ForgeConstants.PLUGIN_PACKET_QUEUE, new ClientPacketQueue(StateRegistry.PLAY));
}
@Override
public boolean handle(ConnectedPlayer player, IForgeLoginWrapperPacket msg, VelocityServerConnection server) {
if (msg.getId() == 98) {
if (scheduledFuture.cancel(false)) {
player.getConnection().getChannel().pipeline().remove(ForgeConstants.RESET_LISTENER);
player.getConnection().setState(StateRegistry.LOGIN);
player.setPhase(NOT_STARTED);
if (msg.getId() == 80) {
player.getConnection().getChannel().pipeline().remove(ForgeConstants.RESET_LISTENER);
player.setPhase(NOT_STARTED);
//Send all held messages
if (pending != null)
player.getConnection().write(pending);
player.getConnection().getChannel().pipeline().remove(ForgeConstants.LOGIN_PACKET_QUEUE);
if (!(server.getConnection().getType() instanceof ForgeFMLConnectionType)) {
// -> vanilla
complete(player, ((GenericForgeLoginWrapperPacket) msg).success());
player.getConnectionInFlight().getConnection().getChannel().config().setAutoRead(true);
if (!(server.getConnection().getType() instanceof ForgeFMLConnectionType)) {
//Forge -> Vanilla
MinecraftConnection connection = player.getConnection();
((OutboundSuccessHolder) connection.getChannel().pipeline().get(ForgeConstants.SERVER_SUCCESS_LISTENER))
.sendPacket();
connection.setState(StateRegistry.PLAY);
//Plugins may now send packets to client
connection.getChannel().pipeline().remove(ForgeConstants.PLUGIN_PACKET_QUEUE);
((VelocityServer) Ambassador.getInstance().server).registerConnection(player);
}
}
return true;
} else {
return false;
}
}
},
COMPLETE {
@Override
void onTransitionToNewPhase(ConnectedPlayer player) {
//Plugins may now send packets to client
player.getConnection().getChannel().pipeline().remove(ForgeConstants.PLUGIN_PACKET_QUEUE);
((VelocityServer) Ambassador.getInstance().server).registerConnection(player);
}
@Override
public void resetConnectionPhase(ConnectedPlayer player) {
player.disconnect(Component.text("reconnect"));
}
@Override
public boolean consideredComplete() {
return true;
}
};
public LoginPluginMessage pending;
public boolean handle(ConnectedPlayer player, IForgeLoginWrapperPacket msg, VelocityServerConnection server) {
player.setPhase(nextPhase());
@ -133,12 +108,51 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase
return true;
}
void onTransitionToNewPhase(ConnectedPlayer player) {
public void complete(ConnectedPlayer player, boolean resettable) {
MinecraftConnection connection = player.getConnection();
((OutboundSuccessHolder) connection.getChannel().pipeline().get(ForgeConstants.SERVER_SUCCESS_LISTENER))
.sendPacket();
connection.setState(StateRegistry.PLAY);
if (resettable) {
player.setPhase(RESETTABLE);
RESETTABLE.onTransitionToNewPhase(player);
} else {
player.setPhase(COMPLETE);
COMPLETE.onTransitionToNewPhase(player);
}
}
public void resetAndWrite(ConnectedPlayer player, LoginPluginMessage message) {
player.getConnection().write(message);
@Override
public void resetConnectionPhase(ConnectedPlayer player) {
MinecraftConnection connection = player.getConnection();
//There is no going back even if the handshake fails. No reason to still be connected.
if (player.getConnectedServer() != null) {
player.getConnectedServer().disconnect();
player.setConnectedServer(null);
}
//Don't handle anything from the server until the reset has completed.
//player.getConnectionInFlight().getConnection().getChannel().config().setAutoRead(false);
if (connection.getState() == StateRegistry.PLAY) {
connection.write(new PluginMessage("fml:handshake", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generatePluginResetPacket())));
connection.setState(StateRegistry.LOGIN);
} else {
connection.write(new LoginPluginMessage(80,"fml:handshake", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generateResetPacket())));
}
//Prepare to receive reset ACK
connection.getChannel().pipeline().addBefore(Connections.MINECRAFT_DECODER,
ForgeConstants.RESET_LISTENER, new FML2CRPMResetCompleteDecoder());
//Transition
player.setPhase(WAITING_RESET);
WAITING_RESET.onTransitionToNewPhase(player);
}
void onTransitionToNewPhase(ConnectedPlayer player) {
}
VelocityForgeClientConnectionPhase nextPhase() {

View File

@ -6,10 +6,12 @@ import io.netty.buffer.ByteBuf;
public class GenericForgeLoginWrapperPacket extends DeferredByteBufHolder implements IForgeLoginWrapperPacket {
private final int id;
private final boolean success;
public GenericForgeLoginWrapperPacket(ByteBuf input, int id) {
public GenericForgeLoginWrapperPacket(ByteBuf input, int id, boolean success) {
super(input);
this.id = id;
this.success = success;
}
@Override
@ -21,4 +23,8 @@ public class GenericForgeLoginWrapperPacket extends DeferredByteBufHolder implem
public int getId() {
return id;
}
public boolean success() {
return success;
}
}

View File

@ -2,7 +2,8 @@ package org.adde0109.ambassador.forge.packet;
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
public interface IForgeLoginWrapperPacket {
public interface IForgeLoginWrapperPacket<T> {
public T read(LoginPluginResponse message);
public LoginPluginResponse encode();
public int getId();
}

View File

@ -0,0 +1,34 @@
package org.adde0109.ambassador.forge.packet;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
import java.util.List;
import java.util.Map;
public class ModListPacket implements IForgeLoginWrapperPacket{
private List<String> mods;
private Map<ChannelIdentifier, String> channels;
private List<ChannelIdentifier> registries;
private final int id;
public ModListPacket(int id) {
this.id = id;
}
@Override
public Object read(LoginPluginResponse message) {
return null;
}
@Override
public LoginPluginResponse encode() {
return null;
}
@Override
public int getId() {
return 0;
}
}

View File

@ -28,7 +28,7 @@ public class ModListReplyPacket implements IForgeLoginWrapperPacket {
this.id = id;
}
public static ModListReplyPacket read(LoginPluginResponse msg) {
public ModListReplyPacket read(LoginPluginResponse msg) {
ByteBuf input = msg.content();
List<String> mods = new ArrayList<>();

View File

@ -26,7 +26,7 @@ public class ForgeLoginWrapperDecoder extends MessageToMessageDecoder<LoginPlugi
String channel = ProtocolUtils.readString(buf);
if (!channel.equals("fml:handshake")) {
buf.readerIndex(originalReaderIndex);
out.add(new GenericForgeLoginWrapperPacket(buf.retain(), msg.getId()));
out.add(new GenericForgeLoginWrapperPacket(buf.retain(), msg.getId(), true));
return;
}
int length = ProtocolUtils.readVarInt(buf);
@ -35,7 +35,7 @@ public class ForgeLoginWrapperDecoder extends MessageToMessageDecoder<LoginPlugi
out.add(ModListReplyPacket.read(msg));
} else {
buf.readerIndex(originalReaderIndex);
out.add(new GenericForgeLoginWrapperPacket(buf.retain(), msg.getId()));
out.add(new GenericForgeLoginWrapperPacket(buf.retain(), msg.getId(), true));
}
}

View File

@ -51,30 +51,24 @@ public class ForgeLoginSessionHandler implements MinecraftSessionHandler {
if ((serverConnection.getPhase() instanceof VelocityForgeBackendConnectionPhase phase)) {
phase.onLoginSuccess(serverConnection,serverConnection.getPlayer());
}
ConnectedPlayer player = serverConnection.getPlayer();
if (player.getConnectedServer() != null && player.getConnectedServer().getConnection().getType() instanceof ForgeFMLConnectionType) {
//Forge -> vanilla
//Has not already been reset
//Not for Vanilla -> Vanilla
player.getPhase().resetConnectionPhase(player);
} else if (player.getConnection().getState() == StateRegistry.LOGIN) {
//Initial vanilla
//Vanilla -> Forge
//Forge -> Forge
MinecraftConnection connection = player.getConnection();
((OutboundSuccessHolder) connection.getChannel().pipeline().get(ForgeConstants.SERVER_SUCCESS_LISTENER))
.sendPacket();
connection.setState(StateRegistry.PLAY);
connection.getChannel().pipeline().remove(ForgeConstants.PLUGIN_PACKET_QUEUE);
((VelocityServer) Ambassador.getInstance().server).registerConnection(player);
}
original.handle(packet);
if (serverConnection.getConnection() == null) {
return true;
}
ConnectedPlayer player = serverConnection.getPlayer();
if (!(serverConnection.getConnection().getType() instanceof ForgeFMLConnectionType)) {
//Initial vanilla - because we need to find out if reset packet works
//Forge -> vanilla
if (player.getConnectedServer() == null) {
//Initial Vanilla
serverConnection.getConnection().setSessionHandler(
new ForgePlaySessionHandler((TransitionSessionHandler) serverConnection
.getConnection().getSessionHandler(),serverConnection));
//Send empty Mod list
player.getConnectionInFlight().getConnection().getChannel().config().setAutoRead(false);
}
} else {
//TODO: Read modlist
((VelocityForgeClientConnectionPhase) player.getPhase()).complete(player, true);
}
return true;
}

View File

@ -20,7 +20,7 @@ public class ForgePlaySessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(JoinGame packet) {
if (serverConnection.getPlayer().getPhase() instanceof VelocityForgeClientConnectionPhase clientPhase) {
serverConnection.getPlayer().setPhase(VelocityForgeClientConnectionPhase.COMPLETE);
serverConnection.getPlayer().setPhase(VelocityForgeClientConnectionPhase.RESETTABLE);
}
return MinecraftSessionHandler.super.handle(packet);
}

View File

@ -1,48 +1,49 @@
package org.adde0109.ambassador.velocity.client;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import io.netty.channel.*;
import io.netty.handler.codec.EncoderException;
import io.netty.util.ReferenceCountUtil;
public class PluginLoginPacketQueue extends ChannelOutboundHandlerAdapter {
private PendingWriteQueue queue;
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
queue = new PendingWriteQueue(ctx);
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
MinecraftConnection connection = ctx.pipeline().get(MinecraftConnection.class);
if (connection.getState() == StateRegistry.LOGIN && msg instanceof MinecraftPacket packet) {
try {
StateRegistry.LOGIN.getProtocolRegistry(ProtocolUtils.Direction.CLIENTBOUND ,
connection.getProtocolVersion()).getPacketId(packet);
ctx.write(msg,promise);
} catch (IllegalArgumentException e) {
queue.add(msg, promise);
}
} else {
ctx.write(msg,promise);
}
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel().isActive()) {
queue.removeAndWriteAll();
ctx.flush();
} else {
queue.removeAndFailAll(new ChannelException());
}
}
}
package org.adde0109.ambassador.velocity.client;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import io.netty.channel.*;
public class ClientPacketQueue extends ChannelOutboundHandlerAdapter {
private PendingWriteQueue queue;
private final StateRegistry registry;
public ClientPacketQueue(StateRegistry registry) {
this.registry = registry;
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
queue = new PendingWriteQueue(ctx);
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
MinecraftConnection connection = ctx.pipeline().get(MinecraftConnection.class);
if (msg instanceof MinecraftPacket packet) {
try {
registry.getProtocolRegistry(ProtocolUtils.Direction.CLIENTBOUND ,
connection.getProtocolVersion()).getPacketId(packet);
queue.add(msg,promise);
} catch (IllegalArgumentException e) {
ctx.write(msg, promise);
}
} else {
ctx.write(msg,promise);
}
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel().isActive()) {
queue.removeAndWriteAll();
ctx.flush();
} else {
queue.removeAndFailAll(new ChannelException());
}
}
}

View File

@ -1,9 +1,8 @@
package org.adde0109.ambassador.velocity.client;
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.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.adde0109.ambassador.forge.packet.GenericForgeLoginWrapperPacket;
@ -25,9 +24,9 @@ public class FML2CRPMResetCompleteDecoder extends ChannelInboundHandlerAdapter {
try {
int id = ProtocolUtils.readVarInt(buf);
boolean success = buf.readBoolean();
if (id == 98) {
if (id == 80) {
try {
IForgeLoginWrapperPacket packet = new GenericForgeLoginWrapperPacket(buf, id);
IForgeLoginWrapperPacket packet = new GenericForgeLoginWrapperPacket(Unpooled.EMPTY_BUFFER, id, success);
ctx.fireChannelRead(packet);
} finally {
buf.release();

View File

@ -30,12 +30,12 @@ public class VelocityHandshakeSessionHandler implements MinecraftSessionHandler
case "FML2":
connection.setType(ForgeConstants.ForgeFML2);
connection.getChannel().pipeline().addAfter(Connections.MINECRAFT_ENCODER,ForgeConstants.SERVER_SUCCESS_LISTENER, new OutboundSuccessHolder());
connection.getChannel().pipeline().addAfter(Connections.MINECRAFT_ENCODER,ForgeConstants.PLUGIN_PACKET_QUEUE, new PluginLoginPacketQueue());
connection.getChannel().pipeline().addAfter(Connections.MINECRAFT_ENCODER,ForgeConstants.PLUGIN_PACKET_QUEUE, new ClientPacketQueue(StateRegistry.PLAY));
break;
case "FML3":
connection.setType(ForgeConstants.ForgeFML3);
connection.getChannel().pipeline().addAfter(Connections.MINECRAFT_ENCODER,ForgeConstants.SERVER_SUCCESS_LISTENER, new OutboundSuccessHolder());
connection.getChannel().pipeline().addAfter(Connections.MINECRAFT_ENCODER,ForgeConstants.PLUGIN_PACKET_QUEUE, new PluginLoginPacketQueue());
connection.getChannel().pipeline().addAfter(Connections.MINECRAFT_ENCODER,ForgeConstants.PLUGIN_PACKET_QUEUE, new ClientPacketQueue(StateRegistry.PLAY));
break;
}
}