Skip to content

Commit

Permalink
Fix incompatibility with Fabric API 0.9.1 and later.
Browse files Browse the repository at this point in the history
Fixes #111.

This isn't the cleanest possible fix, but it does entirely avoid the issue by cloning the contents of plugin messages.
  • Loading branch information
astei committed Dec 30, 2023
1 parent e6be3e9 commit da56647
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package me.steinborn.krypton.mixin.shared.bugfix;

import io.netty.buffer.Unpooled;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.packet.s2c.common.CustomPayloadS2CPacket;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;

@Mixin(CustomPayloadS2CPacket.class)
public class CustomPayloadS2CPacketFabricAPICompatMixin {

@ModifyVariable(method = "readPayload", index = 1, at = @At(value = "HEAD"), argsOnly = true)
private static PacketByteBuf readPayload$explicitCopy(PacketByteBuf buf) {
PacketByteBuf copy = new PacketByteBuf(Unpooled.copiedBuffer(buf.copy()));
// Pretend to consume everything in the buffer
buf.skipBytes(buf.readableBytes());
return copy;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ public String getRefMapperConfig() {

@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
if (mixinClassName.contains("avoidwork.ThreadedAnvilChunkStorageMixin")) {
// This mixin is incompatible with Immersive Portals.
return !FabricLoader.getInstance().isModLoaded("imm_ptl_core");
if (mixinClassName.contains("CustomPayloadS2CPacketFabricAPICompatMixin")) {
// This mixin should only apply when Fabric API is present.
return FabricLoader.getInstance().isModLoaded("fabric-api");
}
return true;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,72 +1,76 @@
package me.steinborn.krypton.mod.shared.network.compression;

import com.velocitypowered.natives.compression.VelocityCompressor;
import com.velocitypowered.natives.util.MoreByteBufUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.MessageToMessageDecoder;
import net.minecraft.network.PacketByteBuf;

import java.util.List;

public class MinecraftCompressDecoder extends ByteToMessageDecoder {
import static com.google.common.base.Preconditions.checkState;
import static com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible;
import static com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer;

private static final int UNCOMPRESSED_CAP = Boolean.getBoolean("krypton.permit-oversized-packets")
? Integer.MAX_VALUE : 8 * 1024 * 1024;
/**
* Decompresses a Minecraft packet.
*/
public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {

private int threshold;
private final boolean validate;
private final VelocityCompressor compressor;
private static final int VANILLA_MAXIMUM_UNCOMPRESSED_SIZE = 8 * 1024 * 1024; // 8MiB
private static final int HARD_MAXIMUM_UNCOMPRESSED_SIZE = 128 * 1024 * 1024; // 128MiB

public MinecraftCompressDecoder(int threshold, boolean validate, VelocityCompressor compressor) {
this.threshold = threshold;
this.validate = validate;
this.compressor = compressor;
}
private static final int UNCOMPRESSED_CAP =
Boolean.getBoolean("krypton.permit-oversized-packets")
? HARD_MAXIMUM_UNCOMPRESSED_SIZE : VANILLA_MAXIMUM_UNCOMPRESSED_SIZE;

@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (in.readableBytes() != 0) {
PacketByteBuf packetBuf = new PacketByteBuf(in);
int claimedUncompressedSize = packetBuf.readVarInt();
private int threshold;
private final VelocityCompressor compressor;
private final boolean validate;

if (claimedUncompressedSize == 0) {
out.add(packetBuf.readBytes(packetBuf.readableBytes()));
} else {
if (validate) {
if (claimedUncompressedSize < this.threshold) {
throw new DecoderException("Badly compressed packet - size of " + claimedUncompressedSize + " is below server threshold of " + this.threshold);
}
public MinecraftCompressDecoder(int threshold, boolean validate, VelocityCompressor compressor) {
this.threshold = threshold;
this.compressor = compressor;
this.validate = validate;
}

@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
PacketByteBuf bb = new PacketByteBuf(in);
int claimedUncompressedSize = bb.readVarInt();
if (claimedUncompressedSize == 0) {
// This message is not compressed.
out.add(in.retain());
return;
}

if (claimedUncompressedSize > UNCOMPRESSED_CAP) {
throw new DecoderException("Badly compressed packet - size of " + claimedUncompressedSize + " is larger than maximum of " + UNCOMPRESSED_CAP);
}
if (validate) {
checkState(claimedUncompressedSize >= threshold, "Uncompressed size %s is less than"
+ " threshold %s", claimedUncompressedSize, threshold);
checkState(claimedUncompressedSize <= UNCOMPRESSED_CAP,
"Uncompressed size %s exceeds hard threshold of %s", claimedUncompressedSize,
UNCOMPRESSED_CAP);
}

ByteBuf compatibleIn = MoreByteBufUtils.ensureCompatible(ctx.alloc(), compressor, in);
ByteBuf uncompressed = MoreByteBufUtils.preferredBuffer(ctx.alloc(), compressor, claimedUncompressedSize);
ByteBuf compatibleIn = ensureCompatible(ctx.alloc(), compressor, in);
ByteBuf uncompressed = preferredBuffer(ctx.alloc(), compressor, claimedUncompressedSize);
try {
compressor.inflate(compatibleIn, uncompressed, claimedUncompressedSize);
out.add(uncompressed);
in.clear();
compressor.inflate(compatibleIn, uncompressed, claimedUncompressedSize);
out.add(uncompressed);
} catch (Exception e) {
uncompressed.release();
throw e;
uncompressed.release();
throw e;
} finally {
compatibleIn.release();
compatibleIn.release();
}
}

}
}

public void setThreshold(int threshold) {
this.threshold = threshold;
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
compressor.close();
}

@Override
public void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
compressor.close();
}
public void setThreshold(int threshold) {
this.threshold = threshold;
}
}
3 changes: 2 additions & 1 deletion src/main/resources/krypton.accesswidener
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ accessWidener v1 named
accessible class net/minecraft/server/world/ThreadedAnvilChunkStorage$EntityTracker
accessible class net/minecraft/server/world/ThreadedAnvilChunkStorage$TicketManager
accessible method net/minecraft/network/handler/SplitterHandler decode (Lio/netty/channel/ChannelHandlerContext;Lio/netty/buffer/ByteBuf;Ljava/util/List;)V
accessible field net/minecraft/server/world/ThreadedAnvilChunkStorage$EntityTracker entity Lnet/minecraft/entity/Entity;
accessible field net/minecraft/server/world/ThreadedAnvilChunkStorage$EntityTracker entity Lnet/minecraft/entity/Entity;
accessible method net/minecraft/network/packet/s2c/common/CustomPayloadS2CPacket readUnknownPayload (Lnet/minecraft/util/Identifier;Lnet/minecraft/network/PacketByteBuf;)Lnet/minecraft/network/packet/UnknownCustomPayload;
1 change: 1 addition & 0 deletions src/main/resources/krypton.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"plugin": "me.steinborn.krypton.mod.shared.KryptonMixinPlugin",
"client": [],
"mixins": [
"shared.bugfix.CustomPayloadS2CPacketFabricAPICompatMixin",
"shared.debugaid.ResourceLeakDetectorDisableConditionalMixin",
"shared.network.microopt.EntityTrackerEntryMixin",
"shared.network.microopt.StringEncodingMixin",
Expand Down

0 comments on commit da56647

Please sign in to comment.