Decided to use one version. Compatibility with other can be achieved woth protocol hack on proxy side

This commit is contained in:
Nanit 2020-11-26 23:09:40 +02:00
parent 6350181dfc
commit e029336cf0
21 changed files with 96 additions and 109 deletions

View File

@ -7,7 +7,6 @@ import io.netty.channel.ChannelInboundHandlerAdapter;
import ru.nanit.limbo.LimboConfig; import ru.nanit.limbo.LimboConfig;
import ru.nanit.limbo.protocol.packets.login.*; import ru.nanit.limbo.protocol.packets.login.*;
import ru.nanit.limbo.protocol.packets.play.*; import ru.nanit.limbo.protocol.packets.play.*;
import ru.nanit.limbo.protocol.registry.Version;
import ru.nanit.limbo.protocol.pipeline.PacketDecoder; import ru.nanit.limbo.protocol.pipeline.PacketDecoder;
import ru.nanit.limbo.protocol.pipeline.PacketEncoder; import ru.nanit.limbo.protocol.pipeline.PacketEncoder;
import ru.nanit.limbo.protocol.packets.PacketHandshake; import ru.nanit.limbo.protocol.packets.PacketHandshake;
@ -15,6 +14,7 @@ import ru.nanit.limbo.protocol.packets.status.PacketStatusPing;
import ru.nanit.limbo.protocol.packets.status.PacketStatusRequest; import ru.nanit.limbo.protocol.packets.status.PacketStatusRequest;
import ru.nanit.limbo.protocol.packets.status.PacketStatusResponse; import ru.nanit.limbo.protocol.packets.status.PacketStatusResponse;
import ru.nanit.limbo.protocol.registry.State; import ru.nanit.limbo.protocol.registry.State;
import ru.nanit.limbo.protocol.registry.Version;
import ru.nanit.limbo.server.LimboServer; import ru.nanit.limbo.server.LimboServer;
import ru.nanit.limbo.util.Logger; import ru.nanit.limbo.util.Logger;
import ru.nanit.limbo.util.UuidUtil; import ru.nanit.limbo.util.UuidUtil;
@ -29,6 +29,7 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
private final Channel channel; private final Channel channel;
private State state; private State state;
private Version clientVersion;
private UUID uuid; private UUID uuid;
private String username; private String username;
@ -66,12 +67,12 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
public void handlePacket(Object packet){ public void handlePacket(Object packet){
if (packet instanceof PacketHandshake){ if (packet instanceof PacketHandshake){
PacketHandshake handshake = (PacketHandshake) packet; PacketHandshake handshake = (PacketHandshake) packet;
State state = State.getById(handshake.getNextState()); updateState(State.getById(handshake.getNextState()));
updateStateAndVersion(state, handshake.getVersion()); clientVersion = handshake.getVersion();
} }
if (packet instanceof PacketStatusRequest){ if (packet instanceof PacketStatusRequest){
sendPacket(new PacketStatusResponse()); sendPacket(new PacketStatusResponse(server.getConnectionsCount()));
} }
if (packet instanceof PacketStatusPing){ if (packet instanceof PacketStatusPing){
@ -84,6 +85,11 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
return; return;
} }
if (!clientVersion.equals(Version.getCurrentSupported())){
disconnect("Incompatible client version");
return;
}
this.username = ((PacketLoginStart) packet).getUsername(); this.username = ((PacketLoginStart) packet).getUsername();
this.uuid = UuidUtil.getOfflineModeUuid(this.username); this.uuid = UuidUtil.getOfflineModeUuid(this.username);
@ -96,7 +102,7 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
updateState(State.PLAY); updateState(State.PLAY);
server.addConnection(this); server.addConnection(this);
Logger.info("Player %s connected", this.username); Logger.info("Player %s connected (%s)", this.username, channel.remoteAddress());
sendJoinPackets(); sendJoinPackets();
} }
@ -179,16 +185,4 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
channel.pipeline().get(PacketDecoder.class).updateState(state); channel.pipeline().get(PacketDecoder.class).updateState(state);
channel.pipeline().get(PacketEncoder.class).updateState(state); channel.pipeline().get(PacketEncoder.class).updateState(state);
} }
public void updateStateAndVersion(State state, Version version){
PacketDecoder decoder = channel.pipeline().get(PacketDecoder.class);
PacketEncoder encoder = channel.pipeline().get(PacketEncoder.class);
decoder.updateVersion(version);
decoder.updateState(state);
encoder.updateVersion(version);
encoder.updateState(state);
this.state = state;
}
} }

