Initial commit. Very broken and only does Server List Ping!

This commit is contained in:
Andrew Steinborn
2018-07-24 14:08:55 -04:00
commit 666d07e2a8
29 changed files with 1264 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
package io.minimum.minecraft.velocity;
import io.minimum.minecraft.velocity.protocol.ProtocolConstants;
import io.minimum.minecraft.velocity.protocol.netty.*;
import io.minimum.minecraft.velocity.proxy.MinecraftClientSessionHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class Velocity {
public static void main(String... args) throws InterruptedException {
new ServerBootstrap()
.channel(NioServerSocketChannel.class)
.group(new NioEventLoopGroup())
.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast("legacy-ping-decode", new LegacyPingDecoder());
ch.pipeline().addLast("frame-decoder", new MinecraftVarintFrameDecoder());
ch.pipeline().addLast("legacy-ping-encode", new LegacyPingEncoder());
ch.pipeline().addLast("frame-encoder", new MinecraftVarintLengthEncoder());
ch.pipeline().addLast("minecraft-decoder", new MinecraftDecoder(ProtocolConstants.Direction.TO_SERVER));
ch.pipeline().addLast("minecraft-encoder", new MinecraftEncoder(ProtocolConstants.Direction.TO_CLIENT));
ch.pipeline().addLast("handler", new MinecraftClientSessionHandler());
}
})
.bind(26671)
.await();
}
}

View File

@@ -0,0 +1,95 @@
package io.minimum.minecraft.velocity.data;
import net.kyori.text.Component;
public class ServerPing {
private final Version version;
private final Players players;
private final Component description;
private final String favicon;
public ServerPing(Version version, Players players, Component description, String favicon) {
this.version = version;
this.players = players;
this.description = description;
this.favicon = favicon;
}
public Version getVersion() {
return version;
}
public Players getPlayers() {
return players;
}
public Component getDescription() {
return description;
}
public String getFavicon() {
return favicon;
}
@Override
public String toString() {
return "ServerPing{" +
"version=" + version +
", players=" + players +
", description=" + description +
", favicon='" + favicon + '\'' +
'}';
}
public static class Version {
private final int protocol;
private final String version;
public Version(int protocol, String version) {
this.protocol = protocol;
this.version = version;
}
public int getProtocol() {
return protocol;
}
public String getVersion() {
return version;
}
@Override
public String toString() {
return "Version{" +
"protocol=" + protocol +
", version='" + version + '\'' +
'}';
}
}
public static class Players {
private final int online;
private final int max;
public Players(int online, int max) {
this.online = online;
this.max = max;
}
public int getOnline() {
return online;
}
public int getMax() {
return max;
}
@Override
public String toString() {
return "Players{" +
"online=" + online +
", max=" + max +
'}';
}
}
}

View File

@@ -0,0 +1,9 @@
package io.minimum.minecraft.velocity.protocol;
import io.netty.buffer.ByteBuf;
public interface MinecraftPacket {
void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion);
void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion);
}

View File

@@ -0,0 +1,30 @@
package io.minimum.minecraft.velocity.protocol;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
public class PacketWrapper {
private final MinecraftPacket packet;
private final ByteBuf buffer;
public PacketWrapper(MinecraftPacket packet, ByteBuf buffer) {
this.packet = packet;
this.buffer = buffer;
}
public MinecraftPacket getPacket() {
return packet;
}
public ByteBuf getBuffer() {
return buffer;
}
@Override
public String toString() {
return "PacketWrapper{" +
"packet=" + packet +
", buffer=" + ByteBufUtil.hexDump(buffer) +
'}';
}
}

View File

@@ -0,0 +1,10 @@
package io.minimum.minecraft.velocity.protocol;
public enum ProtocolConstants { ;
public static final int MINECRAFT_1_12 = 340;
public enum Direction {
TO_SERVER,
TO_CLIENT
}
}

View File

