Skip to content

Commit

Permalink
Initial LimboAPI and FastMOTD support
Browse files Browse the repository at this point in the history
  • Loading branch information
UserNugget committed Jul 10, 2024
1 parent facc6bc commit 6d0b7f8
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 18 deletions.
2 changes: 2 additions & 0 deletions libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ protocol-support = "3d24efeda6"
paper = "1.20.6-R0.1-SNAPSHOT"
bungeecord = "1.20-R0.1-SNAPSHOT"
velocity = "3.1.0"
velocity-native = "3.3.0-SNAPSHOT"
run-paper = "2.3.0"

[libraries]
Expand All @@ -23,6 +24,7 @@ protocol-support = { group = "com.github.ProtocolSupport", name = "ProtocolSuppo
paper = { group = "io.papermc.paper", name = "paper-api", version.ref = "paper" }
bungeecord = { group = "net.md-5", name = "bungeecord-api", version.ref = "bungeecord" }
velocity = { group = "com.velocitypowered", name = "velocity-api", version.ref = "velocity" }
velocity-native = { group = "com.velocitypowered", name = "velocity-native", version.ref = "velocity-native" }

[bundles]
adventure = [ "adventure-api", "adventure-nbt" ]
Expand Down
1 change: 1 addition & 0 deletions velocity/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ repositories {
dependencies {
compileOnly(libs.netty)
compileOnly(libs.velocity)
compileOnly(libs.velocity.native)
annotationProcessor(libs.velocity)
shadow(project(":api", "shadow"))
shadow(project(":netty-common"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2022 retrooper and contributors
* Copyright (C) 2024 retrooper and contributors
*
* 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
Expand All @@ -21,7 +21,6 @@
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.netty.buffer.ByteBufHelper;
import com.github.retrooper.packetevents.netty.channel.ChannelHelper;
import com.github.retrooper.packetevents.protocol.player.User;
import com.github.retrooper.packetevents.util.EnumUtil;
import com.github.retrooper.packetevents.util.EventCreationUtil;
Expand All @@ -39,10 +38,12 @@

@ChannelHandler.Sharable
public class PacketEventsDecoder extends MessageToMessageDecoder<ByteBuf> {
private static Enum<?> VELOCITY_CONNECTION_EVENT_CONSTANT;
private static Enum<?> VELOCITY_CONNECTION_EVENT_COMPRESSION_ENABLED;
private static Enum<?> VELOCITY_CONNECTION_EVENT_COMPRESSION_DISABLED;
public User user;
public Player player;
public boolean handledCompression;

public PacketEventsDecoder(User user) {
this.user = user;
}
Expand Down Expand Up @@ -83,19 +84,43 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> o

@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object event) throws Exception {
if (VELOCITY_CONNECTION_EVENT_CONSTANT == null) {
if (VELOCITY_CONNECTION_EVENT_COMPRESSION_ENABLED == null) {
Class<? extends Enum<?>> clazz = (Class<? extends Enum<?>>) Reflection.getClassByNameWithoutException("com.velocitypowered.proxy.protocol.VelocityConnectionEvent");
VELOCITY_CONNECTION_EVENT_CONSTANT = EnumUtil.valueOf(clazz, "COMPRESSION_ENABLED");
VELOCITY_CONNECTION_EVENT_COMPRESSION_ENABLED = EnumUtil.valueOf(clazz, "COMPRESSION_ENABLED");
VELOCITY_CONNECTION_EVENT_COMPRESSION_DISABLED = EnumUtil.valueOf(clazz, "COMPRESSION_DISABLED");
}
//We can use == as it is an enum constant
if (event == VELOCITY_CONNECTION_EVENT_CONSTANT && !handledCompression) {
ChannelPipeline pipe = ctx.pipeline();
PacketEventsEncoder encoder = (PacketEventsEncoder) pipe.remove(PacketEvents.ENCODER_NAME);
pipe.addBefore("minecraft-encoder", PacketEvents.ENCODER_NAME, encoder);
PacketEventsDecoder decoder = (PacketEventsDecoder) pipe.remove(PacketEvents.DECODER_NAME);
pipe.addBefore("minecraft-decoder", PacketEvents.DECODER_NAME, decoder);
//System.out.println("Pipe: " + ChannelHelper.pipelineHandlerNamesAsString(ctx.channel()));
handledCompression = true;
if (event == VELOCITY_CONNECTION_EVENT_COMPRESSION_ENABLED || event == VELOCITY_CONNECTION_EVENT_COMPRESSION_DISABLED) {
ChannelPipeline pipeline = ctx.pipeline();

// Check if FastPrepareAPI is injected
ChannelHandlerContext context = pipeline.context("fastprepare-encoder");
if (context != null) {
// Use uncompressed packets instead of compressed ones.
context.handler().getClass().getDeclaredMethod("setShouldSendUncompressed", boolean.class).invoke(context.handler(), true);
}

PacketEventsEncoder encoder = (PacketEventsEncoder) pipeline.get(PacketEvents.ENCODER_NAME);
encoder.enableCompression = event == VELOCITY_CONNECTION_EVENT_COMPRESSION_ENABLED;

// Relocate handlers if FastPrepareAPI was injected or dejected
boolean wasInjected = encoder.fastPrepareApiInjected;
encoder.fastPrepareApiInjected = context != null;
if (wasInjected != encoder.fastPrepareApiInjected) {
handledCompression = false;
}

if (!handledCompression) {
encoder.ignoreRemoval = true;
encoder = (PacketEventsEncoder) pipeline.remove(PacketEvents.ENCODER_NAME);
pipeline.addBefore("minecraft-encoder", PacketEvents.ENCODER_NAME, encoder);
encoder.ignoreRemoval = false;

PacketEventsDecoder decoder = (PacketEventsDecoder) pipeline.remove(PacketEvents.DECODER_NAME);
pipeline.addBefore("minecraft-decoder", PacketEvents.DECODER_NAME, decoder);
//System.out.println("Pipe: " + ChannelHelper.pipelineHandlerNamesAsString(ctx.channel()));
handledCompression = true;
}
}
super.userEventTriggered(ctx, event);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2022 retrooper and contributors
* Copyright (C) 2024 retrooper and contributors
*
* 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
Expand All @@ -21,34 +21,73 @@
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketSendEvent;
import com.github.retrooper.packetevents.netty.buffer.ByteBufHelper;
import com.github.retrooper.packetevents.protocol.ConnectionState;
import com.github.retrooper.packetevents.protocol.player.User;
import com.github.retrooper.packetevents.util.EventCreationUtil;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.config.ProxyConfig;
import com.velocitypowered.natives.compression.VelocityCompressor;
import com.velocitypowered.natives.util.MoreByteBufUtils;
import com.velocitypowered.natives.util.Natives;
import io.github.retrooper.packetevents.injector.VelocityPipelineInjector;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

@ChannelHandler.Sharable
public class PacketEventsEncoder extends MessageToByteEncoder<ByteBuf> {
private static Boolean FASTMOTD_PRESENT;
public Player player;
public User user;
public int compressionThreshold;
public VelocityCompressor compressor;
public boolean fastPrepareApiInjected;
public boolean enableCompression;
public boolean enableCompressionClientside;
public boolean ignoreRemoval;

public PacketEventsEncoder(User user) {
this.user = user;
}

public void read(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
int firstReaderIndex = buffer.readerIndex();

boolean rewriteFrameSize = false;
if (this.user.getEncoderState() == ConnectionState.STATUS && buffer.isReadable()) {
if (FASTMOTD_PRESENT == null) {
VelocityPipelineInjector injector = (VelocityPipelineInjector) PacketEvents.getAPI().getInjector();
FASTMOTD_PRESENT = injector.getServer().getPluginManager().getPlugin("fastmotd").isPresent();
}

if (FASTMOTD_PRESENT) {
int frameSize = ByteBufHelper.readVarInt(buffer);
rewriteFrameSize = frameSize > 0 && frameSize == buffer.readableBytes();
if (!rewriteFrameSize) {
buffer.readerIndex(firstReaderIndex);
}
}
}

PacketSendEvent packetSendEvent = EventCreationUtil.createSendEvent(ctx.channel(), user, player, buffer,
false);
int readerIndex = buffer.readerIndex();
PacketEvents.getAPI().getEventManager().callEvent(packetSendEvent, () -> buffer.readerIndex(readerIndex));
if (!packetSendEvent.isCancelled()) {
if (packetSendEvent.getLastUsedWrapper() != null) {
ByteBufHelper.clear(packetSendEvent.getByteBuf());
if (rewriteFrameSize) {
buffer.writeMedium(0);
}

packetSendEvent.getLastUsedWrapper().writeVarInt(packetSendEvent.getPacketId());
packetSendEvent.getLastUsedWrapper().write();

if (rewriteFrameSize) {
int frameSize = buffer.readableBytes() - 3;
buffer.setMedium(0, (frameSize & 0x7F | 0x80) << 16 | ((frameSize >>> 7) & 0x7F | 0x80) << 8 | (frameSize >>> 14));
}
}
buffer.readerIndex(firstReaderIndex);
} else {
Expand All @@ -61,10 +100,76 @@ public void read(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
}
}

private VelocityCompressor findCompressor() {
if (this.compressor != null) {
return this.compressor;
}

VelocityPipelineInjector injector = (VelocityPipelineInjector) PacketEvents.getAPI().getInjector();
ProxyConfig config = injector.getServer().getConfiguration();

this.compressionThreshold = config.getCompressionThreshold();
if (this.compressionThreshold != -1) {
this.compressor = Natives.compress.get().create(config.getCompressionLevel());
}

return this.compressor;
}

@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
if (!msg.isReadable()) return;

// FastPrepareAPI is injected, completly re-encode packets.
if (this.fastPrepareApiInjected) {
// As FastPrepareAPI can send multiple packets at a single 'encode', we should process them all
while (msg.isReadable()) {
int size = ByteBufHelper.readVarInt(msg);
ByteBuf transformed = ctx.alloc().buffer(size).writeBytes(msg, size);
try {
read(ctx, transformed);
if (!transformed.isReadable()) {
continue;
}

// Re-encode transformed packet
if (this.enableCompression && this.enableCompressionClientside) {
VelocityCompressor velocityCompressor = this.findCompressor();

// Write frame size + uncompressed length + uncompressed or compressed packet data
int uncompressed = transformed.readableBytes();
if (uncompressed < this.compressionThreshold) {
ByteBufHelper.writeVarInt(out, uncompressed + 1);
ByteBufHelper.writeVarInt(out, 0);
out.writeBytes(transformed);
} else {
int frameIndex = out.writerIndex();
out.writeMedium(0);

ByteBufHelper.writeVarInt(out, uncompressed);
ByteBuf compatible = MoreByteBufUtils.ensureCompatible(ctx.alloc(), velocityCompressor, transformed);

try {
velocityCompressor.deflate(compatible, out);
} finally {
compatible.release();
}

int frameSize = (out.writerIndex() - frameIndex) - 3;
out.setMedium(frameIndex, (frameSize & 0x7F | 0x80) << 16 | ((frameSize >>> 7) & 0x7F | 0x80) << 8 | (frameSize >>> 14));
}
} else {
// Write frame size + packet data
ByteBufHelper.writeVarInt(out, transformed.readableBytes());
out.writeBytes(transformed);
}
} finally {
transformed.release();
}
}
return;
}

ByteBuf transformed = ctx.alloc().buffer().writeBytes(msg);
try {
read(ctx, transformed);
Expand All @@ -74,6 +179,14 @@ protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throw
}
}

@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
if (this.compressor != null && !this.ignoreRemoval) {
this.compressor.close();
this.compressor = null;
}
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2022 retrooper and contributors
* Copyright (C) 2024 retrooper and contributors
*
* 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
Expand Down Expand Up @@ -43,6 +43,10 @@ public VelocityPipelineInjector(ProxyServer server) {
this.server = server;
}

public ProxyServer getServer() {
return this.server;
}

@Override
public boolean isServerBound() {
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2024 retrooper and contributors
*
* 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 <http://www.gnu.org/licenses/>.
*/

package io.github.retrooper.packetevents.manager;

import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketSendEvent;
import com.github.retrooper.packetevents.manager.InternalPacketListener;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.login.server.WrapperLoginServerSetCompression;
import io.github.retrooper.packetevents.handlers.PacketEventsEncoder;
import io.netty.channel.Channel;

public class InternalVelocityPacketListener extends InternalPacketListener {

@Override
public void onPacketSend(PacketSendEvent event) {
if (event.getPacketType() == PacketType.Login.Server.SET_COMPRESSION) {
WrapperLoginServerSetCompression compression = new WrapperLoginServerSetCompression(event);
Channel channel = (Channel) event.getUser().getChannel();
PacketEventsEncoder encoder = (PacketEventsEncoder) channel.pipeline().get(PacketEvents.ENCODER_NAME);
encoder.enableCompressionClientside = compression.getThreshold() >= 0;
}

super.onPacketSend(event);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2022 retrooper and contributors
* Copyright (C) 2024 retrooper and contributors
*
* 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
Expand All @@ -22,7 +22,6 @@
import com.github.retrooper.packetevents.PacketEventsAPI;
import com.github.retrooper.packetevents.event.UserLoginEvent;
import com.github.retrooper.packetevents.injector.ChannelInjector;
import com.github.retrooper.packetevents.manager.InternalPacketListener;
import com.github.retrooper.packetevents.manager.player.PlayerManager;
import com.github.retrooper.packetevents.manager.protocol.ProtocolManager;
import com.github.retrooper.packetevents.manager.server.ServerManager;
Expand All @@ -43,6 +42,7 @@
import io.github.retrooper.packetevents.impl.netty.manager.protocol.ProtocolManagerAbstract;
import io.github.retrooper.packetevents.impl.netty.manager.server.ServerManagerAbstract;
import io.github.retrooper.packetevents.injector.VelocityPipelineInjector;
import io.github.retrooper.packetevents.manager.InternalVelocityPacketListener;
import io.github.retrooper.packetevents.manager.PlayerManagerImpl;
import net.kyori.adventure.text.format.NamedTextColor;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -144,7 +144,7 @@ public void load() {

// Register internal packet listener (should be the first listener)
// This listener doesn't do any modifications to the packets, just reads data
getEventManager().registerListener(new InternalPacketListener());
getEventManager().registerListener(new InternalVelocityPacketListener());
}
}

Expand Down

0 comments on commit 6d0b7f8

Please sign in to comment.