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 extends CSPacket> getClientSidePacket(final int packetId) {
return clientSidePackets.get(packetId);
}
+
+ public Integer getServerSidePaclet(final Class extends SCPacket> 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();
+ }
+}