@@ -0,0 +1,58 @@
package io.minimum.minecraft.velocity.protocol;
import com.google.common.base.Preconditions;
import io.netty.buffer.ByteBuf;
import java.nio.charset.StandardCharsets;
public enum ProtocolUtils { ;
private static final int DEFAULT_MAX_STRING_SIZE = 1024 * 1024; // 1MB
public static int readVarInt(ByteBuf buf) {
int numRead = 0;
int result = 0;
byte read;
do {
read = buf.readByte();
int value = (read & 0b01111111);
result |= (value << (7 * numRead));
numRead++;
if (numRead > 5) {
throw new RuntimeException("VarInt is too big");
}
} while ((read & 0b10000000) != 0);
return result;
}
public static void writeVarInt(ByteBuf buf, int value) {
do {
byte temp = (byte)(value & 0b01111111);
// Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone
value >>>= 7;
if (value != 0) {
temp |= 0b10000000;
}
buf.writeByte(temp);
} while (value != 0);
}
public static String readString(ByteBuf buf) {
return readString(buf, DEFAULT_MAX_STRING_SIZE);
}
public static String readString(ByteBuf buf, int cap) {
int length = readVarInt(buf);
Preconditions.checkArgument(length < cap, "Bad string size (got %s, maximum is %s)", length, cap);
byte[] str = new byte[length];
buf.readBytes(str);
return new String(str, StandardCharsets.UTF_8);
}
public static void writeString(ByteBuf buf, String str) {
byte[] asUtf8 = str.getBytes(StandardCharsets.UTF_8);
writeVarInt(buf, asUtf8.length);
buf.writeBytes(asUtf8);
}
}

View File

@@ -0,0 +1,63 @@
package io.minimum.minecraft.velocity.protocol;
import io.minimum.minecraft.velocity.protocol.packets.Handshake;
import io.minimum.minecraft.velocity.protocol.packets.Ping;
import io.minimum.minecraft.velocity.protocol.packets.StatusRequest;
import io.minimum.minecraft.velocity.protocol.packets.StatusResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public enum StateRegistry {
HANDSHAKE {
{
TO_SERVER.register(0x00, Handshake.class, Handshake::new);
}
},
STATUS {
{
TO_SERVER.register(0x00, StatusRequest.class, StatusRequest::new);
TO_SERVER.register(0x01, Ping.class, Ping::new);
TO_CLIENT.register(0x00, StatusResponse.class, StatusResponse::new);
TO_CLIENT.register(0x01, Ping.class, Ping::new);
}
};
public final ProtocolMappings TO_CLIENT = new ProtocolMappings(ProtocolConstants.Direction.TO_CLIENT, this);
public final ProtocolMappings TO_SERVER = new ProtocolMappings(ProtocolConstants.Direction.TO_SERVER, this);
public static class ProtocolMappings {
private final ProtocolConstants.Direction direction;
private final StateRegistry state;
private final Map<Integer, Supplier<? extends MinecraftPacket>> idsToSuppliers = new HashMap<>();
private final Map<Class<? extends MinecraftPacket>, Integer> packetClassesToIds = new HashMap<>();
public ProtocolMappings(ProtocolConstants.Direction direction, StateRegistry state) {
this.direction = direction;
this.state = state;
}
public void register(int id, Class<? extends MinecraftPacket> clazz, Supplier<? extends MinecraftPacket> packetSupplier) {
idsToSuppliers.put(id, packetSupplier);
packetClassesToIds.put(clazz, id);
}
public MinecraftPacket createPacket(int id) {
Supplier<? extends MinecraftPacket> supplier = idsToSuppliers.get(id);
if (supplier == null) {
return null;
}
return supplier.get();
}
public int getId(MinecraftPacket packet) {
Integer id = packetClassesToIds.get(packet.getClass());
if (id == null) {
throw new IllegalArgumentException("Supplied packet " + packet.getClass().getName() + " doesn't have a mapping. Direction " + direction + " State " + state);
}
return id;
}
}
}

View File

@@ -0,0 +1,26 @@
package io.minimum.minecraft.velocity.protocol.netty;
import io.minimum.minecraft.velocity.protocol.packets.LegacyPing;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
public class LegacyPingDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (in.readableBytes() < 2) {
return;
}
short first = in.getUnsignedByte(in.readerIndex());
short second = in.getUnsignedByte(in.readerIndex() + 1);
if (first == 0xfe && second == 0x01) {
in.skipBytes(in.readableBytes());
out.add(new LegacyPing());
}
ctx.pipeline().remove(this);
}
}

View File

