Archived
0

10 Commits

21 changed files with 557 additions and 21 deletions

View File

@@ -1,15 +1,36 @@
apply plugin: 'java' subprojects {
apply plugin: 'java'
project.group = projectGroup project.group = projectGroup
project.version = projectVersion project.version = projectVersion
compileJava { compileJava {
sourceCompatibility = 1.8 sourceCompatibility = 1.8
targetCompatibility = 1.8 targetCompatibility = 1.8
options.encoding = 'UTF-8' options.encoding = 'UTF-8'
} }
repositories { repositories {
mavenLocal() mavenLocal()
mavenCentral() mavenCentral()
}
ext {
slf4j_version = '1.7.30'
library = [
lombok: 'org.projectlombok:lombok:1.18.2',
slf4j: ["org.slf4j:slf4j-api:$slf4j_version",
"org.slf4j:jcl-over-slf4j:$slf4j_version"],
]
}
dependencies {
/* LOGGER */
implementation library.slf4j
/* LOMBOK */
annotationProcessor library.lombok
compileOnly library.lombok
}
} }

View File

@@ -0,0 +1,6 @@
package mc.server.network;
public interface Server {
void start(String host, int port);
}

23
server/build.gradle Normal file
View File

@@ -0,0 +1,23 @@
ext {
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"],
netty: ['io.netty:netty-all:4.1.22.Final'],
commons: ['commons-io:commons-io:2.6']
]
}
dependencies {
/* LOGGER */
implementation library.logger
/* COMPONENTS */
implementation project(':protocol')
implementation project(':server-api')
implementation library.guice
implementation library.netty
implementation library.commons
}

View File

@@ -0,0 +1,16 @@
package mc.server;
import com.google.inject.Guice;
import com.google.inject.Injector;
import mc.server.network.Server;
import mc.server.network.config.NetworkModule;
public class Main {
public static void main(String[] args) {
final Injector injector = Guice.createInjector(new NetworkModule());
final Server server = injector.getInstance(Server.class);
server.start("127.0.0.1", 25565);
}
}

View File

@@ -0,0 +1,65 @@
package mc.server.network.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<String, ChannelHandler> channelHandlerMap(HandshakeHandler handshakeHandler) {
final Map<String, ChannelHandler> 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;
}
}

View File

@@ -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<SocketChannel> {
private final Provider<Map<String, ChannelHandler>> channelHandlerMapProvider;
@Inject
public ChannelInitializer(
@Named("channelHandlerMap") Provider<Map<String, ChannelHandler>> channelHandlerMapProvider) {
this.channelHandlerMapProvider = channelHandlerMapProvider;
}
@Override
protected void initChannel(SocketChannel socketChannel) {
final ChannelPipeline pipeline = socketChannel.pipeline();
channelHandlerMapProvider.get().forEach(pipeline::addLast);
}
}

View File

@@ -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<State> ATTR_STATE = AttributeKey.newInstance("ATTR_STATE");
}

View File

@@ -0,0 +1,28 @@
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<ServerBootstrap> 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);
}
}
}
}

View File

@@ -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<Object> 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);
}
}
}

View File

@@ -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<Packet> {
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));
}
}

View File

@@ -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<Object> 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));
}
}
}

View File

@@ -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<P extends Packet> extends SimpleChannelInboundHandler<P> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, P msg) throws Exception {
channelRead1(ctx, msg);
}
protected abstract void channelRead1(ChannelHandlerContext ctx, P packet) throws Exception;
}

View File

@@ -0,0 +1,34 @@
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.State;
import mc.protocol.handshake.client.HandshakePacket;
import static mc.server.network.impl.NettyConstants.ATTR_STATE;
@Slf4j
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
public class HandshakeHandler extends AbstractPacketHandler<HandshakePacket> {
private final Provider<StatusHandler> statusHandlerProvider;
private final Provider<PingHandler> pingHandlerProvider;
private final Provider<LoginHandler> loginHandlerProvider;
@Override
protected void channelRead1(ChannelHandlerContext ctx, HandshakePacket packet) {
log.info("{}", packet);
ctx.channel().attr(ATTR_STATE).set(packet.getNextState());
if (packet.getNextState() == State.STATUS) {
ctx.channel().pipeline().replace("handshake_handler", "status_handler", statusHandlerProvider.get());
ctx.channel().pipeline().addAfter("status_handler", "ping_handler", pingHandlerProvider.get());
} else if (packet.getNextState() == State.LOGIN) {
ctx.channel().pipeline().replace("handshake_handler", "login_handler", loginHandlerProvider.get());
}
}
}

View File

@@ -0,0 +1,23 @@
package mc.server.network.impl.handler;
import io.netty.channel.ChannelHandlerContext;
import mc.protocol.login.client.LoginStartPacket;
import mc.protocol.login.server.DisconnectPacket;
import mc.protocol.text.Text;
import mc.protocol.text.TextColor;
import mc.protocol.text.TextStyle;
public class LoginHandler extends AbstractPacketHandler<LoginStartPacket> {
@Override
protected void channelRead1(ChannelHandlerContext ctx, LoginStartPacket packet) {
DisconnectPacket disconnectPacket = new DisconnectPacket();
disconnectPacket.setReason(Text.builder()
.append(Text.of(TextColor.WHITE, "Server is "))
.color(TextColor.RED).style(TextStyle.BOLD).append("NOT ")
.color(TextColor.WHITE).append("available.")
.build());
ctx.channel().writeAndFlush(disconnectPacket).channel().disconnect();
}
}

View File

@@ -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<PingPacket> {
@Override
protected void channelRead1(ChannelHandlerContext ctx, PingPacket packet) {
ctx.writeAndFlush(packet).channel().disconnect();
}
}

View File

@@ -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<StatusServerRequest> {
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")));
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1 +1,7 @@
rootProject.name = projectName rootProject.name = projectName
include ':protocol'
project(':protocol').projectDir = new File(settingsDir, '../mc-protocol')
include ':server-api'
include ':server'

View File

@@ -1,8 +0,0 @@
package mc.server;
public class Main {
public static void main(String[] args) {
System.out.println("hello?");
}
}