Compare commits

...
Author SHA1 Message Date
Jake Potrebic fd5c62850a Bytecode Modification Framework 2026-08-10 18:20:44 +02:00
35 changed files with 340 additions and 1003 deletions

No files matched your search

+3
View File
@@ -154,6 +154,9 @@ dependencies {
testImplementation("org.ow2.asm:asm-tree:9.9.1")
testImplementation("org.junit-pioneer:junit-pioneer:2.3.0") // CartesianTest
// Bytecode framework
implementation("io.papermc:asm-utils:0.0.4-SNAPSHOT")
// Spark
implementation("me.lucko:spark-api:0.1-20240720.200737-2")
implementation("me.lucko:spark-paper:1.10.172")
@@ -0,0 +1,182 @@
package io.papermc.paper.plugin;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedType;
import java.util.Comparator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import org.bukkit.craftbukkit.util.Versioning;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException;
@NullMarked
public sealed interface ApiVersion extends io.papermc.asm.versioned.ApiVersion<ApiVersion> permits ApiVersion.Legacy, ApiVersion.Modern, ApiVersion.None {
Serializer SERIALIZER = new Serializer();
ApiVersion CURRENT = getOrCreateVersion(Versioning.getCurrentApiVersion());
ApiVersion FLATTENING = getOrCreateVersion("1.13");
ApiVersion FIELD_NAME_PARITY = getOrCreateVersion("1.20.5");
ApiVersion ABSTRACT_COW = getOrCreateVersion("1.21.5");
ApiVersion ABSTRACT_CUBE_MOB = getOrCreateVersion("26.2");
ApiVersion NONE = getOrCreateVersion("none");
String getVersionString();
@Override
default int compareTo(final ApiVersion o) {
return switch (o) {
case final Legacy oLegacy -> switch (this) {
case final Legacy thisLegacy -> thisLegacy.compareSameType(oLegacy);
case final Modern ignored -> 1;
case final None ignored -> -1;
};
case final Modern oModern -> switch (this) {
case final Legacy ignored -> -1;
case final Modern thisModern -> thisModern.compareSameType(oModern);
case final None ignored -> -1;
};
case final None ignored -> switch (this) {
case final Legacy ignored2 -> 1;
case final Modern ignored2 -> 1;
case final None ignored2 -> 0;
};
};
}
record Legacy(int major, int minor, int patch) implements ApiVersion {
private static final Comparator<Legacy> COMPARATOR = Comparator
.comparingInt(Legacy::major)
.thenComparingInt(Legacy::minor)
.thenComparingInt(Legacy::patch);
int compareSameType(final Legacy other) {
return COMPARATOR.compare(this, other);
}
@Override
public String getVersionString() {
return this.major() + "." + this.minor() + "." + this.patch();
}
@Override
public String toString() {
return this.getVersionString();
}
}
record Modern(int year, int drop, int hotfix) implements ApiVersion {
private static final Comparator<Modern> COMPARATOR = Comparator
.comparingInt(Modern::year)
.thenComparingInt(Modern::drop)
.thenComparingInt(Modern::hotfix);
int compareSameType(final Modern other) {
return COMPARATOR.compare(this, other);
}
@Override
public String getVersionString() {
return this.year() + "." + this.drop() + "." + this.hotfix();
}
@Override
public String toString() {
return this.getVersionString();
}
}
record None() implements ApiVersion {
private static final None INSTANCE = new None();
@Override
public String getVersionString() {
return "none";
}
@Override
public String toString() {
return this.getVersionString();
}
}
final class Serializer extends ScalarSerializer.Annotated<ApiVersion> {
Serializer() {
super(ApiVersion.class);
}
@Override
public ApiVersion deserialize(final AnnotatedType type, final Object obj) throws SerializationException {
try {
final ApiVersion version = getOrCreateVersion(obj.toString());
final Minimum min = type.getAnnotation(Minimum.class);
if (min != null) {
final ApiVersion minVersion = getOrCreateVersion(min.value());
if (version.isOlderThan(minVersion)) {
throw new SerializationException(ApiVersion.class, version + " is too old for a paper plugin!");
}
}
return version;
} catch (final IllegalArgumentException ex) {
throw new SerializationException(ApiVersion.class, "Could not parse version string", ex);
}
}
@Override
protected Object serialize(final AnnotatedType type, final ApiVersion item, final Predicate<Class<?>> typeSupported) {
return item.getVersionString();
}
}
static ApiVersion getOrCreateVersion(final @Nullable String versionString) {
class Holder {
private static final Map<String, ApiVersion> cache = new ConcurrentHashMap<>();
}
if (versionString == null || versionString.isBlank() || versionString.equalsIgnoreCase("none")) {
return None.INSTANCE;
}
final ApiVersion parsed = parse(versionString);
Holder.cache.putIfAbsent(parsed.getVersionString(), parsed);
return Holder.cache.get(parsed.getVersionString());
}
private static ApiVersion parse(final String versionString) {
final String[] parts = versionString.split("\\.");
if (parts.length < 2 || parts.length > 3) {
throw new IllegalArgumentException("Version string must have 2 or 3 numbers");
}
final int first = Integer.parseInt(parts[0]);
final int second = Integer.parseInt(parts[1]);
final int third = parts.length == 3 ? Integer.parseInt(parts[2]) : 0;
if (first == 1) {
if (second > 21) {
throw new IllegalArgumentException("Legacy version string must be 1.21.xx or lower");
}
return new Legacy(first, second, third);
} else {
if (first < 26) {
throw new IllegalArgumentException("Modern version string must be 26.x.x or higher");
}
return new Modern(first, second, third);
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Minimum {
String value();
}
}
@@ -1,12 +0,0 @@
package io.papermc.paper.plugin.entrypoint.classloader;
import io.papermc.paper.plugin.configuration.PluginMeta;
// Stub, implement in future.
public class PaperClassloaderBytecodeModifier implements ClassloaderBytecodeModifier {
@Override
public byte[] modify(PluginMeta configuration, byte[] bytecode) {
return bytecode;
}
}
@@ -0,0 +1,60 @@
package io.papermc.paper.plugin.entrypoint.classloader.bytecode;
import com.google.common.collect.Iterators;
import io.papermc.paper.plugin.ApiVersion;
import io.papermc.paper.plugin.configuration.PluginMeta;
import io.papermc.paper.plugin.entrypoint.classloader.ClassloaderBytecodeModifier;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import net.minecraft.util.Util;
import org.objectweb.asm.Opcodes;
public class PaperClassloaderBytecodeModifier implements ClassloaderBytecodeModifier {
private static final Map<ApiVersion, List<ModifierFactory>> MODIFIERS = Util.make(new LinkedHashMap<>(), map -> {
});
private final Map<ApiVersion, List<VersionedClassloaderBytecodeModifier>> constructedModifiers = MODIFIERS.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, entry -> {
return entry.getValue().stream().map(factory -> factory.create(Opcodes.ASM9)).toList();
}));
@Override
public byte[] modify(final PluginMeta configuration, byte[] bytecode) {
int start = -1;
if (configuration.getAPIVersion() != null) {
int i = 0;
for (final Map.Entry<ApiVersion, List<VersionedClassloaderBytecodeModifier>> entry : this.constructedModifiers.entrySet()) {
final ApiVersion apiVersion = ApiVersion.getOrCreateVersion(configuration.getAPIVersion());
final ApiVersion modifierApiVersion = entry.getKey();
if (apiVersion.isOlderThanOrSameAs(modifierApiVersion)) {
start = i;
break;
}
i++;
}
} else {
start = 0;
}
if (start == -1) {
return bytecode; // no modification needed. The plugin version is newer than all versioned modifiers
}
final Iterator<Map.Entry<ApiVersion, List<VersionedClassloaderBytecodeModifier>>> it = this.constructedModifiers.entrySet().iterator();
Iterators.advance(it, start);
while (it.hasNext()) {
for (final VersionedClassloaderBytecodeModifier modifier : it.next().getValue()) {
bytecode = modifier.modify(configuration, bytecode);
}
}
return bytecode;
}
private interface ModifierFactory {
VersionedClassloaderBytecodeModifier create(int api);
}
}
@@ -0,0 +1,32 @@
package io.papermc.paper.plugin.entrypoint.classloader.bytecode;
import io.papermc.asm.AbstractRewriteRuleVisitorFactory;
import io.papermc.asm.ClassInfoProvider;
import io.papermc.asm.rules.builder.RuleFactoryConfiguration;
import io.papermc.paper.plugin.configuration.PluginMeta;
import io.papermc.paper.plugin.entrypoint.classloader.ClassloaderBytecodeModifier;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import static io.papermc.asm.util.DescriptorUtils.desc;
public abstract class VersionedClassloaderBytecodeModifier extends AbstractRewriteRuleVisitorFactory implements ClassloaderBytecodeModifier, RuleFactoryConfiguration.Holder {
protected VersionedClassloaderBytecodeModifier(final int api) {
super(api, ClassInfoProvider.basic());
}
@Override
public final byte[] modify(final PluginMeta config, final byte[] bytecode) {
final ClassReader cr = new ClassReader(bytecode);
final ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS); // need to compute maxs because of instruction removal in ctor rewriting
cr.accept(this.createVisitor(cw), 0);
return cw.toByteArray();
}
@Override
public final RuleFactoryConfiguration configuration() {
return RuleFactoryConfiguration.create(desc(this.getClass()));
}
}
@@ -0,0 +1,4 @@
@NullMarked
package io.papermc.paper.plugin.entrypoint.classloader.bytecode;
import org.jspecify.annotations.NullMarked;
@@ -5,15 +5,13 @@ import com.google.common.collect.ImmutableList;
import io.papermc.paper.configuration.constraint.Constraint;
import io.papermc.paper.configuration.serializer.ComponentSerializer;
import io.papermc.paper.configuration.serializer.EnumValueSerializer;
import io.papermc.paper.plugin.ApiVersion;
import io.papermc.paper.plugin.configuration.PluginMeta;
import io.papermc.paper.plugin.provider.configuration.serializer.PermissionConfigurationSerializer;
import io.papermc.paper.plugin.provider.configuration.serializer.constraints.PluginConfigConstraints;
import io.papermc.paper.plugin.provider.configuration.type.DependencyConfiguration;
import io.papermc.paper.plugin.provider.configuration.type.PermissionConfiguration;
import io.papermc.paper.plugin.provider.configuration.type.PluginDependencyLifeCycle;
import java.lang.reflect.Type;
import java.util.function.Predicate;
import org.bukkit.craftbukkit.util.ApiVersion;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.PluginLoadOrder;
@@ -26,8 +24,6 @@ import org.spongepowered.configurate.loader.HeaderMode;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.ObjectMapper;
import org.spongepowered.configurate.objectmapping.meta.Required;
import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.yaml.NodeStyle;
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
@@ -63,6 +59,7 @@ public class PaperPluginMeta implements PluginMeta {
@FlattenedResolver
private PermissionConfiguration permissionConfiguration = new PermissionConfiguration(PermissionDefault.OP, List.of());
@Required
@ApiVersion.Minimum("1.19")
private ApiVersion apiVersion;
private Map<PluginDependencyLifeCycle, Map<String, DependencyConfiguration>> dependencies = new EnumMap<>(PluginDependencyLifeCycle.class);
@@ -70,7 +67,6 @@ public class PaperPluginMeta implements PluginMeta {
public PaperPluginMeta() {
}
static final ApiVersion MINIMUM = ApiVersion.getOrCreateVersion("1.19");
public static PaperPluginMeta create(BufferedReader reader) throws ConfigurateException {
YamlConfigurationLoader loader = YamlConfigurationLoader.builder()
.indent(2)
@@ -78,28 +74,9 @@ public class PaperPluginMeta implements PluginMeta {
.headerMode(HeaderMode.NONE)
.source(() -> reader)
.defaultOptions((options) -> {
return options.serializers((serializers) -> {
serializers
.register(new ScalarSerializer<>(ApiVersion.class) {
@Override
public ApiVersion deserialize(final Type type, final Object obj) throws SerializationException {
try {
final ApiVersion version = ApiVersion.getOrCreateVersion(obj.toString());
if (version.isOlderThan(MINIMUM)) {
throw new SerializationException(version + " is too old for a paper plugin!");
}
return version;
} catch (final IllegalArgumentException e) {
throw new SerializationException(e);
}
}
@Override
protected Object serialize(final ApiVersion item, final Predicate<Class<?>> typeSupported) {
return item.getVersionString();
}
})
.register(ApiVersion.SERIALIZER)
.register(new EnumValueSerializer())
.register(PermissionConfiguration.class, PermissionConfigurationSerializer.SERIALIZER)
.register(new ComponentSerializer())
@@ -1,5 +1,6 @@
package io.papermc.paper.registry.entry;
import io.papermc.paper.plugin.ApiVersion;
import io.papermc.paper.registry.PaperRegistryBuilder;
import io.papermc.paper.registry.RegistryKey;
import java.util.function.BiFunction;
@@ -10,7 +11,6 @@ import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import org.bukkit.Keyed;
import org.bukkit.NamespacedKey;
import org.bukkit.craftbukkit.util.ApiVersion;
import static io.papermc.paper.registry.entry.RegistryEntryMeta.RegistryModificationApiSupport.ADDABLE;
import static io.papermc.paper.registry.entry.RegistryEntryMeta.RegistryModificationApiSupport.MODIFIABLE;
@@ -1,6 +1,7 @@
package io.papermc.paper.registry.entry;
import com.google.common.base.Preconditions;
import io.papermc.paper.plugin.ApiVersion;
import io.papermc.paper.registry.PaperRegistryBuilder;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
@@ -16,7 +17,6 @@ import net.minecraft.resources.ResourceKey;
import org.bukkit.Keyed;
import org.bukkit.NamespacedKey;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.craftbukkit.util.ApiVersion;
public sealed interface RegistryEntryMeta<M, A extends Keyed> permits RegistryEntryMeta.ApiOnly, RegistryEntryMeta.ServerSide { // TODO remove Keyed
@@ -1,6 +1,7 @@
package org.bukkit.craftbukkit;
import com.google.common.base.Preconditions;
import io.papermc.paper.plugin.ApiVersion;
import io.papermc.paper.registry.PaperRegistries;
import io.papermc.paper.registry.RegistryAccess;
import io.papermc.paper.registry.RegistryKey;
@@ -8,7 +9,6 @@ import io.papermc.paper.registry.entry.RegistryEntryMeta;
import io.papermc.paper.registry.set.NamedRegistryKeySetImpl;
import io.papermc.paper.registry.tag.Tag;
import io.papermc.paper.util.Holderable;
import io.papermc.paper.util.MCUtil;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
@@ -24,7 +24,6 @@ import org.bukkit.NamespacedKey;
import org.bukkit.Particle;
import org.bukkit.Registry;
import org.bukkit.craftbukkit.legacy.FieldRename;
import org.bukkit.craftbukkit.util.ApiVersion;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.bukkit.craftbukkit.util.Handleable;
import org.bukkit.entity.EntityType;
@@ -13,6 +13,7 @@ import com.mojang.serialization.JsonOps;
import io.papermc.paper.configuration.GlobalConfiguration;
import io.papermc.paper.configuration.PaperServerConfiguration;
import io.papermc.paper.configuration.ServerConfiguration;
import io.papermc.paper.plugin.ApiVersion;
import io.papermc.paper.util.MCUtil;
import io.papermc.paper.world.PaperWorldLoader;
import io.papermc.paper.world.migration.WorldFolderMigration;
@@ -153,7 +154,6 @@ import org.bukkit.craftbukkit.ban.CraftIpBanList;
import org.bukkit.craftbukkit.ban.CraftProfileBanList;
import org.bukkit.craftbukkit.block.data.CraftBlockData;
import org.bukkit.craftbukkit.boss.CraftBossBar;
import org.bukkit.craftbukkit.boss.CraftKeyedBossbar;
import org.bukkit.craftbukkit.command.CraftCommandMap;
import org.bukkit.craftbukkit.command.VanillaCommandWrapper;
import org.bukkit.craftbukkit.entity.CraftEntityFactory;
@@ -197,7 +197,6 @@ import org.bukkit.craftbukkit.tag.CraftEntityTag;
import org.bukkit.craftbukkit.tag.CraftFluidTag;
import org.bukkit.craftbukkit.tag.CraftGameEventTag;
import org.bukkit.craftbukkit.tag.CraftItemTag;
import org.bukkit.craftbukkit.util.ApiVersion;
import org.bukkit.craftbukkit.util.CraftChatMessage;
import org.bukkit.craftbukkit.util.CraftIconCache;
import org.bukkit.craftbukkit.util.CraftLocation;
@@ -1,6 +1,7 @@
package org.bukkit.craftbukkit.attribute;
import com.google.common.base.Preconditions;
import io.papermc.paper.plugin.ApiVersion;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.util.OldEnumHolderable;
import java.util.Locale;
@@ -10,7 +11,6 @@ import org.bukkit.NamespacedKey;
import org.bukkit.attribute.Attribute;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.craftbukkit.legacy.FieldRename;
import org.bukkit.craftbukkit.util.ApiVersion;
public class CraftAttribute extends OldEnumHolderable<Attribute, net.minecraft.world.entity.ai.attributes.Attribute> implements Attribute {
@@ -1,6 +1,7 @@
package org.bukkit.craftbukkit.enchantments;
import com.google.common.base.Preconditions;
import io.papermc.paper.plugin.ApiVersion;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.util.Holderable;
import java.util.Locale;
@@ -12,7 +13,6 @@ import org.bukkit.NamespacedKey;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.craftbukkit.legacy.FieldRename;
import org.bukkit.craftbukkit.util.ApiVersion;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.enchantments.EnchantmentTarget;
import org.bukkit.enchantments.EnchantmentWrapper;
@@ -1,6 +1,7 @@
package org.bukkit.craftbukkit.entity;
import com.google.common.base.Preconditions;
import io.papermc.paper.plugin.ApiVersion;
import io.papermc.paper.registry.RegistryKey;
import java.util.Locale;
import net.minecraft.core.Holder;
@@ -9,7 +10,6 @@ import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.craftbukkit.legacy.FieldRename;
import org.bukkit.craftbukkit.util.ApiVersion;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.bukkit.entity.EntityType;
@@ -1,8 +1,8 @@
package org.bukkit.craftbukkit.inventory;
import com.google.common.base.Preconditions;
import io.papermc.paper.plugin.ApiVersion;
import org.bukkit.craftbukkit.legacy.FieldRename;
import org.bukkit.craftbukkit.util.ApiVersion;
import org.bukkit.inventory.ItemFlag;
public class CraftItemFlag {
@@ -1,5 +1,6 @@
package org.bukkit.craftbukkit.legacy;
import io.papermc.paper.plugin.ApiVersion;
import java.util.function.BiFunction;
import org.bukkit.NamespacedKey;
import org.bukkit.Particle;
@@ -12,7 +13,6 @@ import org.bukkit.craftbukkit.legacy.reroute.DoNotReroute;
import org.bukkit.craftbukkit.legacy.reroute.InjectPluginVersion;
import org.bukkit.craftbukkit.legacy.reroute.RerouteMethodName;
import org.bukkit.craftbukkit.legacy.reroute.RerouteStatic;
import org.bukkit.craftbukkit.util.ApiVersion;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemFlag;
@@ -1,5 +1,6 @@
package org.bukkit.craftbukkit.legacy;
import io.papermc.paper.plugin.ApiVersion;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
@@ -29,7 +30,6 @@ import org.bukkit.craftbukkit.legacy.reroute.InjectPluginVersion;
import org.bukkit.craftbukkit.legacy.reroute.RerouteStatic;
import org.bukkit.craftbukkit.tag.CraftBlockTag;
import org.bukkit.craftbukkit.tag.CraftItemTag;
import org.bukkit.craftbukkit.util.ApiVersion;
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
import org.bukkit.enchantments.EnchantmentTarget;
import org.bukkit.entity.Animals;
@@ -1,7 +0,0 @@
package org.bukkit.craftbukkit.legacy.enums;
/**
* A crash dummy to use, instead of the old enums which matured to Abstracthood or Interfacehood and the baby enums which are still growing.
*/
public enum DummyEnum {
}
@@ -1,288 +0,0 @@
package org.bukkit.craftbukkit.legacy.enums;
import com.google.common.base.Converter;
import com.google.common.base.Enums;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import org.bukkit.Art;
import org.bukkit.Fluid;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.Sound;
import org.bukkit.attribute.Attribute;
import org.bukkit.block.Biome;
import org.bukkit.block.banner.PatternType;
import org.bukkit.craftbukkit.legacy.FieldRename;
import org.bukkit.craftbukkit.legacy.reroute.DoNotReroute;
import org.bukkit.craftbukkit.legacy.reroute.InjectPluginVersion;
import org.bukkit.craftbukkit.legacy.reroute.NotInBukkit;
import org.bukkit.craftbukkit.legacy.reroute.RequireCompatibility;
import org.bukkit.craftbukkit.legacy.reroute.RequirePluginVersion;
import org.bukkit.craftbukkit.legacy.reroute.RerouteArgumentType;
import org.bukkit.craftbukkit.legacy.reroute.RerouteReturnType;
import org.bukkit.craftbukkit.legacy.reroute.RerouteStatic;
import org.bukkit.craftbukkit.util.ApiVersion;
import org.bukkit.craftbukkit.util.ClassTraverser;
import org.bukkit.entity.Cat;
import org.bukkit.entity.Frog;
import org.bukkit.entity.Villager;
import org.bukkit.map.MapCursor;
import org.bukkit.util.OldEnum;
@Deprecated
@NotInBukkit
@RequireCompatibility("enum-compatibility-mode")
@RequirePluginVersion(maxInclusive = "1.20.6")
public class EnumEvil {
private static final Map<Class<?>, LegacyRegistryData> REGISTRIES = new HashMap<>();
static {
// Add Classes which got changed here
REGISTRIES.put(Art.class, new LegacyRegistryData(Registry.ART, Art::valueOf));
REGISTRIES.put(Attribute.class, new LegacyRegistryData(Registry.ATTRIBUTE, Attribute::valueOf));
REGISTRIES.put(Biome.class, new LegacyRegistryData(Registry.BIOME, Biome::valueOf));
REGISTRIES.put(Fluid.class, new LegacyRegistryData(Registry.FLUID, Fluid::valueOf));
REGISTRIES.put(Villager.Type.class, new LegacyRegistryData(Registry.VILLAGER_TYPE, Villager.Type::valueOf));
REGISTRIES.put(Villager.Profession.class, new LegacyRegistryData(Registry.VILLAGER_PROFESSION, Villager.Profession::valueOf));
REGISTRIES.put(Sound.class, new LegacyRegistryData(Registry.SOUNDS, Sound::valueOf));
REGISTRIES.put(Frog.Variant.class, new LegacyRegistryData(Registry.FROG_VARIANT, Frog.Variant::valueOf));
REGISTRIES.put(Cat.Type.class, new LegacyRegistryData(Registry.CAT_VARIANT, Cat.Type::valueOf));
REGISTRIES.put(MapCursor.Type.class, new LegacyRegistryData(Registry.MAP_DECORATION_TYPE, MapCursor.Type::valueOf));
REGISTRIES.put(PatternType.class, new LegacyRegistryData(Registry.BANNER_PATTERN, PatternType::valueOf));
}
public static LegacyRegistryData getRegistryData(Class<?> clazz) {
ClassTraverser it = new ClassTraverser(clazz);
LegacyRegistryData registryData;
while (it.hasNext()) {
registryData = EnumEvil.REGISTRIES.get(it.next());
if (registryData != null) {
return registryData;
}
}
return null;
}
@DoNotReroute
public static Registry<?> getRegistry(Class<?> clazz) {
LegacyRegistryData registryData = EnumEvil.getRegistryData(clazz);
if (registryData != null) {
return registryData.registry();
}
return null;
}
@RerouteStatic("com/google/common/collect/Maps")
@RerouteReturnType("java/util/EnumSet")
public static ImposterEnumMap newEnumMap(Class<?> objectClass) {
return new ImposterEnumMap(objectClass);
}
@RerouteStatic("com/google/common/collect/Maps")
@RerouteReturnType("java/util/EnumSet")
public static ImposterEnumMap newEnumMap(Map map) {
return new ImposterEnumMap(map);
}
@RerouteStatic("com/google/common/collect/Sets")
public static Collector<?, ?, ?> toImmutableEnumSet() {
return Collectors.toUnmodifiableSet();
}
@RerouteStatic("com/google/common/collect/Sets")
@RerouteReturnType("java/util/EnumSet")
public static ImposterEnumSet newEnumSet(Iterable<?> iterable, Class<?> clazz) {
ImposterEnumSet set = ImposterEnumSet.noneOf(clazz);
for (Object some : iterable) {
set.add(some);
}
return set;
}
@RerouteStatic("com/google/common/collect/Sets")
public static ImmutableSet<?> immutableEnumSet(Iterable<?> iterable) {
return ImmutableSet.of(iterable);
}
@RerouteStatic("com/google/common/collect/Sets")
public static ImmutableSet<?> immutableEnumSet(@RerouteArgumentType("java/lang/Enum") Object first, @RerouteArgumentType("[java/lang/Enum") Object... rest) {
return ImmutableSet.of(first, rest);
}
@RerouteStatic("com/google/common/base/Enums")
public static Field getField(@RerouteArgumentType("java/lang/Enum") Object value) {
if (value instanceof Enum eValue) {
return Enums.getField(eValue);
}
try {
return value.getClass().getField(((OldEnum) value).name());
} catch (NoSuchFieldException impossible) {
throw new AssertionError(impossible);
}
}
@RerouteStatic("com/google/common/base/Enums")
public static com.google.common.base.Optional getIfPresent(Class clazz, String name, @InjectPluginVersion ApiVersion apiVersion) {
if (clazz.isEnum()) {
return Enums.getIfPresent(clazz, name);
}
Registry registry = EnumEvil.getRegistry(clazz);
if (registry == null) {
return com.google.common.base.Optional.absent();
}
name = FieldRename.rename(apiVersion, clazz.getName().replace('.', '/'), name);
return com.google.common.base.Optional.fromNullable(registry.get(NamespacedKey.fromString(name.toLowerCase(Locale.ROOT))));
}
@RerouteStatic("com/google/common/base/Enums")
public static Converter stringConverter(Class clazz, @InjectPluginVersion ApiVersion apiVersion) {
if (clazz.isEnum()) {
return Enums.stringConverter(clazz);
}
return new StringConverter(apiVersion, clazz);
}
public static Object[] getEnumConstants(Class<?> clazz) {
if (clazz.isEnum()) {
return clazz.getEnumConstants();
}
Registry<?> registry = EnumEvil.getRegistry(clazz);
if (registry == null) {
return clazz.getEnumConstants();
}
// Need to do this in such away to avoid ClassCastException
List<?> values = Lists.newArrayList(registry);
Object array = Array.newInstance(clazz, values.size());
for (int i = 0; i < values.size(); i++) {
Array.set(array, i, values.get(i));
}
return (Object[]) array;
}
public static String name(@RerouteArgumentType("java/lang/Enum") Object object) {
if (object instanceof OldEnum<?>) {
return ((OldEnum<?>) object).name();
}
return ((Enum<?>) object).name();
}
public static int compareTo(@RerouteArgumentType("java/lang/Enum") Object object, @RerouteArgumentType("java/lang/Enum") Object other) {
if (object instanceof OldEnum<?>) {
return ((OldEnum) object).compareTo((OldEnum) other);
}
return ((Enum) object).compareTo((Enum) other);
}
public static Class<?> getDeclaringClass(@RerouteArgumentType("java/lang/Enum") Object object) {
Class<?> clazz = object.getClass();
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? clazz : zuper;
}
public static Optional<Enum.EnumDesc> describeConstable(@RerouteArgumentType("java/lang/Enum") Object object) {
return EnumEvil.getDeclaringClass(object)
.describeConstable()
.map(c -> Enum.EnumDesc.of(c, EnumEvil.name(object)));
}
@RerouteStatic("java/lang/Enum")
@RerouteReturnType("java/lang/Enum")
public static Object valueOf(Class enumClass, String name, @InjectPluginVersion ApiVersion apiVersion) {
name = FieldRename.rename(apiVersion, enumClass.getName().replace('.', '/'), name);
LegacyRegistryData registryData = EnumEvil.getRegistryData(enumClass);
if (registryData != null) {
return registryData.function().apply(name);
}
return Enum.valueOf(enumClass, name);
}
public static String toString(@RerouteArgumentType("java/lang/Enum") Object object) {
return object.toString();
}
public static int ordinal(@RerouteArgumentType("java/lang/Enum") Object object) {
if (object instanceof OldEnum<?>) {
return ((OldEnum<?>) object).ordinal();
}
return ((Enum<?>) object).ordinal();
}
public record LegacyRegistryData(Registry<?> registry, Function<String, ?> function) {
}
private static final class StringConverter<T extends OldEnum<T>> extends Converter<String, T> implements Serializable {
private final ApiVersion apiVersion;
private final Class<T> clazz;
private transient LegacyRegistryData registryData;
StringConverter(ApiVersion apiVersion, Class<T> clazz) {
this.apiVersion = apiVersion;
this.clazz = clazz;
}
@Override
protected T doForward(String value) {
if (this.registryData == null) {
this.registryData = EnumEvil.getRegistryData(this.clazz);
}
value = FieldRename.rename(this.apiVersion, this.clazz.getName().replace('.', '/'), value);
return (T) this.registryData.function().apply(value);
}
@Override
protected String doBackward(T enumValue) {
return enumValue.name();
}
@Override
public boolean equals(Object object) {
if (object instanceof StringConverter<?> that) {
return this.clazz.equals(that.clazz);
}
return false;
}
@Override
public int hashCode() {
return this.clazz.hashCode();
}
@Override
public String toString() {
return "Enums.stringConverter(" + this.clazz.getName() + ".class)";
}
private static final long serialVersionUID = 0L;
}
}
@@ -1,137 +0,0 @@
package org.bukkit.craftbukkit.legacy.enums;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
* The "I can't believe it works" map.
* It replaces every EnumMap with the ImposterEnumMap and uses a HashMap instead of an object array.
* Used so that plugins which use an EnumMap still work.
*/
public class ImposterEnumMap extends AbstractMap<Object, Object> {
private final Class<?> objectClass;
private final Map map;
public ImposterEnumMap(Class<?> objectClass) {
this.objectClass = objectClass;
this.map = ImposterEnumMap.getMap(objectClass);
}
public ImposterEnumMap(EnumMap enumMap) {
this.objectClass = DummyEnum.class;
this.map = enumMap.clone();
}
public ImposterEnumMap(Map map) {
if (map instanceof ImposterEnumMap) {
this.objectClass = ((ImposterEnumMap) map).objectClass;
this.map = ImposterEnumMap.getMap(this.objectClass);
} else {
this.objectClass = DummyEnum.class;
this.map = new TreeMap();
}
this.map.putAll(map);
}
private static Map getMap(Class<?> objectClass) {
// Since we replace every enum map we might also replace some maps which are for real enums.
// If this is the case use a EnumMap instead of a HashMap
if (objectClass.isEnum()) {
return new EnumMap(objectClass);
} else {
return new HashMap();
}
}
@Override
public int size() {
return this.map.size();
}
@Override
public boolean containsValue(Object value) {
return this.map.containsValue(value);
}
@Override
public boolean containsKey(Object key) {
return this.map.containsKey(key);
}
@Override
public Object get(Object key) {
return this.map.get(key);
}
@Override
public Object put(Object key, Object value) {
this.typeCheck(key);
return this.map.put(key, value);
}
@Override
public Object remove(Object key) {
return this.map.remove(key);
}
@Override
public void putAll(Map<? extends Object, ?> m) {
if (this.map instanceof EnumMap<?, ?>) {
this.map.putAll(m);
}
super.putAll(m);
}
@Override
public void clear() {
this.map.clear();
}
@Override
public Set<Object> keySet() {
return this.map.keySet();
}
@Override
public Collection<Object> values() {
return this.map.values();
}
@Override
public Set<Entry<Object, Object>> entrySet() {
return this.map.entrySet();
}
@Override
public boolean equals(Object o) {
return this.map.equals(o);
}
@Override
public int hashCode() {
return this.map.hashCode();
}
@Override
public ImposterEnumMap clone() {
ImposterEnumMap enumMap = new ImposterEnumMap(this.objectClass);
enumMap.putAll(this.map);
return enumMap;
}
private void typeCheck(Object object) {
if (this.objectClass != DummyEnum.class) {
if (!this.objectClass.isAssignableFrom(object.getClass())) {
throw new ClassCastException(object.getClass() + " != " + this.objectClass);
}
}
}
}
@@ -1,346 +0,0 @@
package org.bukkit.craftbukkit.legacy.enums;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
import org.bukkit.Registry;
import org.bukkit.util.OldEnum;
import org.jetbrains.annotations.NotNull;
public class ImposterEnumSet extends AbstractSet<Object> {
private final Class<?> objectClass;
private final Set set;
private static Set createSet(Class<?> clazz) {
if (clazz.isEnum()) {
return EnumSet.noneOf((Class<Enum>) clazz);
} else {
return new TreeSet();
}
}
public static ImposterEnumSet noneOf(Class<?> clazz) {
Set set = ImposterEnumSet.createSet(clazz);
return new ImposterEnumSet(set, clazz);
}
public static ImposterEnumSet allOf(Class<?> clazz) {
Set set;
if (clazz.isEnum()) {
set = EnumSet.allOf((Class<Enum>) clazz);
} else {
set = new HashSet();
Registry registry = EnumEvil.getRegistry(clazz);
if (registry == null) {
throw new IllegalArgumentException("Class " + clazz + " is not an Enum nor an OldEnum");
}
for (Object object : registry) {
set.add(object);
}
}
return new ImposterEnumSet(set, clazz);
}
public static ImposterEnumSet copyOf(Set set) {
Class<?> clazz;
if (set instanceof ImposterEnumSet imposter) {
set = imposter.set;
clazz = imposter.objectClass;
} else {
if (!set.isEmpty()) {
clazz = (Class<?>) set.stream()
.filter(val -> val != null)
.map(val -> val.getClass())
.findAny()
.orElse(Object.class);
} else {
clazz = Object.class;
}
}
Set newSet = ImposterEnumSet.createSet(clazz);
newSet.addAll(set);
return new ImposterEnumSet(newSet, clazz);
}
public static ImposterEnumSet copyOf(Collection collection) {
Class<?> clazz;
if (collection instanceof ImposterEnumSet imposter) {
collection = imposter.set;
clazz = imposter.objectClass;
} else {
if (!collection.isEmpty()) {
clazz = (Class<?>) collection.stream()
.filter(val -> val != null)
.map(val -> val.getClass())
.findAny()
.orElse(Object.class);
} else {
clazz = Object.class;
}
}
Set newSet = ImposterEnumSet.createSet(clazz);
newSet.addAll(collection);
return new ImposterEnumSet(newSet, clazz);
}
public static ImposterEnumSet complementOf(Set set) {
Class<?> clazz = null;
if (set instanceof ImposterEnumSet imposter) {
set = imposter.set;
clazz = imposter.objectClass;
}
if (set instanceof EnumSet<?> enumSet) {
enumSet = EnumSet.complementOf(enumSet);
if (clazz != null) {
return new ImposterEnumSet(enumSet, clazz);
}
if (!set.isEmpty()) {
clazz = (Class<?>) set.stream()
.filter(val -> val != null)
.map(val -> val.getClass())
.findAny()
.orElse(Object.class);
} else {
clazz = (Class<?>) enumSet.stream()
.filter(val -> val != null)
.map(val -> val.getClass())
.map(val -> (Class) val)
.findAny()
.orElse(Object.class);
}
return new ImposterEnumSet(enumSet, clazz);
}
if (set.isEmpty() && clazz == null) {
throw new IllegalStateException("Class is null and set is empty, cannot get class!");
}
if (clazz == null) {
clazz = (Class<?>) set.stream()
.filter(val -> val != null)
.map(val -> val.getClass())
.findAny()
.orElse(Object.class);
}
Registry registry = EnumEvil.getRegistry(clazz);
Set newSet = new HashSet();
for (Object value : registry) {
if (set.contains(value)) {
continue;
}
newSet.add(value);
}
return new ImposterEnumSet(newSet, clazz);
}
public static ImposterEnumSet of(Object e) {
Set set = ImposterEnumSet.createSet(e.getClass());
set.add(e);
return new ImposterEnumSet(set, e.getClass());
}
public static ImposterEnumSet of(Object e1, Object e2) {
Set set = ImposterEnumSet.createSet(e1.getClass());
set.add(e1);
set.add(e2);
return new ImposterEnumSet(set, e1.getClass());
}
public static ImposterEnumSet of(Object e1, Object e2, Object e3) {
Set set = ImposterEnumSet.createSet(e1.getClass());
set.add(e1);
set.add(e2);
set.add(e3);
return new ImposterEnumSet(set, e1.getClass());
}
public static ImposterEnumSet of(Object e1, Object e2, Object e3, Object e4) {
Set set = ImposterEnumSet.createSet(e1.getClass());
set.add(e1);
set.add(e2);
set.add(e3);
set.add(e4);
return new ImposterEnumSet(set, e1.getClass());
}
public static ImposterEnumSet of(Object e1, Object e2, Object e3, Object e4, Object e5) {
Set set = ImposterEnumSet.createSet(e1.getClass());
set.add(e1);
set.add(e2);
set.add(e3);
set.add(e4);
set.add(e5);
return new ImposterEnumSet(set, e1.getClass());
}
public static ImposterEnumSet of(Object e, Object... rest) {
Set set = ImposterEnumSet.createSet(e.getClass());
set.add(e);
Collections.addAll(set, rest);
return new ImposterEnumSet(set, e.getClass());
}
public static ImposterEnumSet range(Object from, Object to) {
Set set;
if (from.getClass().isEnum()) {
set = EnumSet.range((Enum) from, (Enum) to);
} else {
set = new HashSet();
Registry registry = EnumEvil.getRegistry(from.getClass());
for (Object o : registry) {
if (((OldEnum) o).ordinal() < ((OldEnum) from).ordinal()) {
continue;
}
if (((OldEnum) o).ordinal() > ((OldEnum) to).ordinal()) {
continue;
}
set.add(o);
}
}
return new ImposterEnumSet(set, from.getClass());
}
private ImposterEnumSet(Set set, Class<?> objectClass) {
this.set = set;
this.objectClass = objectClass;
}
@Override
public Iterator<Object> iterator() {
return this.set.iterator();
}
@Override
public int size() {
return this.set.size();
}
@Override
public boolean equals(Object o) {
return this.set.equals(o);
}
@Override
public int hashCode() {
return this.set.hashCode();
}
@Override
public boolean removeAll(Collection<?> c) {
return this.set.removeAll(c);
}
@Override
public boolean isEmpty() {
return this.set.isEmpty();
}
@Override
public boolean contains(Object o) {
return this.set.contains(o);
}
@NotNull
@Override
public Object[] toArray() {
return this.set.toArray();
}
@NotNull
@Override
public <T> T[] toArray(@NotNull T[] a) {
return (T[]) this.set.toArray(a);
}
@Override
public boolean add(Object o) {
this.typeCheck(o);
return this.set.add(o);
}
@Override
public boolean remove(Object o) {
return this.set.remove(o);
}
@Override
public boolean containsAll(@NotNull Collection<?> c) {
return this.set.containsAll(c);
}
@Override
public boolean addAll(@NotNull Collection<?> c) {
if (this.set instanceof EnumSet<?>) {
this.set.addAll(c);
}
return super.addAll(c);
}
@Override
public boolean retainAll(@NotNull Collection<?> c) {
return this.set.retainAll(c);
}
@Override
public void clear() {
this.set.clear();
}
@Override
public String toString() {
return this.set.toString();
}
public ImposterEnumSet clone() {
Set newSet;
if (this.set instanceof EnumSet<?> enumSet) {
newSet = enumSet.clone();
} else {
newSet = new HashSet();
newSet.addAll(this.set);
}
return new ImposterEnumSet(newSet, this.objectClass);
}
private void typeCheck(Object object) {
if (this.objectClass != DummyEnum.class) {
if (!this.objectClass.isAssignableFrom(object.getClass())) {
throw new ClassCastException(object.getClass() + " != " + this.objectClass);
}
}
}
}
@@ -1,12 +1,12 @@
package org.bukkit.craftbukkit.legacy.fieldrename;
import io.papermc.paper.plugin.ApiVersion;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
import org.bukkit.NamespacedKey;
import org.bukkit.craftbukkit.util.ApiVersion;
public record FieldRenameData(RenameData<String> renameData, RenameData<NamespacedKey> keyRenameData) {
@@ -1,6 +1,7 @@
package org.bukkit.craftbukkit.legacy.reroute;
import org.bukkit.craftbukkit.util.ApiVersion;
import io.papermc.paper.plugin.ApiVersion;
public record RequirePluginVersionData(ApiVersion minInclusive, ApiVersion maxInclusive) {
@@ -1,9 +1,9 @@
package org.bukkit.craftbukkit.legacy.reroute;
import io.papermc.paper.plugin.ApiVersion;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import org.bukkit.craftbukkit.util.ApiVersion;
import org.bukkit.craftbukkit.util.ClassTraverser;
import org.jetbrains.annotations.VisibleForTesting;
import org.objectweb.asm.Type;
@@ -1,6 +1,7 @@
package org.bukkit.craftbukkit.legacy.reroute;
import com.google.common.base.Preconditions;
import io.papermc.paper.plugin.ApiVersion;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
@@ -10,7 +11,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import org.bukkit.craftbukkit.util.ApiVersion;
import org.objectweb.asm.Type;
public class RerouteBuilder {
@@ -2,6 +2,7 @@ package org.bukkit.craftbukkit.potion;
import com.google.common.base.Preconditions;
import com.google.common.base.Suppliers;
import io.papermc.paper.plugin.ApiVersion;
import io.papermc.paper.registry.RegistryKey;
import java.util.List;
import java.util.Locale;
@@ -13,7 +14,6 @@ import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.craftbukkit.legacy.FieldRename;
import org.bukkit.craftbukkit.util.ApiVersion;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
@@ -1,132 +0,0 @@
package org.bukkit.craftbukkit.util;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
public final class ApiVersion implements Comparable<ApiVersion>, Serializable {
public static final ApiVersion CURRENT;
public static final ApiVersion FLATTENING;
public static final ApiVersion FIELD_NAME_PARITY;
public static final ApiVersion ABSTRACT_COW;
public static final ApiVersion ABSTRACT_CUBE_MOB;
public static final ApiVersion NONE;
private static final Map<String, ApiVersion> versions;
static {
versions = new HashMap<>();
CURRENT = getOrCreateVersion(Versioning.getCurrentApiVersion());
FLATTENING = getOrCreateVersion("1.13");
FIELD_NAME_PARITY = getOrCreateVersion("1.20.5");
ABSTRACT_COW = getOrCreateVersion("1.21.5");
ABSTRACT_CUBE_MOB = getOrCreateVersion("26.2");
NONE = getOrCreateVersion("none");
}
private final boolean none;
private final int major;
private final int minor;
private final int patch;
private ApiVersion() {
this.none = true;
this.major = Integer.MIN_VALUE;
this.minor = Integer.MIN_VALUE;
this.patch = Integer.MIN_VALUE;
}
private ApiVersion(int major, int minor, int patch) {
this.none = false;
this.major = major;
this.minor = minor;
this.patch = patch;
}
public static ApiVersion getOrCreateVersion(String versionString) {
if (versionString == null || versionString.trim().isEmpty() || versionString.equalsIgnoreCase("none")) {
return ApiVersion.versions.computeIfAbsent("none", s -> new ApiVersion());
}
ApiVersion version = ApiVersion.versions.get(versionString);
if (version != null) {
return version;
}
String[] versionParts = versionString.split("\\.");
if (versionParts.length != 2 && versionParts.length != 3) {
throw new IllegalArgumentException(String.format("API version string should be of format \"major.minor.patch\" or \"major.minor\", where \"major\", \"minor\" and \"patch\" are numbers. For example \"1.18.2\" or \"1.13\", but got '%s' instead.", versionString));
}
int major = ApiVersion.parseNumber(versionParts[0]);
int minor = ApiVersion.parseNumber(versionParts[1]);
int patch;
if (versionParts.length == 3) {
patch = ApiVersion.parseNumber(versionParts[2]);
} else {
patch = 0;
}
versionString = ApiVersion.toVersionString(major, minor, patch);
return ApiVersion.versions.computeIfAbsent(versionString, s -> new ApiVersion(major, minor, patch));
}
private static int parseNumber(String number) {
return Integer.parseInt(number);
}
private static String toVersionString(int major, int minor, int patch) {
return major + "." + minor + "." + patch;
}
@Override
public int compareTo(@NotNull ApiVersion other) {
int result = Integer.compare(this.major, other.major);
if (result == 0) {
result = Integer.compare(this.minor, other.minor);
}
if (result == 0) {
result = Integer.compare(this.patch, other.patch);
}
return result;
}
public String getVersionString() {
if (this.none) {
return "none";
}
return ApiVersion.toVersionString(this.major, this.minor, this.patch);
}
public boolean isNewerThan(ApiVersion apiVersion) {
return this.compareTo(apiVersion) > 0;
}
public boolean isOlderThan(ApiVersion apiVersion) {
return this.compareTo(apiVersion) < 0;
}
public boolean isNewerThanOrSameAs(ApiVersion apiVersion) {
return this.compareTo(apiVersion) >= 0;
}
public boolean isOlderThanOrSameAs(ApiVersion apiVersion) {
return this.compareTo(apiVersion) <= 0;
}
@Override
public String toString() {
return this.getVersionString();
}
private static final long serialVersionUID = 0L;
}
@@ -1,15 +1,13 @@
package org.bukkit.craftbukkit.util;
import com.google.common.base.Predicates;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.io.ByteStreams;
import io.papermc.paper.plugin.ApiVersion;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
@@ -31,7 +29,6 @@ import org.bukkit.Material;
import org.bukkit.craftbukkit.legacy.FieldRename;
import org.bukkit.craftbukkit.legacy.MaterialRerouting;
import org.bukkit.craftbukkit.legacy.MethodRerouting;
import org.bukkit.craftbukkit.legacy.enums.EnumEvil;
import org.bukkit.craftbukkit.legacy.reroute.Reroute;
import org.bukkit.craftbukkit.legacy.reroute.RerouteArgument;
import org.bukkit.craftbukkit.legacy.reroute.RerouteBuilder;
@@ -70,12 +67,6 @@ public class Commodore {
"org/bukkit/inventory/ItemStack (S)V setDurability"
));
private static final Map<String, String> ENUM_RENAMES = Map.of(
"java/lang/Enum", "java/lang/Object",
"java/util/EnumSet", "org/bukkit/craftbukkit/legacy/enums/ImposterEnumSet",
"java/util/EnumMap", "org/bukkit/craftbukkit/legacy/enums/ImposterEnumMap"
);
private static final Map<String, String> RENAMES = Map.of(
"org/bukkit/entity/TextDisplay$TextAligment", "org/bukkit/entity/TextDisplay$TextAlignment", // SPIGOT-7335
"org/spigotmc/event/entity/EntityMountEvent", "org/bukkit/event/entity/EntityMountEvent",
@@ -123,7 +114,6 @@ public class Commodore {
.create(compatibilityPresent)
.forClass(FieldRename.class)
.forClass(MethodRerouting.class)
.forClass(EnumEvil.class)
.build();
this.reroutes.clear();
@@ -19,6 +19,8 @@ import io.papermc.paper.adventure.PaperAdventure;
import io.papermc.paper.datapack.DynamicBuiltinPack;
import io.papermc.paper.datapack.DynamicBuiltinPacks;
import io.papermc.paper.entity.EntitySerializationFlag;
import io.papermc.paper.plugin.ApiVersion;
import io.papermc.paper.plugin.entrypoint.classloader.ClassloaderBytecodeModifier;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.util.MCUtil;
import java.io.IOException;
@@ -421,6 +423,7 @@ public final class CraftMagicNumbers implements UnsafeValues {
// Paper end
try {
clazz = this.commodore.convert(clazz, pdf.getName(), ApiVersion.getOrCreateVersion(pdf.getAPIVersion()), ((CraftServer) Bukkit.getServer()).activeCompatibilities);
clazz = ClassloaderBytecodeModifier.bytecodeModifier().modify(pdf, clazz);
} catch (Exception ex) {
Bukkit.getLogger().log(Level.SEVERE, "Fatal error trying to convert " + pdf.getFullName() + ":" + path, ex);
}
@@ -1 +1 @@
io.papermc.paper.plugin.entrypoint.classloader.PaperClassloaderBytecodeModifier
io.papermc.paper.plugin.entrypoint.classloader.bytecode.PaperClassloaderBytecodeModifier
@@ -3,6 +3,7 @@ package org.bukkit.craftbukkit.legacy;
import static org.junit.jupiter.api.Assertions.*;
import com.google.common.base.Joiner;
import com.google.common.base.Predicates;
import io.papermc.paper.plugin.ApiVersion;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -15,7 +16,6 @@ import java.util.stream.Stream;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.legacy.reroute.Reroute;
import org.bukkit.craftbukkit.legacy.reroute.RerouteBuilder;
import org.bukkit.craftbukkit.util.ApiVersion;
import org.bukkit.craftbukkit.util.Commodore;
import org.bukkit.support.environment.Normal;
import org.junit.jupiter.api.AfterAll;
@@ -2,9 +2,9 @@ package org.bukkit.craftbukkit.legacy.reroute;
import static org.junit.jupiter.api.Assertions.*;
import com.google.common.base.Predicates;
import io.papermc.paper.plugin.ApiVersion;
import java.util.List;
import java.util.Map;
import org.bukkit.craftbukkit.util.ApiVersion;
import org.bukkit.support.environment.Normal;
import org.junit.jupiter.api.Test;
@@ -20,12 +20,12 @@ public class InjectPluginVersionTest extends AbstractRerouteTest {
"java/lang/Object",
"getList",
false,
"(Ljava/lang/Object;Lorg/bukkit/craftbukkit/util/ApiVersion;)Ljava/util/List;",
"(Ljava/lang/Object;Lio/papermc/paper/plugin/ApiVersion;)Ljava/util/List;",
"org/bukkit/craftbukkit/legacy/reroute/InjectPluginVersionTest$InjectPluginVersionTestData",
"getList",
this.create(
this.create("Ljava/lang/Object;", "Ljava/lang/Object;", false, false, null),
this.create("Lorg/bukkit/craftbukkit/util/ApiVersion;", "Lorg/bukkit/craftbukkit/util/ApiVersion;", false, true, null)
this.create("Lio/papermc/paper/plugin/ApiVersion;", "Lio/papermc/paper/plugin/ApiVersion;", false, true, null)
),
"Ljava/util/List;",
true,
@@ -1,8 +1,9 @@
package org.bukkit.craftbukkit.legacy.reroute;
import static org.junit.jupiter.api.Assertions.*;
import io.papermc.paper.plugin.ApiVersion;
import java.lang.annotation.Annotation;
import org.bukkit.craftbukkit.util.ApiVersion;
import org.bukkit.support.environment.Normal;
import org.junit.jupiter.api.Test;
@@ -19,56 +20,56 @@ public class RequirePluginVersionDataTest {
@Test
public void testDifferent() {
RequirePluginVersionData data = RequirePluginVersionData.create(new RequirePluginVersionImpl("", "1.21", "1.23"));
RequirePluginVersionData data = RequirePluginVersionData.create(new RequirePluginVersionImpl("", "1.20", "1.21"));
assertEquals(ApiVersion.getOrCreateVersion("1.21"), data.minInclusive());
assertEquals(ApiVersion.getOrCreateVersion("1.23"), data.maxInclusive());
assertEquals(ApiVersion.getOrCreateVersion("1.20"), data.minInclusive());
assertEquals(ApiVersion.getOrCreateVersion("1.21"), data.maxInclusive());
}
@Test
public void testValue() {
RequirePluginVersionData data = RequirePluginVersionData.create(new RequirePluginVersionImpl("1.42", "", ""));
RequirePluginVersionData data = RequirePluginVersionData.create(new RequirePluginVersionImpl("1.21", "", ""));
assertEquals(ApiVersion.getOrCreateVersion("1.42"), data.minInclusive());
assertEquals(ApiVersion.getOrCreateVersion("1.42"), data.maxInclusive());
assertEquals(ApiVersion.getOrCreateVersion("1.21"), data.minInclusive());
assertEquals(ApiVersion.getOrCreateVersion("1.21"), data.maxInclusive());
}
@Test
public void testValueAndMin() {
assertThrows(IllegalArgumentException.class, () -> RequirePluginVersionData.create(new RequirePluginVersionImpl("1.42", "1.52", "")));
assertThrows(IllegalArgumentException.class, () -> RequirePluginVersionData.create(new RequirePluginVersionImpl("1.20", "1.21", "")));
}
@Test
public void testValueAndMax() {
assertThrows(IllegalArgumentException.class, () -> RequirePluginVersionData.create(new RequirePluginVersionImpl("1.42", "", "1.53")));
assertThrows(IllegalArgumentException.class, () -> RequirePluginVersionData.create(new RequirePluginVersionImpl("1.20", "", "1.21")));
}
@Test
public void testValueAndMinMax() {
assertThrows(IllegalArgumentException.class, () -> RequirePluginVersionData.create(new RequirePluginVersionImpl("1.42", "1.51", "1.53")));
assertThrows(IllegalArgumentException.class, () -> RequirePluginVersionData.create(new RequirePluginVersionImpl("1.19", "1.20", "1.21")));
}
@Test
public void testMinNewerThanMax() {
assertThrows(IllegalArgumentException.class, () -> RequirePluginVersionData.create(new RequirePluginVersionImpl("", "1.59", "1.57")));
assertThrows(IllegalArgumentException.class, () -> RequirePluginVersionData.create(new RequirePluginVersionImpl("", "1.21", "1.20")));
}
@Test
public void testOnlyMin() {
RequirePluginVersionData data = RequirePluginVersionData.create(new RequirePluginVersionImpl("", "1.44", ""));
RequirePluginVersionData data = RequirePluginVersionData.create(new RequirePluginVersionImpl("", "1.21", ""));
assertEquals(ApiVersion.getOrCreateVersion("1.44"), data.minInclusive());
assertEquals(ApiVersion.getOrCreateVersion("1.21"), data.minInclusive());
assertNull(data.maxInclusive());
}
@Test
public void testOnlyMax() {
RequirePluginVersionData data = RequirePluginVersionData.create(new RequirePluginVersionImpl("", "", "1.96"));
RequirePluginVersionData data = RequirePluginVersionData.create(new RequirePluginVersionImpl("", "", "1.21"));
assertNull(data.minInclusive());
assertEquals(ApiVersion.getOrCreateVersion("1.96"), data.maxInclusive());
assertEquals(ApiVersion.getOrCreateVersion("1.21"), data.maxInclusive());
}
@Test
@@ -1,8 +1,8 @@
package org.bukkit.craftbukkit.legacy.reroute;
import io.papermc.paper.plugin.ApiVersion;
import java.util.List;
import java.util.Map;
import org.bukkit.craftbukkit.util.ApiVersion;
import org.bukkit.support.environment.Normal;
import org.junit.jupiter.api.Test;
@@ -26,7 +26,7 @@ public class RequirePluginVersionTest extends AbstractRerouteTest {
),
"Ljava/util/List;",
true,
new RequirePluginVersionData(ApiVersion.getOrCreateVersion("1.42"), ApiVersion.getOrCreateVersion("1.42"))
new RequirePluginVersionData(ApiVersion.getOrCreateVersion("1.21"), ApiVersion.getOrCreateVersion("1.21"))
)
)
);
@@ -34,7 +34,7 @@ public class RequirePluginVersionTest extends AbstractRerouteTest {
public static class RequirePluginVersionTestData {
@RequirePluginVersion("1.42")
@RequirePluginVersion("1.21")
public static List<String> getList(Object o) {
return null;
}
@@ -1,6 +1,8 @@
package org.bukkit.craftbukkit.util;
import static org.junit.jupiter.api.Assertions.*;
import io.papermc.paper.plugin.ApiVersion;
import java.util.stream.Stream;
import net.minecraft.SharedConstants;
import org.bukkit.support.environment.VanillaFeature;
@@ -19,7 +21,9 @@ public class ApiVersionTest {
Arguments.of("none", "none"),
Arguments.of("1.12", "1.12.0"),
Arguments.of("1.13.3", "1.13.3"),
Arguments.of("1.+20.3", "1.20.3")
Arguments.of("1.+20.3", "1.20.3"),
Arguments.of("26.1", "26.1.0"),
Arguments.of("28.2.3", "28.2.3")
);
}
@@ -27,7 +31,11 @@ public class ApiVersionTest {
return Stream.of(
Arguments.of("none", "none", CompareResult.SAME),
Arguments.of("none", "1.20", CompareResult.SMALLER),
Arguments.of("2.20.3", "1.30.4", CompareResult.BIGGER),
Arguments.of("1.20.3", "1.19.4", CompareResult.BIGGER),
Arguments.of("26.1", "1.21.4", CompareResult.BIGGER),
Arguments.of("26.3.2", "1.21.11", CompareResult.BIGGER),
Arguments.of("none", "26.1.2", CompareResult.SMALLER),
Arguments.of("1.8.9", "26.1.2", CompareResult.SMALLER),
Arguments.of("1.13", "1.12", CompareResult.BIGGER),
Arguments.of("1.13.2", "1.13.3", CompareResult.SMALLER)
);
@@ -90,8 +98,8 @@ public class ApiVersionTest {
@Test
public void testSameInstance() {
ApiVersion one = ApiVersion.getOrCreateVersion("1.23.3");
ApiVersion second = ApiVersion.getOrCreateVersion("1.+23.3");
ApiVersion one = ApiVersion.getOrCreateVersion("1.20.3");
ApiVersion second = ApiVersion.getOrCreateVersion("1.+20.3");
assertSame(one, second);
}