Compare commits

...

39 Commits
v1.5.2 ... main

Author SHA1 Message Date
Nan1t
a5b9e3510d Edit readme 2024-06-14 13:23:20 +03:00
Nan1t
155e6cc6c3 Renamed some keys in dimension codec. Increased version. 2024-06-14 13:21:58 +03:00
Nan1t
b99a533e1a Fixed spelling. Removed unnecessary var 2024-06-14 13:09:07 +03:00
Nan1t
94edb65dac Experimental support for 1.21 2024-06-14 12:51:09 +03:00
Nan1t
48346762bf Edit README.md 2024-05-02 17:36:57 +03:00
Max
402484c838
Update README.md 2024-05-02 17:31:57 +03:00
Nan1t
32b75fb9d8 Edit README.md 2024-05-02 16:55:03 +03:00
Nan1t
3f7c8bb1df Moved initial logging after config loaded 2024-05-02 16:38:56 +03:00
Nan1t
e9b6c888bc Added logback logging 2024-05-02 16:10:40 +03:00
Nan1t
6bbde1f578 Fixed spelling 2024-05-02 14:01:45 +03:00
Nan1t
f36f8dac6c Removed unused code 2024-05-02 13:52:42 +03:00
Max
f137495c4e
Merge pull request #75 from Pantera07/dev/better-limit
Better Packet Limiter
2024-05-02 13:44:55 +03:00
Max
04a646b271
Merge pull request #78 from jonesdevelopment/main
Optimize VarInt writing
2024-05-02 13:18:00 +03:00
Max
cd42e91cd1
Merge pull request #82 from BoomEaro/feature/1.20.5
Support 1.20.6
2024-05-02 13:03:01 +03:00
BoomEaro
e3e12db006
Update README.md 2024-04-30 23:36:48 +03:00
BoomEaro
a750bc50d2
Update README.md 2024-04-29 16:38:24 +03:00
BoomEaro
e54d8d6ed3
Fix wrong mappings for KeepAlive packet 2024-04-25 16:59:02 +03:00
BoomEaro
f5684107c8
Add support for 1.20.5 2024-04-25 14:06:07 +03:00
Pantera (Mad_Daniel)
ebe9c19a05
Increase maxPacketSize 2024-01-22 10:39:52 +09:00
Max
72774fbff9
Delete .github directory 2023-12-30 13:56:44 +02:00
Max
0489f883d1
Update README.md 2023-12-30 13:56:17 +02:00
Nan1t
6919420916 Update version 2023-12-30 13:46:08 +02:00
Max
dfa6cf3862
Update README.md 2023-12-30 13:44:29 +02:00
Max
93ad159833
Merge pull request #79 from BoomEaro/feature/1.20.3
Support 1.20.3
2023-12-30 13:42:50 +02:00
BoomEaro
f257426173
Use writePacket method 2023-12-06 18:37:28 +02:00
BoomEaro
4fd0389356
Bump netty 4.1.101.Final 2023-12-06 18:22:57 +02:00
BoomEaro
23bcfc7b20
Add support for 1.20.3 2023-12-06 18:21:54 +02:00
Michel Elkenwaat
9dc7027a22
feat: optimize VarInt writing (See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/) 2023-11-22 16:29:15 +01:00
Pantera (Mad_Daniel)
145d57679e
Better Packet Limiter
Reference: https://github.com/Spottedleaf/PacketLimiter
2023-10-11 19:00:57 +09:00
Nan1t
ff84c8f564 Update README 2023-10-01 14:07:45 +03:00
Nan1t
b5605f8d12 Remove unnecessary print 2023-10-01 13:59:46 +03:00
Nan1t
5615ec2321 Added experimental options to limit incoming packets 2023-10-01 13:58:08 +03:00
Nan1t
9490dc3ef2 Updated netty 2023-10-01 11:35:20 +03:00
Nan1t
7309ca3b41 Added generated version constant for 'version' command 2023-10-01 11:30:00 +03:00
Max
dceb977a75
Merge pull request #74 from BoomEaro/feature/1.20.2
Support 1.20.2
2023-10-01 11:03:37 +03:00
BoomEaro
9ce3146045 Add support for 1.20.2 2023-09-27 20:06:54 +03:00
Max
fb15336b42
Merge pull request #62 from Pantera07/info/1.20.1
Add version information for 1.20.1
2023-06-13 19:34:16 +03:00
Pantera (Mad_Daniel)
ddb27d421b
Add version information for 1.20.1
Add version information for 1.20.1
2023-06-13 00:08:20 +09:00
Nan1t
b301617f99 Edit README 2023-06-10 11:30:14 +03:00
47 changed files with 3931 additions and 192 deletions

1
.github/FUNDING.yml vendored
View File

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

View File

