Skip to content

Commit

Permalink
disguise api
Browse files Browse the repository at this point in the history
and generate maps to allow entity metadata syncher validation
  • Loading branch information
yannicklamprecht committed Nov 3, 2024
1 parent ce0a041 commit 44df8ba
Show file tree
Hide file tree
Showing 14 changed files with 4,578 additions and 0 deletions.
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ updatingMinecraft=false
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.vfs.watch=false
org.gradle.jvmargs=-Xmx4096m
2 changes: 2 additions & 0 deletions paper-server-generator.settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Uncomment to enable the 'paper-server-generator' project
// include(":paper-server-generator")
36 changes: 36 additions & 0 deletions paper-server-generator/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import io.papermc.paperweight.PaperweightSourceGeneratorHelper
import io.papermc.paperweight.extension.PaperweightSourceGeneratorExt

plugins {
java
}

plugins.apply(PaperweightSourceGeneratorHelper::class)

extensions.configure(PaperweightSourceGeneratorExt::class) {
atFile.set(projectDir.toPath().resolve("wideners.at").toFile())
}

dependencies {
implementation("com.squareup:javapoet:1.13.0")
implementation(project(":paper-api"))
implementation(project(":paper-api-generator"))
implementation("io.github.classgraph:classgraph:4.8.112")
implementation("org.jetbrains:annotations:24.0.1")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

tasks.register<JavaExec>("generate") {
dependsOn(tasks.check)
mainClass.set("io.papermc.generator.Main")
classpath(sourceSets.main.map { it.runtimeClasspath })
args(projectDir.toPath().resolve("generated").toString())
}

tasks.test {
useJUnitPlatform()
}

group = "io.papermc.paper"
version = "1.0-SNAPSHOT"

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package io.papermc.generator;

import com.google.common.util.concurrent.MoreExecutors;
import com.mojang.logging.LogUtils;

import io.papermc.generator.types.EntityMetaWatcherGenerator;
import io.papermc.generator.types.EntityTypeToEntityClassGenerator;
import io.papermc.generator.types.SourceGenerator;
import io.papermc.generator.utils.TagCollector;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import net.minecraft.SharedConstants;
import net.minecraft.commands.Commands;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.LayeredRegistryAccess;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.RegistryDataLoader;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.RegistryLayer;
import net.minecraft.server.ReloadableServerResources;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.repository.Pack;
import net.minecraft.server.packs.repository.PackRepository;
import net.minecraft.server.packs.repository.ServerPacksSource;
import net.minecraft.server.packs.resources.MultiPackResourceManager;
import net.minecraft.tags.TagKey;
import net.minecraft.tags.TagLoader;
import net.minecraft.world.flag.FeatureFlags;
import org.apache.commons.io.file.PathUtils;
import org.slf4j.Logger;

public final class Main {

private static final Logger LOGGER = LogUtils.getLogger();
public static final RegistryAccess.Frozen REGISTRY_ACCESS;
public static final Map<TagKey<?>, String> EXPERIMENTAL_TAGS;

static {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
final PackRepository resourceRepository = ServerPacksSource.createVanillaTrustedRepository();
resourceRepository.reload();
final MultiPackResourceManager resourceManager = new MultiPackResourceManager(PackType.SERVER_DATA, resourceRepository.getAvailablePacks().stream().map(Pack::open).toList());
LayeredRegistryAccess<RegistryLayer> layers = RegistryLayer.createRegistryAccess();
final List<Registry.PendingTags<?>> pendingTags = TagLoader.loadTagsForExistingRegistries(resourceManager, layers.getLayer(RegistryLayer.STATIC));
final List<HolderLookup.RegistryLookup<?>> worldGenLayer = TagLoader.buildUpdatedLookups(layers.getAccessForLoading(RegistryLayer.WORLDGEN), pendingTags);
final RegistryAccess.Frozen frozenWorldgenRegistries = RegistryDataLoader.load(resourceManager, worldGenLayer, RegistryDataLoader.WORLDGEN_REGISTRIES);
layers = layers.replaceFrom(RegistryLayer.WORLDGEN, frozenWorldgenRegistries);
REGISTRY_ACCESS = layers.compositeAccess().freeze();
final ReloadableServerResources reloadableServerResources = ReloadableServerResources.loadResources(
resourceManager,
layers,
pendingTags,
FeatureFlags.VANILLA_SET,
Commands.CommandSelection.DEDICATED,
0,
MoreExecutors.directExecutor(),
MoreExecutors.directExecutor()
).join();
reloadableServerResources.updateStaticRegistryTags();
EXPERIMENTAL_TAGS = TagCollector.grabExperimental(resourceManager);
}

private Main() {
}

public static void main(final String[] args) {
LOGGER.info("Running SERVER generators...");

SourceGenerator[] SERVER = {
new EntityMetaWatcherGenerator("EntityMetaWatcher", "io.papermc.paper.entity.meta"),
new EntityTypeToEntityClassGenerator("EntityTypeToEntityClass", "io.papermc.paper.entity.meta"),
};

generate(Paths.get(args[0]), SERVER);
}

private static void generate(Path output, SourceGenerator[] generators) {
try {
if (Files.exists(output)) {
PathUtils.deleteDirectory(output);
}
Files.createDirectories(output);

for (final SourceGenerator generator : generators) {
generator.writeToFile(output);
}

LOGGER.info("Files written to {}", output.toAbsolutePath());
} catch (final Exception ex) {
throw new RuntimeException(ex);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package io.papermc.generator.types;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.WildcardTypeName;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ScanResult;
import io.papermc.generator.utils.Annotations;
import io.papermc.generator.utils.ReflectionHelper;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
import javax.lang.model.element.Modifier;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializer;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.world.entity.Entity;
import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.qual.DefaultQualifier;

@DefaultQualifier(NonNull.class)
public class EntityMetaWatcherGenerator extends SimpleGenerator {

private static final ParameterizedTypeName GENERIC_ENTITY_DATA_SERIALIZER = ParameterizedTypeName.get(ClassName.get(Map.class), ClassName.get(Long.class), ParameterizedTypeName.get(ClassName.get(EntityDataSerializer.class), WildcardTypeName.subtypeOf(Object.class)));
private static final ParameterizedTypeName ENTITY_CLASS = ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(Entity.class));
private static final ParameterizedTypeName OUTER_MAP_TYPE = ParameterizedTypeName.get(ClassName.get(Map.class), ENTITY_CLASS, GENERIC_ENTITY_DATA_SERIALIZER);

public EntityMetaWatcherGenerator(String className, String packageName) {
super(className, packageName);
}

@Override
protected TypeSpec getTypeSpec() {
Map<EntityDataSerializer<?>, String> dataAccessorStringMap = serializerMap();

List<Class<?>> classes;
try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("net.minecraft").scan()) {
classes = scanResult.getSubclasses(net.minecraft.world.entity.Entity.class.getName()).loadClasses();
}

classes = classes.stream()
.filter(clazz -> !java.lang.reflect.Modifier.isAbstract(clazz.getModifiers()))
.toList();

record Pair(Class<?> clazz, List<? extends EntityDataAccessor<?>> metaResults) {}

final List<Pair> list = classes.stream()
.map(clazz -> new Pair(
clazz,
ReflectionHelper.getAllForAllParents(clazz, EntityMetaWatcherGenerator::doFilter)
.stream()
.map(this::createData)
.filter(Objects::nonNull)
.toList()
)
)
.toList();

Map<Class<?>, List<? extends EntityDataAccessor<?>>> vanillaNames = new TreeMap<>(Comparator.comparing(Class::getSimpleName));
vanillaNames.putAll(list.stream()
.collect(Collectors.toMap(pair -> pair.clazz, pair -> pair.metaResults)));

TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(this.className)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addAnnotations(Annotations.CLASS_HEADER);

generateIdAccessorMethods(vanillaNames, dataAccessorStringMap, typeBuilder);
generateClassToTypeMap(typeBuilder, vanillaNames.keySet());
generateIsValidAccessorForEntity(typeBuilder);

return typeBuilder.build();
}

private void generateIsValidAccessorForEntity(TypeSpec.Builder builder) {
var methodBuilder = MethodSpec.methodBuilder("isValidForClass")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
.returns(boolean.class)
.addParameter(ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(Entity.class)), "clazz")
.addParameter(EntityDataAccessor.class, "accessor")
.addStatement("Map<Long, EntityDataSerializer<?>> serializerMap = VALID_ENTITY_META_MAP.get(clazz)")
.beginControlFlow("if(serializerMap == null)")
.addStatement("return false")
.endControlFlow()
.addStatement("var serializer = serializerMap.get(accessor.id())")
.addStatement("return serializer != null && serializer == accessor.serializer()");

builder.addMethod(methodBuilder.build());
}

private void generateClassToTypeMap(TypeSpec.Builder typeBuilder, Set<Class<?>> classes){
typeBuilder.addField(
FieldSpec.builder(OUTER_MAP_TYPE, "VALID_ENTITY_META_MAP", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.initializer("initialize()")
.build()
);

MethodSpec.Builder builder = MethodSpec.methodBuilder("initialize")
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.returns(OUTER_MAP_TYPE)
.addStatement("$T result = new $T<>()", OUTER_MAP_TYPE, ClassName.get(HashMap.class));

classes.forEach(aClass -> {
String name = StringUtils.uncapitalize(aClass.getSimpleName());
if(!name.isBlank()) {
builder.addStatement("result.put($T.class, $L())", aClass, name);
}
});

typeBuilder.addMethod(builder.addStatement("return $T.copyOf(result)", Map.class).build());
}

private static void generateIdAccessorMethods(Map<Class<?>, List<? extends EntityDataAccessor<?>>> vanillaNames, Map<EntityDataSerializer<?>, String> dataAccessorStringMap, TypeSpec.Builder typeBuilder) {
for (final Map.Entry<Class<?>, List<? extends EntityDataAccessor<?>>> perClassResults : vanillaNames.entrySet()) {
if (perClassResults.getKey().getSimpleName().isBlank()) {
continue;
}
var simpleName = perClassResults.getKey().getSimpleName();

ClassName hashMap = ClassName.get(HashMap.class);

MethodSpec.Builder builder = MethodSpec.methodBuilder(StringUtils.uncapitalize(simpleName))
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.returns(GENERIC_ENTITY_DATA_SERIALIZER)
.addStatement("$T result = new $T<>()", GENERIC_ENTITY_DATA_SERIALIZER, hashMap);

perClassResults.getValue().forEach(result -> {
builder.addStatement("result.put($LL, $T.$L)", result.id(), EntityDataSerializers.class, dataAccessorStringMap.get(result.serializer()));
});

var method = builder.addStatement("return $T.copyOf(result)", Map.class)
.build();

typeBuilder.addMethod(method);
}
}

private @Nullable EntityDataAccessor<?> createData(Field field) {
try {
field.setAccessible(true);
return (EntityDataAccessor<?>) field.get(null);
} catch (IllegalAccessException e) {
return null;
}
}

private static boolean doFilter(Field field) {
return java.lang.reflect.Modifier.isStatic(field.getModifiers()) && field.getType().isAssignableFrom(EntityDataAccessor.class);
}

@Override
protected JavaFile.Builder file(JavaFile.Builder builder) {
return builder.skipJavaLangImports(true);
}

private Map<EntityDataSerializer<?>, String> serializerMap(){
return Arrays.stream(EntityDataSerializers.class.getDeclaredFields())
.filter(field -> field.getType() == EntityDataSerializer.class)
.map(field -> {
try {
return Map.entry((EntityDataSerializer<?>)field.get(0), field.getName());
} catch (IllegalAccessException e) {
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
}
Loading

0 comments on commit 44df8ba

Please sign in to comment.