@@ -0,0 +1,36 @@
package io.minimum.minecraft.velocity.protocol.netty;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import io.minimum.minecraft.velocity.protocol.packets.LegacyPingResponse;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class LegacyPingEncoder extends MessageToByteEncoder<LegacyPingResponse> {
@Override
protected void encode(ChannelHandlerContext ctx, LegacyPingResponse msg, ByteBuf out) throws Exception {
out.writeByte(0xff);
String serializedResponse = serialize(msg);
byte[] serializedBytes = serializedResponse.getBytes(StandardCharsets.UTF_16BE);
out.writeShort(serializedBytes.length);
out.writeBytes(serializedBytes);
System.out.println(ByteBufUtil.prettyHexDump(out));
}
private String serialize(LegacyPingResponse response) {
List<String> parts = ImmutableList.of(
"§1",
Integer.toString(response.getProtocolVersion()),
response.getServerVersion(),
response.getMotd(),
Integer.toString(response.getPlayersOnline()),
Integer.toString(response.getPlayersMax())
);
return Joiner.on('\0').join(parts);
}
}

View File

@@ -0,0 +1,64 @@
package io.minimum.minecraft.velocity.protocol.netty;
import com.google.common.base.Preconditions;
import io.minimum.minecraft.velocity.protocol.*;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import java.util.List;
public class MinecraftDecoder extends MessageToMessageDecoder<ByteBuf> {
private StateRegistry state;
private final ProtocolConstants.Direction direction;
private int protocolVersion;
public MinecraftDecoder(ProtocolConstants.Direction direction) {
this.state = StateRegistry.HANDSHAKE;
this.direction = Preconditions.checkNotNull(direction, "direction");
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
if (msg.isReadable()) {
return;
}
ByteBuf slice = msg.slice().retain();
int packetId = ProtocolUtils.readVarInt(msg);
StateRegistry.ProtocolMappings mappings = direction == ProtocolConstants.Direction.TO_CLIENT ? state.TO_CLIENT : state.TO_SERVER;
MinecraftPacket packet = mappings.createPacket(packetId);
System.out.println("Decode!");
System.out.println("packet ID: " + packetId);
System.out.println("packet hexdump: " + ByteBufUtil.hexDump(slice));
if (packet == null) {
msg.skipBytes(msg.readableBytes());
out.add(new PacketWrapper(null, slice));
} else {
packet.decode(msg, direction, protocolVersion);
out.add(new PacketWrapper(packet, slice));
}
}
public int getProtocolVersion() {
return protocolVersion;
}
public void setProtocolVersion(int protocolVersion) {
this.protocolVersion = protocolVersion;
}
public StateRegistry getState() {
return state;
}
public void setState(StateRegistry state) {
this.state = state;
}
public ProtocolConstants.Direction getDirection() {
return direction;
}
}

View File

@@ -0,0 +1,46 @@
package io.minimum.minecraft.velocity.protocol.netty;
import com.google.common.base.Preconditions;
import io.minimum.minecraft.velocity.protocol.*;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
public class MinecraftEncoder extends MessageToByteEncoder<MinecraftPacket> {
private StateRegistry state;
private final ProtocolConstants.Direction direction;
private int protocolVersion;
public MinecraftEncoder(ProtocolConstants.Direction direction) {
this.state = StateRegistry.HANDSHAKE;
this.direction = Preconditions.checkNotNull(direction, "direction");
}
@Override
protected void encode(ChannelHandlerContext ctx, MinecraftPacket msg, ByteBuf out) throws Exception {
StateRegistry.ProtocolMappings mappings = direction == ProtocolConstants.Direction.TO_CLIENT ? state.TO_CLIENT : state.TO_SERVER;
int packetId = mappings.getId(msg);
ProtocolUtils.writeVarInt(out, packetId);
msg.encode(out, direction, protocolVersion);
}
public int getProtocolVersion() {
return protocolVersion;
}
public void setProtocolVersion(int protocolVersion) {
this.protocolVersion = protocolVersion;
}
public StateRegistry getState() {
return state;
}
public void setState(StateRegistry state) {
this.state = state;
}
public ProtocolConstants.Direction getDirection() {
return direction;
}
}

View File

@@ -0,0 +1,28 @@
package io.minimum.minecraft.velocity.protocol.netty;
import io.minimum.minecraft.velocity.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (in.readableBytes() < 1) {
return;
}
in.markReaderIndex();
int packetLength = ProtocolUtils.readVarInt(in);
if (in.readableBytes() < packetLength) {
in.resetReaderIndex();
return;
}
System.out.println("Got a varint-prefixed packet length " + packetLength);
out.add(in.slice(in.readerIndex(), packetLength).retain());
in.skipBytes(packetLength);
}
}

