mirror of
https://github.com/PaperMC/Paper.git
synced 2025-08-16 04:33:56 -07:00
Reimplement ItemStack Obfuscation (#11817)
Reimplementation of the itemstack obfuscation config that leverages the component patch map codec to drop unwanted components on items or replaces them with sanitized versions. Co-authored-by: Bjarne Koll <git@lynxplay.dev> Co-authored-by: Jake Potrebic <jake.m.potrebic@gmail.com>
This commit is contained in:
@@ -1,5 +1,34 @@
|
|||||||
--- a/net/minecraft/core/component/DataComponentPatch.java
|
--- a/net/minecraft/core/component/DataComponentPatch.java
|
||||||
+++ b/net/minecraft/core/component/DataComponentPatch.java
|
+++ b/net/minecraft/core/component/DataComponentPatch.java
|
||||||
|
@@ -86,6 +_,11 @@
|
||||||
|
buffer.writeVarInt(0);
|
||||||
|
buffer.writeVarInt(0);
|
||||||
|
} else {
|
||||||
|
+ // Paper start - data sanitization for items
|
||||||
|
+ final io.papermc.paper.util.ItemObfuscationSession itemObfuscationSession = value.map.isEmpty()
|
||||||
|
+ ? null // Avoid thread local lookup of current session if it won't be needed anyway.
|
||||||
|
+ : io.papermc.paper.util.ItemObfuscationSession.currentSession();
|
||||||
|
+ // Paper end - data sanitization for items
|
||||||
|
int i = 0;
|
||||||
|
int i1 = 0;
|
||||||
|
|
||||||
|
@@ -93,7 +_,7 @@
|
||||||
|
value.map
|
||||||
|
)) {
|
||||||
|
if (entry.getValue().isPresent()) {
|
||||||
|
- i++;
|
||||||
|
+ if (!io.papermc.paper.util.ItemComponentSanitizer.shouldDrop(itemObfuscationSession, entry.getKey())) i++; // Paper - data sanitization for items
|
||||||
|
} else {
|
||||||
|
i1++;
|
||||||
|
}
|
||||||
|
@@ -106,6 +_,7 @@
|
||||||
|
value.map
|
||||||
|
)) {
|
||||||
|
Optional<?> optional = entryx.getValue();
|
||||||
|
+ optional = io.papermc.paper.util.ItemComponentSanitizer.override(itemObfuscationSession, entryx.getKey(), entryx.getValue()); // Paper - data sanitization for items
|
||||||
|
if (optional.isPresent()) {
|
||||||
|
DataComponentType<?> dataComponentType = entryx.getKey();
|
||||||
|
DataComponentType.STREAM_CODEC.encode(buffer, dataComponentType);
|
||||||
@@ -125,7 +_,13 @@
|
@@ -125,7 +_,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,11 +5,11 @@
|
|||||||
);
|
);
|
||||||
public static final DataComponentType<ChargedProjectiles> CHARGED_PROJECTILES = register(
|
public static final DataComponentType<ChargedProjectiles> CHARGED_PROJECTILES = register(
|
||||||
- "charged_projectiles", builder -> builder.persistent(ChargedProjectiles.CODEC).networkSynchronized(ChargedProjectiles.STREAM_CODEC).cacheEncoding()
|
- "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
|
+ "charged_projectiles", builder -> builder.persistent(ChargedProjectiles.CODEC).networkSynchronized(io.papermc.paper.util.OversizedItemComponentSanitizer.CHARGED_PROJECTILES).cacheEncoding() // Paper - sanitize charged projectiles
|
||||||
);
|
);
|
||||||
public static final DataComponentType<BundleContents> BUNDLE_CONTENTS = register(
|
public static final DataComponentType<BundleContents> BUNDLE_CONTENTS = register(
|
||||||
- "bundle_contents", builder -> builder.persistent(BundleContents.CODEC).networkSynchronized(BundleContents.STREAM_CODEC).cacheEncoding()
|
- "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
|
+ "bundle_contents", builder -> builder.persistent(BundleContents.CODEC).networkSynchronized(io.papermc.paper.util.OversizedItemComponentSanitizer.BUNDLE_CONTENTS).cacheEncoding() // Paper - sanitize bundle contents
|
||||||
);
|
);
|
||||||
public static final DataComponentType<PotionContents> POTION_CONTENTS = register(
|
public static final DataComponentType<PotionContents> POTION_CONTENTS = register(
|
||||||
"potion_contents", builder -> builder.persistent(PotionContents.CODEC).networkSynchronized(PotionContents.STREAM_CODEC).cacheEncoding()
|
"potion_contents", builder -> builder.persistent(PotionContents.CODEC).networkSynchronized(PotionContents.STREAM_CODEC).cacheEncoding()
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
);
|
);
|
||||||
public static final DataComponentType<ItemContainerContents> CONTAINER = register(
|
public static final DataComponentType<ItemContainerContents> CONTAINER = register(
|
||||||
- "container", builder -> builder.persistent(ItemContainerContents.CODEC).networkSynchronized(ItemContainerContents.STREAM_CODEC).cacheEncoding()
|
- "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
|
+ "container", builder -> builder.persistent(ItemContainerContents.CODEC).networkSynchronized(io.papermc.paper.util.OversizedItemComponentSanitizer.CONTAINER).cacheEncoding() // Paper - sanitize container contents
|
||||||
);
|
);
|
||||||
public static final DataComponentType<BlockItemStateProperties> BLOCK_STATE = register(
|
public static final DataComponentType<BlockItemStateProperties> BLOCK_STATE = register(
|
||||||
"block_state", builder -> builder.persistent(BlockItemStateProperties.CODEC).networkSynchronized(BlockItemStateProperties.STREAM_CODEC).cacheEncoding()
|
"block_state", builder -> builder.persistent(BlockItemStateProperties.CODEC).networkSynchronized(BlockItemStateProperties.STREAM_CODEC).cacheEncoding()
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void pack(List<SynchedEntityData.DataValue<?>> dataValues, RegistryFriendlyByteBuf buffer) {
|
private static void pack(List<SynchedEntityData.DataValue<?>> dataValues, RegistryFriendlyByteBuf buffer) {
|
||||||
+ try (io.papermc.paper.util.DataSanitizationUtil.DataSanitizer ignored = io.papermc.paper.util.DataSanitizationUtil.start(true)) { // Paper - data sanitization
|
+ try (io.papermc.paper.util.ItemObfuscationSession ignored = io.papermc.paper.util.ItemObfuscationSession.start(io.papermc.paper.configuration.GlobalConfiguration.get().anticheat.obfuscation.items.binding.level)) { // Paper - data sanitization
|
||||||
for (SynchedEntityData.DataValue<?> dataValue : dataValues) {
|
for (SynchedEntityData.DataValue<?> dataValue : dataValues) {
|
||||||
dataValue.write(buffer);
|
dataValue.write(buffer);
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,7 @@
|
|||||||
buffer.writeVarInt(this.entity);
|
buffer.writeVarInt(this.entity);
|
||||||
int size = this.slots.size();
|
int size = this.slots.size();
|
||||||
|
|
||||||
+ try (io.papermc.paper.util.DataSanitizationUtil.DataSanitizer ignored = io.papermc.paper.util.DataSanitizationUtil.start(this.sanitize)) { // Paper - data sanitization
|
+ try (final io.papermc.paper.util.ItemObfuscationSession ignored = io.papermc.paper.util.ItemObfuscationSession.start(this.sanitize ? io.papermc.paper.configuration.GlobalConfiguration.get().anticheat.obfuscation.items.binding.level : io.papermc.paper.util.ItemObfuscationSession.ObfuscationLevel.NONE)) { // Paper - data sanitization
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
Pair<EquipmentSlot, ItemStack> pair = this.slots.get(i);
|
Pair<EquipmentSlot, ItemStack> pair = this.slots.get(i);
|
||||||
EquipmentSlot equipmentSlot = pair.getFirst();
|
EquipmentSlot equipmentSlot = pair.getFirst();
|
||||||
|
@@ -21,14 +21,15 @@
|
|||||||
+ if (value.isEmpty() || value.getItem() == null) { // CraftBukkit - NPE fix itemstack.getItem()
|
+ if (value.isEmpty() || value.getItem() == null) { // CraftBukkit - NPE fix itemstack.getItem()
|
||||||
buffer.writeVarInt(0);
|
buffer.writeVarInt(0);
|
||||||
} else {
|
} else {
|
||||||
buffer.writeVarInt(value.getCount());
|
- buffer.writeVarInt(value.getCount());
|
||||||
|
+ buffer.writeVarInt(io.papermc.paper.util.ItemComponentSanitizer.sanitizeCount(io.papermc.paper.util.ItemObfuscationSession.currentSession(), value, value.getCount())); // Paper - potentially sanitize count
|
||||||
ITEM_STREAM_CODEC.encode(buffer, value.getItemHolder());
|
ITEM_STREAM_CODEC.encode(buffer, value.getItemHolder());
|
||||||
+ // Spigot start - filter
|
+ // Spigot start - filter
|
||||||
+ // value = value.copy();
|
+ // value = value.copy();
|
||||||
+ // CraftItemStack.setItemMeta(value, CraftItemStack.getItemMeta(value)); // Paper - This is no longer with raw NBT being handled in metadata
|
+ // 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
|
+ // Paper start - adventure; conditionally render translatable components
|
||||||
+ boolean prev = net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.get();
|
+ boolean prev = net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.get();
|
||||||
+ try {
|
+ try (final io.papermc.paper.util.SafeAutoClosable ignored = io.papermc.paper.util.ItemObfuscationSession.withContext(c -> c.itemStack(value))) { // pass the itemstack as context to the obfuscation session
|
||||||
+ net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.set(true);
|
+ net.minecraft.network.chat.ComponentSerialization.DONT_RENDER_TRANSLATABLES.set(true);
|
||||||
DataComponentPatch.STREAM_CODEC.encode(buffer, value.components.asPatch());
|
DataComponentPatch.STREAM_CODEC.encode(buffer, value.components.asPatch());
|
||||||
+ } finally {
|
+ } finally {
|
||||||
|
@@ -80,7 +80,7 @@ public abstract class Configurations<G, W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@MustBeInvokedByOverriders
|
@MustBeInvokedByOverriders
|
||||||
protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder() {
|
protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder(RegistryAccess registryAccess) {
|
||||||
return this.createLoaderBuilder();
|
return this.createLoaderBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ public abstract class Configurations<G, W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public G initializeGlobalConfiguration(final RegistryAccess registryAccess) throws ConfigurateException {
|
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 {
|
private void trySaveFileNode(YamlConfigurationLoader loader, ConfigurationNode node, String filename) throws ConfigurateException {
|
||||||
@@ -117,9 +117,9 @@ public abstract class Configurations<G, W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected G initializeGlobalConfiguration(final CheckedFunction<ConfigurationNode, G, SerializationException> creator) throws ConfigurateException {
|
protected G initializeGlobalConfiguration(final RegistryAccess registryAccess, final CheckedFunction<ConfigurationNode, G, SerializationException> creator) throws ConfigurateException {
|
||||||
final Path configFile = this.globalFolder.resolve(this.globalConfigFileName);
|
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()))
|
.defaultOptions(this.applyObjectMapperFactory(this.createGlobalObjectMapperFactoryBuilder().build()))
|
||||||
.path(configFile)
|
.path(configFile)
|
||||||
.build();
|
.build();
|
||||||
|
@@ -5,10 +5,14 @@ import io.papermc.paper.FeatureHooks;
|
|||||||
import io.papermc.paper.configuration.constraint.Constraints;
|
import io.papermc.paper.configuration.constraint.Constraints;
|
||||||
import io.papermc.paper.configuration.type.number.DoubleOr;
|
import io.papermc.paper.configuration.type.number.DoubleOr;
|
||||||
import io.papermc.paper.configuration.type.number.IntOr;
|
import io.papermc.paper.configuration.type.number.IntOr;
|
||||||
|
import io.papermc.paper.util.ItemObfuscationBinding;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import net.minecraft.core.component.DataComponents;
|
||||||
import net.minecraft.network.protocol.Packet;
|
import net.minecraft.network.protocol.Packet;
|
||||||
import net.minecraft.network.protocol.game.ServerboundPlaceRecipePacket;
|
import net.minecraft.network.protocol.game.ServerboundPlaceRecipePacket;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.world.item.Items;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||||
@@ -20,6 +24,7 @@ import org.spongepowered.configurate.objectmapping.meta.Setting;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.OptionalInt;
|
import java.util.OptionalInt;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"})
|
@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"})
|
||||||
public class GlobalConfiguration extends ConfigurationPart {
|
public class GlobalConfiguration extends ConfigurationPart {
|
||||||
@@ -69,7 +74,7 @@ public class GlobalConfiguration extends ConfigurationPart {
|
|||||||
)
|
)
|
||||||
public int playerMaxConcurrentChunkGenerates = 0;
|
public int playerMaxConcurrentChunkGenerates = 0;
|
||||||
}
|
}
|
||||||
static void set(GlobalConfiguration instance) {
|
static void set(final GlobalConfiguration instance) {
|
||||||
GlobalConfiguration.instance = instance;
|
GlobalConfiguration.instance = instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,4 +359,41 @@ public class GlobalConfiguration extends ConfigurationPart {
|
|||||||
public boolean disableChorusPlantUpdates = false;
|
public boolean disableChorusPlantUpdates = false;
|
||||||
public boolean disableMushroomBlockUpdates = false;
|
public boolean disableMushroomBlockUpdates = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Anticheat anticheat;
|
||||||
|
|
||||||
|
public class Anticheat extends ConfigurationPart {
|
||||||
|
|
||||||
|
public Obfuscation obfuscation;
|
||||||
|
|
||||||
|
public class Obfuscation extends ConfigurationPart {
|
||||||
|
public Items items;
|
||||||
|
|
||||||
|
public class Items extends ConfigurationPart {
|
||||||
|
|
||||||
|
public boolean enableItemObfuscation = false;
|
||||||
|
public ItemObfuscationBinding.AssetObfuscationConfiguration allModels = new ItemObfuscationBinding.AssetObfuscationConfiguration(
|
||||||
|
true,
|
||||||
|
Set.of(DataComponents.LODESTONE_TRACKER),
|
||||||
|
Set.of()
|
||||||
|
);
|
||||||
|
|
||||||
|
public Map<ResourceLocation, ItemObfuscationBinding.AssetObfuscationConfiguration> modelOverrides = Map.of(
|
||||||
|
Objects.requireNonNull(net.minecraft.world.item.Items.ELYTRA.components().get(DataComponents.ITEM_MODEL)),
|
||||||
|
new ItemObfuscationBinding.AssetObfuscationConfiguration(
|
||||||
|
true,
|
||||||
|
Set.of(DataComponents.DAMAGE),
|
||||||
|
Set.of()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
public transient ItemObfuscationBinding binding;
|
||||||
|
|
||||||
|
@PostProcess
|
||||||
|
public void bindDataSanitizer() {
|
||||||
|
this.binding = new ItemObfuscationBinding(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,7 @@ import io.papermc.paper.configuration.serializer.ComponentSerializer;
|
|||||||
import io.papermc.paper.configuration.serializer.EnumValueSerializer;
|
import io.papermc.paper.configuration.serializer.EnumValueSerializer;
|
||||||
import io.papermc.paper.configuration.serializer.NbtPathSerializer;
|
import io.papermc.paper.configuration.serializer.NbtPathSerializer;
|
||||||
import io.papermc.paper.configuration.serializer.PacketClassSerializer;
|
import io.papermc.paper.configuration.serializer.PacketClassSerializer;
|
||||||
|
import io.papermc.paper.configuration.serializer.ResourceLocationSerializer;
|
||||||
import io.papermc.paper.configuration.serializer.StringRepresentableSerializer;
|
import io.papermc.paper.configuration.serializer.StringRepresentableSerializer;
|
||||||
import io.papermc.paper.configuration.serializer.collections.FastutilMapSerializer;
|
import io.papermc.paper.configuration.serializer.collections.FastutilMapSerializer;
|
||||||
import io.papermc.paper.configuration.serializer.collections.MapSerializer;
|
import io.papermc.paper.configuration.serializer.collections.MapSerializer;
|
||||||
@@ -48,6 +49,7 @@ import java.util.List;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import net.minecraft.core.RegistryAccess;
|
import net.minecraft.core.RegistryAccess;
|
||||||
|
import net.minecraft.core.component.DataComponentType;
|
||||||
import net.minecraft.core.registries.Registries;
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
@@ -180,6 +182,7 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
|
|||||||
.register(Duration.SERIALIZER)
|
.register(Duration.SERIALIZER)
|
||||||
.register(DurationOrDisabled.SERIALIZER)
|
.register(DurationOrDisabled.SERIALIZER)
|
||||||
.register(NbtPathSerializer.SERIALIZER)
|
.register(NbtPathSerializer.SERIALIZER)
|
||||||
|
.register(ResourceLocationSerializer.INSTANCE)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,16 +196,17 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder() {
|
protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder(RegistryAccess registryAccess) {
|
||||||
return super.createGlobalLoaderBuilder()
|
return super.createGlobalLoaderBuilder(registryAccess)
|
||||||
.defaultOptions(PaperConfigurations::defaultGlobalOptions);
|
.defaultOptions((options) -> defaultGlobalOptions(registryAccess, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ConfigurationOptions defaultGlobalOptions(ConfigurationOptions options) {
|
private static ConfigurationOptions defaultGlobalOptions(RegistryAccess registryAccess, ConfigurationOptions options) {
|
||||||
return options
|
return options
|
||||||
.header(GLOBAL_HEADER)
|
.header(GLOBAL_HEADER)
|
||||||
.serializers(builder -> builder
|
.serializers(builder -> builder
|
||||||
.register(new PacketClassSerializer())
|
.register(new PacketClassSerializer())
|
||||||
|
.register(new RegistryValueSerializer<>(new TypeToken<DataComponentType<?>>() {}, registryAccess, Registries.DATA_COMPONENT_TYPE, false))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,7 +320,7 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
|
|||||||
|
|
||||||
public void reloadConfigs(MinecraftServer server) {
|
public void reloadConfigs(MinecraftServer server) {
|
||||||
try {
|
try {
|
||||||
this.initializeGlobalConfiguration(reloader(this.globalConfigClass, GlobalConfiguration.get()));
|
this.initializeGlobalConfiguration(server.registryAccess(), reloader(this.globalConfigClass, GlobalConfiguration.get()));
|
||||||
this.initializeWorldDefaultsConfiguration(server.registryAccess());
|
this.initializeWorldDefaultsConfiguration(server.registryAccess());
|
||||||
for (ServerLevel level : server.getAllLevels()) {
|
for (ServerLevel level : server.getAllLevels()) {
|
||||||
this.createWorldConfig(createWorldContextMap(level), reloader(this.worldConfigClass, level.paperConfig()));
|
this.createWorldConfig(createWorldContextMap(level), reloader(this.worldConfigClass, level.paperConfig()));
|
||||||
@@ -454,9 +458,9 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
|
|||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static ConfigurationNode createForTesting() {
|
static ConfigurationNode createForTesting(RegistryAccess registryAccess) {
|
||||||
ObjectMapper.Factory factory = defaultGlobalFactoryBuilder(ObjectMapper.factoryBuilder()).build();
|
ObjectMapper.Factory factory = defaultGlobalFactoryBuilder(ObjectMapper.factoryBuilder()).build();
|
||||||
ConfigurationOptions options = defaultGlobalOptions(defaultOptions(ConfigurationOptions.defaults()))
|
ConfigurationOptions options = defaultGlobalOptions(registryAccess, defaultOptions(ConfigurationOptions.defaults()))
|
||||||
.serializers(builder -> builder.register(type -> ConfigurationPart.class.isAssignableFrom(erase(type)), factory.asTypeSerializer()));
|
.serializers(builder -> builder.register(type -> ConfigurationPart.class.isAssignableFrom(erase(type)), factory.asTypeSerializer()));
|
||||||
return BasicConfigurationNode.root(options);
|
return BasicConfigurationNode.root(options);
|
||||||
}
|
}
|
||||||
|
@@ -88,17 +88,6 @@ public class WorldConfiguration extends ConfigurationPart {
|
|||||||
|
|
||||||
public class Anticheat 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 AntiXray antiXray;
|
||||||
|
|
||||||
public class AntiXray extends ConfigurationPart {
|
public class AntiXray extends ConfigurationPart {
|
||||||
|
@@ -0,0 +1,26 @@
|
|||||||
|
package io.papermc.paper.configuration.serializer;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import org.spongepowered.configurate.serialize.ScalarSerializer;
|
||||||
|
import org.spongepowered.configurate.serialize.SerializationException;
|
||||||
|
|
||||||
|
public class ResourceLocationSerializer extends ScalarSerializer<ResourceLocation> {
|
||||||
|
|
||||||
|
public static final ScalarSerializer<ResourceLocation> INSTANCE = new ResourceLocationSerializer();
|
||||||
|
|
||||||
|
private ResourceLocationSerializer() {
|
||||||
|
super(ResourceLocation.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResourceLocation deserialize(final Type type, final Object obj) throws SerializationException {
|
||||||
|
return ResourceLocation.read(obj.toString()).getOrThrow(s -> new SerializationException(ResourceLocation.class, s));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object serialize(final ResourceLocation item, final Predicate<Class<?>> typeSupported) {
|
||||||
|
return item.toString();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,98 @@
|
|||||||
|
package io.papermc.paper.util;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import io.papermc.paper.configuration.GlobalConfiguration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
import net.minecraft.Util;
|
||||||
|
import net.minecraft.core.component.DataComponentType;
|
||||||
|
import net.minecraft.core.component.DataComponents;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.util.RandomSource;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.alchemy.PotionContents;
|
||||||
|
import net.minecraft.world.item.component.LodestoneTracker;
|
||||||
|
import net.minecraft.world.item.enchantment.ItemEnchantments;
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
|
||||||
|
@NullMarked
|
||||||
|
public final class ItemComponentSanitizer {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This returns for types, that when configured to be serialized, should instead return these objects.
|
||||||
|
* This is possibly because dropping the patched type may introduce visual changes.
|
||||||
|
*/
|
||||||
|
static final Map<DataComponentType<?>, UnaryOperator<?>> SANITIZATION_OVERRIDES = Util.make(ImmutableMap.<DataComponentType<?>, UnaryOperator<?>>builder(), (map) -> {
|
||||||
|
put(map, DataComponents.LODESTONE_TRACKER, empty(new LodestoneTracker(Optional.empty(), false))); // We need it to be present to keep the glint
|
||||||
|
put(map, DataComponents.ENCHANTMENTS, empty(dummyEnchantments())); // We need to keep it present to keep the glint
|
||||||
|
put(map, DataComponents.STORED_ENCHANTMENTS, empty(dummyEnchantments())); // We need to keep it present to keep the glint
|
||||||
|
put(map, DataComponents.POTION_CONTENTS, ItemComponentSanitizer::sanitizePotionContents); // Custom situational serialization
|
||||||
|
}
|
||||||
|
).build();
|
||||||
|
|
||||||
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
|
private static <T> void put(final ImmutableMap.Builder map, final DataComponentType<T> type, final UnaryOperator<T> object) {
|
||||||
|
map.put(type, object);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> UnaryOperator<T> empty(final T object) {
|
||||||
|
return (unused) -> object;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PotionContents sanitizePotionContents(final PotionContents potionContents) {
|
||||||
|
// We have a custom color! We can hide everything!
|
||||||
|
if (potionContents.customColor().isPresent()) {
|
||||||
|
return new PotionContents(Optional.empty(), potionContents.customColor(), List.of(), Optional.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// WE cannot hide anything really, as the color is a mix of potion/potion contents, which can
|
||||||
|
// possibly be reversed.
|
||||||
|
return potionContents;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We cant use the empty map from enchantments because we want to keep the glow
|
||||||
|
private static ItemEnchantments dummyEnchantments() {
|
||||||
|
final ItemEnchantments.Mutable obj = new ItemEnchantments.Mutable(ItemEnchantments.EMPTY);
|
||||||
|
obj.set(MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.ENCHANTMENT).getRandom(RandomSource.create()).orElseThrow(), 1);
|
||||||
|
return obj.toImmutable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int sanitizeCount(final ItemObfuscationSession obfuscationSession, final ItemStack itemStack, final int count) {
|
||||||
|
if (obfuscationSession.obfuscationLevel() != ItemObfuscationSession.ObfuscationLevel.ALL) return count; // Ignore if we are not obfuscating
|
||||||
|
|
||||||
|
if (GlobalConfiguration.get().anticheat.obfuscation.items.binding.getAssetObfuscation(itemStack).sanitizeCount()) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean shouldDrop(final ItemObfuscationSession obfuscationSession, final DataComponentType<?> key) {
|
||||||
|
if (obfuscationSession.obfuscationLevel() != ItemObfuscationSession.ObfuscationLevel.ALL) return false; // Ignore if we are not obfuscating
|
||||||
|
|
||||||
|
final ItemStack targetItemstack = obfuscationSession.context().itemStack();
|
||||||
|
|
||||||
|
// Only drop if configured to do so.
|
||||||
|
return GlobalConfiguration.get().anticheat.obfuscation.items.binding.getAssetObfuscation(targetItemstack).patchStrategy().get(key) == ItemObfuscationBinding.BoundObfuscationConfiguration.MutationType.Drop.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<?> override(final ItemObfuscationSession obfuscationSession, final DataComponentType<?> key, final Optional<?> value) {
|
||||||
|
if (obfuscationSession.obfuscationLevel() != ItemObfuscationSession.ObfuscationLevel.ALL) return value; // Ignore if we are not obfuscating
|
||||||
|
|
||||||
|
// Ignore removed values
|
||||||
|
if (value.isEmpty()) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ItemStack targetItemstack = obfuscationSession.context().itemStack();
|
||||||
|
|
||||||
|
return switch (GlobalConfiguration.get().anticheat.obfuscation.items.binding.getAssetObfuscation(targetItemstack).patchStrategy().get(key)) {
|
||||||
|
case final ItemObfuscationBinding.BoundObfuscationConfiguration.MutationType.Drop ignored -> Optional.empty();
|
||||||
|
case final ItemObfuscationBinding.BoundObfuscationConfiguration.MutationType.Sanitize sanitize -> Optional.of(sanitize.sanitizer().apply(value.get()));
|
||||||
|
case null -> value;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,133 @@
|
|||||||
|
package io.papermc.paper.util;
|
||||||
|
|
||||||
|
import io.papermc.paper.configuration.GlobalConfiguration;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
import net.minecraft.core.component.DataComponentType;
|
||||||
|
import net.minecraft.core.component.DataComponents;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||||
|
import org.spongepowered.configurate.objectmapping.meta.Required;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The item obfuscation binding is a state bound by the configured item obfuscation.
|
||||||
|
* It only hosts the bound and computed data from the global configuration.
|
||||||
|
*/
|
||||||
|
@NullMarked
|
||||||
|
public final class ItemObfuscationBinding {
|
||||||
|
|
||||||
|
public final ItemObfuscationSession.ObfuscationLevel level;
|
||||||
|
private final BoundObfuscationConfiguration base;
|
||||||
|
private final Map<ResourceLocation, BoundObfuscationConfiguration> overrides;
|
||||||
|
|
||||||
|
public ItemObfuscationBinding(final GlobalConfiguration.Anticheat.Obfuscation.Items items) {
|
||||||
|
this.level = items.enableItemObfuscation ? ItemObfuscationSession.ObfuscationLevel.ALL : ItemObfuscationSession.ObfuscationLevel.OVERSIZED;
|
||||||
|
this.base = bind(items.allModels);
|
||||||
|
final Map<ResourceLocation, BoundObfuscationConfiguration> overrides = new HashMap<>();
|
||||||
|
for (final Map.Entry<ResourceLocation, AssetObfuscationConfiguration> entry : items.modelOverrides.entrySet()) {
|
||||||
|
overrides.put(entry.getKey(), bind(entry.getValue()));
|
||||||
|
}
|
||||||
|
this.overrides = Collections.unmodifiableMap(overrides);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record BoundObfuscationConfiguration(boolean sanitizeCount,
|
||||||
|
Map<DataComponentType<?>, MutationType> patchStrategy) {
|
||||||
|
|
||||||
|
sealed interface MutationType permits MutationType.Drop, MutationType.Sanitize {
|
||||||
|
enum Drop implements MutationType {
|
||||||
|
INSTANCE
|
||||||
|
}
|
||||||
|
|
||||||
|
record Sanitize(UnaryOperator sanitizer) implements MutationType {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConfigSerializable
|
||||||
|
public record AssetObfuscationConfiguration(@Required boolean sanitizeCount,
|
||||||
|
Set<DataComponentType<?>> dontObfuscate,
|
||||||
|
Set<DataComponentType<?>> alsoObfuscate) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BoundObfuscationConfiguration bind(final AssetObfuscationConfiguration config) {
|
||||||
|
final Set<DataComponentType<?>> base = new HashSet<>(BASE_OVERRIDERS);
|
||||||
|
base.addAll(config.alsoObfuscate());
|
||||||
|
base.removeAll(config.dontObfuscate());
|
||||||
|
|
||||||
|
final Map<DataComponentType<?>, BoundObfuscationConfiguration.MutationType> finalStrategy = new HashMap<>();
|
||||||
|
// Configure what path the data component should go through, should it be dropped, or should it be sanitized?
|
||||||
|
for (final DataComponentType<?> type : base) {
|
||||||
|
// We require some special logic, sanitize it rather than dropping it.
|
||||||
|
final UnaryOperator<?> sanitizationOverride = ItemComponentSanitizer.SANITIZATION_OVERRIDES.get(type);
|
||||||
|
if (sanitizationOverride != null) {
|
||||||
|
finalStrategy.put(type, new BoundObfuscationConfiguration.MutationType.Sanitize(sanitizationOverride));
|
||||||
|
} else {
|
||||||
|
finalStrategy.put(type, BoundObfuscationConfiguration.MutationType.Drop.INSTANCE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BoundObfuscationConfiguration(config.sanitizeCount(), finalStrategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BoundObfuscationConfiguration getAssetObfuscation(final ItemStack itemStack) {
|
||||||
|
if (this.overrides.isEmpty()) {
|
||||||
|
return this.base;
|
||||||
|
}
|
||||||
|
return this.overrides.getOrDefault(itemStack.get(DataComponents.ITEM_MODEL), this.base);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final Set<DataComponentType<?>> BASE_OVERRIDERS = 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.CAN_PLACE_ON,
|
||||||
|
DataComponents.CAN_BREAK,
|
||||||
|
DataComponents.ATTRIBUTE_MODIFIERS,
|
||||||
|
DataComponents.HIDE_ADDITIONAL_TOOLTIP,
|
||||||
|
DataComponents.HIDE_TOOLTIP,
|
||||||
|
DataComponents.REPAIR_COST,
|
||||||
|
DataComponents.USE_REMAINDER,
|
||||||
|
DataComponents.FOOD,
|
||||||
|
DataComponents.DAMAGE_RESISTANT,
|
||||||
|
// Not important on the player
|
||||||
|
DataComponents.TOOL,
|
||||||
|
DataComponents.ENCHANTABLE,
|
||||||
|
DataComponents.REPAIRABLE,
|
||||||
|
DataComponents.GLIDER,
|
||||||
|
DataComponents.TOOLTIP_STYLE,
|
||||||
|
DataComponents.DEATH_PROTECTION,
|
||||||
|
DataComponents.STORED_ENCHANTMENTS,
|
||||||
|
DataComponents.MAP_ID,
|
||||||
|
DataComponents.POTION_CONTENTS,
|
||||||
|
DataComponents.SUSPICIOUS_STEW_EFFECTS,
|
||||||
|
DataComponents.WRITABLE_BOOK_CONTENT,
|
||||||
|
DataComponents.WRITTEN_BOOK_CONTENT,
|
||||||
|
DataComponents.CUSTOM_DATA,
|
||||||
|
DataComponents.ENTITY_DATA,
|
||||||
|
DataComponents.BUCKET_ENTITY_DATA,
|
||||||
|
DataComponents.BLOCK_ENTITY_DATA,
|
||||||
|
DataComponents.INSTRUMENT,
|
||||||
|
DataComponents.OMINOUS_BOTTLE_AMPLIFIER,
|
||||||
|
DataComponents.JUKEBOX_PLAYABLE,
|
||||||
|
DataComponents.LODESTONE_TRACKER,
|
||||||
|
DataComponents.FIREWORKS,
|
||||||
|
DataComponents.NOTE_BLOCK_SOUND,
|
||||||
|
DataComponents.BEES,
|
||||||
|
DataComponents.LOCK,
|
||||||
|
DataComponents.CONTAINER_LOOT
|
||||||
|
);
|
||||||
|
}
|
@@ -0,0 +1,114 @@
|
|||||||
|
package io.papermc.paper.util;
|
||||||
|
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The item obfuscation session may be started by a thread to indicate that items should be obfuscated when serialized
|
||||||
|
* for network usage.
|
||||||
|
* <p>
|
||||||
|
* A session is persistent throughout an entire thread and will be "activated" by passing an {@link ObfuscationContext}
|
||||||
|
* to start/switch context methods.
|
||||||
|
*/
|
||||||
|
@NullMarked
|
||||||
|
public class ItemObfuscationSession implements SafeAutoClosable {
|
||||||
|
|
||||||
|
static final ThreadLocal<ItemObfuscationSession> THREAD_LOCAL_SESSION = ThreadLocal.withInitial(ItemObfuscationSession::new);
|
||||||
|
|
||||||
|
public static ItemObfuscationSession currentSession() {
|
||||||
|
return THREAD_LOCAL_SESSION.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obfuscation level on a specific context.
|
||||||
|
*/
|
||||||
|
public enum ObfuscationLevel {
|
||||||
|
NONE,
|
||||||
|
OVERSIZED,
|
||||||
|
ALL;
|
||||||
|
|
||||||
|
public boolean obfuscateOversized() {
|
||||||
|
return switch (this) {
|
||||||
|
case OVERSIZED, ALL -> true;
|
||||||
|
default -> false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isObfuscating() {
|
||||||
|
return this != NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ItemObfuscationSession start(final ObfuscationLevel level) {
|
||||||
|
final ItemObfuscationSession sanitizer = THREAD_LOCAL_SESSION.get();
|
||||||
|
sanitizer.switchContext(new ObfuscationContext(sanitizer, null, null, level));
|
||||||
|
return sanitizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the context of the currently running session by requiring the unary operator to emit a new context
|
||||||
|
* based on the current one.
|
||||||
|
* The method expects the caller to use the withers on the context.
|
||||||
|
*
|
||||||
|
* @param contextUpdater the operator to construct the new context.
|
||||||
|
* @return the context callback to close once the context expires.
|
||||||
|
*/
|
||||||
|
public static SafeAutoClosable withContext(final UnaryOperator<ObfuscationContext> contextUpdater) {
|
||||||
|
final ItemObfuscationSession session = THREAD_LOCAL_SESSION.get();
|
||||||
|
|
||||||
|
// Don't pass any context if we are not currently sanitizing
|
||||||
|
if (!session.obfuscationLevel().isObfuscating()) return () -> {
|
||||||
|
};
|
||||||
|
|
||||||
|
final ObfuscationContext newContext = contextUpdater.apply(session.context());
|
||||||
|
Preconditions.checkState(newContext != session.context(), "withContext yielded same context instance, this will break the stack on close");
|
||||||
|
session.switchContext(newContext);
|
||||||
|
return newContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ObfuscationContext root = new ObfuscationContext(this, null, null, ObfuscationLevel.NONE);
|
||||||
|
private ObfuscationContext context = root;
|
||||||
|
|
||||||
|
public void switchContext(final ObfuscationContext context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObfuscationContext context() {
|
||||||
|
return this.context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
this.context = root;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObfuscationLevel obfuscationLevel() {
|
||||||
|
return this.context.level;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ObfuscationContext(
|
||||||
|
ItemObfuscationSession parent,
|
||||||
|
@Nullable ObfuscationContext previousContext,
|
||||||
|
@Nullable ItemStack itemStack,
|
||||||
|
ObfuscationLevel level
|
||||||
|
) implements SafeAutoClosable {
|
||||||
|
|
||||||
|
public ObfuscationContext itemStack(final ItemStack itemStack) {
|
||||||
|
return new ObfuscationContext(this.parent, this, itemStack, this.level);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObfuscationContext level(final ObfuscationLevel obfuscationLevel) {
|
||||||
|
return new ObfuscationContext(this.parent, this, this.itemStack, obfuscationLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
// Restore the previous context when this context is closed.
|
||||||
|
this.parent().switchContext(this.previousContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,9 +1,8 @@
|
|||||||
package io.papermc.paper.util;
|
package io.papermc.paper.util;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.function.UnaryOperator;
|
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||||
import net.minecraft.network.codec.StreamCodec;
|
import net.minecraft.network.codec.StreamCodec;
|
||||||
import net.minecraft.util.Mth;
|
import net.minecraft.util.Mth;
|
||||||
@@ -12,26 +11,38 @@ import net.minecraft.world.item.Items;
|
|||||||
import net.minecraft.world.item.component.BundleContents;
|
import net.minecraft.world.item.component.BundleContents;
|
||||||
import net.minecraft.world.item.component.ChargedProjectiles;
|
import net.minecraft.world.item.component.ChargedProjectiles;
|
||||||
import net.minecraft.world.item.component.ItemContainerContents;
|
import net.minecraft.world.item.component.ItemContainerContents;
|
||||||
import org.apache.commons.lang3.math.Fraction;
|
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
|
||||||
|
|
||||||
@DefaultQualifier(NonNull.class)
|
public final class OversizedItemComponentSanitizer {
|
||||||
public final class DataSanitizationUtil {
|
|
||||||
|
|
||||||
private static final ThreadLocal<DataSanitizer> DATA_SANITIZER = ThreadLocal.withInitial(DataSanitizer::new);
|
/*
|
||||||
|
These represent codecs that are meant to help get rid of possibly big items by ALWAYS hiding this data.
|
||||||
public static DataSanitizer start(final boolean sanitize) {
|
*/
|
||||||
final DataSanitizer sanitizer = DATA_SANITIZER.get();
|
public static final StreamCodec<RegistryFriendlyByteBuf, ChargedProjectiles> CHARGED_PROJECTILES = codec(ChargedProjectiles.STREAM_CODEC, OversizedItemComponentSanitizer::sanitizeChargedProjectiles);
|
||||||
if (sanitize) {
|
|
||||||
sanitizer.start();
|
|
||||||
}
|
|
||||||
return sanitizer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final StreamCodec<RegistryFriendlyByteBuf, ChargedProjectiles> CHARGED_PROJECTILES = codec(ChargedProjectiles.STREAM_CODEC, DataSanitizationUtil::sanitizeChargedProjectiles);
|
|
||||||
public static final StreamCodec<RegistryFriendlyByteBuf, BundleContents> BUNDLE_CONTENTS = codec(BundleContents.STREAM_CODEC, DataSanitizationUtil::sanitizeBundleContents);
|
|
||||||
public static final StreamCodec<RegistryFriendlyByteBuf, ItemContainerContents> CONTAINER = codec(ItemContainerContents.STREAM_CODEC, contents -> ItemContainerContents.EMPTY);
|
public static final StreamCodec<RegistryFriendlyByteBuf, ItemContainerContents> CONTAINER = codec(ItemContainerContents.STREAM_CODEC, contents -> ItemContainerContents.EMPTY);
|
||||||
|
public static final StreamCodec<RegistryFriendlyByteBuf, BundleContents> BUNDLE_CONTENTS = new StreamCodec<>() {
|
||||||
|
@Override
|
||||||
|
public BundleContents decode(final RegistryFriendlyByteBuf buffer) {
|
||||||
|
return BundleContents.STREAM_CODEC.decode(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encode(final RegistryFriendlyByteBuf buffer, final BundleContents value) {
|
||||||
|
if (!ItemObfuscationSession.currentSession().obfuscationLevel().obfuscateOversized()) {
|
||||||
|
BundleContents.STREAM_CODEC.encode(buffer, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable further obfuscation to skip e.g. count.
|
||||||
|
try (final SafeAutoClosable ignored = ItemObfuscationSession.withContext(c -> c.level(ItemObfuscationSession.ObfuscationLevel.OVERSIZED))){
|
||||||
|
BundleContents.STREAM_CODEC.encode(buffer, sanitizeBundleContents(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static <B, A> StreamCodec<B, A> codec(final StreamCodec<B, A> delegate, final UnaryOperator<A> sanitizer) {
|
||||||
|
return new DataSanitizationCodec<>(delegate, sanitizer);
|
||||||
|
}
|
||||||
|
|
||||||
private static ChargedProjectiles sanitizeChargedProjectiles(final ChargedProjectiles projectiles) {
|
private static ChargedProjectiles sanitizeChargedProjectiles(final ChargedProjectiles projectiles) {
|
||||||
if (projectiles.isEmpty()) {
|
if (projectiles.isEmpty()) {
|
||||||
@@ -39,21 +50,26 @@ public final class DataSanitizationUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ChargedProjectiles.of(List.of(
|
return ChargedProjectiles.of(List.of(
|
||||||
new ItemStack(projectiles.contains(Items.FIREWORK_ROCKET) ? Items.FIREWORK_ROCKET : Items.ARROW)
|
new ItemStack(
|
||||||
));
|
projectiles.contains(Items.FIREWORK_ROCKET)
|
||||||
|
? Items.FIREWORK_ROCKET
|
||||||
|
: Items.ARROW
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Although bundles no longer change their size based on fullness, fullness is exposed in item models.
|
||||||
private static BundleContents sanitizeBundleContents(final BundleContents contents) {
|
private static BundleContents sanitizeBundleContents(final BundleContents contents) {
|
||||||
if (contents.isEmpty()) {
|
if (contents.isEmpty()) {
|
||||||
return contents;
|
return contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bundles change their texture based on their fullness.
|
|
||||||
// A bundles content weight may be anywhere from 0 to, basically, infinity.
|
// A bundles content weight may be anywhere from 0 to, basically, infinity.
|
||||||
// A weight of 1 is the usual maximum case
|
// A weight of 1 is the usual maximum case
|
||||||
int sizeUsed = Mth.mulAndTruncate(contents.weight(), 64);
|
int sizeUsed = Mth.mulAndTruncate(contents.weight(), 64);
|
||||||
// Early out, *most* bundles should not be overfilled above a weight of one.
|
// Early out, *most* bundles should not be overfilled above a weight of one.
|
||||||
if (sizeUsed <= 64) return new BundleContents(List.of(new ItemStack(Items.PAPER, Math.max(1, sizeUsed))));
|
if (sizeUsed <= 64) {
|
||||||
|
return new BundleContents(List.of(new ItemStack(Items.PAPER, Math.max(1, sizeUsed))));
|
||||||
|
}
|
||||||
|
|
||||||
final List<ItemStack> sanitizedRepresentation = new ObjectArrayList<>(sizeUsed / 64 + 1);
|
final List<ItemStack> sanitizedRepresentation = new ObjectArrayList<>(sizeUsed / 64 + 1);
|
||||||
while (sizeUsed > 0) {
|
while (sizeUsed > 0) {
|
||||||
@@ -66,20 +82,19 @@ public final class DataSanitizationUtil {
|
|||||||
return new BundleContents(sanitizedRepresentation);
|
return new BundleContents(sanitizedRepresentation);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <B, A> StreamCodec<B, A> codec(final StreamCodec<B, A> delegate, final UnaryOperator<A> sanitizer) {
|
// Codec used to override encoding if sanitization is enabled
|
||||||
return new DataSanitizationCodec<>(delegate, sanitizer);
|
private record DataSanitizationCodec<B, A>(StreamCodec<B, A> delegate,
|
||||||
}
|
UnaryOperator<A> sanitizer) implements StreamCodec<B, A> {
|
||||||
|
|
||||||
private record DataSanitizationCodec<B, A>(StreamCodec<B, A> delegate, UnaryOperator<A> sanitizer) implements StreamCodec<B, A> {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull A decode(final @NonNull B buf) {
|
public @NonNull A decode(final @NonNull B buf) {
|
||||||
return this.delegate.decode(buf);
|
return this.delegate.decode(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("resource")
|
||||||
@Override
|
@Override
|
||||||
public void encode(final @NonNull B buf, final @NonNull A value) {
|
public void encode(final @NonNull B buf, final @NonNull A value) {
|
||||||
if (!DATA_SANITIZER.get().value().get()) {
|
if (ItemObfuscationSession.currentSession().obfuscationLevel().obfuscateOversized()) {
|
||||||
this.delegate.encode(buf, value);
|
this.delegate.encode(buf, value);
|
||||||
} else {
|
} else {
|
||||||
this.delegate.encode(buf, this.sanitizer.apply(value));
|
this.delegate.encode(buf, this.sanitizer.apply(value));
|
||||||
@@ -87,22 +102,4 @@ public final class DataSanitizationUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public record DataSanitizer(AtomicBoolean value) implements AutoCloseable {
|
|
||||||
|
|
||||||
public DataSanitizer() {
|
|
||||||
this(new AtomicBoolean(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
|
||||||
this.value.compareAndSet(false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
this.value.compareAndSet(true, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private DataSanitizationUtil() {
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -0,0 +1,10 @@
|
|||||||
|
package io.papermc.paper.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type of {@link AutoCloseable} that does not throw a checked exception.
|
||||||
|
*/
|
||||||
|
public interface SafeAutoClosable extends AutoCloseable {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void close();
|
||||||
|
}
|
@@ -1,14 +1,15 @@
|
|||||||
package io.papermc.paper.configuration;
|
package io.papermc.paper.configuration;
|
||||||
|
|
||||||
|
import net.minecraft.core.RegistryAccess;
|
||||||
import org.spongepowered.configurate.ConfigurationNode;
|
import org.spongepowered.configurate.ConfigurationNode;
|
||||||
import org.spongepowered.configurate.serialize.SerializationException;
|
import org.spongepowered.configurate.serialize.SerializationException;
|
||||||
|
|
||||||
public final class GlobalConfigTestingBase {
|
public final class GlobalConfigTestingBase {
|
||||||
|
|
||||||
public static void setupGlobalConfigForTest() {
|
public static void setupGlobalConfigForTest(RegistryAccess registryAccess) {
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
if (GlobalConfiguration.get() == null) {
|
if (GlobalConfiguration.get() == null) {
|
||||||
ConfigurationNode node = PaperConfigurations.createForTesting();
|
ConfigurationNode node = PaperConfigurations.createForTesting(registryAccess);
|
||||||
try {
|
try {
|
||||||
GlobalConfiguration globalConfiguration = node.require(GlobalConfiguration.class);
|
GlobalConfiguration globalConfiguration = node.require(GlobalConfiguration.class);
|
||||||
GlobalConfiguration.set(globalConfiguration);
|
GlobalConfiguration.set(globalConfiguration);
|
||||||
|
@@ -91,7 +91,7 @@ public final class DummyServerHelper {
|
|||||||
when(instance.getPluginManager()).thenReturn(pluginManager);
|
when(instance.getPluginManager()).thenReturn(pluginManager);
|
||||||
// Paper end - testing additions
|
// Paper end - testing additions
|
||||||
|
|
||||||
io.papermc.paper.configuration.GlobalConfigTestingBase.setupGlobalConfigForTest(); // Paper - configuration files - setup global configuration test base
|
io.papermc.paper.configuration.GlobalConfigTestingBase.setupGlobalConfigForTest(RegistryHelper.getRegistry()); // Paper - configuration files - setup global configuration test base
|
||||||
|
|
||||||
// Paper start - add test for recipe conversion
|
// Paper start - add test for recipe conversion
|
||||||
when(instance.recipeIterator()).thenAnswer(ignored ->
|
when(instance.recipeIterator()).thenAnswer(ignored ->
|
||||||
|
Reference in New Issue
Block a user