Renamed root packet

This commit is contained in:
Nan1t
2022-12-11 13:36:24 +03:00
parent 8210e61989
commit 77a68efdc8
63 changed files with 330 additions and 329 deletions

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo;
public final class LimboConstants {
public static final String VELOCITY_INFO_CHANNEL = "velocity:player_info";
public static final String BRAND_CHANNEL = "minecraft:brand";
private LimboConstants() {}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo;
import ua.nanit.limbo.server.LimboServer;
import ua.nanit.limbo.server.Logger;
public final class NanoLimbo {
public static void main(String[] args) {
try {
new LimboServer().start();
} catch (Exception e) {
Logger.error("Cannot start server: ", e);
}
}
}

View File

@@ -0,0 +1,253 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.configuration;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.ConfigurationOptions;
import org.spongepowered.configurate.serialize.TypeSerializerCollection;
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
import ua.nanit.limbo.util.Colors;
import ua.nanit.limbo.server.data.BossBar;
import ua.nanit.limbo.server.data.InfoForwarding;
import ua.nanit.limbo.server.data.PingData;
import ua.nanit.limbo.server.data.Title;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public final class LimboConfig {
private final Path root;
private SocketAddress address;
private int maxPlayers;
private PingData pingData;
private String dimensionType;
private int gameMode;
private boolean useBrandName;
private boolean useJoinMessage;
private boolean useBossBar;
private boolean useTitle;
private boolean usePlayerList;
private boolean useHeaderAndFooter;
private String brandName;
private String joinMessage;
private BossBar bossBar;
private Title title;
private String playerListUsername;
private String playerListHeader;
private String playerListFooter;
private InfoForwarding infoForwarding;
private long readTimeout;
private int debugLevel;
private boolean useEpoll;
private int bossGroupSize;
private int workerGroupSize;
public LimboConfig(Path root) {
this.root = root;
}
public void load() throws Exception {
ConfigurationOptions options = ConfigurationOptions.defaults().serializers(getSerializers());
YamlConfigurationLoader loader = YamlConfigurationLoader.builder()
.source(this::getReader)
.defaultOptions(options)
.build();
ConfigurationNode conf = loader.load();
address = conf.node("bind").get(SocketAddress.class);
maxPlayers = conf.node("maxPlayers").getInt();
pingData = conf.node("ping").get(PingData.class);
dimensionType = conf.node("dimension").getString("the_end");
if (dimensionType.equalsIgnoreCase("nether")) {
dimensionType = "the_nether";
}
if (dimensionType.equalsIgnoreCase("end")) {
dimensionType = "the_end";
}
gameMode = conf.node("gameMode").getInt();
useBrandName = conf.node("brandName", "enable").getBoolean();
useJoinMessage = conf.node("joinMessage", "enable").getBoolean();
useBossBar = conf.node("bossBar", "enable").getBoolean();
useTitle = conf.node("title", "enable").getBoolean();
usePlayerList = conf.node("playerList", "enable").getBoolean();
playerListUsername = conf.node("playerList", "username").getString();
useHeaderAndFooter = conf.node("headerAndFooter", "enable").getBoolean();
if (useBrandName)
brandName = conf.node("brandName", "content").getString();
if (useJoinMessage)
joinMessage = Colors.of(conf.node("joinMessage", "text").getString(""));
if (useBossBar)
bossBar = conf.node("bossBar").get(BossBar.class);
if (useTitle)
title = conf.node("title").get(Title.class);
if (useHeaderAndFooter) {
playerListHeader = Colors.of(conf.node("headerAndFooter", "header").getString());
playerListFooter = Colors.of(conf.node("headerAndFooter", "footer").getString());
}
infoForwarding = conf.node("infoForwarding").get(InfoForwarding.class);
readTimeout = conf.node("readTimeout").getLong();
debugLevel = conf.node("debugLevel").getInt();
useEpoll = conf.node("netty", "useEpoll").getBoolean(true);
bossGroupSize = conf.node("netty", "threads", "bossGroup").getInt(1);
workerGroupSize = conf.node("netty", "threads", "workerGroup").getInt(4);
}
private BufferedReader getReader() throws IOException {
String name = "settings.yml";
Path filePath = Paths.get(root.toString(), name);
if (!Files.exists(filePath)) {
InputStream stream = getClass().getResourceAsStream( "/" + name);
if (stream == null)
throw new FileNotFoundException("Cannot find settings resource file");
Files.copy(stream, filePath);
}
return Files.newBufferedReader(filePath);
}
private TypeSerializerCollection getSerializers() {
return TypeSerializerCollection.builder()
.register(SocketAddress.class, new SocketAddressSerializer())
.register(InfoForwarding.class, new InfoForwarding.Serializer())
.register(PingData.class, new PingData.Serializer())
.register(BossBar.class, new BossBar.Serializer())
.register(Title.class, new Title.Serializer())
.build();
}
public SocketAddress getAddress() {
return address;
}
public int getMaxPlayers() {
return maxPlayers;
}
public PingData getPingData() {
return pingData;
}
public String getDimensionType() {
return dimensionType;
}
public int getGameMode() {
return gameMode;
}
public InfoForwarding getInfoForwarding() {
return infoForwarding;
}
public long getReadTimeout() {
return readTimeout;
}
public int getDebugLevel() {
return debugLevel;
}
public boolean isUseBrandName() {
return useBrandName;
}
public boolean isUseJoinMessage() {
return useJoinMessage;
}
public boolean isUseBossBar() {
return useBossBar;
}
public boolean isUseTitle() {
return useTitle;
}
public boolean isUsePlayerList() {
return usePlayerList;
}
public boolean isUseHeaderAndFooter() {
return useHeaderAndFooter;
}
public String getBrandName() {
return brandName;
}
public String getJoinMessage() {
return joinMessage;
}
public BossBar getBossBar() {
return bossBar;
}
public Title getTitle() {
return title;
}
public String getPlayerListUsername() {
return playerListUsername;
}
public String getPlayerListHeader() {
return playerListHeader;
}
public String getPlayerListFooter() {
return playerListFooter;
}
public boolean isUseEpoll() {
return useEpoll;
}
public int getBossGroupSize() {
return bossGroupSize;
}
public int getWorkerGroupSize() {
return workerGroupSize;
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.configuration;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.serialize.TypeSerializer;
import java.lang.reflect.Type;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public class SocketAddressSerializer implements TypeSerializer<SocketAddress> {
@Override
public SocketAddress deserialize(Type type, ConfigurationNode node) {
String ip = node.node("ip").getString();
int port = node.node("port").getInt();
SocketAddress address;
if (ip == null || ip.isEmpty()) {
address = new InetSocketAddress(port);
} else {
address = new InetSocketAddress(ip, port);
}
return address;
}
@Override
public void serialize(Type type, @Nullable SocketAddress obj, ConfigurationNode node) {
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.connection;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.timeout.ReadTimeoutHandler;
import 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.server.LimboServer;
import java.util.concurrent.TimeUnit;
public class ClientChannelInitializer extends ChannelInitializer<Channel> {
private final LimboServer server;
public ClientChannelInitializer(LimboServer server) {
this.server = server;
}
@Override
protected void initChannel(Channel channel) {
ChannelPipeline pipeline = channel.pipeline();
PacketDecoder decoder = new PacketDecoder();
PacketEncoder encoder = new PacketEncoder();
ClientConnection connection = new ClientConnection(channel, server, decoder, encoder);
pipeline.addLast("timeout", new ReadTimeoutHandler(server.getConfig().getReadTimeout(),
TimeUnit.MILLISECONDS));
pipeline.addLast("frame_decoder", new VarIntFrameDecoder());
pipeline.addLast("frame_encoder", new VarIntLengthEncoder());
pipeline.addLast("decoder", decoder);
pipeline.addLast("encoder", encoder);
pipeline.addLast("handler", connection);
}
}

View File

@@ -0,0 +1,305 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.connection;
import com.grack.nanojson.JsonArray;
import com.grack.nanojson.JsonObject;
import com.grack.nanojson.JsonParser;
import com.grack.nanojson.JsonParserException;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.jetbrains.annotations.NotNull;
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.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.util.UuidUtil;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
public class ClientConnection extends ChannelInboundHandlerAdapter {
private final LimboServer server;
private final Channel channel;
private final GameProfile gameProfile;
private final PacketDecoder decoder;
private final PacketEncoder encoder;
private State state;
private Version clientVersion;
private SocketAddress address;
private int velocityLoginMessageId = -1;
public ClientConnection(Channel channel, LimboServer server, PacketDecoder decoder, PacketEncoder encoder) {
this.server = server;
this.channel = channel;
this.decoder = decoder;
this.encoder = encoder;
this.address = channel.remoteAddress();
this.gameProfile = new GameProfile();
}
public UUID getUuid() {
return gameProfile.getUuid();
}
public String getUsername() {
return gameProfile.getUsername();
}
public SocketAddress getAddress() {
return address;
}
public Version getClientVersion() {
return clientVersion;
}
public GameProfile getGameProfile() {
return gameProfile;
}
@Override
public void channelInactive(@NotNull ChannelHandlerContext ctx) throws Exception {
if (state.equals(State.PLAY)) {
server.getConnections().removeConnection(this);
}
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
if (channel.isActive()) {
Logger.error("Unhandled exception: ", cause);
}
}
@Override
public void channelRead(@NotNull ChannelHandlerContext ctx, @NotNull Object msg) {
handlePacket(msg);
}
public void handlePacket(Object packet) {
if (packet instanceof Packet) {
((Packet)packet).handle(this, server);
}
}
public void fireLoginSuccess() {
if (server.getConfig().getInfoForwarding().isModern() && velocityLoginMessageId == -1) {
disconnectLogin("You need to connect with Velocity");
return;
}
sendPacket(PacketSnapshots.PACKET_LOGIN_SUCCESS);
updateState(State.PLAY);
server.getConnections().addConnection(this);
Runnable sendPlayPackets = () -> {
writePacket(PacketSnapshots.PACKET_JOIN_GAME);
writePacket(PacketSnapshots.PACKET_PLAYER_ABILITIES);
if (clientVersion.less(Version.V1_9)) {
writePacket(PacketSnapshots.PACKET_PLAYER_POS_AND_LOOK_LEGACY);
} else {
writePacket(PacketSnapshots.PACKET_PLAYER_POS_AND_LOOK);
}
if (clientVersion.moreOrEqual(Version.V1_19_3))
writePacket(PacketSnapshots.PACKET_SPAWN_POSITION);
if (server.getConfig().isUsePlayerList() || clientVersion.equals(Version.V1_16_4))
writePacket(PacketSnapshots.PACKET_PLAYER_INFO);
if (clientVersion.moreOrEqual(Version.V1_13)) {
writePacket(PacketSnapshots.PACKET_DECLARE_COMMANDS);
if (PacketSnapshots.PACKET_PLUGIN_MESSAGE != null)
writePacket(PacketSnapshots.PACKET_PLUGIN_MESSAGE);
}
if (PacketSnapshots.PACKET_BOSS_BAR != null && clientVersion.moreOrEqual(Version.V1_9))
writePacket(PacketSnapshots.PACKET_BOSS_BAR);
if (PacketSnapshots.PACKET_JOIN_MESSAGE != null)
writePacket(PacketSnapshots.PACKET_JOIN_MESSAGE);
if (PacketSnapshots.PACKET_TITLE_TITLE != null && clientVersion.moreOrEqual(Version.V1_8))
writeTitle();
if (PacketSnapshots.PACKET_HEADER_AND_FOOTER != null && clientVersion.moreOrEqual(Version.V1_8))
writePacket(PacketSnapshots.PACKET_HEADER_AND_FOOTER);
sendKeepAlive();
};
if (clientVersion.lessOrEqual(Version.V1_7_6)) {
this.channel.eventLoop().schedule(sendPlayPackets, 100, TimeUnit.MILLISECONDS);
} else {
sendPlayPackets.run();
}
}
public void disconnectLogin(String reason) {
if (isConnected() && state == State.LOGIN) {
PacketDisconnect disconnect = new PacketDisconnect();
disconnect.setReason(reason);
sendPacketAndClose(disconnect);
}
}
public void writeTitle() {
if (clientVersion.moreOrEqual(Version.V1_17)) {
writePacket(PacketSnapshots.PACKET_TITLE_TITLE);
writePacket(PacketSnapshots.PACKET_TITLE_SUBTITLE);
writePacket(PacketSnapshots.PACKET_TITLE_TIMES);
} else {
writePacket(PacketSnapshots.PACKET_TITLE_LEGACY_TITLE);
writePacket(PacketSnapshots.PACKET_TITLE_LEGACY_SUBTITLE);
writePacket(PacketSnapshots.PACKET_TITLE_LEGACY_TIMES);
}
}
public void sendKeepAlive() {
if (state.equals(State.PLAY)) {
PacketKeepAlive keepAlive = new PacketKeepAlive();
keepAlive.setId(ThreadLocalRandom.current().nextLong());
sendPacket(keepAlive);
}
}
public void sendPacket(Object packet) {
if (isConnected())
channel.writeAndFlush(packet, channel.voidPromise());
}
public void sendPacketAndClose(Object packet) {
if (isConnected())
channel.writeAndFlush(packet).addListener(ChannelFutureListener.CLOSE);
}
public void writePacket(Object packet) {
if (isConnected())
channel.write(packet, channel.voidPromise());
}
public boolean isConnected() {
return channel.isActive();
}
public void updateState(State state) {
this.state = state;
decoder.updateState(state);
encoder.updateState(state);
}
public void updateVersion(Version version) {
clientVersion = version;
decoder.updateVersion(version);
encoder.updateVersion(version);
}
public void setAddress(String host) {
this.address = new InetSocketAddress(host, ((InetSocketAddress)this.address).getPort());
}
boolean checkBungeeGuardHandshake(String handshake) {
String[] split = handshake.split("\00");
if (split.length != 4)
return false;
String socketAddressHostname = split[1];
UUID uuid = UuidUtil.fromString(split[2]);
JsonArray arr;
try {
arr = JsonParser.array().from(split[3]);
} catch (JsonParserException e) {
return false;
}
String token = null;
for (Object obj : arr) {
if (obj instanceof JsonObject) {
JsonObject prop = (JsonObject) obj;
if (prop.getString("name").equals("bungeeguard-token")) {
token = prop.getString("value");
break;
}
}
}
if (!server.getConfig().getInfoForwarding().hasToken(token))
return false;
setAddress(socketAddressHostname);
gameProfile.setUuid(uuid);
Logger.debug("Successfully verified BungeeGuard token");
return true;
}
int getVelocityLoginMessageId() {
return velocityLoginMessageId;
}
void setVelocityLoginMessageId(int velocityLoginMessageId) {
this.velocityLoginMessageId = velocityLoginMessageId;
}
boolean checkVelocityKeyIntegrity(ByteMessage buf) {
byte[] signature = new byte[32];
buf.readBytes(signature);
byte[] data = new byte[buf.readableBytes()];
buf.getBytes(buf.readerIndex(), data);
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(server.getConfig().getInfoForwarding().getSecretKey(), "HmacSHA256"));
byte[] mySignature = mac.doFinal(data);
if (!MessageDigest.isEqual(signature, mySignature))
return false;
} catch (InvalidKeyException |java.security.NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
int version = buf.readVarInt();
if (version != 1)
throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted " + '\001');
return true;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.connection;
import java.util.UUID;
public class GameProfile {
private UUID uuid;
private String username;
public UUID getUuid() {
return uuid;
}
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}

View File

@@ -0,0 +1,130 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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.login.PacketLoginPluginRequest;
import ua.nanit.limbo.protocol.packets.login.PacketLoginPluginResponse;
import ua.nanit.limbo.protocol.packets.login.PacketLoginStart;
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.util.UuidUtil;
import java.util.concurrent.ThreadLocalRandom;
public class PacketHandler {
private final LimboServer server;
public PacketHandler(LimboServer server) {
this.server = server;
}
public void handle(ClientConnection conn, PacketHandshake packet) {
conn.updateVersion(packet.getVersion());
conn.updateState(packet.getNextState());
Logger.debug("Pinged from %s [%s]", conn.getAddress(),
conn.getClientVersion().toString());
if (server.getConfig().getInfoForwarding().isLegacy()) {
String[] split = packet.getHost().split("\00");
if (split.length == 3 || split.length == 4) {
conn.setAddress(split[1]);
conn.getGameProfile().setUuid(UuidUtil.fromString(split[2]));
} else {
conn.disconnectLogin("You've enabled player info forwarding. You need to connect with proxy");
}
} else if (server.getConfig().getInfoForwarding().isBungeeGuard()) {
if (!conn.checkBungeeGuardHandshake(packet.getHost())) {
conn.disconnectLogin("Invalid BungeeGuard token or handshake format");
}
}
}
public void handle(ClientConnection conn, PacketStatusRequest packet) {
conn.sendPacket(new PacketStatusResponse(server));
}
public void handle(ClientConnection conn, PacketStatusPing packet) {
conn.sendPacketAndClose(packet);
}
public void handle(ClientConnection conn, PacketLoginStart packet) {
if (server.getConfig().getMaxPlayers() > 0 &&
server.getConnections().getCount() >= server.getConfig().getMaxPlayers()) {
conn.disconnectLogin("Too many players connected");
return;
}
if (!conn.getClientVersion().isSupported()) {
conn.disconnectLogin("Unsupported client version");
return;
}
if (server.getConfig().getInfoForwarding().isModern()) {
int loginId = ThreadLocalRandom.current().nextInt(0, Integer.MAX_VALUE);
PacketLoginPluginRequest request = new PacketLoginPluginRequest();
request.setMessageId(loginId);
request.setChannel(LimboConstants.VELOCITY_INFO_CHANNEL);
request.setData(Unpooled.EMPTY_BUFFER);
conn.setVelocityLoginMessageId(loginId);
conn.sendPacket(request);
return;
}
if (!server.getConfig().getInfoForwarding().isModern()) {
conn.getGameProfile().setUsername(packet.getUsername());
conn.getGameProfile().setUuid(UuidUtil.getOfflineModeUuid(packet.getUsername()));
}
conn.fireLoginSuccess();
}
public void handle(ClientConnection conn, PacketLoginPluginResponse packet) {
if (server.getConfig().getInfoForwarding().isModern()
&& packet.getMessageId() == conn.getVelocityLoginMessageId()) {
if (!packet.isSuccessful() || packet.getData() == null) {
conn.disconnectLogin("You need to connect with Velocity");
return;
}
if (!conn.checkVelocityKeyIntegrity(packet.getData())) {
conn.disconnectLogin("Can't verify forwarded player info");
return;
}
// Order is important
conn.setAddress(packet.getData().readString());
conn.getGameProfile().setUuid(packet.getData().readUuid());
conn.getGameProfile().setUsername(packet.getData().readString());
conn.fireLoginSuccess();
}
}
}

View File

@@ -0,0 +1,182 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.connection;
import ua.nanit.limbo.LimboConstants;
import ua.nanit.limbo.protocol.PacketSnapshot;
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.UuidUtil;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
public final class PacketSnapshots {
public static PacketSnapshot PACKET_LOGIN_SUCCESS;
public static PacketSnapshot PACKET_JOIN_GAME;
public static PacketSnapshot PACKET_SPAWN_POSITION;
public static PacketSnapshot PACKET_PLUGIN_MESSAGE;
public static PacketSnapshot PACKET_PLAYER_ABILITIES;
public static PacketSnapshot PACKET_PLAYER_INFO;
public static PacketSnapshot PACKET_DECLARE_COMMANDS;
public static PacketSnapshot PACKET_JOIN_MESSAGE;
public static PacketSnapshot PACKET_BOSS_BAR;
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
public static PacketSnapshot PACKET_PLAYER_POS_AND_LOOK;
public static PacketSnapshot PACKET_TITLE_TITLE;
public static PacketSnapshot PACKET_TITLE_SUBTITLE;
public static PacketSnapshot PACKET_TITLE_TIMES;
public static PacketSnapshot PACKET_TITLE_LEGACY_TITLE;
public static PacketSnapshot PACKET_TITLE_LEGACY_SUBTITLE;
public static PacketSnapshot PACKET_TITLE_LEGACY_TIMES;
private PacketSnapshots() { }
public static void initPackets(LimboServer server) {
final String username = server.getConfig().getPingData().getVersion();
final UUID uuid = UuidUtil.getOfflineModeUuid(username);
PacketLoginSuccess loginSuccess = new PacketLoginSuccess();
loginSuccess.setUsername(username);
loginSuccess.setUuid(uuid);
PacketJoinGame joinGame = new PacketJoinGame();
String worldName = "minecraft:" + server.getConfig().getDimensionType().toLowerCase();
joinGame.setEntityId(0);
joinGame.setEnableRespawnScreen(true);
joinGame.setFlat(false);
joinGame.setGameMode(server.getConfig().getGameMode());
joinGame.setHardcore(false);
joinGame.setMaxPlayers(server.getConfig().getMaxPlayers());
joinGame.setPreviousGameMode(-1);
joinGame.setReducedDebugInfo(true);
joinGame.setDebug(false);
joinGame.setViewDistance(0);
joinGame.setWorldName(worldName);
joinGame.setWorldNames(worldName);
joinGame.setHashedSeed(0);
joinGame.setDimensionRegistry(server.getDimensionRegistry());
PacketPlayerAbilities playerAbilities = new PacketPlayerAbilities();
playerAbilities.setFlyingSpeed(0.0F);
playerAbilities.setFlags(0x02);
playerAbilities.setFieldOfView(0.1F);
int teleportId = ThreadLocalRandom.current().nextInt();
PacketPlayerPositionAndLook positionAndLookLegacy
= new PacketPlayerPositionAndLook(0, 64, 0, 0, 0, teleportId);
PacketPlayerPositionAndLook positionAndLook
= new PacketPlayerPositionAndLook(0, 400, 0, 0, 0, teleportId);
PacketSpawnPosition packetSpawnPosition = new PacketSpawnPosition(0, 400, 0);
PacketDeclareCommands declareCommands = new PacketDeclareCommands();
declareCommands.setCommands(Collections.emptyList());
PacketPlayerInfo info = new PacketPlayerInfo();
info.setUsername(server.getConfig().getPlayerListUsername());
info.setGameMode(server.getConfig().getGameMode());
info.setUuid(uuid);
PACKET_LOGIN_SUCCESS = PacketSnapshot.of(loginSuccess);
PACKET_JOIN_GAME = PacketSnapshot.of(joinGame);
PACKET_PLAYER_POS_AND_LOOK_LEGACY = PacketSnapshot.of(positionAndLookLegacy);
PACKET_PLAYER_POS_AND_LOOK = PacketSnapshot.of(positionAndLook);
PACKET_SPAWN_POSITION = PacketSnapshot.of(packetSpawnPosition);
PACKET_PLAYER_ABILITIES = PacketSnapshot.of(playerAbilities);
PACKET_PLAYER_INFO = PacketSnapshot.of(info);
PACKET_DECLARE_COMMANDS = PacketSnapshot.of(declareCommands);
if (server.getConfig().isUseHeaderAndFooter()) {
PacketPlayerListHeader header = new PacketPlayerListHeader();
header.setHeader(server.getConfig().getPlayerListHeader());
header.setFooter(server.getConfig().getPlayerListFooter());
PACKET_HEADER_AND_FOOTER = PacketSnapshot.of(header);
}
if (server.getConfig().isUseBrandName()){
PacketPluginMessage pluginMessage = new PacketPluginMessage();
pluginMessage.setChannel(LimboConstants.BRAND_CHANNEL);
pluginMessage.setMessage(server.getConfig().getBrandName());
PACKET_PLUGIN_MESSAGE = PacketSnapshot.of(pluginMessage);
}
if (server.getConfig().isUseJoinMessage()) {
PacketChatMessage joinMessage = new PacketChatMessage();
joinMessage.setJsonData(server.getConfig().getJoinMessage());
joinMessage.setPosition(PacketChatMessage.PositionLegacy.SYSTEM_MESSAGE);
joinMessage.setSender(UUID.randomUUID());
PACKET_JOIN_MESSAGE = PacketSnapshot.of(joinMessage);
}
if (server.getConfig().isUseBossBar()) {
PacketBossBar bossBar = new PacketBossBar();
bossBar.setBossBar(server.getConfig().getBossBar());
bossBar.setUuid(UUID.randomUUID());
PACKET_BOSS_BAR = PacketSnapshot.of(bossBar);
}
if (server.getConfig().isUseTitle()) {
Title title = server.getConfig().getTitle();
PacketTitleSetTitle packetTitle = new PacketTitleSetTitle();
PacketTitleSetSubTitle packetSubtitle = new PacketTitleSetSubTitle();
PacketTitleTimes packetTimes = new PacketTitleTimes();
PacketTitleLegacy legacyTitle = new PacketTitleLegacy();
PacketTitleLegacy legacySubtitle = new PacketTitleLegacy();
PacketTitleLegacy legacyTimes = new PacketTitleLegacy();
packetTitle.setTitle(title.getTitle());
packetSubtitle.setSubtitle(title.getSubtitle());
packetTimes.setFadeIn(title.getFadeIn());
packetTimes.setStay(title.getStay());
packetTimes.setFadeOut(title.getFadeOut());
legacyTitle.setTitle(title);
legacyTitle.setAction(PacketTitleLegacy.Action.SET_TITLE);
legacySubtitle.setTitle(title);
legacySubtitle.setAction(PacketTitleLegacy.Action.SET_SUBTITLE);
legacyTimes.setTitle(title);
legacyTimes.setAction(PacketTitleLegacy.Action.SET_TIMES_AND_DISPLAY);
PACKET_TITLE_TITLE = PacketSnapshot.of(packetTitle);
PACKET_TITLE_SUBTITLE = PacketSnapshot.of(packetSubtitle);
PACKET_TITLE_TIMES = PacketSnapshot.of(packetTimes);
PACKET_TITLE_LEGACY_TITLE = PacketSnapshot.of(legacyTitle);
PACKET_TITLE_LEGACY_SUBTITLE = PacketSnapshot.of(legacySubtitle);
PACKET_TITLE_LEGACY_TIMES = PacketSnapshot.of(legacyTimes);
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.connection.pipeline;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
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 java.util.List;
public class PacketDecoder extends MessageToMessageDecoder<ByteBuf> {
private State.PacketRegistry mappings;
private Version version;
public PacketDecoder() {
updateVersion(Version.getMin());
updateState(State.HANDSHAKING);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) throws Exception {
if (!ctx.channel().isActive() || mappings == null) return;
ByteMessage msg = new ByteMessage(buf);
int packetId = msg.readVarInt();
Packet packet = mappings.getPacket(packetId);
if (packet != null) {
Logger.debug("Received packet %s[0x%s]", packet.toString(), Integer.toHexString(packetId));
try {
packet.decode(msg, version);
} catch (Exception e) {
Logger.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));
}
}
public void updateVersion(Version version) {
this.version = version;
}
public void updateState(State state) {
this.mappings = state.serverBound.getRegistry(version);
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.connection.pipeline;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import ua.nanit.limbo.protocol.ByteMessage;
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;
public class PacketEncoder extends MessageToByteEncoder<Packet> {
private State.PacketRegistry registry;
private Version version;
public PacketEncoder() {
updateVersion(Version.getMin());
updateState(State.HANDSHAKING);
}
@Override
protected void encode(ChannelHandlerContext ctx, Packet packet, ByteBuf out) throws Exception {
if (registry == null) return;
ByteMessage msg = new ByteMessage(out);
int packetId;
if (packet instanceof PacketSnapshot) {
packetId = registry.getPacketId(((PacketSnapshot)packet).getWrappedPacket().getClass());
} else {
packetId = registry.getPacketId(packet.getClass());
}
if (packetId == -1) {
Logger.warning("Undefined packet class: %s[0x%s]", packet.getClass().getName(), Integer.toHexString(packetId));
return;
}
msg.writeVarInt(packetId);
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());
}
} catch (Exception e) {
Logger.error("Cannot encode packet 0x%s: %s", Integer.toHexString(packetId), e.getMessage());
}
}
public void updateVersion(Version version) {
this.version = version;
}
public void updateState(State state) {
this.registry = state.clientBound.getRegistry(version);
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.connection.pipeline;
import io.netty.util.ByteProcessor;
public class VarIntByteDecoder implements ByteProcessor {
private int readVarInt;
private int bytesRead;
private DecodeResult result = DecodeResult.TOO_SHORT;
@Override
public boolean process(byte k) {
readVarInt |= (k & 0x7F) << bytesRead++ * 7;
if (bytesRead > 3) {
result = DecodeResult.TOO_BIG;
return false;
}
if ((k & 0x80) != 128) {
result = DecodeResult.SUCCESS;
return false;
}
return true;
}
public int getReadVarInt() {
return readVarInt;
}
public int getBytesRead() {
return bytesRead;
}
public DecodeResult getResult() {
return result;
}
public enum DecodeResult {
SUCCESS,
TOO_SHORT,
TOO_BIG
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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 java.util.List;
public class VarIntFrameDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
if (!ctx.channel().isActive()) {
in.clear();
return;
}
VarIntByteDecoder reader = new VarIntByteDecoder();
int varIntEnd = in.forEachByte(reader);
if (varIntEnd == -1) return;
if (reader.getResult() == VarIntByteDecoder.DecodeResult.SUCCESS) {
int readVarInt = reader.getReadVarInt();
int bytesRead = reader.getBytesRead();
if (readVarInt < 0) {
Logger.error("[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");
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.connection.pipeline;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import ua.nanit.limbo.protocol.ByteMessage;
@ChannelHandler.Sharable
public class VarIntLengthEncoder extends MessageToByteEncoder<ByteBuf> {
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf buf, ByteBuf out) {
ByteMessage msg = new ByteMessage(out);
msg.writeVarInt(buf.readableBytes());
msg.writeBytes(buf);
}
@Override
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) {
int anticipatedRequiredCapacity = 5 + msg.readableBytes();
return ctx.alloc().heapBuffer(anticipatedRequiredCapacity);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol;
import ua.nanit.limbo.connection.ClientConnection;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.LimboServer;
public interface Packet {
void encode(ByteMessage msg, Version version);
void decode(ByteMessage msg, Version version);
default void handle(ClientConnection conn, LimboServer server) {
// Ignored by default
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol;
import ua.nanit.limbo.protocol.registry.Version;
public interface PacketIn extends Packet {
@Override
default void encode(ByteMessage msg, Version version) {
// Can be ignored for incoming packets
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol;
import ua.nanit.limbo.protocol.registry.Version;
public interface PacketOut extends Packet {
@Override
default void decode(ByteMessage msg, Version version) {
// Can be ignored for outgoing packets
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol;
import ua.nanit.limbo.protocol.registry.Version;
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
*/
public class PacketSnapshot implements PacketOut {
private final PacketOut packet;
private final Map<Version, byte[]> versionMessages = new HashMap<>();
private final Map<Version, Version> mappings = new HashMap<>();
public PacketSnapshot(PacketOut packet) {
this.packet = packet;
}
public PacketOut getWrappedPacket() {
return packet;
}
public void encode() {
Map<Integer, Version> hashes = new HashMap<>();
for (Version version : Version.values()) {
if (version.equals(Version.UNDEFINED)) continue;
ByteMessage encodedMessage = ByteMessage.create();
packet.encode(encodedMessage, version);
int hash = encodedMessage.hashCode();
Version hashed = hashes.get(hash);
if (hashed != null) {
mappings.put(version, hashed);
} else {
hashes.put(hash, version);
mappings.put(version, version);
versionMessages.put(version, encodedMessage.toByteArray());
}
encodedMessage.release();
}
}
@Override
public void encode(ByteMessage msg, Version version) {
Version mapped = mappings.get(version);
byte[] message = versionMessages.get(mapped);
if (message != null)
msg.writeBytes(message);
else
throw new IllegalArgumentException("No mappings for version " + version);
}
@Override
public String toString() {
return packet.getClass().getSimpleName();
}
public static PacketSnapshot of(PacketOut packet) {
PacketSnapshot snapshot = new PacketSnapshot(packet);
snapshot.encode();
return snapshot;
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets;
import ua.nanit.limbo.connection.ClientConnection;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketIn;
import ua.nanit.limbo.protocol.registry.State;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.LimboServer;
public class PacketHandshake implements PacketIn {
private Version version;
private String host;
private int port;
private State nextState;
public Version getVersion() {
return version;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public State getNextState() {
return nextState;
}
@Override
public void decode(ByteMessage msg, Version version) {
try {
this.version = Version.of(msg.readVarInt());
} catch (IllegalArgumentException e) {
this.version = Version.UNDEFINED;
}
this.host = msg.readString();
this.port = msg.readUnsignedShort();
this.nextState = State.getById(msg.readVarInt());
}
@Override
public String toString() {
return getClass().getSimpleName();
}
@Override
public void handle(ClientConnection conn, LimboServer server) {
server.getPacketHandler().handle(conn, this);
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.login;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
public class PacketDisconnect implements PacketOut {
private String reason;
public void setReason(String reason) {
this.reason = reason;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeString(String.format("{\"text\": \"%s\"}", reason));
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.login;
import io.netty.buffer.ByteBuf;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
public class PacketLoginPluginRequest implements PacketOut {
private int messageId;
private String channel;
private ByteBuf data;
public void setMessageId(int messageId) {
this.messageId = messageId;
}
public void setChannel(String channel) {
this.channel = channel;
}
public void setData(ByteBuf data) {
this.data = data;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeVarInt(messageId);
msg.writeString(channel);
msg.writeBytes(data);
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.login;
import ua.nanit.limbo.connection.ClientConnection;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketIn;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.LimboServer;
public class PacketLoginPluginResponse implements PacketIn {
private int messageId;
private boolean successful;
private ByteMessage data;
public int getMessageId() {
return messageId;
}
public boolean isSuccessful() {
return successful;
}
public ByteMessage getData() {
return data;
}
@Override
public void decode(ByteMessage msg, Version version) {
messageId = msg.readVarInt();
successful = msg.readBoolean();
if (msg.readableBytes() > 0) {
int i = msg.readableBytes();
data = new ByteMessage(msg.readBytes(i));
}
}
@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,48 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.login;
import ua.nanit.limbo.connection.ClientConnection;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketIn;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.LimboServer;
public class PacketLoginStart implements PacketIn {
private String username;
public String getUsername() {
return username;
}
@Override
public void decode(ByteMessage msg, Version version) {
this.username = msg.readString();
}
@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,59 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.login;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
import java.util.UUID;
public class PacketLoginSuccess implements PacketOut {
private UUID uuid;
private String username;
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public void encode(ByteMessage msg, Version version) {
if (version.moreOrEqual(Version.V1_16)) {
msg.writeUuid(uuid);
} else if (version.moreOrEqual(Version.V1_7_6)) {
msg.writeString(uuid.toString());
} else {
msg.writeString(uuid.toString().replace("-", ""));
}
msg.writeString(username);
if (version.moreOrEqual(Version.V1_19)) {
msg.writeVarInt(0);
}
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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;
import ua.nanit.limbo.server.data.BossBar;
import java.util.UUID;
/**
* Packet for 1.9+
*/
public class PacketBossBar implements PacketOut {
private UUID uuid;
private BossBar bossBar;
private int flags;
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
public void setBossBar(BossBar bossBar) {
this.bossBar = bossBar;
}
public void setFlags(int flags) {
this.flags = flags;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeUuid(uuid);
msg.writeVarInt(0); // Create bossbar
msg.writeString(bossBar.getText());
msg.writeFloat(bossBar.getHealth());
msg.writeVarInt(bossBar.getColor().getIndex());
msg.writeVarInt(bossBar.getDivision().getIndex());
msg.writeByte(flags);
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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;
import java.util.UUID;
public class PacketChatMessage implements PacketOut {
private String jsonData;
private PositionLegacy position;
private UUID sender;
public void setJsonData(String jsonData) {
this.jsonData = jsonData;
}
public void setPosition(PositionLegacy position) {
this.position = position;
}
public void setSender(UUID sender) {
this.sender = sender;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeString(jsonData);
if (version.moreOrEqual(Version.V1_19_1)) {
msg.writeBoolean(position.index == PositionLegacy.ACTION_BAR.index);
} else if (version.moreOrEqual(Version.V1_19)) {
msg.writeVarInt(position.index);
} else if (version.moreOrEqual(Version.V1_8)) {
msg.writeByte(position.index);
}
if (version.moreOrEqual(Version.V1_16) && version.less(Version.V1_19))
msg.writeUuid(sender);
}
public enum PositionLegacy {
CHAT(0),
SYSTEM_MESSAGE(1),
ACTION_BAR(2);
private final int index;
PositionLegacy(int index) {
this.index = index;
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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;
import java.util.List;
/**
* Packet for 1.13+
*/
public class PacketDeclareCommands implements PacketOut {
private List<String> commands;
public void setCommands(List<String> commands) {
this.commands = commands;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeVarInt(commands.size() * 2 + 1); // +1 because declaring root node
// Declare root node
msg.writeByte(0);
msg.writeVarInt(commands.size());
for (int i = 1; i <= commands.size() * 2; i++) {
msg.writeVarInt(i++);
}
// Declare other commands
int i = 1;
for (String cmd : commands) {
msg.writeByte(1 | 0x04);
msg.writeVarInt(1);
msg.writeVarInt(i + 1);
msg.writeString(cmd);
i++;
msg.writeByte(2 | 0x04 | 0x10);
msg.writeVarInt(1);
msg.writeVarInt(i);
msg.writeString("arg");
msg.writeString("brigadier:string");
msg.writeVarInt(0);
msg.writeString("minecraft:ask_server");
i++;
}
msg.writeVarInt(0);
}
}

View File

@@ -0,0 +1,229 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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;
import ua.nanit.limbo.world.DimensionRegistry;
public class PacketJoinGame implements PacketOut {
private int entityId;
private boolean isHardcore = false;
private int gameMode = 2;
private int previousGameMode = -1;
private String[] worldNames;
private DimensionRegistry dimensionRegistry;
private String worldName;
private long hashedSeed;
private int maxPlayers;
private int viewDistance = 2;
private boolean reducedDebugInfo;
private boolean enableRespawnScreen;
private boolean isDebug;
private boolean isFlat;
public void setEntityId(int entityId) {
this.entityId = entityId;
}
public void setHardcore(boolean hardcore) {
isHardcore = hardcore;
}
public void setGameMode(int gameMode) {
this.gameMode = gameMode;
}
public void setPreviousGameMode(int previousGameMode) {
this.previousGameMode = previousGameMode;
}
public void setWorldNames(String... worldNames) {
this.worldNames = worldNames;
}
public void setDimensionRegistry(DimensionRegistry dimensionRegistry) {
this.dimensionRegistry = dimensionRegistry;
}
public void setWorldName(String worldName) {
this.worldName = worldName;
}
public void setHashedSeed(long hashedSeed) {
this.hashedSeed = hashedSeed;
}
public void setMaxPlayers(int maxPlayers) {
this.maxPlayers = maxPlayers;
}
public void setViewDistance(int viewDistance) {
this.viewDistance = viewDistance;
}
public void setReducedDebugInfo(boolean reducedDebugInfo) {
this.reducedDebugInfo = reducedDebugInfo;
}
public void setEnableRespawnScreen(boolean enableRespawnScreen) {
this.enableRespawnScreen = enableRespawnScreen;
}
public void setDebug(boolean debug) {
isDebug = debug;
}
public void setFlat(boolean flat) {
isFlat = flat;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeInt(entityId);
if (version.fromTo(Version.V1_7_2, Version.V1_7_6)) {
msg.writeByte(gameMode == 3 ? 1 : gameMode);
msg.writeByte(dimensionRegistry.getDefaultDimension_1_16().getId());
msg.writeByte(0); // Difficulty
msg.writeByte(maxPlayers);
msg.writeString("flat"); // Level type
}
if (version.fromTo(Version.V1_8, Version.V1_9)) {
msg.writeByte(gameMode);
msg.writeByte(dimensionRegistry.getDefaultDimension_1_16().getId());
msg.writeByte(0); // Difficulty
msg.writeByte(maxPlayers);
msg.writeString("flat"); // Level type
msg.writeBoolean(reducedDebugInfo);
}
if (version.fromTo(Version.V1_9_1, Version.V1_13_2)) {
msg.writeByte(gameMode);
msg.writeInt(dimensionRegistry.getDefaultDimension_1_16().getId());
msg.writeByte(0); // Difficulty
msg.writeByte(maxPlayers);
msg.writeString("flat"); // Level type
msg.writeBoolean(reducedDebugInfo);
}
if (version.fromTo(Version.V1_14, Version.V1_14_4)) {
msg.writeByte(gameMode);
msg.writeInt(dimensionRegistry.getDefaultDimension_1_16().getId());
msg.writeByte(maxPlayers);
msg.writeString("flat"); // Level type
msg.writeVarInt(viewDistance);
msg.writeBoolean(reducedDebugInfo);
}
if (version.fromTo(Version.V1_15, Version.V1_15_2)) {
msg.writeByte(gameMode);
msg.writeInt(dimensionRegistry.getDefaultDimension_1_16().getId());
msg.writeLong(hashedSeed);
msg.writeByte(maxPlayers);
msg.writeString("flat"); // Level type
msg.writeVarInt(viewDistance);
msg.writeBoolean(reducedDebugInfo);
msg.writeBoolean(enableRespawnScreen);
}
if (version.fromTo(Version.V1_16, Version.V1_16_1)) {
msg.writeByte(gameMode);
msg.writeByte(previousGameMode);
msg.writeStringsArray(worldNames);
msg.writeCompoundTag(dimensionRegistry.getOldCodec());
msg.writeString(dimensionRegistry.getDefaultDimension_1_16().getName());
msg.writeString(worldName);
msg.writeLong(hashedSeed);
msg.writeByte(maxPlayers);
msg.writeVarInt(viewDistance);
msg.writeBoolean(reducedDebugInfo);
msg.writeBoolean(enableRespawnScreen);
msg.writeBoolean(isDebug);
msg.writeBoolean(isFlat);
}
if (version.fromTo(Version.V1_16_2, Version.V1_17_1)) {
msg.writeBoolean(isHardcore);
msg.writeByte(gameMode);
msg.writeByte(previousGameMode);
msg.writeStringsArray(worldNames);
msg.writeCompoundTag(dimensionRegistry.getCodec_1_16());
msg.writeCompoundTag(dimensionRegistry.getDefaultDimension_1_16().getData());
msg.writeString(worldName);
msg.writeLong(hashedSeed);
msg.writeVarInt(maxPlayers);
msg.writeVarInt(viewDistance);
msg.writeBoolean(reducedDebugInfo);
msg.writeBoolean(enableRespawnScreen);
msg.writeBoolean(isDebug);
msg.writeBoolean(isFlat);
}
if (version.fromTo(Version.V1_18, Version.V1_18_2)) {
msg.writeBoolean(isHardcore);
msg.writeByte(gameMode);
msg.writeByte(previousGameMode);
msg.writeStringsArray(worldNames);
if (version.moreOrEqual(Version.V1_18_2)) {
msg.writeCompoundTag(dimensionRegistry.getCodec_1_18_2());
msg.writeCompoundTag(dimensionRegistry.getDefaultDimension_1_18_2().getData());
} else {
msg.writeCompoundTag(dimensionRegistry.getCodec_1_16());
msg.writeCompoundTag(dimensionRegistry.getDefaultDimension_1_16().getData());
}
msg.writeString(worldName);
msg.writeLong(hashedSeed);
msg.writeVarInt(maxPlayers);
msg.writeVarInt(viewDistance);
msg.writeVarInt(viewDistance); // Simulation Distance
msg.writeBoolean(reducedDebugInfo);
msg.writeBoolean(enableRespawnScreen);
msg.writeBoolean(isDebug);
msg.writeBoolean(isFlat);
}
if (version.moreOrEqual(Version.V1_19)) {
msg.writeBoolean(isHardcore);
msg.writeByte(gameMode);
msg.writeByte(previousGameMode);
msg.writeStringsArray(worldNames);
if (version.moreOrEqual(Version.V1_19_1)) {
msg.writeCompoundTag(dimensionRegistry.getCodec_1_19_1());
}
else {
msg.writeCompoundTag(dimensionRegistry.getCodec_1_19());
}
msg.writeString(worldName); // World type
msg.writeString(worldName);
msg.writeLong(hashedSeed);
msg.writeVarInt(maxPlayers);
msg.writeVarInt(viewDistance);
msg.writeVarInt(viewDistance); // Simulation Distance
msg.writeBoolean(reducedDebugInfo);
msg.writeBoolean(enableRespawnScreen);
msg.writeBoolean(isDebug);
msg.writeBoolean(isFlat);
msg.writeBoolean(false);
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.play;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.Packet;
import ua.nanit.limbo.protocol.registry.Version;
public class PacketKeepAlive implements Packet {
private long id;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
@Override
public void encode(ByteMessage msg, Version version) {
if (version.moreOrEqual(Version.V1_12_2)) {
msg.writeLong(id);
} else if (version.moreOrEqual(Version.V1_8)) {
msg.writeVarInt((int) id);
} else {
msg.writeInt((int) id);
}
}
@Override
public void decode(ByteMessage msg, Version version) {
if (version.moreOrEqual(Version.V1_12_2)) {
this.id = msg.readLong();
} else if (version.moreOrEqual(Version.V1_8)) {
this.id = msg.readVarInt();
} else {
this.id = msg.readInt();
}
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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 PacketPlayerAbilities implements PacketOut {
private int flags = 0x02;
private float flyingSpeed = 0.0F;
private float fieldOfView = 0.1F;
public void setFlags(int flags) {
this.flags = flags;
}
public void setFlyingSpeed(float flyingSpeed) {
this.flyingSpeed = flyingSpeed;
}
public void setFieldOfView(float fieldOfView) {
this.fieldOfView = fieldOfView;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeByte(flags);
msg.writeFloat(flyingSpeed);
msg.writeFloat(fieldOfView);
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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;
import java.util.EnumSet;
import java.util.UUID;
/**
* This packet was very simplified and using only for ADD_PLAYER action
*/
public class PacketPlayerInfo implements PacketOut {
private int gameMode = 3;
private String username = "";
private UUID uuid;
public void setGameMode(int gameMode) {
this.gameMode = gameMode;
}
public void setUsername(String username) {
this.username = username;
}
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
@Override
public void encode(ByteMessage msg, Version version) {
if (version.less(Version.V1_8)) {
msg.writeString(username);
msg.writeBoolean(true); // Is online
msg.writeShort(0);
} else {
if (version.moreOrEqual(Version.V1_19_3)) {
EnumSet<Action> actions = EnumSet.noneOf(Action.class);
actions.add(Action.ADD_PLAYER);
actions.add(Action.UPDATE_LISTED);
actions.add(Action.UPDATE_GAMEMODE);
msg.writeEnumSet(actions, Action.class);
msg.writeVarInt(1); // Array length (1 element)
msg.writeUuid(uuid); // UUID
msg.writeString(username); //Username
msg.writeVarInt(0); //Properties (0 is empty)
msg.writeBoolean(true); //Update listed
msg.writeVarInt(gameMode); //Gamemode
return;
}
msg.writeVarInt(0); // Add player action
msg.writeVarInt(1);
msg.writeUuid(uuid);
msg.writeString(username);
msg.writeVarInt(0);
msg.writeVarInt(gameMode);
msg.writeVarInt(60);
msg.writeBoolean(false);
if (version.moreOrEqual(Version.V1_19)) {
msg.writeBoolean(false);
}
}
}
public static enum Action {
ADD_PLAYER,
INITIALIZE_CHAT,
UPDATE_GAMEMODE,
UPDATE_LISTED,
UPDATE_LATENCY,
UPDATE_DISPLAY_NAME;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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 PacketPlayerListHeader implements PacketOut {
private String header;
private String footer;
public void setHeader(String header) {
this.header = header;
}
public void setFooter(String footer) {
this.footer = footer;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeString(header);
msg.writeString(footer);
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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 PacketPlayerPositionAndLook implements PacketOut {
private double x;
private double y;
private double z;
private float yaw;
private float pitch;
private int teleportId;
public PacketPlayerPositionAndLook() {}
public PacketPlayerPositionAndLook(double x, double y, double z, float yaw, float pitch, int teleportId) {
this.x = x;
this.y = y;
this.z = z;
this.yaw = yaw;
this.pitch = pitch;
this.teleportId = teleportId;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeDouble(x);
msg.writeDouble(y + (version.less(Version.V1_8) ? 1.62F : 0));
msg.writeDouble(z);
msg.writeFloat(yaw);
msg.writeFloat(pitch);
if (version.moreOrEqual(Version.V1_8)) {
msg.writeByte(0x08);
} else {
msg.writeBoolean(true);
}
if (version.moreOrEqual(Version.V1_9)) {
msg.writeVarInt(teleportId);
}
if (version.moreOrEqual(Version.V1_17)) {
msg.writeBoolean(false); // Dismount vehicle
}
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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 PacketPluginMessage implements PacketOut {
private String channel;
private String message;
public void setChannel(String channel) {
this.channel = channel;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeString(channel);
msg.writeString(message);
}
}

View File

@@ -0,0 +1,30 @@
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 PacketSpawnPosition implements PacketOut {
private long x;
private long y;
private long z;
public PacketSpawnPosition() { }
public PacketSpawnPosition(long x, long y, long z) {
this.x = x;
this.y = y;
this.z = z;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeLong(encodePosition(x, y ,z));
msg.writeFloat(0);
}
private static long encodePosition(long x, long y, long z) {
return ((x & 0x3FFFFFF) << 38) | ((z & 0x3FFFFFF) << 12) | (y & 0xFFF);
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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;
import ua.nanit.limbo.server.data.Title;
public class PacketTitleLegacy implements PacketOut {
private Action action;
private final PacketTitleSetTitle title;
private final PacketTitleSetSubTitle subtitle;
private final PacketTitleTimes times;
public PacketTitleLegacy() {
this.title = new PacketTitleSetTitle();
this.subtitle = new PacketTitleSetSubTitle();
this.times = new PacketTitleTimes();
}
public void setAction(Action action) {
this.action = action;
}
public void setTitle(Title title) {
this.title.setTitle(title.getTitle());
this.subtitle.setSubtitle(title.getSubtitle());
this.times.setFadeIn(title.getFadeIn());
this.times.setStay(title.getStay());
this.times.setFadeOut(title.getFadeOut());
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeVarInt(action.getId(version));
switch (action) {
case SET_TITLE:
title.encode(msg, version);
break;
case SET_SUBTITLE:
subtitle.encode(msg, version);
break;
case SET_TIMES_AND_DISPLAY:
times.encode(msg, version);
break;
default:
throw new IllegalArgumentException("Invalid title action: " + action);
}
}
public enum Action {
SET_TITLE(0),
SET_SUBTITLE(1),
SET_TIMES_AND_DISPLAY(3, 2);
private final int id;
private final int legacyId;
Action(int id, int legacyId) {
this.id = id;
this.legacyId = legacyId;
}
Action(int id) {
this(id, id);
}
public int getId(Version version) {
return version.less(Version.V1_11) ? legacyId : id;
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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 PacketTitleSetSubTitle implements PacketOut {
private String subtitle;
public void setSubtitle(String subtitle) {
this.subtitle = subtitle;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeString(subtitle);
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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 PacketTitleSetTitle implements PacketOut {
private String title;
public void setTitle(String title) {
this.title = title;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeString(title);
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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 PacketTitleTimes implements PacketOut {
private int fadeIn;
private int stay;
private int fadeOut;
public void setFadeIn(int fadeIn) {
this.fadeIn = fadeIn;
}
public void setStay(int stay) {
this.stay = stay;
}
public void setFadeOut(int fadeOut) {
this.fadeOut = fadeOut;
}
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeInt(fadeIn);
msg.writeInt(stay);
msg.writeInt(fadeOut);
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.status;
import ua.nanit.limbo.connection.ClientConnection;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.Packet;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.LimboServer;
public class PacketStatusPing implements Packet {
private long randomId;
@Override
public void encode(ByteMessage msg, Version version) {
msg.writeLong(randomId);
}
@Override
public void decode(ByteMessage msg, Version version) {
this.randomId = msg.readLong();
}
@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,42 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.status;
import ua.nanit.limbo.connection.ClientConnection;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketIn;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.LimboServer;
public class PacketStatusRequest implements PacketIn {
@Override
public void decode(ByteMessage msg, Version version) {
}
@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,59 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.packets.status;
import ua.nanit.limbo.protocol.ByteMessage;
import ua.nanit.limbo.protocol.PacketOut;
import ua.nanit.limbo.protocol.registry.Version;
import ua.nanit.limbo.server.LimboServer;
public class PacketStatusResponse implements PacketOut {
private static final String TEMPLATE = "{ \"version\": { \"name\": \"%s\", \"protocol\": %d }, \"players\": { \"max\": %d, \"online\": %d, \"sample\": [] }, \"description\": %s }";
private LimboServer server;
public PacketStatusResponse() { }
public PacketStatusResponse(LimboServer server) {
this.server = server;
}
@Override
public void encode(ByteMessage msg, Version version) {
int protocol = server.getConfig().getInfoForwarding().isNone()
? version.getProtocolNumber()
: Version.getMax().getProtocolNumber();
String ver = server.getConfig().getPingData().getVersion();
String desc = server.getConfig().getPingData().getDescription();
msg.writeString(getResponseJson(ver, protocol,
server.getConfig().getMaxPlayers(),
server.getConnections().getCount(), desc));
}
@Override
public String toString() {
return getClass().getSimpleName();
}
private String getResponseJson(String version, int protocol, int maxPlayers, int online, String description) {
return String.format(TEMPLATE, version, protocol, maxPlayers, online, description);
}
}

View File

@@ -0,0 +1,359 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.registry;
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.status.PacketStatusPing;
import ua.nanit.limbo.protocol.packets.status.PacketStatusRequest;
import ua.nanit.limbo.protocol.packets.status.PacketStatusResponse;
import java.util.*;
import java.util.function.Supplier;
import static ua.nanit.limbo.protocol.registry.Version.*;
public enum State {
HANDSHAKING(0) {
{
serverBound.register(PacketHandshake::new,
map(0x00, Version.getMin(), Version.getMax())
);
}
},
STATUS(1) {
{
serverBound.register(PacketStatusRequest::new,
map(0x00, Version.getMin(), Version.getMax())
);
serverBound.register(PacketStatusPing::new,
map(0x01, Version.getMin(), Version.getMax())
);
clientBound.register(PacketStatusResponse::new,
map(0x00, Version.getMin(), Version.getMax())
);
clientBound.register(PacketStatusPing::new,
map(0x01, Version.getMin(), Version.getMax())
);
}
},
LOGIN(2) {
{
serverBound.register(PacketLoginStart::new,
map(0x00, Version.getMin(), Version.getMax())
);
serverBound.register(PacketLoginPluginResponse::new,
map(0x02, Version.getMin(), Version.getMax())
);
clientBound.register(PacketDisconnect::new,
map(0x00, Version.getMin(), Version.getMax())
);
clientBound.register(PacketLoginSuccess::new,
map(0x02, Version.getMin(), Version.getMax())
);
clientBound.register(PacketLoginPluginRequest::new,
map(0x04, Version.getMin(), Version.getMax())
);
}
},
PLAY(3) {
{
serverBound.register(PacketKeepAlive::new,
map(0x00, V1_7_2, V1_8),
map(0x0B, V1_9, V1_11_1),
map(0x0C, V1_12, V1_12),
map(0x0B, V1_12_1, V1_12_2),
map(0x0E, V1_13, V1_13_2),
map(0x0F, V1_14, V1_15_2),
map(0x10, V1_16, V1_16_4),
map(0x0F, V1_17, V1_18_2),
map(0x11, V1_19, V1_19),
map(0x12, V1_19_1, V1_19_1),
map(0x11, V1_19_3, V1_19_3)
);
clientBound.register(PacketDeclareCommands::new,
map(0x11, V1_13, V1_14_4),
map(0x12, V1_15, V1_15_2),
map(0x11, V1_16, V1_16_1),
map(0x10, V1_16_2, V1_16_4),
map(0x12, V1_17, V1_18_2),
map(0x0F, V1_19, V1_19_1),
map(0x0E, V1_19_3, V1_19_3)
);
clientBound.register(PacketJoinGame::new,
map(0x01, V1_7_2, V1_8),
map(0x23, V1_9, V1_12_2),
map(0x25, V1_13, V1_14_4),
map(0x26, V1_15, V1_15_2),
map(0x25, V1_16, V1_16_1),
map(0x24, V1_16_2, V1_16_4),
map(0x26, V1_17, V1_18_2),
map(0x23, V1_19, V1_19),
map(0x25, V1_19_1, V1_19_1),
map(0x24, V1_19_3, V1_19_3)
);
clientBound.register(PacketPluginMessage::new,
map(0x19, V1_13, V1_13_2),
map(0x18, V1_14, V1_14_4),
map(0x19, V1_15, V1_15_2),
map(0x18, V1_16, V1_16_1),
map(0x17, V1_16_2, V1_16_4),
map(0x18, V1_17, V1_18_2),
map(0x15, V1_19, V1_19),
map(0x16, V1_19_1, V1_19_1),
map(0x15, V1_19_3, V1_19_3)
);
clientBound.register(PacketPlayerAbilities::new,
map(0x39, V1_7_2, V1_8),
map(0x2B, V1_9, V1_12),
map(0x2C, V1_12_1, V1_12_2),
map(0x2E, V1_13, V1_13_2),
map(0x31, V1_14, V1_14_4),
map(0x32, V1_15, V1_15_2),
map(0x31, V1_16, V1_16_1),
map(0x30, V1_16_2, V1_16_4),
map(0x32, V1_17, V1_18_2),
map(0x2F, V1_19, V1_19),
map(0x31, V1_19_1, V1_19_1),
map(0x30, V1_19_3, V1_19_3)
);
clientBound.register(PacketPlayerPositionAndLook::new,
map(0x08, V1_7_2, V1_8),
map(0x2E, V1_9, V1_12),
map(0x2F, V1_12_1, V1_12_2),
map(0x32, V1_13, V1_13_2),
map(0x35, V1_14, V1_14_4),
map(0x36, V1_15, V1_15_2),
map(0x35, V1_16, V1_16_1),
map(0x34, V1_16_2, V1_16_4),
map(0x38, V1_17, V1_18_2),
map(0x36, V1_19, V1_19),
map(0x39, V1_19_1, V1_19_1),
map(0x38, V1_19_3, V1_19_3)
);
clientBound.register(PacketKeepAlive::new,
map(0x00, V1_7_2, V1_8),
map(0x1F, V1_9, V1_12_2),
map(0x21, V1_13, V1_13_2),
map(0x20, V1_14, V1_14_4),
map(0x21, V1_15, V1_15_2),
map(0x20, V1_16, V1_16_1),
map(0x1F, V1_16_2, V1_16_4),
map(0x21, V1_17, V1_18_2),
map(0x1E, V1_19, V1_19),
map(0x20, V1_19_1, V1_19_1),
map(0x1F, V1_19_3, V1_19_3)
);
clientBound.register(PacketChatMessage::new,
map(0x02, V1_7_2, V1_8),
map(0x0F, V1_9, V1_12_2),
map(0x0E, V1_13, V1_14_4),
map(0x0F, V1_15, V1_15_2),
map(0x0E, V1_16, V1_16_4),
map(0x0F, V1_17, V1_18_2),
map(0x5F, V1_19, V1_19),
map(0x62, V1_19_1, V1_19_1),
map(0x60, V1_19_3, V1_19_3)
);
clientBound.register(PacketBossBar::new,
map(0x0C, V1_9, V1_14_4),
map(0x0D, V1_15, V1_15_2),
map(0x0C, V1_16, V1_16_4),
map(0x0D, V1_17, V1_18_2),
map(0x0A, V1_19, V1_19_3)
);
clientBound.register(PacketPlayerInfo::new,
map(0x38, V1_7_2, V1_8),
map(0x2D, V1_9, V1_12),
map(0x2E, V1_12_1, V1_12_2),
map(0x30, V1_13, V1_13_2),
map(0x33, V1_14, V1_14_4),
map(0x34, V1_15, V1_15_2),
map(0x33, V1_16, V1_16_1),
map(0x32, V1_16_2, V1_16_4),
map(0x36, V1_17, V1_18_2),
map(0x34, V1_19, V1_19),
map(0x37, V1_19_1, V1_19_1),
map(0x36, V1_19_3, V1_19_3)
);
clientBound.register(PacketTitleLegacy::new,
map(0x45, V1_8, V1_11_1),
map(0x47, V1_12, V1_12),
map(0x48, V1_12_1, V1_12_2),
map(0x4B, V1_13, V1_13_2),
map(0x4F, V1_14, V1_14_4),
map(0x50, V1_15, V1_15_2),
map(0x4F, V1_16, V1_16_4)
);
clientBound.register(PacketTitleSetTitle::new,
map(0x59, V1_17, V1_17_1),
map(0x5A, V1_18, V1_19),
map(0x5D, V1_19_1, V1_19_1),
map(0x5B, V1_19_3, V1_19_3)
);
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)
);
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)
);
clientBound.register(PacketPlayerListHeader::new,
map(0x47, V1_8, V1_8),
map(0x48, V1_9, V1_9_2),
map(0x47, V1_9_4, V1_11_1),
map(0x49, V1_12, V1_12),
map(0x4A, V1_12_1, V1_12_2),
map(0x4E, V1_13, V1_13_2),
map(0x53, V1_14, V1_14_4),
map(0x54, V1_15, V1_15_2),
map(0x53, V1_16, V1_16_4),
map(0x5E, V1_17, V1_17_1),
map(0x5F, V1_18, V1_18_2),
map(0x60, V1_19, V1_19),
map(0x63, V1_19_1, V1_19_1),
map(0x61, V1_19_3, V1_19_3)
);
clientBound.register(PacketSpawnPosition::new,
map(0x4C, V1_19_3, V1_19_3)
);
}
};
private static final Map<Integer, State> STATE_BY_ID = new HashMap<>();
static {
for (State registry : values()) {
STATE_BY_ID.put(registry.stateId, registry);
}
}
private final int stateId;
public final ProtocolMappings serverBound = new ProtocolMappings();
public final ProtocolMappings clientBound = new ProtocolMappings();
State(int stateId) {
this.stateId = stateId;
}
public static State getById(int stateId) {
return STATE_BY_ID.get(stateId);
}
public static class ProtocolMappings {
private final Map<Version, PacketRegistry> registry = new HashMap<>();
public PacketRegistry getRegistry(Version version) {
return registry.getOrDefault(version, registry.get(getMin()));
}
public void register(Supplier<?> packet, Mapping... mappings) {
for (Mapping mapping : mappings) {
for (Version ver : getRange(mapping)) {
PacketRegistry reg = registry.computeIfAbsent(ver, PacketRegistry::new);
reg.register(mapping.packetId, packet);
}
}
}
private Collection<Version> getRange(Mapping mapping) {
Version from = mapping.from;
Version curr = mapping.to;
if (curr == from)
return Collections.singletonList(from);
List<Version> versions = new LinkedList<>();
while (curr != from) {
versions.add(curr);
curr = curr.getPrev();
}
versions.add(from);
return versions;
}
}
public static class PacketRegistry {
private final Version version;
private final Map<Integer, Supplier<?>> packetsById = new HashMap<>();
private final Map<Class<?>, Integer> packetIdByClass = new HashMap<>();
public PacketRegistry(Version version) {
this.version = version;
}
public Version getVersion() {
return version;
}
public Packet getPacket(int packetId) {
Supplier<?> supplier = packetsById.get(packetId);
return supplier == null ? null : (Packet) supplier.get();
}
public int getPacketId(Class<?> packetClass) {
return packetIdByClass.getOrDefault(packetClass, -1);
}
public void register(int packetId, Supplier<?> supplier) {
packetsById.put(packetId, supplier);
packetIdByClass.put(supplier.get().getClass(), packetId);
}
}
private static class Mapping {
private final int packetId;
private final Version from;
private final Version to;
public Mapping(int packetId, Version from, Version to) {
this.from = from;
this.to = to;
this.packetId = packetId;
}
}
/**
* Map packet id to version range
* @param packetId Packet id
* @param from Minimal version (include)
* @param to Last version (include)
* @return Created mapping
*/
private static Mapping map(int packetId, Version from, Version to) {
return new Mapping(packetId, from, to);
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.protocol.registry;
import java.util.HashMap;
import java.util.Map;
public enum Version {
UNDEFINED(-1),
V1_7_2(4),
// 1.7.2-1.7.5 has same protocol numbers
V1_7_6(5),
// 1.7.6-1.7.10 has same protocol numbers
V1_8(47),
// 1.8-1.8.8 has same protocol numbers
V1_9(107),
V1_9_1(108),
V1_9_2(109),
V1_9_4(110),
V1_10(210),
// 1.10-1.10.2 has same protocol numbers
V1_11(315),
V1_11_1(316),
// 1.11.2 has same protocol number
V1_12(335),
V1_12_1(338),
V1_12_2(340),
V1_13(393),
V1_13_1(401),
V1_13_2(404),
V1_14(477),
V1_14_1(480),
V1_14_2(485),
V1_14_3(490),
V1_14_4(498),
V1_15(573),
V1_15_1(575),
V1_15_2(578),
V1_16(735),
V1_16_1(736),
V1_16_2(751),
V1_16_3(753),
V1_16_4(754),
// 1.16.5 has same protocol number
V1_17(755),
V1_17_1(756),
V1_18(757),
// 1.18.1 has same protocol number
V1_18_2(758),
V1_19(759),
V1_19_1(760),
V1_19_3(761);
private static final Map<Integer, Version> VERSION_MAP;
private static final Version MAX;
static {
Version[] values = values();
VERSION_MAP = new HashMap<>();
MAX = values[values.length - 1];
Version last = null;
for (Version version : values) {
version.prev = last;
last = version;
VERSION_MAP.put(version.getProtocolNumber(), version);
}
}
private final int protocolNumber;
private Version prev;
Version(int protocolNumber) {
this.protocolNumber = protocolNumber;
}
public int getProtocolNumber() {
return this.protocolNumber;
}
public Version getPrev() {
return prev;
}
public boolean more(Version another) {
return this.protocolNumber > another.protocolNumber;
}
public boolean moreOrEqual(Version another) {
return this.protocolNumber >= another.protocolNumber;
}
public boolean less(Version another) {
return this.protocolNumber < another.protocolNumber;
}
public boolean lessOrEqual(Version another) {
return this.protocolNumber <= another.protocolNumber;
}
public boolean fromTo(Version min, Version max) {
return this.protocolNumber >= min.protocolNumber && this.protocolNumber <= max.protocolNumber;
}
public boolean isSupported() {
return this != UNDEFINED;
}
public static Version getMin() {
return V1_7_2;
}
public static Version getMax() {
return MAX;
}
public static Version of(int protocolNumber) {
return VERSION_MAP.getOrDefault(protocolNumber, UNDEFINED);
}
}

View File

@@ -0,0 +1,9 @@
package ua.nanit.limbo.server;
public interface Command {
void execute();
String description();
}

View File

@@ -0,0 +1,59 @@
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 java.util.*;
public final class CommandManager extends Thread {
private final Map<String, Command> commands = new HashMap<>();
public Map<String, Command> getCommands() {
return Collections.unmodifiableMap(commands);
}
public Command getCommand(String name) {
return commands.get(name.toLowerCase());
}
public void register(String name, Command cmd) {
commands.put(name.toLowerCase(), cmd);
}
@Override
public void run() {
Scanner scanner = new Scanner(System.in);
String command;
while (true) {
try {
command = scanner.nextLine().trim();
} catch (NoSuchElementException e) {
break;
}
Command handler = getCommand(command);
if (handler != null) {
try {
handler.execute();
} catch (Throwable t) {
Logger.error("Cannot execute command:", t);
}
continue;
}
Logger.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());
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.server;
import ua.nanit.limbo.connection.ClientConnection;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public final class Connections {
private final Map<UUID, ClientConnection> connections;
public Connections() {
connections = new ConcurrentHashMap<>();
}
public Collection<ClientConnection> getAllConnections() {
return Collections.unmodifiableCollection(connections.values());
}
public int getCount() {
return connections.size();
}
public void addConnection(ClientConnection connection) {
connections.put(connection.getUuid(), connection);
Logger.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());
}
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.ServerChannel;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.ResourceLeakDetector;
import ua.nanit.limbo.configuration.LimboConfig;
import ua.nanit.limbo.connection.ClientChannelInitializer;
import ua.nanit.limbo.connection.ClientConnection;
import ua.nanit.limbo.connection.PacketHandler;
import ua.nanit.limbo.connection.PacketSnapshots;
import ua.nanit.limbo.world.DimensionRegistry;
import java.nio.file.Paths;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public final class LimboServer {
private LimboConfig config;
private PacketHandler packetHandler;
private Connections connections;
private DimensionRegistry dimensionRegistry;
private ScheduledFuture<?> keepAliveTask;
private EventLoopGroup bossGroup;
private EventLoopGroup workerGroup;
private CommandManager commandManager;
public LimboConfig getConfig() {
return config;
}
public PacketHandler getPacketHandler() {
return packetHandler;
}
public Connections getConnections() {
return connections;
}
public DimensionRegistry getDimensionRegistry() {
return dimensionRegistry;
}
public CommandManager getCommandManager() {
return commandManager;
}
public void start() throws Exception {
Logger.info("Starting server...");
ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED);
config = new LimboConfig(Paths.get("./"));
config.load();
packetHandler = new PacketHandler(this);
dimensionRegistry = new DimensionRegistry(this);
dimensionRegistry.load(config.getDimensionType());
connections = new Connections();
PacketSnapshots.initPackets(this);
startBootstrap();
keepAliveTask = workerGroup.scheduleAtFixedRate(this::broadcastKeepAlive, 0L, 5L, TimeUnit.SECONDS);
Runtime.getRuntime().addShutdownHook(new Thread(this::stop, "NanoLimbo shutdown thread"));
Logger.info("Server started on %s", config.getAddress());
Logger.setLevel(config.getDebugLevel());
commandManager = new CommandManager();
commandManager.registerAll(this);
commandManager.start();
System.gc();
}
private void startBootstrap() {
Class<? extends ServerChannel> channelClass;
if (config.isUseEpoll() && Epoll.isAvailable()) {
bossGroup = new EpollEventLoopGroup(config.getBossGroupSize());
workerGroup = new EpollEventLoopGroup(config.getWorkerGroupSize());
channelClass = EpollServerSocketChannel.class;
Logger.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");
}
new ServerBootstrap()
.group(bossGroup, workerGroup)
.channel(channelClass)
.childHandler(new ClientChannelInitializer(this))
.childOption(ChannelOption.TCP_NODELAY, true)
.localAddress(config.getAddress())
.bind();
}
private void broadcastKeepAlive() {
connections.getAllConnections().forEach(ClientConnection::sendKeepAlive);
}
private void stop() {
Logger.info("Stopping server...");
if (keepAliveTask != null) {
keepAliveTask.cancel(true);
}
if (bossGroup != null) {
bossGroup.shutdownGracefully();
}
if (workerGroup != null) {
workerGroup.shutdownGracefully();
}
Logger.info("Server stopped, Goodbye!");
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.server;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
public final class Logger {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("hh:mm:ss");
private static int debugLevel = Level.INFO.getIndex();
private Logger() {}
public static int getLevel() {
return debugLevel;
}
public static void info(Object msg, Object... args) {
print(Level.INFO, msg, null, args);
}
public static void debug(Object msg, Object... args) {
print(Level.DEBUG, msg, null, args);
}
public static void warning(Object msg, Object... args) {
print(Level.WARNING, msg, null, args);
}
public static void warning(Object msg, Throwable t, Object... args) {
print(Level.WARNING, msg, t, args);
}
public static void error(Object msg, Object... args) {
print(Level.ERROR, msg, null, args);
}
public static void error(Object msg, Throwable t, Object... args) {
print(Level.ERROR, msg, t, args);
}
public static void print(Level level, Object msg, Throwable t, Object... args) {
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);
}
static void setLevel(int level) {
debugLevel = level;
}
public enum Level {
ERROR("ERROR", 0),
WARNING("WARNING", 1),
INFO("INFO", 2),
DEBUG("DEBUG", 3);
private final String display;
private final int index;
Level(String display, int index) {
this.display = display;
this.index = index;
}
public String getDisplay() {
return display;
}
public int getIndex() {
return index;
}
}
}

View File

@@ -0,0 +1,24 @@
package ua.nanit.limbo.server.commands;
import ua.nanit.limbo.server.Command;
import ua.nanit.limbo.server.LimboServer;
import ua.nanit.limbo.server.Logger;
public class CmdConn implements Command {
private final LimboServer server;
public CmdConn(LimboServer server) {
this.server = server;
}
@Override
public void execute() {
Logger.info("Connections: %d", server.getConnections().getCount());
}
@Override
public String description() {
return "Display connections count";
}
}

View File

@@ -0,0 +1,32 @@
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 java.util.Map;
public class CmdHelp implements Command {
private final LimboServer server;
public CmdHelp(LimboServer server) {
this.server = server;
}
@Override
public void execute() {
Map<String, Command> commands = server.getCommandManager().getCommands();
Logger.info("Available commands:");
for (Map.Entry<String, Command> entry : commands.entrySet()) {
Logger.info("%s - %s", entry.getKey(), entry.getValue().description());
}
}
@Override
public String description() {
return "Show this message";
}
}

View File

@@ -0,0 +1,28 @@
package ua.nanit.limbo.server.commands;
import ua.nanit.limbo.server.Command;
import ua.nanit.limbo.server.Logger;
public class CmdMem implements Command {
@Override
public void execute() {
Runtime runtime = Runtime.getRuntime();
long mb = 1024 * 1024;
long used = (runtime.totalMemory() - runtime.freeMemory()) / mb;
long total = runtime.totalMemory() / mb;
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);
}
@Override
public String description() {
return "Display memory usage";
}
}

View File

@@ -0,0 +1,16 @@
package ua.nanit.limbo.server.commands;
import ua.nanit.limbo.server.Command;
public class CmdStop implements Command {
@Override
public void execute() {
System.exit(0);
}
@Override
public String description() {
return "Stop the server";
}
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.server.data;
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.util.Colors;
import java.lang.reflect.Type;
public class BossBar {
private String text;
private float health;
private Color color;
private Division division;
public String getText() {
return text;
}
public float getHealth() {
return health;
}
public Color getColor() {
return color;
}
public Division getDivision() {
return division;
}
public void setText(String text) {
this.text = text;
}
public void setHealth(float health) {
this.health = health;
}
public void setColor(Color color) {
this.color = color;
}
public void setDivision(Division division) {
this.division = division;
}
public enum Color {
PINK(0),
BLUE(1),
RED(2),
GREEN(3),
YELLOW(4),
PURPLE(5),
WHITE(6);
private final int index;
Color(int index) {
this.index = index;
}
public int getIndex() {
return index;
}
}
public enum Division {
SOLID(0),
DASHES_6(1),
DASHES_10(2),
DASHES_12(3),
DASHES_20(4);
private final int index;
Division(int index) {
this.index = index;
}
public int getIndex() {
return index;
}
}
public static class Serializer implements TypeSerializer<BossBar> {
@Override
public BossBar deserialize(Type type, ConfigurationNode node) throws SerializationException {
BossBar bossBar = new BossBar();
bossBar.setText(Colors.of(node.node("text").getString("")));
bossBar.setHealth(node.node("health").getFloat());
if (bossBar.getHealth() < 0 || bossBar.getHealth() > 1)
throw new SerializationException("BossBar health value must be between 0.0 and 1.0");
try {
bossBar.setColor(Color.valueOf(node.node("color").getString("").toUpperCase()));
} catch (IllegalArgumentException e) {
throw new SerializationException("Invalid bossbar color");
}
try {
bossBar.setDivision(Division.valueOf(node.node("division").getString("").toUpperCase()));
} catch (IllegalArgumentException e) {
throw new SerializationException("Invalid bossbar division");
}
return bossBar;
}
@Override
public void serialize(Type type, @Nullable BossBar obj, ConfigurationNode node) {
}
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.server.data;
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 java.nio.charset.StandardCharsets;
import java.util.List;
public class InfoForwarding {
private Type type;
private byte[] secretKey;
private List<String> tokens;
public Type getType() {
return type;
}
public byte[] getSecretKey() {
return secretKey;
}
public List<String> getTokens() {
return tokens;
}
public boolean hasToken(String token) {
return tokens != null && token != null && tokens.contains(token);
}
public boolean isNone() {
return type == Type.NONE;
}
public boolean isLegacy() {
return type == Type.LEGACY;
}
public boolean isModern() {
return type == Type.MODERN;
}
public boolean isBungeeGuard() {
return type == Type.BUNGEE_GUARD;
}
public enum Type {
NONE,
LEGACY,
MODERN,
BUNGEE_GUARD
}
public static class Serializer implements TypeSerializer<InfoForwarding> {
@Override
public InfoForwarding deserialize(java.lang.reflect.Type type, ConfigurationNode node) throws SerializationException {
InfoForwarding forwarding = new InfoForwarding();
try {
forwarding.type = Type.valueOf(node.node("type").getString("").toUpperCase());
} catch (IllegalArgumentException e) {
throw new SerializationException("Undefined info forwarding type");
}
if (forwarding.type == Type.MODERN) {
forwarding.secretKey = node.node("secret").getString("").getBytes(StandardCharsets.UTF_8);
}
if (forwarding.type == Type.BUNGEE_GUARD) {
forwarding.tokens = node.node("tokens").getList(String.class);
}
return forwarding;
}
@Override
public void serialize(java.lang.reflect.Type type, @Nullable InfoForwarding obj, ConfigurationNode node) throws SerializationException {
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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.util.Colors;
import java.lang.reflect.Type;
public class PingData {
private String version;
private String description;
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public static class Serializer implements TypeSerializer<PingData> {
@Override
public PingData deserialize(Type type, ConfigurationNode node) {
PingData pingData = new PingData();
pingData.setDescription(Colors.of(node.node("description").getString("")));
pingData.setVersion(Colors.of(node.node("version").getString("")));
return pingData;
}
@Override
public void serialize(Type type, @Nullable PingData obj, ConfigurationNode node) {
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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.util.Colors;
import java.lang.reflect.Type;
public class Title {
private String title;
private String subtitle;
private int fadeIn;
private int stay;
private int fadeOut;
public String getTitle() {
return title;
}
public String getSubtitle() {
return subtitle;
}
public int getFadeIn() {
return fadeIn;
}
public int getStay() {
return stay;
}
public int getFadeOut() {
return fadeOut;
}
public void setTitle(String title) {
this.title = title;
}
public void setSubtitle(String subtitle) {
this.subtitle = subtitle;
}
public void setFadeIn(int fadeIn) {
this.fadeIn = fadeIn;
}
public void setStay(int stay) {
this.stay = stay;
}
public void setFadeOut(int fadeOut) {
this.fadeOut = fadeOut;
}
public static class Serializer implements TypeSerializer<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.setFadeIn(node.node("fadeIn").getInt(10));
title.setStay(node.node("stay").getInt(100));
title.setFadeOut(node.node("fadeOut").getInt(10));
return title;
}
@Override
public void serialize(Type type, @Nullable Title obj, ConfigurationNode node) {
// Not used
}
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.util;
public final class Colors {
private static final char CHAR_FROM = '&';
private static final char CHAR_TO = '\u00a7';
private Colors() {
}
public static String of(String text) {
if (text == null) return null;
return text.replace(CHAR_FROM, CHAR_TO);
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.util;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
public final class UuidUtil {
private UuidUtil() {}
public static UUID getOfflineModeUuid(String username) {
return UUID.nameUUIDFromBytes(("OfflinePlayer:" + username)
.getBytes(StandardCharsets.UTF_8));
}
public static UUID fromString(String str) {
if(str.contains("-")) return UUID.fromString(str);
return UUID.fromString(str.replaceFirst("(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", "$1-$2-$3-$4-$5"));
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.world;
import net.kyori.adventure.nbt.CompoundBinaryTag;
public class Dimension {
private final int id;
private final String name;
private final CompoundBinaryTag data;
public Dimension(int id, String name, CompoundBinaryTag data) {
this.id = id;
this.name = name;
this.data = data;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public CompoundBinaryTag getData() {
return data;
}
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright (C) 2020 Nan1t
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package ua.nanit.limbo.world;
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 java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
public final class DimensionRegistry {
private final LimboServer server;
private Dimension defaultDimension_1_16;
private Dimension defaultDimension_1_18_2;
private CompoundBinaryTag codec_1_16;
private CompoundBinaryTag codec_1_18_2;
private CompoundBinaryTag codec_1_19;
private CompoundBinaryTag codec_1_19_1;
private CompoundBinaryTag oldCodec;
public DimensionRegistry(LimboServer server) {
this.server = server;
}
public CompoundBinaryTag getCodec_1_16() {
return codec_1_16;
}
public CompoundBinaryTag getCodec_1_18_2() {
return codec_1_18_2;
}
public CompoundBinaryTag getCodec_1_19() {
return codec_1_19;
}
public CompoundBinaryTag getCodec_1_19_1() {
return codec_1_19_1;
}
public CompoundBinaryTag getOldCodec() {
return oldCodec;
}
public Dimension getDefaultDimension_1_16() {
return defaultDimension_1_16;
}
public Dimension getDefaultDimension_1_18_2() {
return defaultDimension_1_18_2;
}
public void load(String def) throws IOException {
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");
// 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_18_2 = getDefaultDimension(def, codec_1_18_2);
}
private Dimension getDefaultDimension(String def, CompoundBinaryTag tag) {
ListBinaryTag dimensions = tag.getCompound("minecraft:dimension_type").getList("value");
CompoundBinaryTag overWorld = (CompoundBinaryTag) ((CompoundBinaryTag) dimensions.get(0)).get("element");
CompoundBinaryTag nether = (CompoundBinaryTag) ((CompoundBinaryTag) dimensions.get(2)).get("element");
CompoundBinaryTag theEnd = (CompoundBinaryTag) ((CompoundBinaryTag) dimensions.get(3)).get("element");
switch (def.toLowerCase()) {
case "overworld":
return new Dimension(0, "minecraft:overworld", overWorld);
case "the_nether":
return new Dimension(-1, "minecraft:nether", nether);
case "the_end":
return new Dimension(1, "minecraft:the_end", theEnd);
default:
Logger.warning("Undefined dimension type: '%s'. Using THE_END as default", def);
return new Dimension(1, "minecraft:the_end", theEnd);
}
}
private CompoundBinaryTag readCodecFile(String resPath) throws IOException {
InputStream in = server.getClass().getResourceAsStream(resPath);
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;
}
}