View File

@@ -0,0 +1,14 @@
package io.minimum.minecraft.velocity.protocol.netty;
import io.minimum.minecraft.velocity.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
public class MinecraftVarintLengthEncoder extends MessageToByteEncoder<ByteBuf> {
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
ProtocolUtils.writeVarInt(out, msg.readableBytes());
out.writeBytes(msg);
}
}

View File

@@ -0,0 +1,71 @@
package io.minimum.minecraft.velocity.protocol.packets;
import io.minimum.minecraft.velocity.protocol.MinecraftPacket;
import io.minimum.minecraft.velocity.protocol.ProtocolConstants;
import io.minimum.minecraft.velocity.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class Handshake implements MinecraftPacket {
private int protocolVersion;
private String serverAddress;
private int port;
private int nextStatus;
public int getProtocolVersion() {
return protocolVersion;
}
public void setProtocolVersion(int protocolVersion) {
this.protocolVersion = protocolVersion;
}
public String getServerAddress() {
return serverAddress;
}
public void setServerAddress(String serverAddress) {
this.serverAddress = serverAddress;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public int getNextStatus() {
return nextStatus;
}
public void setNextStatus(int nextStatus) {
this.nextStatus = nextStatus;
}
@Override
public String toString() {
return "Handshake{" +
"protocolVersion=" + protocolVersion +
", serverAddress='" + serverAddress + '\'' +
", port=" + port +
", nextStatus=" + nextStatus +
'}';
}
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
protocolVersion = ProtocolUtils.readVarInt(buf);
serverAddress = ProtocolUtils.readString(buf, 255);
port = buf.readUnsignedShort();
nextStatus = ProtocolUtils.readVarInt(buf);
}
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
ProtocolUtils.writeVarInt(buf, protocolVersion);
ProtocolUtils.writeString(buf, serverAddress);
buf.writeShort(port);
ProtocolUtils.writeVarInt(buf, nextStatus);
}
}

View File

@@ -0,0 +1,4 @@
package io.minimum.minecraft.velocity.protocol.packets;
public class LegacyPing {
}

View File

@@ -0,0 +1,82 @@
package io.minimum.minecraft.velocity.protocol.packets;
import io.minimum.minecraft.velocity.data.ServerPing;
import net.kyori.text.serializer.ComponentSerializers;
public class LegacyPingResponse {
private int protocolVersion;
private String serverVersion;
private String motd;
private int playersOnline;
private int playersMax;
public LegacyPingResponse() {
}
public LegacyPingResponse(int protocolVersion, String serverVersion, String motd, int playersOnline, int playersMax) {
this.protocolVersion = protocolVersion;
this.serverVersion = serverVersion;
this.motd = motd;
this.playersOnline = playersOnline;
this.playersMax = playersMax;
}
public int getProtocolVersion() {
return protocolVersion;
}
public void setProtocolVersion(int protocolVersion) {
this.protocolVersion = protocolVersion;
}
public String getServerVersion() {
return serverVersion;
}
public void setServerVersion(String serverVersion) {
this.serverVersion = serverVersion;
}
public String getMotd() {
return motd;
}
public void setMotd(String motd) {
this.motd = motd;
}
public int getPlayersOnline() {
return playersOnline;
}
public void setPlayersOnline(int playersOnline) {
this.playersOnline = playersOnline;
}
public int getPlayersMax() {
return playersMax;
}
public void setPlayersMax(int playersMax) {
this.playersMax = playersMax;
}
@Override
public String toString() {
return "LegacyPingResponse{" +
"protocolVersion=" + protocolVersion +
", serverVersion='" + serverVersion + '\'' +
", motd='" + motd + '\'' +
", playersOnline=" + playersOnline +
", playersMax=" + playersMax +
'}';
}
public static LegacyPingResponse from(ServerPing ping) {
return new LegacyPingResponse(ping.getVersion().getProtocol(),
ping.getVersion().getVersion(),
ComponentSerializers.LEGACY.serialize(ping.getDescription()),
ping.getPlayers().getOnline(),
ping.getPlayers().getMax());
}
}

View File

