diff --git a/src/main/java/com/simibubi/create/content/contraptions/AbstractContraptionEntity.java b/src/main/java/com/simibubi/create/content/contraptions/AbstractContraptionEntity.java index aed5425cd6..d29961056b 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/AbstractContraptionEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/AbstractContraptionEntity.java @@ -18,13 +18,13 @@ import com.simibubi.create.AllItems; import com.simibubi.create.AllMovementBehaviours; import com.simibubi.create.AllPackets; import com.simibubi.create.AllSoundEvents; -import com.simibubi.create.Create; import com.simibubi.create.content.contraptions.actors.psi.PortableStorageInterfaceMovement; import com.simibubi.create.content.contraptions.actors.seat.SeatBlock; import com.simibubi.create.content.contraptions.actors.seat.SeatEntity; import com.simibubi.create.content.contraptions.actors.trainControls.ControlsStopControllingPacket; import com.simibubi.create.content.contraptions.behaviour.MovementBehaviour; import com.simibubi.create.content.contraptions.behaviour.MovementContext; +import com.simibubi.create.content.contraptions.data.ContraptionSyncLimiting; import com.simibubi.create.content.contraptions.elevator.ElevatorContraption; import com.simibubi.create.content.contraptions.glue.SuperGlueEntity; import com.simibubi.create.content.contraptions.mounted.MountedContraption; @@ -615,11 +615,8 @@ public abstract class AbstractContraptionEntity extends Entity implements IEntit CompoundTag compound = new CompoundTag(); writeAdditional(compound, true); - if (ContraptionData.isTooLargeForSync(compound)) { - String info = getContraption().getType().id + " @" + position() + " (" + getStringUUID() + ")"; - Create.LOGGER.warn("Could not send Contraption Spawn Data (Packet too big): " + info); - compound = null; - } + if (ContraptionSyncLimiting.isTooLargeForSync(compound)) + compound = null; // don't sync contraption data buffer.writeNbt(compound); } diff --git a/src/main/java/com/simibubi/create/content/contraptions/ContraptionData.java b/src/main/java/com/simibubi/create/content/contraptions/ContraptionData.java deleted file mode 100644 index 158d306a76..0000000000 --- a/src/main/java/com/simibubi/create/content/contraptions/ContraptionData.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.simibubi.create.content.contraptions; - -import com.simibubi.create.compat.Mods; -import com.simibubi.create.foundation.mixin.accessor.NbtAccounterAccessor; -import com.simibubi.create.infrastructure.config.AllConfigs; - -import io.netty.buffer.Unpooled; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtAccounter; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket; - -public class ContraptionData { - /** - * A sane, default maximum for contraption data size. - */ - public static final int DEFAULT_LIMIT = 2_000_000; - /** - * Connectivity expands the NBT packet limit to 2 GB. - */ - public static final int CONNECTIVITY_LIMIT = Integer.MAX_VALUE; - /** - * Packet Fixer expands the NBT packet limit to 200 MB. - */ - public static final int PACKET_FIXER_LIMIT = 209_715_200; - /** - * XL Packets expands the NBT packet limit to 2 GB. - */ - public static final int XL_PACKETS_LIMIT = 2_000_000_000; - /** - * Minecart item sizes are limited by the vanilla slot change packet ({@link ClientboundContainerSetSlotPacket}). - * {@link #DEFAULT_LIMIT} is used as the default. - * Connectivity, PacketFixer, and XL Packets expand the size limit. - * If one of these mods is loaded, we take advantage of it and use the higher limit. - */ - public static final int PICKUP_LIMIT; - - static { - int limit = DEFAULT_LIMIT; - - // Check from largest to smallest to use the smallest limit if multiple mods are loaded. - // It is necessary to use the smallest limit because even if multiple mods are loaded, - // not all of their mixins may be applied. Therefore, it is safest to only assume that - // the mod with the smallest limit is actually active. - if (Mods.CONNECTIVITY.isLoaded()) { - limit = CONNECTIVITY_LIMIT; - } - if (Mods.XLPACKETS.isLoaded()) { - limit = XL_PACKETS_LIMIT; - } - if (Mods.PACKETFIXER.isLoaded()) { - limit = PACKET_FIXER_LIMIT; - } - - PICKUP_LIMIT = limit; - } - - /** - * @return true if the given NBT is too large for a contraption to be synced to clients. - */ - public static boolean isTooLargeForSync(CompoundTag data) { - int max = AllConfigs.server().kinetics.maxDataSize.get(); - return max != 0 && packetSize(data) > max; - } - - /** - * @return true if the given NBT is too large for a contraption to be picked up with a wrench. - */ - public static boolean isTooLargeForPickup(CompoundTag data) { - return packetSize(data) > PICKUP_LIMIT; - } - - /** - * @return the size of the given NBT when put through a packet, in bytes. - */ - public static long packetSize(CompoundTag data) { - FriendlyByteBuf test = new FriendlyByteBuf(Unpooled.buffer()); - test.writeNbt(data); - NbtAccounter sizeTracker = new NbtAccounter(Long.MAX_VALUE); - test.readNbt(sizeTracker); - long size = ((NbtAccounterAccessor) sizeTracker).create$getUsage(); - test.release(); - return size; - } -} diff --git a/src/main/java/com/simibubi/create/content/contraptions/data/ContraptionPickupLimiting.java b/src/main/java/com/simibubi/create/content/contraptions/data/ContraptionPickupLimiting.java new file mode 100644 index 0000000000..a862e443d4 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/contraptions/data/ContraptionPickupLimiting.java @@ -0,0 +1,55 @@ +package com.simibubi.create.content.contraptions.data; + +import com.simibubi.create.compat.Mods; +import com.simibubi.create.foundation.mixin.accessor.NbtAccounterAccessor; + +import io.netty.buffer.Unpooled; +import net.minecraft.Util; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtAccounter; +import net.minecraft.network.FriendlyByteBuf; + +public class ContraptionPickupLimiting { + /// The default NBT limit, defined by {@link FriendlyByteBuf#readNbt()}. + public static final int NBT_LIMIT = 2_097_152; + + // increased nbt limits provided by other mods. + public static final int PACKET_FIXER_LIMIT = NBT_LIMIT * 100; + public static final int XL_PACKETS_LIMIT = Integer.MAX_VALUE; + + // leave some space for the rest of the packet. + public static final int BUFFER = 20_000; + + // the actual limit to be used + public static final int LIMIT = Util.make(() -> { + // the smallest limit needs to be used, as we can't guarantee that all mixins are applied if multiple are present. + if (Mods.PACKETFIXER.isLoaded()) { + return PACKET_FIXER_LIMIT; + } else if (Mods.XLPACKETS.isLoaded()) { + return XL_PACKETS_LIMIT; + } + + // none are present, use vanilla default + return NBT_LIMIT; + }) - BUFFER; + + /** + * @return true if the given NBT is too large for a contraption to be picked up with a wrench. + */ + public static boolean isTooLargeForPickup(CompoundTag data) { + return nbtSize(data) > LIMIT; + } + + /** + * @return the size of the given NBT when read by the client according to {@link NbtAccounter} + */ + private static long nbtSize(CompoundTag data) { + FriendlyByteBuf test = new FriendlyByteBuf(Unpooled.buffer()); + test.writeNbt(data); + NbtAccounter sizeTracker = new NbtAccounter(Long.MAX_VALUE); + test.readNbt(sizeTracker); + long size = ((NbtAccounterAccessor) sizeTracker).create$getUsage(); + test.release(); + return size; + } +} diff --git a/src/main/java/com/simibubi/create/content/contraptions/data/ContraptionSyncLimiting.java b/src/main/java/com/simibubi/create/content/contraptions/data/ContraptionSyncLimiting.java new file mode 100644 index 0000000000..372a30a529 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/contraptions/data/ContraptionSyncLimiting.java @@ -0,0 +1,52 @@ +package com.simibubi.create.content.contraptions.data; + +import com.simibubi.create.compat.Mods; + +import io.netty.buffer.Unpooled; +import net.minecraft.Util; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; + +public class ContraptionSyncLimiting { + /** + * Contraption entity sync is limited by the clientbound custom payload limit, since that's what Forge's + * extended spawn packet uses. The NBT limit is irrelevant since it's bypassed on deserialization. + */ + public static final int SIZE_LIMIT = 1_048_576; + + // increased packet limits provided by other mods. + public static final int PACKET_FIXER_LIMIT = SIZE_LIMIT * 100; + public static final int XL_PACKETS_LIMIT = Integer.MAX_VALUE; + + // leave some room for the rest of the packet. + public static final int BUFFER = 20_000; + + // the actual limit to be used + public static final int LIMIT = Util.make(() -> { + // the smallest limit needs to be used, as we can't guarantee that all mixins are applied if multiple are present. + if (Mods.PACKETFIXER.isLoaded()) { + return PACKET_FIXER_LIMIT; + } else if (Mods.XLPACKETS.isLoaded()) { + return XL_PACKETS_LIMIT; + } + + // none are present, use vanilla default + return SIZE_LIMIT; + }) - BUFFER; + + /** + * @return true if the given NBT is too large for a contraption to be synced to clients. + */ + public static boolean isTooLargeForSync(CompoundTag data) { + return byteSize(data) > LIMIT; + } + + /** + * @return the size of the given NBT when encoded, in bytes + */ + private static long byteSize(CompoundTag data) { + FriendlyByteBuf test = new FriendlyByteBuf(Unpooled.buffer()); + test.writeNbt(data); + return test.writerIndex(); + } +} diff --git a/src/main/java/com/simibubi/create/content/contraptions/data/README.md b/src/main/java/com/simibubi/create/content/contraptions/data/README.md new file mode 100644 index 0000000000..7bae5dbf77 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/contraptions/data/README.md @@ -0,0 +1,37 @@ +# Data Limiting +This pair of classes prevents clients from getting chunkbanned when contraptions are too large. + +This information is up-to-date as of 1.20.1. + +There's a few different packet limits in play: +- the NBT limit: `2_097_152`, enforced by `FriendlyByteBuf.readNbt()` +- the clientbound custom payload limit: `1_048_576` bytes, applies to `ClientboundCustomPayloadPacket` +- the serverbound custom payload limit: `32767` bytes, applies to `ServerboundCustomPayloadPacket` +- the packet limit: `8_388_608` bytes, applies to all packets + +# NBT Size vs Bytes +There's two units in play as well - NBT Size and Bytes. The NBT limit uses NBT size, while the other +three use bytes. I'm (TropheusJ) not sure what exactly an NBT Size unit is - it's not bits, but it's +close. + +Because of this discrepancy, the NBT limit is actually much lower than it seems. It will usually be +the first limit hit. + +Bytes are found by writing a tag to a buffer and getting its `writerIndex`. NBT Size is found using +an `NbtAccounter`. + +# Sync +Sync is pretty straightforward. + +The only limit relevant here is the clientbound custom payload limit. The NBT limit would be relevant, but +client-side deserialization bypasses it. + +Sync is much less of an issue compared to pickup, since a lot of data can be skipped when syncing. + +# Pickup +Two limits are relevant for pickup: the NBT limit and the packet limit. + +The NBT limit is hit way sooner, and is usually the limiting factor. Other mods may increase it, in +which case the packet limit may become relevant. + +The custom payload limit is not relevant since item sync goes through the vanilla `ClientboundContainerSetSlotPacket`. diff --git a/src/main/java/com/simibubi/create/content/contraptions/mounted/MinecartContraptionItem.java b/src/main/java/com/simibubi/create/content/contraptions/mounted/MinecartContraptionItem.java index dd146961cd..94839ce8e1 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/mounted/MinecartContraptionItem.java +++ b/src/main/java/com/simibubi/create/content/contraptions/mounted/MinecartContraptionItem.java @@ -10,11 +10,11 @@ import com.simibubi.create.AllItems; import com.simibubi.create.AllMovementBehaviours; import com.simibubi.create.content.contraptions.AbstractContraptionEntity; import com.simibubi.create.content.contraptions.Contraption; -import com.simibubi.create.content.contraptions.ContraptionData; import com.simibubi.create.content.contraptions.ContraptionMovementSetting; import com.simibubi.create.content.contraptions.OrientedContraptionEntity; import com.simibubi.create.content.contraptions.actors.psi.PortableStorageInterfaceMovement; import com.simibubi.create.content.contraptions.behaviour.MovementContext; +import com.simibubi.create.content.contraptions.data.ContraptionPickupLimiting; import com.simibubi.create.content.kinetics.deployer.DeployerFakePlayer; import com.simibubi.create.foundation.advancement.AllAdvancements; import com.simibubi.create.foundation.utility.CreateLang; @@ -249,7 +249,7 @@ public class MinecartContraptionItem extends Item { ItemStack generatedStack = create(type, oce).setHoverName(entity.getCustomName()); - if (ContraptionData.isTooLargeForPickup(generatedStack.serializeNBT())) { + if (ContraptionPickupLimiting.isTooLargeForPickup(generatedStack.serializeNBT())) { MutableComponent message = CreateLang.translateDirect("contraption.minecart_contraption_too_big") .withStyle(ChatFormatting.RED); player.displayClientMessage(message, true); diff --git a/src/main/java/com/simibubi/create/infrastructure/config/CKinetics.java b/src/main/java/com/simibubi/create/infrastructure/config/CKinetics.java index 707e12238f..a06e24abe4 100644 --- a/src/main/java/com/simibubi/create/infrastructure/config/CKinetics.java +++ b/src/main/java/com/simibubi/create/infrastructure/config/CKinetics.java @@ -1,6 +1,5 @@ package com.simibubi.create.infrastructure.config; -import com.simibubi.create.content.contraptions.ContraptionData; import com.simibubi.create.content.contraptions.ContraptionMovementSetting; import net.createmod.catnip.config.ConfigBase; @@ -32,8 +31,6 @@ public class CKinetics extends ConfigBase { public final ConfigGroup contraptions = group(1, "contraptions", "Moving Contraptions"); public final ConfigInt maxBlocksMoved = i(2048, 1, "maxBlocksMoved", Comments.maxBlocksMoved); - public final ConfigInt maxDataSize = - i(ContraptionData.DEFAULT_LIMIT, 0, "maxDataSize", Comments.bytes, Comments.maxDataDisable, Comments.maxDataSize, Comments.maxDataSize2); public final ConfigInt maxChassisRange = i(16, 1, "maxChassisRange", Comments.maxChassisRange); public final ConfigInt maxPistonPoles = i(64, 1, "maxPistonPoles", Comments.maxPistonPoles); public final ConfigInt maxRopeLength = i(384, 1, "maxRopeLength", Comments.maxRopeLength); @@ -86,9 +83,6 @@ public class CKinetics extends ConfigBase { "multiplier used for calculating exhaustion from speed when a crank is turned."; static String maxBlocksMoved = "Maximum amount of blocks in a structure movable by Pistons, Bearings or other means."; - static String maxDataSize = "Maximum amount of data a contraption can have before it can't be synced with players."; - static String maxDataSize2 = "Un-synced contraptions will not be visible and will not have collision."; - static String maxDataDisable = "[0 to disable this limit]"; static String maxChassisRange = "Maximum value of a chassis attachment range."; static String maxPistonPoles = "Maximum amount of extension poles behind a Mechanical Piston."; static String maxRopeLength = "Max length of rope available off a Rope Pulley.";