View File

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

View File

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

View File

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

View File

@ -44,7 +44,7 @@ public class PacketHandshake implements Packet {
} }
@Override @Override
public void encode(ByteMessage msg, Version version) { public void encode(ByteMessage msg) {
msg.writeVarInt(this.version.getProtocolNumber()); msg.writeVarInt(this.version.getProtocolNumber());
msg.writeString(host); msg.writeString(host);
msg.writeShort(port); msg.writeShort(port);
@ -52,7 +52,7 @@ public class PacketHandshake implements Packet {
} }
@Override @Override
public void decode(ByteMessage msg, Version version) { public void decode(ByteMessage msg) {
this.version = Version.of(msg.readVarInt()); this.version = Version.of(msg.readVarInt());
this.host = msg.readString(); this.host = msg.readString();
this.port = msg.readUnsignedShort(); this.port = msg.readUnsignedShort();

View File

@ -2,7 +2,6 @@ package ru.nanit.limbo.protocol.packets.login;
import ru.nanit.limbo.protocol.ByteMessage; import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.PacketOut; import ru.nanit.limbo.protocol.PacketOut;
import ru.nanit.limbo.protocol.registry.Version;
public class PacketDisconnect implements PacketOut { public class PacketDisconnect implements PacketOut {
@ -13,7 +12,7 @@ public class PacketDisconnect implements PacketOut {
} }
@Override @Override
public void encode(ByteMessage msg, Version version) { public void encode(ByteMessage msg) {
msg.writeString(String.format("{\"text\": \"%s\"}", reason)); msg.writeString(String.format("{\"text\": \"%s\"}", reason));
} }

View File

@ -1,7 +1,6 @@
package ru.nanit.limbo.protocol.packets.login; package ru.nanit.limbo.protocol.packets.login;
import ru.nanit.limbo.protocol.*; import ru.nanit.limbo.protocol.*;
import ru.nanit.limbo.protocol.registry.Version;
public class PacketLoginStart implements PacketIn { public class PacketLoginStart implements PacketIn {
@ -12,7 +11,7 @@ public class PacketLoginStart implements PacketIn {
} }
@Override @Override
public void decode(ByteMessage msg, Version version) { public void decode(ByteMessage msg) {
this.username = msg.readString(); this.username = msg.readString();
} }

View File

@ -2,7 +2,6 @@ package ru.nanit.limbo.protocol.packets.login;
import ru.nanit.limbo.protocol.ByteMessage; import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.PacketOut; import ru.nanit.limbo.protocol.PacketOut;
import ru.nanit.limbo.protocol.registry.Version;
import java.util.UUID; import java.util.UUID;
@ -20,7 +19,7 @@ public class PacketLoginSuccess implements PacketOut {
} }
@Override @Override
public void encode(ByteMessage msg, Version version) { public void encode(ByteMessage msg) {
msg.writeUuid(uuid); msg.writeUuid(uuid);
msg.writeString(username); msg.writeString(username);
} }

View File

@ -2,7 +2,6 @@ package ru.nanit.limbo.protocol.packets.play;
import ru.nanit.limbo.protocol.ByteMessage; import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.PacketOut; import ru.nanit.limbo.protocol.PacketOut;
import ru.nanit.limbo.protocol.registry.Version;
import java.util.UUID; import java.util.UUID;
@ -40,7 +39,7 @@ public class PacketBossBar implements PacketOut {
} }
@Override @Override
public void encode(ByteMessage msg, Version version) { public void encode(ByteMessage msg) {
msg.writeUuid(uuid); msg.writeUuid(uuid);
msg.writeVarInt(0); // Create bossbar msg.writeVarInt(0); // Create bossbar
msg.writeString(title); msg.writeString(title);

View File

@ -2,7 +2,6 @@ package ru.nanit.limbo.protocol.packets.play;
import ru.nanit.limbo.protocol.ByteMessage; import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.PacketOut; import ru.nanit.limbo.protocol.PacketOut;
import ru.nanit.limbo.protocol.registry.Version;
import java.util.UUID; import java.util.UUID;
@ -25,7 +24,7 @@ public class PacketChatMessage implements PacketOut {
} }
@Override @Override
public void encode(ByteMessage msg, Version version) { public void encode(ByteMessage msg) {
msg.writeString(jsonData); msg.writeString(jsonData);
msg.writeByte(position.index); msg.writeByte(position.index);
msg.writeUuid(sender); msg.writeUuid(sender);

View File

@ -3,7 +3,6 @@ package ru.nanit.limbo.protocol.packets.play;
import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.nbt.CompoundBinaryTag;
import ru.nanit.limbo.protocol.ByteMessage; import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.PacketOut; import ru.nanit.limbo.protocol.PacketOut;
import ru.nanit.limbo.protocol.registry.Version;
public class PacketJoinGame implements PacketOut { public class PacketJoinGame implements PacketOut {
@ -84,7 +83,7 @@ public class PacketJoinGame implements PacketOut {
} }
@Override @Override
public void encode(ByteMessage msg, Version version) { public void encode(ByteMessage msg) {
msg.writeInt(entityId); msg.writeInt(entityId);
msg.writeBoolean(isHardcore); msg.writeBoolean(isHardcore);
msg.writeByte(gameMode); msg.writeByte(gameMode);

View File

@ -2,7 +2,6 @@ package ru.nanit.limbo.protocol.packets.play;
import ru.nanit.limbo.protocol.ByteMessage; import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.Packet; import ru.nanit.limbo.protocol.Packet;
import ru.nanit.limbo.protocol.registry.Version;
public class PacketKeepAlive implements Packet { public class PacketKeepAlive implements Packet {
@ -17,12 +16,12 @@ public class PacketKeepAlive implements Packet {
} }
@Override @Override
public void encode(ByteMessage msg, Version version) { public void encode(ByteMessage msg) {
msg.writeLong(id); msg.writeLong(id);
} }
@Override @Override
public void decode(ByteMessage msg, Version version) { public void decode(ByteMessage msg) {
this.id = msg.readLong(); this.id = msg.readLong();
} }

View File

@ -2,7 +2,6 @@ package ru.nanit.limbo.protocol.packets.play;
import ru.nanit.limbo.protocol.ByteMessage; import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.PacketOut; import ru.nanit.limbo.protocol.PacketOut;
import ru.nanit.limbo.protocol.registry.Version;
public class PacketPlayerPositionAndLook implements PacketOut { public class PacketPlayerPositionAndLook implements PacketOut {
@ -43,7 +42,7 @@ public class PacketPlayerPositionAndLook implements PacketOut {
} }
@Override @Override
public void encode(ByteMessage msg, Version version) { public void encode(ByteMessage msg) {
msg.writeDouble(x); msg.writeDouble(x);
msg.writeDouble(y); msg.writeDouble(y);
msg.writeDouble(z); msg.writeDouble(z);

View File

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

View File

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

View File

@ -8,11 +8,20 @@ public class PacketStatusResponse implements PacketOut {
private static final String TEMPLATE = "{ \"version\": { \"name\": \"%s\", \"protocol\": %d }, \"players\": { \"max\": %d, \"online\": %d, \"sample\": [] }, \"description\": %s }"; private static final String TEMPLATE = "{ \"version\": { \"name\": \"%s\", \"protocol\": %d }, \"players\": { \"max\": %d, \"online\": %d, \"sample\": [] }, \"description\": %s }";
private int online;
public PacketStatusResponse(){ }
public PacketStatusResponse(int online){
this.online = online;
}
@Override @Override
public void encode(ByteMessage msg, Version version) { public void encode(ByteMessage msg) {
String ver = LimboConfig.getPingData().getVersion(); String ver = LimboConfig.getPingData().getVersion();
String desc = LimboConfig.getPingData().getDescription(); String desc = LimboConfig.getPingData().getDescription();
String json = getResponseJson(ver, version.getProtocolNumber(), LimboConfig.getMaxPlayers(), 0, desc); String json = getResponseJson(ver, Version.getCurrentSupported().getProtocolNumber(),
LimboConfig.getMaxPlayers(), online, desc);
msg.writeString(json); msg.writeString(json);
} }

View File

@ -5,18 +5,15 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder; import io.netty.handler.codec.MessageToMessageDecoder;
import ru.nanit.limbo.protocol.*; import ru.nanit.limbo.protocol.*;
import ru.nanit.limbo.protocol.registry.State; import ru.nanit.limbo.protocol.registry.State;
import ru.nanit.limbo.protocol.registry.Version;
import ru.nanit.limbo.util.Logger; import ru.nanit.limbo.util.Logger;
import java.util.List; import java.util.List;
public class PacketDecoder extends MessageToMessageDecoder<ByteBuf> { public class PacketDecoder extends MessageToMessageDecoder<ByteBuf> {
private State.PacketVersionRegistry.PacketIdRegistry<?> mappings; private State.PacketRegistry mappings;
private Version version;
public PacketDecoder(){ public PacketDecoder(){
updateVersion(Version.getMinimal());
updateState(State.HANDSHAKING); updateState(State.HANDSHAKING);
} }
@ -30,7 +27,7 @@ public class PacketDecoder extends MessageToMessageDecoder<ByteBuf> {
if (packet != null){ if (packet != null){
try { try {
packet.decode(msg, mappings.getVersion()); packet.decode(msg);
} catch (Exception e){ } catch (Exception e){
Logger.warning("Cannot decode packet 0x%s: %s", Integer.toHexString(packetId), e.getMessage()); Logger.warning("Cannot decode packet 0x%s: %s", Integer.toHexString(packetId), e.getMessage());
} }
@ -41,12 +38,8 @@ public class PacketDecoder extends MessageToMessageDecoder<ByteBuf> {
} }
} }
public void updateVersion(Version version){
this.version = version;
}
public void updateState(State state){ public void updateState(State state){
this.mappings = state.serverBound.getRegistry(version); this.mappings = state.serverBound;
} }
} }

View File

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

View File

@ -16,39 +16,35 @@ public enum State {
HANDSHAKING(0){ HANDSHAKING(0){
{ {
serverBound.register(Version.getMinimal(), 0x00, PacketHandshake::new); serverBound.register(0x00, PacketHandshake::new);
} }
}, },
STATUS(1){ STATUS(1){
{ {
serverBound.register(Version.getMinimal(), 0x01, PacketStatusPing::new); serverBound.register(0x01, PacketStatusPing::new);
serverBound.register(Version.getMinimal(), 0x00, PacketStatusRequest::new); serverBound.register(0x00, PacketStatusRequest::new);
clientBound.register(Version.getMinimal(), 0x00, PacketStatusResponse::new); clientBound.register(0x00, PacketStatusResponse::new);
clientBound.register(Version.getMinimal(), 0x01, PacketStatusPing::new); clientBound.register(0x01, PacketStatusPing::new);
} }
}, },
LOGIN(2){ LOGIN(2){
{ {
serverBound.register(Version.getMinimal(), 0x00, PacketLoginStart::new); serverBound.register(0x00, PacketLoginStart::new);
clientBound.register(Version.getMinimal(), 0x00, PacketDisconnect::new); clientBound.register(0x00, PacketDisconnect::new);
clientBound.register(Version.getMinimal(), 0x02, PacketLoginSuccess::new); clientBound.register(0x02, PacketLoginSuccess::new);
} }
}, },
PLAY(3){ PLAY(3){
{ {
serverBound.register(Version.V1_16_4, 0x10, PacketKeepAlive::new); serverBound.register(0x10, PacketKeepAlive::new);
clientBound.register(Version.V1_16_4, 0x24, PacketJoinGame::new); clientBound.register(0x24, PacketJoinGame::new);
clientBound.register(Version.V1_16_4, 0x34, PacketPlayerPositionAndLook::new); clientBound.register(0x34, PacketPlayerPositionAndLook::new);
clientBound.register(Version.V1_16_4, 0x1F, PacketKeepAlive::new); clientBound.register(0x1F, PacketKeepAlive::new);
clientBound.register(Version.V1_16_4, 0x0E, PacketChatMessage::new); clientBound.register(0x0E, PacketChatMessage::new);
clientBound.register(Version.V1_16_4, 0x0C, PacketBossBar::new); clientBound.register(0x0C, PacketBossBar::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<>(); private static final Map<Integer, State> STATE_BY_ID = new HashMap<>();
static { static {
@ -57,6 +53,10 @@ public enum State {
} }
} }
private final int stateId;
public final PacketRegistry serverBound = new PacketRegistry();
public final PacketRegistry clientBound = new PacketRegistry();
State(int stateId){ State(int stateId){
this.stateId = stateId; this.stateId = stateId;
} }
@ -65,6 +65,31 @@ public enum State {
return STATE_BY_ID.get(stateId); return STATE_BY_ID.get(stateId);
} }
public static class PacketRegistry {
private final Map<Integer, Supplier<?>> packetsById = new HashMap<>();
private final Map<Class<?>, Integer> packetIdByClass = new HashMap<>();
public Packet getPacket(int packetId){
Supplier<?> supplier = packetsById.get(packetId);
return supplier == null ? null : (Packet) supplier.get();
}
public int getPacketId(Class<?> packetClass){
return packetIdByClass.getOrDefault(packetClass, -1);
}
public void register(int packetId, Supplier<?> supplier){
packetsById.put(packetId, supplier);
packetIdByClass.put(supplier.get().getClass(), packetId);
}
}
/*
Temporary don't needed
public static class PacketVersionRegistry { public static class PacketVersionRegistry {
private final Map<Version, PacketIdRegistry<?>> MAPPINGS = new HashMap<>(); private final Map<Version, PacketIdRegistry<?>> MAPPINGS = new HashMap<>();
@ -109,6 +134,6 @@ public enum State {
} }
} }*/
} }

View File

@ -1,6 +1,5 @@
package ru.nanit.limbo.protocol.registry; package ru.nanit.limbo.protocol.registry;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -44,8 +43,8 @@ public enum Version {
} }
} }
public static Version getMinimal(){ public static Version getCurrentSupported(){
return V1_9; return V1_16_4;
} }
public static Version of(int protocolNumber){ public static Version of(int protocolNumber){
@ -62,16 +61,4 @@ public enum Version {
return this.protocolNumber; 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;
}
} }

View File

@ -18,6 +18,8 @@ ping-version=NanoLimbo
ping-description={"text": "NanoLimbo"} ping-description={"text": "NanoLimbo"}
# Player info forwarding support. Available types: NONE, LEGACY, MODERN # Player info forwarding support. Available types: NONE, LEGACY, MODERN
# MODERN - Velocity native forwarding type.
# LEGACY - BungeeCord forwarding type (Velocity supports it too)
ip-forwarding=LEGACY ip-forwarding=LEGACY
# If you use MODERN type of forwarding, enter your secret code here # If you use MODERN type of forwarding, enter your secret code here