mirror of
https://github.com/PaperMC/Paper.git
synced 2024-12-26 22:40:21 +01:00
Finalize initial implementation
This commit is contained in:
parent
37a20964c3
commit
c8b186c9b6
4 changed files with 334 additions and 164 deletions
|
@ -134,7 +134,7 @@
|
|||
LOGGER.info("Loading properties");
|
||||
DedicatedServerProperties properties = this.settings.getProperties();
|
||||
if (this.isSingleplayer()) {
|
||||
@@ -132,13 +_,51 @@
|
||||
@@ -132,13 +_,52 @@
|
||||
this.setLocalIp(properties.serverIp);
|
||||
}
|
||||
|
||||
|
@ -148,6 +148,7 @@
|
|||
+ this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess());
|
||||
+ this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess());
|
||||
+ // Paper end - initialize global and world-defaults configuration
|
||||
+ io.papermc.paper.util.DataSanitizationUtil.bindCodecs(); // Paper - bind codec network serializers for sanitization
|
||||
+ this.server.spark.enableEarlyIfRequested(); // Paper - spark
|
||||
+ // Paper start - fix converting txt to json file; convert old users earlier after PlayerList creation but before file load/save
|
||||
+ if (this.convertOldUsers()) {
|
||||
|
|
|
@ -5,9 +5,9 @@ import io.papermc.paper.FeatureHooks;
|
|||
import io.papermc.paper.configuration.constraint.Constraints;
|
||||
import io.papermc.paper.configuration.type.number.DoubleOr;
|
||||
import io.papermc.paper.configuration.type.number.IntOr;
|
||||
import io.papermc.paper.util.DataSanitizationUtil;
|
||||
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;
|
||||
|
@ -21,8 +21,8 @@ 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.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
@ -373,15 +373,11 @@ public class GlobalConfiguration extends ConfigurationPart {
|
|||
public Items items = new Items();
|
||||
|
||||
public class Items extends ConfigurationPart {
|
||||
public static Set<DataComponentType<?>> OVERRIDEN_TYPES = new HashSet<>();
|
||||
|
||||
public boolean enableItemObfuscation = false;
|
||||
public AssetObfuscationConfiguration allModels = new AssetObfuscationConfiguration(true,
|
||||
Set.of(
|
||||
public static Set<DataComponentType<?>> BASE_OVERRIDERS = Set.of(
|
||||
DataComponents.MAX_STACK_SIZE,
|
||||
DataComponents.MAX_DAMAGE,
|
||||
DataComponents.DAMAGE,
|
||||
DataComponents.UNBREAKABLE,
|
||||
//DataComponents.UNBREAKABLE, - we cant really do anything about tihs
|
||||
DataComponents.CUSTOM_NAME,
|
||||
DataComponents.ITEM_NAME,
|
||||
DataComponents.LORE,
|
||||
|
@ -392,35 +388,37 @@ public class GlobalConfiguration extends ConfigurationPart {
|
|||
DataComponents.WRITABLE_BOOK_CONTENT,
|
||||
DataComponents.WRITTEN_BOOK_CONTENT,
|
||||
DataComponents.MAP_ID,
|
||||
DataComponents.MAP_DECORATIONS,
|
||||
DataComponents.MAP_POST_PROCESSING,
|
||||
DataComponents.LODESTONE_TRACKER
|
||||
);
|
||||
|
||||
public boolean enableItemObfuscation = false;
|
||||
public AssetObfuscationConfiguration allModels = new AssetObfuscationConfiguration(true,
|
||||
Set.of(DataComponents.LODESTONE_TRACKER),
|
||||
Set.of()
|
||||
);
|
||||
|
||||
public Map<String, AssetObfuscationConfiguration> modelOverrides = Map.of(
|
||||
net.minecraft.world.item.Items.ELYTRA.components().get(DataComponents.ITEM_MODEL).toString(), new AssetObfuscationConfiguration(true,
|
||||
Set.of(DataComponents.DAMAGE),
|
||||
Set.of()
|
||||
)
|
||||
);
|
||||
public Map<String, AssetObfuscationConfiguration> 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<DataComponentType<?>> sanitizedComponents) {
|
||||
public record AssetObfuscationConfiguration(@Required boolean sanitizeCount, Set<DataComponentType<?>> dontObfuscate, Set<DataComponentType<?>> alsoObfuscate) {
|
||||
public AssetObfuscationConfiguration(final boolean sanitizeCount, final @Nullable Set<DataComponentType<?>> dontObfuscate, final @Nullable Set<DataComponentType<?>> alsoObfuscate) {
|
||||
this.sanitizeCount = sanitizeCount;
|
||||
this.dontObfuscate = Objects.requireNonNullElse(dontObfuscate, Set.of());
|
||||
this.alsoObfuscate = Objects.requireNonNullElse(alsoObfuscate, Set.of());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@PostProcess
|
||||
public void computeOverridenTypes() {
|
||||
OVERRIDEN_TYPES.addAll(this.allModels.sanitizedComponents);
|
||||
for (AssetObfuscationConfiguration configuration : this.modelOverrides.values()) {
|
||||
OVERRIDEN_TYPES.addAll(configuration.sanitizedComponents);
|
||||
}
|
||||
DataSanitizationUtil.compute(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,32 +1,21 @@
|
|||
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 java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import net.minecraft.core.component.DataComponentType;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
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;
|
||||
|
@ -34,7 +23,55 @@ import org.checkerframework.framework.qual.DefaultQualifier;
|
|||
@DefaultQualifier(NonNull.class)
|
||||
public final class DataSanitizationUtil {
|
||||
|
||||
private static final ThreadLocal<DataSanitizer> DATA_SANITIZER = ThreadLocal.withInitial(DataSanitizer::new);
|
||||
static final ThreadLocal<DataSanitizer> DATA_SANITIZER = ThreadLocal.withInitial(DataSanitizer::new);
|
||||
|
||||
// <editor-fold desc="Cache Optimization" defaultstate="collapsed">
|
||||
static Set<DataComponentType<?>> OVERRIDE_TYPES = new HashSet<>();
|
||||
static BoundObfuscationConfiguration BOUND_BASE = null;
|
||||
static Map<ResourceLocation, BoundObfuscationConfiguration> BOUND_OVERRIDES = new HashMap<>();
|
||||
// We need to have a special ignore item to indicate this item should not be obfuscated.
|
||||
// Randomize the namespace to ensure servers dont try to utilize this.
|
||||
static ResourceLocation IGNORE_OBFUSCATION_ITEM = ResourceLocation.tryParse("paper:ignore_obfuscation_item/" + UUID.randomUUID());
|
||||
|
||||
public static void compute(GlobalConfiguration.Anticheat.Obfuscation.Items items) {
|
||||
|
||||
OVERRIDE_TYPES.addAll(GlobalConfiguration.Anticheat.Obfuscation.Items.BASE_OVERRIDERS);
|
||||
|
||||
// Add any possible new types obfuscated
|
||||
OVERRIDE_TYPES.addAll(items.allModels.alsoObfuscate());
|
||||
for (GlobalConfiguration.Anticheat.Obfuscation.Items.AssetObfuscationConfiguration configuration : items.modelOverrides.values()) {
|
||||
OVERRIDE_TYPES.addAll(configuration.alsoObfuscate());
|
||||
}
|
||||
|
||||
// now bind them all
|
||||
BOUND_BASE = bind(items.allModels);
|
||||
for (Map.Entry<String, GlobalConfiguration.Anticheat.Obfuscation.Items.AssetObfuscationConfiguration> entry : items.modelOverrides.entrySet()) {
|
||||
BOUND_OVERRIDES.put(ResourceLocation.parse(entry.getKey()), bind(entry.getValue()));
|
||||
}
|
||||
BOUND_OVERRIDES.put(IGNORE_OBFUSCATION_ITEM, new BoundObfuscationConfiguration(false, Set.of()));
|
||||
}
|
||||
|
||||
public record BoundObfuscationConfiguration(boolean sanitizeCount, Set<DataComponentType<?>> sanitize) {
|
||||
|
||||
}
|
||||
|
||||
public static BoundObfuscationConfiguration bind(GlobalConfiguration.Anticheat.Obfuscation.Items.AssetObfuscationConfiguration config) {
|
||||
Set<DataComponentType<?>> base = new HashSet<>(GlobalConfiguration.Anticheat.Obfuscation.Items.BASE_OVERRIDERS);
|
||||
base.addAll(config.alsoObfuscate());
|
||||
base.removeAll(config.dontObfuscate());
|
||||
|
||||
return new BoundObfuscationConfiguration(config.sanitizeCount(), base);
|
||||
}
|
||||
|
||||
public static void bindCodecs() {
|
||||
BuiltInRegistries.DATA_COMPONENT_TYPE.stream().forEach(DataComponentType::streamCodec); // populate the consumers
|
||||
OVERRIDE_TYPES.clear(); // We dont need this anymore
|
||||
}
|
||||
// </editor-fold>
|
||||
|
||||
public static BoundObfuscationConfiguration getAssetObfuscation(ItemStack resourceLocation) {
|
||||
return BOUND_OVERRIDES.getOrDefault(resourceLocation.get(DataComponents.ITEM_MODEL), BOUND_BASE);
|
||||
}
|
||||
|
||||
public static DataSanitizer start(final boolean sanitize) {
|
||||
final DataSanitizer sanitizer = DATA_SANITIZER.get();
|
||||
|
@ -46,118 +83,21 @@ public final class DataSanitizationUtil {
|
|||
|
||||
public static SafeAutoClosable passContext(final ItemStack itemStack) {
|
||||
final DataSanitizer sanitizer = DATA_SANITIZER.get();
|
||||
if (!sanitizer.isSanitizing()) {
|
||||
if (sanitizer.isNotSanitizing()) {
|
||||
return () -> {}; // Dont pass any context
|
||||
}
|
||||
|
||||
return new ContentScope(sanitizer.scope.get(), itemStack);
|
||||
ContentScope closable = new ContentScope(sanitizer.scope.get(), itemStack);;
|
||||
sanitizer.scope.set(closable);
|
||||
return closable;
|
||||
}
|
||||
|
||||
|
||||
public static final Map<DataComponentType<?>, StreamCodec<? super RegistryFriendlyByteBuf, ?>> 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<DataComponentType<?>, Object> EMPTY_MAP = Map.of(
|
||||
DataComponents.LODESTONE_TRACKER, new LodestoneTracker(Optional.empty(), false)
|
||||
);
|
||||
|
||||
|
||||
public static <T> StreamCodec<? super RegistryFriendlyByteBuf, T> get(DataComponentType<T> componentType, StreamCodec<? super RegistryFriendlyByteBuf, T> vanillaCodec) {
|
||||
// First check do we have our built in overrides, if so, override it.
|
||||
if (BUILT_IN_CODEC_OVERRIDES.containsKey(componentType)) {
|
||||
return (StreamCodec<RegistryFriendlyByteBuf, T>) 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()) {
|
||||
return projectiles;
|
||||
}
|
||||
|
||||
return ChargedProjectiles.of(List.of(
|
||||
new ItemStack(projectiles.contains(Items.FIREWORK_ROCKET) ? Items.FIREWORK_ROCKET : Items.ARROW)
|
||||
));
|
||||
}
|
||||
|
||||
private static BundleContents sanitizeBundleContents(final BundleContents contents) {
|
||||
if (contents.isEmpty()) {
|
||||
return contents;
|
||||
}
|
||||
|
||||
// Bundles change their texture based on their fullness.
|
||||
// A bundles content weight may be anywhere from 0 to, basically, infinity.
|
||||
// A weight of 1 is the usual maximum case
|
||||
int sizeUsed = Mth.mulAndTruncate(contents.weight(), 64);
|
||||
// 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))));
|
||||
|
||||
final List<ItemStack> sanitizedRepresentation = new ObjectArrayList<>(sizeUsed / 64 + 1);
|
||||
while (sizeUsed > 0) {
|
||||
final int stackCount = Math.min(64, sizeUsed);
|
||||
sanitizedRepresentation.add(new ItemStack(Items.PAPER, stackCount));
|
||||
sizeUsed -= stackCount;
|
||||
}
|
||||
// Now we add a single fake item that uses the same amount of slots as all other items.
|
||||
// Ensure that potentially overstacked bundles are not represented by empty (count=0) itemstacks.
|
||||
return new BundleContents(sanitizedRepresentation);
|
||||
}
|
||||
private static <B, A> StreamCodec<B, A> codec(final StreamCodec<B, A> delegate, final UnaryOperator<A> sanitizer) {
|
||||
return new DataSanitizationCodec<>(delegate, sanitizer);
|
||||
return ItemComponentSanitizer.get(componentType, vanillaCodec);
|
||||
}
|
||||
|
||||
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<A>(DataComponentType<?> type, @Nullable Object override) implements UnaryOperator<A> {
|
||||
|
||||
@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<B, A>(StreamCodec<B, A> delegate, UnaryOperator<A> sanitizer) implements StreamCodec<B, A> {
|
||||
|
||||
@Override
|
||||
public @NonNull A decode(final @NonNull B buf) {
|
||||
return this.delegate.decode(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(final @NonNull B buf, final @NonNull A value) {
|
||||
if (!DATA_SANITIZER.get().isSanitizing()) {
|
||||
this.delegate.encode(buf, value);
|
||||
} else {
|
||||
this.delegate.encode(buf, this.sanitizer.apply(value));
|
||||
}
|
||||
}
|
||||
return ItemComponentSanitizer.sanitizeCount(itemStack, count);
|
||||
}
|
||||
|
||||
public record DataSanitizer(AtomicBoolean value, AtomicReference<ContentScope> scope) implements SafeAutoClosable {
|
||||
|
@ -175,8 +115,8 @@ public final class DataSanitizationUtil {
|
|||
this.value.compareAndSet(true, false);
|
||||
}
|
||||
|
||||
public boolean isSanitizing() {
|
||||
return value().get();
|
||||
public boolean isNotSanitizing() {
|
||||
return !value().get();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,231 @@
|
|||
package io.papermc.paper.util;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.papermc.paper.configuration.GlobalConfiguration;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.UnaryOperator;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.core.HolderSet;
|
||||
import net.minecraft.core.component.DataComponentType;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.LockCode;
|
||||
import net.minecraft.world.item.AdventureModePredicate;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.alchemy.PotionContents;
|
||||
import net.minecraft.world.item.component.BundleContents;
|
||||
import net.minecraft.world.item.component.ChargedProjectiles;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.component.DeathProtection;
|
||||
import net.minecraft.world.item.component.ItemContainerContents;
|
||||
import net.minecraft.world.item.component.LodestoneTracker;
|
||||
import net.minecraft.world.item.component.OminousBottleAmplifier;
|
||||
import net.minecraft.world.item.component.Tool;
|
||||
import net.minecraft.world.item.component.UseCooldown;
|
||||
import net.minecraft.world.item.component.UseRemainder;
|
||||
import net.minecraft.world.item.component.WritableBookContent;
|
||||
import net.minecraft.world.item.component.WrittenBookContent;
|
||||
import net.minecraft.world.item.enchantment.ItemEnchantments;
|
||||
import net.minecraft.world.item.enchantment.Repairable;
|
||||
import net.minecraft.world.level.saveddata.maps.MapId;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
final class ItemComponentSanitizer {
|
||||
|
||||
/*
|
||||
These represent codecs that are meant to help get rid of possibly big items by ALWAYS hiding this data.
|
||||
*/
|
||||
public static final Map<DataComponentType<?>, StreamCodec<? super RegistryFriendlyByteBuf, ?>> OVERSIZE_OVERRIDES = Map.of(
|
||||
DataComponents.CHARGED_PROJECTILES, codec(ChargedProjectiles.STREAM_CODEC, ItemComponentSanitizer::sanitizeChargedProjectiles),
|
||||
DataComponents.BUNDLE_CONTENTS, codec(BundleContents.STREAM_CODEC, ItemComponentSanitizer::sanitizeBundleContents),
|
||||
DataComponents.CONTAINER, codec(ItemContainerContents.STREAM_CODEC, contents -> ItemContainerContents.EMPTY)
|
||||
);
|
||||
|
||||
/*
|
||||
* This map defines overrides for specific DataComponentType entries that should be sanitized.
|
||||
*
|
||||
* Components critical to item functionality are not sanitized.
|
||||
* This map only applies to components that are patched ontop of items and should ignore its prototype.
|
||||
*
|
||||
* Any components not present in this map, the prototype value will be sent.
|
||||
*/
|
||||
public 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)));
|
||||
put(map, DataComponents.ENCHANTMENTS, empty(dummyEnchantments()));
|
||||
put(map, DataComponents.MAX_DAMAGE, empty(1));
|
||||
put(map, DataComponents.DAMAGE, empty(0));
|
||||
put(map, DataComponents.CUSTOM_NAME, empty(Component.empty()));
|
||||
put(map, DataComponents.TOOLTIP_STYLE, empty(ResourceLocation.parse("paper:fake-tooltip")));
|
||||
put(map, DataComponents.DEATH_PROTECTION, empty(new DeathProtection(List.of())));
|
||||
put(map, DataComponents.STORED_ENCHANTMENTS, empty(dummyEnchantments()));
|
||||
put(map, DataComponents.CAN_PLACE_ON, empty(new AdventureModePredicate(List.of(), true)));
|
||||
put(map, DataComponents.REPAIR_COST, empty(0));
|
||||
put(map, DataComponents.USE_REMAINDER, empty(new UseRemainder(ItemStack.EMPTY)));
|
||||
put(map, DataComponents.USE_COOLDOWN, empty(new UseCooldown(1, Optional.empty())));
|
||||
put(map, DataComponents.TOOL, empty(new Tool(List.of(), 1F, 1)));
|
||||
put(map, DataComponents.REPAIRABLE, empty(new Repairable(HolderSet.empty())));
|
||||
put(map, DataComponents.CUSTOM_DATA, empty(CustomData.EMPTY));
|
||||
put(map, DataComponents.POTION_CONTENTS, ItemComponentSanitizer::sanitizePotionContents);
|
||||
put(map, DataComponents.ENTITY_DATA, empty(CustomData.EMPTY));
|
||||
put(map, DataComponents.BUCKET_ENTITY_DATA, empty(CustomData.EMPTY));
|
||||
put(map, DataComponents.BLOCK_ENTITY_DATA, empty(CustomData.EMPTY));
|
||||
put(map, DataComponents.OMINOUS_BOTTLE_AMPLIFIER, empty(new OminousBottleAmplifier(0)));
|
||||
put(map, DataComponents.BEES, empty(List.of()));
|
||||
put(map, DataComponents.LOCK, empty(LockCode.NO_LOCK));
|
||||
put(map, DataComponents.WRITABLE_BOOK_CONTENT, empty(WritableBookContent.EMPTY));
|
||||
put(map, DataComponents.WRITTEN_BOOK_CONTENT, empty(WrittenBookContent.EMPTY));
|
||||
put(map, DataComponents.MAP_ID, empty(new MapId(0)));
|
||||
}
|
||||
).build();
|
||||
|
||||
private static <T> void put(ImmutableMap.Builder map, DataComponentType<T> type, UnaryOperator<T> object) {
|
||||
map.put(type, object);
|
||||
}
|
||||
|
||||
private static <T> UnaryOperator<T> empty(T object) {
|
||||
return (unused) -> object;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public static <T> StreamCodec<? super RegistryFriendlyByteBuf, T> get(DataComponentType<T> componentType, StreamCodec<? super RegistryFriendlyByteBuf, T> vanillaCodec) {
|
||||
// First check do we have our built in overrides, if so, override it.
|
||||
if (OVERSIZE_OVERRIDES.containsKey(componentType)) {
|
||||
return (StreamCodec<RegistryFriendlyByteBuf, T>) OVERSIZE_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 && DataSanitizationUtil.OVERRIDE_TYPES.contains(componentType)) {
|
||||
return codec(vanillaCodec, new DefaultDataComponentSanitizer<>(componentType, (UnaryOperator) SANITIZATION_OVERRIDES.get(componentType)));
|
||||
}
|
||||
|
||||
return vanillaCodec;
|
||||
}
|
||||
|
||||
// <editor-fold desc="Component Sanitizers" defaultstate="collapsed">
|
||||
|
||||
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() {
|
||||
ItemEnchantments.Mutable obj = new ItemEnchantments.Mutable(ItemEnchantments.EMPTY);
|
||||
obj.set(MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.ENCHANTMENT).getRandom(RandomSource.create()).orElseThrow(), 1);
|
||||
return obj.toImmutable();
|
||||
}
|
||||
|
||||
private static ChargedProjectiles sanitizeChargedProjectiles(final ChargedProjectiles projectiles) {
|
||||
if (projectiles.isEmpty()) {
|
||||
return projectiles;
|
||||
}
|
||||
|
||||
return ChargedProjectiles.of(List.of(
|
||||
new ItemStack(projectiles.contains(Items.FIREWORK_ROCKET) ? Items.FIREWORK_ROCKET : Items.ARROW)
|
||||
));
|
||||
}
|
||||
|
||||
private static final ItemStack BUNDLE_ITEM_FILLER = Util.make(new ItemStack(Items.PAPER, 1), (itemStack) -> {
|
||||
if (GlobalConfiguration.get().anticheat.obfuscation.items.enableItemObfuscation) {
|
||||
itemStack.set(DataComponents.ITEM_MODEL, DataSanitizationUtil.IGNORE_OBFUSCATION_ITEM); // Prevent this item from being obfuscated
|
||||
}
|
||||
});
|
||||
|
||||
// Although bundles no longer change their size based on fullness, fullness is exposed in item models.
|
||||
private static BundleContents sanitizeBundleContents(final BundleContents contents) {
|
||||
if (contents.isEmpty()) {
|
||||
return contents;
|
||||
}
|
||||
|
||||
// A bundles content weight may be anywhere from 0 to, basically, infinity.
|
||||
// A weight of 1 is the usual maximum case
|
||||
int sizeUsed = Mth.mulAndTruncate(contents.weight(), 64);
|
||||
// Early out, *most* bundles should not be overfilled above a weight of one.
|
||||
if (sizeUsed <= 64) {
|
||||
return new BundleContents(List.of(BUNDLE_ITEM_FILLER.copyWithCount(Math.max(1, sizeUsed))));
|
||||
}
|
||||
|
||||
final List<ItemStack> sanitizedRepresentation = new ObjectArrayList<>(sizeUsed / 64 + 1);
|
||||
while (sizeUsed > 0) {
|
||||
final int stackCount = Math.min(64, sizeUsed);
|
||||
sanitizedRepresentation.add(BUNDLE_ITEM_FILLER.copyWithCount(stackCount));
|
||||
sizeUsed -= stackCount;
|
||||
}
|
||||
// Now we add a single fake item that uses the same amount of slots as all other items.
|
||||
// Ensure that potentially overstacked bundles are not represented by empty (count=0) itemstacks.
|
||||
return new BundleContents(sanitizedRepresentation);
|
||||
}
|
||||
// </editor-fold>
|
||||
|
||||
public static int sanitizeCount(ItemStack itemStack, int count) {
|
||||
GlobalConfiguration.Anticheat.Obfuscation.Items items = GlobalConfiguration.get().anticheat.obfuscation.items;
|
||||
if (items.enableItemObfuscation && DataSanitizationUtil.getAssetObfuscation(itemStack).sanitizeCount()) {
|
||||
return 1;
|
||||
} else {
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
private static <B, A> StreamCodec<B, A> codec(final StreamCodec<B, A> delegate, final UnaryOperator<A> sanitizer) {
|
||||
return new DataSanitizationCodec<>(delegate, sanitizer);
|
||||
}
|
||||
|
||||
// Serializer that will override the type depending on the asset of the item provided
|
||||
private record DefaultDataComponentSanitizer<A>(DataComponentType<?> type,
|
||||
@Nullable UnaryOperator<A> override) implements UnaryOperator<A> {
|
||||
|
||||
@Override
|
||||
public A apply(final A oldvalue) {
|
||||
DataSanitizationUtil.ContentScope scope = DataSanitizationUtil.DATA_SANITIZER.get().scope().get();
|
||||
ItemStack targetItemstack = scope.itemStack();
|
||||
// Does this asset override this component? If not, return oldValue.
|
||||
if (!DataSanitizationUtil.getAssetObfuscation(targetItemstack).sanitize().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 this.override == null ? (A) scope.itemStack().getPrototype().getOrDefault(this.type, oldvalue) : this.override.apply(oldvalue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Codec used to override encoding if sanitization is enabled
|
||||
private record DataSanitizationCodec<B, A>(StreamCodec<B, A> delegate,
|
||||
UnaryOperator<A> sanitizer) implements StreamCodec<B, A> {
|
||||
|
||||
@Override
|
||||
public @NonNull A decode(final @NonNull B buf) {
|
||||
return this.delegate.decode(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(final @NonNull B buf, final @NonNull A value) {
|
||||
if (DataSanitizationUtil.DATA_SANITIZER.get().isNotSanitizing()) {
|
||||
this.delegate.encode(buf, value);
|
||||
} else {
|
||||
this.delegate.encode(buf, this.sanitizer.apply(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue