inject into velocity backend (currently broken)

Add client-side mode for UserConnection

remove unused code

fix duplicate method on rebase
This commit is contained in:
creeper123123321 2020-04-13 12:59:09 -03:00 committed by Five (Xer)
parent 69c8cf8ffb
commit 4813cc3077
No known key found for this signature in database
GPG Key ID: A3F306B10E6330E7
14 changed files with 286 additions and 58 deletions

View File

@ -61,7 +61,7 @@ public class BukkitEncodeHandler extends MessageToByteEncoder implements ViaHand
@Override
public void transform(ByteBuf bytebuf) throws Exception {
info.checkOutgoingPacket();
if (!info.checkOutgoingPacket()) throw CancelEncoderException.generate(null);
if (!info.shouldTransformPacket()) return;
info.transformOutgoing(bytebuf, CancelEncoderException::generate);
}

View File

@ -22,7 +22,7 @@ public class BungeeEncodeHandler extends MessageToMessageEncoder<ByteBuf> {
@Override
protected void encode(final ChannelHandlerContext ctx, ByteBuf bytebuf, List<Object> out) throws Exception {
info.checkOutgoingPacket();
if (!info.checkOutgoingPacket()) throw CancelEncoderException.generate(null);
if (!info.shouldTransformPacket()) {
out.add(bytebuf.retain());
return;

View File

@ -18,7 +18,6 @@ import us.myles.ViaVersion.protocols.base.ProtocolInfo;
import us.myles.ViaVersion.util.PipelineUtil;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
@ -29,6 +28,7 @@ public class UserConnection {
private final Channel channel;
private ProtocolInfo protocolInfo;
Map<Class, StoredObject> storedObjects = new ConcurrentHashMap<>();
private final boolean clientSide;
private boolean active = true;
private boolean pendingDisconnect;
private Object lastPacket;
@ -42,8 +42,23 @@ public class UserConnection {
private int secondsObserved;
private int warnings;
public UserConnection(@Nullable Channel channel) {
/**
* Creates an UserConnection. When it's a client-side connection, some method behaviors are modified.
* @param channel netty channel.
* @param clientSide true if it's a client-side connection
*/
public UserConnection(@Nullable Channel channel, boolean clientSide) {
this.channel = channel;
this.clientSide = clientSide;
}
/**
*
* @see #UserConnection(Channel, boolean)
* @param channel
*/
public UserConnection(@Nullable Channel channel) {
this(channel, false);
}
/**
@ -91,12 +106,24 @@ public class UserConnection {
* @param packet The raw packet to send
* @param currentThread Should it run in the same thread
*/
public void sendRawPacket(ByteBuf packet, boolean currentThread) {
ChannelHandler handler = channel.pipeline().get(Via.getManager().getInjector().getEncoderName());
if (currentThread) {
channel.pipeline().context(handler).writeAndFlush(packet);
public void sendRawPacket(final ByteBuf packet, boolean currentThread) {
Runnable act;
if (clientSide) {
// We'll just assume that Via decoder isn't wrapping the original decoder
act = () -> getChannel().pipeline()
.context(Via.getManager().getInjector().getDecoderName()).fireChannelRead(packet);
} else {
channel.eventLoop().submit(() -> channel.pipeline().context(handler).writeAndFlush(packet));
act = () -> channel.pipeline().context(Via.getManager().getInjector().getEncoderName()).writeAndFlush(packet);
}
if (currentThread) {
act.run();
} else {
try {
channel.eventLoop().submit(act);
} catch (Throwable e) {
packet.release(); // Couldn't schedule
e.printStackTrace();
}
}
}
@ -106,11 +133,25 @@ public class UserConnection {
* @param packet The raw packet to send
* @return ChannelFuture of the packet being sent
*/
public ChannelFuture sendRawPacketFuture(ByteBuf packet) {
ChannelHandler handler = channel.pipeline().get(Via.getManager().getInjector().getEncoderName());
public ChannelFuture sendRawPacketFuture(final ByteBuf packet) {
if (clientSide) {
return sendRawPacketFutureClientSide(packet);
} else {
return sendRawPacketFutureServerSide(packet);
}
}
private ChannelFuture sendRawPacketFutureServerSide(final ByteBuf packet) {
final ChannelHandler handler = channel.pipeline().get(Via.getManager().getInjector().getEncoderName());
return channel.pipeline().context(handler).writeAndFlush(packet);
}
private ChannelFuture sendRawPacketFutureClientSide(final ByteBuf packet) {
// Assume that decoder isn't wrapping
getChannel().pipeline().context(Via.getManager().getInjector().getDecoderName()).fireChannelRead(packet);
return getChannel().newSucceededFuture();
}
/**
* Send a raw packet to the player (netty thread).
*
@ -156,6 +197,7 @@ public class UserConnection {
* @see #incrementReceived()
*/
public boolean exceedsMaxPPS() {
if (clientSide) return false; // Don't apply PPS limiting for client-side
ViaVersionConfig conf = Via.getConfig();
// Max PPS Checker
if (conf.getMaxPPS() > 0) {
@ -195,14 +237,8 @@ public class UserConnection {
if (!channel.isOpen() || pendingDisconnect) return;
pendingDisconnect = true;
UUID uuid = protocolInfo.getUuid();
if (uuid == null) {
channel.close(); // Just disconnect, we don't know what the connection is
return;
}
Via.getPlatform().runSync(() -> {
if (!Via.getPlatform().kickPlayer(uuid, ChatColor.translateAlternateColorCodes('&', reason))) {
if (!Via.getPlatform().disconnect(this, ChatColor.translateAlternateColorCodes('&', reason))) {
channel.close(); // =)
}
});
@ -214,9 +250,20 @@ public class UserConnection {
* @param packet Raw packet to be sent
* @param currentThread If {@code true} executes immediately, {@code false} submits a task to EventLoop
*/
public void sendRawPacketToServer(ByteBuf packet, boolean currentThread) {
ByteBuf buf = packet.alloc().buffer();
public void sendRawPacketToServer(final ByteBuf packet, boolean currentThread) {
if (clientSide) {
sendRawPacketToServerClientSide(packet, currentThread);
} else {
sendRawPacketToServerServerSide(packet, currentThread);
}
}
private void sendRawPacketToServerServerSide(final ByteBuf packet, boolean currentThread) {
final ByteBuf buf = packet.alloc().buffer();
try {
// We'll use passing through because there are some encoder wrappers
ChannelHandlerContext context = PipelineUtil
.getPreviousContext(Via.getManager().getInjector().getDecoderName(), channel.pipeline());
try {
Type.VAR_INT.writePrimitive(buf, PacketWrapper.PASSTHROUGH_ID);
} catch (Exception e) {
@ -224,23 +271,18 @@ public class UserConnection {
Via.getPlatform().getLogger().warning("Type.VAR_INT.write thrown an exception: " + e);
}
buf.writeBytes(packet);
ChannelHandlerContext context = PipelineUtil
.getPreviousContext(Via.getManager().getInjector().getDecoderName(), channel.pipeline());
if (currentThread) {
Runnable act = () -> {
if (context != null) {
context.fireChannelRead(buf);
} else {
channel.pipeline().fireChannelRead(buf);
}
};
if (currentThread) {
act.run();
} else {
try {
channel.eventLoop().submit(() -> {
if (context != null) {
context.fireChannelRead(buf);
} else {
channel.pipeline().fireChannelRead(buf);
}
});
channel.eventLoop().submit(act);
} catch (Throwable t) {
// Couldn't schedule
buf.release();
@ -252,6 +294,21 @@ public class UserConnection {
}
}
private void sendRawPacketToServerClientSide(final ByteBuf packet, boolean currentThread) {
Runnable act = () -> getChannel().pipeline()
.context(Via.getManager().getInjector().getEncoderName()).writeAndFlush(packet);
if (currentThread) {
act.run();
} else {
try {
getChannel().eventLoop().submit(act);
} catch (Throwable e) {
e.printStackTrace();
packet.release(); // Couldn't schedule
}
}
}
/**
* Sends a raw packet to the server. It will submit a task to EventLoop.
*
@ -262,11 +319,24 @@ public class UserConnection {
}
/**
* Monitors serverbound packets.
* Monitors incoming packets
*
* @return false if this packet should be cancelled
*/
public boolean checkIncomingPacket() {
if (clientSide) {
return checkClientBound();
} else {
return checkServerBound();
}
}
private boolean checkClientBound() {
incrementSent();
return true;
}
private boolean checkServerBound() {
// Ignore if pending disconnect
if (pendingDisconnect) return false;
// Increment received + Check PPS
@ -274,10 +344,16 @@ public class UserConnection {
}
/**
* Monitors clientbound packets.
* Monitors outgoing packets
*
* @return false if this packet should be cancelled
*/
public void checkOutgoingPacket() {
incrementSent();
public boolean checkOutgoingPacket() {
if (clientSide) {
return checkServerBound();
} else {
return checkClientBound();
}
}
/**
@ -290,7 +366,8 @@ public class UserConnection {
}
/**
* Transforms the clientbound packet contained in an outgoing ByteBuf.
* Transforms the outgoing packet contained in ByteBuf. When clientSide is true, this packet is considered
* serverbound.
*
* @param buf ByteBuf with packet id and packet contents
* @param cancelSupplier Function called with original CancelException for generating the Exception used when
@ -301,11 +378,12 @@ public class UserConnection {
*/
public void transformOutgoing(ByteBuf buf, Function<Throwable, Exception> cancelSupplier) throws Exception {
if (!buf.isReadable()) return;
transform(buf, Direction.OUTGOING, cancelSupplier);
transform(buf, clientSide ? Direction.INCOMING : Direction.OUTGOING, cancelSupplier);
}
/**
* Transforms the serverbound packet contained in an incoming ByteBuf.
* Transforms the incoming packet contained in ByteBuf. When clientSide is true, this packet is considered
* clientbound
*
* @param buf ByteBuf with packet id and packet contents
* @param cancelSupplier Function called with original CancelException for generating the Exception used when
@ -316,7 +394,7 @@ public class UserConnection {
*/
public void transformIncoming(ByteBuf buf, Function<Throwable, Exception> cancelSupplier) throws Exception {
if (!buf.isReadable()) return;
transform(buf, Direction.INCOMING, cancelSupplier);
transform(buf, clientSide ? Direction.OUTGOING : Direction.INCOMING, cancelSupplier);
}
private void transform(ByteBuf buf, Direction direction, Function<Throwable, Exception> cancelSupplier) throws Exception {
@ -459,4 +537,12 @@ public class UserConnection {
public int hashCode() {
return Long.hashCode(id);
}
public boolean isClientSide() {
return clientSide;
}
public boolean shouldApplyBlockProtocol() {
return !clientSide; // Don't apply protocol blocking on client-side
}
}

View File

@ -1,10 +1,13 @@
package us.myles.ViaVersion.api.platform;
import com.google.gson.JsonObject;
import us.myles.ViaVersion.api.Via;
import us.myles.ViaVersion.api.ViaAPI;
import us.myles.ViaVersion.api.ViaVersionConfig;
import us.myles.ViaVersion.api.command.ViaCommandSender;
import us.myles.ViaVersion.api.configuration.ConfigurationProvider;
import us.myles.ViaVersion.api.data.UserConnection;
import us.myles.ViaVersion.protocols.base.ProtocolInfo;
import java.io.File;
import java.util.UUID;
@ -121,6 +124,20 @@ public interface ViaPlatform<T> {
*/
boolean kickPlayer(UUID uuid, String message);
/**
* Disconnects an UserConnection for a reason
*
* @param connection The UserConnection
* @param message The message to kick them with
* @return True if it was successful
*/
default boolean disconnect(UserConnection connection, String message) {
if (connection.isClientSide()) return false;
UUID uuid = connection.get(ProtocolInfo.class).getUuid();
if (uuid == null) return false;
return kickPlayer(uuid, message);
}
/**
* Check if the plugin is enabled.
*

View File

@ -165,6 +165,7 @@ public class BaseProtocol1_7 extends SimpleProtocol {
int protocol = wrapper.user().getProtocolInfo().getProtocolVersion();
if (Via.getConfig().getBlockedProtocols().contains(protocol)) {
if (!wrapper.user().getChannel().isOpen()) return;
if (!wrapper.user().shouldApplyBlockProtocol()) return;
PacketWrapper disconnectPacket = new PacketWrapper(0x00, null, wrapper.user()); // Disconnect Packet
Protocol1_9To1_8.FIX_JSON.write(disconnectPacket, ChatColor.translateAlternateColorCodes('&', Via.getConfig().getBlockedDisconnectMsg()));

View File

@ -42,7 +42,7 @@ public class SpongeEncodeHandler extends MessageToByteEncoder<Object> implements
@Override
public void transform(ByteBuf bytebuf) throws Exception {
info.checkOutgoingPacket();
if (!info.checkOutgoingPacket()) throw CancelEncoderException.generate(null);
if (!info.shouldTransformPacket()) return;
info.transformOutgoing(bytebuf, CancelEncoderException::generate);
}

View File

@ -73,7 +73,7 @@ public class VelocityPlugin implements ViaPlatform<Player> {
api = new VelocityViaAPI();
conf = new VelocityViaConfig(configDir.toFile());
logger = new LoggerWrapper(loggerslf4j);
connectionManager = new ViaConnectionManager();
connectionManager = new VelocityConnectionManager();
Via.init(ViaManager.builder()
.platform(this)
.commandHandler(commandHandler)

View File

@ -7,12 +7,14 @@ import us.myles.ViaVersion.api.protocol.ProtocolPipeline;
import java.lang.reflect.Method;
public class VelocityChannelInitializer extends ChannelInitializer {
private final ChannelInitializer original;
public class VelocityChannelInitializer extends ChannelInitializer<Channel> {
private final ChannelInitializer<?> original;
private final boolean clientSide;
private static Method initChannel;
public VelocityChannelInitializer(ChannelInitializer original) {
public VelocityChannelInitializer(ChannelInitializer<?> original, boolean clientSide) {
this.original = original;
this.clientSide = clientSide;
}
static {
@ -28,7 +30,7 @@ public class VelocityChannelInitializer extends ChannelInitializer {
protected void initChannel(Channel channel) throws Exception {
initChannel.invoke(original, channel);
UserConnection user = new UserConnection(channel);
UserConnection user = new UserConnection(channel, clientSide);
new ProtocolPipeline(user);
// We need to add a separated handler because Velocity uses pipeline().get(MINECRAFT_DECODER)

View File

@ -3,16 +3,22 @@ package us.myles.ViaVersion.velocity.handlers;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.codec.MessageToMessageDecoder;
import us.myles.ViaVersion.api.PacketWrapper;
import us.myles.ViaVersion.api.data.UserConnection;
import us.myles.ViaVersion.api.type.Type;
import us.myles.ViaVersion.exception.CancelDecoderException;
import us.myles.ViaVersion.exception.CancelCodecException;
import us.myles.ViaVersion.util.PipelineUtil;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
@ChannelHandler.Sharable
public class VelocityDecodeHandler extends MessageToMessageDecoder<ByteBuf> {
private final UserConnection info;
boolean handledCompression;
public VelocityDecodeHandler(UserConnection info) {
this.info = info;
@ -28,13 +34,55 @@ public class VelocityDecodeHandler extends MessageToMessageDecoder<ByteBuf> {
ByteBuf transformedBuf = ctx.alloc().buffer().writeBytes(bytebuf);
try {
info.transformIncoming(transformedBuf, CancelDecoderException::generate);
out.add(transformedBuf.retain());
boolean needsCompress = handleCompressionOrder(ctx, draft);
info.transformIncoming(draft, CancelDecoderException::generate);
if (needsCompress) {
recompress(ctx, draft);
}
out.add(draft.retain());
} finally {
transformedBuf.release();
}
}
private boolean handleCompressionOrder(ChannelHandlerContext ctx, ByteBuf draft) throws InvocationTargetException {
//if (handledCompression) return false;
int decoderIndex = ctx.pipeline().names().indexOf("compression-decoder");
if (decoderIndex == -1) return false;
handledCompression = true;
if (decoderIndex > ctx.pipeline().names().indexOf("via-decoder")) {
System.out.println("bad decoder order");
// Need to decompress this packet due to bad order
ByteBuf decompressed = (ByteBuf) PipelineUtil.callDecode((MessageToMessageDecoder<?>) ctx.pipeline().get("compression-decoder"), ctx, draft).get(0);
try {
draft.clear().writeBytes(decompressed);
} finally {
decompressed.release();
}
ChannelHandler encoder = ctx.pipeline().get("via-encoder");
ChannelHandler decoder = ctx.pipeline().get("via-decoder");
ctx.pipeline().remove(encoder);
ctx.pipeline().remove(decoder);
ctx.pipeline().addAfter("compression-encoder", "via-encoder", encoder);
ctx.pipeline().addAfter("compression-decoder", "via-decoder", decoder);
return true;
}
return false;
}
private void recompress(ChannelHandlerContext ctx, ByteBuf draft) throws Exception {
ByteBuf compressed = ctx.alloc().buffer();
try {
PipelineUtil.callEncode((MessageToByteEncoder<?>) ctx.pipeline().get("compression-encoder"), ctx, draft, compressed);
draft.clear().writeBytes(compressed);
} finally {
compressed.release();
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (cause instanceof CancelCodecException) return;

View File

@ -25,7 +25,7 @@ public class VelocityEncodeHandler extends MessageToMessageEncoder<ByteBuf> {
@Override
protected void encode(final ChannelHandlerContext ctx, ByteBuf bytebuf, List<Object> out) throws Exception {
info.checkOutgoingPacket();
if (!info.checkOutgoingPacket()) throw CancelEncoderException.generate(null);
if (!info.shouldTransformPacket()) {
out.add(bytebuf.retain());
return;
@ -46,10 +46,14 @@ public class VelocityEncodeHandler extends MessageToMessageEncoder<ByteBuf> {
}
}
private boolean handleCompressionOrder(ChannelHandlerContext ctx, ByteBuf buf) throws InvocationTargetException {
boolean needsCompress = false;
if (!handledCompression
&& ctx.pipeline().names().indexOf("compression-encoder") > ctx.pipeline().names().indexOf("via-encoder")) {
private boolean handleCompressionOrder(ChannelHandlerContext ctx, ByteBuf draft) throws InvocationTargetException {
//if (handledCompression) return false;
int encoderIndex = ctx.pipeline().names().indexOf("compression-encoder");
if (encoderIndex == -1) return false;
handledCompression = true;
if (encoderIndex > ctx.pipeline().names().indexOf("via-encoder")) {
System.out.println("bad decoder order");
// Need to decompress this packet due to bad order
ByteBuf decompressed = (ByteBuf) PipelineUtil.callDecode((MessageToMessageDecoder<?>) ctx.pipeline().get("compression-decoder"), ctx, buf).get(0);
try {
@ -63,10 +67,9 @@ public class VelocityEncodeHandler extends MessageToMessageEncoder<ByteBuf> {
ctx.pipeline().remove(decoder);
ctx.pipeline().addAfter("compression-encoder", "via-encoder", encoder);
ctx.pipeline().addAfter("compression-decoder", "via-decoder", decoder);
needsCompress = true;
handledCompression = true;
return true;
}
return needsCompress;
return false;
}
private void recompress(ChannelHandlerContext ctx, ByteBuf buf) throws InvocationTargetException {

View File

@ -0,0 +1,33 @@
package us.myles.ViaVersion.velocity.platform;
import io.netty.channel.ServerChannel;
import us.myles.ViaVersion.api.data.UserConnection;
import us.myles.ViaVersion.api.platform.ViaConnectionManager;
import java.util.Objects;
public class VelocityConnectionManager extends ViaConnectionManager {
@Override
public void onLoginSuccess(UserConnection connection) {
if (connection.isClientSide()) {
System.out.println("backend connect!");
Objects.requireNonNull(connection, "connection is null!");
connections.add(connection);
} else {
System.out.println("frontend connect!");
super.onLoginSuccess(connection);
}
}
@Override
public void onDisconnect(UserConnection connection) {
if (connection.isClientSide()) {
System.out.println("backend disconnect!");
Objects.requireNonNull(connection, "connection is null!");
connections.remove(connection);
} else {
System.out.println("frontend disconnect!");
super.onDisconnect(connection);
}
}
}

View File

@ -30,13 +30,25 @@ public class VelocityViaInjector implements ViaInjector {
return (ChannelInitializer) ReflectionUtil.invoke(channelInitializerHolder, "get");
}
private ChannelInitializer getBackendInitializer() throws Exception {
Object connectionManager = ReflectionUtil.get(VelocityPlugin.PROXY, "cm", Object.class);
Object channelInitializerHolder = ReflectionUtil.invoke(connectionManager, "getBackendChannelInitializer");
return (ChannelInitializer) ReflectionUtil.invoke(channelInitializerHolder, "get");
}
@Override
public void inject() throws Exception {
Object connectionManager = ReflectionUtil.get(VelocityPlugin.PROXY, "cm", Object.class);
Object channelInitializerHolder = ReflectionUtil.invoke(connectionManager, "getServerChannelInitializer");
ChannelInitializer originalInitializer = getInitializer();
channelInitializerHolder.getClass().getMethod("set", ChannelInitializer.class)
.invoke(channelInitializerHolder, new VelocityChannelInitializer(originalInitializer));
.invoke(channelInitializerHolder, new VelocityChannelInitializer(originalInitializer, false));
Object backendInitializerHolder = ReflectionUtil.invoke(connectionManager, "getBackendChannelInitializer");
ChannelInitializer backendInitializer = getBackendInitializer();
backendInitializerHolder.getClass().getMethod("set", ChannelInitializer.class)
.invoke(backendInitializerHolder, new VelocityChannelInitializer(backendInitializer, true));
}
@Override

View File

@ -26,7 +26,7 @@ public class VelocityViaLoader implements ViaPlatformLoader {
if (ProtocolRegistry.SERVER_PROTOCOL < ProtocolVersion.v1_9.getVersion()) {
Via.getManager().getProviders().use(MovementTransmitterProvider.class, new VelocityMovementTransmitter());
Via.getManager().getProviders().use(BossBarProvider.class, new VelocityBossBarProvider());
VelocityPlugin.PROXY.getEventManager().register(plugin, new ElytraPatch());
//VelocityPlugin.PROXY.getEventManager().register(plugin, new ElytraPatch());
}
Via.getManager().getProviders().use(VersionProvider.class, new VelocityVersionProvider());
@ -34,7 +34,7 @@ public class VelocityViaLoader implements ViaPlatformLoader {
// We don't need main hand patch because Join Game packet makes client send hand data again
VelocityPlugin.PROXY.getEventManager().register(plugin, new UpdateListener());
VelocityPlugin.PROXY.getEventManager().register(plugin, new VelocityServerHandler());
//VelocityPlugin.PROXY.getEventManager().register(plugin, new VelocityServerHandler());
int pingInterval = ((VelocityViaConfig) Via.getPlatform().getConf()).getVelocityPingInterval();
if (pingInterval > 0) {

View File

@ -1,20 +1,46 @@
package us.myles.ViaVersion.velocity.providers;
import com.velocitypowered.api.proxy.ServerConnection;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ServerChannel;
import us.myles.ViaVersion.VelocityPlugin;
import us.myles.ViaVersion.api.Via;
import us.myles.ViaVersion.api.data.UserConnection;
import us.myles.ViaVersion.api.protocol.ProtocolVersion;
import us.myles.ViaVersion.protocols.base.VersionProvider;
import us.myles.ViaVersion.velocity.platform.VelocityViaInjector;
import us.myles.ViaVersion.velocity.service.ProtocolDetectorService;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.stream.IntStream;
public class VelocityVersionProvider extends VersionProvider {
private static Method getAssociation;
static {
try {
getAssociation = Class.forName("com.velocitypowered.proxy.connection.MinecraftConnection").getMethod("getAssociation");
} catch (NoSuchMethodException | ClassNotFoundException e) {
e.printStackTrace();
}
}
@Override
public int getServerProtocol(UserConnection user) throws Exception {
int playerVersion = user.getProtocolInfo().getProtocolVersion();
return user.isClientSide() ? getBackProtocol(user) : getFrontProtocol(user);
}
private int getBackProtocol(UserConnection user) throws Exception {
System.out.println("backend protocol!");
ChannelHandler mcHandler = user.getChannel().pipeline().get("handler");
return ProtocolDetectorService.getProtocolId(
((ServerConnection) getAssociation.invoke(mcHandler)).getServerInfo().getName());
}
private int getFrontProtocol(UserConnection user) throws Exception {
System.out.println("frontend protocol!");
int playerVersion = user.get(ProtocolInfo.class).getProtocolVersion();
IntStream versions = com.velocitypowered.api.network.ProtocolVersion.SUPPORTED_VERSIONS.stream()
.mapToInt(com.velocitypowered.api.network.ProtocolVersion::getProtocol);