Added support for EaglerCraft clients

This commit is contained in:
RaphiMC 2023-04-07 19:15:43 +02:00
parent 8da007c0a4
commit 4494920acd
9 changed files with 415 additions and 7 deletions

View File

@ -16,6 +16,8 @@ For a full user guide go to the [Usage for Players](#usage-for-players-gui) sect
## Supported Client versions
- Release (1.7.2 - 1.19.4)
- Classic, Alpha, Beta, Release 1.0 - 1.6.4 (Only passthrough)
- Eaglercraft 1.8
ViaProxy supports joining to any of the listed server version from any of the listed client versions.

View File

@ -59,6 +59,7 @@ public class Options {
public static String RESOURCE_PACK_URL; // Example: http://example.com/resourcepack.zip
public static boolean SERVER_HAPROXY_PROTOCOL;
public static boolean LEGACY_CLIENT_PASSTHROUGH;
public static boolean ALLOW_EAGLERCRAFT_CLIENTS;
public static void parse(final String[] args) throws IOException {
final OptionParser parser = new OptionParser();
@ -79,7 +80,8 @@ public class Options {
final OptionSpec<String> resourcePackUrl = parser.acceptsAll(asList("resource_pack_url", "resource_pack", "rpu", "rp"), "URL of a resource pack which clients can optionally download").withRequiredArg().ofType(String.class);
final OptionSpec<String> proxyUrl = parser.acceptsAll(asList("proxy_url", "proxy"), "URL of a SOCKS(4/5)/HTTP(S) proxy which will be used for backend TCP connections").withRequiredArg().ofType(String.class);
final OptionSpec<Void> serverHaProxyProtocol = parser.acceptsAll(asList("server-haproxy-protocol", "server-haproxy"), "Send HAProxy protocol messages to the backend server");
final OptionSpec<Void> legacyClientPassthrough = parser.acceptsAll(asList("legacy_client_passthrough", "legacy_passthrough"), "Allow <= 1.6.4 clients to connect to the backend server");
final OptionSpec<Void> legacyClientPassthrough = parser.acceptsAll(asList("legacy_client_passthrough", "legacy_passthrough"), "Allow <= 1.6.4 clients to connect to the backend server (No protocol translation)");
final OptionSpec<Void> allowEaglerCraftClients = parser.acceptsAll(asList("allow_eaglercraft_clients", "allow_eaglercraft"), "Allow EaglerCraft clients to connect to ViaProxy");
PluginManager.EVENT_MANAGER.call(new PreOptionsParseEvent(parser));
final OptionSet options = parser.parse(args);
@ -119,6 +121,7 @@ public class Options {
}
SERVER_HAPROXY_PROTOCOL = options.has(serverHaProxyProtocol);
LEGACY_CLIENT_PASSTHROUGH = options.has(legacyClientPassthrough);
ALLOW_EAGLERCRAFT_CLIENTS = options.has(allowEaglerCraftClients);
PluginManager.EVENT_MANAGER.call(new PostOptionsParseEvent(options));
}

View File

@ -26,11 +26,24 @@ import net.raphimc.viaproxy.cli.options.Options;
import net.raphimc.viaproxy.plugins.PluginManager;
import net.raphimc.viaproxy.plugins.events.Client2ProxyChannelInitializeEvent;
import net.raphimc.viaproxy.plugins.events.types.ITyped;
import net.raphimc.viaproxy.proxy.client2proxy.eaglercraft.EaglercraftInitialHandler;
import net.raphimc.viaproxy.proxy.client2proxy.passthrough.LegacyClientPassthroughHandler;
import java.util.function.Supplier;
public class Client2ProxyChannelInitializer extends MinecraftChannelInitializer {
public static final String EAGLERCRAFT_INITIAL_HANDLER_NAME = "eaglercraft-initial-handler";
public static final String WEBSOCKET_SSL_HANDLER_NAME = "ws-ssl-handler";
public static final String WEBSOCKET_HTTP_CODEC_NAME = "ws-http-codec";
public static final String WEBSOCKET_HTTP_AGGREGATOR_NAME = "ws-http-aggregator";
public static final String WEBSOCKET_COMPRESSION_NAME = "ws-compression";
public static final String WEBSOCKET_HANDLER_NAME = "ws-handler";
public static final String WEBSOCKET_ACTIVE_NOTIFIER_NAME = "ws-active-notifier";
public static final String EAGLERCRAFT_HANDLER_NAME = "eaglercraft-handler";
public static final String LEGACY_PASSTHROUGH_HANDLER_NAME = "legacy-passthrough-handler";
public Client2ProxyChannelInitializer(final Supplier<ChannelHandler> handlerSupplier) {
super(handlerSupplier);
}
@ -42,9 +55,13 @@ public class Client2ProxyChannelInitializer extends MinecraftChannelInitializer
return;
}
if (Options.LEGACY_CLIENT_PASSTHROUGH) {
channel.pipeline().addLast("legacy-passthrough-handler", new LegacyClientPassthroughHandler());
if (Options.ALLOW_EAGLERCRAFT_CLIENTS) {
channel.pipeline().addLast(EAGLERCRAFT_INITIAL_HANDLER_NAME, new EaglercraftInitialHandler());
}
if (Options.LEGACY_CLIENT_PASSTHROUGH) {
channel.pipeline().addLast(LEGACY_PASSTHROUGH_HANDLER_NAME, new LegacyClientPassthroughHandler());
}
super.initChannel(channel);
channel.attr(MCPipeline.PACKET_REGISTRY_ATTRIBUTE_KEY).set(PacketRegistryUtil.getHandshakeRegistry(false));

View File

@ -0,0 +1,266 @@
/*
* This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy
* Copyright (C) 2023 RK_01/RaphiMC and 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 <http://www.gnu.org/licenses/>.
*/
package net.raphimc.viaproxy.proxy.client2proxy.eaglercraft;
import com.google.common.net.HostAndPort;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import net.raphimc.netminecraft.constants.ConnectionState;
import net.raphimc.netminecraft.constants.MCPackets;
import net.raphimc.netminecraft.constants.MCPipeline;
import net.raphimc.netminecraft.packet.PacketTypes;
import net.raphimc.viaproxy.ViaProxy;
import net.raphimc.viaproxy.util.logging.Logger;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
// Thanks to ayunami2000 for helping with the eaglercraft protocol
public class EaglercraftHandler extends MessageToMessageCodec<WebSocketFrame, ByteBuf> {
private static final int CLIENT_VERSION = 0x01;
private static final int SERVER_VERSION = 0x02;
private static final int VERSION_MISMATCH = 0x03;
private static final int CLIENT_REQUEST_LOGIN = 0x04;
private static final int SERVER_ALLOW_LOGIN = 0x05;
private static final int SERVER_DENY_LOGIN = 0x06;
private static final int CLIENT_PROFILE_DATA = 0x07;
private static final int CLIENT_FINISH_LOGIN = 0x08;
private static final int SERVER_FINISH_LOGIN = 0x09;
private static final int SERVER_ERROR = 0xFF;
private HostAndPort host;
private State state = State.PRE_HANDSHAKE;
private int protocolVersion;
private String username;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.attr(MCPipeline.COMPRESSION_THRESHOLD_ATTRIBUTE_KEY).set(-2); // Disable automatic compression in Proxy2ServerHandler
ctx.pipeline().remove(MCPipeline.SIZER_HANDLER_NAME);
super.channelActive(ctx);
}
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
if (this.state != State.LOGIN_COMPLETE) {
throw new IllegalStateException("Cannot send packets before login is completed");
}
out.add(new BinaryWebSocketFrame(in.retain()));
}
@Override
protected void decode(ChannelHandlerContext ctx, WebSocketFrame in, List<Object> out) {
if (in instanceof BinaryWebSocketFrame) {
final ByteBuf data = in.content();
switch (this.state) {
case PRE_HANDSHAKE:
if (data.readableBytes() >= 2) { // Check for legacy client
if (data.getByte(0) == (byte) 2 && data.getByte(1) == (byte) 69) {
throw new IllegalStateException("Your client is not yet supported");
}
}
this.state = State.HANDSHAKE;
case HANDSHAKE:
int packetId = data.readUnsignedByte(); // packet id
if (packetId == CLIENT_VERSION) {
int eaglercraftVersion = data.readUnsignedByte(); // eaglercraft version
final int minecraftVersion;
if (eaglercraftVersion == 1) {
minecraftVersion = data.readUnsignedByte(); // minecraft version
} else if (eaglercraftVersion == 2) {
int count = data.readUnsignedShort(); // eaglercraft versions
final List<Integer> eaglercraftVersions = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
eaglercraftVersions.add(data.readUnsignedShort()); // eaglercraft version id
}
if (!eaglercraftVersions.contains(2) && !eaglercraftVersions.contains(3)) {
Logger.LOGGER.error("No supported eaglercraft versions found");
ctx.close();
return;
}
if (eaglercraftVersions.contains(3)) {
eaglercraftVersion = 3;
}
count = data.readUnsignedShort(); // minecraft versions
final List<Integer> minecraftVersions = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
minecraftVersions.add(data.readUnsignedShort()); // minecraft version id
}
if (minecraftVersions.size() != 1) {
Logger.LOGGER.error("No supported minecraft versions found");
ctx.close();
}
minecraftVersion = minecraftVersions.get(0);
} else {
throw new IllegalArgumentException("Unknown Eaglercraft version: " + eaglercraftVersion);
}
final String clientBrand = data.readCharSequence(data.readUnsignedByte(), StandardCharsets.US_ASCII).toString(); // client brand
final String clientVersionString = data.readCharSequence(data.readUnsignedByte(), StandardCharsets.US_ASCII).toString(); // client version
if (eaglercraftVersion >= 2) {
data.skipBytes(1); // auth enabled
data.skipBytes(data.readUnsignedByte()); // auth username
}
if (data.isReadable()) {
throw new IllegalArgumentException("Too much data in packet: " + data.readableBytes() + " bytes");
}
this.state = State.HANDSHAKE_COMPLETE;
this.protocolVersion = minecraftVersion;
Logger.LOGGER.info("Eaglercraft client connected: " + clientBrand + " " + clientVersionString);
final String serverBrand = "ViaProxy";
final String serverVersionString = ViaProxy.VERSION;
final ByteBuf response = ctx.alloc().buffer();
response.writeByte(SERVER_VERSION); // packet id
if (eaglercraftVersion == 1) {
response.writeByte(1); // eaglercraft version
} else {
response.writeShort(eaglercraftVersion); // eaglercraft version
response.writeShort(minecraftVersion); // minecraft version
}
response.writeByte(serverBrand.length()).writeCharSequence(serverBrand, StandardCharsets.US_ASCII); // server brand
response.writeByte(serverVersionString.length()).writeCharSequence(serverVersionString, StandardCharsets.US_ASCII); // server version
response.writeByte(0); // auth method
response.writeShort(0); // salt length
ctx.writeAndFlush(new BinaryWebSocketFrame(response));
} else {
throw new IllegalStateException("Unexpected packet id " + packetId + " in state " + this.state);
}
break;
case HANDSHAKE_COMPLETE:
packetId = data.readUnsignedByte(); // packet id
if (packetId == CLIENT_REQUEST_LOGIN) {
final String username = data.readCharSequence(data.readUnsignedByte(), StandardCharsets.US_ASCII).toString(); // username
data.readCharSequence(data.readUnsignedByte(), StandardCharsets.US_ASCII).toString(); // requested server
data.skipBytes(data.readUnsignedByte()); // auth password
if (data.isReadable()) {
throw new IllegalArgumentException("Too much data in packet: " + data.readableBytes() + " bytes");
}
this.state = State.LOGIN;
this.username = username;
final UUID uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8));
final ByteBuf response = ctx.alloc().buffer();
response.writeByte(SERVER_ALLOW_LOGIN); // packet id
response.writeByte(username.length()).writeCharSequence(username, StandardCharsets.US_ASCII); // username
response.writeLong(uuid.getMostSignificantBits()).writeLong(uuid.getLeastSignificantBits()); // uuid
ctx.writeAndFlush(new BinaryWebSocketFrame(response));
} else {
throw new IllegalStateException("Unexpected packet id " + packetId + " in state " + this.state);
}
break;
case LOGIN:
packetId = data.readUnsignedByte(); // packet id
if (packetId == CLIENT_PROFILE_DATA) {
final String type = data.readCharSequence(data.readUnsignedByte(), StandardCharsets.US_ASCII).toString();
final byte[] dataBytes = new byte[data.readUnsignedShort()];
data.readBytes(dataBytes);
if (data.isReadable()) {
throw new IllegalArgumentException("Too much data in packet: " + data.readableBytes() + " bytes");
}
} else if (packetId == CLIENT_FINISH_LOGIN) {
if (data.isReadable()) {
throw new IllegalArgumentException("Too much data in packet: " + data.readableBytes() + " bytes");
}
this.state = State.LOGIN_COMPLETE;
final int handshakeId = MCPackets.C2S_HANDSHAKE.getId(this.protocolVersion);
final int loginHelloId = MCPackets.C2S_LOGIN_HELLO.getId(this.protocolVersion);
if (handshakeId == -1 || loginHelloId == -1) {
Logger.LOGGER.error("Unsupported protocol version: " + this.protocolVersion);
ctx.close();
return;
}
final ByteBuf handshake = ctx.alloc().buffer();
PacketTypes.writeVarInt(handshake, handshakeId); // packet id
PacketTypes.writeVarInt(handshake, this.protocolVersion); // protocol version
PacketTypes.writeString(handshake, this.host.getHost()); // address
handshake.writeShort(this.host.getPort()); // port
PacketTypes.writeVarInt(handshake, ConnectionState.LOGIN.getId()); // next state
out.add(handshake);
final ByteBuf loginHello = ctx.alloc().buffer();
PacketTypes.writeVarInt(loginHello, loginHelloId); // packet id
PacketTypes.writeString(loginHello, this.username); // username
out.add(loginHello);
final ByteBuf response = ctx.alloc().buffer();
response.writeByte(SERVER_FINISH_LOGIN); // packet id
ctx.writeAndFlush(new BinaryWebSocketFrame(response));
} else {
throw new IllegalStateException("Unexpected packet id " + packetId + " in state " + this.state);
}
break;
case LOGIN_COMPLETE:
out.add(data.retain());
break;
default:
throw new IllegalStateException("Unexpected binary frame in state " + this.state);
}
} else if (in instanceof TextWebSocketFrame) {
final String text = ((TextWebSocketFrame) in).text();
ctx.close();
if (this.state != State.PRE_HANDSHAKE) {
throw new IllegalStateException("Unexpected text frame in state " + this.state);
}
} else {
throw new UnsupportedOperationException("Unsupported frame type: " + in.getClass().getName());
}
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
final WebSocketServerProtocolHandler.HandshakeComplete handshake = (WebSocketServerProtocolHandler.HandshakeComplete) evt;
if (!handshake.requestHeaders().contains("Host")) {
ctx.close();
return;
}
this.host = HostAndPort.fromString(handshake.requestHeaders().get("Host")).withDefaultPort(80);
}
super.userEventTriggered(ctx, evt);
}
public enum State {
PRE_HANDSHAKE,
HANDSHAKE,
HANDSHAKE_COMPLETE,
LOGIN,
LOGIN_COMPLETE,
}
}

