Finalize initial implementation

This commit is contained in:
Owen1212055 2024-12-24 22:43:56 -05:00
parent 37a20964c3
commit c8b186c9b6
No known key found for this signature in database
GPG key ID: 2133292072886A30
4 changed files with 334 additions and 164 deletions

View file

@ -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()) {

View file

@ -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);
}
}
}

View file

@ -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();
}
}

View file

@ -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));
}
}
}
}