@ -1,16 +1,17 @@
plugins {
id 'java'
id 'com.github.johnrengelman.shadow' version '7.1.2'
id 'com.github.gmazzo.buildconfig' version '3.1.0'
}
group 'ru.nanit'
version '1.5.2'
group 'ua.nanit'
version '1.8.1'
compileJava {
options.encoding = "UTF-8"
}
tasks.withType(JavaCompile) {
tasks.withType(JavaCompile).configureEach {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
@ -23,10 +24,18 @@ dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api: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 'io.netty:netty-all:4.1.93.Final'
implementation 'io.netty:netty-all:4.1.101.Final'
implementation 'net.kyori:adventure-nbt:4.14.0'
implementation 'com.grack:nanojson:1.8'
implementation 'com.google.code.gson:gson:2.10.1'
}
buildConfig {
className("BuildConfig")
packageName("ua.nanit.limbo")
buildConfigField('String', 'LIMBO_VERSION', "\"${project.version}\"")
}
shadowJar {
@ -36,7 +45,9 @@ shadowJar {
attributes('Main-Class': 'ua.nanit.limbo.NanoLimbo')
}
minimize()
minimize {
exclude(dependency('ch.qos.logback:logback-classic:.*:.*'))
}
}
test {

View File

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

View File

@ -71,6 +71,11 @@ public final class LimboConfig {
private int bossGroupSize;
private int workerGroupSize;
private boolean useTrafficLimits;
private int maxPacketSize;
private double interval;
private double maxPacketRate;
public LimboConfig(Path root) {
this.root = root;
}
@ -127,6 +132,11 @@ public final class LimboConfig {
useEpoll = conf.node("netty", "useEpoll").getBoolean(true);
bossGroupSize = conf.node("netty", "threads", "bossGroup").getInt(1);
workerGroupSize = conf.node("netty", "threads", "workerGroup").getInt(4);
useTrafficLimits = conf.node("traffic", "enable").getBoolean(false);
maxPacketSize = conf.node("traffic", "maxPacketSize").getInt(-1);
interval = conf.node("traffic", "interval").getDouble(-1.0);
maxPacketRate = conf.node("traffic", "maxPacketRate").getDouble(-1.0);
}
private BufferedReader getReader() throws IOException {
@ -250,4 +260,20 @@ public final class LimboConfig {
public int getWorkerGroupSize() {
return workerGroupSize;
}
public boolean isUseTrafficLimits() {
return useTrafficLimits;
}
public int getMaxPacketSize() {
return maxPacketSize;
}
public double getInterval() {
return interval;
}
public double getMaxPacketRate() {
return maxPacketRate;
}
}

View File

@ -21,10 +21,7 @@ import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.timeout.ReadTimeoutHandler;
import ua.nanit.limbo.connection.pipeline.PacketDecoder;
import ua.nanit.limbo.connection.pipeline.PacketEncoder;
import ua.nanit.limbo.connection.pipeline.VarIntFrameDecoder;
import ua.nanit.limbo.connection.pipeline.VarIntLengthEncoder;
import ua.nanit.limbo.connection.pipeline.*;
import ua.nanit.limbo.server.LimboServer;
import java.util.concurrent.TimeUnit;
@ -49,6 +46,15 @@ public class ClientChannelInitializer extends ChannelInitializer<Channel> {
TimeUnit.MILLISECONDS));
pipeline.addLast("frame_decoder", new VarIntFrameDecoder());
pipeline.addLast("frame_encoder", new VarIntLengthEncoder());
if (server.getConfig().isUseTrafficLimits()) {
pipeline.addLast("traffic_limit", new ChannelTrafficHandler(
server.getConfig().getMaxPacketSize(),
server.getConfig().getInterval(),
server.getConfig().getMaxPacketRate()
));
}
pipeline.addLast("decoder", decoder);
pipeline.addLast("encoder", encoder);
pipeline.addLast("handler", connection);

View File

@ -30,12 +30,13 @@ import ua.nanit.limbo.connection.pipeline.PacketDecoder;
import ua.nanit.limbo.connection.pipeline.PacketEncoder;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.Packet;
import ua.nanit.limbo.protocol.PacketSnapshot;
import ua.nanit.limbo.protocol.packets.login.PacketDisconnect;
import ua.nanit.limbo.protocol.packets.play.PacketKeepAlive;
import ua.nanit.limbo.protocol.registry.State;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.LimboServer;
import ua.nanit.limbo.server.Logger;
import ua.nanit.limbo.server.Log;
import ua.nanit.limbo.util.UuidUtil;
import javax.crypto.Mac;
@ -94,7 +95,7 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
@Override
public void channelInactive(@NotNull ChannelHandlerContext ctx) throws Exception {
if (state.equals(State.PLAY)) {
if (state.equals(State.PLAY) || state.equals(State.CONFIGURATION)) {
server.getConnections().removeConnection(this);
}
super.channelInactive(ctx);
@ -103,7 +104,7 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
if (channel.isActive()) {
Logger.error("Unhandled exception: ", cause);
Log.error("Unhandled exception: ", cause);
}
}
@ -114,7 +115,7 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
public void handlePacket(Object packet) {
if (packet instanceof Packet) {
((Packet)packet).handle(this, server);
((Packet) packet).handle(this, server);
}
}
@ -125,9 +126,21 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
}
sendPacket(PacketSnapshots.PACKET_LOGIN_SUCCESS);
updateState(State.PLAY);
server.getConnections().addConnection(this);
// Preparing for configuration mode
if (clientVersion.moreOrEqual(Version.V1_20_2)) {
updateEncoderState(State.CONFIGURATION);
return;
}
spawnPlayer();
}
public void spawnPlayer() {
updateState(State.PLAY);
Runnable sendPlayPackets = () -> {
writePacket(PacketSnapshots.PACKET_JOIN_GAME);
writePacket(PacketSnapshots.PACKET_PLAYER_ABILITIES);
@ -163,6 +176,14 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
if (PacketSnapshots.PACKET_HEADER_AND_FOOTER != null && clientVersion.moreOrEqual(Version.V1_8))
writePacket(PacketSnapshots.PACKET_HEADER_AND_FOOTER);
if (clientVersion.moreOrEqual(Version.V1_20_3)) {
writePacket(PacketSnapshots.PACKET_START_WAITING_CHUNKS);
for (PacketSnapshot chunk : PacketSnapshots.PACKETS_EMPTY_CHUNKS) {
writePacket(chunk);
}
}
sendKeepAlive();
};
@ -173,6 +194,23 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
}
}
public void onLoginAcknowledgedReceived() {
updateState(State.CONFIGURATION);
if (PacketSnapshots.PACKET_PLUGIN_MESSAGE != null)
writePacket(PacketSnapshots.PACKET_PLUGIN_MESSAGE);
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);
}
public void disconnectLogin(String reason) {
if (isConnected() && state == State.LOGIN) {
PacketDisconnect disconnect = new PacketDisconnect();
@ -226,6 +264,10 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
encoder.updateState(state);
}
public void updateEncoderState(State state) {
encoder.updateState(state);
}
public void updateVersion(Version version) {
clientVersion = version;
decoder.updateVersion(version);
@ -233,7 +275,7 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
}
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) {
@ -270,7 +312,7 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
setAddress(socketAddressHostname);
gameProfile.setUuid(uuid);
Logger.debug("Successfully verified BungeeGuard token");
Log.debug("Successfully verified BungeeGuard token");
return true;
}
@ -294,7 +336,7 @@ public class ClientConnection extends ChannelInboundHandlerAdapter {
byte[] mySignature = mac.doFinal(data);
if (!MessageDigest.isEqual(signature, mySignature))
return false;
} catch (InvalidKeyException |java.security.NoSuchAlgorithmException e) {
} catch (InvalidKeyException | java.security.NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
int version = buf.readVarInt();

View File

@ -20,6 +20,8 @@ package ua.nanit.limbo.connection;
import io.netty.buffer.Unpooled;
import ua.nanit.limbo.LimboConstants;
import ua.nanit.limbo.protocol.packets.PacketHandshake;
import ua.nanit.limbo.protocol.packets.configuration.PacketFinishConfiguration;
import ua.nanit.limbo.protocol.packets.login.PacketLoginAcknowledged;
import ua.nanit.limbo.protocol.packets.login.PacketLoginPluginRequest;
import ua.nanit.limbo.protocol.packets.login.PacketLoginPluginResponse;
import ua.nanit.limbo.protocol.packets.login.PacketLoginStart;
@ -27,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.PacketStatusResponse;
import ua.nanit.limbo.server.LimboServer;
import ua.nanit.limbo.server.Logger;
import ua.nanit.limbo.server.Log;
import ua.nanit.limbo.util.UuidUtil;
import java.util.concurrent.ThreadLocalRandom;
@ -44,7 +46,7 @@ public class PacketHandler {
conn.updateVersion(packet.getVersion());
conn.updateState(packet.getNextState());
Logger.debug("Pinged from %s [%s]", conn.getAddress(),
Log.debug("Pinged from %s [%s]", conn.getAddress(),
conn.getClientVersion().toString());
if (server.getConfig().getInfoForwarding().isLegacy()) {
@ -127,4 +129,12 @@ public class PacketHandler {
}
}
public void handle(ClientConnection conn, PacketLoginAcknowledged packet) {
conn.onLoginAcknowledgedReceived();
}
public void handle(ClientConnection conn, PacketFinishConfiguration packet) {
conn.spawnPlayer();
}
}

View File

@ -17,15 +17,24 @@
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.protocol.PacketSnapshot;
import ua.nanit.limbo.protocol.packets.configuration.PacketFinishConfiguration;
import ua.nanit.limbo.protocol.packets.configuration.PacketRegistryData;
import ua.nanit.limbo.protocol.packets.login.PacketLoginSuccess;
import ua.nanit.limbo.protocol.packets.play.*;
import ua.nanit.limbo.server.LimboServer;
import ua.nanit.limbo.server.data.Title;
import ua.nanit.limbo.util.NbtMessageUtil;
import ua.nanit.limbo.util.UuidUtil;
import ua.nanit.limbo.world.Dimension;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
@ -43,7 +52,7 @@ public final class PacketSnapshots {
public static PacketSnapshot PACKET_HEADER_AND_FOOTER;
public static PacketSnapshot PACKET_PLAYER_POS_AND_LOOK_LEGACY;
// For 1.19 we need to spawn player outside world to avoid stuck in terrain loading
// For 1.19 we need to spawn player outside the world to avoid stuck in terrain loading
public static PacketSnapshot PACKET_PLAYER_POS_AND_LOOK;
public static PacketSnapshot PACKET_TITLE_TITLE;
@ -54,6 +63,12 @@ public final class PacketSnapshots {
public static PacketSnapshot PACKET_TITLE_LEGACY_SUBTITLE;
public static PacketSnapshot PACKET_TITLE_LEGACY_TIMES;
public static PacketSnapshot PACKET_REGISTRY_DATA;
public static List<PacketSnapshot> PACKETS_REGISTRY_DATA;
public static PacketSnapshot PACKET_FINISH_CONFIGURATION;
public static List<PacketSnapshot> PACKETS_EMPTY_CHUNKS;
public static PacketSnapshot PACKET_START_WAITING_CHUNKS;
private PacketSnapshots() { }
@ -117,8 +132,8 @@ public final class PacketSnapshots {
if (server.getConfig().isUseHeaderAndFooter()) {
PacketPlayerListHeader header = new PacketPlayerListHeader();
header.setHeader(server.getConfig().getPlayerListHeader());
header.setFooter(server.getConfig().getPlayerListFooter());
header.setHeader(NbtMessageUtil.create(server.getConfig().getPlayerListHeader()));
header.setFooter(NbtMessageUtil.create(server.getConfig().getPlayerListFooter()));
PACKET_HEADER_AND_FOOTER = PacketSnapshot.of(header);
}
@ -131,7 +146,7 @@ public final class PacketSnapshots {
if (server.getConfig().isUseJoinMessage()) {
PacketChatMessage joinMessage = new PacketChatMessage();
joinMessage.setJsonData(server.getConfig().getJoinMessage());
joinMessage.setMessage(NbtMessageUtil.create(server.getConfig().getJoinMessage()));
joinMessage.setPosition(PacketChatMessage.PositionLegacy.SYSTEM_MESSAGE);
joinMessage.setSender(UUID.randomUUID());
PACKET_JOIN_MESSAGE = PacketSnapshot.of(joinMessage);
@ -178,5 +193,65 @@ public final class PacketSnapshots {
PACKET_TITLE_LEGACY_SUBTITLE = PacketSnapshot.of(legacySubtitle);
PACKET_TITLE_LEGACY_TIMES = PacketSnapshot.of(legacyTimes);
}
PacketRegistryData packetRegistryData = new PacketRegistryData();
packetRegistryData.setDimensionRegistry(server.getDimensionRegistry());
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());
PacketGameEvent packetGameEvent = new PacketGameEvent();
packetGameEvent.setType((byte) 13); // Waiting for chunks type
packetGameEvent.setValue(0);
PACKET_START_WAITING_CHUNKS = PacketSnapshot.of(packetGameEvent);
int chunkXOffset = (int) 0 >> 4; // Default x position is 0
int chunkZOffset = (int) 0 >> 4; // Default z position is 0
int chunkEdgeSize = 1; // TODO Make configurable?
List<PacketSnapshot> emptyChunks = new ArrayList<>();
// Make multiple chunks for edges
for (int chunkX = chunkXOffset - chunkEdgeSize; chunkX <= chunkXOffset + chunkEdgeSize; ++chunkX) {
for (int chunkZ = chunkZOffset - chunkEdgeSize; chunkZ <= chunkZOffset + chunkEdgeSize; ++chunkZ) {
PacketEmptyChunk packetEmptyChunk = new PacketEmptyChunk();
packetEmptyChunk.setX(chunkX);
packetEmptyChunk.setZ(chunkZ);
emptyChunks.add(PacketSnapshot.of(packetEmptyChunk));
}
}
PACKETS_EMPTY_CHUNKS = emptyChunks;
}
}

View File

@ -0,0 +1,111 @@
package ua.nanit.limbo.connection.pipeline;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.jetbrains.annotations.NotNull;
import ua.nanit.limbo.server.Log;
import java.util.Arrays;
public class ChannelTrafficHandler extends ChannelInboundHandlerAdapter {
private final int maxPacketSize;
private final double maxPacketRate;
private final PacketBucket packetBucket;
public ChannelTrafficHandler(int maxPacketSize, double interval, double maxPacketRate) {
this.maxPacketSize = maxPacketSize;
this.maxPacketRate = maxPacketRate;
this.packetBucket = (interval > 0.0 && maxPacketRate > 0.0) ? new PacketBucket(interval * 1000.0, 150) : null;
}
@Override
public void channelRead(@NotNull ChannelHandlerContext ctx, @NotNull Object msg) throws Exception {
if (msg instanceof ByteBuf) {
ByteBuf in = (ByteBuf) msg;
int bytes = in.readableBytes();
if (maxPacketSize > 0 && bytes > maxPacketSize) {
closeConnection(ctx, "Closed %s due to large packet size (%d bytes)", ctx.channel().remoteAddress(), bytes);
return;
}
if (packetBucket != null) {
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);
}
private void closeConnection(ChannelHandlerContext ctx, String reason, Object... args) {
ctx.close();
Log.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.registry.State;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.Logger;
import ua.nanit.limbo.server.Log;
import java.util.List;
@ -47,16 +47,20 @@ public class PacketDecoder extends MessageToMessageDecoder<ByteBuf> {
Packet packet = mappings.getPacket(packetId);
if (packet != null) {
Logger.debug("Received packet %s[0x%s]", packet.toString(), Integer.toHexString(packetId));
Log.debug("Received packet %s[0x%s] (%d bytes)", packet.toString(), Integer.toHexString(packetId), msg.readableBytes());
try {
packet.decode(msg, version);
} catch (Exception e) {
Logger.warning("Cannot decode packet 0x%s: %s", Integer.toHexString(packetId), e.getMessage());
if (Log.isDebug()) {
Log.warning("Cannot decode packet 0x%s", e, Integer.toHexString(packetId));
} else {
Log.warning("Cannot decode packet 0x%s: %s", Integer.toHexString(packetId), e.getMessage());
}
}
ctx.fireChannelRead(packet);
} else {
Logger.debug("Undefined incoming packet: 0x" + Integer.toHexString(packetId));
Log.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.registry.State;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.Logger;
import ua.nanit.limbo.server.Log;
public class PacketEncoder extends MessageToByteEncoder<Packet> {
@ -51,7 +51,7 @@ public class PacketEncoder extends MessageToByteEncoder<Packet> {
}
if (packetId == -1) {
Logger.warning("Undefined packet class: %s[0x%s]", packet.getClass().getName(), Integer.toHexString(packetId));
Log.warning("Undefined packet class: %s[0x%s] (%d bytes)", packet.getClass().getName(), Integer.toHexString(packetId), msg.readableBytes());
return;
}
@ -60,11 +60,11 @@ public class PacketEncoder extends MessageToByteEncoder<Packet> {
try {
packet.encode(msg, version);
if (Logger.getLevel() >= Logger.Level.DEBUG.getIndex()) {
Logger.debug("Sending %s[0x%s] packet (%d bytes)", packet.toString(), Integer.toHexString(packetId), msg.readableBytes());
if (Log.isDebug()) {
Log.debug("Sending %s[0x%s] packet (%d bytes)", packet.toString(), Integer.toHexString(packetId), msg.readableBytes());
}
} catch (Exception e) {
Logger.error("Cannot encode packet 0x%s: %s", Integer.toHexString(packetId), e.getMessage());
Log.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.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import ua.nanit.limbo.server.Logger;
import ua.nanit.limbo.server.Log;
import java.util.List;
@ -42,18 +42,19 @@ public class VarIntFrameDecoder extends ByteToMessageDecoder {
int readVarInt = reader.getReadVarInt();
int bytesRead = reader.getBytesRead();
if (readVarInt < 0) {
Logger.error("[VarIntFrameDecoder] Bad data length");
Log.error("[VarIntFrameDecoder] Bad data length");
} else if (readVarInt == 0) {
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("[VarIntFrameDecoder] Too big data");
Log.error("[VarIntFrameDecoder] Too big data");
}
}
}

View File

@ -21,8 +21,8 @@ import io.netty.buffer.*;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.EncoderException;
import io.netty.util.ByteProcessor;
import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.*;
import ua.nanit.limbo.protocol.registry.Version;
import java.io.IOException;
import java.io.InputStream;
@ -73,14 +73,37 @@ public class ByteMessage extends ByteBuf {
}
public void writeVarInt(int value) {
while (true) {
if ((value & 0xFFFFFF80) == 0) {
// Peel the one and two byte count cases explicitly as they are the most common VarInt sizes
// that the proxy will write, to improve inlining.
if ((value & (0xFFFFFFFF << 7)) == 0) {
buf.writeByte(value);
return;
} else if ((value & (0xFFFFFFFF << 14)) == 0) {
int w = (value & 0x7F | 0x80) << 8 | (value >>> 7);
buf.writeShort(w);
} else {
writeVarIntFull(value);
}
}
buf.writeByte(value & 0x7F | 0x80);
value >>>= 7;
private void writeVarIntFull(final int value) {
// See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/
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);
}
}
@ -193,6 +216,63 @@ public class ByteMessage extends ByteBuf {
}
}
public void writeNamelessCompoundTag(BinaryTag binaryTag) {
try (ByteBufOutputStream stream = new ByteBufOutputStream(buf)) {
stream.writeByte(binaryTag.type().id());
// TODO Find a way to improve this...
if (binaryTag instanceof CompoundBinaryTag) {
CompoundBinaryTag tag = (CompoundBinaryTag) binaryTag;
tag.type().write(tag, stream);
}
else if (binaryTag instanceof ByteBinaryTag) {
ByteBinaryTag tag = (ByteBinaryTag) binaryTag;
tag.type().write(tag, stream);
}
else if (binaryTag instanceof ShortBinaryTag) {
ShortBinaryTag tag = (ShortBinaryTag) binaryTag;
tag.type().write(tag, stream);
}
else if (binaryTag instanceof IntBinaryTag) {
IntBinaryTag tag = (IntBinaryTag) binaryTag;
tag.type().write(tag, stream);
}
else if (binaryTag instanceof LongBinaryTag) {
LongBinaryTag tag = (LongBinaryTag) binaryTag;
tag.type().write(tag, stream);
}
else if (binaryTag instanceof DoubleBinaryTag) {
DoubleBinaryTag tag = (DoubleBinaryTag) binaryTag;
tag.type().write(tag, stream);
}
else if (binaryTag instanceof StringBinaryTag) {
StringBinaryTag tag = (StringBinaryTag) binaryTag;
tag.type().write(tag, stream);
}
else if (binaryTag instanceof ListBinaryTag) {
ListBinaryTag tag = (ListBinaryTag) binaryTag;
tag.type().write(tag, stream);
}
else if (binaryTag instanceof EndBinaryTag) {
EndBinaryTag tag = (EndBinaryTag) binaryTag;
tag.type().write(tag, stream);
}
}
catch (IOException e) {
throw new EncoderException("Cannot write NBT CompoundTag");
}
}
public void writeNbtMessage(NbtMessage nbtMessage, Version version) {
if (version.moreOrEqual(Version.V1_20_3)) {
writeNamelessCompoundTag(nbtMessage.getTag());
}
else {
writeString(nbtMessage.getJson());
}
}
public <E extends Enum<E>> void writeEnumSet(EnumSet<E> enumset, Class<E> oclass) {
E[] enums = oclass.getEnumConstants();
BitSet bits = new BitSet(enums.length);

View File

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

View File

@ -0,0 +1,30 @@
package ua.nanit.limbo.protocol;
import net.kyori.adventure.nbt.BinaryTag;
public class NbtMessage {
private String json;
private BinaryTag tag;
public NbtMessage(String json, BinaryTag tag) {
this.json = json;
this.tag = tag;
}
public String getJson() {
return json;
}
public void setJson(String json) {
this.json = json;
}
public BinaryTag getTag() {
return tag;
}
public void setTag(BinaryTag tag) {
this.tag = tag;
}
}

View File

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

View File

@ -0,0 +1,19 @@
package ua.nanit.limbo.protocol.packets.configuration;
import ua.nanit.limbo.connection.ClientConnection;
import ua.nanit.limbo.protocol.PacketIn;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.server.LimboServer;
public class PacketFinishConfiguration implements PacketIn, PacketOut {
@Override
public void handle(ClientConnection conn, LimboServer server) {
server.getPacketHandler().handle(conn, this);
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}

View File

@ -0,0 +1,32 @@
package ua.nanit.limbo.protocol.packets.configuration;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.MetadataWriter;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.world.DimensionRegistry;
public class PacketRegistryData implements PacketOut {
private DimensionRegistry dimensionRegistry;
private MetadataWriter metadataWriter;
public void setDimensionRegistry(DimensionRegistry dimensionRegistry) {
this.dimensionRegistry = dimensionRegistry;
}
public void setMetadataWriter(MetadataWriter metadataWriter) {
this.metadataWriter = metadataWriter;
}
@Override
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());
}
}

View File

@ -0,0 +1,19 @@
package ua.nanit.limbo.protocol.packets.login;
import ua.nanit.limbo.connection.ClientConnection;
import ua.nanit.limbo.protocol.PacketIn;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.server.LimboServer;
public class PacketLoginAcknowledged implements PacketIn, PacketOut {
@Override
public void handle(ClientConnection conn, LimboServer server) {
server.getPacketHandler().handle(conn, this);
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}

View File

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

View File

@ -49,7 +49,7 @@ public class PacketBossBar implements PacketOut {
public void encode(ByteMessage msg, Version version) {
msg.writeUuid(uuid);
msg.writeVarInt(0); // Create bossbar
msg.writeString(bossBar.getText());
msg.writeNbtMessage(bossBar.getText(), version);
msg.writeFloat(bossBar.getHealth());
msg.writeVarInt(bossBar.getColor().getIndex());
msg.writeVarInt(bossBar.getDivision().getIndex());

View File

@ -18,6 +18,7 @@
package ua.nanit.limbo.protocol.packets.play;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.NbtMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
@ -25,12 +26,12 @@ import java.util.UUID;
public class PacketChatMessage implements PacketOut {
private String jsonData;
private NbtMessage message;
private PositionLegacy position;
private UUID sender;
public void setJsonData(String jsonData) {
this.jsonData = jsonData;
public void setMessage(NbtMessage message) {
this.message = message;
}
public void setPosition(PositionLegacy position) {
@ -43,7 +44,7 @@ public class PacketChatMessage implements PacketOut {
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeString(jsonData);
msg.writeNbtMessage(message, version);
if (version.moreOrEqual(Version.V1_19_1)) {
msg.writeBoolean(position.index == PositionLegacy.ACTION_BAR.index);
} else if (version.moreOrEqual(Version.V1_19)) {

View File

@ -0,0 +1,47 @@
package ua.nanit.limbo.protocol.packets.play;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.LongArrayBinaryTag;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
public class PacketEmptyChunk implements PacketOut {
private int x;
private int z;
public void setX(int x) {
this.x = x;
}
public void setZ(int z) {
this.z = z;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeInt(x);
msg.writeInt(z);
LongArrayBinaryTag longArrayTag = LongArrayBinaryTag.longArrayBinaryTag(new long[37]);
CompoundBinaryTag tag = CompoundBinaryTag.builder()
.put("MOTION_BLOCKING", longArrayTag).build();
CompoundBinaryTag rootTag = CompoundBinaryTag.builder()
.put("root", tag).build();
msg.writeNamelessCompoundTag(rootTag);
byte[] sectionData = new byte[]{0, 0, 0, 0, 0, 0, 1, 0};
msg.writeVarInt(sectionData.length * 16);
for (int i = 0; i < 16; i++) {
msg.writeBytes(sectionData);
}
msg.writeVarInt(0);
byte[] lightData = new byte[]{1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 3, -1, -1, 0, 0};
msg.ensureWritable(lightData.length);
msg.writeBytes(lightData, 1, lightData.length - 1);
}
}

View File

@ -0,0 +1,25 @@
package ua.nanit.limbo.protocol.packets.play;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
public class PacketGameEvent implements PacketOut {
private byte type;
private float value;
public void setType(byte type) {
this.type = type;
}
public void setValue(float value) {
this.value = value;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeByte(type);
msg.writeFloat(value);
}
}

View File

@ -38,6 +38,8 @@ public class PacketJoinGame implements PacketOut {
private boolean enableRespawnScreen;
private boolean isDebug;
private boolean isFlat;
private boolean limitedCrafting;
private boolean secureProfile;
public void setEntityId(int entityId) {
this.entityId = entityId;
@ -95,6 +97,14 @@ public class PacketJoinGame implements PacketOut {
isFlat = flat;
}
public void setLimitedCrafting(boolean limitedCrafting) {
this.limitedCrafting = limitedCrafting;
}
public void setSecureProfile(boolean secureProfile) {
this.secureProfile = secureProfile;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeInt(entityId);
@ -230,7 +240,7 @@ public class PacketJoinGame implements PacketOut {
msg.writeBoolean(false);
}
if (version.moreOrEqual(Version.V1_20)) {
if (version.equals(Version.V1_20)) {
msg.writeBoolean(isHardcore);
msg.writeByte(gameMode);
msg.writeByte(previousGameMode);
@ -249,6 +259,47 @@ public class PacketJoinGame implements PacketOut {
msg.writeBoolean(false);
msg.writeVarInt(0);
}
if (version.fromTo(Version.V1_20_2, Version.V1_20_3)) {
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.writeString(worldName);
msg.writeString(worldName);
msg.writeLong(hashedSeed);
msg.writeByte(gameMode);
msg.writeByte(previousGameMode);
msg.writeBoolean(isDebug);
msg.writeBoolean(isFlat);
msg.writeBoolean(false);
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

@ -18,25 +18,26 @@
package ua.nanit.limbo.protocol.packets.play;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.NbtMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
public class PacketPlayerListHeader implements PacketOut {
private String header;
private String footer;
private NbtMessage header;
private NbtMessage footer;
public void setHeader(String header) {
public void setHeader(NbtMessage header) {
this.header = header;
}
public void setFooter(String footer) {
public void setFooter(NbtMessage footer) {
this.footer = footer;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeString(header);
msg.writeString(footer);
msg.writeNbtMessage(header, version);
msg.writeNbtMessage(footer, version);
}
}

View File

@ -39,4 +39,5 @@ public class PacketPluginMessage implements PacketOut {
msg.writeString(channel);
msg.writeString(message);
}
}

View File

@ -18,20 +18,21 @@
package ua.nanit.limbo.protocol.packets.play;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.NbtMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
public class PacketTitleSetSubTitle implements PacketOut {
private String subtitle;
private NbtMessage subtitle;
public void setSubtitle(String subtitle) {
public void setSubtitle(NbtMessage subtitle) {
this.subtitle = subtitle;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeString(subtitle);
msg.writeNbtMessage(subtitle, version);
}
}

View File

@ -18,20 +18,21 @@
package ua.nanit.limbo.protocol.packets.play;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.NbtMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
public class PacketTitleSetTitle implements PacketOut {
private String title;
private NbtMessage title;
public void setTitle(String title) {
public void setTitle(NbtMessage title) {
this.title = title;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeString(title);
msg.writeNbtMessage(title, version);
}
}

View File

@ -21,6 +21,7 @@ import ua.nanit.limbo.protocol.Packet;
import ua.nanit.limbo.protocol.packets.PacketHandshake;
import ua.nanit.limbo.protocol.packets.login.*;
import ua.nanit.limbo.protocol.packets.play.*;
import ua.nanit.limbo.protocol.packets.configuration.*;
import ua.nanit.limbo.protocol.packets.status.PacketStatusPing;
import ua.nanit.limbo.protocol.packets.status.PacketStatusRequest;
import ua.nanit.limbo.protocol.packets.status.PacketStatusResponse;
@ -63,6 +64,10 @@ public enum State {
serverBound.register(PacketLoginPluginResponse::new,
map(0x02, Version.getMin(), Version.getMax())
);
serverBound.register(
PacketLoginAcknowledged::new,
map(0x03, V1_20_2, Version.getMax())
);
clientBound.register(PacketDisconnect::new,
map(0x00, Version.getMin(), Version.getMax())
);
@ -74,7 +79,52 @@ public enum State {
);
}
},
PLAY(3) {
CONFIGURATION(3) {
{
clientBound.register(
PacketPluginMessage::new,
map(0x00, V1_20_2, V1_20_3),
map(0x01, V1_20_5, V1_21)
);
clientBound.register(
PacketDisconnect::new,
map(0x01, V1_20_2, V1_20_3),
map(0x02, V1_20_5, V1_21)
);
clientBound.register(
PacketFinishConfiguration::new,
map(0x02, V1_20_2, V1_20_3),
map(0x03, V1_20_5, V1_21)
);
clientBound.register(
PacketKeepAlive::new,
map(0x03, V1_20_2, V1_20_3),
map(0x04, V1_20_5, V1_21)
);
clientBound.register(
PacketRegistryData::new,
map(0x05, V1_20_2, V1_20_3),
map(0x07, V1_20_5, V1_21)
);
serverBound.register(
PacketPluginMessage::new,
map(0x01, V1_20_2, V1_20_3),
map(0x02, V1_20_2, V1_21)
);
serverBound.register(
PacketFinishConfiguration::new,
map(0x02, V1_20_2, V1_20_3),
map(0x03, V1_20_5, V1_21)
);
serverBound.register(
PacketKeepAlive::new,
map(0x03, V1_20_2, V1_20_3),
map(0x04, V1_20_5, V1_21)
);
}
},
PLAY(4) {
{
serverBound.register(PacketKeepAlive::new,
map(0x00, V1_7_2, V1_8),
@ -88,7 +138,10 @@ public enum State {
map(0x11, V1_19, V1_19),
map(0x12, V1_19_1, V1_19_1),
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(0x15, V1_20_3, V1_20_3),
map(0x18, V1_20_5, V1_21)
);
clientBound.register(PacketDeclareCommands::new,
@ -99,7 +152,8 @@ public enum State {
map(0x12, V1_17, V1_18_2),
map(0x0F, V1_19, V1_19_1),
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)
);
clientBound.register(PacketJoinGame::new,
map(0x01, V1_7_2, V1_8),
@ -112,7 +166,9 @@ public enum State {
map(0x23, V1_19, V1_19),
map(0x25, V1_19_1, V1_19_1),
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(0x2B, V1_20_5, V1_21)
);
clientBound.register(PacketPluginMessage::new,
map(0x19, V1_13, V1_13_2),
@ -124,7 +180,9 @@ public enum State {
map(0x15, V1_19, V1_19),
map(0x16, V1_19_1, V1_19_1),
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(0x19, V1_20_5, V1_21)
);
clientBound.register(PacketPlayerAbilities::new,
map(0x39, V1_7_2, V1_8),
@ -139,7 +197,9 @@ public enum State {
map(0x2F, V1_19, V1_19),
map(0x31, V1_19_1, V1_19_1),
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(0x38, V1_20_5, V1_21)
);
clientBound.register(PacketPlayerPositionAndLook::new,
map(0x08, V1_7_2, V1_8),
@ -154,7 +214,9 @@ public enum State {
map(0x36, V1_19, V1_19),
map(0x39, V1_19_1, V1_19_1),
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(0x40, V1_20_5, V1_21)
);
clientBound.register(PacketKeepAlive::new,
map(0x00, V1_7_2, V1_8),
@ -168,7 +230,9 @@ public enum State {
map(0x1E, V1_19, V1_19),
map(0x20, V1_19_1, V1_19_1),
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(0x26, V1_20_5, V1_21)
);
clientBound.register(PacketChatMessage::new,
map(0x02, V1_7_2, V1_8),
@ -180,7 +244,10 @@ public enum State {
map(0x5F, V1_19, V1_19),
map(0x62, V1_19_1, V1_19_1),
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(0x69, V1_20_3, V1_20_3),
map(0x6C, V1_20_5, V1_21)
);
clientBound.register(PacketBossBar::new,
map(0x0C, V1_9, V1_14_4),
@ -188,7 +255,8 @@ public enum State {
map(0x0C, V1_16, V1_16_4),
map(0x0D, V1_17, V1_18_2),
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)
);
clientBound.register(PacketPlayerInfo::new,
map(0x38, V1_7_2, V1_8),
@ -203,7 +271,9 @@ public enum State {
map(0x34, V1_19, V1_19),
map(0x37, V1_19_1, V1_19_1),
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(0x3E, V1_20_5, V1_21)
);
clientBound.register(PacketTitleLegacy::new,
map(0x45, V1_8, V1_11_1),
@ -219,21 +289,30 @@ public enum State {
map(0x5A, V1_18, V1_19),
map(0x5D, V1_19_1, V1_19_1),
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(0x63, V1_20_3, V1_20_3),
map(0x65, V1_20_5, V1_21)
);
clientBound.register(PacketTitleSetSubTitle::new,
map(0x57, V1_17, V1_17_1),
map(0x58, V1_18, V1_19),
map(0x5B, V1_19_1, V1_19_1),
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(0x61, V1_20_3, V1_20_3),
map(0x63, V1_20_5, V1_21)
);
clientBound.register(PacketTitleTimes::new,
map(0x5A, V1_17, V1_17_1),
map(0x5B, V1_18, V1_19),
map(0x5E, V1_19_1, V1_19_1),
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(0x64, V1_20_3, V1_20_3),
map(0x66, V1_20_5, V1_21)
);
clientBound.register(PacketPlayerListHeader::new,
map(0x47, V1_8, V1_8),
@ -250,11 +329,25 @@ public enum State {
map(0x60, V1_19, V1_19),
map(0x63, V1_19_1, V1_19_1),
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(0x6A, V1_20_3, V1_20_3),
map(0x6D, V1_20_5, V1_21)
);
clientBound.register(PacketSpawnPosition::new,
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(0x54, V1_20_3, V1_20_3),
map(0x56, V1_20_5, V1_21)
);
clientBound.register(PacketGameEvent::new,
map(0x20, V1_20_3, V1_20_3),
map(0x22, V1_20_5, V1_21)
);
clientBound.register(PacketEmptyChunk::new,
map(0x25, V1_20_3, V1_20_3),
map(0x27, V1_20_5, V1_21)
);
}
};

View File

@ -68,7 +68,13 @@ public enum Version {
// 1.19.2 has same protocol number
V1_19_3(761),
V1_19_4(762),
V1_20(763);
V1_20(763),
// 1.20.1 has same protocol number
V1_20_2(764),
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 Version MAX;

View File

@ -1,9 +1,6 @@
package ua.nanit.limbo.server;
import ua.nanit.limbo.server.commands.CmdConn;
import ua.nanit.limbo.server.commands.CmdHelp;
import ua.nanit.limbo.server.commands.CmdMem;
import ua.nanit.limbo.server.commands.CmdStop;
import ua.nanit.limbo.server.commands.*;
import java.util.*;
@ -19,8 +16,10 @@ public final class CommandManager extends Thread {
return commands.get(name.toLowerCase());
}
public void register(String name, Command cmd) {
commands.put(name.toLowerCase(), cmd);
public void register(Command cmd, String... aliases) {
for (String alias : aliases) {
commands.put(alias.toLowerCase(), cmd);
}
}
@Override
@ -41,19 +40,20 @@ public final class CommandManager extends Thread {
try {
handler.execute();
} catch (Throwable t) {
Logger.error("Cannot execute command:", t);
Log.error("Cannot execute command:", t);
}
continue;
}
Logger.info("Unknown command. Type \"help\" to get commands list");
Log.info("Unknown command. Type \"help\" to get commands list");
}
}
public void registerAll(LimboServer server) {
register("help", new CmdHelp(server));
register("conn", new CmdConn(server));
register("mem", new CmdMem());
register("stop", new CmdStop());
register(new CmdHelp(server), "help");
register(new CmdConn(server), "conn");
register(new CmdMem(), "mem");
register(new CmdStop(), "stop");
register(new CmdVersion(), "version", "ver");
}
}

View File

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

View File

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

View File

@ -17,61 +17,71 @@
package ua.nanit.limbo.server;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import ch.qos.logback.classic.Logger;
import org.slf4j.LoggerFactory;
public final class Logger {
public final class Log {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("hh:mm:ss");
private static final Logger LOGGER = (Logger) LoggerFactory.getLogger("Limbo");
private static int debugLevel = Level.INFO.getIndex();
private Logger() {}
public static int getLevel() {
return debugLevel;
}
private Log() {}
public static void info(Object msg, Object... args) {
print(Level.INFO, msg, null, args);
LOGGER.info(String.format(msg.toString(), args));
}
public static void debug(Object msg, Object... args) {
print(Level.DEBUG, msg, null, args);
LOGGER.debug(String.format(msg.toString(), args));
}
public static void warning(Object msg, Object... args) {
print(Level.WARNING, msg, null, args);
LOGGER.warn(String.format(msg.toString(), args));
}
public static void warning(Object msg, Throwable t, Object... args) {
print(Level.WARNING, msg, t, args);
LOGGER.warn(String.format(msg.toString(), args), t);
}
public static void error(Object msg, Object... args) {
print(Level.ERROR, msg, null, args);
LOGGER.error(msg.toString(), args);
}
public static void error(Object msg, Throwable t, Object... args) {
print(Level.ERROR, msg, t, args);
LOGGER.error(String.format(msg.toString(), args), t);
}
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();
}
}
private static String getPrefix(Level level) {
return String.format("[%s] [%s]", getTime(), level.getDisplay());
}
private static String getTime() {
return LocalTime.now().format(FORMATTER);
public static boolean isDebug() {
return debugLevel >= Level.DEBUG.getIndex();
}
static void setLevel(int 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 {

View File

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

View File

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

View File

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

View File

@ -0,0 +1,18 @@
package ua.nanit.limbo.server.commands;
import ua.nanit.limbo.server.Command;
import ua.nanit.limbo.server.Log;
import ua.nanit.limbo.BuildConfig;
public class CmdVersion implements Command {
@Override
public void execute() {
Log.info("Version: %s", BuildConfig.LIMBO_VERSION);
}
@Override
public String description() {
return "Display limbo version";
}
}

View File

@ -21,18 +21,20 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.serialize.TypeSerializer;
import ua.nanit.limbo.protocol.NbtMessage;
import ua.nanit.limbo.util.Colors;
import ua.nanit.limbo.util.NbtMessageUtil;
import java.lang.reflect.Type;
public class BossBar {
private String text;
private NbtMessage text;
private float health;
private Color color;
private Division division;
public String getText() {
public NbtMessage getText() {
return text;
}
@ -48,7 +50,7 @@ public class BossBar {
return division;
}
public void setText(String text) {
public void setText(NbtMessage text) {
this.text = text;
}
@ -110,7 +112,7 @@ public class BossBar {
public BossBar deserialize(Type type, ConfigurationNode node) throws SerializationException {
BossBar bossBar = new BossBar();
bossBar.setText(Colors.of(node.node("text").getString("")));
bossBar.setText(NbtMessageUtil.create(Colors.of(node.node("text").getString(""))));
bossBar.setHealth(node.node("health").getFloat());
if (bossBar.getHealth() < 0 || bossBar.getHealth() > 1)

View File

@ -20,23 +20,25 @@ package ua.nanit.limbo.server.data;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.serialize.TypeSerializer;
import ua.nanit.limbo.protocol.NbtMessage;
import ua.nanit.limbo.util.Colors;
import ua.nanit.limbo.util.NbtMessageUtil;
import java.lang.reflect.Type;
public class Title {
private String title;
private String subtitle;
private NbtMessage title;
private NbtMessage subtitle;
private int fadeIn;
private int stay;
private int fadeOut;
public String getTitle() {
public NbtMessage getTitle() {
return title;
}
public String getSubtitle() {
public NbtMessage getSubtitle() {
return subtitle;
}
@ -52,11 +54,11 @@ public class Title {
return fadeOut;
}
public void setTitle(String title) {
public void setTitle(NbtMessage title) {
this.title = title;
}
public void setSubtitle(String subtitle) {
public void setSubtitle(NbtMessage subtitle) {
this.subtitle = subtitle;
}
@ -77,8 +79,8 @@ public class Title {
@Override
public Title deserialize(Type type, ConfigurationNode node) {
Title title = new Title();
title.setTitle(Colors.of(node.node("title").getString("")));
title.setSubtitle(Colors.of(node.node("subtitle").getString("")));
title.setTitle(NbtMessageUtil.create(Colors.of(node.node("title").getString(""))));
title.setSubtitle(NbtMessageUtil.create(Colors.of(node.node("subtitle").getString(""))));
title.setFadeIn(node.node("fadeIn").getInt(10));
title.setStay(node.node("stay").getInt(100));
title.setFadeOut(node.node("fadeOut").getInt(10));

View File

@ -0,0 +1,110 @@
package ua.nanit.limbo.util;
import com.google.gson.*;
import net.kyori.adventure.nbt.*;
import ua.nanit.limbo.protocol.NbtMessage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class NbtMessageUtil {
public static NbtMessage create(String json) {
BinaryTag compoundBinaryTag = fromJson(JsonParser.parseString(json));
return new NbtMessage(json, compoundBinaryTag);
}
public static BinaryTag fromJson(JsonElement json) {
if (json instanceof JsonPrimitive) {
JsonPrimitive jsonPrimitive = (JsonPrimitive) json;
if (jsonPrimitive.isNumber()) {
Number number = json.getAsNumber();
if (number instanceof Byte) {
return ByteBinaryTag.byteBinaryTag((Byte) number);
} else if (number instanceof Short) {
return ShortBinaryTag.shortBinaryTag((Short) number);
} else if (number instanceof Integer) {
return IntBinaryTag.intBinaryTag((Integer) number);
} else if (number instanceof Long) {
return LongBinaryTag.longBinaryTag((Long) number);
} else if (number instanceof Float) {
return FloatBinaryTag.floatBinaryTag((Float) number);
} else if (number instanceof Double) {
return DoubleBinaryTag.doubleBinaryTag((Double) number);
}
} else if (jsonPrimitive.isString()) {
return StringBinaryTag.stringBinaryTag(jsonPrimitive.getAsString());
} else if (jsonPrimitive.isBoolean()) {
return ByteBinaryTag.byteBinaryTag(jsonPrimitive.getAsBoolean() ? (byte) 1 : (byte) 0);
} else {
throw new IllegalArgumentException("Unknown JSON primitive: " + jsonPrimitive);
}
} else if (json instanceof JsonObject) {
CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder();
for (Map.Entry<String, JsonElement> property : ((JsonObject) json).entrySet()) {
builder.put(property.getKey(), fromJson(property.getValue()));
}
return builder.build();
} else if (json instanceof JsonArray) {
List<JsonElement> jsonArray = ((JsonArray) json).asList();
if (jsonArray.isEmpty()) {
return ListBinaryTag.listBinaryTag(EndBinaryTag.endBinaryTag().type(), Collections.emptyList());
}
BinaryTagType tagByteType = ByteBinaryTag.ZERO.type();
BinaryTagType tagIntType = IntBinaryTag.intBinaryTag(0).type();
BinaryTagType tagLongType = LongBinaryTag.longBinaryTag(0).type();
BinaryTag listTag;
BinaryTagType listType = fromJson(jsonArray.get(0)).type();
if (listType.equals(tagByteType)) {
byte[] bytes = new byte[jsonArray.size()];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (Byte) ((JsonPrimitive) jsonArray.get(i)).getAsNumber();
}
listTag = ByteArrayBinaryTag.byteArrayBinaryTag(bytes);
} else if (listType.equals(tagIntType)) {
int[] ints = new int[jsonArray.size()];
for (int i = 0; i < ints.length; i++) {
ints[i] = (Integer) ((JsonPrimitive) jsonArray.get(i)).getAsNumber();
}
listTag = IntArrayBinaryTag.intArrayBinaryTag(ints);
} else if (listType.equals(tagLongType)) {
long[] longs = new long[jsonArray.size()];
for (int i = 0; i < longs.length; i++) {
longs[i] = (Long) ((JsonPrimitive) jsonArray.get(i)).getAsNumber();
}
listTag = LongArrayBinaryTag.longArrayBinaryTag(longs);
} else {
List<BinaryTag> tagItems = new ArrayList<>(jsonArray.size());
for (JsonElement jsonEl : jsonArray) {
BinaryTag subTag = fromJson(jsonEl);
if (subTag.type() != listType) {
throw new IllegalArgumentException("Cannot convert mixed JsonArray to Tag");
}
tagItems.add(subTag);
}
listTag = ListBinaryTag.listBinaryTag(listType, tagItems);
}
return listTag;
} else if (json instanceof JsonNull) {
return EndBinaryTag.endBinaryTag();
}
throw new IllegalArgumentException("Unknown JSON element: " + json);
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,30 @@
<?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'
port: 65535
# Max amount of players can join to server
# Max number of players can join to server
# Set -1 to make it infinite
maxPlayers: 100
@ -16,7 +16,7 @@ ping:
description: '{"text": "&9NanoLimbo"}'
version: 'NanoLimbo'
# 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
protocol: -1
@ -24,7 +24,7 @@ ping:
dimension: THE_END
# Whether to display the player in the player list
# For 1.16.5 clients, player list will be sent even if disabled, to avoid crash
# For 1.16.5 clients, the player list will be sent even if disabled, to avoid crash
playerList:
enable: false
username: 'NanoLimbo'
@ -50,12 +50,12 @@ brandName:
enable: true
content: 'NanoLimbo'
# Message sends when player join to server
# Message sends when player joins to the server
joinMessage:
enable: true
text: '{"text": "&eWelcome to the Limbo!"}'
# BossBar displays when player join to server
# BossBar displays when player joins to the server
# For 1.9+ clients
bossBar:
enable: true
@ -87,7 +87,7 @@ title:
# - LEGACY
# - MODERN
# - BUNGEE_GUARD
# Don't use secret if you not use MODERN type
# Don't use secret if you do not use MODERN type
infoForwarding:
type: NONE
secret: '<YOUR_SECRET_HERE>'
@ -105,11 +105,29 @@ readTimeout: 30000
# 3 - Display errors, warnings, info, debug
debugLevel: 2
# Warning! Do not touch params of this block, if you not completely sure what is this!
# Warning! Do not touch params of this block if you are not completely sure what is this!
netty:
# Use Linux native transport type, if it possible
# Use a Linux native transport type, if it possible
useEpoll: true
# EventLoopGroup threads count
threads:
bossGroup: 1
workerGroup: 4
# 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.
traffic:
# If true, then additional handler will be added to the channel pipeline
enable: true
# Max packet size in bytes
# Unlimited if -1
maxPacketSize: 8192
# The interval to measure packets over
# Lowering this value will limit peak packets from players which would target people with bad connections
# Raising this value will allow higher peak packet rates, which will help with people who have poor connections
# Ignored if -1.0
interval: 7.0
# The maximum packets per second for players
# It is measured over the configured interval
# Ignored if -1.0
maxPacketRate: 500.0