blob: 12f1baacb1efdc90a9029aee00cd9a841bd004d5 [file] [log] [blame] [raw]
package protocolsupport.protocol.pipeline.initial;
import java.text.MessageFormat;
import java.util.EnumMap;
import java.util.concurrent.TimeUnit;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.DecoderException;
import io.netty.util.concurrent.Future;
import protocolsupport.ProtocolSupport;
import protocolsupport.ProtocolSupportFileLog;
import protocolsupport.api.ProtocolSupportAPI;
import protocolsupport.api.ProtocolType;
import protocolsupport.api.ProtocolVersion;
import protocolsupport.protocol.ConnectionImpl;
import protocolsupport.protocol.codec.MiscDataCodec;
import protocolsupport.protocol.codec.StringCodec;
import protocolsupport.protocol.codec.VarNumberCodec;
import protocolsupport.protocol.pipeline.ChannelHandlers;
import protocolsupport.protocol.pipeline.IPipeLineBuilder;
import protocolsupport.protocol.pipeline.common.VarIntFrameDecoder;
import protocolsupport.protocol.pipeline.common.VarIntFrameEncoder;
import protocolsupport.utils.JavaSystemProperty;
import protocolsupport.utils.netty.Decompressor;
import protocolsupport.utils.netty.ReplayingDecoderByteBuf;
import protocolsupport.utils.netty.ReplayingDecoderByteBuf.EOFSignal;
import protocolsupport.zplatform.PlatformUtils;
import protocolsupport.zplatform.ServerPlatform;
import protocolsupport.zplatform.impl.encapsulated.EncapsulatedProtocolInfo;
import protocolsupport.zplatform.impl.encapsulated.EncapsulatedProtocolUtils;
import protocolsupportbuildprocessor.Preload;
@Preload
public class InitialPacketDecoder extends SimpleChannelInboundHandler<ByteBuf> {
protected static final int ping152delay = JavaSystemProperty.getValue("ping152delay", 100, Integer::parseInt);
protected static final int pingLegacyDelay = JavaSystemProperty.getValue("pinglegacydelay", 200, Integer::parseInt);
static {
{
String message = MessageFormat.format("Assume 1.5.2 ping delay: {0}", ping152delay);
ProtocolSupport.logInfo(message);
if (ProtocolSupportFileLog.isEnabled()) {
ProtocolSupportFileLog.logInfoMessage(message);
}
}
{
String message = MessageFormat.format("Assume legacy ping delay: {0}", pingLegacyDelay);
ProtocolSupport.logInfo(message);
if (ProtocolSupportFileLog.isEnabled()) {
ProtocolSupportFileLog.logInfoMessage(message);
}
}
}
protected static final EnumMap<ProtocolVersion, IPipeLineBuilder> pipelineBuilders = new EnumMap<>(ProtocolVersion.class);
static {
pipelineBuilders.put(ProtocolVersion.MINECRAFT_FUTURE, new protocolsupport.protocol.pipeline.version.v_f.PipeLineBuilder());
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_17, new protocolsupport.protocol.pipeline.version.v_1_17.PipeLineBuilder());
IPipeLineBuilder builder16r2 = new protocolsupport.protocol.pipeline.version.v_1_16.r2.PipeLineBuilder();
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_16_4, builder16r2);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_16_3, builder16r2);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_16_2, builder16r2);
IPipeLineBuilder builder16r1 = new protocolsupport.protocol.pipeline.version.v_1_16.r1.PipeLineBuilder();
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_16_1, builder16r1);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_16, builder16r1);
IPipeLineBuilder builder15 = new protocolsupport.protocol.pipeline.version.v_1_15.PipeLineBuilder();
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_15_2, builder15);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_15_1, builder15);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_15, builder15);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_14_4, new protocolsupport.protocol.pipeline.version.v_1_14.r2.PipeLineBuilder());
IPipeLineBuilder builder14r1 = new protocolsupport.protocol.pipeline.version.v_1_14.r1.PipeLineBuilder();
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_14_3, builder14r1);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_14_2, builder14r1);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_14_1, builder14r1);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_14, builder14r1);
IPipeLineBuilder builder13 = new protocolsupport.protocol.pipeline.version.v_1_13.PipeLineBuilder();
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_13_2, builder13);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_13_1, builder13);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_13, builder13);
IPipeLineBuilder builder12 = new protocolsupport.protocol.pipeline.version.v_1_12.r2.PipeLineBuilder();
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_12_2, builder12);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_12_1, builder12);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_12, new protocolsupport.protocol.pipeline.version.v_1_12.r1.PipeLineBuilder());
IPipeLineBuilder builder11 = new protocolsupport.protocol.pipeline.version.v_1_11.PipeLineBuilder();
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_11_1, builder11);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_11, builder11);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_10, new protocolsupport.protocol.pipeline.version.v_1_10.PipeLineBuilder());
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_9_4, new protocolsupport.protocol.pipeline.version.v_1_9.r2.PipeLineBuilder());
IPipeLineBuilder builder9r1 = new protocolsupport.protocol.pipeline.version.v_1_9.r1.PipeLineBuilder();
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_9_2, builder9r1);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_9_1, builder9r1);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_9, builder9r1);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_8, new protocolsupport.protocol.pipeline.version.v_1_8.PipeLineBuilder());
IPipeLineBuilder builder7 = new protocolsupport.protocol.pipeline.version.v_1_7.PipeLineBuilder();
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_7_10, builder7);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_7_5, builder7);
IPipeLineBuilder builder6 = new protocolsupport.protocol.pipeline.version.v_1_6.PipeLineBuilder();
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_6_4, builder6);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_6_2, builder6);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_6_1, builder6);
IPipeLineBuilder builder5 = new protocolsupport.protocol.pipeline.version.v_1_5.PipeLineBuilder();
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_5_2, builder5);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_5_1, builder5);
pipelineBuilders.put(ProtocolVersion.MINECRAFT_1_4_7, new protocolsupport.protocol.pipeline.version.v_1_4.PipeLineBuilder());
pipelineBuilders.put(ProtocolVersion.MINECRAFT_LEGACY, new protocolsupport.protocol.pipeline.version.v_l.PipeLineBuilder());
}
protected final ReplayingDecoderByteBuf buffer = new ReplayingDecoderByteBuf(Unpooled.buffer());
protected Future<?> responseTask;
protected void scheduleTask(ChannelHandlerContext ctx, Runnable task, long delay, TimeUnit tu) {
responseTask = ctx.executor().schedule(task, delay, tu);
}
protected void cancelTask() {
if (responseTask != null) {
responseTask.cancel(true);
responseTask = null;
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
cancelTask();
super.channelInactive(ctx);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
cancelTask();
super.handlerRemoved(ctx);
}
@Override
public void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Exception {
if (!buf.isReadable()) {
return;
}
buffer.unwrap().writeBytes(buf);
buffer.readerIndex(0);
decode(ctx);
}
protected boolean firstread = true;
protected EncapsulatedProtocolInfo encapsulatedinfo = null;
protected void decode(ChannelHandlerContext ctx) throws Exception {
cancelTask();
if (firstread) {
int firstbyte = buffer.readUnsignedByte();
if (firstbyte == 0) {
encapsulatedinfo = EncapsulatedProtocolUtils.readInfo(buffer);
buffer.discardReadBytes();
}
buffer.readerIndex(0);
firstread = false;
}
try {
if (encapsulatedinfo == null) {
decodeRaw(ctx);
} else {
decodeEncapsulated(ctx);
}
} catch (EOFSignal ex) {
}
}
protected void decodeRaw(ChannelHandlerContext ctx) {
Channel channel = ctx.channel();
int firstbyte = buffer.readUnsignedByte();
switch (firstbyte) {
case 0xFE: { //old ping or a part of varint length
if (buffer.readableBytes() == 0) {
//no more data received, it may be old protocol, or we just not received all data yet, so delay assuming as really old protocol for some time
scheduleTask(ctx, new SetProtocolTask(this, channel, ProtocolVersion.MINECRAFT_LEGACY), pingLegacyDelay, TimeUnit.MILLISECONDS);
} else if (buffer.readUnsignedByte() == 1) {
//1.5-1.6 ping or maybe a finishing byte for 1.7+ packet length
if (buffer.readableBytes() == 0) {
//no more data received, it may be 1.5.2 or we just didn't receive 1.6 or 1.7+ data yet, so delay assuming as 1.5.2 for some time
scheduleTask(ctx, new SetProtocolTask(this, channel, ProtocolVersion.MINECRAFT_1_5_2), ping152delay, TimeUnit.MILLISECONDS);
} else if (
(buffer.readUnsignedByte() == 0xFA) &&
"MC|PingHost".equals(StringCodec.readShortUTF16BEString(buffer, Short.MAX_VALUE))
) {
//definitely 1.6
buffer.readUnsignedShort();
setProtocol(channel, ProtocolUtils.get16PingVersion(buffer.readUnsignedByte()));
} else {
//it was 1.7+ handshake after all
//hope that there won't be any handshake packet with id 0xFA in future because it will be more difficult to support it
setProtocol(channel, attemptDecodeNewHandshake(buffer));
}
} else {
//1.7+ handshake
setProtocol(channel, attemptDecodeNewHandshake(buffer));
}
break;
}
case 0x02: { // <= 1.6.4 handshake
setProtocol(channel, ProtocolUtils.readOldHandshake(buffer));
break;
}
default: { // >= 1.7 handshake
setProtocol(channel, attemptDecodeNewHandshake(buffer));
break;
}
}
}
protected static ProtocolVersion attemptDecodeNewHandshake(ByteBuf bytebuf) {
bytebuf.readerIndex(0);
return ProtocolUtils.readNewHandshake(bytebuf.readSlice(VarNumberCodec.readVarInt(bytebuf)));
}
protected void decodeEncapsulated(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
ByteBuf firstpacketdata = buffer.readSlice(VarNumberCodec.readVarInt(buffer));
if (encapsulatedinfo.hasCompression()) {
int uncompressedlength = VarNumberCodec.readVarInt(firstpacketdata);
if (uncompressedlength != 0) {
firstpacketdata = Unpooled.wrappedBuffer(Decompressor.decompressStatic(MiscDataCodec.readAllBytes(firstpacketdata), uncompressedlength));
}
}
int firstbyte = firstpacketdata.readUnsignedByte();
switch (firstbyte) {
case 0xFE: { //legacy ping
if (firstpacketdata.readableBytes() == 0) {
//< 1.5.2
setProtocol(channel, ProtocolVersion.MINECRAFT_LEGACY);
} else if (firstpacketdata.readUnsignedByte() == 1) {
//1.5 or 1.6 ping
if (firstpacketdata.readableBytes() == 0) {
//1.5.*, we just assume 1.5.2
setProtocol(channel, ProtocolVersion.MINECRAFT_1_5_2);
} else if (
(firstpacketdata.readUnsignedByte() == 0xFA) &&
"MC|PingHost".equals(StringCodec.readShortUTF16BEString(firstpacketdata, Short.MAX_VALUE))
) {
//1.6.*
firstpacketdata.readUnsignedShort();
setProtocol(channel, ProtocolUtils.get16PingVersion(firstpacketdata.readUnsignedByte()));
} else {
throw new DecoderException("Unable to detect incoming protocol");
}
} else {
throw new DecoderException("Unable to detect incoming protocol");
}
break;
}
case 0x02: { // <= 1.6.4 handshake
setProtocol(channel, ProtocolUtils.readOldHandshake(firstpacketdata));
break;
}
case 0x00: { // >= 1.7 handshake
setProtocol(channel, ProtocolUtils.readNewHandshake(firstpacketdata));
break;
}
default: {
throw new DecoderException("Unable to detect incoming protocol");
}
}
}
protected void setProtocol(Channel channel, ProtocolVersion version) {
ConnectionImpl connection = prepare(channel, version);
IPipeLineBuilder builder = pipelineBuilders.get(connection.getVersion());
builder.buildCodec(channel, connection);
connection.initIO();
if (encapsulatedinfo == null) {
builder.buildPipeLine(channel, connection);
} else {
PlatformUtils putils = ServerPlatform.get().getMiscUtils();
putils.setFraming(channel.pipeline(), new VarIntFrameDecoder(), new VarIntFrameEncoder());
if (encapsulatedinfo.hasCompression()) {
putils.enableCompression(channel.pipeline(), putils.getCompressionThreshold());
}
if ((encapsulatedinfo.getAddress() != null) && connection.getRawAddress().getAddress().isLoopbackAddress()) {
connection.changeAddress(encapsulatedinfo.getAddress());
}
}
buffer.readerIndex(0);
channel.pipeline().firstContext().fireChannelRead(buffer.unwrap());
}
protected static ConnectionImpl prepare(Channel channel, ProtocolVersion version) {
channel.pipeline().remove(ChannelHandlers.INITIAL_DECODER);
ConnectionImpl connection = ConnectionImpl.getFromChannel(channel);
if (ServerPlatform.get().getMiscUtils().isDebugging()) {
ProtocolSupport.logInfo(MessageFormat.format("{0} connected with protocol version {1}", connection.getAddress(), version));
}
connection.getNetworkManagerWrapper().setPacketListener(ServerPlatform.get().getMiscUtils().createHandshakeListener(connection.getNetworkManagerWrapper()));
if (!ProtocolSupportAPI.isProtocolVersionEnabled(version)) {
if (version.getProtocolType() == ProtocolType.PC) {
version = version.isBeforeOrEq(ProtocolVersion.MINECRAFT_1_6_4) ? ProtocolVersion.MINECRAFT_LEGACY : ProtocolVersion.MINECRAFT_FUTURE;
} else {
throw new IllegalArgumentException(MessageFormat.format("Unable to get legacy or future version for disabled protocol version {0}", version));
}
}
connection.setVersion(version);
return connection;
}
}