diff --git a/build.gradle b/build.gradle index 0205135..e5b7eca 100644 --- a/build.gradle +++ b/build.gradle @@ -13,3 +13,35 @@ repositories { mavenLocal() mavenCentral() } + +ext { + slf4j_version = '1.7.30' + logback_version = '1.2.3' + + library = [ + guice: ['com.google.inject:guice:4.1.0'], + logger: ["ch.qos.logback:logback-core:$logback_version", + "ch.qos.logback:logback-classic:$logback_version"], + lombok: ['org.projectlombok:lombok:1.18.2'], + netty: ['io.netty:netty-all:4.1.22.Final'], + slf4j: ["org.slf4j:slf4j-api:$slf4j_version", + "org.slf4j:jcl-over-slf4j:$slf4j_version"], + commons: ['commons-io:commons-io:2.6'] + ] +} + +dependencies { + /* LOGGER */ + implementation library.slf4j + implementation library.logger + + /* LOMBOK */ + annotationProcessor library.lombok + compileOnly library.lombok + + /* COMPONENTS */ + implementation library.guice + implementation library.netty + implementation library.commons + implementation project(':protocol') +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 766e691..f234c19 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,4 @@ -rootProject.name = projectName \ No newline at end of file +rootProject.name = projectName + +include ':protocol' +project(':protocol').projectDir = new File(settingsDir, '../mc-protocol') \ No newline at end of file diff --git a/src/main/java/mc/server/Main.java b/src/main/java/mc/server/Main.java index 107397e..1ea7689 100644 --- a/src/main/java/mc/server/Main.java +++ b/src/main/java/mc/server/Main.java @@ -1,8 +1,16 @@ package mc.server; +import com.google.inject.Guice; +import com.google.inject.Injector; +import mc.server.config.NetworkModule; +import mc.server.network.Server; + public class Main { public static void main(String[] args) { - System.out.println("hello?"); + final Injector injector = Guice.createInjector(new NetworkModule()); + + final Server server = injector.getInstance(Server.class); + server.start("127.0.0.1", 25565); } } diff --git a/src/main/java/mc/server/config/NetworkModule.java b/src/main/java/mc/server/config/NetworkModule.java new file mode 100644 index 0000000..19c4b70 --- /dev/null +++ b/src/main/java/mc/server/config/NetworkModule.java @@ -0,0 +1,65 @@ +package mc.server.config; + +import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import com.google.inject.Singleton; +import com.google.inject.name.Named; +import com.google.inject.name.Names; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelHandler; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.logging.LoggingHandler; +import mc.protocol.PacketDirection; +import mc.protocol.io.coder.ProtocolDecoder; +import mc.protocol.io.coder.ProtocolEncoder; +import mc.protocol.io.coder.ProtocolSplitter; +import mc.server.network.Server; +import mc.server.network.impl.ChannelInitializer; +import mc.server.network.impl.NettyServer; +import mc.server.network.impl.codec.PacketDecoder; +import mc.server.network.impl.codec.PacketEncoder; +import mc.server.network.impl.codec.PacketSplitter; +import mc.server.network.impl.handler.HandshakeHandler; + +import java.util.HashMap; +import java.util.Map; + +public class NetworkModule extends AbstractModule { + + @Override + protected void configure() { + bind(Server.class).to(NettyServer.class).in(Singleton.class); + bind(EventLoopGroup.class).annotatedWith(Names.named("bossGroup")).toInstance(new NioEventLoopGroup(1)); + bind(EventLoopGroup.class).annotatedWith(Names.named("workerGroup")).toInstance(new NioEventLoopGroup()); + } + + @Provides + @Singleton + ServerBootstrap serverBootstrap(@Named("bossGroup") EventLoopGroup bossGroup, + @Named("workerGroup") EventLoopGroup workerGroup, + ChannelInitializer channelChannelInitializer) { + ServerBootstrap bootstrap = new ServerBootstrap(); + + bootstrap.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .childHandler(channelChannelInitializer); + + return bootstrap; + } + + @Provides + @Named("channelHandlerMap") + Map channelHandlerMap(HandshakeHandler handshakeHandler) { + final Map map = new HashMap<>(); + + map.put("logger", new LoggingHandler()); + map.put("packet_splitter", new PacketSplitter(new ProtocolSplitter())); + map.put("packet_decoder", new PacketDecoder(new ProtocolDecoder(PacketDirection.SERVER_BOUND))); + map.put("packet_encoder", new PacketEncoder(new ProtocolEncoder(PacketDirection.CLIENT_BOUND))); + map.put("handshake_handler", handshakeHandler); + + return map; + } +} diff --git a/src/main/java/mc/server/network/Server.java b/src/main/java/mc/server/network/Server.java new file mode 100644 index 0000000..4b08ccc --- /dev/null +++ b/src/main/java/mc/server/network/Server.java @@ -0,0 +1,6 @@ +package mc.server.network; + +public interface Server { + + void start(String host, int port); +} diff --git a/src/main/java/mc/server/network/impl/ChannelInitializer.java b/src/main/java/mc/server/network/impl/ChannelInitializer.java new file mode 100644 index 0000000..28f1d50 --- /dev/null +++ b/src/main/java/mc/server/network/impl/ChannelInitializer.java @@ -0,0 +1,29 @@ +package mc.server.network.impl; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; +import com.google.inject.name.Named; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.SocketChannel; + +import java.util.Map; + +@Singleton +public class ChannelInitializer extends io.netty.channel.ChannelInitializer { + + private final Provider> channelHandlerMapProvider; + + @Inject + public ChannelInitializer( + @Named("channelHandlerMap") Provider> channelHandlerMapProvider) { + this.channelHandlerMapProvider = channelHandlerMapProvider; + } + + @Override + protected void initChannel(SocketChannel socketChannel) { + final ChannelPipeline pipeline = socketChannel.pipeline(); + channelHandlerMapProvider.get().forEach(pipeline::addLast); + } +} diff --git a/src/main/java/mc/server/network/impl/NettyConstants.java b/src/main/java/mc/server/network/impl/NettyConstants.java new file mode 100644 index 0000000..17b8a16 --- /dev/null +++ b/src/main/java/mc/server/network/impl/NettyConstants.java @@ -0,0 +1,12 @@ +package mc.server.network.impl; + +import io.netty.util.AttributeKey; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import mc.protocol.State; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class NettyConstants { + + public static final AttributeKey ATTR_STATE = AttributeKey.newInstance("ATTR_STATE"); +} diff --git a/src/main/java/mc/server/network/impl/NettyServer.java b/src/main/java/mc/server/network/impl/NettyServer.java new file mode 100644 index 0000000..2b580ae --- /dev/null +++ b/src/main/java/mc/server/network/impl/NettyServer.java @@ -0,0 +1,29 @@ +package mc.server.network.impl; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import io.netty.bootstrap.ServerBootstrap; +import lombok.extern.slf4j.Slf4j; +import mc.server.network.Server; + + +@Slf4j +public class NettyServer implements Server { + + @Inject + private Provider serverBootstrapProvider; + + @Override + public void start(String host, int port) { + try { + log.info("Network starting: {}:{}", host, port); + serverBootstrapProvider.get() + .bind(host, port) + .sync().channel().closeFuture().sync(); + } catch (InterruptedException e) { + if (log.isTraceEnabled()) { + log.trace("{}: {}", e.getClass().getSimpleName(), e.getMessage(), e); + } + } + } +} diff --git a/src/main/java/mc/server/network/impl/codec/PacketDecoder.java b/src/main/java/mc/server/network/impl/codec/PacketDecoder.java new file mode 100644 index 0000000..8d383af --- /dev/null +++ b/src/main/java/mc/server/network/impl/codec/PacketDecoder.java @@ -0,0 +1,43 @@ +package mc.server.network.impl.codec; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; +import lombok.RequiredArgsConstructor; +import mc.protocol.Packet; +import mc.protocol.State; +import mc.protocol.io.coder.ProtocolDecoder; +import mc.server.network.impl.io.ByteBufNetInputStream; + +import java.util.List; +import java.util.Objects; + +import static mc.server.network.impl.NettyConstants.ATTR_STATE; + +@RequiredArgsConstructor +public class PacketDecoder extends ByteToMessageDecoder { + + private final ProtocolDecoder protocolDecoder; + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + ctx.channel().attr(ATTR_STATE).set(State.HANDSHAKING); + super.channelActive(ctx); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + ctx.channel().attr(ATTR_STATE).set(null); + super.channelInactive(ctx); + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { + final State state = Objects.requireNonNull(ctx.channel().attr(ATTR_STATE).get()); + + final Packet packet = protocolDecoder.decode(state, new ByteBufNetInputStream(in)); + if (packet != null) { + out.add(packet); + } + } +} diff --git a/src/main/java/mc/server/network/impl/codec/PacketEncoder.java b/src/main/java/mc/server/network/impl/codec/PacketEncoder.java new file mode 100644 index 0000000..3e0fb7d --- /dev/null +++ b/src/main/java/mc/server/network/impl/codec/PacketEncoder.java @@ -0,0 +1,24 @@ +package mc.server.network.impl.codec; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import lombok.RequiredArgsConstructor; +import mc.protocol.Packet; +import mc.protocol.State; +import mc.protocol.io.coder.ProtocolEncoder; +import mc.server.network.impl.io.ByteBufNetOutputStream; + +import static mc.server.network.impl.NettyConstants.ATTR_STATE; + +@RequiredArgsConstructor +public class PacketEncoder extends MessageToByteEncoder { + + private final ProtocolEncoder protocolEncoder; + + @Override + protected void encode(ChannelHandlerContext ctx, Packet packet, ByteBuf out) { + final State state = ctx.channel().attr(ATTR_STATE).get(); + protocolEncoder.encode(state, packet, new ByteBufNetOutputStream(out)); + } +} diff --git a/src/main/java/mc/server/network/impl/codec/PacketSplitter.java b/src/main/java/mc/server/network/impl/codec/PacketSplitter.java new file mode 100644 index 0000000..6589c0a --- /dev/null +++ b/src/main/java/mc/server/network/impl/codec/PacketSplitter.java @@ -0,0 +1,29 @@ +package mc.server.network.impl.codec; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; +import lombok.RequiredArgsConstructor; +import mc.protocol.io.NetInputStream; +import mc.protocol.io.coder.ProtocolSplitter; +import mc.server.network.impl.io.ByteBufNetInputStream; + +import java.util.List; + +@RequiredArgsConstructor +public class PacketSplitter extends ByteToMessageDecoder { + + private final ProtocolSplitter protocolSplitter; + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { + final NetInputStream netInputStream = protocolSplitter.split(new ByteBufNetInputStream(in)); + + if (netInputStream != null) { + byte[] buff = new byte[netInputStream.readableBytes()]; + netInputStream.readBytes(buff); + out.add(Unpooled.wrappedBuffer(buff)); + } + } +} diff --git a/src/main/java/mc/server/network/impl/handler/AbstractPacketHandler.java b/src/main/java/mc/server/network/impl/handler/AbstractPacketHandler.java new file mode 100644 index 0000000..09fa7f8 --- /dev/null +++ b/src/main/java/mc/server/network/impl/handler/AbstractPacketHandler.java @@ -0,0 +1,15 @@ +package mc.server.network.impl.handler; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import mc.protocol.Packet; + +public abstract class AbstractPacketHandler

extends SimpleChannelInboundHandler

{ + + @Override + protected void channelRead0(ChannelHandlerContext ctx, P msg) throws Exception { + channelRead1(ctx, msg); + } + + protected abstract void channelRead1(ChannelHandlerContext ctx, P packet) throws Exception; +} diff --git a/src/main/java/mc/server/network/impl/handler/HandshakeHandler.java b/src/main/java/mc/server/network/impl/handler/HandshakeHandler.java new file mode 100644 index 0000000..b055295 --- /dev/null +++ b/src/main/java/mc/server/network/impl/handler/HandshakeHandler.java @@ -0,0 +1,27 @@ +package mc.server.network.impl.handler; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import io.netty.channel.ChannelHandlerContext; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import mc.protocol.handshake.client.HandshakePacket; + +import static mc.server.network.impl.NettyConstants.ATTR_STATE; + +@Slf4j +@RequiredArgsConstructor(onConstructor = @__({ @Inject })) +public class HandshakeHandler extends AbstractPacketHandler { + + private final Provider statusHandlerProvider; + private final Provider pingHandlerProvider; + + @Override + protected void channelRead1(ChannelHandlerContext ctx, HandshakePacket packet) { + log.info("{}", packet); + + ctx.channel().attr(ATTR_STATE).set(packet.getNextState()); + ctx.channel().pipeline().replace("handshake_handler", "status_handler", statusHandlerProvider.get()); + ctx.channel().pipeline().addAfter("status_handler", "ping_handler", pingHandlerProvider.get()); + } +} diff --git a/src/main/java/mc/server/network/impl/handler/PingHandler.java b/src/main/java/mc/server/network/impl/handler/PingHandler.java new file mode 100644 index 0000000..21b9fcd --- /dev/null +++ b/src/main/java/mc/server/network/impl/handler/PingHandler.java @@ -0,0 +1,12 @@ +package mc.server.network.impl.handler; + +import io.netty.channel.ChannelHandlerContext; +import mc.protocol.status.PingPacket; + +public class PingHandler extends AbstractPacketHandler { + + @Override + protected void channelRead1(ChannelHandlerContext ctx, PingPacket packet) { + ctx.writeAndFlush(packet).channel().disconnect(); + } +} diff --git a/src/main/java/mc/server/network/impl/handler/StatusHandler.java b/src/main/java/mc/server/network/impl/handler/StatusHandler.java new file mode 100644 index 0000000..9011478 --- /dev/null +++ b/src/main/java/mc/server/network/impl/handler/StatusHandler.java @@ -0,0 +1,50 @@ +package mc.server.network.impl.handler; + +import io.netty.channel.ChannelHandlerContext; +import lombok.extern.slf4j.Slf4j; +import mc.protocol.ProtocolConstant; +import mc.protocol.dto.ServerInfo; +import mc.protocol.status.client.StatusServerRequest; +import mc.protocol.status.server.StatusServerResponse; +import mc.protocol.text.Text; +import org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.util.Base64; +import java.util.Collections; + +@Slf4j +public class StatusHandler extends AbstractPacketHandler { + + private static final String FAVICON_HEADER = "data:image/png;base64,"; + + @Override + protected void channelRead1(ChannelHandlerContext ctx, StatusServerRequest packet) throws Exception { + log.info("{}", packet); + + final ServerInfo.Version version = new ServerInfo.Version(); + version.setName(ProtocolConstant.PROTOCOL_VERSION_VALUE); + version.setProtocol(ProtocolConstant.PROTOCOL_VERSION); + + final ServerInfo.PlayersInfo playersInfo = new ServerInfo.PlayersInfo(); + playersInfo.setMax(20); + playersInfo.setOnline(0); + playersInfo.setSamplePlayers(Collections.emptyList()); + + final ServerInfo serverInfo = new ServerInfo(); + serverInfo.setVersion(version); + serverInfo.setDescription(Text.of("MC-SERVER 1.8.8")); + serverInfo.setFaviconBase64(getEmbeddedIconBase64()); + serverInfo.setPlayersInfo(playersInfo); + + StatusServerResponse response = new StatusServerResponse(); + response.setServerInfoDto(serverInfo); + + ctx.channel().writeAndFlush(response); + } + + private String getEmbeddedIconBase64() throws IOException { + return FAVICON_HEADER + Base64.getEncoder() + .encodeToString(IOUtils.toByteArray(getClass().getResourceAsStream("/icon.png"))); + } +} diff --git a/src/main/java/mc/server/network/impl/io/ByteBufNetInputStream.java b/src/main/java/mc/server/network/impl/io/ByteBufNetInputStream.java new file mode 100644 index 0000000..3ba8b98 --- /dev/null +++ b/src/main/java/mc/server/network/impl/io/ByteBufNetInputStream.java @@ -0,0 +1,61 @@ +package mc.server.network.impl.io; + +import io.netty.buffer.ByteBuf; +import lombok.RequiredArgsConstructor; +import mc.protocol.io.NetInputStream; + +@RequiredArgsConstructor +public class ByteBufNetInputStream extends NetInputStream { + + private final ByteBuf byteBuf; + + @Override + public void markReadIndex() { + byteBuf.markReaderIndex(); + } + + @Override + public void resetReadIndex() { + byteBuf.resetReaderIndex(); + } + + @Override + public int readableBytes() { + return byteBuf.readableBytes(); + } + + @Override + public byte readByte() { + return byteBuf.readByte(); + } + + @Override + public int readBytes(byte[] buffer, int offset, int lengtn) { + return byteBuf.readBytes(buffer, offset, lengtn).readableBytes(); + } + + @Override + public int readShort() { + return byteBuf.readShort(); + } + + @Override + public int readInt() { + return byteBuf.readInt(); + } + + @Override + public long readLong() { + return byteBuf.readLong(); + } + + @Override + public float readFloat() { + return byteBuf.readFloat(); + } + + @Override + public double readDouble() { + return byteBuf.readDouble(); + } +} diff --git a/src/main/java/mc/server/network/impl/io/ByteBufNetOutputStream.java b/src/main/java/mc/server/network/impl/io/ByteBufNetOutputStream.java new file mode 100644 index 0000000..badf014 --- /dev/null +++ b/src/main/java/mc/server/network/impl/io/ByteBufNetOutputStream.java @@ -0,0 +1,47 @@ +package mc.server.network.impl.io; + +import io.netty.buffer.ByteBuf; +import lombok.RequiredArgsConstructor; +import mc.protocol.io.NetOutputStream; + +@RequiredArgsConstructor +public class ByteBufNetOutputStream extends NetOutputStream { + + private final ByteBuf byteBuf; + + @Override + public void writeByte(int value) { + byteBuf.writeByte(value); + } + + @Override + public void writeBytes(byte[] buffer, int offset, int lengtn) { + byteBuf.writeBytes(buffer, offset, lengtn); + } + + @Override + public void writeShort(int value) { + byteBuf.writeShort(value); + } + + @Override + public void writeInt(int value) { + byteBuf.writeInt(value); + } + + @Override + public void writeLong(long value) { + byteBuf.writeLong(value); + } + + @Override + public void writeFloat(float value) { + byteBuf.writeFloat(value); + } + + @Override + public void writeDouble(double value) { + byteBuf.writeDouble(value); + } +} + diff --git a/src/main/resources/icon.png b/src/main/resources/icon.png new file mode 100644 index 0000000..d512fc5 Binary files /dev/null and b/src/main/resources/icon.png differ