Skip to content

Commit

Permalink
Refactor fabric network handlers
Browse files Browse the repository at this point in the history
This removes compression handling from encoder, as vanilla compression setup does not interfere with packetevents
This also refactors the packet encoder to be based directly on the ChannelOutboundHandlerAdapter, instead of the MessageToByteEncoder

Requires testing - there could be a retain missing in the PacketEncoder
  • Loading branch information
booky10 committed Oct 20, 2024
1 parent b72502b commit 5356a01
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,102 +18,35 @@

package io.github.retrooper.packetevents.handler;

import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.protocol.player.User;
import com.github.retrooper.packetevents.util.PacketEventsImplHelper;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import net.minecraft.world.entity.player.Player;
import org.jetbrains.annotations.ApiStatus;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

@ChannelHandler.Sharable
@ApiStatus.Internal
public class PacketDecoder extends MessageToMessageDecoder<ByteBuf> {
private static Method DECOMPRESSOR_METHOD, COMPRESSOR_METHOD;

public User user;
public Player player;
public boolean checkedCompression;

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

@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
if (msg.isReadable()) {
ByteBuf outputBuffer = ctx.alloc().buffer().writeBytes(msg);
boolean recompress = handleCompression(ctx, outputBuffer);
PacketEventsImplHelper.handleClientBoundPacket(ctx.channel(), user, player, outputBuffer, false);
if (outputBuffer.isReadable()) {
if (recompress) {
recompress(ctx, outputBuffer);
}
out.add(outputBuffer.retain());
}
if (!msg.isReadable()) {
return;
}
}

@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
}


private boolean handleCompression(ChannelHandlerContext ctx, ByteBuf buffer) {
if (checkedCompression) return false;
if (ctx.pipeline().names().indexOf("decompress") > ctx.pipeline().names().indexOf(PacketEvents.DECODER_NAME)) {
// Need to decompress this packet due to bad order
ChannelHandler decompressor = ctx.pipeline().get("decompress");
//CompressionDecoder
try {
if (DECOMPRESSOR_METHOD == null) {
DECOMPRESSOR_METHOD = decompressor.getClass().getDeclaredMethod("decode", ChannelHandlerContext.class, ByteBuf.class, List.class);
}
List<?> list = new ArrayList<>(1);
DECOMPRESSOR_METHOD.invoke(decompressor, ctx, buffer, list);
ByteBuf decompressed = (ByteBuf) list.get(0);
if (buffer != decompressed) {
try {
buffer.clear().writeBytes(decompressed);
} finally {
decompressed.release();
}
}
//Relocate handlers
PacketDecoder decoder = (PacketDecoder) ctx.pipeline().remove(PacketEvents.DECODER_NAME);
ctx.pipeline().addAfter("decompress", PacketEvents.DECODER_NAME, decoder);
PacketEncoder encoder = (PacketEncoder) ctx.pipeline().remove(PacketEvents.ENCODER_NAME);
ctx.pipeline().addAfter("compress", PacketEvents.ENCODER_NAME, encoder);
checkedCompression = true;
return true;
} catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
e.printStackTrace();
}
}
return false;
}

