diff --git a/pom.xml b/pom.xml index b5e43b8..3864046 100644 --- a/pom.xml +++ b/pom.xml @@ -77,6 +77,11 @@ guava 24.1-jre + + com.google.code.gson + gson + 2.8.2 + diff --git a/src/main/java/mc/core/ByteArrayOutputNetStream.java b/src/main/java/mc/core/ByteArrayOutputNetStream.java new file mode 100644 index 0000000..03e2902 --- /dev/null +++ b/src/main/java/mc/core/ByteArrayOutputNetStream.java @@ -0,0 +1,40 @@ +/* + * DmitriyMX + * 2018-04-08 + */ +package mc.core; + +import java.io.ByteArrayOutputStream; + +public class ByteArrayOutputNetStream extends NetStream { + private ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + @Override + public byte readByte() { + throw new UnsupportedOperationException(); + } + + @Override + public void readBytes(byte[] buffer) { + throw new UnsupportedOperationException(); + } + + @Override + public int readUnsignedShort() { + throw new UnsupportedOperationException(); + } + + @Override + public void writeByte(int value) { + baos.write(value); + } + + @Override + public void writeBytes(byte[] buffer) { + baos.write(buffer, 0, buffer.length); + } + + public byte[] toByteArray() { + return baos.toByteArray(); + } +} diff --git a/src/main/java/mc/core/Config.java b/src/main/java/mc/core/Config.java index 08b4969..247c2e0 100644 --- a/src/main/java/mc/core/Config.java +++ b/src/main/java/mc/core/Config.java @@ -7,7 +7,7 @@ package mc.core; public interface Config { int getMaxPlayers(); String getDescriptionServer(); - String getFaviconBase64(); + byte[] getFaviconBase64(); String getHost(); int getPort(); } diff --git a/src/main/java/mc/core/Main.java b/src/main/java/mc/core/Main.java index f079a2a..4385a32 100644 --- a/src/main/java/mc/core/Main.java +++ b/src/main/java/mc/core/Main.java @@ -10,11 +10,13 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; @Slf4j public class Main { - public static void main(String[] args) { - ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); - Config config = context.getBean("config", Config.class); + public static ApplicationContext appContext; //FIXME - Server server = context.getBean("server", Server.class); + public static void main(String[] args) { + appContext = new ClassPathXmlApplicationContext("spring.xml"); + Config config = appContext.getBean("config", Config.class); + + Server server = appContext.getBean("server", Server.class); try { server.start(config.getHost(), config.getPort()); } catch (StartServerException e) { diff --git a/src/main/java/mc/core/NetStream.java b/src/main/java/mc/core/NetStream.java index 898e5cb..53286b1 100644 --- a/src/main/java/mc/core/NetStream.java +++ b/src/main/java/mc/core/NetStream.java @@ -42,6 +42,18 @@ public abstract class NetStream { return result; } + public void writeVarInt(int value) { + do { + byte temp = (byte)(value & 0b01111111); + value >>>= 7; + if (value != 0) { + temp |= 0b10000000; + } + + writeByte(temp); + } while (value != 0); + } + public String readString() { int length = readVarInt(); @@ -51,7 +63,15 @@ public abstract class NetStream { return new String(buffer, StandardCharsets.UTF_8); } + public void writeString(String value) { + writeVarInt(value.length()); + writeBytes(value.getBytes()); + } + public abstract byte readByte(); public abstract void readBytes(byte[] buffer); public abstract int readUnsignedShort(); + + public abstract void writeByte(int value); + public abstract void writeBytes(byte[] buffer); } diff --git a/src/main/java/mc/core/SCPacket.java b/src/main/java/mc/core/SCPacket.java new file mode 100644 index 0000000..161b47e --- /dev/null +++ b/src/main/java/mc/core/SCPacket.java @@ -0,0 +1,9 @@ +/* + * DmitriyMX + * 2018-04-08 + */ +package mc.core; + +public interface SCPacket { + byte[] toByteArray(); +} diff --git a/src/main/java/mc/core/embedded/ConfigFromSpring.java b/src/main/java/mc/core/embedded/ConfigFromSpring.java index d5cc215..215f77a 100644 --- a/src/main/java/mc/core/embedded/ConfigFromSpring.java +++ b/src/main/java/mc/core/embedded/ConfigFromSpring.java @@ -19,7 +19,7 @@ import java.util.Base64; public class ConfigFromSpring implements Config { @Setter private String descriptionServer; - private String faviconBase64; + private byte[] faviconBase64; @Setter private int maxPlayers; @Setter @@ -30,10 +30,8 @@ public class ConfigFromSpring implements Config { public void setFaviconBase64(File faviconImageFile) { log.debug("faviconImageFile: {}", faviconImageFile.getAbsolutePath()); try { - faviconBase64 = new String( - Base64.getEncoder().encode( - FileUtils.readFileToByteArray(faviconImageFile) - ) + faviconBase64 = Base64.getEncoder().encode( + FileUtils.readFileToByteArray(faviconImageFile) ); } catch (IOException e) { log.warn("Can't load favicon", e); diff --git a/src/main/java/mc/core/netty/NettyServer.java b/src/main/java/mc/core/netty/NettyServer.java index edba79c..f5189c7 100644 --- a/src/main/java/mc/core/netty/NettyServer.java +++ b/src/main/java/mc/core/netty/NettyServer.java @@ -24,7 +24,8 @@ public class NettyServer implements Server{ socketChannel.pipeline().addLast( new LoggingHandler(), new PacketDecoder(), - new PacketHandler() + new PacketHandler(), + new PacketEncoder() ); } }; diff --git a/src/main/java/mc/core/netty/PacketEncoder.java b/src/main/java/mc/core/netty/PacketEncoder.java new file mode 100644 index 0000000..5f6673e --- /dev/null +++ b/src/main/java/mc/core/netty/PacketEncoder.java @@ -0,0 +1,32 @@ +/* + * DmitriyMX + * 2018-04-08 + */ +package mc.core.netty; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import lombok.extern.slf4j.Slf4j; +import mc.core.NetStream; +import mc.core.SCPacket; + +@Slf4j +public class PacketEncoder extends MessageToByteEncoder { + @Override + protected void encode(ChannelHandlerContext ctx, SCPacket pkt, ByteBuf out) throws Exception { + State state = ctx.channel().attr(State.ATTR_STATE).get(); + Integer id = state.getServerSidePaclet(pkt.getClass()); + if (id == null) { + log.error("Packet not found: {}:{}", state.name(), pkt.getClass().getSimpleName()); + return; + } + + byte[] bytes = pkt.toByteArray(); + NetStream netStream = new WrapperNetStream(out); + + netStream.writeVarInt(bytes.length + NetStream.sizeVarInt(id)); + netStream.writeVarInt(id); + netStream.writeBytes(bytes); + } +} diff --git a/src/main/java/mc/core/netty/PacketHandler.java b/src/main/java/mc/core/netty/PacketHandler.java index 5458007..5f89ac1 100644 --- a/src/main/java/mc/core/netty/PacketHandler.java +++ b/src/main/java/mc/core/netty/PacketHandler.java @@ -4,11 +4,16 @@ */ package mc.core.netty; +import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import mc.core.CSPacket; +import mc.core.Config; +import mc.core.Main; import mc.core.netty.packets.StatusRequest; +import mc.core.netty.packets.StatusResponse; import java.lang.reflect.Method; import java.util.Arrays; @@ -23,17 +28,27 @@ public class PacketHandler extends SimpleChannelInboundHandler { Optional optionalMethod = Arrays.stream(this.getClass().getDeclaredMethods()) .filter(method -> method.getName().equals("on" + packet.getClass().getSimpleName()) && method.getParameterCount() == 2 - && method.getParameterTypes()[0].isAssignableFrom(ctx.getClass()) + && method.getParameterTypes()[0].isAssignableFrom(Channel.class) && method.getParameterTypes()[1].isAssignableFrom(packet.getClass())) .findFirst(); if (optionalMethod.isPresent()) { Method method = optionalMethod.get(); - method.invoke(this, ctx, packet); + method.invoke(this, ctx.channel(), packet); } } - public void onStatusRequest(ChannelHandlerContext ctx, StatusRequest packet) { - log.info("Catch!"); + public void onStatusRequest(Channel channel, StatusRequest packet) { + if (!packet.getNextState().equals(State.UNKNOWN)) { + channel.attr(State.ATTR_STATE).set(packet.getNextState()); + } + + Config config = Main.appContext.getBean("config", Config.class); //FIXME + StatusResponse pkt = new StatusResponse(); + pkt.setDescription(config.getDescriptionServer()); + pkt.setMaxOnline(config.getMaxPlayers()); + pkt.setFaviconBase64(config.getFaviconBase64()); + + channel.writeAndFlush(pkt); } } diff --git a/src/main/java/mc/core/netty/State.java b/src/main/java/mc/core/netty/State.java index d118d5c..e507b48 100644 --- a/src/main/java/mc/core/netty/State.java +++ b/src/main/java/mc/core/netty/State.java @@ -8,7 +8,9 @@ import com.google.common.collect.ImmutableMap; import io.netty.util.AttributeKey; import lombok.RequiredArgsConstructor; import mc.core.CSPacket; +import mc.core.SCPacket; import mc.core.netty.packets.StatusRequest; +import mc.core.netty.packets.StatusResponse; import java.util.Arrays; import java.util.Map; @@ -16,10 +18,14 @@ import java.util.Optional; @RequiredArgsConstructor public enum State { - UNKNOWN(0, ImmutableMap.of()), - STATUS(1, ImmutableMap.of( + UNKNOWN(0, ImmutableMap.of(), ImmutableMap.of()), + STATUS(1, + ImmutableMap.of( 0, StatusRequest.class - )); + ), ImmutableMap.of( + StatusResponse.class, 0 + ) + ); public static final AttributeKey ATTR_STATE = AttributeKey.newInstance("ATTR_STATE"); @@ -33,8 +39,13 @@ public enum State { private final int id; private final Map> clientSidePackets; + private final Map, Integer> serverSidePackets; public Class getClientSidePacket(final int packetId) { return clientSidePackets.get(packetId); } + + public Integer getServerSidePaclet(final Class clazz) { + return serverSidePackets.get(clazz); + } } diff --git a/src/main/java/mc/core/netty/WrapperNetStream.java b/src/main/java/mc/core/netty/WrapperNetStream.java index e69b067..34039da 100644 --- a/src/main/java/mc/core/netty/WrapperNetStream.java +++ b/src/main/java/mc/core/netty/WrapperNetStream.java @@ -26,4 +26,14 @@ public class WrapperNetStream extends NetStream { public int readUnsignedShort() { return byteBuf.readUnsignedShort(); } + + @Override + public void writeByte(int value) { + byteBuf.writeByte(value); + } + + @Override + public void writeBytes(byte[] buffer) { + byteBuf.writeBytes(buffer); + } } diff --git a/src/main/java/mc/core/netty/packets/StatusResponse.java b/src/main/java/mc/core/netty/packets/StatusResponse.java new file mode 100644 index 0000000..a494914 --- /dev/null +++ b/src/main/java/mc/core/netty/packets/StatusResponse.java @@ -0,0 +1,56 @@ +/* + * DmitriyMX + * 2018-04-08 + */ +package mc.core.netty.packets; + +import com.google.gson.JsonObject; +import lombok.Setter; +import lombok.ToString; +import mc.core.ByteArrayOutputNetStream; +import mc.core.SCPacket; + +@ToString +public class StatusResponse implements SCPacket { + private static final String name = "1.12.2"; + private static final int protocol = 340; + + @Setter + private int maxOnline; + @Setter + private int online; + @Setter + private String description; + @Setter + private byte[] faviconBase64; + + @Override + public byte[] toByteArray() { + ByteArrayOutputNetStream netStream = new ByteArrayOutputNetStream(); + + JsonObject versionObj = new JsonObject(); + versionObj.addProperty("name", name); + versionObj.addProperty("protocol", protocol); + + JsonObject playersObj = new JsonObject(); + playersObj.addProperty("max", maxOnline); + playersObj.addProperty("online", online); + + JsonObject descriptionObj = new JsonObject(); + descriptionObj.addProperty("text", description); + + JsonObject rootObj = new JsonObject(); + rootObj.add("version", versionObj); + rootObj.add("players", playersObj); + rootObj.add("description", descriptionObj); + + if (faviconBase64 != null && faviconBase64.length > 0) { + rootObj.addProperty("favicon", + "data:image/png;base64," + new String(faviconBase64) + ); + } + + netStream.writeString(rootObj.toString()); + return netStream.toByteArray(); + } +}