View File

@ -0,0 +1,77 @@
/*
* This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy
* Copyright (C) 2023 RK_01/RaphiMC and 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 <http://www.gnu.org/licenses/>.
*/
package net.raphimc.viaproxy.proxy.client2proxy.eaglercraft;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import net.raphimc.viaproxy.proxy.client2proxy.Client2ProxyChannelInitializer;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class EaglercraftInitialHandler extends ByteToMessageDecoder {
private static SslContext sslContext;
static {
final File certFolder = new File("certs");
if (certFolder.exists()) {
try {
sslContext = SslContextBuilder.forServer(new File(certFolder, "fullchain.pem"), new File(certFolder, "privkey.pem")).build();
} catch (Throwable e) {
throw new RuntimeException("Failed to load SSL context", e);
}
}
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
if (!ctx.channel().isOpen()) return;
if (!in.isReadable()) return;
if (in.readableBytes() >= 3) {
final byte[] data = new byte[3];
in.getBytes(0, data);
final String method = new String(data, StandardCharsets.UTF_8);
if (method.equals("GET")) { // Websocket request
if (sslContext != null) {
ctx.pipeline().addBefore(Client2ProxyChannelInitializer.EAGLERCRAFT_INITIAL_HANDLER_NAME, Client2ProxyChannelInitializer.WEBSOCKET_SSL_HANDLER_NAME, sslContext.newHandler(ctx.alloc()));
}
ctx.pipeline().addBefore(Client2ProxyChannelInitializer.EAGLERCRAFT_INITIAL_HANDLER_NAME, Client2ProxyChannelInitializer.WEBSOCKET_HTTP_CODEC_NAME, new HttpServerCodec());
ctx.pipeline().addBefore(Client2ProxyChannelInitializer.EAGLERCRAFT_INITIAL_HANDLER_NAME, Client2ProxyChannelInitializer.WEBSOCKET_HTTP_AGGREGATOR_NAME, new HttpObjectAggregator(65535, true));
ctx.pipeline().addBefore(Client2ProxyChannelInitializer.EAGLERCRAFT_INITIAL_HANDLER_NAME, Client2ProxyChannelInitializer.WEBSOCKET_COMPRESSION_NAME, new WebSocketServerCompressionHandler());
ctx.pipeline().addBefore(Client2ProxyChannelInitializer.EAGLERCRAFT_INITIAL_HANDLER_NAME, Client2ProxyChannelInitializer.WEBSOCKET_HANDLER_NAME, new WebSocketServerProtocolHandler("/", null, true));
ctx.pipeline().addBefore(Client2ProxyChannelInitializer.EAGLERCRAFT_INITIAL_HANDLER_NAME, Client2ProxyChannelInitializer.WEBSOCKET_ACTIVE_NOTIFIER_NAME, new WebSocketActiveNotifier());
ctx.pipeline().addBefore(Client2ProxyChannelInitializer.EAGLERCRAFT_INITIAL_HANDLER_NAME, Client2ProxyChannelInitializer.EAGLERCRAFT_HANDLER_NAME, new EaglercraftHandler());
}
ctx.pipeline().remove(this);
ctx.pipeline().fireChannelRead(in.retain());
}
}
}

