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 Apr 29, 2024
1 parent 8f7ac62 commit c37d61b
Show file tree
Hide file tree
Showing 19 changed files with 4,186 additions and 0 deletions.
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-server"))
implementation("io.github.classgraph:classgraph:4.8.47")
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.

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

import io.papermc.generator.types.EntityMetaWatcherGenerator;
import io.papermc.generator.types.SourceGenerator;

public interface Generators {

SourceGenerator[] SERVER = {
new EntityMetaWatcherGenerator("EntityMetaWatcher", "io.papermc.paper.entity.meta")
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package io.papermc.generator;

import com.mojang.logging.LogUtils;

import io.papermc.generator.types.SourceGenerator;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import net.minecraft.SharedConstants;
import net.minecraft.core.LayeredRegistryAccess;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.RegistryDataLoader;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.RegistryLayer;
import net.minecraft.server.WorldLoader;
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 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;

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();
layers = WorldLoader.loadAndReplaceLayer(resourceManager, layers, RegistryLayer.WORLDGEN, RegistryDataLoader.WORLDGEN_REGISTRIES);
REGISTRY_ACCESS = layers.compositeAccess().freeze();
}

private Main() {
}

public static void main(final String[] args) {
LOGGER.info("Running API generators...");
generate(Paths.get(args[0]), Generators.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,193 @@
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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
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;

import static io.papermc.generator.utils.Annotations.NOT_NULL;

@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 = 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)
.addAnnotations(List.of(NOT_NULL))
.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)
.addAnnotations(List.of(NOT_NULL))
.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));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.papermc.generator.types;

import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.TypeSpec;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public abstract class SimpleGenerator implements SourceGenerator {

protected final String className;
protected final String packageName;

protected SimpleGenerator(String className, String packageName) {
this.className = className;
this.packageName = packageName;
}

protected abstract TypeSpec getTypeSpec();

protected abstract JavaFile.Builder file(JavaFile.Builder builder);

@Override
public void writeToFile(Path parent) throws IOException {
Path packagePath = parent.resolve(this.packageName.replace('.', '/'));
Files.createDirectories(packagePath);

JavaFile.Builder builder = JavaFile.builder(this.packageName, this.getTypeSpec())
.indent(" ");
this.file(builder);

Files.writeString(packagePath.resolve(this.className + ".java"), this.file(builder).build().toString(), StandardCharsets.UTF_8);
}

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

import java.io.IOException;
import java.nio.file.Path;

public interface SourceGenerator {

void writeToFile(Path parent) throws IOException;
}

0 comments on commit c37d61b

Please sign in to comment.