commit ccedd3a160fc7c29f585469afa2a537df8dd28c8 Author: Nanit Date: Wed Nov 25 19:16:28 2020 +0200 Moved from Gitlab diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..545fcec --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.idea +.gradle +gradle +gradlew +gradlew.bat +build \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..3277d27 --- /dev/null +++ b/build.gradle @@ -0,0 +1,26 @@ +plugins { + id 'java' +} + +group 'ru.nanit' +version '1.0' + +repositories { + mavenCentral() +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.12' + compile group: 'io.netty', name: 'netty-all', version: '4.1.54.Final' + compile group: 'net.kyori', name: 'adventure-nbt', version: '4.1.1' +} + +jar { + manifest { + attributes("Main-Class": "ru.nanit.limbo.NanoLimbo") + } + + from { + configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..f5663d0 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'NanoLimbo' + diff --git a/src/main/java/ru/nanit/limbo/LimboConfig.java b/src/main/java/ru/nanit/limbo/LimboConfig.java new file mode 100644 index 0000000..43c11db --- /dev/null +++ b/src/main/java/ru/nanit/limbo/LimboConfig.java @@ -0,0 +1,94 @@ +package ru.nanit.limbo; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Properties; + +public final class LimboConfig { + + private static String host; + private static int port; + private static boolean onlineMode; + private static int maxPlayers; + private static IpForwardingType ipForwardingType; + private static long readTimeout; + private static PingData pingData; + + public static void load(Path file) throws IOException { + if (!Files.exists(file)){ + Files.copy(LimboConfig.class.getResourceAsStream("/settings.properties"), file); + } + + Properties properties = new Properties(); + properties.load(Files.newInputStream(file)); + + host = properties.getProperty("host"); + port = Integer.parseInt(properties.getProperty("port")); + onlineMode = Boolean.parseBoolean(properties.getProperty("online-mode")); + maxPlayers = Integer.parseInt(properties.getProperty("max-players")); + ipForwardingType = IpForwardingType.valueOf(properties.getProperty("ip-forwarding").toUpperCase()); + readTimeout = Long.parseLong(properties.getProperty("read-timeout")); + pingData = new PingData(); + + pingData.setVersion(properties.getProperty("ping-version")); + pingData.setDescription(properties.getProperty("ping-description")); + } + + public static String getHost() { + return host; + } + + public static int getPort() { + return port; + } + + public static boolean isOnlineMode() { + return onlineMode; + } + + public static int getMaxPlayers() { + return maxPlayers; + } + + public static IpForwardingType getIpForwardingType() { + return ipForwardingType; + } + + public static long getReadTimeout() { + return readTimeout; + } + + public static PingData getPingData() { + return pingData; + } + + public enum IpForwardingType { + NONE, + LEGACY, + MODERN + } + + public static class PingData { + + private String version; + private String description; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + } + +} diff --git a/src/main/java/ru/nanit/limbo/NanoLimbo.java b/src/main/java/ru/nanit/limbo/NanoLimbo.java new file mode 100644 index 0000000..e811d95 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/NanoLimbo.java @@ -0,0 +1,26 @@ +package ru.nanit.limbo; + +import ru.nanit.limbo.server.LimboServer; +import ru.nanit.limbo.util.Logger; + +import java.nio.file.Paths; + +public final class NanoLimbo { + + private LimboServer server; + + public void start() throws Exception { + LimboConfig.load(Paths.get("./settings.properties")); + + server = new LimboServer(); + server.start(); + } + + public static void main(String[] args){ + try { + new NanoLimbo().start(); + } catch (Exception e){ + Logger.error("Cannot start server: ", e); + } + } +} diff --git a/src/main/java/ru/nanit/limbo/connection/ClientChannelInitializer.java b/src/main/java/ru/nanit/limbo/connection/ClientChannelInitializer.java new file mode 100644 index 0000000..688eaf5 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/connection/ClientChannelInitializer.java @@ -0,0 +1,36 @@ +package ru.nanit.limbo.connection; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.handler.timeout.ReadTimeoutHandler; +import ru.nanit.limbo.LimboConfig; +import ru.nanit.limbo.protocol.pipeline.VarIntFrameDecoder; +import ru.nanit.limbo.protocol.pipeline.PacketDecoder; +import ru.nanit.limbo.protocol.pipeline.PacketEncoder; +import ru.nanit.limbo.protocol.pipeline.VarIntLengthEncoder; +import ru.nanit.limbo.server.LimboServer; + +import java.util.concurrent.TimeUnit; + +public class ClientChannelInitializer extends ChannelInitializer { + + private final LimboServer server; + + public ClientChannelInitializer(LimboServer server){ + this.server = server; + } + + @Override + protected void initChannel(Channel channel) { + ChannelPipeline pipeline = channel.pipeline(); + + pipeline.addLast("timeout", new ReadTimeoutHandler(LimboConfig.getReadTimeout(), TimeUnit.MILLISECONDS)); + pipeline.addLast("frame_decoder", new VarIntFrameDecoder()); + pipeline.addLast("frame_encoder", new VarIntLengthEncoder()); + pipeline.addLast("decoder", new PacketDecoder()); + pipeline.addLast("encoder", new PacketEncoder()); + pipeline.addLast("handler", new ClientConnection(channel, server)); + } + +} diff --git a/src/main/java/ru/nanit/limbo/connection/ClientConnection.java b/src/main/java/ru/nanit/limbo/connection/ClientConnection.java new file mode 100644 index 0000000..169202d --- /dev/null +++ b/src/main/java/ru/nanit/limbo/connection/ClientConnection.java @@ -0,0 +1,105 @@ +package ru.nanit.limbo.connection; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import ru.nanit.limbo.protocol.packets.login.*; +import ru.nanit.limbo.protocol.registry.Version; +import ru.nanit.limbo.protocol.pipeline.PacketDecoder; +import ru.nanit.limbo.protocol.pipeline.PacketEncoder; +import ru.nanit.limbo.protocol.packets.PacketHandshake; +import ru.nanit.limbo.protocol.packets.status.PacketStatusPing; +import ru.nanit.limbo.protocol.packets.status.PacketStatusRequest; +import ru.nanit.limbo.protocol.packets.status.PacketStatusResponse; +import ru.nanit.limbo.protocol.registry.State; +import ru.nanit.limbo.server.LimboServer; +import ru.nanit.limbo.util.UuidUtil; + +public class ClientConnection extends ChannelInboundHandlerAdapter { + + private final LimboServer server; + private final Channel channel; + + private String username; + + public ClientConnection(Channel channel, LimboServer server){ + this.channel = channel; + this.server = server; + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + handlePacket(msg); + } + + public void handlePacket(Object packet){ + if (packet instanceof PacketHandshake){ + PacketHandshake handshake = (PacketHandshake) packet; + State state = State.getById(handshake.getNextState()); + updateStateAndVersion(state, handshake.getVersion()); + } + + if (packet instanceof PacketStatusRequest){ + sendPacket(new PacketStatusResponse()); + } + + if (packet instanceof PacketStatusPing){ + sendPacketAndClose(packet); + } + + if (packet instanceof PacketLoginStart){ + this.username = ((PacketLoginStart) packet).getUsername(); + + // Limbo always in offline mode. Online mode set on proxy side + PacketLoginSuccess loginSuccess = new PacketLoginSuccess(); + + loginSuccess.setUuid(UuidUtil.getOfflineModeUuid(this.username)); + loginSuccess.setUsername(this.username); + + sendPacket(loginSuccess); + updateState(State.PLAY); + } + } + + public void sendPacket(Object packet){ + if (isConnected()) + channel.writeAndFlush(packet, channel.voidPromise()); + } + + public void sendPacketAndClose(Object packet){ + if (isConnected()) + channel.writeAndFlush(packet).addListener(ChannelFutureListener.CLOSE); + } + + public void disconnect(){ + if (channel.isActive()){ + channel.close(); + } + } + + public void disconnect(String reason){ + PacketDisconnect packet = new PacketDisconnect(); + packet.setReason(reason); + sendPacketAndClose(packet); + } + + public boolean isConnected(){ + return channel.isActive(); + } + + public void updateState(State state){ + channel.pipeline().get(PacketDecoder.class).updateState(state); + channel.pipeline().get(PacketEncoder.class).updateState(state); + } + + public void updateStateAndVersion(State state, Version version){ + PacketDecoder decoder = channel.pipeline().get(PacketDecoder.class); + PacketEncoder encoder = channel.pipeline().get(PacketEncoder.class); + + decoder.updateVersion(version); + decoder.updateState(state); + encoder.updateVersion(version); + encoder.updateState(state); + } +} diff --git a/src/main/java/ru/nanit/limbo/protocol/ByteMessage.java b/src/main/java/ru/nanit/limbo/protocol/ByteMessage.java new file mode 100644 index 0000000..da7467b --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/ByteMessage.java @@ -0,0 +1,1107 @@ +package ru.nanit.limbo.protocol; + +import io.netty.buffer.*; +import io.netty.handler.codec.DecoderException; +import io.netty.handler.codec.EncoderException; +import io.netty.util.ByteProcessor; +import net.kyori.adventure.nbt.BinaryTagIO; +import net.kyori.adventure.nbt.CompoundBinaryTag; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +public class ByteMessage extends ByteBuf { + + private final ByteBuf buf; + + public ByteMessage(ByteBuf buf){ + this.buf = buf; + } + + /* Minecraft protocol methods */ + + public int readVarInt() { + int i = 0; + int maxRead = Math.min(5, buf.readableBytes()); + + for (int j = 0; j < maxRead; j++) { + int k = buf.readByte(); + i |= (k & 0x7F) << j * 7; + if ((k & 0x80) != 128) { + return i; + } + } + + throw new IllegalArgumentException("Cannot read VarInt"); + } + + public void writeVarInt(int value){ + while (true) { + if ((value & 0xFFFFFF80) == 0) { + buf.writeByte(value); + return; + } + + buf.writeByte(value & 0x7F | 0x80); + value >>>= 7; + } + } + + public String readString() { + return readString(readVarInt()); + } + + private String readString(int length) { + String str = buf.toString(buf.readerIndex(), length, StandardCharsets.UTF_8); + buf.skipBytes(length); + return str; + } + + public void writeString(CharSequence str) { + int size = ByteBufUtil.utf8Bytes(str); + writeVarInt(size); + buf.writeCharSequence(str, StandardCharsets.UTF_8); + } + + public byte[] readBytesArray(){ + int length = readVarInt(); + byte[] array = new byte[length]; + buf.readBytes(array); + return array; + } + + public void writeBytesArray(byte[] array) { + writeVarInt(array.length); + buf.writeBytes(array); + } + + public int[] readIntArray() { + int len = readVarInt(); + int[] array = new int[len]; + for (int i = 0; i < len; i++) { + array[i] = readVarInt(); + } + return array; + } + + public UUID readUuid() { + long msb = buf.readLong(); + long lsb = buf.readLong(); + return new UUID(msb, lsb); + } + + public void writeUuid(UUID uuid) { + buf.writeLong(uuid.getMostSignificantBits()); + buf.writeLong(uuid.getLeastSignificantBits()); + } + + public String[] readStringsArray() { + int length = readVarInt(); + String[] ret = new String[length]; + for (int i = 0; i < length; i++) { + ret[i] = readString(); + } + return ret; + } + + public void writeStringsArray(String[] stringArray) { + writeVarInt(stringArray.length); + for (String str : stringArray) { + writeString(str); + } + } + + public CompoundBinaryTag readCompoundTag() { + try { + return BinaryTagIO.readDataInput(new ByteBufInputStream(buf)); + } catch (IOException thrown) { + throw new DecoderException("Cannot read NBT CompoundTag"); + } + } + + public void writeCompoundTag(CompoundBinaryTag compoundTag) { + try { + BinaryTagIO.writeDataOutput(compoundTag, new ByteBufOutputStream(buf)); + } catch (IOException e) { + throw new EncoderException("Cannot write NBT CompoundTag"); + } + } + + /* Delegated methods */ + + @Override + public int capacity() { + return buf.capacity(); + } + + @Override + public ByteBuf capacity(int newCapacity) { + return buf.capacity(newCapacity); + } + + @Override + public int maxCapacity() { + return buf.maxCapacity(); + } + + @Override + public ByteBufAllocator alloc() { + return buf.alloc(); + } + + @Override + @Deprecated + public ByteOrder order() { + return buf.order(); + } + + @Override + @Deprecated + public ByteBuf order(ByteOrder endianness) { + return buf.order(endianness); + } + + @Override + public ByteBuf unwrap() { + return buf.unwrap(); + } + + @Override + public boolean isDirect() { + return buf.isDirect(); + } + + @Override + public boolean isReadOnly() { + return buf.isReadOnly(); + } + + @Override + public ByteBuf asReadOnly() { + return buf.asReadOnly(); + } + + @Override + public int readerIndex() { + return buf.readerIndex(); + } + + @Override + public ByteBuf readerIndex(int readerIndex) { + return buf.readerIndex(readerIndex); + } + + @Override + public int writerIndex() { + return buf.writerIndex(); + } + + @Override + public ByteBuf writerIndex(int writerIndex) { + return buf.writerIndex(writerIndex); + } + + @Override + public ByteBuf setIndex(int readerIndex, int writerIndex) { + return buf.setIndex(readerIndex, writerIndex); + } + + @Override + public int readableBytes() { + return buf.readableBytes(); + } + + @Override + public int writableBytes() { + return buf.writableBytes(); + } + + @Override + public int maxWritableBytes() { + return buf.maxWritableBytes(); + } + + @Override + public int maxFastWritableBytes() { + return buf.maxFastWritableBytes(); + } + + @Override + public boolean isReadable() { + return buf.isReadable(); + } + + @Override + public boolean isReadable(int size) { + return buf.isReadable(size); + } + + @Override + public boolean isWritable() { + return buf.isWritable(); + } + + @Override + public boolean isWritable(int size) { + return buf.isWritable(size); + } + + @Override + public ByteBuf clear() { + return buf.clear(); + } + + @Override + public ByteBuf markReaderIndex() { + return buf.markReaderIndex(); + } + + @Override + public ByteBuf resetReaderIndex() { + return buf.resetReaderIndex(); + } + + @Override + public ByteBuf markWriterIndex() { + return buf.markWriterIndex(); + } + + @Override + public ByteBuf resetWriterIndex() { + return buf.resetWriterIndex(); + } + + @Override + public ByteBuf discardReadBytes() { + return buf.discardReadBytes(); + } + + @Override + public ByteBuf discardSomeReadBytes() { + return buf.discardSomeReadBytes(); + } + + @Override + public ByteBuf ensureWritable(int minWritableBytes) { + return buf.ensureWritable(minWritableBytes); + } + + @Override + public int ensureWritable(int minWritableBytes, boolean force) { + return buf.ensureWritable(minWritableBytes, force); + } + + @Override + public boolean getBoolean(int index) { + return buf.getBoolean(index); + } + + @Override + public byte getByte(int index) { + return buf.getByte(index); + } + + @Override + public short getUnsignedByte(int index) { + return buf.getUnsignedByte(index); + } + + @Override + public short getShort(int index) { + return buf.getShort(index); + } + + @Override + public short getShortLE(int index) { + return buf.getShortLE(index); + } + + @Override + public int getUnsignedShort(int index) { + return buf.getUnsignedShort(index); + } + + @Override + public int getUnsignedShortLE(int index) { + return buf.getUnsignedShortLE(index); + } + + @Override + public int getMedium(int index) { + return buf.getMedium(index); + } + + @Override + public int getMediumLE(int index) { + return buf.getMediumLE(index); + } + + @Override + public int getUnsignedMedium(int index) { + return buf.getUnsignedMedium(index); + } + + @Override + public int getUnsignedMediumLE(int index) { + return buf.getUnsignedMediumLE(index); + } + + @Override + public int getInt(int index) { + return buf.getInt(index); + } + + @Override + public int getIntLE(int index) { + return buf.getIntLE(index); + } + + @Override + public long getUnsignedInt(int index) { + return buf.getUnsignedInt(index); + } + + @Override + public long getUnsignedIntLE(int index) { + return buf.getUnsignedIntLE(index); + } + + @Override + public long getLong(int index) { + return buf.getLong(index); + } + + @Override + public long getLongLE(int index) { + return buf.getLongLE(index); + } + + @Override + public char getChar(int index) { + return buf.getChar(index); + } + + @Override + public float getFloat(int index) { + return buf.getFloat(index); + } + + @Override + public float getFloatLE(int index) { + return buf.getFloatLE(index); + } + + @Override + public double getDouble(int index) { + return buf.getDouble(index); + } + + @Override + public double getDoubleLE(int index) { + return buf.getDoubleLE(index); + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst) { + return buf.getBytes(index, dst); + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int length) { + return buf.getBytes(index, dst, length); + } + + @Override + public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + return buf.getBytes(index, dst, dstIndex, length); + } + + @Override + public ByteBuf getBytes(int index, byte[] dst) { + return buf.getBytes(index, dst); + } + + @Override + public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { + return buf.getBytes(index, dst, dstIndex, length); + } + + @Override + public ByteBuf getBytes(int index, ByteBuffer dst) { + return buf.getBytes(index, dst); + } + + @Override + public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException { + return buf.getBytes(index, out, length); + } + + @Override + public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { + return buf.getBytes(index, out, length); + } + + @Override + public int getBytes(int index, FileChannel out, long position, int length) throws IOException { + return buf.getBytes(index, out, position, length); + } + + @Override + public CharSequence getCharSequence(int index, int length, Charset charset) { + return buf.getCharSequence(index, length, charset); + } + + @Override + public ByteBuf setBoolean(int index, boolean value) { + return buf.setBoolean(index, value); + } + + @Override + public ByteBuf setByte(int index, int value) { + return buf.setByte(index, value); + } + + @Override + public ByteBuf setShort(int index, int value) { + return buf.setShort(index, value); + } + + @Override + public ByteBuf setShortLE(int index, int value) { + return buf.setShortLE(index, value); + } + + @Override + public ByteBuf setMedium(int index, int value) { + return buf.setMedium(index, value); + } + + @Override + public ByteBuf setMediumLE(int index, int value) { + return buf.setMediumLE(index, value); + } + + @Override + public ByteBuf setInt(int index, int value) { + return buf.setInt(index, value); + } + + @Override + public ByteBuf setIntLE(int index, int value) { + return buf.setIntLE(index, value); + } + + @Override + public ByteBuf setLong(int index, long value) { + return buf.setLong(index, value); + } + + @Override + public ByteBuf setLongLE(int index, long value) { + return buf.setLongLE(index, value); + } + + @Override + public ByteBuf setChar(int index, int value) { + return buf.setChar(index, value); + } + + @Override + public ByteBuf setFloat(int index, float value) { + return buf.setFloat(index, value); + } + + @Override + public ByteBuf setFloatLE(int index, float value) { + return buf.setFloatLE(index, value); + } + + @Override + public ByteBuf setDouble(int index, double value) { + return buf.setDouble(index, value); + } + + @Override + public ByteBuf setDoubleLE(int index, double value) { + return buf.setDoubleLE(index, value); + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src) { + return buf.setBytes(index, src); + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src, int length) { + return buf.setBytes(index, src, length); + } + + @Override + public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { + return buf.setBytes(index, src, srcIndex, length); + } + + @Override + public ByteBuf setBytes(int index, byte[] src) { + return buf.setBytes(index, src); + } + + @Override + public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { + return buf.setBytes(index, src, srcIndex, length); + } + + @Override + public ByteBuf setBytes(int index, ByteBuffer src) { + return buf.setBytes(index, src); + } + + @Override + public int setBytes(int index, InputStream in, int length) throws IOException { + return buf.setBytes(index, in, length); + } + + @Override + public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { + return buf.setBytes(index, in, length); + } + + @Override + public int setBytes(int index, FileChannel in, long position, int length) throws IOException { + return buf.setBytes(index, in, position, length); + } + + @Override + public ByteBuf setZero(int index, int length) { + return buf.setZero(index, length); + } + + @Override + public int setCharSequence(int index, CharSequence sequence, Charset charset) { + return buf.setCharSequence(index, sequence, charset); + } + + @Override + public boolean readBoolean() { + return buf.readBoolean(); + } + + @Override + public byte readByte() { + return buf.readByte(); + } + + @Override + public short readUnsignedByte() { + return buf.readUnsignedByte(); + } + + @Override + public short readShort() { + return buf.readShort(); + } + + @Override + public short readShortLE() { + return buf.readShortLE(); + } + + @Override + public int readUnsignedShort() { + return buf.readUnsignedShort(); + } + + @Override + public int readUnsignedShortLE() { + return buf.readUnsignedShortLE(); + } + + @Override + public int readMedium() { + return buf.readMedium(); + } + + @Override + public int readMediumLE() { + return buf.readMediumLE(); + } + + @Override + public int readUnsignedMedium() { + return buf.readUnsignedMedium(); + } + + @Override + public int readUnsignedMediumLE() { + return buf.readUnsignedMediumLE(); + } + + @Override + public int readInt() { + return buf.readInt(); + } + + @Override + public int readIntLE() { + return buf.readIntLE(); + } + + @Override + public long readUnsignedInt() { + return buf.readUnsignedInt(); + } + + @Override + public long readUnsignedIntLE() { + return buf.readUnsignedIntLE(); + } + + @Override + public long readLong() { + return buf.readLong(); + } + + @Override + public long readLongLE() { + return buf.readLongLE(); + } + + @Override + public char readChar() { + return buf.readChar(); + } + + @Override + public float readFloat() { + return buf.readFloat(); + } + + @Override + public float readFloatLE() { + return buf.readFloatLE(); + } + + @Override + public double readDouble() { + return buf.readDouble(); + } + + @Override + public double readDoubleLE() { + return buf.readDoubleLE(); + } + + @Override + public ByteBuf readBytes(int length) { + return buf.readBytes(length); + } + + @Override + public ByteBuf readSlice(int length) { + return buf.readSlice(length); + } + + @Override + public ByteBuf readRetainedSlice(int length) { + return buf.readRetainedSlice(length); + } + + @Override + public ByteBuf readBytes(ByteBuf dst) { + return buf.readBytes(dst); + } + + @Override + public ByteBuf readBytes(ByteBuf dst, int length) { + return buf.readBytes(dst, length); + } + + @Override + public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length) { + return buf.readBytes(dst, dstIndex, length); + } + + @Override + public ByteBuf readBytes(byte[] dst) { + return buf.readBytes(dst); + } + + @Override + public ByteBuf readBytes(byte[] dst, int dstIndex, int length) { + return buf.readBytes(dst, dstIndex, length); + } + + @Override + public ByteBuf readBytes(ByteBuffer dst) { + return buf.readBytes(dst); + } + + @Override + public ByteBuf readBytes(OutputStream out, int length) throws IOException { + return buf.readBytes(out, length); + } + + @Override + public int readBytes(GatheringByteChannel out, int length) throws IOException { + return buf.readBytes(out, length); + } + + @Override + public CharSequence readCharSequence(int length, Charset charset) { + return buf.readCharSequence(length, charset); + } + + @Override + public int readBytes(FileChannel out, long position, int length) throws IOException { + return buf.readBytes(out, position, length); + } + + @Override + public ByteBuf skipBytes(int length) { + return buf.skipBytes(length); + } + + @Override + public ByteBuf writeBoolean(boolean value) { + return buf.writeBoolean(value); + } + + @Override + public ByteBuf writeByte(int value) { + return buf.writeByte(value); + } + + @Override + public ByteBuf writeShort(int value) { + return buf.writeShort(value); + } + + @Override + public ByteBuf writeShortLE(int value) { + return buf.writeShortLE(value); + } + + @Override + public ByteBuf writeMedium(int value) { + return buf.writeMedium(value); + } + + @Override + public ByteBuf writeMediumLE(int value) { + return buf.writeMediumLE(value); + } + + @Override + public ByteBuf writeInt(int value) { + return buf.writeInt(value); + } + + @Override + public ByteBuf writeIntLE(int value) { + return buf.writeIntLE(value); + } + + @Override + public ByteBuf writeLong(long value) { + return buf.writeLong(value); + } + + @Override + public ByteBuf writeLongLE(long value) { + return buf.writeLongLE(value); + } + + @Override + public ByteBuf writeChar(int value) { + return buf.writeChar(value); + } + + @Override + public ByteBuf writeFloat(float value) { + return buf.writeFloat(value); + } + + @Override + public ByteBuf writeFloatLE(float value) { + return buf.writeFloatLE(value); + } + + @Override + public ByteBuf writeDouble(double value) { + return buf.writeDouble(value); + } + + @Override + public ByteBuf writeDoubleLE(double value) { + return buf.writeDoubleLE(value); + } + + @Override + public ByteBuf writeBytes(ByteBuf src) { + return buf.writeBytes(src); + } + + @Override + public ByteBuf writeBytes(ByteBuf src, int length) { + return buf.writeBytes(src, length); + } + + @Override + public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) { + return buf.writeBytes(src, srcIndex, length); + } + + @Override + public ByteBuf writeBytes(byte[] src) { + return buf.writeBytes(src); + } + + @Override + public ByteBuf writeBytes(byte[] src, int srcIndex, int length) { + return buf.writeBytes(src, srcIndex, length); + } + + @Override + public ByteBuf writeBytes(ByteBuffer src) { + return buf.writeBytes(src); + } + + @Override + public int writeBytes(InputStream in, int length) throws IOException { + return buf.writeBytes(in, length); + } + + @Override + public int writeBytes(ScatteringByteChannel in, int length) throws IOException { + return buf.writeBytes(in, length); + } + + @Override + public int writeBytes(FileChannel in, long position, int length) throws IOException { + return buf.writeBytes(in, position, length); + } + + @Override + public ByteBuf writeZero(int length) { + return buf.writeZero(length); + } + + @Override + public int writeCharSequence(CharSequence sequence, Charset charset) { + return buf.writeCharSequence(sequence, charset); + } + + @Override + public int indexOf(int fromIndex, int toIndex, byte value) { + return buf.indexOf(fromIndex, toIndex, value); + } + + @Override + public int bytesBefore(byte value) { + return buf.bytesBefore(value); + } + + @Override + public int bytesBefore(int length, byte value) { + return buf.bytesBefore(length, value); + } + + @Override + public int bytesBefore(int index, int length, byte value) { + return buf.bytesBefore(index, length, value); + } + + @Override + public int forEachByte(ByteProcessor processor) { + return buf.forEachByte(processor); + } + + @Override + public int forEachByte(int index, int length, ByteProcessor processor) { + return buf.forEachByte(index, length, processor); + } + + @Override + public int forEachByteDesc(ByteProcessor processor) { + return buf.forEachByteDesc(processor); + } + + @Override + public int forEachByteDesc(int index, int length, ByteProcessor processor) { + return buf.forEachByteDesc(index, length, processor); + } + + @Override + public ByteBuf copy() { + return buf.copy(); + } + + @Override + public ByteBuf copy(int index, int length) { + return buf.copy(index, length); + } + + @Override + public ByteBuf slice() { + return buf.slice(); + } + + @Override + public ByteBuf retainedSlice() { + return buf.retainedSlice(); + } + + @Override + public ByteBuf slice(int index, int length) { + return buf.slice(index, length); + } + + @Override + public ByteBuf retainedSlice(int index, int length) { + return buf.retainedSlice(index, length); + } + + @Override + public ByteBuf duplicate() { + return buf.duplicate(); + } + + @Override + public ByteBuf retainedDuplicate() { + return buf.retainedDuplicate(); + } + + @Override + public int nioBufferCount() { + return buf.nioBufferCount(); + } + + @Override + public ByteBuffer nioBuffer() { + return buf.nioBuffer(); + } + + @Override + public ByteBuffer nioBuffer(int index, int length) { + return buf.nioBuffer(index, length); + } + + @Override + public ByteBuffer internalNioBuffer(int index, int length) { + return buf.internalNioBuffer(index, length); + } + + @Override + public ByteBuffer[] nioBuffers() { + return buf.nioBuffers(); + } + + @Override + public ByteBuffer[] nioBuffers(int index, int length) { + return buf.nioBuffers(index, length); + } + + @Override + public boolean hasArray() { + return buf.hasArray(); + } + + @Override + public byte[] array() { + return buf.array(); + } + + @Override + public int arrayOffset() { + return buf.arrayOffset(); + } + + @Override + public boolean hasMemoryAddress() { + return buf.hasMemoryAddress(); + } + + @Override + public long memoryAddress() { + return buf.memoryAddress(); + } + + @Override + public boolean isContiguous() { + return buf.isContiguous(); + } + + @Override + public String toString(Charset charset) { + return buf.toString(charset); + } + + @Override + public String toString(int index, int length, Charset charset) { + return buf.toString(index, length, charset); + } + + @Override + public int hashCode() { + return buf.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return buf.equals(obj); + } + + @Override + public int compareTo(ByteBuf buffer) { + return buf.compareTo(buffer); + } + + @Override + public String toString() { + return buf.toString(); + } + + @Override + public ByteBuf retain(int increment) { + return buf.retain(increment); + } + + @Override + public ByteBuf retain() { + return buf.retain(); + } + + @Override + public ByteBuf touch() { + return buf.touch(); + } + + @Override + public ByteBuf touch(Object hint) { + return buf.touch(hint); + } + + @Override + public int refCnt() { + return buf.refCnt(); + } + + @Override + public boolean release() { + return buf.release(); + } + + @Override + public boolean release(int decrement) { + return buf.release(decrement); + } +} diff --git a/src/main/java/ru/nanit/limbo/protocol/Direction.java b/src/main/java/ru/nanit/limbo/protocol/Direction.java new file mode 100644 index 0000000..9e67e48 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/Direction.java @@ -0,0 +1,8 @@ +package ru.nanit.limbo.protocol; + +public enum Direction { + + CLIENT, + SERVER + +} diff --git a/src/main/java/ru/nanit/limbo/protocol/Packet.java b/src/main/java/ru/nanit/limbo/protocol/Packet.java new file mode 100644 index 0000000..7acb523 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/Packet.java @@ -0,0 +1,11 @@ +package ru.nanit.limbo.protocol; + +import ru.nanit.limbo.protocol.registry.Version; + +public interface Packet { + + void encode(ByteMessage msg, Direction direction, Version version); + + void decode(ByteMessage msg, Direction direction, Version version); + +} diff --git a/src/main/java/ru/nanit/limbo/protocol/PacketIn.java b/src/main/java/ru/nanit/limbo/protocol/PacketIn.java new file mode 100644 index 0000000..13a3589 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/PacketIn.java @@ -0,0 +1,12 @@ +package ru.nanit.limbo.protocol; + +import ru.nanit.limbo.protocol.registry.Version; + +public interface PacketIn extends Packet { + + @Override + default void encode(ByteMessage msg, Direction direction, Version version) { + // Can be ignored for incoming packets + } + +} diff --git a/src/main/java/ru/nanit/limbo/protocol/PacketOut.java b/src/main/java/ru/nanit/limbo/protocol/PacketOut.java new file mode 100644 index 0000000..d3e24c7 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/PacketOut.java @@ -0,0 +1,12 @@ +package ru.nanit.limbo.protocol; + +import ru.nanit.limbo.protocol.registry.Version; + +public interface PacketOut extends Packet { + + @Override + default void decode(ByteMessage msg, Direction direction, Version version) { + // Can be ignored for outgoing packets + } + +} diff --git a/src/main/java/ru/nanit/limbo/protocol/packets/PacketHandshake.java b/src/main/java/ru/nanit/limbo/protocol/packets/PacketHandshake.java new file mode 100644 index 0000000..7fc626f --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/packets/PacketHandshake.java @@ -0,0 +1,62 @@ +package ru.nanit.limbo.protocol.packets; + +import ru.nanit.limbo.protocol.ByteMessage; +import ru.nanit.limbo.protocol.Packet; +import ru.nanit.limbo.protocol.Direction; +import ru.nanit.limbo.protocol.registry.Version; + +public class PacketHandshake implements Packet { + + private Version version; + private String host; + private int port; + private int nextState; + + public Version getVersion() { + return version; + } + + public void setVersion(Version version) { + this.version = version; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public int getNextState() { + return nextState; + } + + public void setNextState(int nextState) { + this.nextState = nextState; + } + + @Override + public void encode(ByteMessage msg, Direction direction, Version version) { + msg.writeVarInt(this.version.getProtocolNumber()); + msg.writeString(host); + msg.writeShort(port); + msg.writeVarInt(nextState); + } + + @Override + public void decode(ByteMessage msg, Direction direction, Version version) { + this.version = Version.of(msg.readVarInt()); + this.host = msg.readString(); + this.port = msg.readUnsignedShort(); + this.nextState = msg.readVarInt(); + } +} diff --git a/src/main/java/ru/nanit/limbo/protocol/packets/login/PacketDisconnect.java b/src/main/java/ru/nanit/limbo/protocol/packets/login/PacketDisconnect.java new file mode 100644 index 0000000..4be6429 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/packets/login/PacketDisconnect.java @@ -0,0 +1,21 @@ +package ru.nanit.limbo.protocol.packets.login; + +import ru.nanit.limbo.protocol.ByteMessage; +import ru.nanit.limbo.protocol.Direction; +import ru.nanit.limbo.protocol.PacketOut; +import ru.nanit.limbo.protocol.registry.Version; + +public class PacketDisconnect implements PacketOut { + + private String reason; + + public void setReason(String reason) { + this.reason = reason; + } + + @Override + public void encode(ByteMessage msg, Direction direction, Version version) { + msg.writeString(String.format("{\"text\": \"%s\"}", reason)); + } + +} diff --git a/src/main/java/ru/nanit/limbo/protocol/packets/login/PacketLoginStart.java b/src/main/java/ru/nanit/limbo/protocol/packets/login/PacketLoginStart.java new file mode 100644 index 0000000..b148325 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/packets/login/PacketLoginStart.java @@ -0,0 +1,19 @@ +package ru.nanit.limbo.protocol.packets.login; + +import ru.nanit.limbo.protocol.*; +import ru.nanit.limbo.protocol.registry.Version; + +public class PacketLoginStart implements PacketIn { + + private String username; + + public String getUsername() { + return username; + } + + @Override + public void decode(ByteMessage msg, Direction direction, Version version) { + this.username = msg.readString(); + } + +} diff --git a/src/main/java/ru/nanit/limbo/protocol/packets/login/PacketLoginSuccess.java b/src/main/java/ru/nanit/limbo/protocol/packets/login/PacketLoginSuccess.java new file mode 100644 index 0000000..27c1c9d --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/packets/login/PacketLoginSuccess.java @@ -0,0 +1,29 @@ +package ru.nanit.limbo.protocol.packets.login; + +import ru.nanit.limbo.protocol.ByteMessage; +import ru.nanit.limbo.protocol.Direction; +import ru.nanit.limbo.protocol.PacketOut; +import ru.nanit.limbo.protocol.registry.Version; + +import java.util.UUID; + +public class PacketLoginSuccess implements PacketOut { + + private UUID uuid; + private String username; + + public void setUuid(UUID uuid) { + this.uuid = uuid; + } + + public void setUsername(String username) { + this.username = username; + } + + @Override + public void encode(ByteMessage msg, Direction direction, Version version) { + msg.writeUuid(uuid); + msg.writeString(username); + } + +} diff --git a/src/main/java/ru/nanit/limbo/protocol/packets/play/PacketJoinGame.java b/src/main/java/ru/nanit/limbo/protocol/packets/play/PacketJoinGame.java new file mode 100644 index 0000000..698b70e --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/packets/play/PacketJoinGame.java @@ -0,0 +1,99 @@ +package ru.nanit.limbo.protocol.packets.play; + +import net.kyori.adventure.nbt.CompoundBinaryTag; +import ru.nanit.limbo.protocol.ByteMessage; +import ru.nanit.limbo.protocol.Direction; +import ru.nanit.limbo.protocol.PacketOut; +import ru.nanit.limbo.protocol.registry.Version; + +import java.util.List; + +public class PacketJoinGame implements PacketOut { + + private int entityId; + private boolean isHardcore = false; + private int gameMode = 2; + private int previousGameMode = -1; + private int worldCount = 1; + private List worldNames; + private CompoundBinaryTag dimensionCodec; + private CompoundBinaryTag dimension; + private String worldName; + private long hashedSeed; + private int maxPlayers; + private int viewDistance = 2; + private boolean reducedDebugInfo; + private boolean enableRespawnScreen; + private boolean isDebug; + private boolean isFlat; + + public void setEntityId(int entityId) { + this.entityId = entityId; + } + + public void setHardcore(boolean hardcore) { + isHardcore = hardcore; + } + + public void setGameMode(int gameMode) { + this.gameMode = gameMode; + } + + public void setPreviousGameMode(int previousGameMode) { + this.previousGameMode = previousGameMode; + } + + public void setWorldCount(int worldCount) { + this.worldCount = worldCount; + } + + public void setWorldNames(List worldNames) { + this.worldNames = worldNames; + } + + public void setDimensionCodec(CompoundBinaryTag dimensionCodec) { + this.dimensionCodec = dimensionCodec; + } + + public void setDimension(CompoundBinaryTag dimension) { + this.dimension = dimension; + } + + public void setWorldName(String worldName) { + this.worldName = worldName; + } + + public void setHashedSeed(long hashedSeed) { + this.hashedSeed = hashedSeed; + } + + public void setMaxPlayers(int maxPlayers) { + this.maxPlayers = maxPlayers; + } + + public void setViewDistance(int viewDistance) { + this.viewDistance = viewDistance; + } + + public void setReducedDebugInfo(boolean reducedDebugInfo) { + this.reducedDebugInfo = reducedDebugInfo; + } + + public void setEnableRespawnScreen(boolean enableRespawnScreen) { + this.enableRespawnScreen = enableRespawnScreen; + } + + public void setDebug(boolean debug) { + isDebug = debug; + } + + public void setFlat(boolean flat) { + isFlat = flat; + } + + @Override + public void encode(ByteMessage msg, Direction direction, Version version) { + + } + +} diff --git a/src/main/java/ru/nanit/limbo/protocol/packets/status/PacketStatusPing.java b/src/main/java/ru/nanit/limbo/protocol/packets/status/PacketStatusPing.java new file mode 100644 index 0000000..4ede3af --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/packets/status/PacketStatusPing.java @@ -0,0 +1,22 @@ +package ru.nanit.limbo.protocol.packets.status; + +import ru.nanit.limbo.protocol.ByteMessage; +import ru.nanit.limbo.protocol.Packet; +import ru.nanit.limbo.protocol.Direction; +import ru.nanit.limbo.protocol.registry.Version; + +public class PacketStatusPing implements Packet { + + private long randomId; + + @Override + public void encode(ByteMessage msg, Direction direction, Version version) { + msg.writeLong(randomId); + } + + @Override + public void decode(ByteMessage msg, Direction direction, Version version) { + this.randomId = msg.readLong(); + } + +} diff --git a/src/main/java/ru/nanit/limbo/protocol/packets/status/PacketStatusRequest.java b/src/main/java/ru/nanit/limbo/protocol/packets/status/PacketStatusRequest.java new file mode 100644 index 0000000..ad224e4 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/packets/status/PacketStatusRequest.java @@ -0,0 +1,13 @@ +package ru.nanit.limbo.protocol.packets.status; + +import ru.nanit.limbo.protocol.*; +import ru.nanit.limbo.protocol.registry.Version; + +public class PacketStatusRequest implements PacketIn { + + @Override + public void decode(ByteMessage msg, Direction direction, Version version) { + + } + +} diff --git a/src/main/java/ru/nanit/limbo/protocol/packets/status/PacketStatusResponse.java b/src/main/java/ru/nanit/limbo/protocol/packets/status/PacketStatusResponse.java new file mode 100644 index 0000000..7e1bbea --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/packets/status/PacketStatusResponse.java @@ -0,0 +1,22 @@ +package ru.nanit.limbo.protocol.packets.status; + +import ru.nanit.limbo.LimboConfig; +import ru.nanit.limbo.protocol.*; +import ru.nanit.limbo.protocol.registry.Version; + +public class PacketStatusResponse implements PacketOut { + + private static final String TEMPLATE = "{ \"version\": { \"name\": \"%s\", \"protocol\": %d }, \"players\": { \"max\": %d, \"online\": %d, \"sample\": [] }, \"description\": %s }"; + + @Override + public void encode(ByteMessage msg, Direction direction, Version version) { + String ver = LimboConfig.getPingData().getVersion(); + String desc = LimboConfig.getPingData().getDescription(); + String json = getResponseJson(ver, version.getProtocolNumber(), LimboConfig.getMaxPlayers(), 0, desc); + msg.writeString(json); + } + + private String getResponseJson(String version, int protocol, int maxPlayers, int online, String description){ + return String.format(TEMPLATE, version, protocol, maxPlayers, online, description); + } +} diff --git a/src/main/java/ru/nanit/limbo/protocol/pipeline/PacketDecoder.java b/src/main/java/ru/nanit/limbo/protocol/pipeline/PacketDecoder.java new file mode 100644 index 0000000..b7bc147 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/pipeline/PacketDecoder.java @@ -0,0 +1,52 @@ +package ru.nanit.limbo.protocol.pipeline; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageDecoder; +import ru.nanit.limbo.protocol.*; +import ru.nanit.limbo.protocol.registry.State; +import ru.nanit.limbo.protocol.registry.Version; +import ru.nanit.limbo.util.Logger; + +import java.util.List; + +public class PacketDecoder extends MessageToMessageDecoder { + + private State.PacketVersionRegistry.PacketIdRegistry mappings; + private Version version; + + public PacketDecoder(){ + updateVersion(Version.getMinimal()); + updateState(State.HANDSHAKING); + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List out) throws Exception { + if (!ctx.channel().isActive() || mappings == null) return; + + ByteMessage msg = new ByteMessage(buf); + int packetId = msg.readVarInt(); + Packet packet = mappings.getPacket(packetId); + + if (packet != null){ + try { + packet.decode(msg, Direction.SERVER, mappings.getVersion()); + } catch (Exception e){ + Logger.warning("Cannot decode packet 0x%s: %s", Integer.toHexString(packetId), e.getMessage()); + } + + ctx.fireChannelRead(packet); + } else { + Logger.warning("Undefined incoming packet: 0x" + Integer.toHexString(packetId)); + } + } + + public void updateVersion(Version version){ + this.version = version; + } + + public void updateState(State state){ + this.mappings = state.serverBound.getRegistry(version); + } + +} diff --git a/src/main/java/ru/nanit/limbo/protocol/pipeline/PacketEncoder.java b/src/main/java/ru/nanit/limbo/protocol/pipeline/PacketEncoder.java new file mode 100644 index 0000000..cc23bcc --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/pipeline/PacketEncoder.java @@ -0,0 +1,52 @@ +package ru.nanit.limbo.protocol.pipeline; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import ru.nanit.limbo.protocol.ByteMessage; +import ru.nanit.limbo.protocol.Packet; +import ru.nanit.limbo.protocol.Direction; +import ru.nanit.limbo.protocol.registry.Version; +import ru.nanit.limbo.protocol.registry.State; +import ru.nanit.limbo.util.Logger; + +public class PacketEncoder extends MessageToByteEncoder { + + private State.PacketVersionRegistry.PacketIdRegistry mappings; + private Version version; + + public PacketEncoder(){ + updateVersion(Version.getMinimal()); + updateState(State.HANDSHAKING); + } + + @Override + protected void encode(ChannelHandlerContext ctx, Packet packet, ByteBuf out) throws Exception { + if (mappings == null) return; + + ByteMessage msg = new ByteMessage(out); + int packetId = mappings.getPacketId(packet.getClass()); + + if (packetId == -1){ + Logger.warning("Undefined packet class: %s", packet.getClass().getName()); + return; + } + + msg.writeVarInt(packetId); + + try { + packet.encode(msg, Direction.CLIENT, version); + } catch (Exception e){ + Logger.warning("Cannot encode packet 0x%s: %s", Integer.toHexString(packetId), e.getMessage()); + } + } + + public void updateVersion(Version version){ + this.version = version; + } + + public void updateState(State state){ + this.mappings = state.clientBound.getRegistry(version); + } + +} diff --git a/src/main/java/ru/nanit/limbo/protocol/pipeline/VarIntByteDecoder.java b/src/main/java/ru/nanit/limbo/protocol/pipeline/VarIntByteDecoder.java new file mode 100644 index 0000000..efa2f6d --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/pipeline/VarIntByteDecoder.java @@ -0,0 +1,42 @@ +package ru.nanit.limbo.protocol.pipeline; + +import io.netty.util.ByteProcessor; + +public class VarIntByteDecoder implements ByteProcessor { + + private int readVarInt; + private int bytesRead; + private DecodeResult result = DecodeResult.TOO_SHORT; + + @Override + public boolean process(byte k) { + readVarInt |= (k & 0x7F) << bytesRead++ * 7; + if (bytesRead > 3) { + result = DecodeResult.TOO_BIG; + return false; + } + if ((k & 0x80) != 128) { + result = DecodeResult.SUCCESS; + return false; + } + return true; + } + + public int getReadVarint() { + return readVarInt; + } + + public int getBytesRead() { + return bytesRead; + } + + public DecodeResult getResult() { + return result; + } + + public enum DecodeResult { + SUCCESS, + TOO_SHORT, + TOO_BIG + } +} \ No newline at end of file diff --git a/src/main/java/ru/nanit/limbo/protocol/pipeline/VarIntFrameDecoder.java b/src/main/java/ru/nanit/limbo/protocol/pipeline/VarIntFrameDecoder.java new file mode 100644 index 0000000..275e989 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/pipeline/VarIntFrameDecoder.java @@ -0,0 +1,46 @@ +package ru.nanit.limbo.protocol.pipeline; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; +import ru.nanit.limbo.util.Logger; + +import java.util.List; + +public class VarIntFrameDecoder extends ByteToMessageDecoder { + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { + if (!ctx.channel().isActive()) { + in.clear(); + return; + } + + final VarIntByteDecoder reader = new VarIntByteDecoder(); + + int varintEnd = in.forEachByte(reader); + + if (varintEnd == -1) { + return; + } + + if (reader.getResult() == VarIntByteDecoder.DecodeResult.SUCCESS) { + int readVarint = reader.getReadVarint(); + int bytesRead = reader.getBytesRead(); + if (readVarint < 0) { + Logger.error("BAD_LENGTH_CACHED"); + } else if (readVarint == 0) { + // skip over the empty packet and ignore it + in.readerIndex(varintEnd + 1); + } else { + int minimumRead = bytesRead + readVarint; + if (in.isReadable(minimumRead)) { + out.add(in.retainedSlice(varintEnd + 1, readVarint)); + in.skipBytes(minimumRead); + } + } + } else if (reader.getResult() == VarIntByteDecoder.DecodeResult.TOO_BIG) { + Logger.error("Too big"); + } + } +} \ No newline at end of file diff --git a/src/main/java/ru/nanit/limbo/protocol/pipeline/VarIntLengthEncoder.java b/src/main/java/ru/nanit/limbo/protocol/pipeline/VarIntLengthEncoder.java new file mode 100644 index 0000000..f6693c3 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/pipeline/VarIntLengthEncoder.java @@ -0,0 +1,24 @@ +package ru.nanit.limbo.protocol.pipeline; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import ru.nanit.limbo.protocol.ByteMessage; + +@ChannelHandler.Sharable +public class VarIntLengthEncoder extends MessageToByteEncoder { + + @Override + protected void encode(ChannelHandlerContext ctx, ByteBuf buf, ByteBuf out) throws Exception { + ByteMessage msg = new ByteMessage(out); + msg.writeVarInt(buf.readableBytes()); + msg.writeBytes(buf); + } + + @Override + protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception { + int anticipatedRequiredCapacity = 5 + msg.readableBytes(); + return ctx.alloc().heapBuffer(anticipatedRequiredCapacity); + } +} diff --git a/src/main/java/ru/nanit/limbo/protocol/registry/State.java b/src/main/java/ru/nanit/limbo/protocol/registry/State.java new file mode 100644 index 0000000..52f1651 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/registry/State.java @@ -0,0 +1,109 @@ +package ru.nanit.limbo.protocol.registry; + +import ru.nanit.limbo.protocol.Packet; +import ru.nanit.limbo.protocol.packets.*; +import ru.nanit.limbo.protocol.packets.login.*; +import ru.nanit.limbo.protocol.packets.play.PacketJoinGame; +import ru.nanit.limbo.protocol.packets.status.PacketStatusPing; +import ru.nanit.limbo.protocol.packets.status.PacketStatusRequest; +import ru.nanit.limbo.protocol.packets.status.PacketStatusResponse; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +public enum State { + + HANDSHAKING(0){ + { + serverBound.register(Version.getMinimal(), 0x00, PacketHandshake::new); + } + }, + STATUS(1){ + { + clientBound.register(Version.getMinimal(), 0x00, PacketStatusResponse::new); + clientBound.register(Version.getMinimal(), 0x01, PacketStatusPing::new); + serverBound.register(Version.getMinimal(), 0x01, PacketStatusPing::new); + serverBound.register(Version.getMinimal(), 0x00, PacketStatusRequest::new); + } + }, + LOGIN(2){ + { + clientBound.register(Version.getMinimal(), 0x00, PacketDisconnect::new); + clientBound.register(Version.getMinimal(), 0x02, PacketLoginSuccess::new); + serverBound.register(Version.getMinimal(), 0x00, PacketLoginStart::new); + } + }, + PLAY(3){ + { + clientBound.register(Version.V1_16_4, 0x24, PacketJoinGame::new); + } + }; + + private final int stateId; + public final PacketVersionRegistry serverBound = new PacketVersionRegistry(); + public final PacketVersionRegistry clientBound = new PacketVersionRegistry(); + + private static final Map STATE_BY_ID = new HashMap<>(); + + static { + for (State registry : values()){ + STATE_BY_ID.put(registry.stateId, registry); + } + } + + State(int stateId){ + this.stateId = stateId; + } + + public static State getById(int stateId){ + return STATE_BY_ID.get(stateId); + } + + public static class PacketVersionRegistry { + + private final Map> MAPPINGS = new HashMap<>(); + + public PacketIdRegistry getRegistry(Version version){ + PacketIdRegistry registry = MAPPINGS.get(version); + return registry != null ? registry : MAPPINGS.get(version.getClosest(MAPPINGS.keySet())); + } + + public void register(Version version, int packetId, Supplier supplier){ + PacketIdRegistry registry = (PacketIdRegistry) MAPPINGS.computeIfAbsent(version, PacketIdRegistry::new); + registry.register(packetId, supplier); + } + + public static class PacketIdRegistry { + + private final Version version; + private final Map> packetsById = new HashMap<>(); + private final Map, Integer> packetIdByClass = new HashMap<>(); + + public PacketIdRegistry(Version version){ + this.version = version; + } + + public Version getVersion(){ + return version; + } + + public Packet getPacket(int packetId){ + Supplier supplier = packetsById.get(packetId); + return supplier == null ? null : supplier.get(); + } + + public int getPacketId(Class packetClass){ + return packetIdByClass.getOrDefault(packetClass, -1); + } + + public void register(int packetId, Supplier supplier){ + packetsById.put(packetId, supplier); + packetIdByClass.put(supplier.get().getClass(), packetId); + } + + } + + } + +} diff --git a/src/main/java/ru/nanit/limbo/protocol/registry/Version.java b/src/main/java/ru/nanit/limbo/protocol/registry/Version.java new file mode 100644 index 0000000..bf6361e --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/registry/Version.java @@ -0,0 +1,77 @@ +package ru.nanit.limbo.protocol.registry; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public enum Version { + + UNDEFINED(-1), + V1_9(107), + V1_9_1(108), + V1_9_2(109), + V1_9_4(110), + V1_10(210), + V1_11(315), + V1_11_1(316), + V1_12(335), + V1_12_1(338), + V1_12_2(340), + V1_13(393), + V1_13_1(401), + V1_13_2(404), + V1_14(477), + V1_14_1(480), + V1_14_2(485), + V1_14_3(490), + V1_14_4(498), + V1_15(573), + V1_15_1(575), + V1_15_2(578), + V1_16(735), + V1_16_1(736), + V1_16_2(751), + V1_16_3(753), + V1_16_4(754); + + public static final Map VERSION_MAP; + + static { + VERSION_MAP = new HashMap<>(); + + for (Version version : values()){ + VERSION_MAP.put(version.getProtocolNumber(), version); + } + } + + public static Version getMinimal(){ + return V1_9; + } + + public static Version of(int protocolNumber){ + return VERSION_MAP.getOrDefault(protocolNumber, UNDEFINED); + } + + private final int protocolNumber; + + Version(int protocolNumber){ + this.protocolNumber = protocolNumber; + } + + public int getProtocolNumber(){ + return this.protocolNumber; + } + + public Version getClosest(Collection available){ + Version closest = getMinimal(); + + for (Version version : available){ + if (version.protocolNumber > closest.protocolNumber && version.protocolNumber < this.protocolNumber){ + closest = version; + } + } + + return closest; + } + +} diff --git a/src/main/java/ru/nanit/limbo/server/LimboServer.java b/src/main/java/ru/nanit/limbo/server/LimboServer.java new file mode 100644 index 0000000..293d3e9 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/server/LimboServer.java @@ -0,0 +1,24 @@ +package ru.nanit.limbo.server; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import ru.nanit.limbo.LimboConfig; +import ru.nanit.limbo.connection.ClientChannelInitializer; +import ru.nanit.limbo.util.Logger; + +public final class LimboServer { + + public void start() throws Exception { + Logger.info("Starting server..."); + + ServerBootstrap bootstrap = new ServerBootstrap() + .group(new NioEventLoopGroup(), new NioEventLoopGroup()) + .channel(NioServerSocketChannel.class) + .childHandler(new ClientChannelInitializer(this)); + + bootstrap.bind(LimboConfig.getHost(), LimboConfig.getPort()); + Logger.info("Server started on %s:%d", LimboConfig.getHost(), LimboConfig.getPort()); + } + +} diff --git a/src/main/java/ru/nanit/limbo/util/Logger.java b/src/main/java/ru/nanit/limbo/util/Logger.java new file mode 100644 index 0000000..5408f07 --- /dev/null +++ b/src/main/java/ru/nanit/limbo/util/Logger.java @@ -0,0 +1,63 @@ +package ru.nanit.limbo.util; + +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +public final class Logger { + + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("hh:mm:ss"); + + public static void info(Object msg, Object... args){ + print(Level.INFO, msg, null, args); + } + + public static void info(Object msg, Throwable t, Object... args){ + print(Level.INFO, msg, t, args); + } + + public static void warning(Object msg, Object... args){ + print(Level.WARNING, msg, null, args); + } + + public static void warning(Object msg, Throwable t, Object... args){ + print(Level.WARNING, msg, t, args); + } + + public static void error(Object msg, Object... args){ + print(Level.ERROR, msg, null, args); + } + + public static void error(Object msg, Throwable t, Object... args){ + print(Level.ERROR, msg, t, args); + } + + public static void print(Level level, Object msg, Throwable t, Object... args){ + System.out.println(String.format("%s: %s", getPrefix(level), String.format(msg.toString(), args))); + if (t != null) t.printStackTrace(); + } + + private static String getPrefix(Level level){ + return String.format("[%s] [%s]", getTime(), level.getDisplay()); + } + + private static String getTime(){ + return LocalTime.now().format(FORMATTER); + } + + public enum Level { + + INFO ("INFO"), + WARNING("WARNING"), + ERROR("ERROR"); + + private final String display; + + Level(String display){ + this.display = display; + } + + public String getDisplay() { + return display; + } + } +} diff --git a/src/main/java/ru/nanit/limbo/util/UuidUtil.java b/src/main/java/ru/nanit/limbo/util/UuidUtil.java new file mode 100644 index 0000000..27c389a --- /dev/null +++ b/src/main/java/ru/nanit/limbo/util/UuidUtil.java @@ -0,0 +1,13 @@ +package ru.nanit.limbo.util; + +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +public class UuidUtil { + + public static UUID getOfflineModeUuid(String username){ + return UUID.nameUUIDFromBytes(("OfflinePlayer:" + username) + .getBytes(StandardCharsets.UTF_8)); + } + +} diff --git a/src/main/resources/settings.properties b/src/main/resources/settings.properties new file mode 100644 index 0000000..5ff0d02 --- /dev/null +++ b/src/main/resources/settings.properties @@ -0,0 +1,16 @@ +# +# NanoLimbo configuration +# + +host=localhost +port=65535 +max-players=100 + +# Proxy forwarding support. Available types: NONE, LEGACY, MODERN +ip-forwarding=LEGACY + +# Read timeout for connections in milliseconds +read-timeout=30000 + +ping-version=NanoLimbo +ping-description={"text": "NanoLimbo"} \ No newline at end of file