View File

@ -0,0 +1,40 @@
/*
* This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy
* Copyright (C) 2023 RK_01/RaphiMC and 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 <http://www.gnu.org/licenses/>.
*/
package net.raphimc.viaproxy.proxy.client2proxy.eaglercraft;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
public class WebSocketActiveNotifier extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
ctx.fireChannelActive();
ctx.pipeline().remove(this);
}
super.userEventTriggered(ctx, evt);
}
}

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.raphimc.viaproxy.proxy.client2proxy;
package net.raphimc.viaproxy.proxy.client2proxy.passthrough;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;

View File

@ -45,6 +45,9 @@ import java.util.function.Supplier;
public class Proxy2ServerChannelInitializer extends MinecraftChannelInitializer {
public static final String VIAPROXY_PROXY_HANDLER_NAME = "viaproxy-proxy-handler";
public static final String VIAPROXY_HAPROXY_ENCODER_NAME = "viaproxy-haproxy-encoder";
public Proxy2ServerChannelInitializer(final Supplier<ChannelHandler> handlerSupplier) {
super(handlerSupplier);
}
@ -62,10 +65,10 @@ public class Proxy2ServerChannelInitializer extends MinecraftChannelInitializer
proxyConnection.setUserConnection(user);
if (Options.PROXY_URL != null && !proxyConnection.getServerVersion().equals(VersionEnum.bedrockLatest)) {
channel.pipeline().addLast("viaproxy-proxy-handler", this.getProxyHandler());
channel.pipeline().addLast(VIAPROXY_PROXY_HANDLER_NAME, this.getProxyHandler());
}
if (Options.SERVER_HAPROXY_PROTOCOL) {
channel.pipeline().addLast("viaproxy-haproxy-encoder", HAProxyMessageEncoder.INSTANCE);
channel.pipeline().addLast(VIAPROXY_HAPROXY_ENCODER_NAME, HAProxyMessageEncoder.INSTANCE);
}
super.initChannel(channel);

View File

@ -137,7 +137,7 @@ public class Proxy2ServerHandler extends SimpleChannelInboundHandler<IPacket> {
private void handleLoginSuccess(final S2CLoginSuccessPacket1_7 packet) throws Exception {
if (this.proxyConnection.getClientVersion().isNewerThanOrEqualTo(VersionEnum.r1_8)) {
if (Options.COMPRESSION_THRESHOLD > -1) {
if (Options.COMPRESSION_THRESHOLD > -1 && this.proxyConnection.getC2P().attr(MCPipeline.COMPRESSION_THRESHOLD_ATTRIBUTE_KEY).get() == -1) {
this.proxyConnection.getC2P().writeAndFlush(new S2CLoginCompressionPacket(Options.COMPRESSION_THRESHOLD)).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE).await();
this.proxyConnection.getC2P().attr(MCPipeline.COMPRESSION_THRESHOLD_ATTRIBUTE_KEY).set(Options.COMPRESSION_THRESHOLD);
}