blob: 398f7d5c8374ef5d9883e29741531c54f5abd2a1 [file] [log] [blame] [raw]
package protocolsupport.protocol;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.CorruptedFrameException;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;
import protocolsupport.injector.ProtocolLibFixer;
import protocolsupport.protocol.DataStorage.ProtocolVersion;
import protocolsupport.protocol.IPipeLineBuilder.DecoderEncoderTuple;
public class InitialPacketDecoder extends ChannelInboundHandlerAdapter {
private final Timer pingTimeout = new Timer();
@Override
public void channelRead(final ChannelHandlerContext ctx, final Object inputObj) throws Exception {
try {
final ByteBuf input = (ByteBuf) inputObj;
if (!input.isReadable()) {
return;
}
//detect protocol
ProtocolVersion handshakeversion = ProtocolVersion.UNKNOWN;
//reset reader index to 0
input.readerIndex(0);
int firstbyte = input.readUnsignedByte();
switch (firstbyte) {
case 0xFE: { //1.6 ping or 1.5 ping (should we check if FE is actually a part of a varint length?)
try {
if (input.readUnsignedByte() == 1) {
if (input.readableBytes() == 0) {
//1.5.2 or maybe we still didn't receive it all
pingTimeout.schedule(new TimerTask() {
@Override
public void run() {
try {
SocketAddress remoteAddress = ctx.channel().remoteAddress();
if (DataStorage.getVersion(remoteAddress) == ProtocolVersion.UNKNOWN) {
DataStorage.setVersion(remoteAddress, ProtocolVersion.MINECRAFT_1_5_2);
rebuildPipeLine(ctx, input, ProtocolVersion.MINECRAFT_1_5_2);
}
} catch (Throwable t) {
ctx.channel().close();
}
}
}, 500);
} else if (
(input.readUnsignedByte() == 0xFA) &&
"MC|PingHost".equals(new String(input.readBytes(input.readUnsignedShort() * 2).array(), StandardCharsets.UTF_16BE))
) { //1.6.*
input.readUnsignedShort();
handshakeversion = ProtocolVersion.fromId(input.readUnsignedByte());
}
}
} catch (IndexOutOfBoundsException ex) {
}
break;
}
case 0x02: { //1.6 or 1.5.2 handshake
try {
handshakeversion = ProtocolVersion.fromId(input.readUnsignedByte());
} catch (IndexOutOfBoundsException ex) {
}
break;
}
default: { //1.7 or 1.8 handshake
input.readerIndex(0);
ByteBuf data = getVarIntPrefixedData(input);
if (data != null) {
handshakeversion = read1_7_1_8Handshake(data);
}
break;
}
}
//if we detected the protocol than we save it and process data
if (handshakeversion != ProtocolVersion.UNKNOWN) {
System.out.println(ctx.channel().remoteAddress()+" connected with protocol version "+handshakeversion);
DataStorage.setVersion(ctx.channel().remoteAddress(), handshakeversion);
rebuildPipeLine(ctx, input, handshakeversion);
}
} catch (Throwable t) {
t.printStackTrace();
ctx.channel().close();
}
}
@SuppressWarnings("serial")
private static HashMap<ProtocolVersion, IPipeLineBuilder> pipelineBuilders = new HashMap<ProtocolVersion, IPipeLineBuilder>() {{
put(ProtocolVersion.MINECRAFT_1_8, new protocolsupport.protocol.v_1_8.PipeLineBuilder());
put(ProtocolVersion.MINECRAFT_1_7_10, new protocolsupport.protocol.v_1_7.PipeLineBuilder());
put(ProtocolVersion.MINECRAFT_1_7_5, new protocolsupport.protocol.v_1_7.PipeLineBuilder());
put(ProtocolVersion.MINECRAFT_1_6_4, new protocolsupport.protocol.v_1_6.PipeLineBuilder());
put(ProtocolVersion.MINECRAFT_1_6_2, new protocolsupport.protocol.v_1_6.PipeLineBuilder());
put(ProtocolVersion.MINECRAFT_1_5_2, new protocolsupport.protocol.v_1_5.PipeLineBuilder());
}};
private void rebuildPipeLine(final ChannelHandlerContext ctx, final ByteBuf input, ProtocolVersion version) throws Exception {
ctx.channel().pipeline().remove(InitialPacketDecoder.class);
DecoderEncoderTuple tuple = pipelineBuilders.get(version).buildPipeLine(ctx);
ProtocolLibFixer.fixProtocolLib(ctx.channel().pipeline(), tuple.getDecoder(), tuple.getEncoder());
//set reader index to 0
input.readerIndex(0);
//fire data read
ctx.channel().pipeline().firstContext().fireChannelRead(input);
}
private ByteBuf getVarIntPrefixedData(final ByteBuf byteBuf) {
final byte[] array = new byte[3];
for (int i = 0; i < array.length; ++i) {
if (!byteBuf.isReadable()) {
return null;
}
array[i] = byteBuf.readByte();
if (array[i] >= 0) {
final int length = readVarInt(Unpooled.wrappedBuffer(array));
if (byteBuf.readableBytes() < length) {
return null;
}
return byteBuf.readBytes(length);
}
}
throw new CorruptedFrameException("Packet length is wider than 21 bit");
}
private ProtocolVersion read1_7_1_8Handshake(ByteBuf data) {
if (readVarInt(data) == 0x00) {
return ProtocolVersion.fromId(readVarInt(data));
}
return ProtocolVersion.UNKNOWN;
}
private int readVarInt(ByteBuf data) {
int value = 0;
int length = 0;
byte b0;
do {
b0 = data.readByte();
value |= (b0 & 0x7F) << (length++ * 7);
if (length > 5) {
throw new RuntimeException("VarInt too big");
}
} while ((b0 & 0x80) == 0x80);
return value;
}
}