Compare commits

..

No commits in common. "main" and "1.7" have entirely different histories.
main ... 1.7

33 changed files with 231 additions and 3288 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
ko_fi: nanit

View File

@ -5,16 +5,16 @@ The main goal of this project is maximum simplicity with a minimum number of sen
The limbo is empty; there is no ability to set a schematic building since this is not necessary. The limbo is empty; there is no ability to set a schematic building since this is not necessary.
You can send useful information via chat or boss bar. You can send useful information via chat or boss bar.
The server is fully clear. It is only able to keep a lot of players while the main server is down. No plugins, no logs. The server is fully clear. It is only able keep a lot of players while the main server is down.
General features: General features:
* High performance. The server doesn't save or cache any useless (for limbo) data. * High performance. The server doesn't save or cache any useless (for limbo) data.
* Doesn't spawn threads per player. Use a fixed thread pool. * Doesn't spawn threads per player. Uses a fixed thread pool.
* Support for **BungeeCord** and **Velocity** info forwarding. * Support for **BungeeCord** and **Velocity** info forwarding.
* Support for [BungeeGuard](https://www.spigotmc.org/resources/79601/) handshake format. * Support for [BungeeGuard](https://www.spigotmc.org/resources/79601/) handshake format.
* Multiple versions support. * Multiple versions support.
* Fully configurable. * Fully configurable.
* Lightweight. App size around **3MB**. * Lightweight. App size around **2MB**.
![](https://i.imgur.com/sT8p1Gz.png) ![](https://i.imgur.com/sT8p1Gz.png)
@ -35,8 +35,7 @@ Symbol `X` means all minor versions.
- [x] 1.17.X - [x] 1.17.X
- [x] 1.18.X - [x] 1.18.X
- [x] 1.19.X - [x] 1.19.X
- [x] 1.20.X - [x] 1.20-1.20.4
- [x] 1.21
The server **doesn't** support snapshots. The server **doesn't** support snapshots.
@ -51,16 +50,14 @@ Note that the server also will be closed correctly if you just press `Ctrl+C`.
### Installation ### Installation
Required software: JRE 11+
The installation process is simple. The installation process is simple.
1. Download the latest version of the program [**here**](https://github.com/Nan1t/NanoLimbo/releases). 1. Download the latest version of the program [**here**](https://github.com/Nan1t/NanoLimbo/releases).
2. Put the jar file in the folder you want. 2. Put the jar file in the folder you want.
3. Create a start script as you did for Bukkit or BungeeCord, with a command like this: 3. Create a start script as you did for Bukkit or BungeeCord, with a command like this:
`java -jar NanoLimbo-<version>.jar` `java -jar NanoLimbo-<version>.jar`
4. The server will create `settings.yml` file, which is the server configuration. 5. The server will create `settings.yml` file, which is the server configuration.
5. Configure it as you want and restart the server. 6. Configure it as you want and restart the server.
### Player info forwarding ### Player info forwarding
@ -78,14 +75,16 @@ Then add your tokens to `tokens` list.
### Contributing ### Contributing
Feel free to create a pull request if you find some bug or optimization opportunity, or if you want Feel free to create a pull request if you found some bug or optimization opportunity, or if you want
to add some functionality that is suitable for a limbo server and won't significantly load the server. to add some functionality that is suitable for a limbo server and won't significantly load the server.
All PRs should target the `dev` branch to keep the `main` branch stable and clean.
### Building ### Building
Required software: Required software:
* JDK 11+ * JDK 1.8+
* Gradle 7+ (optional) * Gradle 7+ (optional)
To build a minimized jar, go to the project root directory and run in the terminal: To build a minimized jar, go to the project root directory and run in the terminal:

View File

@ -4,8 +4,8 @@ plugins {
id 'com.github.gmazzo.buildconfig' version '3.1.0' id 'com.github.gmazzo.buildconfig' version '3.1.0'
} }
group 'ua.nanit' group 'ru.nanit'
version '1.8.1' version '1.7'
compileJava { compileJava {
options.encoding = "UTF-8" options.encoding = "UTF-8"
@ -24,7 +24,6 @@ dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.3' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.3'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.3' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.3'
implementation 'ch.qos.logback:logback-classic:1.5.6'
implementation 'org.spongepowered:configurate-yaml:4.1.2' implementation 'org.spongepowered:configurate-yaml:4.1.2'
implementation 'io.netty:netty-all:4.1.101.Final' implementation 'io.netty:netty-all:4.1.101.Final'
implementation 'net.kyori:adventure-nbt:4.14.0' implementation 'net.kyori:adventure-nbt:4.14.0'
@ -45,9 +44,7 @@ shadowJar {
attributes('Main-Class': 'ua.nanit.limbo.NanoLimbo') attributes('Main-Class': 'ua.nanit.limbo.NanoLimbo')
} }
minimize { minimize()
exclude(dependency('ch.qos.logback:logback-classic:.*:.*'))
}
} }
test { test {

View File

@ -18,7 +18,7 @@
package ua.nanit.limbo; package ua.nanit.limbo;
import ua.nanit.limbo.server.LimboServer; import ua.nanit.limbo.server.LimboServer;
import ua.nanit.limbo.server.Log; import ua.nanit.limbo.server.Logger;
public final class NanoLimbo { public final class NanoLimbo {
@ -26,7 +26,7 @@ public final class NanoLimbo {
try { try {
new LimboServer().start(); new LimboServer().start();
} catch (Exception e) { } catch (Exception e) {
Log.error("Cannot start server: ", e); Logger.error("Cannot start server: ", e);
} }
} }

View File

@ -73,8 +73,8 @@ public final class LimboConfig {
private boolean useTrafficLimits; private boolean useTrafficLimits;
private int maxPacketSize; private int maxPacketSize;
private double interval; private int maxPacketsPerSec;
private double maxPacketRate; private int maxBytesPerSec;
public LimboConfig(Path root) { public LimboConfig(Path root) {
this.root = root; this.root = root;
@ -134,9 +134,9 @@ public final class LimboConfig {
workerGroupSize = conf.node("netty", "threads", "workerGroup").getInt(4); workerGroupSize = conf.node("netty", "threads", "workerGroup").getInt(4);
useTrafficLimits = conf.node("traffic", "enable").getBoolean(false); useTrafficLimits = conf.node("traffic", "enable").getBoolean(false);
maxPacketSize = conf.node("traffic", "maxPacketSize").getInt(-1); maxPacketSize = conf.node("traffic", "packetSize").getInt(-1);
interval = conf.node("traffic", "interval").getDouble(-1.0); maxPacketsPerSec = conf.node("traffic", "packets").getInt(-1);
maxPacketRate = conf.node("traffic", "maxPacketRate").getDouble(-1.0); maxBytesPerSec = conf.node("traffic", "bytes").getInt(-1);
} }
private BufferedReader getReader() throws IOException { private BufferedReader getReader() throws IOException {
@ -269,11 +269,11 @@ public final class LimboConfig {
return maxPacketSize; return maxPacketSize;
} }
public double getInterval() { public int getMaxPacketsPerSec() {
return interval; return maxPacketsPerSec;
} }
public double getMaxPacketRate() { public int getMaxBytesPerSec() {
return maxPacketRate; return maxBytesPerSec;
} }
} }

View File

@ -50,8 +50,8 @@ public class ClientChannelInitializer extends ChannelInitializer<Channel> {
if (server.getConfig().isUseTrafficLimits()) { if (server.getConfig().isUseTrafficLimits()) {
pipeline.addLast("traffic_limit", new ChannelTrafficHandler( pipeline.addLast("traffic_limit", new ChannelTrafficHandler(
server.getConfig().getMaxPacketSize(), server.getConfig().getMaxPacketSize(),
server.getConfig().getInterval(), server.getConfig().getMaxPacketsPerSec(),
server.getConfig().getMaxPacketRate() server.getConfig().getMaxBytesPerSec()
)); ));
} }

View File

@ -36,7 +36,7 @@ import ua.nanit.limbo.protocol.packets.play.PacketKeepAlive;
import ua.nanit.limbo.protocol.registry.State; import ua.nanit.limbo.protocol.registry.State;
import ua.nanit.limbo.protocol.registry.Version; import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.LimboServer; import ua.nanit.limbo.server.LimboServer;
import ua.nanit.limbo.server.Log; import ua.nanit.limbo.server.Logger;
import ua.nanit.limbo.util.UuidUtil; import ua.nanit.limbo.util.UuidUtil;
import javax.crypto.Mac; import javax.crypto.Mac;
@ -104,7 +104,7 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
@Override @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
if (channel.isActive()) { if (channel.isActive()) {
Log.error("Unhandled exception: ", cause); Logger.error("Unhandled exception: ", cause);
} }
} }
@ -115,7 +115,7 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
public void handlePacket(Object packet) { public void handlePacket(Object packet) {
if (packet instanceof Packet) { if (packet instanceof Packet) {
((Packet) packet).handle(this, server); ((Packet)packet).handle(this, server);
} }
} }
@ -199,14 +199,7 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
if (PacketSnapshots.PACKET_PLUGIN_MESSAGE != null) if (PacketSnapshots.PACKET_PLUGIN_MESSAGE != null)
writePacket(PacketSnapshots.PACKET_PLUGIN_MESSAGE); writePacket(PacketSnapshots.PACKET_PLUGIN_MESSAGE);
writePacket(PacketSnapshots.PACKET_REGISTRY_DATA);
if (clientVersion.moreOrEqual(Version.V1_20_5)) {
for (PacketSnapshot packet : PacketSnapshots.PACKETS_REGISTRY_DATA) {
writePacket(packet);
}
} else {
writePacket(PacketSnapshots.PACKET_REGISTRY_DATA);
}
sendPacket(PacketSnapshots.PACKET_FINISH_CONFIGURATION); sendPacket(PacketSnapshots.PACKET_FINISH_CONFIGURATION);
} }
@ -264,6 +257,10 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
encoder.updateState(state); encoder.updateState(state);
} }
public void updateDecoderState(State state) {
decoder.updateState(state);
}
public void updateEncoderState(State state) { public void updateEncoderState(State state) {
encoder.updateState(state); encoder.updateState(state);
} }
@ -275,7 +272,7 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
} }
public void setAddress(String host) { public void setAddress(String host) {
this.address = new InetSocketAddress(host, ((InetSocketAddress) this.address).getPort()); this.address = new InetSocketAddress(host, ((InetSocketAddress)this.address).getPort());
} }
boolean checkBungeeGuardHandshake(String handshake) { boolean checkBungeeGuardHandshake(String handshake) {
@ -312,7 +309,7 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
setAddress(socketAddressHostname); setAddress(socketAddressHostname);
gameProfile.setUuid(uuid); gameProfile.setUuid(uuid);
Log.debug("Successfully verified BungeeGuard token"); Logger.debug("Successfully verified BungeeGuard token");
return true; return true;
} }
@ -336,7 +333,7 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
byte[] mySignature = mac.doFinal(data); byte[] mySignature = mac.doFinal(data);
if (!MessageDigest.isEqual(signature, mySignature)) if (!MessageDigest.isEqual(signature, mySignature))
return false; return false;
} catch (InvalidKeyException | java.security.NoSuchAlgorithmException e) { } catch (InvalidKeyException |java.security.NoSuchAlgorithmException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }
int version = buf.readVarInt(); int version = buf.readVarInt();

