mirror of
https://github.com/Nan1t/NanoLimbo.git
synced 2025-07-08 19:20:13 +02:00
Moved from Gitlab
This commit is contained in:
commit
ccedd3a160
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
.idea
|
||||
.gradle
|
||||
gradle
|
||||
gradlew
|
||||
gradlew.bat
|
||||
build
|
26
build.gradle
Normal file
26
build.gradle
Normal 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
2
settings.gradle
Normal file
@ -0,0 +1,2 @@
|
||||
rootProject.name = 'NanoLimbo'
|
||||
|
94
src/main/java/ru/nanit/limbo/LimboConfig.java
Normal file
94
src/main/java/ru/nanit/limbo/LimboConfig.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
26
src/main/java/ru/nanit/limbo/NanoLimbo.java
Normal file
26
src/main/java/ru/nanit/limbo/NanoLimbo.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
105
src/main/java/ru/nanit/limbo/connection/ClientConnection.java
Normal file
105
src/main/java/ru/nanit/limbo/connection/ClientConnection.java
Normal 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);
|
||||
}
|
||||
}
|
1107
src/main/java/ru/nanit/limbo/protocol/ByteMessage.java
Normal file
1107
src/main/java/ru/nanit/limbo/protocol/ByteMessage.java
Normal file
File diff suppressed because it is too large
Load Diff
8
src/main/java/ru/nanit/limbo/protocol/Direction.java
Normal file
8
src/main/java/ru/nanit/limbo/protocol/Direction.java
Normal file
@ -0,0 +1,8 @@
|
||||
package ru.nanit.limbo.protocol;
|
||||
|
||||
public enum Direction {
|
||||
|
||||
CLIENT,
|
||||
SERVER
|
||||
|
||||
}
|
11
src/main/java/ru/nanit/limbo/protocol/Packet.java
Normal file
11
src/main/java/ru/nanit/limbo/protocol/Packet.java
Normal 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);
|
||||
|
||||
}
|
12
src/main/java/ru/nanit/limbo/protocol/PacketIn.java
Normal file
12
src/main/java/ru/nanit/limbo/protocol/PacketIn.java
Normal 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
|
||||
}
|
||||
|
||||
}
|
12
src/main/java/ru/nanit/limbo/protocol/PacketOut.java
Normal file
12
src/main/java/ru/nanit/limbo/protocol/PacketOut.java
Normal 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
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
109
src/main/java/ru/nanit/limbo/protocol/registry/State.java
Normal file
109
src/main/java/ru/nanit/limbo/protocol/registry/State.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
77
src/main/java/ru/nanit/limbo/protocol/registry/Version.java
Normal file
77
src/main/java/ru/nanit/limbo/protocol/registry/Version.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
24
src/main/java/ru/nanit/limbo/server/LimboServer.java
Normal file
24
src/main/java/ru/nanit/limbo/server/LimboServer.java
Normal 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());
|
||||
}
|
||||
|
||||
}
|
63
src/main/java/ru/nanit/limbo/util/Logger.java
Normal file
63
src/main/java/ru/nanit/limbo/util/Logger.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
13
src/main/java/ru/nanit/limbo/util/UuidUtil.java
Normal file
13
src/main/java/ru/nanit/limbo/util/UuidUtil.java
Normal 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));
|
||||
}
|
||||
|
||||
}
|
16
src/main/resources/settings.properties
Normal file
16
src/main/resources/settings.properties
Normal 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"}
|
Loading…
x
Reference in New Issue
Block a user