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

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.idea
.gradle
gradle
gradlew
gradlew.bat
build

26
build.gradle Normal file
View File

@ -0,0 +1,26 @@
plugins {
id 'java'
}
group 'ru.nanit'
version '1.0'
repositories {
mavenCentral()
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
compile group: 'io.netty', name: 'netty-all', version: '4.1.54.Final'
compile group: 'net.kyori', name: 'adventure-nbt', version: '4.1.1'
}
jar {
manifest {
attributes("Main-Class": "ru.nanit.limbo.NanoLimbo")
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}

2
settings.gradle Normal file
View File

@ -0,0 +1,2 @@
rootProject.name = 'NanoLimbo'

View File

@ -0,0 +1,94 @@
package ru.nanit.limbo;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;
public final class LimboConfig {
private static String host;
private static int port;
private static boolean onlineMode;
private static int maxPlayers;
private static IpForwardingType ipForwardingType;
private static long readTimeout;
private static PingData pingData;
public static void load(Path file) throws IOException {
if (!Files.exists(file)){
Files.copy(LimboConfig.class.getResourceAsStream("/settings.properties"), file);
}
Properties properties = new Properties();
properties.load(Files.newInputStream(file));
host = properties.getProperty("host");
port = Integer.parseInt(properties.getProperty("port"));
onlineMode = Boolean.parseBoolean(properties.getProperty("online-mode"));
maxPlayers = Integer.parseInt(properties.getProperty("max-players"));
ipForwardingType = IpForwardingType.valueOf(properties.getProperty("ip-forwarding").toUpperCase());
readTimeout = Long.parseLong(properties.getProperty("read-timeout"));
pingData = new PingData();
pingData.setVersion(properties.getProperty("ping-version"));
pingData.setDescription(properties.getProperty("ping-description"));
}
public static String getHost() {
return host;
}
public static int getPort() {
return port;
}
public static boolean isOnlineMode() {
return onlineMode;
}
public static int getMaxPlayers() {
return maxPlayers;
}
public static IpForwardingType getIpForwardingType() {
return ipForwardingType;
}
public static long getReadTimeout() {
return readTimeout;
}
public static PingData getPingData() {
return pingData;
}
public enum IpForwardingType {
NONE,
LEGACY,
MODERN
}
public static class PingData {
private String version;
private String description;
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
}

View File

@ -0,0 +1,26 @@
package ru.nanit.limbo;
import ru.nanit.limbo.server.LimboServer;
import ru.nanit.limbo.util.Logger;
import java.nio.file.Paths;
public final class NanoLimbo {
private LimboServer server;
public void start() throws Exception {
LimboConfig.load(Paths.get("./settings.properties"));
server = new LimboServer();
server.start();
}
public static void main(String[] args){
try {
new NanoLimbo().start();
} catch (Exception e){
Logger.error("Cannot start server: ", e);
}
}
}

View File

@ -0,0 +1,36 @@
package ru.nanit.limbo.connection;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.timeout.ReadTimeoutHandler;
import ru.nanit.limbo.LimboConfig;
import ru.nanit.limbo.protocol.pipeline.VarIntFrameDecoder;
import ru.nanit.limbo.protocol.pipeline.PacketDecoder;
import ru.nanit.limbo.protocol.pipeline.PacketEncoder;
import ru.nanit.limbo.protocol.pipeline.VarIntLengthEncoder;
import ru.nanit.limbo.server.LimboServer;
import java.util.concurrent.TimeUnit;
public class ClientChannelInitializer extends ChannelInitializer<Channel> {
private final LimboServer server;
public ClientChannelInitializer(LimboServer server){
this.server = server;
}
@Override
protected void initChannel(Channel channel) {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast("timeout", new ReadTimeoutHandler(LimboConfig.getReadTimeout(), TimeUnit.MILLISECONDS));
pipeline.addLast("frame_decoder", new VarIntFrameDecoder());
pipeline.addLast("frame_encoder", new VarIntLengthEncoder());
pipeline.addLast("decoder", new PacketDecoder());
pipeline.addLast("encoder", new PacketEncoder());
pipeline.addLast("handler", new ClientConnection(channel, server));
}
}

View File

@ -0,0 +1,105 @@
package ru.nanit.limbo.connection;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import ru.nanit.limbo.protocol.packets.login.*;
import ru.nanit.limbo.protocol.registry.Version;
import ru.nanit.limbo.protocol.pipeline.PacketDecoder;
import ru.nanit.limbo.protocol.pipeline.PacketEncoder;
import ru.nanit.limbo.protocol.packets.PacketHandshake;
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 ru.nanit.limbo.protocol.registry.State;
import ru.nanit.limbo.server.LimboServer;
import ru.nanit.limbo.util.UuidUtil;
public class ClientConnection extends ChannelInboundHandlerAdapter {
private final LimboServer server;
private final Channel channel;
private String username;
public ClientConnection(Channel channel, LimboServer server){
this.channel = channel;
this.server = server;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
handlePacket(msg);
}
public void handlePacket(Object packet){
if (packet instanceof PacketHandshake){
PacketHandshake handshake = (PacketHandshake) packet;
State state = State.getById(handshake.getNextState());
updateStateAndVersion(state, handshake.getVersion());
}
if (packet instanceof PacketStatusRequest){
sendPacket(new PacketStatusResponse());
}
if (packet instanceof PacketStatusPing){
sendPacketAndClose(packet);
}
if (packet instanceof PacketLoginStart){
this.username = ((PacketLoginStart) packet).getUsername();
// Limbo always in offline mode. Online mode set on proxy side
PacketLoginSuccess loginSuccess = new PacketLoginSuccess();
loginSuccess.setUuid(UuidUtil.getOfflineModeUuid(this.username));
loginSuccess.setUsername(this.username);
sendPacket(loginSuccess);
updateState(State.PLAY);
}
}
public void sendPacket(Object packet){
if (isConnected())
channel.writeAndFlush(packet, channel.voidPromise());
}
public void sendPacketAndClose(Object packet){
if (isConnected())
channel.writeAndFlush(packet).addListener(ChannelFutureListener.CLOSE);
}
public void disconnect(){
if (channel.isActive()){
channel.close();
}
}
public void disconnect(String reason){
PacketDisconnect packet = new PacketDisconnect();
packet.setReason(reason);
sendPacketAndClose(packet);
}
public boolean isConnected(){
return channel.isActive();
}
public void updateState(State state){
channel.pipeline().get(PacketDecoder.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);
}
}

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

View File

@ -0,0 +1,24 @@
package ru.nanit.limbo.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import ru.nanit.limbo.LimboConfig;
import ru.nanit.limbo.connection.ClientChannelInitializer;
import ru.nanit.limbo.util.Logger;
public final class LimboServer {
public void start() throws Exception {
Logger.info("Starting server...");
ServerBootstrap bootstrap = new ServerBootstrap()
.group(new NioEventLoopGroup(), new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ClientChannelInitializer(this));
bootstrap.bind(LimboConfig.getHost(), LimboConfig.getPort());
Logger.info("Server started on %s:%d", LimboConfig.getHost(), LimboConfig.getPort());
}
}

View File

@ -0,0 +1,63 @@
package ru.nanit.limbo.util;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
public final class Logger {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("hh:mm:ss");
public static void info(Object msg, Object... args){
print(Level.INFO, msg, null, args);
}
public static void info(Object msg, Throwable t, Object... args){
print(Level.INFO, msg, t, args);
}
public static void warning(Object msg, Object... args){
print(Level.WARNING, msg, null, args);
}
public static void warning(Object msg, Throwable t, Object... args){
print(Level.WARNING, msg, t, args);
}
public static void error(Object msg, Object... args){
print(Level.ERROR, msg, null, args);
}
public static void error(Object msg, Throwable t, Object... args){
print(Level.ERROR, msg, t, args);
}
public static void print(Level level, Object msg, Throwable t, Object... args){
System.out.println(String.format("%s: %s", getPrefix(level), String.format(msg.toString(), args)));
if (t != null) t.printStackTrace();
}
private static String getPrefix(Level level){
return String.format("[%s] [%s]", getTime(), level.getDisplay());
}
private static String getTime(){
return LocalTime.now().format(FORMATTER);
}
public enum Level {
INFO ("INFO"),
WARNING("WARNING"),
ERROR("ERROR");
private final String display;
Level(String display){
this.display = display;
}
public String getDisplay() {
return display;
}
}
}

View File

@ -0,0 +1,13 @@
package ru.nanit.limbo.util;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
public class UuidUtil {
public static UUID getOfflineModeUuid(String username){
return UUID.nameUUIDFromBytes(("OfflinePlayer:" + username)
.getBytes(StandardCharsets.UTF_8));
}
}

View File

@ -0,0 +1,16 @@
#
# NanoLimbo configuration
#
host=localhost
port=65535
max-players=100
# Proxy forwarding support. Available types: NONE, LEGACY, MODERN
ip-forwarding=LEGACY
# Read timeout for connections in milliseconds
read-timeout=30000
ping-version=NanoLimbo
ping-description={"text": "NanoLimbo"}