View File

@ -29,7 +29,7 @@ import ua.nanit.limbo.protocol.packets.status.PacketStatusPing;
import ua.nanit.limbo.protocol.packets.status.PacketStatusRequest; import ua.nanit.limbo.protocol.packets.status.PacketStatusRequest;
import ua.nanit.limbo.protocol.packets.status.PacketStatusResponse; import ua.nanit.limbo.protocol.packets.status.PacketStatusResponse;
import ua.nanit.limbo.server.LimboServer; import ua.nanit.limbo.server.LimboServer;
import ua.nanit.limbo.server.Log; import ua.nanit.limbo.server.Logger;
import ua.nanit.limbo.util.UuidUtil; import ua.nanit.limbo.util.UuidUtil;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
@ -46,7 +46,7 @@ public class PacketHandler {
conn.updateVersion(packet.getVersion()); conn.updateVersion(packet.getVersion());
conn.updateState(packet.getNextState()); conn.updateState(packet.getNextState());
Log.debug("Pinged from %s [%s]", conn.getAddress(), Logger.debug("Pinged from %s [%s]", conn.getAddress(),
conn.getClientVersion().toString()); conn.getClientVersion().toString());
if (server.getConfig().getInfoForwarding().isLegacy()) { if (server.getConfig().getInfoForwarding().isLegacy()) {

View File

@ -17,9 +17,6 @@
package ua.nanit.limbo.connection; package ua.nanit.limbo.connection;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.ListBinaryTag;
import ua.nanit.limbo.LimboConstants; import ua.nanit.limbo.LimboConstants;
import ua.nanit.limbo.protocol.PacketSnapshot; import ua.nanit.limbo.protocol.PacketSnapshot;
import ua.nanit.limbo.protocol.packets.configuration.PacketFinishConfiguration; import ua.nanit.limbo.protocol.packets.configuration.PacketFinishConfiguration;
@ -30,7 +27,6 @@ import ua.nanit.limbo.server.LimboServer;
import ua.nanit.limbo.server.data.Title; import ua.nanit.limbo.server.data.Title;
import ua.nanit.limbo.util.NbtMessageUtil; import ua.nanit.limbo.util.NbtMessageUtil;
import ua.nanit.limbo.util.UuidUtil; import ua.nanit.limbo.util.UuidUtil;
import ua.nanit.limbo.world.Dimension;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -52,7 +48,7 @@ public final class PacketSnapshots {
public static PacketSnapshot PACKET_HEADER_AND_FOOTER; public static PacketSnapshot PACKET_HEADER_AND_FOOTER;
public static PacketSnapshot PACKET_PLAYER_POS_AND_LOOK_LEGACY; public static PacketSnapshot PACKET_PLAYER_POS_AND_LOOK_LEGACY;
// For 1.19 we need to spawn player outside the world to avoid stuck in terrain loading // For 1.19 we need to spawn player outside world to avoid stuck in terrain loading
public static PacketSnapshot PACKET_PLAYER_POS_AND_LOOK; public static PacketSnapshot PACKET_PLAYER_POS_AND_LOOK;
public static PacketSnapshot PACKET_TITLE_TITLE; public static PacketSnapshot PACKET_TITLE_TITLE;
@ -64,7 +60,6 @@ public final class PacketSnapshots {
public static PacketSnapshot PACKET_TITLE_LEGACY_TIMES; public static PacketSnapshot PACKET_TITLE_LEGACY_TIMES;
public static PacketSnapshot PACKET_REGISTRY_DATA; public static PacketSnapshot PACKET_REGISTRY_DATA;
public static List<PacketSnapshot> PACKETS_REGISTRY_DATA;
public static PacketSnapshot PACKET_FINISH_CONFIGURATION; public static PacketSnapshot PACKET_FINISH_CONFIGURATION;
public static List<PacketSnapshot> PACKETS_EMPTY_CHUNKS; public static List<PacketSnapshot> PACKETS_EMPTY_CHUNKS;
@ -198,38 +193,6 @@ public final class PacketSnapshots {
packetRegistryData.setDimensionRegistry(server.getDimensionRegistry()); packetRegistryData.setDimensionRegistry(server.getDimensionRegistry());
PACKET_REGISTRY_DATA = PacketSnapshot.of(packetRegistryData); PACKET_REGISTRY_DATA = PacketSnapshot.of(packetRegistryData);
Dimension dimension1_21 = server.getDimensionRegistry().getDimension_1_21();
List<PacketSnapshot> packetRegistries = new ArrayList<>();
CompoundBinaryTag dimensionTag = dimension1_21.getData();
for (String registryType : dimensionTag.keySet()) {
CompoundBinaryTag compoundRegistryType = dimensionTag.getCompound(registryType);
PacketRegistryData registryData = new PacketRegistryData();
registryData.setDimensionRegistry(server.getDimensionRegistry());
ListBinaryTag values = compoundRegistryType.getList("value");
registryData.setMetadataWriter((message, version) -> {
message.writeString(registryType);
message.writeVarInt(values.size());
for (BinaryTag entry : values) {
CompoundBinaryTag entryTag = (CompoundBinaryTag) entry;
String name = entryTag.getString("name");
CompoundBinaryTag element = entryTag.getCompound("element");
message.writeString(name);
message.writeBoolean(true);
message.writeNamelessCompoundTag(element);
}
});
packetRegistries.add(PacketSnapshot.of(registryData));
}
PACKETS_REGISTRY_DATA = packetRegistries;
PACKET_FINISH_CONFIGURATION = PacketSnapshot.of(new PacketFinishConfiguration()); PACKET_FINISH_CONFIGURATION = PacketSnapshot.of(new PacketFinishConfiguration());
PacketGameEvent packetGameEvent = new PacketGameEvent(); PacketGameEvent packetGameEvent = new PacketGameEvent();

View File

@ -4,20 +4,23 @@ import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInboundHandlerAdapter;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import ua.nanit.limbo.server.Log; import ua.nanit.limbo.server.Logger;
import java.util.Arrays;
public class ChannelTrafficHandler extends ChannelInboundHandlerAdapter { public class ChannelTrafficHandler extends ChannelInboundHandlerAdapter {
private final int maxPacketSize; private final int packetSize;
private final double maxPacketRate; private final int packetsPerSec;
private final PacketBucket packetBucket; private final int bytesPerSec;
public ChannelTrafficHandler(int maxPacketSize, double interval, double maxPacketRate) { private int packetsCounter;
this.maxPacketSize = maxPacketSize; private int bytesCounter;
this.maxPacketRate = maxPacketRate;
this.packetBucket = (interval > 0.0 && maxPacketRate > 0.0) ? new PacketBucket(interval * 1000.0, 150) : null; private long lastPacket;
public ChannelTrafficHandler(int packetSize, int packetsPerSec, int bytesPerSec) {
this.packetSize = packetSize;
this.packetsPerSec = packetsPerSec;
this.bytesPerSec = bytesPerSec;
} }
@Override @Override
@ -26,86 +29,47 @@ public class ChannelTrafficHandler extends ChannelInboundHandlerAdapter {
ByteBuf in = (ByteBuf) msg; ByteBuf in = (ByteBuf) msg;
int bytes = in.readableBytes(); int bytes = in.readableBytes();
if (maxPacketSize > 0 && bytes > maxPacketSize) { if (packetSize > 0 && bytes > packetSize) {
closeConnection(ctx, "Closed %s due to large packet size (%d bytes)", ctx.channel().remoteAddress(), bytes); closeConnection(ctx, "Closed %s due too large packet size (%d bytes)", ctx.channel().remoteAddress(), bytes);
return; return;
} }
if (packetBucket != null) { if (!measureTraffic(ctx, bytes)) return;
packetBucket.incrementPackets(1);
if (packetBucket.getCurrentPacketRate() > maxPacketRate) {
closeConnection(ctx, "Closed %s due to many packets sent (%d in the last %.1f seconds)", ctx.channel().remoteAddress(), packetBucket.sum, (packetBucket.intervalTime / 1000.0));
return;
}
}
} }
super.channelRead(ctx, msg); super.channelRead(ctx, msg);
} }
private boolean measureTraffic(ChannelHandlerContext ctx, int bytes) {
if (packetsPerSec < 0 && bytesPerSec < 0) return true;
long time = System.currentTimeMillis();
if (time - lastPacket >= 1000) {
bytesCounter = 0;
packetsCounter = 0;
}
packetsCounter++;
bytesCounter += bytes;
if (packetsPerSec > 0 && packetsCounter > packetsPerSec) {
closeConnection(ctx, "Closed %s due too frequent packet sending (%d per sec)", ctx.channel().remoteAddress(), packetsCounter);
return false;
}
if (bytesPerSec > 0 && bytesCounter > bytesPerSec) {
closeConnection(ctx, "Closed %s due too many bytes sent per second (%d per sec)", ctx.channel().remoteAddress(), bytesCounter);
return false;
}
lastPacket = time;
return true;
}
private void closeConnection(ChannelHandlerContext ctx, String reason, Object... args) { private void closeConnection(ChannelHandlerContext ctx, String reason, Object... args) {
ctx.close(); ctx.close();
Log.info(reason, args); Logger.info(reason, args);
}
private static class PacketBucket {
private static final double NANOSECONDS_TO_MILLISECONDS = 1.0e-6;
private static final int MILLISECONDS_TO_SECONDS = 1000;
private final double intervalTime;
private final double intervalResolution;
private final int[] data;
private int newestData;
private double lastBucketTime;
private int sum;
public PacketBucket(final double intervalTime, final int totalBuckets) {
this.intervalTime = intervalTime;
this.intervalResolution = intervalTime / totalBuckets;
this.data = new int[totalBuckets];
}
public void incrementPackets(final int packets) {
double timeMs = System.nanoTime() * NANOSECONDS_TO_MILLISECONDS;
double timeDelta = timeMs - this.lastBucketTime;
if (timeDelta < 0.0) {
timeDelta = 0.0;
}
if (timeDelta < this.intervalResolution) {
this.data[this.newestData] += packets;
this.sum += packets;
return;
}
int bucketsToMove = (int)(timeDelta / this.intervalResolution);
double nextBucketTime = this.lastBucketTime + bucketsToMove * this.intervalResolution;
if (bucketsToMove >= this.data.length) {
Arrays.fill(this.data, 0);
this.data[0] = packets;
this.sum = packets;
this.newestData = 0;
this.lastBucketTime = timeMs;
return;
}
for (int i = 1; i < bucketsToMove; ++i) {
int index = (this.newestData + i) % this.data.length;
this.sum -= this.data[index];
this.data[index] = 0;
}
int newestDataIndex = (this.newestData + bucketsToMove) % this.data.length;
this.sum += packets - this.data[newestDataIndex];
this.data[newestDataIndex] = packets;
this.newestData = newestDataIndex;
this.lastBucketTime = nextBucketTime;
}
public double getCurrentPacketRate() {
return this.sum / (this.intervalTime / MILLISECONDS_TO_SECONDS);
}
} }
} }

View File

@ -24,7 +24,7 @@ import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.Packet; import ua.nanit.limbo.protocol.Packet;
import ua.nanit.limbo.protocol.registry.State; import ua.nanit.limbo.protocol.registry.State;
import ua.nanit.limbo.protocol.registry.Version; import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.Log; import ua.nanit.limbo.server.Logger;
import java.util.List; import java.util.List;
@ -47,20 +47,22 @@ public class PacketDecoder extends MessageToMessageDecoder<ByteBuf> {
Packet packet = mappings.getPacket(packetId); Packet packet = mappings.getPacket(packetId);
if (packet != null) { if (packet != null) {
Log.debug("Received packet %s[0x%s] (%d bytes)", packet.toString(), Integer.toHexString(packetId), msg.readableBytes()); Logger.debug("Received packet %s[0x%s] (%d bytes)", packet.toString(), Integer.toHexString(packetId), msg.readableBytes());
try { try {
packet.decode(msg, version); packet.decode(msg, version);
} catch (Exception e) { } catch (Exception e) {
if (Log.isDebug()) { if (Logger.isDebug()) {
Log.warning("Cannot decode packet 0x%s", e, Integer.toHexString(packetId)); Logger.warning("Cannot decode packet 0x%s", Integer.toHexString(packetId));
} else { e.printStackTrace();
Log.warning("Cannot decode packet 0x%s: %s", Integer.toHexString(packetId), e.getMessage()); }
else {
Logger.warning("Cannot decode packet 0x%s: %s", Integer.toHexString(packetId), e.getMessage());
} }
} }
ctx.fireChannelRead(packet); ctx.fireChannelRead(packet);
} else { } else {
Log.debug("Undefined incoming packet: 0x" + Integer.toHexString(packetId)); Logger.debug("Undefined incoming packet: 0x" + Integer.toHexString(packetId));
} }
} }

View File

@ -25,7 +25,7 @@ import ua.nanit.limbo.protocol.Packet;
import ua.nanit.limbo.protocol.PacketSnapshot; import ua.nanit.limbo.protocol.PacketSnapshot;
import ua.nanit.limbo.protocol.registry.State; import ua.nanit.limbo.protocol.registry.State;
import ua.nanit.limbo.protocol.registry.Version; import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.Log; import ua.nanit.limbo.server.Logger;
public class PacketEncoder extends MessageToByteEncoder<Packet> { public class PacketEncoder extends MessageToByteEncoder<Packet> {
@ -51,7 +51,7 @@ public class PacketEncoder extends MessageToByteEncoder<Packet> {
} }
if (packetId == -1) { if (packetId == -1) {
Log.warning("Undefined packet class: %s[0x%s] (%d bytes)", packet.getClass().getName(), Integer.toHexString(packetId), msg.readableBytes()); Logger.warning("Undefined packet class: %s[0x%s] (%d bytes)", packet.getClass().getName(), Integer.toHexString(packetId), msg.readableBytes());
return; return;
} }
@ -60,11 +60,11 @@ public class PacketEncoder extends MessageToByteEncoder<Packet> {
try { try {
packet.encode(msg, version); packet.encode(msg, version);
if (Log.isDebug()) { if (Logger.getLevel() >= Logger.Level.DEBUG.getIndex()) {
Log.debug("Sending %s[0x%s] packet (%d bytes)", packet.toString(), Integer.toHexString(packetId), msg.readableBytes()); Logger.debug("Sending %s[0x%s] packet (%d bytes)", packet.toString(), Integer.toHexString(packetId), msg.readableBytes());
} }
} catch (Exception e) { } catch (Exception e) {
Log.error("Cannot encode packet 0x%s: %s", Integer.toHexString(packetId), e.getMessage()); Logger.error("Cannot encode packet 0x%s: %s", Integer.toHexString(packetId), e.getMessage());
} }
} }

View File

@ -20,7 +20,7 @@ package ua.nanit.limbo.connection.pipeline;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.ByteToMessageDecoder;
import ua.nanit.limbo.server.Log; import ua.nanit.limbo.server.Logger;
import java.util.List; import java.util.List;
@ -42,7 +42,7 @@ public class VarIntFrameDecoder extends ByteToMessageDecoder {
int readVarInt = reader.getReadVarInt(); int readVarInt = reader.getReadVarInt();
int bytesRead = reader.getBytesRead(); int bytesRead = reader.getBytesRead();
if (readVarInt < 0) { if (readVarInt < 0) {
Log.error("[VarIntFrameDecoder] Bad data length"); Logger.error("[VarIntFrameDecoder] Bad data length");
} else if (readVarInt == 0) { } else if (readVarInt == 0) {
in.readerIndex(varIntEnd + 1); in.readerIndex(varIntEnd + 1);
} else { } else {
@ -54,7 +54,7 @@ public class VarIntFrameDecoder extends ByteToMessageDecoder {
} }
} }
} else if (reader.getResult() == VarIntByteDecoder.DecodeResult.TOO_BIG) { } else if (reader.getResult() == VarIntByteDecoder.DecodeResult.TOO_BIG) {
Log.error("[VarIntFrameDecoder] Too big data"); Logger.error("[VarIntFrameDecoder] Too big data");
} }
} }
} }

