mirror of
https://github.com/Minestom/Minestom.git
synced 2024-09-29 06:57:28 +02:00
Merge branch 'master' of https://github.com/WhileInside/Minestom
Conflicts: src/main/java/net/minestom/server/network/netty/NettyServer.java src/main/java/net/minestom/server/network/packet/client/login/LoginStartPacket.java src/main/java/net/minestom/server/network/player/FakePlayerConnection.java src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java src/main/java/net/minestom/server/network/player/PlayerConnection.java
This commit is contained in:
commit
1f56bc10fc
@ -18,8 +18,9 @@ repositories {
|
||||
dependencies {
|
||||
testCompile group: 'junit', name: 'junit', version: '4.12'
|
||||
|
||||
// https://mvnrepository.com/artifact/io.netty/netty-all
|
||||
api group: 'io.netty', name: 'netty-all', version: '4.1.50.Final'
|
||||
api 'io.netty:netty-handler:4.1.45.Final'
|
||||
api 'io.netty:netty-codec:4.1.45.Final'
|
||||
implementation 'io.netty:netty-transport-native-epoll:4.1.45.Final:linux-x86_64'
|
||||
|
||||
api 'com.github.jhg023:Pbbl:1.0.2'
|
||||
|
||||
|
@ -72,6 +72,7 @@ public class MinecraftServer {
|
||||
// Config
|
||||
public static final int CHUNK_VIEW_DISTANCE = 10;
|
||||
public static final int ENTITY_VIEW_DISTANCE = 5;
|
||||
public static final int COMPRESSION_THRESHOLD = 256;
|
||||
// Can be modified at performance cost when decreased
|
||||
private static final int MS_TO_SEC = 1000;
|
||||
public static final int TICK_MS = MS_TO_SEC / 20;
|
||||
|
@ -2,8 +2,10 @@ package net.minestom.server.network;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.network.netty.packet.InboundPacket;
|
||||
import net.minestom.server.network.packet.PacketReader;
|
||||
import net.minestom.server.network.packet.client.ClientPlayPacket;
|
||||
import net.minestom.server.network.packet.client.ClientPreplayPacket;
|
||||
@ -40,19 +42,22 @@ public class PacketProcessor {
|
||||
|
||||
private List<Integer> printBlackList = Arrays.asList(17, 18, 19);
|
||||
|
||||
public void process(ChannelHandlerContext channel, ByteBuf buffer, int id, int offset) {
|
||||
PlayerConnection playerConnection =
|
||||
connectionPlayerConnectionMap.computeIfAbsent(channel, c -> new NettyPlayerConnection(channel));
|
||||
public void process(ChannelHandlerContext channel, InboundPacket packet) {
|
||||
PlayerConnection playerConnection = connectionPlayerConnectionMap.computeIfAbsent(
|
||||
channel, c -> new NettyPlayerConnection((SocketChannel) channel.channel())
|
||||
);
|
||||
|
||||
ConnectionState connectionState = playerConnection.getConnectionState();
|
||||
|
||||
//if (!printBlackList.contains(id)) {
|
||||
//System.out.println("RECEIVED ID: 0x" + Integer.toHexString(id) + " State: " + connectionState);
|
||||
//}
|
||||
|
||||
PacketReader packetReader = new PacketReader(buffer);
|
||||
PacketReader packetReader = new PacketReader(packet.body);
|
||||
|
||||
if (connectionState == ConnectionState.UNKNOWN) {
|
||||
// Should be handshake packet
|
||||
if (id == 0) {
|
||||
if (packet.packetId == 0) {
|
||||
HandshakePacket handshakePacket = new HandshakePacket();
|
||||
handshakePacket.read(packetReader);
|
||||
handshakePacket.process(playerConnection, connectionManager);
|
||||
@ -63,26 +68,23 @@ public class PacketProcessor {
|
||||
switch (connectionState) {
|
||||
case PLAY:
|
||||
Player player = playerConnection.getPlayer();
|
||||
ClientPlayPacket playPacket = (ClientPlayPacket) playPacketsHandler.getPacketInstance(id);
|
||||
ClientPlayPacket playPacket = (ClientPlayPacket) playPacketsHandler.getPacketInstance(packet.packetId);
|
||||
playPacket.read(packetReader);
|
||||
//System.out.println("play");
|
||||
player.addPacketToQueue(playPacket);
|
||||
break;
|
||||
case LOGIN:
|
||||
ClientPreplayPacket loginPacket = (ClientPreplayPacket) loginPacketsHandler.getPacketInstance(id);
|
||||
ClientPreplayPacket loginPacket = (ClientPreplayPacket) loginPacketsHandler.getPacketInstance(packet.packetId);
|
||||
loginPacket.read(packetReader);
|
||||
//System.out.println("login");
|
||||
loginPacket.process(playerConnection, connectionManager);
|
||||
break;
|
||||
case STATUS:
|
||||
ClientPreplayPacket statusPacket = (ClientPreplayPacket) statusPacketsHandler.getPacketInstance(id);
|
||||
ClientPreplayPacket statusPacket = (ClientPreplayPacket) statusPacketsHandler.getPacketInstance(packet.packetId);
|
||||
statusPacket.read(packetReader);
|
||||
|
||||
statusPacket.process(playerConnection, connectionManager);
|
||||
break;
|
||||
case UNKNOWN:
|
||||
// Ignore packet (unexpected)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,30 +1,73 @@
|
||||
package net.minestom.server.network.netty;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.epoll.Epoll;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.ServerSocketChannel;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import net.minestom.server.network.PacketProcessor;
|
||||
import net.minestom.server.network.netty.channel.ClientChannel;
|
||||
import net.minestom.server.network.netty.channel.NettyDecoder;
|
||||
import net.minestom.server.network.netty.codec.LegacyPingHandler;
|
||||
import net.minestom.server.network.netty.codec.PacketDecoder;
|
||||
import net.minestom.server.network.netty.codec.PacketEncoder;
|
||||
import net.minestom.server.network.netty.codec.PacketFramer;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public class NettyServer {
|
||||
|
||||
private PacketProcessor packetProcessor;
|
||||
private EventLoopGroup group;
|
||||
private final EventLoopGroup boss, worker;
|
||||
private final ServerBootstrap bootstrap;
|
||||
|
||||
private ServerSocketChannel serverChannel;
|
||||
|
||||
private String address;
|
||||
private int port;
|
||||
|
||||
public NettyServer(PacketProcessor packetProcessor) {
|
||||
this.packetProcessor = packetProcessor;
|
||||
this.group = new NioEventLoopGroup();
|
||||
Class<? extends ServerChannel> channel;
|
||||
|
||||
if (Epoll.isAvailable()) {
|
||||
boss = new EpollEventLoopGroup(2);
|
||||
worker = new EpollEventLoopGroup();
|
||||
|
||||
channel = EpollServerSocketChannel.class;
|
||||
} else {
|
||||
boss = new NioEventLoopGroup(2);
|
||||
worker = new EpollEventLoopGroup();
|
||||
|
||||
channel = NioServerSocketChannel.class;
|
||||
}
|
||||
|
||||
bootstrap = new ServerBootstrap();
|
||||
bootstrap.group(boss, worker);
|
||||
bootstrap.channel(channel);
|
||||
|
||||
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
protected void initChannel(SocketChannel ch) {
|
||||
ChannelConfig config = ch.config();
|
||||
config.setOption(ChannelOption.TCP_NODELAY, true);
|
||||
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
|
||||
pipeline.addLast("legacy-ping", new LegacyPingHandler());
|
||||
|
||||
// Adds packetLength at start | Reads framed bytebuf
|
||||
pipeline.addLast("framer", new PacketFramer());
|
||||
|
||||
// Reads bytebuf and creating inbound packet
|
||||
pipeline.addLast("decoder", new PacketDecoder());
|
||||
|
||||
// Writes packet to bytebuf
|
||||
pipeline.addLast("encoder", new PacketEncoder());
|
||||
|
||||
pipeline.addLast("handler", new ClientChannel(packetProcessor));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void start(String address, int port) {
|
||||
@ -32,27 +75,15 @@ public class NettyServer {
|
||||
this.port = port;
|
||||
|
||||
try {
|
||||
ServerBootstrap serverBootstrap = new ServerBootstrap();
|
||||
serverBootstrap.group(group);
|
||||
serverBootstrap.channel(NioServerSocketChannel.class);
|
||||
serverBootstrap.localAddress(new InetSocketAddress(address, port));
|
||||
ChannelFuture cf = bootstrap.bind(new InetSocketAddress(address, port)).sync();
|
||||
|
||||
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
protected void initChannel(SocketChannel socketChannel) {
|
||||
socketChannel.pipeline().addLast("decoder", new NettyDecoder());
|
||||
socketChannel.pipeline().addLast("encoder", new ClientChannel(packetProcessor));
|
||||
}
|
||||
});
|
||||
ChannelFuture channelFuture = serverBootstrap.bind().sync();
|
||||
channelFuture.channel().closeFuture().sync();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
group.shutdownGracefully().sync();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
if (!cf.isSuccess()) {
|
||||
throw new IllegalStateException("Unable to bind server at " + address + ":" + port);
|
||||
}
|
||||
|
||||
serverChannel = (ServerSocketChannel) cf.channel();
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,10 +96,9 @@ public class NettyServer {
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
try {
|
||||
group.shutdownGracefully().sync();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
serverChannel.close();
|
||||
|
||||
worker.shutdownGracefully();
|
||||
boss.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,18 @@
|
||||
package net.minestom.server.network.netty.channel;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.network.ConnectionManager;
|
||||
import net.minestom.server.network.PacketProcessor;
|
||||
import net.minestom.server.network.netty.packet.PacketHandler;
|
||||
import net.minestom.server.network.packet.PacketReader;
|
||||
import net.minestom.server.network.packet.client.status.LegacyServerListPingPacket;
|
||||
import net.minestom.server.network.netty.packet.InboundPacket;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.Utils;
|
||||
|
||||
public class ClientChannel extends ChannelInboundHandlerAdapter {
|
||||
public class ClientChannel extends SimpleChannelInboundHandler<InboundPacket> {
|
||||
|
||||
private ConnectionManager connectionManager = MinecraftServer.getConnectionManager();
|
||||
private PacketProcessor packetProcessor;
|
||||
private final ConnectionManager connectionManager = MinecraftServer.getConnectionManager();
|
||||
private final PacketProcessor packetProcessor;
|
||||
|
||||
public ClientChannel(PacketProcessor packetProcessor) {
|
||||
this.packetProcessor = packetProcessor;
|
||||
@ -28,26 +24,20 @@ public class ClientChannel extends ChannelInboundHandlerAdapter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object obj) {
|
||||
PacketHandler packetHandler = (PacketHandler) obj;
|
||||
public void channelRead0(ChannelHandlerContext ctx, InboundPacket packet) {
|
||||
try {
|
||||
packetProcessor.process(ctx, packet);
|
||||
} finally {
|
||||
int availableBytes = packet.body.readableBytes();
|
||||
|
||||
int packetLength = packetHandler.length;
|
||||
ByteBuf buffer = packetHandler.buffer;
|
||||
if (availableBytes > 0) {
|
||||
// TODO log4j2
|
||||
System.out.println("Packet 0x" + Integer.toHexString(packet.packetId)
|
||||
+ " not fully read (" + availableBytes + " bytes left)");
|
||||
|
||||
if (packetLength == 0xFE) { // Legacy server ping
|
||||
LegacyServerListPingPacket legacyServerListPingPacket = new LegacyServerListPingPacket();
|
||||
legacyServerListPingPacket.read(new PacketReader(buffer));
|
||||
legacyServerListPingPacket.process(null, null);
|
||||
return;
|
||||
packet.body.skipBytes(availableBytes);
|
||||
}
|
||||
}
|
||||
|
||||
final int varIntLength = Utils.lengthVarInt(packetLength);
|
||||
int packetId = Utils.readVarInt(buffer);
|
||||
|
||||
int offset = varIntLength + Utils.lengthVarInt(packetId);
|
||||
packetProcessor.process(ctx, buffer, packetId, offset);
|
||||
|
||||
buffer.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,51 +0,0 @@
|
||||
package net.minestom.server.network.netty.channel;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import net.minestom.server.network.netty.packet.PacketHandler;
|
||||
import net.minestom.server.utils.Utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class NettyDecoder extends ByteToMessageDecoder {
|
||||
|
||||
private int bytesToRead;
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) {
|
||||
|
||||
// Fix cut packet
|
||||
if (bytesToRead != 0) {
|
||||
int readable = buffer.readableBytes();
|
||||
if (readable >= bytesToRead) {
|
||||
PacketHandler packetHandler = new PacketHandler();
|
||||
packetHandler.length = bytesToRead;
|
||||
packetHandler.buffer = buffer.readBytes(bytesToRead);
|
||||
out.add(packetHandler);
|
||||
bytesToRead = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int packetLength = Utils.readVarInt(buffer);
|
||||
PacketHandler packetHandler = new PacketHandler();
|
||||
packetHandler.length = packetLength;
|
||||
if (packetLength == 0xFE) { // Legacy server ping
|
||||
packetHandler.buffer = buffer.readBytes(2);
|
||||
} else {
|
||||
int readable = buffer.readableBytes();
|
||||
if (readable < packetLength) {
|
||||
// Wait for bytes to arrive
|
||||
bytesToRead = packetLength;
|
||||
return;
|
||||
} else {
|
||||
// There are enough bytes, read them
|
||||
packetHandler.buffer = buffer.readBytes(packetLength);
|
||||
bytesToRead = 0;
|
||||
}
|
||||
}
|
||||
|
||||
out.add(packetHandler);
|
||||
}
|
||||
}
|
@ -0,0 +1,187 @@
|
||||
package net.minestom.server.network.netty.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
// Copied from original minecraft :(
|
||||
public class LegacyPingHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private ByteBuf buf;
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object object) throws Exception {
|
||||
ByteBuf buf = (ByteBuf) object;
|
||||
|
||||
if (this.buf != null) {
|
||||
try {
|
||||
readLegacy1_6(ctx, buf);
|
||||
} finally {
|
||||
buf.release();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
buf.markReaderIndex();
|
||||
|
||||
boolean flag = true;
|
||||
|
||||
try {
|
||||
if (buf.readUnsignedByte() == 0xFE) {
|
||||
int length = buf.readableBytes();
|
||||
|
||||
switch (length) {
|
||||
case 0:
|
||||
this.writeResponse(ctx, this.createResponse(formatResponse(-2)));
|
||||
break;
|
||||
case 1:
|
||||
if (buf.readUnsignedByte() != 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.writeResponse(ctx, this.createResponse(formatResponse(-1)));
|
||||
break;
|
||||
default:
|
||||
if (buf.readUnsignedByte() != 0x01 || buf.readUnsignedByte() != 0xFA) return;
|
||||
|
||||
readLegacy1_6(ctx, buf);
|
||||
break;
|
||||
}
|
||||
|
||||
buf.release();
|
||||
flag = false;
|
||||
}
|
||||
} finally {
|
||||
if (flag) {
|
||||
buf.resetReaderIndex();
|
||||
ctx.channel().pipeline().remove("legacy-ping");
|
||||
ctx.fireChannelRead(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String readLegacyString(ByteBuf buf) {
|
||||
int size = buf.readShort() * Character.BYTES;
|
||||
if (!buf.isReadable(size)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String result = buf.toString(buf.readerIndex(), size, StandardCharsets.UTF_16BE);
|
||||
buf.skipBytes(size);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void readLegacy1_6(ChannelHandlerContext ctx, ByteBuf part) {
|
||||
ByteBuf buf = this.buf;
|
||||
|
||||
if (buf == null) {
|
||||
this.buf = buf = ctx.alloc().buffer();
|
||||
buf.markReaderIndex();
|
||||
} else {
|
||||
buf.resetReaderIndex();
|
||||
}
|
||||
|
||||
buf.writeBytes(part);
|
||||
|
||||
if (!buf.isReadable(Short.BYTES + Short.BYTES + Byte.BYTES + Short.BYTES + Integer.BYTES)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String s = readLegacyString(buf);
|
||||
|
||||
if (s == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!s.equals("MC|PingHost")) {
|
||||
removeHandler(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!buf.isReadable(Short.BYTES) || !buf.isReadable(buf.readShort())) {
|
||||
return;
|
||||
}
|
||||
|
||||
int protocolVersion = buf.readByte();
|
||||
|
||||
if (readLegacyString(buf) == null) {
|
||||
removeHandler(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
buf.skipBytes(4); // port
|
||||
|
||||
if (buf.isReadable()) {
|
||||
removeHandler(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
buf.release();
|
||||
|
||||
this.buf = null;
|
||||
|
||||
this.writeResponse(ctx, this.createResponse(formatResponse(protocolVersion)));
|
||||
}
|
||||
|
||||
private String formatResponse(int playerProtocol) {
|
||||
// todo server motd, online and slots
|
||||
final String motd = "Minestom";
|
||||
final String version = "1.15.2";
|
||||
final int online = 0;
|
||||
final int max = 1;
|
||||
final int protocol = 578; // 1.15.2
|
||||
|
||||
if (playerProtocol == -2) {
|
||||
return String.format(
|
||||
"%s\u00a7%d\u00a7%d",
|
||||
motd, online, max
|
||||
);
|
||||
}
|
||||
|
||||
return String.format(
|
||||
"\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d",
|
||||
protocol, version, motd, online, max
|
||||
);
|
||||
}
|
||||
|
||||
private void removeHandler(ChannelHandlerContext ctx) {
|
||||
ByteBuf buf = this.buf;
|
||||
this.buf = null;
|
||||
|
||||
buf.resetReaderIndex();
|
||||
ctx.pipeline().remove(this);
|
||||
ctx.fireChannelRead(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerRemoved(ChannelHandlerContext ctx) {
|
||||
if (this.buf != null) {
|
||||
this.buf.release();
|
||||
this.buf = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void writeResponse(ChannelHandlerContext ctx, ByteBuf buf) {
|
||||
ctx.pipeline().firstContext().writeAndFlush(buf).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
|
||||
private ByteBuf createResponse(String s) {
|
||||
ByteBuf response = Unpooled.buffer();
|
||||
response.writeByte(255);
|
||||
|
||||
char[] chars = s.toCharArray();
|
||||
|
||||
response.writeShort(chars.length);
|
||||
|
||||
for (char c : chars) {
|
||||
response.writeChar(c);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (2020) [artem]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.minestom.server.network.netty.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageCodec;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import net.minestom.server.utils.Utils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.Inflater;
|
||||
|
||||
// TODO Optimize
|
||||
public class PacketCompressor extends ByteToMessageCodec<ByteBuf> {
|
||||
|
||||
private final byte[] buffer = new byte[8192];
|
||||
|
||||
private final int threshold;
|
||||
|
||||
private final Inflater inflater;
|
||||
private final Deflater deflater;
|
||||
|
||||
public PacketCompressor(int threshold) {
|
||||
this.inflater = new Inflater();
|
||||
this.deflater = new Deflater();
|
||||
|
||||
this.threshold = threshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf from, ByteBuf to) {
|
||||
int i = from.readableBytes();
|
||||
|
||||
if (i < this.threshold) {
|
||||
Utils.writeVarIntBuf(to, 0);
|
||||
to.writeBytes(from);
|
||||
} else {
|
||||
byte[] abyte = new byte[i];
|
||||
from.readBytes(abyte);
|
||||
|
||||
Utils.writeVarIntBuf(to, abyte.length);
|
||||
this.deflater.setInput(abyte, 0, i);
|
||||
this.deflater.finish();
|
||||
|
||||
while (!this.deflater.finished()) {
|
||||
int j = this.deflater.deflate(this.buffer);
|
||||
|
||||
to.writeBytes(this.buffer, 0, j);
|
||||
}
|
||||
|
||||
this.deflater.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) throws Exception {
|
||||
if (buf.readableBytes() != 0) {
|
||||
int i = Utils.readVarInt(buf);
|
||||
|
||||
if (i == 0) {
|
||||
out.add(buf.readRetainedSlice(buf.readableBytes()));
|
||||
} else {
|
||||
if (i < this.threshold) {
|
||||
throw new DecoderException("Badly compressed packet - size of " + i + " is below server threshold of " + this.threshold);
|
||||
}
|
||||
|
||||
if (i > 2097152) {
|
||||
throw new DecoderException("Badly compressed packet - size of " + i + " is larger than protocol maximum of 2097152");
|
||||
}
|
||||
|
||||
byte[] abyte = new byte[buf.readableBytes()];
|
||||
buf.readBytes(abyte);
|
||||
|
||||
this.inflater.setInput(abyte);
|
||||
byte[] abyte1 = new byte[i];
|
||||
|
||||
this.inflater.inflate(abyte1);
|
||||
out.add(Unpooled.wrappedBuffer(abyte1));
|
||||
|
||||
this.inflater.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package net.minestom.server.network.netty.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import net.minestom.server.network.netty.packet.InboundPacket;
|
||||
import net.minestom.server.utils.Utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PacketDecoder extends ByteToMessageDecoder {
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) throws Exception {
|
||||
if (buf.readableBytes() > 0) {
|
||||
list.add(new InboundPacket(Utils.readVarInt(buf), buf));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package net.minestom.server.network.netty.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.utils.PacketUtils;
|
||||
|
||||
public class PacketEncoder extends MessageToByteEncoder<ServerPacket> {
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ServerPacket packet, ByteBuf buf) throws Exception {
|
||||
PacketUtils.writePacket(buf, packet);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package net.minestom.server.network.netty.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageCodec;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import net.minestom.server.utils.Utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PacketFramer extends ByteToMessageCodec<ByteBuf> {
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf from, ByteBuf to) {
|
||||
int packetSize = from.readableBytes();
|
||||
int headerSize = Utils.getVarIntSize(packetSize);
|
||||
|
||||
if (headerSize > 3) {
|
||||
throw new IllegalStateException("Unable to fit " + headerSize + " into 3");
|
||||
}
|
||||
|
||||
to.ensureWritable(packetSize + headerSize);
|
||||
|
||||
Utils.writeVarIntBuf(to, packetSize);
|
||||
to.writeBytes(from, from.readerIndex(), packetSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) {
|
||||
buf.markReaderIndex();
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (!buf.isReadable()) {
|
||||
buf.resetReaderIndex();
|
||||
return;
|
||||
}
|
||||
|
||||
byte b = buf.readByte();
|
||||
|
||||
if (b >= 0) {
|
||||
buf.resetReaderIndex();
|
||||
|
||||
int j = Utils.readVarInt(buf);
|
||||
|
||||
if (buf.readableBytes() < j) {
|
||||
buf.resetReaderIndex();
|
||||
return;
|
||||
}
|
||||
|
||||
out.add(buf.readRetainedSlice(j));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new CorruptedFrameException("length wider than 21-bit");
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package net.minestom.server.network.netty.packet;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class InboundPacket {
|
||||
|
||||
public final int packetId;
|
||||
public final ByteBuf body;
|
||||
|
||||
public InboundPacket(int id, ByteBuf body) {
|
||||
this.packetId = id;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package net.minestom.server.network.netty.packet;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class PacketHandler {
|
||||
|
||||
public int length;
|
||||
public ByteBuf buffer;
|
||||
|
||||
}
|
@ -9,6 +9,7 @@ import net.minestom.server.utils.Utils;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
// TODO delete
|
||||
public class PacketReader {
|
||||
|
||||
private ByteBuf buffer;
|
||||
|
@ -14,6 +14,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
// TODO delete
|
||||
public class PacketWriter {
|
||||
|
||||
private ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
|
@ -1,12 +1,14 @@
|
||||
package net.minestom.server.network.packet.client.login;
|
||||
|
||||
import net.minestom.server.extras.MojangAuth;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.network.ConnectionManager;
|
||||
import net.minestom.server.network.ConnectionState;
|
||||
import net.minestom.server.network.packet.PacketReader;
|
||||
import net.minestom.server.network.packet.client.ClientPreplayPacket;
|
||||
import net.minestom.server.network.packet.server.login.EncryptionRequestPacket;
|
||||
import net.minestom.server.network.packet.server.login.LoginSuccessPacket;
|
||||
import net.minestom.server.network.packet.server.login.SetCompressionPacket;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
|
||||
import java.util.UUID;
|
||||
@ -26,8 +28,14 @@ public class LoginStartPacket implements ClientPreplayPacket {
|
||||
} else {
|
||||
UUID playerUuid = connectionManager.getPlayerConnectionUuid(connection, username);
|
||||
|
||||
LoginSuccessPacket successPacket = new LoginSuccessPacket(playerUuid, username);
|
||||
connection.sendPacket(successPacket);
|
||||
int threshold = MinecraftServer.COMPRESSION_THRESHOLD;
|
||||
|
||||
if (threshold > 0) {
|
||||
connection.enableCompression(threshold);
|
||||
}
|
||||
|
||||
LoginSuccessPacket successPacket = new LoginSuccessPacket(playerUuid, username);
|
||||
connection.sendPacket(successPacket);
|
||||
|
||||
connection.setConnectionState(ConnectionState.PLAY);
|
||||
connectionManager.createPlayer(playerUuid, username, connection);
|
||||
|
@ -0,0 +1,23 @@
|
||||
package net.minestom.server.network.packet.server.login;
|
||||
|
||||
import net.minestom.server.network.packet.PacketWriter;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
|
||||
public class SetCompressionPacket implements ServerPacket {
|
||||
|
||||
public int threshold;
|
||||
|
||||
public SetCompressionPacket(int threshold) {
|
||||
this.threshold = threshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(PacketWriter writer) {
|
||||
writer.writeVarInt(threshold);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return 0x03;
|
||||
}
|
||||
}
|
@ -12,6 +12,11 @@ import java.net.SocketAddress;
|
||||
|
||||
public class FakePlayerConnection extends PlayerConnection {
|
||||
|
||||
@Override
|
||||
public void enableCompression(int threshold) {
|
||||
throw new UnsupportedOperationException("FakePlayer cannot enable compression");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(ByteBuf buffer, boolean copy) {
|
||||
throw new UnsupportedOperationException("FakePlayer cannot read Bytebuf");
|
||||
@ -19,7 +24,7 @@ public class FakePlayerConnection extends PlayerConnection {
|
||||
|
||||
@Override
|
||||
public void writePacket(ByteBuf buffer, boolean copy) {
|
||||
throw new UnsupportedOperationException("FakePlayer cannot read Bytebuf");
|
||||
throw new UnsupportedOperationException("FakePlayer cannot write to Bytebuf");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,13 +1,17 @@
|
||||
package net.minestom.server.network.player;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.Getter;
|
||||
import net.minestom.server.extras.mojangAuth.Decrypter;
|
||||
import net.minestom.server.extras.mojangAuth.Encrypter;
|
||||
import net.minestom.server.extras.mojangAuth.MojangCrypt;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import net.minestom.server.network.netty.codec.PacketCompressor;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.utils.PacketUtils;
|
||||
import net.minestom.server.network.packet.server.login.SetCompressionPacket;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.net.SocketAddress;
|
||||
@ -18,12 +22,11 @@ import java.net.SocketAddress;
|
||||
*/
|
||||
public class NettyPlayerConnection extends PlayerConnection {
|
||||
|
||||
private ChannelHandlerContext channel;
|
||||
private final SocketChannel channel;
|
||||
@Getter
|
||||
private boolean encrypted = false;
|
||||
|
||||
|
||||
public NettyPlayerConnection(ChannelHandlerContext channel) {
|
||||
public NettyPlayerConnection(SocketChannel channel) {
|
||||
super();
|
||||
this.channel = channel;
|
||||
}
|
||||
@ -35,12 +38,19 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableCompression(int threshold) {
|
||||
sendPacket(new SetCompressionPacket(threshold));
|
||||
|
||||
channel.pipeline().addAfter("framer", "compressor", new PacketCompressor(threshold));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(ByteBuf buffer, boolean copy) {
|
||||
//System.out.println(getConnectionState() + " out");
|
||||
if (encrypted) {
|
||||
buffer = buffer.copy();
|
||||
buffer.retain();
|
||||
getChannel().writeAndFlush(buffer);
|
||||
channel.writeAndFlush(buffer);
|
||||
buffer.release();
|
||||
} else {
|
||||
buffer.retain();
|
||||
@ -53,7 +63,7 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
if (encrypted) {
|
||||
buffer = buffer.copy();
|
||||
buffer.retain();
|
||||
getChannel().write(buffer);
|
||||
channel.write(buffer);
|
||||
buffer.release();
|
||||
} else {
|
||||
buffer.retain();
|
||||
@ -64,9 +74,8 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
@Override
|
||||
public void sendPacket(ServerPacket serverPacket) {
|
||||
//System.out.println(serverPacket.getClass().getName() + " out");
|
||||
ByteBuf buffer = PacketUtils.writePacket(serverPacket);
|
||||
sendPacket(buffer, false);
|
||||
buffer.release();
|
||||
//TODO check wat this does
|
||||
channel.writeAndFlush(serverPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -76,7 +85,7 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
|
||||
@Override
|
||||
public SocketAddress getRemoteAddress() {
|
||||
return getChannel().channel().remoteAddress();
|
||||
return getChannel().remoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -84,7 +93,7 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
getChannel().close();
|
||||
}
|
||||
|
||||
public ChannelHandlerContext getChannel() {
|
||||
public Channel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@ public abstract class PlayerConnection {
|
||||
this.connectionState = ConnectionState.UNKNOWN;
|
||||
}
|
||||
|
||||
public abstract void enableCompression(int threshold);
|
||||
/**
|
||||
*
|
||||
* @param buffer The buffer to send.
|
||||
|
@ -7,27 +7,19 @@ import net.minestom.server.network.packet.server.ServerPacket;
|
||||
|
||||
public class PacketUtils {
|
||||
|
||||
public static ByteBuf writePacket(ServerPacket serverPacket) {
|
||||
int id = serverPacket.getId();
|
||||
PacketWriter packetWriter = new PacketWriter();
|
||||
public static void writePacket(ByteBuf buf, ServerPacket packet) {
|
||||
PacketWriter writer = new PacketWriter();
|
||||
|
||||
packetWriter.writeVarInt(id);
|
||||
Utils.writeVarIntBuf(buf, packet.getId());
|
||||
packet.write(writer);
|
||||
buf.writeBytes(writer.toByteArray());
|
||||
}
|
||||
|
||||
serverPacket.write(packetWriter);
|
||||
public static ByteBuf writePacket(ServerPacket packet) {
|
||||
ByteBuf buffer = Unpooled.buffer();
|
||||
|
||||
byte[] bytes = packetWriter.toByteArray();
|
||||
int length = bytes.length;
|
||||
writePacket(buffer, packet);
|
||||
|
||||
int varIntSize = Utils.lengthVarInt(length);
|
||||
|
||||
ByteBuf buffer = Unpooled.buffer(length + varIntSize);
|
||||
Utils.writeVarIntBuf(buffer, length);
|
||||
buffer.writeBytes(bytes);
|
||||
|
||||
//if(!(serverPacket instanceof ChunkDataPacket) && !(serverPacket instanceof PlayerListHeaderAndFooterPacket))
|
||||
//System.out.println("WRITE PACKET: " + serverPacket.getClass().getSimpleName());
|
||||
|
||||
//Unpooled.copiedBuffer(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,14 @@ import java.util.*;
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static int getVarIntSize(int input) {
|
||||
return (input & 0xFFFFFF80) == 0
|
||||
? 1 : (input & 0xFFFFC000) == 0
|
||||
? 2 : (input & 0xFFE00000) == 0
|
||||
? 3 : (input & 0xF0000000) == 0
|
||||
? 4 : 5;
|
||||
}
|
||||
|
||||
public static void writeVarIntBuf(ByteBuf buffer, int value) {
|
||||
do {
|
||||
byte temp = (byte) (value & 0b01111111);
|
||||
@ -51,19 +59,6 @@ public class Utils {
|
||||
} while (value != 0);
|
||||
}
|
||||
|
||||
public static int lengthVarInt(int value) {
|
||||
int i = 0;
|
||||
do {
|
||||
i++;
|
||||
byte temp = (byte) (value & 0b01111111);
|
||||
value >>>= 7;
|
||||
if (value != 0) {
|
||||
temp |= 0b10000000;
|
||||
}
|
||||
} while (value != 0);
|
||||
return i;
|
||||
}
|
||||
|
||||
public static int readVarInt(ByteBuf buffer) {
|
||||
int numRead = 0;
|
||||
int result = 0;
|
||||
|
@ -35,7 +35,7 @@ public class BufferWrapper {
|
||||
|
||||
public void putVarInt(int n) {
|
||||
Utils.writeVarIntBuffer(this, n);
|
||||
size += Utils.lengthVarInt(n);
|
||||
size += Utils.getVarIntSize(n);
|
||||
}
|
||||
|
||||
public void putBytes(byte[] bytes) {
|
||||
|
Loading…
Reference in New Issue
Block a user