private void recompress(ChannelHandlerContext ctx, ByteBuf buffer) {
ByteBuf compressed = ctx.alloc().buffer();
try {
ChannelHandler compressor = ctx.pipeline().get("compress");
if (COMPRESSOR_METHOD == null) {
COMPRESSOR_METHOD = compressor.getClass().getDeclaredMethod("encode", ChannelHandlerContext.class, ByteBuf.class, ByteBuf.class);
}
COMPRESSOR_METHOD.invoke(compressor, ctx, buffer, compressed);
} catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
e.printStackTrace();
}
try {
buffer.clear().writeBytes(compressed);
PacketEvents.getAPI().getLogManager().debug("Recompressed packet!");
} finally {
compressed.release();
PacketEventsImplHelper.handleClientBoundPacket(ctx.channel(), this.user, this.player,
msg, false);
if (msg.isReadable()) {
out.add(msg.retain());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,15 @@
import com.github.retrooper.packetevents.protocol.player.User;
import com.github.retrooper.packetevents.util.PacketEventsImplHelper;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.EncoderException;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.util.ReferenceCountUtil;
import net.minecraft.world.entity.player.Player;
import org.jetbrains.annotations.ApiStatus;

@ApiStatus.Internal
public class PacketEncoder extends ChannelOutboundHandlerAdapter {

@ChannelHandler.Sharable
public class PacketEncoder extends MessageToByteEncoder<ByteBuf> {
public User user;
public Player player;

Expand All @@ -40,43 +39,26 @@ public PacketEncoder(User user) {

@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ByteBuf buf = null;
if (!(msg instanceof ByteBuf in)) {
ctx.write(msg, promise);
return;
}
if (!in.isReadable()) {
in.release();
return;
}

ByteBuf out;
try {
if (acceptOutboundMessage(msg)) {
ByteBuf in = (ByteBuf) msg;
buf = allocateBuffer(ctx, in, true);
try {
encode(ctx, in, buf);
} finally {
ReferenceCountUtil.release(in);
}
if (buf.isReadable()) {
ctx.write(buf, promise);
} else {
buf.release();
//We cancelled this packet, do not pass it on to the next handler.
//ctx.write(Unpooled.EMPTY_BUFFER, promise);
}
buf = null;
} else {
ctx.write(msg, promise);
}
} catch (EncoderException e) {
throw e;
} catch (Throwable e) {
throw new EncoderException(e);
out = (ByteBuf) PacketEventsImplHelper.handleServerBoundPacket(ctx.channel(),
this.user, this.player, in, false);
} finally {
if (buf != null) {
buf.release();
}
in.release();
}
}

@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
if (msg.isReadable()) {
ByteBuf buffer = (ByteBuf) PacketEventsImplHelper.handleServerBoundPacket(ctx.channel(), user, player, msg, false);
out.writeBytes(buffer.retain());
if (out.isReadable()) {
ctx.write(out, promise);
} else {
out.release();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,33 @@
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelPipeline;
import net.minecraft.SharedConstants;
import net.minecraft.network.BandwidthDebugMonitor;
import net.minecraft.network.protocol.PacketFlow;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

@Mixin(net.minecraft.network.Connection.class)
public class PacketEventsInjectorMixin {
public class ConnectionMixin {

// doesn't account for mods like ViaFabric
@Unique
private static final ClientVersion CLIENT_VERSION =
ClientVersion.getById(SharedConstants.getProtocolVersion());

@Inject(method = "configureSerialization", at = @At("TAIL"))
private static void configureSerialization(ChannelPipeline pipeline, PacketFlow flow, boolean memoryOnly, BandwidthDebugMonitor bandwithDebugMonitor, CallbackInfo ci) throws Exception {
private static void configureSerialization(
ChannelPipeline pipeline, PacketFlow flow, boolean memoryOnly,
BandwidthDebugMonitor bandwithDebugMonitor, CallbackInfo ci
) {
PacketEvents.getAPI().getLogManager().debug("Game connected!");

Channel channel = pipeline.channel();
User user = new User(channel, ConnectionState.HANDSHAKING, ClientVersion.getLatest(),
new UserProfile(null, null));
User user = new User(channel, ConnectionState.HANDSHAKING,
CLIENT_VERSION, new UserProfile(null, null));
ProtocolManager.USERS.put(channel, user);

UserConnectEvent connectEvent = new UserConnectEvent(user);
Expand All @@ -54,10 +66,10 @@ private static void configureSerialization(ChannelPipeline pipeline, PacketFlow
channel.unsafe().closeForcibly();
return;
}
PacketDecoder decoder = new PacketDecoder(user);
PacketEncoder encoder = new PacketEncoder(user);
channel.pipeline().addAfter("splitter", PacketEvents.DECODER_NAME, decoder);
channel.pipeline().addAfter("prepender", PacketEvents.ENCODER_NAME, encoder);
channel.closeFuture().addListener((ChannelFutureListener) future -> PacketEventsImplHelper.handleDisconnection(user.getChannel(), user.getUUID()));

channel.pipeline().addAfter("splitter", PacketEvents.DECODER_NAME, new PacketDecoder(user));
channel.pipeline().addAfter("prepender", PacketEvents.ENCODER_NAME, new PacketEncoder(user));
channel.closeFuture().addListener((ChannelFutureListener) future ->
PacketEventsImplHelper.handleDisconnection(user.getChannel(), user.getUUID()));
}
}
2 changes: 1 addition & 1 deletion fabric/src/main/resources/packetevents.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"package": "io.github.retrooper.packetevents.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [
"PacketEventsInjectorMixin"
"ConnectionMixin"
],
"client": [
],
Expand Down

0 comments on commit 5356a01

Please sign in to comment.