Moved from Gitlab

This commit is contained in:
Nanit
2020-11-25 19:16:28 +02:00
commit ccedd3a160
31 changed files with 2250 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
package ru.nanit.limbo.protocol;
public enum Direction {
CLIENT,
SERVER
}

View File

@@ -0,0 +1,11 @@
package ru.nanit.limbo.protocol;
import ru.nanit.limbo.protocol.registry.Version;
public interface Packet {
void encode(ByteMessage msg, Direction direction, Version version);
void decode(ByteMessage msg, Direction direction, Version version);
}

View File

@@ -0,0 +1,12 @@
package ru.nanit.limbo.protocol;
import ru.nanit.limbo.protocol.registry.Version;
public interface PacketIn extends Packet {
@Override
default void encode(ByteMessage msg, Direction direction, Version version) {
// Can be ignored for incoming packets
}
}

View File

@@ -0,0 +1,12 @@
package ru.nanit.limbo.protocol;
import ru.nanit.limbo.protocol.registry.Version;
public interface PacketOut extends Packet {
@Override
default void decode(ByteMessage msg, Direction direction, Version version) {
// Can be ignored for outgoing packets
}
}

View File

@@ -0,0 +1,62 @@
package ru.nanit.limbo.protocol.packets;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.Packet;
import ru.nanit.limbo.protocol.Direction;
import ru.nanit.limbo.protocol.registry.Version;
public class PacketHandshake implements Packet {
private Version version;
private String host;
private int port;
private int nextState;
public Version getVersion() {
return version;
}
public void setVersion(Version version) {
this.version = version;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public int getNextState() {
return nextState;
}
public void setNextState(int nextState) {
this.nextState = nextState;
}
@Override
public void encode(ByteMessage msg, Direction direction, Version version) {
msg.writeVarInt(this.version.getProtocolNumber());
msg.writeString(host);
msg.writeShort(port);
msg.writeVarInt(nextState);
}
@Override
public void decode(ByteMessage msg, Direction direction, Version version) {
this.version = Version.of(msg.readVarInt());
this.host = msg.readString();
this.port = msg.readUnsignedShort();
this.nextState = msg.readVarInt();
}
}

View File

@@ -0,0 +1,21 @@
package ru.nanit.limbo.protocol.packets.login;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.Direction;
import ru.nanit.limbo.protocol.PacketOut;
import ru.nanit.limbo.protocol.registry.Version;
public class PacketDisconnect implements PacketOut {
private String reason;
public void setReason(String reason) {
this.reason = reason;
}
@Override
public void encode(ByteMessage msg, Direction direction, Version version) {
msg.writeString(String.format("{\"text\": \"%s\"}", reason));
}
}

View File

@@ -0,0 +1,19 @@
package ru.nanit.limbo.protocol.packets.login;
import ru.nanit.limbo.protocol.*;
import ru.nanit.limbo.protocol.registry.Version;
public class PacketLoginStart implements PacketIn {
private String username;
public String getUsername() {
return username;
}
@Override
public void decode(ByteMessage msg, Direction direction, Version version) {
this.username = msg.readString();
}
}

View File

@@ -0,0 +1,29 @@
package ru.nanit.limbo.protocol.packets.login;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.Direction;
import ru.nanit.limbo.protocol.PacketOut;
import ru.nanit.limbo.protocol.registry.Version;
import java.util.UUID;
public class PacketLoginSuccess implements PacketOut {
private UUID uuid;
private String username;
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public void encode(ByteMessage msg, Direction direction, Version version) {
msg.writeUuid(uuid);
msg.writeString(username);
}
}

View File

@@ -0,0 +1,99 @@
package ru.nanit.limbo.protocol.packets.play;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.Direction;
import ru.nanit.limbo.protocol.PacketOut;
import ru.nanit.limbo.protocol.registry.Version;
import java.util.List;
public class PacketJoinGame implements PacketOut {
private int entityId;
private boolean isHardcore = false;
private int gameMode = 2;
private int previousGameMode = -1;
private int worldCount = 1;
private List<String> worldNames;
private CompoundBinaryTag dimensionCodec;
private CompoundBinaryTag dimension;
private String worldName;
private long hashedSeed;
private int maxPlayers;
private int viewDistance = 2;
private boolean reducedDebugInfo;
private boolean enableRespawnScreen;
private boolean isDebug;
private boolean isFlat;
public void setEntityId(int entityId) {
this.entityId = entityId;
}
public void setHardcore(boolean hardcore) {
isHardcore = hardcore;
}
public void setGameMode(int gameMode) {
this.gameMode = gameMode;
}
public void setPreviousGameMode(int previousGameMode) {
this.previousGameMode = previousGameMode;
}
public void setWorldCount(int worldCount) {
this.worldCount = worldCount;
}
public void setWorldNames(List<String> worldNames) {
this.worldNames = worldNames;
}
public void setDimensionCodec(CompoundBinaryTag dimensionCodec) {
this.dimensionCodec = dimensionCodec;
}
public void setDimension(CompoundBinaryTag dimension) {
this.dimension = dimension;
}
public void setWorldName(String worldName) {
this.worldName = worldName;
}
public void setHashedSeed(long hashedSeed) {
this.hashedSeed = hashedSeed;
}
public void setMaxPlayers(int maxPlayers) {
this.maxPlayers = maxPlayers;
}
public void setViewDistance(int viewDistance) {
this.viewDistance = viewDistance;
}
public void setReducedDebugInfo(boolean reducedDebugInfo) {
this.reducedDebugInfo = reducedDebugInfo;
}
public void setEnableRespawnScreen(boolean enableRespawnScreen) {
this.enableRespawnScreen = enableRespawnScreen;
}
public void setDebug(boolean debug) {
isDebug = debug;
}
public void setFlat(boolean flat) {
isFlat = flat;
}
@Override
public void encode(ByteMessage msg, Direction direction, Version version) {
}
}

View File

@@ -0,0 +1,22 @@
package ru.nanit.limbo.protocol.packets.status;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.Packet;
import ru.nanit.limbo.protocol.Direction;
import ru.nanit.limbo.protocol.registry.Version;
public class PacketStatusPing implements Packet {
private long randomId;
@Override
public void encode(ByteMessage msg, Direction direction, Version version) {
msg.writeLong(randomId);
}
@Override
public void decode(ByteMessage msg, Direction direction, Version version) {
this.randomId = msg.readLong();
}
}

View File

@@ -0,0 +1,13 @@
package ru.nanit.limbo.protocol.packets.status;
import ru.nanit.limbo.protocol.*;
import ru.nanit.limbo.protocol.registry.Version;
public class PacketStatusRequest implements PacketIn {
@Override
public void decode(ByteMessage msg, Direction direction, Version version) {
}
}

View File

@@ -0,0 +1,22 @@
package ru.nanit.limbo.protocol.packets.status;
import ru.nanit.limbo.LimboConfig;
import ru.nanit.limbo.protocol.*;
import ru.nanit.limbo.protocol.registry.Version;
public class PacketStatusResponse implements PacketOut {
private static final String TEMPLATE = "{ \"version\": { \"name\": \"%s\", \"protocol\": %d }, \"players\": { \"max\": %d, \"online\": %d, \"sample\": [] }, \"description\": %s }";
@Override
public void encode(ByteMessage msg, Direction direction, Version version) {
String ver = LimboConfig.getPingData().getVersion();
String desc = LimboConfig.getPingData().getDescription();
String json = getResponseJson(ver, version.getProtocolNumber(), LimboConfig.getMaxPlayers(), 0, desc);
msg.writeString(json);
}
private String getResponseJson(String version, int protocol, int maxPlayers, int online, String description){
return String.format(TEMPLATE, version, protocol, maxPlayers, online, description);
}
}

View File

@@ -0,0 +1,52 @@
package ru.nanit.limbo.protocol.pipeline;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import ru.nanit.limbo.protocol.*;
import ru.nanit.limbo.protocol.registry.State;
import ru.nanit.limbo.protocol.registry.Version;
import ru.nanit.limbo.util.Logger;
import java.util.List;
public class PacketDecoder extends MessageToMessageDecoder<ByteBuf> {
private State.PacketVersionRegistry.PacketIdRegistry<?> mappings;
private Version version;
public PacketDecoder(){
updateVersion(Version.getMinimal());
updateState(State.HANDSHAKING);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) throws Exception {
if (!ctx.channel().isActive() || mappings == null) return;
ByteMessage msg = new ByteMessage(buf);
int packetId = msg.readVarInt();
Packet packet = mappings.getPacket(packetId);
if (packet != null){
try {
packet.decode(msg, Direction.SERVER, mappings.getVersion());
} catch (Exception e){
Logger.warning("Cannot decode packet 0x%s: %s", Integer.toHexString(packetId), e.getMessage());
}
ctx.fireChannelRead(packet);
} else {
Logger.warning("Undefined incoming packet: 0x" + Integer.toHexString(packetId));
}
}
public void updateVersion(Version version){
this.version = version;
}
public void updateState(State state){
this.mappings = state.serverBound.getRegistry(version);
}
}

View File

@@ -0,0 +1,52 @@
package ru.nanit.limbo.protocol.pipeline;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.Packet;
import ru.nanit.limbo.protocol.Direction;
import ru.nanit.limbo.protocol.registry.Version;
import ru.nanit.limbo.protocol.registry.State;
import ru.nanit.limbo.util.Logger;
public class PacketEncoder extends MessageToByteEncoder<Packet> {
private State.PacketVersionRegistry.PacketIdRegistry<?> mappings;
private Version version;
public PacketEncoder(){
updateVersion(Version.getMinimal());
updateState(State.HANDSHAKING);
}
@Override
protected void encode(ChannelHandlerContext ctx, Packet packet, ByteBuf out) throws Exception {
if (mappings == null) return;
ByteMessage msg = new ByteMessage(out);
int packetId = mappings.getPacketId(packet.getClass());
if (packetId == -1){
Logger.warning("Undefined packet class: %s", packet.getClass().getName());
return;
}
msg.writeVarInt(packetId);
try {
packet.encode(msg, Direction.CLIENT, version);
} catch (Exception e){
Logger.warning("Cannot encode packet 0x%s: %s", Integer.toHexString(packetId), e.getMessage());
}
}
public void updateVersion(Version version){
this.version = version;
}
public void updateState(State state){
this.mappings = state.clientBound.getRegistry(version);
}
}

View File

@@ -0,0 +1,42 @@
package ru.nanit.limbo.protocol.pipeline;
import io.netty.util.ByteProcessor;
public class VarIntByteDecoder implements ByteProcessor {
private int readVarInt;
private int bytesRead;
private DecodeResult result = DecodeResult.TOO_SHORT;
@Override
public boolean process(byte k) {
readVarInt |= (k & 0x7F) << bytesRead++ * 7;
if (bytesRead > 3) {
result = DecodeResult.TOO_BIG;
return false;
}
if ((k & 0x80) != 128) {
result = DecodeResult.SUCCESS;
return false;
}
return true;
}
public int getReadVarint() {
return readVarInt;
}
public int getBytesRead() {
return bytesRead;
}
public DecodeResult getResult() {
return result;
}
public enum DecodeResult {
SUCCESS,
TOO_SHORT,
TOO_BIG
}
}

View File

@@ -0,0 +1,46 @@
package ru.nanit.limbo.protocol.pipeline;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import ru.nanit.limbo.util.Logger;
import java.util.List;
public class VarIntFrameDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
if (!ctx.channel().isActive()) {
in.clear();
return;
}
final VarIntByteDecoder reader = new VarIntByteDecoder();
int varintEnd = in.forEachByte(reader);
if (varintEnd == -1) {
return;
}
if (reader.getResult() == VarIntByteDecoder.DecodeResult.SUCCESS) {
int readVarint = reader.getReadVarint();
int bytesRead = reader.getBytesRead();
if (readVarint < 0) {
Logger.error("BAD_LENGTH_CACHED");
} else if (readVarint == 0) {
// skip over the empty packet and ignore it
in.readerIndex(varintEnd + 1);
} else {
int minimumRead = bytesRead + readVarint;
if (in.isReadable(minimumRead)) {
out.add(in.retainedSlice(varintEnd + 1, readVarint));
in.skipBytes(minimumRead);
}
}
} else if (reader.getResult() == VarIntByteDecoder.DecodeResult.TOO_BIG) {
Logger.error("Too big");
}
}
}