View File

@ -73,37 +73,14 @@ public class ByteMessage extends ByteBuf {
} }
public void writeVarInt(int value) { public void writeVarInt(int value) {
// Peel the one and two byte count cases explicitly as they are the most common VarInt sizes while (true) {
// that the proxy will write, to improve inlining. if ((value & 0xFFFFFF80) == 0) {
if ((value & (0xFFFFFFFF << 7)) == 0) { buf.writeByte(value);
buf.writeByte(value); return;
} else if ((value & (0xFFFFFFFF << 14)) == 0) { }
int w = (value & 0x7F | 0x80) << 8 | (value >>> 7);
buf.writeShort(w);
} else {
writeVarIntFull(value);
}
}
private void writeVarIntFull(final int value) { buf.writeByte(value & 0x7F | 0x80);
// See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/ value >>>= 7;
if ((value & (0xFFFFFFFF << 7)) == 0) {
buf.writeByte(value);
} else if ((value & (0xFFFFFFFF << 14)) == 0) {
int w = (value & 0x7F | 0x80) << 8 | (value >>> 7);
buf.writeShort(w);
} else if ((value & (0xFFFFFFFF << 21)) == 0) {
int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14);
buf.writeMedium(w);
} else if ((value & (0xFFFFFFFF << 28)) == 0) {
int w = (value & 0x7F | 0x80) << 24 | (((value >>> 7) & 0x7F | 0x80) << 16)
| ((value >>> 14) & 0x7F | 0x80) << 8 | (value >>> 21);
buf.writeInt(w);
} else {
int w = (value & 0x7F | 0x80) << 24 | ((value >>> 7) & 0x7F | 0x80) << 16
| ((value >>> 14) & 0x7F | 0x80) << 8 | ((value >>> 21) & 0x7F | 0x80);
buf.writeInt(w);
buf.writeByte(value >>> 28);
} }
} }