@@ -0,0 +1,34 @@
package io.minimum.minecraft.velocity.protocol.packets;
import io.minimum.minecraft.velocity.protocol.MinecraftPacket;
import io.minimum.minecraft.velocity.protocol.ProtocolConstants;
import io.netty.buffer.ByteBuf;
public class Ping implements MinecraftPacket {
private long randomId;
public long getRandomId() {
return randomId;
}
public void setRandomId(long randomId) {
this.randomId = randomId;
}
@Override
public String toString() {
return "Ping{" +
"randomId=" + randomId +
'}';
}
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
randomId = buf.readLong();
}
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
buf.writeLong(randomId);
}
}

View File

@@ -0,0 +1,17 @@
package io.minimum.minecraft.velocity.protocol.packets;
import io.minimum.minecraft.velocity.protocol.MinecraftPacket;
import io.minimum.minecraft.velocity.protocol.ProtocolConstants;
import io.netty.buffer.ByteBuf;
public class StatusRequest implements MinecraftPacket {
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
}
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
}
}

View File

@@ -0,0 +1,35 @@
package io.minimum.minecraft.velocity.protocol.packets;
import io.minimum.minecraft.velocity.protocol.MinecraftPacket;
import io.minimum.minecraft.velocity.protocol.ProtocolConstants;
import io.minimum.minecraft.velocity.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class StatusResponse implements MinecraftPacket {
private String status;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
@Override
public String toString() {
return "StatusResponse{" +
"status='" + status + '\'' +
'}';
}
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
status = ProtocolUtils.readString(buf, Short.MAX_VALUE);
}
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
ProtocolUtils.writeString(buf, status);
}
}

View File

@@ -0,0 +1,93 @@
package io.minimum.minecraft.velocity.proxy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.minimum.minecraft.velocity.data.ServerPing;
import io.minimum.minecraft.velocity.protocol.MinecraftPacket;
import io.minimum.minecraft.velocity.protocol.PacketWrapper;
import io.minimum.minecraft.velocity.protocol.StateRegistry;
import io.minimum.minecraft.velocity.protocol.netty.MinecraftDecoder;
import io.minimum.minecraft.velocity.protocol.netty.MinecraftEncoder;
import io.minimum.minecraft.velocity.protocol.packets.*;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.SimpleChannelInboundHandler;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;
import net.kyori.text.serializer.GsonComponentSerializer;
public class MinecraftClientSessionHandler extends ChannelInboundHandlerAdapter {
private static final Gson GSON = new GsonBuilder()
.registerTypeHierarchyAdapter(Component.class, new GsonComponentSerializer())
.create();
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof PacketWrapper) {
try {
handle(ctx, (PacketWrapper) msg);
} finally {
((PacketWrapper) msg).getBuffer().release();
}
}
if (msg instanceof LegacyPing) {
System.out.println("Got LEGACY status request!");
ServerPing ping = new ServerPing(
new ServerPing.Version(340, "1.12"),
new ServerPing.Players(0, 0),
TextComponent.of("this is a test"),
null
);
LegacyPingResponse response = LegacyPingResponse.from(ping);
ctx.writeAndFlush(response);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
private void handle(ChannelHandlerContext ctx, PacketWrapper msg) {
MinecraftPacket packet = msg.getPacket();
if (packet == null) {
System.out.println("no packet!");
return;
}
if (packet instanceof Handshake) {
System.out.println("Handshake: " + packet);
switch (((Handshake) packet).getNextStatus()) {
case 1:
// status
ctx.pipeline().get(MinecraftDecoder.class).setState(StateRegistry.STATUS);
ctx.pipeline().get(MinecraftEncoder.class).setState(StateRegistry.STATUS);
break;
case 2:
// login
throw new UnsupportedOperationException("Login not supported yet");
}
}
if (packet instanceof StatusRequest) {
System.out.println("Got status request!");
ServerPing ping = new ServerPing(
new ServerPing.Version(340, "1.12.2"),
new ServerPing.Players(0, 0),
TextComponent.of("test"),
null
);
StatusResponse response = new StatusResponse();
response.setStatus(GSON.toJson(ping));
ctx.writeAndFlush(response, ctx.voidPromise());
}
if (packet instanceof Ping) {
System.out.println("Ping: " + packet);
ctx.writeAndFlush(packet).addListener(ChannelFutureListener.CLOSE);
}
}
}