diff --git a/native/compile-linux.sh b/native/compile-linux.sh index 6d92b6462..49c621af9 100755 --- a/native/compile-linux.sh +++ b/native/compile-linux.sh @@ -7,10 +7,10 @@ fi echo "Compiling libdeflate..." cd libdeflate || exit -CFLAGS="-fPIC -O2" make +CFLAGS="-fPIC -O2 -fomit-frame-pointer" make cd .. -CFLAGS="-O3 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/linux/ -fPIC -shared -Wl,-z,noexecstack" +CFLAGS="-O2 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/linux/ -fPIC -shared -Wl,-z,noexecstack -fomit-frame-pointer" ARCH=$(uname -m) mkdir -p src/main/resources/linux_$ARCH gcc $CFLAGS -Ilibdeflate src/main/c/jni_util.c src/main/c/jni_zlib_deflate.c src/main/c/jni_zlib_inflate.c \ diff --git a/native/src/main/java/com/velocitypowered/natives/compression/LibdeflateVelocityCompressor.java b/native/src/main/java/com/velocitypowered/natives/compression/LibdeflateVelocityCompressor.java index 19dead4ac..e72d92efc 100644 --- a/native/src/main/java/com/velocitypowered/natives/compression/LibdeflateVelocityCompressor.java +++ b/native/src/main/java/com/velocitypowered/natives/compression/LibdeflateVelocityCompressor.java @@ -63,6 +63,23 @@ public class LibdeflateVelocityCompressor implements VelocityCompressor { } } + @Override + public boolean deflateSingle(ByteBuf source, ByteBuf destination) throws DataFormatException { + ensureNotDisposed(); + + long sourceAddress = source.memoryAddress() + source.readerIndex(); + long destinationAddress = destination.memoryAddress() + destination.writerIndex(); + + int produced = deflate.process(deflateCtx, sourceAddress, source.readableBytes(), + destinationAddress, destination.writableBytes()); + if (produced > 0) { + destination.writerIndex(destination.writerIndex() + produced); + return true; + } + + return false; + } + private void ensureNotDisposed() { Preconditions.checkState(!disposed, "Object already disposed"); } diff --git a/native/src/main/java/com/velocitypowered/natives/compression/VelocityCompressor.java b/native/src/main/java/com/velocitypowered/natives/compression/VelocityCompressor.java index 09cad102c..997b234bc 100644 --- a/native/src/main/java/com/velocitypowered/natives/compression/VelocityCompressor.java +++ b/native/src/main/java/com/velocitypowered/natives/compression/VelocityCompressor.java @@ -14,4 +14,9 @@ public interface VelocityCompressor extends Disposable, Native { throws DataFormatException; void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException; + + default boolean deflateSingle(ByteBuf source, ByteBuf destination) throws DataFormatException { + deflate(source, destination); + return true; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 54c446a88..3c972be0b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -15,6 +15,7 @@ import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerInfo; +import com.velocitypowered.api.proxy.server.ServerPing; import com.velocitypowered.api.util.Favicon; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.ProxyVersion; @@ -34,8 +35,11 @@ import com.velocitypowered.proxy.plugin.VelocityEventManager; import com.velocitypowered.proxy.plugin.VelocityPluginManager; import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packet.Chat; -import com.velocitypowered.proxy.protocol.util.FaviconSerializer; +import com.velocitypowered.proxy.protocol.util.ping.FaviconSerializer; import com.velocitypowered.proxy.protocol.util.GameProfileSerializer; +import com.velocitypowered.proxy.protocol.util.ping.PlayersSerializer; +import com.velocitypowered.proxy.protocol.util.ping.ServerPingSerializer; +import com.velocitypowered.proxy.protocol.util.ping.VersionSerializer; import com.velocitypowered.proxy.scheduler.VelocityScheduler; import com.velocitypowered.proxy.server.ServerMap; import com.velocitypowered.proxy.util.AddressUtil; @@ -77,8 +81,6 @@ import java.util.stream.Collectors; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.audience.ForwardingAudience; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.TextComponent; -import net.kyori.adventure.text.TranslatableComponent; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.asynchttpclient.AsyncHttpClient; @@ -91,20 +93,26 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { private static final Logger logger = LogManager.getLogger(VelocityServer.class); public static final Gson GENERAL_GSON = new GsonBuilder() - .registerTypeHierarchyAdapter(Favicon.class, FaviconSerializer.INSTANCE) + .registerTypeAdapter(Favicon.class, FaviconSerializer.INSTANCE) .registerTypeHierarchyAdapter(GameProfile.class, GameProfileSerializer.INSTANCE) .create(); private static final Gson PRE_1_16_PING_SERIALIZER = ProtocolUtils .getJsonChatSerializer(ProtocolVersion.MINECRAFT_1_15_2) .serializer() .newBuilder() - .registerTypeHierarchyAdapter(Favicon.class, FaviconSerializer.INSTANCE) + .registerTypeAdapter(Favicon.class, FaviconSerializer.INSTANCE) + .registerTypeAdapter(ServerPing.class, ServerPingSerializer.INSTANCE) + .registerTypeAdapter(ServerPing.Version.class, VersionSerializer.INSTANCE) + .registerTypeAdapter(ServerPing.Players.class, PlayersSerializer.INSTANCE) .create(); private static final Gson POST_1_16_PING_SERIALIZER = ProtocolUtils .getJsonChatSerializer(ProtocolVersion.MINECRAFT_1_16) .serializer() .newBuilder() - .registerTypeHierarchyAdapter(Favicon.class, FaviconSerializer.INSTANCE) + .registerTypeAdapter(Favicon.class, FaviconSerializer.INSTANCE) + .registerTypeAdapter(ServerPing.class, ServerPingSerializer.INSTANCE) + .registerTypeAdapter(ServerPing.Version.class, VersionSerializer.INSTANCE) + .registerTypeAdapter(ServerPing.Players.class, PlayersSerializer.INSTANCE) .create(); private final ConnectionManager cm; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressEncoder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressEncoder.java index a3e36f4cc..b2640a7a8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressEncoder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftCompressEncoder.java @@ -21,14 +21,20 @@ public class MinecraftCompressEncoder extends MessageToByteEncoder { protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception { int uncompressed = msg.readableBytes(); if (uncompressed <= threshold) { - // Under the threshold, there is nothing to do. + // Under the threshold, only indicate the message was too small ProtocolUtils.writeVarInt(out, 0); out.writeBytes(msg); } else { ProtocolUtils.writeVarInt(out, uncompressed); ByteBuf compatibleIn = MoreByteBufUtils.ensureCompatible(ctx.alloc(), compressor, msg); try { - compressor.deflate(compatibleIn, out); + if (!compressor.deflateSingle(compatibleIn, out)) { + // The packet would have been too big. Clear the output buffer and use an uncompressed + // packet instead. + out.clear(); + ProtocolUtils.writeVarInt(out, 0); + out.writeBytes(msg); + } } finally { compatibleIn.release(); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/FaviconSerializer.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/FaviconSerializer.java deleted file mode 100644 index a67862323..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/FaviconSerializer.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.velocitypowered.proxy.protocol.util; - -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; -import com.velocitypowered.api.util.Favicon; -import java.lang.reflect.Type; - -public final class FaviconSerializer implements JsonSerializer, JsonDeserializer { - - public static final FaviconSerializer INSTANCE = new FaviconSerializer(); - - private FaviconSerializer() { - - } - - @Override - public Favicon deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { - return new Favicon(json.getAsString()); - } - - @Override - public JsonElement serialize(Favicon src, Type typeOfSrc, JsonSerializationContext context) { - return new JsonPrimitive(src.getBase64Url()); - } -} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/ping/FaviconSerializer.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/ping/FaviconSerializer.java new file mode 100644 index 000000000..30651dea2 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/ping/FaviconSerializer.java @@ -0,0 +1,26 @@ +package com.velocitypowered.proxy.protocol.util.ping; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import com.velocitypowered.api.util.Favicon; +import java.io.IOException; + +public final class FaviconSerializer extends TypeAdapter { + + public static final FaviconSerializer INSTANCE = new FaviconSerializer(); + + private FaviconSerializer() { + + } + + @Override + public void write(JsonWriter writer, Favicon favicon) throws IOException { + writer.value(favicon.getBase64Url()); + } + + @Override + public Favicon read(JsonReader reader) throws IOException { + return new Favicon(reader.nextString()); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/ping/PlayersSerializer.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/ping/PlayersSerializer.java new file mode 100644 index 000000000..39ad5dfd0 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/ping/PlayersSerializer.java @@ -0,0 +1,45 @@ +package com.velocitypowered.proxy.protocol.util.ping; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.reflect.TypeToken; +import com.velocitypowered.api.proxy.server.ServerPing.Players; +import com.velocitypowered.api.proxy.server.ServerPing.SamplePlayer; +import java.lang.reflect.Type; +import java.util.List; + +public class PlayersSerializer implements JsonSerializer, JsonDeserializer { + + public static final PlayersSerializer INSTANCE = new PlayersSerializer(); + + private PlayersSerializer() { + + } + + @Override + public Players deserialize(JsonElement elem, Type type, + JsonDeserializationContext ctx) throws JsonParseException { + JsonObject object = new JsonObject(); + int online = object.get("online").getAsInt(); + int max = object.get("max").getAsInt(); + List sample = ctx.deserialize(object.get("sample"), + new TypeToken>() {}.getType()); + return new Players(online, max, sample); + } + + @Override + public JsonElement serialize(Players players, Type type, JsonSerializationContext ctx) { + JsonObject object = new JsonObject(); + + object.addProperty("online", players.getOnline()); + object.addProperty("max", players.getMax()); + object.add("sample", ctx.serialize(players.getSample())); + + return object; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/ping/ServerPingSerializer.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/ping/ServerPingSerializer.java new file mode 100644 index 000000000..d2547a68d --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/ping/ServerPingSerializer.java @@ -0,0 +1,52 @@ +package com.velocitypowered.proxy.protocol.util.ping; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.velocitypowered.api.proxy.server.ServerPing; +import com.velocitypowered.api.proxy.server.ServerPing.Players; +import com.velocitypowered.api.proxy.server.ServerPing.Version; +import com.velocitypowered.api.util.Favicon; +import com.velocitypowered.api.util.ModInfo; +import java.lang.reflect.Type; +import net.kyori.adventure.text.Component; + +public class ServerPingSerializer implements JsonSerializer, + JsonDeserializer { + + public static final ServerPingSerializer INSTANCE = new ServerPingSerializer(); + + private ServerPingSerializer() { + + } + + @Override + public ServerPing deserialize(JsonElement elem, Type type, + JsonDeserializationContext ctx) throws JsonParseException { + JsonObject object = elem.getAsJsonObject(); + + Component description = ctx.deserialize(object.get("description"), Component.class); + Version version = ctx.deserialize(object.get("version"), Version.class); + Players players = ctx.deserialize(object.get("players"), Players.class); + Favicon favicon = ctx.deserialize(object.get("favicon"), Favicon.class); + ModInfo modInfo = ctx.deserialize(object.get("modInfo"), ModInfo.class); + + return new ServerPing(version, players, description, favicon, modInfo); + } + + @Override + public JsonElement serialize(ServerPing ping, Type type, + JsonSerializationContext ctx) { + JsonObject object = new JsonObject(); + object.add("description", ctx.serialize(ping.getDescriptionComponent())); + object.add("version", ctx.serialize(ping.getVersion())); + object.add("players", ctx.serialize(ping.getPlayers().orElse(null))); + object.addProperty("favicon", ping.getFavicon().map(Favicon::getBase64Url).orElse(null)); + object.add("modInfo", ctx.serialize(ping.getModinfo().orElse(null))); + return object; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/ping/VersionSerializer.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/ping/VersionSerializer.java new file mode 100644 index 000000000..198c67578 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/util/ping/VersionSerializer.java @@ -0,0 +1,47 @@ +package com.velocitypowered.proxy.protocol.util.ping; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import com.velocitypowered.api.proxy.server.ServerPing.Version; +import java.io.IOException; + +public class VersionSerializer extends TypeAdapter { + + public static final VersionSerializer INSTANCE = new VersionSerializer(); + + private VersionSerializer() { + + } + + @Override + public void write(JsonWriter jsonWriter, Version version) throws IOException { + jsonWriter.beginObject(); + jsonWriter.name("protocol"); + jsonWriter.value(version.getProtocol()); + jsonWriter.name("name"); + jsonWriter.value(version.getName()); + jsonWriter.endObject(); + } + + @Override + public Version read(JsonReader jsonReader) throws IOException { + jsonReader.beginObject(); + + String name = ""; + int protocol = -1; + for (int i = 0; i < 2; i++) { + String elem = jsonReader.nextName(); + if (elem.equals("name")) { + name = jsonReader.nextString(); + } else if (elem.equals("protocol")) { + protocol = jsonReader.nextInt(); + } else { + throw new IllegalStateException("Invalid version specification."); + } + } + + jsonReader.endObject(); + return new Version(protocol, name); + } +}