View File

@ -1,10 +0,0 @@
package ua.nanit.limbo.protocol;
import ua.nanit.limbo.protocol.registry.Version;
@FunctionalInterface
public interface MetadataWriter {
void writeData(ByteMessage message, Version version);
}

View File

@ -23,8 +23,8 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
/** /**
* PacketSnapshot encodes a packet to byte array for each MC version. * PacketSnapshot encodes packet to byte array for each MC version.
* Some versions have the same snapshot, so there are mappings to avoid data copying * Some versions have same snapshot, so there are mappings to avoid data copying
*/ */
public class PacketSnapshot implements PacketOut { public class PacketSnapshot implements PacketOut {

View File

@ -1,7 +1,6 @@
package ua.nanit.limbo.protocol.packets.configuration; package ua.nanit.limbo.protocol.packets.configuration;
import ua.nanit.limbo.protocol.ByteMessage; import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.MetadataWriter;
import ua.nanit.limbo.protocol.PacketOut; import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version; import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.world.DimensionRegistry; import ua.nanit.limbo.world.DimensionRegistry;
@ -9,24 +8,13 @@ import ua.nanit.limbo.world.DimensionRegistry;
public class PacketRegistryData implements PacketOut { public class PacketRegistryData implements PacketOut {
private DimensionRegistry dimensionRegistry; private DimensionRegistry dimensionRegistry;
private MetadataWriter metadataWriter;
public void setDimensionRegistry(DimensionRegistry dimensionRegistry) { public void setDimensionRegistry(DimensionRegistry dimensionRegistry) {
this.dimensionRegistry = dimensionRegistry; this.dimensionRegistry = dimensionRegistry;
} }
public void setMetadataWriter(MetadataWriter metadataWriter) {
this.metadataWriter = metadataWriter;
}
@Override @Override
public void encode(ByteMessage msg, Version version) { public void encode(ByteMessage msg, Version version) {
if (metadataWriter != null) {
if (version.moreOrEqual(Version.V1_20_5)) {
metadataWriter.writeData(msg, version);
return;
}
}
msg.writeNamelessCompoundTag(dimensionRegistry.getCodec_1_20()); msg.writeNamelessCompoundTag(dimensionRegistry.getCodec_1_20());
} }
} }

View File

@ -49,9 +49,6 @@ public class PacketLoginSuccess implements PacketOut {
if (version.moreOrEqual(Version.V1_19)) { if (version.moreOrEqual(Version.V1_19)) {
msg.writeVarInt(0); msg.writeVarInt(0);
} }
if (version.moreOrEqual(Version.V1_20_5)) {
msg.writeBoolean(true);
}
} }
@Override @Override

View File

@ -39,7 +39,6 @@ public class PacketJoinGame implements PacketOut {
private boolean isDebug; private boolean isDebug;
private boolean isFlat; private boolean isFlat;
private boolean limitedCrafting; private boolean limitedCrafting;
private boolean secureProfile;
public void setEntityId(int entityId) { public void setEntityId(int entityId) {
this.entityId = entityId; this.entityId = entityId;
@ -101,10 +100,6 @@ public class PacketJoinGame implements PacketOut {
this.limitedCrafting = limitedCrafting; this.limitedCrafting = limitedCrafting;
} }
public void setSecureProfile(boolean secureProfile) {
this.secureProfile = secureProfile;
}
@Override @Override
public void encode(ByteMessage msg, Version version) { public void encode(ByteMessage msg, Version version) {
msg.writeInt(entityId); msg.writeInt(entityId);
@ -260,7 +255,7 @@ public class PacketJoinGame implements PacketOut {
msg.writeVarInt(0); msg.writeVarInt(0);
} }
if (version.fromTo(Version.V1_20_2, Version.V1_20_3)) { if (version.moreOrEqual(Version.V1_20_2)) {
msg.writeBoolean(isHardcore); msg.writeBoolean(isHardcore);
msg.writeStringsArray(worldNames); msg.writeStringsArray(worldNames);
msg.writeVarInt(maxPlayers); msg.writeVarInt(maxPlayers);
@ -279,27 +274,6 @@ public class PacketJoinGame implements PacketOut {
msg.writeBoolean(false); msg.writeBoolean(false);
msg.writeVarInt(0); msg.writeVarInt(0);
} }
if (version.moreOrEqual(Version.V1_20_5)) {
msg.writeBoolean(isHardcore);
msg.writeStringsArray(worldNames);
msg.writeVarInt(maxPlayers);
msg.writeVarInt(viewDistance);
msg.writeVarInt(viewDistance); // Simulation Distance
msg.writeBoolean(reducedDebugInfo);
msg.writeBoolean(enableRespawnScreen);
msg.writeBoolean(limitedCrafting);
msg.writeVarInt(dimensionRegistry.getDimension_1_20_5().getId());
msg.writeString(worldName);
msg.writeLong(hashedSeed);
msg.writeByte(gameMode);
msg.writeByte(previousGameMode);
msg.writeBoolean(isDebug);
msg.writeBoolean(isFlat);
msg.writeBoolean(false);
msg.writeVarInt(0);
msg.writeBoolean(secureProfile);
}
} }
} }

View File

@ -66,7 +66,7 @@ public enum State {
); );
serverBound.register( serverBound.register(
PacketLoginAcknowledged::new, PacketLoginAcknowledged::new,
map(0x03, V1_20_2, Version.getMax()) map(0x03, V1_20_2, V1_20_3)
); );
clientBound.register(PacketDisconnect::new, clientBound.register(PacketDisconnect::new,
map(0x00, Version.getMin(), Version.getMax()) map(0x00, Version.getMin(), Version.getMax())
@ -83,44 +83,36 @@ public enum State {
{ {
clientBound.register( clientBound.register(
PacketPluginMessage::new, PacketPluginMessage::new,
map(0x00, V1_20_2, V1_20_3), map(0x00, V1_20_2, V1_20_3)
map(0x01, V1_20_5, V1_21)
); );
clientBound.register( clientBound.register(
PacketDisconnect::new, PacketDisconnect::new,
map(0x01, V1_20_2, V1_20_3), map(0x01, V1_20_2, V1_20_3)
map(0x02, V1_20_5, V1_21)
); );
clientBound.register( clientBound.register(
PacketFinishConfiguration::new, PacketFinishConfiguration::new,
map(0x02, V1_20_2, V1_20_3), map(0x02, V1_20_2, V1_20_3)
map(0x03, V1_20_5, V1_21)
); );
clientBound.register( clientBound.register(
PacketKeepAlive::new, PacketKeepAlive::new,
map(0x03, V1_20_2, V1_20_3), map(0x03, V1_20_2, V1_20_3)
map(0x04, V1_20_5, V1_21)
); );
clientBound.register( clientBound.register(
PacketRegistryData::new, PacketRegistryData::new,
map(0x05, V1_20_2, V1_20_3), map(0x05, V1_20_2, V1_20_3)
map(0x07, V1_20_5, V1_21)
); );
serverBound.register( serverBound.register(
PacketPluginMessage::new, PacketPluginMessage::new,
map(0x01, V1_20_2, V1_20_3), map(0x01, V1_20_2, V1_20_3)
map(0x02, V1_20_2, V1_21)
); );
serverBound.register( serverBound.register(
PacketFinishConfiguration::new, PacketFinishConfiguration::new,
map(0x02, V1_20_2, V1_20_3), map(0x02, V1_20_2, V1_20_3)
map(0x03, V1_20_5, V1_21)
); );
serverBound.register( serverBound.register(
PacketKeepAlive::new, PacketKeepAlive::new,
map(0x03, V1_20_2, V1_20_3), map(0x03, V1_20_2, V1_20_3)
map(0x04, V1_20_5, V1_21)
); );
} }
}, },
@ -140,8 +132,7 @@ public enum State {
map(0x11, V1_19_3, V1_19_3), map(0x11, V1_19_3, V1_19_3),
map(0x12, V1_19_4, V1_20), map(0x12, V1_19_4, V1_20),
map(0x14, V1_20_2, V1_20_2), map(0x14, V1_20_2, V1_20_2),
map(0x15, V1_20_3, V1_20_3), map(0x15, V1_20_3, V1_20_3)
map(0x18, V1_20_5, V1_21)
); );
clientBound.register(PacketDeclareCommands::new, clientBound.register(PacketDeclareCommands::new,
@ -153,7 +144,7 @@ public enum State {
map(0x0F, V1_19, V1_19_1), map(0x0F, V1_19, V1_19_1),
map(0x0E, V1_19_3, V1_19_3), map(0x0E, V1_19_3, V1_19_3),
map(0x10, V1_19_4, V1_20), map(0x10, V1_19_4, V1_20),
map(0x11, V1_20_2, V1_21) map(0x11, V1_20_2, V1_20_3)
); );
clientBound.register(PacketJoinGame::new, clientBound.register(PacketJoinGame::new,
map(0x01, V1_7_2, V1_8), map(0x01, V1_7_2, V1_8),
@ -167,8 +158,7 @@ public enum State {
map(0x25, V1_19_1, V1_19_1), map(0x25, V1_19_1, V1_19_1),
map(0x24, V1_19_3, V1_19_3), map(0x24, V1_19_3, V1_19_3),
map(0x28, V1_19_4, V1_20), map(0x28, V1_19_4, V1_20),
map(0x29, V1_20_2, V1_20_3), map(0x29, V1_20_2, V1_20_3)
map(0x2B, V1_20_5, V1_21)
); );
clientBound.register(PacketPluginMessage::new, clientBound.register(PacketPluginMessage::new,
map(0x19, V1_13, V1_13_2), map(0x19, V1_13, V1_13_2),
@ -181,8 +171,7 @@ public enum State {
map(0x16, V1_19_1, V1_19_1), map(0x16, V1_19_1, V1_19_1),
map(0x15, V1_19_3, V1_19_3), map(0x15, V1_19_3, V1_19_3),
map(0x17, V1_19_4, V1_20), map(0x17, V1_19_4, V1_20),
map(0x18, V1_20_2, V1_20_3), map(0x18, V1_20_2, V1_20_3)
map(0x19, V1_20_5, V1_21)
); );
clientBound.register(PacketPlayerAbilities::new, clientBound.register(PacketPlayerAbilities::new,
map(0x39, V1_7_2, V1_8), map(0x39, V1_7_2, V1_8),
@ -198,8 +187,7 @@ public enum State {
map(0x31, V1_19_1, V1_19_1), map(0x31, V1_19_1, V1_19_1),
map(0x30, V1_19_3, V1_19_3), map(0x30, V1_19_3, V1_19_3),
map(0x34, V1_19_4, V1_20), map(0x34, V1_19_4, V1_20),
map(0x36, V1_20_2, V1_20_3), map(0x36, V1_20_2, V1_20_3)
map(0x38, V1_20_5, V1_21)
); );
clientBound.register(PacketPlayerPositionAndLook::new, clientBound.register(PacketPlayerPositionAndLook::new,
map(0x08, V1_7_2, V1_8), map(0x08, V1_7_2, V1_8),
@ -215,8 +203,7 @@ public enum State {
map(0x39, V1_19_1, V1_19_1), map(0x39, V1_19_1, V1_19_1),
map(0x38, V1_19_3, V1_19_3), map(0x38, V1_19_3, V1_19_3),
map(0x3C, V1_19_4, V1_20), map(0x3C, V1_19_4, V1_20),
map(0x3E, V1_20_2, V1_20_3), map(0x3E, V1_20_2, V1_20_3)
map(0x40, V1_20_5, V1_21)
); );
clientBound.register(PacketKeepAlive::new, clientBound.register(PacketKeepAlive::new,
map(0x00, V1_7_2, V1_8), map(0x00, V1_7_2, V1_8),
@ -231,8 +218,7 @@ public enum State {
map(0x20, V1_19_1, V1_19_1), map(0x20, V1_19_1, V1_19_1),
map(0x1F, V1_19_3, V1_19_3), map(0x1F, V1_19_3, V1_19_3),
map(0x23, V1_19_4, V1_20), map(0x23, V1_19_4, V1_20),
map(0x24, V1_20_2, V1_20_3), map(0x24, V1_20_2, V1_20_3)
map(0x26, V1_20_5, V1_21)
); );
clientBound.register(PacketChatMessage::new, clientBound.register(PacketChatMessage::new,
map(0x02, V1_7_2, V1_8), map(0x02, V1_7_2, V1_8),
@ -246,8 +232,7 @@ public enum State {
map(0x60, V1_19_3, V1_19_3), map(0x60, V1_19_3, V1_19_3),
map(0x64, V1_19_4, V1_20), map(0x64, V1_19_4, V1_20),
map(0x67, V1_20_2, V1_20_2), map(0x67, V1_20_2, V1_20_2),
map(0x69, V1_20_3, V1_20_3), map(0x69, V1_20_3, V1_20_3)
map(0x6C, V1_20_5, V1_21)
); );
clientBound.register(PacketBossBar::new, clientBound.register(PacketBossBar::new,
map(0x0C, V1_9, V1_14_4), map(0x0C, V1_9, V1_14_4),
@ -256,7 +241,7 @@ public enum State {
map(0x0D, V1_17, V1_18_2), map(0x0D, V1_17, V1_18_2),
map(0x0A, V1_19, V1_19_3), map(0x0A, V1_19, V1_19_3),
map(0x0B, V1_19_4, V1_20), map(0x0B, V1_19_4, V1_20),
map(0x0A, V1_20_2, V1_21) map(0x0A, V1_20_2, V1_20_3)
); );
clientBound.register(PacketPlayerInfo::new, clientBound.register(PacketPlayerInfo::new,
map(0x38, V1_7_2, V1_8), map(0x38, V1_7_2, V1_8),
@ -272,8 +257,7 @@ public enum State {
map(0x37, V1_19_1, V1_19_1), map(0x37, V1_19_1, V1_19_1),
map(0x36, V1_19_3, V1_19_3), map(0x36, V1_19_3, V1_19_3),
map(0x3A, V1_19_4, V1_20), map(0x3A, V1_19_4, V1_20),
map(0x3C, V1_20_2, V1_20_3), map(0x3C, V1_20_2, V1_20_3)
map(0x3E, V1_20_5, V1_21)
); );
clientBound.register(PacketTitleLegacy::new, clientBound.register(PacketTitleLegacy::new,
map(0x45, V1_8, V1_11_1), map(0x45, V1_8, V1_11_1),
@ -291,8 +275,7 @@ public enum State {
map(0x5B, V1_19_3, V1_19_3), map(0x5B, V1_19_3, V1_19_3),
map(0x5F, V1_19_4, V1_20), map(0x5F, V1_19_4, V1_20),
map(0x61, V1_20_2, V1_20_2), map(0x61, V1_20_2, V1_20_2),
map(0x63, V1_20_3, V1_20_3), map(0x63, V1_20_3, V1_20_3)
map(0x65, V1_20_5, V1_21)
); );
clientBound.register(PacketTitleSetSubTitle::new, clientBound.register(PacketTitleSetSubTitle::new,
map(0x57, V1_17, V1_17_1), map(0x57, V1_17, V1_17_1),
@ -301,8 +284,7 @@ public enum State {
map(0x59, V1_19_3, V1_19_3), map(0x59, V1_19_3, V1_19_3),
map(0x5D, V1_19_4, V1_20), map(0x5D, V1_19_4, V1_20),
map(0x5F, V1_20_2, V1_20_2), map(0x5F, V1_20_2, V1_20_2),
map(0x61, V1_20_3, V1_20_3), map(0x61, V1_20_3, V1_20_3)
map(0x63, V1_20_5, V1_21)
); );
clientBound.register(PacketTitleTimes::new, clientBound.register(PacketTitleTimes::new,
map(0x5A, V1_17, V1_17_1), map(0x5A, V1_17, V1_17_1),
@ -311,8 +293,7 @@ public enum State {
map(0x5C, V1_19_3, V1_19_3), map(0x5C, V1_19_3, V1_19_3),
map(0x60, V1_19_4, V1_20), map(0x60, V1_19_4, V1_20),
map(0x62, V1_20_2, V1_20_2), map(0x62, V1_20_2, V1_20_2),
map(0x64, V1_20_3, V1_20_3), map(0x64, V1_20_3, V1_20_3)
map(0x66, V1_20_5, V1_21)
); );
clientBound.register(PacketPlayerListHeader::new, clientBound.register(PacketPlayerListHeader::new,
map(0x47, V1_8, V1_8), map(0x47, V1_8, V1_8),
@ -331,23 +312,19 @@ public enum State {
map(0x61, V1_19_3, V1_19_3), map(0x61, V1_19_3, V1_19_3),
map(0x65, V1_19_4, V1_20), map(0x65, V1_19_4, V1_20),
map(0x68, V1_20_2, V1_20_2), map(0x68, V1_20_2, V1_20_2),
map(0x6A, V1_20_3, V1_20_3), map(0x6A, V1_20_3, V1_20_3)
map(0x6D, V1_20_5, V1_21)
); );
clientBound.register(PacketSpawnPosition::new, clientBound.register(PacketSpawnPosition::new,
map(0x4C, V1_19_3, V1_19_3), map(0x4C, V1_19_3, V1_19_3),
map(0x50, V1_19_4, V1_20), map(0x50, V1_19_4, V1_20),
map(0x52, V1_20_2, V1_20_2), map(0x52, V1_20_2, V1_20_2),
map(0x54, V1_20_3, V1_20_3), map(0x54, V1_20_3, V1_20_3)
map(0x56, V1_20_5, V1_21)
); );
clientBound.register(PacketGameEvent::new, clientBound.register(PacketGameEvent::new,
map(0x20, V1_20_3, V1_20_3), map(0x20, V1_20_3, V1_20_3)
map(0x22, V1_20_5, V1_21)
); );
clientBound.register(PacketEmptyChunk::new, clientBound.register(PacketEmptyChunk::new,
map(0x25, V1_20_3, V1_20_3), map(0x25, V1_20_3, V1_20_3)
map(0x27, V1_20_5, V1_21)
); );
} }
}; };

View File

@ -71,10 +71,7 @@ public enum Version {
V1_20(763), V1_20(763),
// 1.20.1 has same protocol number // 1.20.1 has same protocol number
V1_20_2(764), V1_20_2(764),
V1_20_3(765), V1_20_3(765);
V1_20_5(766),
// 1.20.6 has same protocol number
V1_21(767);
private static final Map<Integer, Version> VERSION_MAP; private static final Map<Integer, Version> VERSION_MAP;
private static final Version MAX; private static final Version MAX;

View File

@ -40,12 +40,12 @@ public final class CommandManager extends Thread {
try { try {
handler.execute(); handler.execute();
} catch (Throwable t) { } catch (Throwable t) {
Log.error("Cannot execute command:", t); Logger.error("Cannot execute command:", t);
} }
continue; continue;
} }
Log.info("Unknown command. Type \"help\" to get commands list"); Logger.info("Unknown command. Type \"help\" to get commands list");
} }
} }

View File

@ -43,12 +43,12 @@ public final class Connections {
public void addConnection(ClientConnection connection) { public void addConnection(ClientConnection connection) {
connections.put(connection.getUuid(), connection); connections.put(connection.getUuid(), connection);
Log.info("Player %s connected (%s) [%s]", connection.getUsername(), Logger.info("Player %s connected (%s) [%s]", connection.getUsername(),
connection.getAddress(), connection.getClientVersion()); connection.getAddress(), connection.getClientVersion());
} }
public void removeConnection(ClientConnection connection) { public void removeConnection(ClientConnection connection) {
connections.remove(connection.getUuid()); connections.remove(connection.getUuid());
Log.info("Player %s disconnected", connection.getUsername()); Logger.info("Player %s disconnected", connection.getUsername());
} }
} }

View File

@ -72,14 +72,13 @@ public final class LimboServer {
} }
public void start() throws Exception { public void start() throws Exception {
config = new LimboConfig(Paths.get("./")); Logger.info("Starting server...");
config.load();
Log.setLevel(config.getDebugLevel());
Log.info("Starting server...");
ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED); ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED);
config = new LimboConfig(Paths.get("./"));
config.load();
packetHandler = new PacketHandler(this); packetHandler = new PacketHandler(this);
dimensionRegistry = new DimensionRegistry(this); dimensionRegistry = new DimensionRegistry(this);
dimensionRegistry.load(config.getDimensionType()); dimensionRegistry.load(config.getDimensionType());
@ -93,7 +92,9 @@ public final class LimboServer {
Runtime.getRuntime().addShutdownHook(new Thread(this::stop, "NanoLimbo shutdown thread")); Runtime.getRuntime().addShutdownHook(new Thread(this::stop, "NanoLimbo shutdown thread"));
Log.info("Server started on %s", config.getAddress()); Logger.info("Server started on %s", config.getAddress());
Logger.setLevel(config.getDebugLevel());
commandManager = new CommandManager(); commandManager = new CommandManager();
commandManager.registerAll(this); commandManager.registerAll(this);
@ -109,12 +110,12 @@ public final class LimboServer {
bossGroup = new EpollEventLoopGroup(config.getBossGroupSize()); bossGroup = new EpollEventLoopGroup(config.getBossGroupSize());
workerGroup = new EpollEventLoopGroup(config.getWorkerGroupSize()); workerGroup = new EpollEventLoopGroup(config.getWorkerGroupSize());
channelClass = EpollServerSocketChannel.class; channelClass = EpollServerSocketChannel.class;
Log.debug("Using Epoll transport type"); Logger.debug("Using Epoll transport type");
} else { } else {
bossGroup = new NioEventLoopGroup(config.getBossGroupSize()); bossGroup = new NioEventLoopGroup(config.getBossGroupSize());
workerGroup = new NioEventLoopGroup(config.getWorkerGroupSize()); workerGroup = new NioEventLoopGroup(config.getWorkerGroupSize());
channelClass = NioServerSocketChannel.class; channelClass = NioServerSocketChannel.class;
Log.debug("Using Java NIO transport type"); Logger.debug("Using Java NIO transport type");
} }
new ServerBootstrap() new ServerBootstrap()
@ -131,7 +132,7 @@ public final class LimboServer {
} }
private void stop() { private void stop() {
Log.info("Stopping server..."); Logger.info("Stopping server...");
if (keepAliveTask != null) { if (keepAliveTask != null) {
keepAliveTask.cancel(true); keepAliveTask.cancel(true);
@ -145,7 +146,7 @@ public final class LimboServer {
workerGroup.shutdownGracefully(); workerGroup.shutdownGracefully();
} }
Log.info("Server stopped, Goodbye!"); Logger.info("Server stopped, Goodbye!");
} }
} }

View File

@ -17,71 +17,65 @@
package ua.nanit.limbo.server; package ua.nanit.limbo.server;
import ch.qos.logback.classic.Logger; import java.time.LocalTime;
import org.slf4j.LoggerFactory; import java.time.format.DateTimeFormatter;
public final class Log { public final class Logger {
private static final Logger LOGGER = (Logger) LoggerFactory.getLogger("Limbo"); private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("hh:mm:ss");
private static int debugLevel = Level.INFO.getIndex(); private static int debugLevel = Level.INFO.getIndex();
private Log() {} private Logger() {}
public static int getLevel() {
return debugLevel;
}
public static void info(Object msg, Object... args) { public static void info(Object msg, Object... args) {
LOGGER.info(String.format(msg.toString(), args)); print(Level.INFO, msg, null, args);
} }
public static void debug(Object msg, Object... args) { public static void debug(Object msg, Object... args) {
LOGGER.debug(String.format(msg.toString(), args)); print(Level.DEBUG, msg, null, args);
} }
public static void warning(Object msg, Object... args) { public static void warning(Object msg, Object... args) {
LOGGER.warn(String.format(msg.toString(), args)); print(Level.WARNING, msg, null, args);
} }
public static void warning(Object msg, Throwable t, Object... args) { public static void warning(Object msg, Throwable t, Object... args) {
LOGGER.warn(String.format(msg.toString(), args), t); print(Level.WARNING, msg, t, args);
} }
public static void error(Object msg, Object... args) { public static void error(Object msg, Object... args) {
LOGGER.error(msg.toString(), args); print(Level.ERROR, msg, null, args);
} }
public static void error(Object msg, Throwable t, Object... args) { public static void error(Object msg, Throwable t, Object... args) {
LOGGER.error(String.format(msg.toString(), args), t); print(Level.ERROR, msg, t, args);
}
public static void print(Level level, Object msg, Throwable t, Object... args) {
if (debugLevel >= level.getIndex()) {
System.out.printf("%s: %s%n", getPrefix(level), String.format(msg.toString(), args));
if (t != null) t.printStackTrace();
}
} }
public static boolean isDebug() { public static boolean isDebug() {
return debugLevel >= Level.DEBUG.getIndex(); return debugLevel >= Level.DEBUG.getIndex();
} }
private static String getPrefix(Level level) {
return String.format("[%s] [%s]", getTime(), level.getDisplay());
}
private static String getTime() {
return LocalTime.now().format(FORMATTER);
}
static void setLevel(int level) { static void setLevel(int level) {
debugLevel = level; debugLevel = level;
Logger logback = getRootLogger();
if (logback != null) {
logback.setLevel(convertLevel(level));
}
}
private static Logger getRootLogger() {
return (Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
}
private static ch.qos.logback.classic.Level convertLevel(int level) {
switch (level) {
case 0:
return ch.qos.logback.classic.Level.ERROR;
case 1:
return ch.qos.logback.classic.Level.WARN;
case 2:
return ch.qos.logback.classic.Level.INFO;
case 3:
return ch.qos.logback.classic.Level.DEBUG;
default:
throw new IllegalStateException("Undefined log level: " + level);
}
} }
public enum Level { public enum Level {

View File

@ -2,7 +2,7 @@ package ua.nanit.limbo.server.commands;
import ua.nanit.limbo.server.Command; import ua.nanit.limbo.server.Command;
import ua.nanit.limbo.server.LimboServer; import ua.nanit.limbo.server.LimboServer;
import ua.nanit.limbo.server.Log; import ua.nanit.limbo.server.Logger;
public class CmdConn implements Command { public class CmdConn implements Command {
@ -14,7 +14,7 @@ public class CmdConn implements Command {
@Override @Override
public void execute() { public void execute() {
Log.info("Connections: %d", server.getConnections().getCount()); Logger.info("Connections: %d", server.getConnections().getCount());
} }
@Override @Override

View File

@ -2,7 +2,7 @@ package ua.nanit.limbo.server.commands;
import ua.nanit.limbo.server.Command; import ua.nanit.limbo.server.Command;
import ua.nanit.limbo.server.LimboServer; import ua.nanit.limbo.server.LimboServer;
import ua.nanit.limbo.server.Log; import ua.nanit.limbo.server.Logger;
import java.util.Map; import java.util.Map;
@ -18,10 +18,10 @@ public class CmdHelp implements Command {
public void execute() { public void execute() {
Map<String, Command> commands = server.getCommandManager().getCommands(); Map<String, Command> commands = server.getCommandManager().getCommands();
Log.info("Available commands:"); Logger.info("Available commands:");
for (Map.Entry<String, Command> entry : commands.entrySet()) { for (Map.Entry<String, Command> entry : commands.entrySet()) {
Log.info("%s - %s", entry.getKey(), entry.getValue().description()); Logger.info("%s - %s", entry.getKey(), entry.getValue().description());
} }
} }

View File

@ -1,7 +1,7 @@
package ua.nanit.limbo.server.commands; package ua.nanit.limbo.server.commands;
import ua.nanit.limbo.server.Command; import ua.nanit.limbo.server.Command;
import ua.nanit.limbo.server.Log; import ua.nanit.limbo.server.Logger;
public class CmdMem implements Command { public class CmdMem implements Command {
@ -14,11 +14,11 @@ public class CmdMem implements Command {
long free = runtime.freeMemory() / mb; long free = runtime.freeMemory() / mb;
long max = runtime.maxMemory() / mb; long max = runtime.maxMemory() / mb;
Log.info("Memory usage:"); Logger.info("Memory usage:");
Log.info("Used: %d MB", used); Logger.info("Used: %d MB", used);
Log.info("Total: %d MB", total); Logger.info("Total: %d MB", total);
Log.info("Free: %d MB", free); Logger.info("Free: %d MB", free);
Log.info("Max: %d MB", max); Logger.info("Max: %d MB", max);
} }
@Override @Override

View File

@ -1,14 +1,14 @@
package ua.nanit.limbo.server.commands; package ua.nanit.limbo.server.commands;
import ua.nanit.limbo.server.Command; import ua.nanit.limbo.server.Command;
import ua.nanit.limbo.server.Log; import ua.nanit.limbo.server.Logger;
import ua.nanit.limbo.BuildConfig; import ua.nanit.limbo.BuildConfig;
public class CmdVersion implements Command { public class CmdVersion implements Command {
@Override @Override
public void execute() { public void execute() {
Log.info("Version: %s", BuildConfig.LIMBO_VERSION); Logger.info("Version: %s", BuildConfig.LIMBO_VERSION);
} }
@Override @Override

View File

@ -21,7 +21,7 @@ import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.ListBinaryTag; import net.kyori.adventure.nbt.ListBinaryTag;
import net.kyori.adventure.nbt.TagStringIO; import net.kyori.adventure.nbt.TagStringIO;
import ua.nanit.limbo.server.LimboServer; import ua.nanit.limbo.server.LimboServer;
import ua.nanit.limbo.server.Log; import ua.nanit.limbo.server.Logger;
import java.io.*; import java.io.*;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -33,8 +33,6 @@ public final class DimensionRegistry {
private Dimension defaultDimension_1_16; private Dimension defaultDimension_1_16;
private Dimension defaultDimension_1_18_2; private Dimension defaultDimension_1_18_2;
private Dimension dimension_1_20_5;
private Dimension dimension_1_21;
private CompoundBinaryTag codec_1_16; private CompoundBinaryTag codec_1_16;
private CompoundBinaryTag codec_1_18_2; private CompoundBinaryTag codec_1_18_2;
@ -42,7 +40,6 @@ public final class DimensionRegistry {
private CompoundBinaryTag codec_1_19_1; private CompoundBinaryTag codec_1_19_1;
private CompoundBinaryTag codec_1_19_4; private CompoundBinaryTag codec_1_19_4;
private CompoundBinaryTag codec_1_20; private CompoundBinaryTag codec_1_20;
private CompoundBinaryTag codec_1_21;
private CompoundBinaryTag oldCodec; private CompoundBinaryTag oldCodec;
public DimensionRegistry(LimboServer server) { public DimensionRegistry(LimboServer server) {
@ -73,10 +70,6 @@ public final class DimensionRegistry {
return codec_1_20; return codec_1_20;
} }
public CompoundBinaryTag getCodec_1_21() {
return codec_1_21;
}
public CompoundBinaryTag getOldCodec() { public CompoundBinaryTag getOldCodec() {
return oldCodec; return oldCodec;
} }
@ -89,30 +82,18 @@ public final class DimensionRegistry {
return defaultDimension_1_18_2; return defaultDimension_1_18_2;
} }
public Dimension getDimension_1_20_5() {
return dimension_1_20_5;
}
public Dimension getDimension_1_21() {
return dimension_1_21;
}
public void load(String def) throws IOException { public void load(String def) throws IOException {
// On 1.16-1.16.1 different codec format
oldCodec = readCodecFile("/dimension/codec_old.snbt");
codec_1_16 = readCodecFile("/dimension/codec_1_16.snbt"); codec_1_16 = readCodecFile("/dimension/codec_1_16.snbt");
codec_1_18_2 = readCodecFile("/dimension/codec_1_18_2.snbt"); codec_1_18_2 = readCodecFile("/dimension/codec_1_18_2.snbt");
codec_1_19 = readCodecFile("/dimension/codec_1_19.snbt"); codec_1_19 = readCodecFile("/dimension/codec_1_19.snbt");
codec_1_19_1 = readCodecFile("/dimension/codec_1_19_1.snbt"); codec_1_19_1 = readCodecFile("/dimension/codec_1_19_1.snbt");
codec_1_19_4 = readCodecFile("/dimension/codec_1_19_4.snbt"); codec_1_19_4 = readCodecFile("/dimension/codec_1_19_4.snbt");
codec_1_20 = readCodecFile("/dimension/codec_1_20.snbt"); codec_1_20 = readCodecFile("/dimension/codec_1_20.snbt");
codec_1_21 = readCodecFile("/dimension/codec_1_21.snbt"); // On 1.16-1.16.1 different codec format
oldCodec = readCodecFile("/dimension/codec_old.snbt");
defaultDimension_1_16 = getDefaultDimension(def, codec_1_16); defaultDimension_1_16 = getDefaultDimension(def, codec_1_16);
defaultDimension_1_18_2 = getDefaultDimension(def, codec_1_18_2); defaultDimension_1_18_2 = getDefaultDimension(def, codec_1_18_2);
dimension_1_20_5 = getModernDimension(def, codec_1_20);
dimension_1_21 = getModernDimension(def, codec_1_21);
} }
private Dimension getDefaultDimension(String def, CompoundBinaryTag tag) { private Dimension getDefaultDimension(String def, CompoundBinaryTag tag) {
@ -130,37 +111,29 @@ public final class DimensionRegistry {
case "the_end": case "the_end":
return new Dimension(1, "minecraft:the_end", theEnd); return new Dimension(1, "minecraft:the_end", theEnd);
default: default:
Log.warning("Undefined dimension type: '%s'. Using THE_END as default", def); Logger.warning("Undefined dimension type: '%s'. Using THE_END as default", def);
return new Dimension(1, "minecraft:the_end", theEnd); return new Dimension(1, "minecraft:the_end", theEnd);
} }
} }
private Dimension getModernDimension(String def, CompoundBinaryTag tag) {
switch (def.toLowerCase()) {
case "overworld":
return new Dimension(0, "minecraft:overworld", tag);
case "the_nether":
return new Dimension(2, "minecraft:nether", tag);
case "the_end":
return new Dimension(3, "minecraft:the_end", tag);
default:
Log.warning("Undefined dimension type: '%s'. Using THE_END as default", def);
return new Dimension(3, "minecraft:the_end", tag);
}
}
private CompoundBinaryTag readCodecFile(String resPath) throws IOException { private CompoundBinaryTag readCodecFile(String resPath) throws IOException {
InputStream in = server.getClass().getResourceAsStream(resPath); InputStream in = server.getClass().getResourceAsStream(resPath);
if (in == null) if(in == null)
throw new FileNotFoundException("Cannot find dimension registry file"); throw new FileNotFoundException("Cannot find dimension registry file");
return TagStringIO.get().asCompound(streamToString(in)); return TagStringIO.get().asCompound(streamToString(in));
} }
private String streamToString(InputStream in) throws IOException { private String streamToString(InputStream in) throws IOException {
try (BufferedReader bufReader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) { InputStreamReader isReader = new InputStreamReader(in, StandardCharsets.UTF_8);
return bufReader.lines().collect(Collectors.joining("\n")); BufferedReader bufReader = new BufferedReader(isReader);
} String content = bufReader.lines()
.collect(Collectors.joining("\n"));
isReader.close();
bufReader.close();
return content;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration>
<configuration>
<import class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"/>
<import class="ch.qos.logback.core.ConsoleAppender"/>
<import class="ch.qos.logback.core.FileAppender"/>
<timestamp key="bySecond" datePattern="yyyy-MM-dd"/>
<appender name="STDOUT" class="ConsoleAppender">
<encoder class="PatternLayoutEncoder">
<pattern>%d{HH:mm:ss.SSS} %-5level %logger{36} -%kvp- %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="FileAppender">
<file>logs/log-${bySecond}.txt</file>
<immediateFlush>true</immediateFlush>
<encoder class="PatternLayoutEncoder">
<pattern>%d{HH:mm:ss.SSS} %-5level %logger{36} -%kvp- %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="FILE"/>
<appender-ref ref="STDOUT"/>
</root>
</configuration>

View File

@ -7,7 +7,7 @@ bind:
ip: 'localhost' ip: 'localhost'
port: 65535 port: 65535
# Max number of players can join to server # Max amount of players can join to server
# Set -1 to make it infinite # Set -1 to make it infinite
maxPlayers: 100 maxPlayers: 100
@ -16,7 +16,7 @@ ping:
description: '{"text": "&9NanoLimbo"}' description: '{"text": "&9NanoLimbo"}'
version: 'NanoLimbo' version: 'NanoLimbo'
# Return static protocol version number in ping result # Return static protocol version number in ping result
# By default, its -1 to return the client version if it supported # By default, its -1 to return the client version, if it supported
# https://wiki.vg/Protocol_version_numbers # https://wiki.vg/Protocol_version_numbers
protocol: -1 protocol: -1
@ -24,7 +24,7 @@ ping:
dimension: THE_END dimension: THE_END
# Whether to display the player in the player list # Whether to display the player in the player list
# For 1.16.5 clients, the player list will be sent even if disabled, to avoid crash # For 1.16.5 clients, player list will be sent even if disabled, to avoid crash
playerList: playerList:
enable: false enable: false
username: 'NanoLimbo' username: 'NanoLimbo'
@ -50,12 +50,12 @@ brandName:
enable: true enable: true
content: 'NanoLimbo' content: 'NanoLimbo'
# Message sends when player joins to the server # Message sends when player join to server
joinMessage: joinMessage:
enable: true enable: true
text: '{"text": "&eWelcome to the Limbo!"}' text: '{"text": "&eWelcome to the Limbo!"}'
# BossBar displays when player joins to the server # BossBar displays when player join to server
# For 1.9+ clients # For 1.9+ clients
bossBar: bossBar:
enable: true enable: true
@ -87,7 +87,7 @@ title:
# - LEGACY # - LEGACY
# - MODERN # - MODERN
# - BUNGEE_GUARD # - BUNGEE_GUARD
# Don't use secret if you do not use MODERN type # Don't use secret if you not use MODERN type
infoForwarding: infoForwarding:
type: NONE type: NONE
secret: '<YOUR_SECRET_HERE>' secret: '<YOUR_SECRET_HERE>'
@ -105,29 +105,27 @@ readTimeout: 30000
# 3 - Display errors, warnings, info, debug # 3 - Display errors, warnings, info, debug
debugLevel: 2 debugLevel: 2
# Warning! Do not touch params of this block if you are not completely sure what is this! # Warning! Do not touch params of this block, if you not completely sure what is this!
netty: netty:
# Use a Linux native transport type, if it possible # Use Linux native transport type, if it possible
useEpoll: true useEpoll: true
# EventLoopGroup threads count # EventLoopGroup threads count
threads: threads:
bossGroup: 1 bossGroup: 1
workerGroup: 4 workerGroup: 4
# [Experimental]
# Options to check incoming traffic and kick potentially malicious connections. # Options to check incoming traffic and kick potentially malicious connections.
# Take into account that player can send many small packets, for example, just moving mouse. # Take into account that player can send many small packets, for example just moving mouse.
traffic: traffic:
# If true, then additional handler will be added to the channel pipeline # If true, then additional handler will be added to channel pipeline
enable: true enable: false
# Max packet size in bytes # Max packet size in bytes
# Unlimited if -1 # Unlimited if -1
maxPacketSize: 8192 packetSize: 1024
# The interval to measure packets over # How many packets per second allowed for single connection
# Lowering this value will limit peak packets from players which would target people with bad connections # Ignored if -1
# Raising this value will allow higher peak packet rates, which will help with people who have poor connections packets: -1
# Ignored if -1.0 # How many bytes per second allowed for single connection
interval: 7.0 # Ignored if -1
# The maximum packets per second for players bytes: 2048
# It is measured over the configured interval
# Ignored if -1.0
maxPacketRate: 500.0