View File

@@ -0,0 +1,24 @@
package ru.nanit.limbo.protocol.pipeline;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import ru.nanit.limbo.protocol.ByteMessage;
@ChannelHandler.Sharable
public class VarIntLengthEncoder extends MessageToByteEncoder<ByteBuf> {
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf buf, ByteBuf out) throws Exception {
ByteMessage msg = new ByteMessage(out);
msg.writeVarInt(buf.readableBytes());
msg.writeBytes(buf);
}
@Override
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception {
int anticipatedRequiredCapacity = 5 + msg.readableBytes();
return ctx.alloc().heapBuffer(anticipatedRequiredCapacity);
}
}

View File

@@ -0,0 +1,109 @@
package ru.nanit.limbo.protocol.registry;
import ru.nanit.limbo.protocol.Packet;
import ru.nanit.limbo.protocol.packets.*;
import ru.nanit.limbo.protocol.packets.login.*;
import ru.nanit.limbo.protocol.packets.play.PacketJoinGame;
import ru.nanit.limbo.protocol.packets.status.PacketStatusPing;
import ru.nanit.limbo.protocol.packets.status.PacketStatusRequest;
import ru.nanit.limbo.protocol.packets.status.PacketStatusResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public enum State {
HANDSHAKING(0){
{
serverBound.register(Version.getMinimal(), 0x00, PacketHandshake::new);
}
},
STATUS(1){
{
clientBound.register(Version.getMinimal(), 0x00, PacketStatusResponse::new);
clientBound.register(Version.getMinimal(), 0x01, PacketStatusPing::new);
serverBound.register(Version.getMinimal(), 0x01, PacketStatusPing::new);
serverBound.register(Version.getMinimal(), 0x00, PacketStatusRequest::new);
}
},
LOGIN(2){
{
clientBound.register(Version.getMinimal(), 0x00, PacketDisconnect::new);
clientBound.register(Version.getMinimal(), 0x02, PacketLoginSuccess::new);
serverBound.register(Version.getMinimal(), 0x00, PacketLoginStart::new);
}
},
PLAY(3){
{
clientBound.register(Version.V1_16_4, 0x24, PacketJoinGame::new);
}
};
private final int stateId;
public final PacketVersionRegistry serverBound = new PacketVersionRegistry();
public final PacketVersionRegistry clientBound = new PacketVersionRegistry();
private static final Map<Integer, State> STATE_BY_ID = new HashMap<>();
static {
for (State registry : values()){
STATE_BY_ID.put(registry.stateId, registry);
}
}
State(int stateId){
this.stateId = stateId;
}
public static State getById(int stateId){
return STATE_BY_ID.get(stateId);
}
public static class PacketVersionRegistry {
private final Map<Version, PacketIdRegistry<?>> MAPPINGS = new HashMap<>();
public PacketIdRegistry<?> getRegistry(Version version){
PacketIdRegistry<?> registry = MAPPINGS.get(version);
return registry != null ? registry : MAPPINGS.get(version.getClosest(MAPPINGS.keySet()));
}
public <T extends Packet> void register(Version version, int packetId, Supplier<T> supplier){
PacketIdRegistry<T> registry = (PacketIdRegistry<T>) MAPPINGS.computeIfAbsent(version, PacketIdRegistry::new);
registry.register(packetId, supplier);
}
public static class PacketIdRegistry<T extends Packet> {
private final Version version;
private final Map<Integer, Supplier<T>> packetsById = new HashMap<>();
private final Map<Class<?>, Integer> packetIdByClass = new HashMap<>();
public PacketIdRegistry(Version version){
this.version = version;
}
public Version getVersion(){
return version;
}
public Packet getPacket(int packetId){
Supplier<T> supplier = packetsById.get(packetId);
return supplier == null ? null : supplier.get();
}
public int getPacketId(Class<?> packetClass){
return packetIdByClass.getOrDefault(packetClass, -1);
}
public void register(int packetId, Supplier<T> supplier){
packetsById.put(packetId, supplier);
packetIdByClass.put(supplier.get().getClass(), packetId);
}
}
}
}

