diff --git a/paper-server/patches/sources/net/minecraft/core/component/DataComponentType.java.patch b/paper-server/patches/sources/net/minecraft/core/component/DataComponentType.java.patch new file mode 100644 index 0000000000..7f5323a432 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/core/component/DataComponentType.java.patch @@ -0,0 +1,25 @@ +--- a/net/minecraft/core/component/DataComponentType.java ++++ b/net/minecraft/core/component/DataComponentType.java +@@ -80,11 +_,11 @@ + static class SimpleType implements DataComponentType { + @Nullable + private final Codec codec; +- private final StreamCodec streamCodec; ++ private final java.util.function.Supplier> streamCodec; // Paper + + SimpleType(@Nullable Codec codec, StreamCodec streamCodec) { + this.codec = codec; +- this.streamCodec = streamCodec; ++ this.streamCodec = com.google.common.base.Suppliers.memoize(() -> io.papermc.paper.util.DataSanitizationUtil.get(this, streamCodec)); // Paper + } + + @Nullable +@@ -95,7 +_,7 @@ + + @Override + public StreamCodec streamCodec() { +- return this.streamCodec; ++ return this.streamCodec.get(); // Paper + } + + @Override diff --git a/paper-server/patches/sources/net/minecraft/core/component/DataComponents.java.patch b/paper-server/patches/sources/net/minecraft/core/component/DataComponents.java.patch deleted file mode 100644 index c1f061a0af..0000000000 --- a/paper-server/patches/sources/net/minecraft/core/component/DataComponents.java.patch +++ /dev/null @@ -1,24 +0,0 @@ ---- a/net/minecraft/core/component/DataComponents.java -+++ b/net/minecraft/core/component/DataComponents.java -@@ -180,10 +_,10 @@ - "map_post_processing", builder -> builder.networkSynchronized(MapPostProcessing.STREAM_CODEC) - ); - public static final DataComponentType CHARGED_PROJECTILES = register( -- "charged_projectiles", builder -> builder.persistent(ChargedProjectiles.CODEC).networkSynchronized(ChargedProjectiles.STREAM_CODEC).cacheEncoding() -+ "charged_projectiles", builder -> builder.persistent(ChargedProjectiles.CODEC).networkSynchronized(io.papermc.paper.util.DataSanitizationUtil.CHARGED_PROJECTILES).cacheEncoding() // Paper - sanitize charged projectiles - ); - public static final DataComponentType BUNDLE_CONTENTS = register( -- "bundle_contents", builder -> builder.persistent(BundleContents.CODEC).networkSynchronized(BundleContents.STREAM_CODEC).cacheEncoding() -+ "bundle_contents", builder -> builder.persistent(BundleContents.CODEC).networkSynchronized(io.papermc.paper.util.DataSanitizationUtil.BUNDLE_CONTENTS).cacheEncoding() // Paper - sanitize bundle contents - ); - public static final DataComponentType POTION_CONTENTS = register( - "potion_contents", builder -> builder.persistent(PotionContents.CODEC).networkSynchronized(PotionContents.STREAM_CODEC).cacheEncoding() -@@ -250,7 +_,7 @@ - "pot_decorations", builder -> builder.persistent(PotDecorations.CODEC).networkSynchronized(PotDecorations.STREAM_CODEC).cacheEncoding() - ); - public static final DataComponentType CONTAINER = register( -- "container", builder -> builder.persistent(ItemContainerContents.CODEC).networkSynchronized(ItemContainerContents.STREAM_CODEC).cacheEncoding() -+ "container", builder -> builder.persistent(ItemContainerContents.CODEC).networkSynchronized(io.papermc.paper.util.DataSanitizationUtil.CONTAINER).cacheEncoding() // Paper - sanitize container contents - ); - public static final DataComponentType BLOCK_STATE = register( - "block_state", builder -> builder.persistent(BlockItemStateProperties.CODEC).networkSynchronized(BlockItemStateProperties.STREAM_CODEC).cacheEncoding() diff --git a/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch b/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch index 2bd24dd054..d2ba980157 100644 --- a/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/item/ItemStack.java.patch @@ -21,14 +21,15 @@ + if (value.isEmpty() || value.getItem() == null) { // CraftBukkit - NPE fix itemstack.getItem() buffer.writeVarInt(0); } else { - buffer.writeVarInt(value.getCount()); +- buffer.writeVarInt(value.getCount()); ++ buffer.writeVarInt(io.papermc.paper.util.DataSanitizationUtil.sanitizeCount(value, value.getCount())); // Paper ITEM_STREAM_CODEC.encode(buffer, value.getItemHolder()); + // Spigot start - filter + // value = value.copy(); + // CraftItemStack.setItemMeta(value, CraftItemStack.getItemMeta(value)); // Paper - This is no longer with raw NBT being handled in metadata + // Paper start - adventure; conditionally render translatable components + boolean prev = net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.get(); -+ try { ++ try (io.papermc.paper.util.DataSanitizationUtil.SafeAutoClosable unused = io.papermc.paper.util.DataSanitizationUtil.passContext(value)) { + net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.set(true); DataComponentPatch.STREAM_CODEC.encode(buffer, value.components.asPatch()); + } finally { diff --git a/paper-server/src/main/java/io/papermc/paper/configuration/Configurations.java b/paper-server/src/main/java/io/papermc/paper/configuration/Configurations.java index 109e569b7b..4a9258b62d 100644 --- a/paper-server/src/main/java/io/papermc/paper/configuration/Configurations.java +++ b/paper-server/src/main/java/io/papermc/paper/configuration/Configurations.java @@ -80,7 +80,7 @@ public abstract class Configurations { } @MustBeInvokedByOverriders - protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder() { + protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder(RegistryAccess registryAccess) { return this.createLoaderBuilder(); } @@ -104,7 +104,7 @@ public abstract class Configurations { } public G initializeGlobalConfiguration(final RegistryAccess registryAccess) throws ConfigurateException { - return this.initializeGlobalConfiguration(creator(this.globalConfigClass, true)); + return this.initializeGlobalConfiguration(registryAccess, creator(this.globalConfigClass, true)); } private void trySaveFileNode(YamlConfigurationLoader loader, ConfigurationNode node, String filename) throws ConfigurateException { @@ -117,9 +117,9 @@ public abstract class Configurations { } } - protected G initializeGlobalConfiguration(final CheckedFunction creator) throws ConfigurateException { + protected G initializeGlobalConfiguration(final RegistryAccess registryAccess, final CheckedFunction creator) throws ConfigurateException { final Path configFile = this.globalFolder.resolve(this.globalConfigFileName); - final YamlConfigurationLoader loader = this.createGlobalLoaderBuilder() + final YamlConfigurationLoader loader = this.createGlobalLoaderBuilder(registryAccess) .defaultOptions(this.applyObjectMapperFactory(this.createGlobalObjectMapperFactoryBuilder().build())) .path(configFile) .build(); diff --git a/paper-server/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/paper-server/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java index 088b8fe5d1..dde005a228 100644 --- a/paper-server/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +++ b/paper-server/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java @@ -7,8 +7,13 @@ import io.papermc.paper.configuration.type.number.DoubleOr; import io.papermc.paper.configuration.type.number.IntOr; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import net.minecraft.Util; +import net.minecraft.core.component.DataComponentType; +import net.minecraft.core.component.DataComponents; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ServerboundPlaceRecipePacket; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.spongepowered.configurate.objectmapping.ConfigSerializable; @@ -16,10 +21,13 @@ import org.spongepowered.configurate.objectmapping.meta.Comment; import org.spongepowered.configurate.objectmapping.meta.PostProcess; import org.spongepowered.configurate.objectmapping.meta.Required; import org.spongepowered.configurate.objectmapping.meta.Setting; +import org.spongepowered.configurate.serialize.SerializationException; +import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.OptionalInt; +import java.util.Set; @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"}) public class GlobalConfiguration extends ConfigurationPart { @@ -354,4 +362,68 @@ public class GlobalConfiguration extends ConfigurationPart { public boolean disableChorusPlantUpdates = false; public boolean disableMushroomBlockUpdates = false; } + + public Anticheat anticheat; + + public class Anticheat extends ConfigurationPart { + + public Obfuscation obfuscation; + + public class Obfuscation extends ConfigurationPart { + public Items items = new Items(); + + public class Items extends ConfigurationPart { + public static Set> OVERRIDEN_TYPES = new HashSet<>(); + + public boolean enableItemObfuscation = false; + public AssetObfuscationConfiguration allModels = new AssetObfuscationConfiguration(true, + Set.of( + DataComponents.MAX_STACK_SIZE, + DataComponents.MAX_DAMAGE, + DataComponents.DAMAGE, + DataComponents.UNBREAKABLE, + DataComponents.CUSTOM_NAME, + DataComponents.ITEM_NAME, + DataComponents.LORE, + DataComponents.RARITY, + DataComponents.ENCHANTMENTS, + DataComponents.ATTRIBUTE_MODIFIERS, + DataComponents.CUSTOM_DATA, + DataComponents.WRITABLE_BOOK_CONTENT, + DataComponents.WRITTEN_BOOK_CONTENT, + DataComponents.MAP_ID, + DataComponents.MAP_DECORATIONS, + DataComponents.MAP_POST_PROCESSING, + DataComponents.LODESTONE_TRACKER + ) + ); + public Map modelOverrides = Map.of( + net.minecraft.world.item.Items.ELYTRA.components().get(DataComponents.ITEM_MODEL).toString(), new AssetObfuscationConfiguration( + true, Util.make(new HashSet<>(), (obj) -> { + obj.addAll(allModels.sanitizedComponents()); + obj.remove(DataComponents.DAMAGE); // don't sanitize damage for elytra + } + ) + )); + + public AssetObfuscationConfiguration getAssetObfuscation(ItemStack resourceLocation) { + return this.modelOverrides.getOrDefault(resourceLocation.get(DataComponents.ITEM_MODEL).toString(), this.allModels); // todo make optimized resourcelocation lookup + } + + @ConfigSerializable + public record AssetObfuscationConfiguration(boolean sanitizeCount, Set> sanitizedComponents) { + } + + @PostProcess + public void computeOverridenTypes() { + OVERRIDEN_TYPES.addAll(this.allModels.sanitizedComponents); + for (AssetObfuscationConfiguration configuration : this.modelOverrides.values()) { + OVERRIDEN_TYPES.addAll(configuration.sanitizedComponents); + } + } + } + + } + + } } diff --git a/paper-server/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java b/paper-server/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java index 098ab351de..af9ce08f4f 100644 --- a/paper-server/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java +++ b/paper-server/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java @@ -48,6 +48,7 @@ import java.util.List; import java.util.function.Function; import java.util.function.Supplier; import net.minecraft.core.RegistryAccess; +import net.minecraft.core.component.DataComponentType; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; @@ -193,16 +194,17 @@ public class PaperConfigurations extends Configurations defaultGlobalOptions(registryAccess, options)); } - private static ConfigurationOptions defaultGlobalOptions(ConfigurationOptions options) { + private static ConfigurationOptions defaultGlobalOptions(RegistryAccess registryAccess, ConfigurationOptions options) { return options .header(GLOBAL_HEADER) .serializers(builder -> builder .register(new PacketClassSerializer()) + .register(new RegistryValueSerializer<>(new TypeToken>() {}, registryAccess, Registries.DATA_COMPONENT_TYPE, false)) ); } @@ -316,7 +318,7 @@ public class PaperConfigurations extends Configurations builder.register(type -> ConfigurationPart.class.isAssignableFrom(erase(type)), factory.asTypeSerializer())); return BasicConfigurationNode.root(options); } diff --git a/paper-server/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java b/paper-server/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java index b1c917d650..845b0ac8b1 100644 --- a/paper-server/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java +++ b/paper-server/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java @@ -88,17 +88,6 @@ public class WorldConfiguration extends ConfigurationPart { public class Anticheat extends ConfigurationPart { - public Obfuscation obfuscation; - - public class Obfuscation extends ConfigurationPart { - public Items items = new Items(); - public class Items extends ConfigurationPart { - public boolean hideItemmeta = false; - public boolean hideDurability = false; - public boolean hideItemmetaWithVisualEffects = false; - } - } - public AntiXray antiXray; public class AntiXray extends ConfigurationPart { diff --git a/paper-server/src/main/java/io/papermc/paper/util/DataSanitizationUtil.java b/paper-server/src/main/java/io/papermc/paper/util/DataSanitizationUtil.java index 72483dedd3..91afe7001a 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/DataSanitizationUtil.java +++ b/paper-server/src/main/java/io/papermc/paper/util/DataSanitizationUtil.java @@ -1,19 +1,34 @@ package io.papermc.paper.util; +import java.io.Closeable; +import java.io.IOException; import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.UnaryOperator; +import com.google.common.base.Preconditions; +import io.papermc.paper.configuration.GlobalConfiguration; import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.core.component.DataComponentType; +import net.minecraft.core.component.DataComponents; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Mth; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.item.component.BundleContents; import net.minecraft.world.item.component.ChargedProjectiles; import net.minecraft.world.item.component.ItemContainerContents; +import net.minecraft.world.item.component.LodestoneTracker; +import net.minecraft.world.item.enchantment.ItemEnchantments; +import net.minecraft.world.level.Level; import org.apache.commons.lang3.math.Fraction; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.qual.DefaultQualifier; @DefaultQualifier(NonNull.class) @@ -29,9 +44,41 @@ public final class DataSanitizationUtil { return sanitizer; } - public static final StreamCodec CHARGED_PROJECTILES = codec(ChargedProjectiles.STREAM_CODEC, DataSanitizationUtil::sanitizeChargedProjectiles); - public static final StreamCodec BUNDLE_CONTENTS = codec(BundleContents.STREAM_CODEC, DataSanitizationUtil::sanitizeBundleContents); - public static final StreamCodec CONTAINER = codec(ItemContainerContents.STREAM_CODEC, contents -> ItemContainerContents.EMPTY); + public static SafeAutoClosable passContext(final ItemStack itemStack) { + final DataSanitizer sanitizer = DATA_SANITIZER.get(); + if (!sanitizer.isSanitizing()) { + return () -> {}; // Dont pass any context + } + + return new ContentScope(sanitizer.scope.get(), itemStack); + } + + + public static final Map, StreamCodec> BUILT_IN_CODEC_OVERRIDES = Map.of( + DataComponents.CHARGED_PROJECTILES, codec(ChargedProjectiles.STREAM_CODEC, DataSanitizationUtil::sanitizeChargedProjectiles), + DataComponents.BUNDLE_CONTENTS, codec(BundleContents.STREAM_CODEC, DataSanitizationUtil::sanitizeBundleContents), + DataComponents.CONTAINER, codec(ItemContainerContents.STREAM_CODEC, contents -> ItemContainerContents.EMPTY) + ); + + public static final Map, Object> EMPTY_MAP = Map.of( + DataComponents.LODESTONE_TRACKER, new LodestoneTracker(Optional.empty(), false) + ); + + + public static StreamCodec get(DataComponentType componentType, StreamCodec vanillaCodec) { + // First check do we have our built in overrides, if so, override it. + if (BUILT_IN_CODEC_OVERRIDES.containsKey(componentType)) { + return (StreamCodec) BUILT_IN_CODEC_OVERRIDES.get(componentType); + } + + // Now check the obfuscation, where we lookup if the type is overriden in any of the configurations, if so, wrap the codec + GlobalConfiguration.Anticheat.Obfuscation.Items obfuscation = GlobalConfiguration.get().anticheat.obfuscation.items; + if (obfuscation.enableItemObfuscation && GlobalConfiguration.Anticheat.Obfuscation.Items.OVERRIDEN_TYPES.contains(componentType)) { + return new DataSanitizationCodec<>(vanillaCodec, new DefaultDataComponentSanitizer<>(componentType, EMPTY_MAP.get(componentType))); + } + + return vanillaCodec; + } private static ChargedProjectiles sanitizeChargedProjectiles(final ChargedProjectiles projectiles) { if (projectiles.isEmpty()) { @@ -65,11 +112,37 @@ public final class DataSanitizationUtil { // Ensure that potentially overstacked bundles are not represented by empty (count=0) itemstacks. return new BundleContents(sanitizedRepresentation); } - private static StreamCodec codec(final StreamCodec delegate, final UnaryOperator sanitizer) { return new DataSanitizationCodec<>(delegate, sanitizer); } + public static int sanitizeCount(ItemStack itemStack, int count) { + GlobalConfiguration.Anticheat.Obfuscation.Items items = GlobalConfiguration.get().anticheat.obfuscation.items; + if (items.enableItemObfuscation && items.getAssetObfuscation(itemStack).sanitizeCount()) { + return 1; + } else { + return count; + } + } + + + private record DefaultDataComponentSanitizer(DataComponentType type, @Nullable Object override) implements UnaryOperator { + + @Override + public A apply(final A oldvalue) { + ContentScope scope = DATA_SANITIZER.get().scope.get(); + ItemStack targetItemstack = scope.itemStack; + // Does this asset override this component? If not, return oldValue. + if (!GlobalConfiguration.get().anticheat.obfuscation.items.getAssetObfuscation(targetItemstack).sanitizedComponents().contains(this.type)) { + return oldvalue; + } + + // We don't need to check if its overriden because we KNOW it is if we are serializing it over to the client. + + return (A) scope.itemStack.getPrototype().getOrDefault(this.type, this.override == null ? oldvalue : this.override); + } + } + private record DataSanitizationCodec(StreamCodec delegate, UnaryOperator sanitizer) implements StreamCodec { @Override @@ -79,7 +152,7 @@ public final class DataSanitizationUtil { @Override public void encode(final @NonNull B buf, final @NonNull A value) { - if (!DATA_SANITIZER.get().value().get()) { + if (!DATA_SANITIZER.get().isSanitizing()) { this.delegate.encode(buf, value); } else { this.delegate.encode(buf, this.sanitizer.apply(value)); @@ -87,10 +160,10 @@ public final class DataSanitizationUtil { } } - public record DataSanitizer(AtomicBoolean value) implements AutoCloseable { + public record DataSanitizer(AtomicBoolean value, AtomicReference scope) implements SafeAutoClosable { public DataSanitizer() { - this(new AtomicBoolean(false)); + this(new AtomicBoolean(false), new AtomicReference<>()); } public void start() { @@ -101,6 +174,27 @@ public final class DataSanitizationUtil { public void close() { this.value.compareAndSet(true, false); } + + public boolean isSanitizing() { + return value().get(); + } + } + + public record ContentScope(@Nullable ContentScope oldScope, ItemStack itemStack) implements SafeAutoClosable { + public ContentScope { + Preconditions.checkNotNull(DATA_SANITIZER.get(), "Expected data santizier content available"); + } + + @Override + public void close() { + DATA_SANITIZER.get().scope().set(this.oldScope); + } + } + + public interface SafeAutoClosable extends AutoCloseable { + + @Override + void close(); } private DataSanitizationUtil() {