View File

@@ -0,0 +1,77 @@
package ru.nanit.limbo.protocol.registry;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public enum Version {
UNDEFINED(-1),
V1_9(107),
V1_9_1(108),
V1_9_2(109),
V1_9_4(110),
V1_10(210),
V1_11(315),
V1_11_1(316),
V1_12(335),
V1_12_1(338),
V1_12_2(340),
V1_13(393),
V1_13_1(401),
V1_13_2(404),
V1_14(477),
V1_14_1(480),
V1_14_2(485),
V1_14_3(490),
V1_14_4(498),
V1_15(573),
V1_15_1(575),
V1_15_2(578),
V1_16(735),
V1_16_1(736),
V1_16_2(751),
V1_16_3(753),
V1_16_4(754);
public static final Map<Integer, Version> VERSION_MAP;
static {
VERSION_MAP = new HashMap<>();
for (Version version : values()){
VERSION_MAP.put(version.getProtocolNumber(), version);
}
}
public static Version getMinimal(){
return V1_9;
}
public static Version of(int protocolNumber){
return VERSION_MAP.getOrDefault(protocolNumber, UNDEFINED);
}
private final int protocolNumber;
Version(int protocolNumber){
this.protocolNumber = protocolNumber;
}
public int getProtocolNumber(){
return this.protocolNumber;
}
public Version getClosest(Collection<Version> available){
Version closest = getMinimal();
for (Version version : available){
if (version.protocolNumber > closest.protocolNumber && version.protocolNumber < this.protocolNumber){
closest = version;
}
}
return closest;
}
}