1 Commits
Author SHA1 Message Date
Jason Penilla 256a5a68e1 Add plugin remap tool 2024-08-06 13:45:30 -07:00
8 changed files with 1254 additions and 1 deletions

No files matched your search

+3 -1
View File
@@ -2,9 +2,11 @@ import xyz.jpenilla.resourcefactory.bukkit.BukkitPluginYaml
plugins {
`java-library`
id("io.papermc.paperweight.userdev") version "1.7.2"
id("io.papermc.paperweight.userdev")
id("xyz.jpenilla.run-paper") version "2.3.0" // Adds runServer and runMojangMappedServer tasks for testing
id("xyz.jpenilla.resource-factory-bukkit-convention") version "1.1.1" // Generates plugin.yml based on the Gradle config
`remap-plugin-src`
}
group = "io.papermc.paperweight"
+12
View File
@@ -0,0 +1,12 @@
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
gradlePluginPortal()
}
dependencies {
implementation("io.papermc.paperweight:paperweight-userdev:1.7.2")
}
@@ -0,0 +1,514 @@
/*
* Copyright (c) 2018 Cadix Development (https://www.cadixdev.org)
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which accompanies this distribution,
* and is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package modified.mercury;
import static paper.libs.org.cadixdev.mercury.util.BombeBindings.isPackagePrivate;
import paper.libs.org.cadixdev.lorenz.MappingSet;
import paper.libs.org.cadixdev.lorenz.model.ClassMapping;
import paper.libs.org.cadixdev.lorenz.model.InnerClassMapping;
import paper.libs.org.cadixdev.lorenz.model.Mapping;
import paper.libs.org.cadixdev.lorenz.model.TopLevelClassMapping;
import paper.libs.org.cadixdev.mercury.RewriteContext;
import paper.libs.org.cadixdev.mercury.jdt.rewrite.imports.ImportRewrite;
import paper.libs.org.cadixdev.mercury.util.GracefulCheck;
import paper.libs.org.eclipse.jdt.core.dom.AST;
import paper.libs.org.eclipse.jdt.core.dom.ASTNode;
import paper.libs.org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import paper.libs.org.eclipse.jdt.core.dom.AnnotatableType;
import paper.libs.org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
import paper.libs.org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration;
import paper.libs.org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import paper.libs.org.eclipse.jdt.core.dom.EnumDeclaration;
import paper.libs.org.eclipse.jdt.core.dom.IBinding;
import paper.libs.org.eclipse.jdt.core.dom.IDocElement;
import paper.libs.org.eclipse.jdt.core.dom.ITypeBinding;
import paper.libs.org.eclipse.jdt.core.dom.ImportDeclaration;
import paper.libs.org.eclipse.jdt.core.dom.Javadoc;
import paper.libs.org.eclipse.jdt.core.dom.Modifier;
import paper.libs.org.eclipse.jdt.core.dom.Name;
import paper.libs.org.eclipse.jdt.core.dom.NameQualifiedType;
import paper.libs.org.eclipse.jdt.core.dom.PackageDeclaration;
import paper.libs.org.eclipse.jdt.core.dom.QualifiedName;
import paper.libs.org.eclipse.jdt.core.dom.QualifiedType;
import paper.libs.org.eclipse.jdt.core.dom.SimpleName;
import paper.libs.org.eclipse.jdt.core.dom.SimpleType;
import paper.libs.org.eclipse.jdt.core.dom.TagElement;
import paper.libs.org.eclipse.jdt.core.dom.TypeDeclaration;
import paper.libs.org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import paper.libs.org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import paper.libs.org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class RemapperVisitor extends SimpleRemapperVisitor {
private final ImportRewrite importRewrite;
private final Deque<ImportContext> importStack = new ArrayDeque<>();
private final String simpleDeobfuscatedName;
public RemapperVisitor(RewriteContext context, MappingSet mappings, boolean javadoc) {
super(context, mappings, javadoc);
this.importRewrite = context.createImportRewrite();
importRewrite.setUseContextToFilterImplicitImports(true);
TopLevelClassMapping primary = mappings.getTopLevelClassMapping(context.getQualifiedPrimaryType()).orElse(null);
if (primary != null) {
context.setPackageName(primary.getDeobfuscatedPackage().replace('/', '.'));
this.importRewrite.setImplicitPackageName(context.getPackageName());
this.simpleDeobfuscatedName = primary.getSimpleDeobfuscatedName();
context.setPrimaryType(simpleDeobfuscatedName);
List<String> implicitTypes = new ArrayList<>();
String simpleObfuscatedName = primary.getSimpleObfuscatedName();
@SuppressWarnings("unchecked")
List<AbstractTypeDeclaration> types = context.getCompilationUnit().types();
for (AbstractTypeDeclaration type : types) {
String name = type.getName().getIdentifier();
if (name.equals(simpleObfuscatedName)) {
implicitTypes.add(simpleDeobfuscatedName);
} else {
implicitTypes.add(mappings.getTopLevelClassMapping(context.getPackageName() + '.' + name)
.map(Mapping::getSimpleDeobfuscatedName)
.orElse(name));
}
}
this.importRewrite.setImplicitTypes(implicitTypes);
} else {
this.simpleDeobfuscatedName = null;
}
}
private void remapType(SimpleName node, ITypeBinding binding) {
if (binding.isTypeVariable() || GracefulCheck.checkGracefully(this.context, binding)) {
return;
}
if (binding.getBinaryName() == null) {
throw new IllegalStateException("Binary name for binding " + binding.getQualifiedName() + " is null. Did you forget to add a library to the classpath?");
}
ClassMapping<?, ?> mapping = this.mappings.computeClassMapping(binding.getBinaryName()).orElse(null);
if (node.getParent() instanceof AbstractTypeDeclaration
|| node.getParent() instanceof QualifiedType
|| node.getParent() instanceof NameQualifiedType
|| binding.isLocal()) {
if (mapping != null) {
updateIdentifier(node, mapping.getSimpleDeobfuscatedName());
}
return;
}
String qualifiedName = (mapping != null ? mapping.getFullDeobfuscatedName().replace('/', '.') : binding.getBinaryName()).replace('$', '.');
String newName = this.importRewrite.addImport(qualifiedName, this.importStack.peek());
if (!node.getIdentifier().equals(newName) && !node.isVar()) {
if (newName.indexOf('.') == -1) {
this.context.createASTRewrite().set(node, SimpleName.IDENTIFIER_PROPERTY, newName, null);
} else {
// Qualified name
this.context.createASTRewrite().replace(node, node.getAST().newName(newName), null);
}
}
}
private void remapQualifiedType(QualifiedName node, ITypeBinding binding) {
String binaryName = binding.getBinaryName();
if (binaryName == null) {
if (this.context.getMercury().isGracefulClasspathChecks() || this.context.getMercury().isGracefulJavadocClasspathChecks() && GracefulCheck.isJavadoc(node)) {
return;
}
throw new IllegalStateException("No binary name for " + binding.getQualifiedName());
}
TopLevelClassMapping mapping = this.mappings.getTopLevelClassMapping(binaryName).orElse(null);
if (mapping == null) {
return;
}
String newName = mapping.getDeobfuscatedName().replace('/', '.');
if (binaryName.equals(newName)) {
return;
}
this.context.createASTRewrite().replace(node, node.getAST().newName(newName), null);
}
private void remapInnerType(QualifiedName qualifiedName, ITypeBinding outerClass) {
final String binaryName = outerClass.getBinaryName();
if (binaryName == null) {
if (this.context.getMercury().isGracefulClasspathChecks()) {
return;
}
throw new IllegalStateException("No binary name for " + outerClass.getQualifiedName());
}
ClassMapping<?, ?> outerClassMapping = this.mappings.computeClassMapping(binaryName).orElse(null);
if (outerClassMapping == null) {
return;
}
SimpleName node = qualifiedName.getName();
InnerClassMapping mapping = outerClassMapping.getInnerClassMapping(node.getIdentifier()).orElse(null);
if (mapping == null) {
return;
}
updateIdentifier(node, mapping.getDeobfuscatedName());
}
@Override
protected void visit(SimpleName node, IBinding binding) {
switch (binding.getKind()) {
case IBinding.TYPE:
remapType(node, (ITypeBinding) binding);
break;
case IBinding.METHOD:
case IBinding.VARIABLE:
super.visit(node, binding);
break;
case IBinding.PACKAGE:
// This is ignored because it should be covered by separate handling
// of QualifiedName (for full-qualified class references),
// PackageDeclaration and ImportDeclaration
default:
throw new IllegalStateException("Unhandled binding: " + binding.getClass().getSimpleName() + " (" + binding.getKind() + ')');
}
}
@Override
public boolean visit(final TagElement tag) {
// We don't want to visit the names of some Javadoc tags, since they can't be remapped.
if (TagElement.TAG_LINK.equals(tag.getTagName())) {
// With a @link tag, the first fragment will be a name
if (tag.fragments().size() >= 1) {
final Object fragment = tag.fragments().get(0);
// A package might be a SimpleName (test), or a QualifiedName (test.test)
if (fragment instanceof Name) {
final Name name = (Name) fragment;
final IBinding binding = name.resolveBinding();
if (binding != null) {
// We can't remap packages, so don't visit package names
if (binding.getKind() == IBinding.PACKAGE) {
return false;
}
}
}
}
}
return super.visit(tag);
}
@Override
public boolean visit(QualifiedName node) {
IBinding binding = node.resolveBinding();
if (binding == null) {
if (this.context.getMercury().isGracefulClasspathChecks()) {
return false;
}
throw new IllegalStateException("No binding for qualified name node " + node.getFullyQualifiedName());
}
if (binding.getKind() != IBinding.TYPE) {
// Unpack the qualified name and remap method/field and type separately
return true;
}
Name qualifier = node.getQualifier();
IBinding qualifierBinding = qualifier.resolveBinding();
switch (qualifierBinding.getKind()) {
case IBinding.PACKAGE:
// Remap full qualified type
remapQualifiedType(node, (ITypeBinding) binding);
break;
case IBinding.TYPE:
// Remap inner type separately
remapInnerType(node, (ITypeBinding) qualifierBinding);
// Remap the qualifier
qualifier.accept(this);
break;
default:
throw new IllegalStateException("Unexpected qualifier binding: " + binding.getClass().getSimpleName() + " (" + binding.getKind() + ')');
}
return false;
}
@Override
public boolean visit(NameQualifiedType node) {
// Annotated inner class -> com.package.Outer.@NonNull Inner
// existing mechanisms will handle
final IBinding qualBinding = node.getQualifier().resolveBinding();
if (qualBinding != null && qualBinding.getKind() == IBinding.TYPE) {
return true;
}
ITypeBinding binding = node.getName().resolveTypeBinding();
if (binding == null) {
if (this.context.getMercury().isGracefulClasspathChecks()) {
return false;
}
throw new IllegalStateException("No binding for qualified name node " + node.getName());
}
final ClassMapping<?, ?> classMapping = this.mappings.computeClassMapping(binding.getBinaryName()).orElse(null);
if (classMapping == null) {
return false;
}
// qualified -> default package (test.@NonNull ObfClass -> @NonNull Core):
final String deobfPackage = classMapping.getDeobfuscatedPackage();
final ASTRewrite rewrite = this.context.createASTRewrite();
if (deobfPackage == null || deobfPackage.isEmpty()) {
// if we have annotations, those need to be moved to a new SimpleType node
final ASTNode nameNode;
if (node.isAnnotatable() && !node.annotations().isEmpty()) {
final SimpleType type = node.getName().getAST().newSimpleType((Name) rewrite.createCopyTarget(node.getName()));
transferAnnotations(node, type);
nameNode = type;
} else {
nameNode = node.getName();
}
rewrite.replace(node, nameNode, null);
} else {
// qualified -> other qualified:
rewrite.set(node, NameQualifiedType.QUALIFIER_PROPERTY, node.getAST().newName(deobfPackage.replace('/', '.')), null);
}
node.getName().accept(this);
return false;
}
@Override
public boolean visit(PackageDeclaration node) {
String currentPackage = node.getName().getFullyQualifiedName();
if (this.context.getPackageName().isEmpty()) {
// remove package declaration if remapped to root package
this.context.createASTRewrite().remove(node, null);
} else if (!currentPackage.equals(this.context.getPackageName())) {
this.context.createASTRewrite().replace(node.getName(), node.getAST().newName(this.context.getPackageName()), null);
}
return false;
}
@Override
public boolean visit(ImportDeclaration node) {
if (node.isStatic()) {
// Remap class/member reference separately
return true;
}
IBinding binding = node.resolveBinding();
if (binding != null) {
switch (binding.getKind()) {
case IBinding.TYPE:
ITypeBinding typeBinding = (ITypeBinding) binding;
String name = typeBinding.getBinaryName();
if (name == null) {
if (this.context.getMercury().isGracefulClasspathChecks()) {
return false;
}
throw new IllegalStateException("No binary name for " + typeBinding.getQualifiedName() + ". Did you add the library to the classpath?");
}
ClassMapping<?, ?> mapping = this.mappings.computeClassMapping(name).orElse(null);
if (mapping != null && !name.equals(mapping.getFullDeobfuscatedName().replace('/', '.'))) {
this.importRewrite.removeImport(typeBinding.getQualifiedName());
} else if (this.simpleDeobfuscatedName != null && this.simpleDeobfuscatedName.equals(typeBinding.getName())) {
this.importRewrite.removeImport(typeBinding.getQualifiedName());
}
break;
}
}
return false;
}
private void pushImportContext(ITypeBinding binding) {
ImportContext context = new ImportContext(this.importRewrite.getDefaultImportRewriteContext(), this.importStack.peek());
collectImportContext(context, binding);
this.importStack.push(context);
}
private void collectImportContext(ImportContext context, ITypeBinding binding) {
if (binding == null) {
return;
}
// Names from inner classes
for (ITypeBinding inner : binding.getDeclaredTypes()) {
if (GracefulCheck.checkGracefully(this.context, inner)) {
continue;
}
int modifiers = inner.getModifiers();
if (Modifier.isPrivate(modifiers)) {
// Inner type must be declared in this compilation unit
if (this.context.getCompilationUnit().findDeclaringNode(inner) == null) {
continue;
}
}
ClassMapping<?, ?> mapping = this.mappings.computeClassMapping(inner.getBinaryName()).orElse(null);
if (isPackagePrivate(modifiers)) {
// Must come from the same package
String packageName = mapping != null ? mapping.getDeobfuscatedPackage() : inner.getPackage().getName();
if (!packageName.replace('/', '.').equals(this.context.getPackageName().replace('/', '.'))) {
continue;
}
}
String simpleName;
String qualifiedName;
if (mapping != null) {
simpleName = mapping.getSimpleDeobfuscatedName();
qualifiedName = mapping.getFullDeobfuscatedName().replace('/', '.').replace('$', '.');
} else {
simpleName = inner.getName();
qualifiedName = inner.getBinaryName().replace('$', '.');
}
if (!context.conflicts.contains(simpleName)) {
String current = context.implicit.putIfAbsent(simpleName, qualifiedName);
if (current != null && !current.equals(qualifiedName)) {
context.implicit.remove(simpleName);
context.conflicts.add(simpleName);
}
}
}
// Inherited names
collectImportContext(context, binding.getSuperclass());
for (ITypeBinding parent : binding.getInterfaces()) {
collectImportContext(context, parent);
}
}
@Override
public boolean visit(AnnotationTypeDeclaration node) {
pushImportContext(node.resolveBinding());
return true;
}
@Override
public boolean visit(AnonymousClassDeclaration node) {
pushImportContext(node.resolveBinding());
return true;
}
@Override
public boolean visit(EnumDeclaration node) {
pushImportContext(node.resolveBinding());
return true;
}
@Override
public boolean visit(TypeDeclaration node) {
pushImportContext(node.resolveBinding());
return true;
}
@Override
public void endVisit(AnnotationTypeDeclaration node) {
this.importStack.pop();
}
@Override
public void endVisit(AnonymousClassDeclaration node) {
this.importStack.pop();
}
@Override
public void endVisit(EnumDeclaration node) {
this.importStack.pop();
}
@Override
public void endVisit(TypeDeclaration node) {
this.importStack.pop();
}
private void transferAnnotations(final AnnotatableType oldNode, final AnnotatableType newNode) {
// we don't support type annotations, ignore
if (newNode.getAST().apiLevel() < AST.JLS8) {
return;
}
if (oldNode.annotations().isEmpty()) {
// none to transfer
return;
}
// transfer and visit
final ListRewrite rewrite = this.context.createASTRewrite().getListRewrite(newNode, newNode.getAnnotationsProperty());
for (Object annotation : oldNode.annotations()) {
final ASTNode annotationNode = (ASTNode) annotation;
annotationNode.accept(this);
rewrite.insertLast(annotationNode, null);
}
}
private static class ImportContext extends ImportRewrite.ImportRewriteContext {
private final ImportRewrite.ImportRewriteContext defaultContext;
final Map<String, String> implicit;
final Set<String> conflicts;
ImportContext(ImportRewrite.ImportRewriteContext defaultContext, ImportContext parent) {
this.defaultContext = defaultContext;
if (parent != null) {
this.implicit = new HashMap<>(parent.implicit);
this.conflicts = new HashSet<>(parent.conflicts);
} else {
this.implicit = new HashMap<>();
this.conflicts = new HashSet<>();
}
}
@Override
public int findInContext(String qualifier, String name, int kind) {
int result = this.defaultContext.findInContext(qualifier, name, kind);
if (result != RES_NAME_UNKNOWN) {
return result;
}
if (kind == KIND_TYPE) {
String current = implicit.get(name);
if (current != null) {
return current.equals(qualifier + '.' + name) ? RES_NAME_FOUND : RES_NAME_CONFLICT;
}
if (conflicts.contains(name)) {
return RES_NAME_CONFLICT; // TODO
}
}
return RES_NAME_UNKNOWN;
}
}
}
@@ -0,0 +1,496 @@
/*
* Copyright (c) 2018 Cadix Development (https://www.cadixdev.org)
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which accompanies this distribution,
* and is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package modified.mercury;
import static paper.libs.org.cadixdev.mercury.util.BombeBindings.convertSignature;
import paper.libs.org.cadixdev.bombe.analysis.InheritanceProvider;
import paper.libs.org.cadixdev.bombe.type.signature.FieldSignature;
import paper.libs.org.cadixdev.bombe.type.signature.MethodSignature;
import paper.libs.org.cadixdev.lorenz.MappingSet;
import paper.libs.org.cadixdev.lorenz.model.ClassMapping;
import paper.libs.org.cadixdev.lorenz.model.FieldMapping;
import paper.libs.org.cadixdev.lorenz.model.InnerClassMapping;
import paper.libs.org.cadixdev.lorenz.model.MemberMapping;
import paper.libs.org.cadixdev.lorenz.model.MethodMapping;
import paper.libs.org.cadixdev.lorenz.model.MethodParameterMapping;
import paper.libs.org.cadixdev.mercury.RewriteContext;
import paper.libs.org.cadixdev.mercury.analysis.MercuryInheritanceProvider;
import paper.libs.org.cadixdev.mercury.util.GracefulCheck;
import paper.libs.org.eclipse.jdt.core.dom.ASTNode;
import paper.libs.org.eclipse.jdt.core.dom.ASTVisitor;
import paper.libs.org.eclipse.jdt.core.dom.Block;
import paper.libs.org.eclipse.jdt.core.dom.IBinding;
import paper.libs.org.eclipse.jdt.core.dom.IMethodBinding;
import paper.libs.org.eclipse.jdt.core.dom.ITypeBinding;
import paper.libs.org.eclipse.jdt.core.dom.IVariableBinding;
import paper.libs.org.eclipse.jdt.core.dom.LambdaExpression;
import paper.libs.org.eclipse.jdt.core.dom.MethodDeclaration;
import paper.libs.org.eclipse.jdt.core.dom.SimpleName;
import paper.libs.org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import paper.libs.org.eclipse.jdt.core.dom.VariableDeclaration;
import paper.libs.org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
/**
* Remaps only methods, fields, and parameters.
*/
public class SimpleRemapperVisitor extends ASTVisitor {
private static final String LVT_NAMES_PROPERTY = "org.cadixdev.mercury.lvtNames";
private static final String LOCAL_VARIABLE_NAME_PROPERTY = "org.cadixdev.mercury.localVariableName";
private static final String NEW_PARAM_NAMES_PROPERTY = "org.cadixdev.mercury.newParamNames";
final RewriteContext context;
final MappingSet mappings;
private final InheritanceProvider inheritanceProvider;
public SimpleRemapperVisitor(RewriteContext context, MappingSet mappings, boolean javadoc) {
super(javadoc);
this.context = context;
this.mappings = mappings;
this.inheritanceProvider = MercuryInheritanceProvider.get(context.getMercury());
}
final void updateIdentifier(SimpleName node, String newName) {
if (!node.getIdentifier().equals(newName) && !node.isVar()) {
this.context.createASTRewrite().set(node, SimpleName.IDENTIFIER_PROPERTY, newName, null);
}
}
private void remapMethod(SimpleName node, IMethodBinding binding) {
ITypeBinding declaringClass = binding.getDeclaringClass();
if (GracefulCheck.checkGracefully(this.context, declaringClass)) {
return;
}
final ClassMapping<?, ?> classMapping = this.mappings.getOrCreateClassMapping(declaringClass.getBinaryName());
if (binding.isConstructor()) {
updateIdentifier(node, classMapping.getSimpleDeobfuscatedName());
} else {
final MethodMapping mapping = findMethodMapping(declaringClass, binding);
if (mapping == null) {
return;
}
updateIdentifier(node, mapping.getDeobfuscatedName());
}
}
private void remapField(SimpleName node, IVariableBinding binding) {
if (!binding.isField()) {
if (binding.isParameter()) {
remapParameter(node, binding);
} else {
checkLocalVariable(node, binding);
}
return;
}
ITypeBinding declaringClass = binding.getDeclaringClass();
if (declaringClass == null) {
return;
}
ClassMapping<?, ?> classMapping = this.mappings.getClassMapping(declaringClass.getBinaryName()).orElse(null);
if (classMapping == null) {
return;
}
FieldSignature bindingSignature = convertSignature(binding);
FieldMapping mapping = findMemberMapping(bindingSignature, classMapping, ClassMapping::computeFieldMapping);
if (mapping == null) {
return;
}
updateIdentifier(node, mapping.getDeobfuscatedName());
}
private MethodMapping findMethodMapping(ITypeBinding declaringClass, IMethodBinding declaringMethod) {
final ClassMapping<?, ?> classMapping = this.mappings.getClassMapping(declaringClass.getBinaryName()).orElse(null);
if (classMapping == null) {
return null;
}
final MethodSignature methodSig = convertSignature(declaringMethod);
MethodMapping methodMapping = findMemberMapping(methodSig, classMapping, ClassMapping::getMethodMapping);
if (methodMapping == null) {
classMapping.complete(this.inheritanceProvider, declaringClass);
methodMapping = classMapping.getMethodMapping(methodSig).orElse(null);
}
return methodMapping;
}
private <T extends MemberMapping<?, ?>, M> T findMemberMapping(
M matcher,
ClassMapping<?, ?> classMapping,
BiFunction<ClassMapping<?, ?>, M, Optional<? extends T>> getMapping
) {
T mapping = getMapping.apply(classMapping, matcher).orElse(null);
if (mapping != null) {
return mapping;
}
if (!this.context.getMercury().isFlexibleAnonymousClassMemberLookups()) {
return null;
}
return findMemberMappingAnonClass(matcher, classMapping, getMapping);
}
private <T extends MemberMapping<?, ?>, M> T findMemberMappingAnonClass(
M matcher,
ClassMapping<?, ?> classMapping,
BiFunction<ClassMapping<?, ?>, M, Optional<? extends T>> getMapping
) {
// If neither name is different then this method won't do anything
if (Objects.equals(classMapping.getObfuscatedName(), classMapping.getDeobfuscatedName())) {
return null;
}
// Anonymous classes must be inner classes
if (!(classMapping instanceof InnerClassMapping)) {
return null;
}
// Verify this is inner class is anonymous
if (!classMapping.getObfuscatedName().chars().allMatch(Character::isDigit)) {
return null;
}
ClassMapping<?, ?> parentMapping = ((InnerClassMapping) classMapping).getParent();
if (parentMapping == null) {
return null;
}
// Find a sibling anonymous class whose obfuscated name is our deobfuscated name
ClassMapping<?, ?> otherClassMapping = parentMapping
.getInnerClassMapping(classMapping.getDeobfuscatedName()).orElse(null);
if (otherClassMapping != null) {
T mapping = getMapping.apply(otherClassMapping, matcher).orElse(null);
if (mapping != null) {
return mapping;
}
}
// Find a sibling anonymous class whose deobfuscated name is our obfuscated name
// We have to do something a little less direct for this case
for (InnerClassMapping innerClassMapping : parentMapping.getInnerClassMappings()) {
if (Objects.equals(classMapping.getObfuscatedName(), innerClassMapping.getDeobfuscatedName())) {
otherClassMapping = innerClassMapping;
break;
}
}
if (otherClassMapping == null) {
return null;
}
return getMapping.apply(otherClassMapping, matcher).orElse(null);
}
private void remapParameter(SimpleName node, IVariableBinding binding) {
IMethodBinding declaringMethod = binding.getDeclaringMethod();
if (declaringMethod == null) {
return;
}
int index = -1;
ASTNode n = context.getCompilationUnit().findDeclaringNode(declaringMethod);
if (n instanceof MethodDeclaration) {
MethodDeclaration methodDeclaration = (MethodDeclaration) n;
@SuppressWarnings("unchecked")
List<SingleVariableDeclaration> parameters = methodDeclaration.parameters();
for (int i = 0; i < parameters.size(); i++) {
if (binding.equals(parameters.get(i).resolveBinding())) {
index = i;
}
}
}
if (index == -1) {
return;
}
final ITypeBinding declaringClass = declaringMethod.getDeclaringClass();
if (declaringClass == null) {
return;
}
final MethodMapping methodMapping = findMethodMapping(declaringClass, declaringMethod);
if (methodMapping == null) {
return;
}
methodMapping.getParameterMapping(index).ifPresent(paramMapping -> updateIdentifier(node, paramMapping.getDeobfuscatedName()));
}
/**
* Check if a local variable needs to be renamed because it conflicts with a new parameter name. This will also
* attempt to check cases where local variables are defined in lambda expressions.
*
* @param node The local variable node to check
* @param binding The variable binding corresponding to the local variable name
*/
private void checkLocalVariable(SimpleName node, IVariableBinding binding) {
final ASTNode bindingNode = this.context.getCompilationUnit().findDeclaringNode(binding);
final String localVariableName = bindingNode != null ? (String) bindingNode.getProperty(LOCAL_VARIABLE_NAME_PROPERTY) : null; // modified
if (localVariableName != null) {
updateIdentifier(node, localVariableName);
return;
}
IMethodBinding declaringMethod = binding.getDeclaringMethod();
if (declaringMethod == null) {
return;
}
if (declaringMethod.getDeclaringMember() != null) {
// lambda method
final LambdaExpression lambdaExpr = getLambdaMethodDeclaration(declaringMethod);
if (lambdaExpr == null) {
return;
}
// Climb out of declaring stack until we find a method which isn't a lambda
IMethodBinding outerMethod = declaringMethod;
while (outerMethod.getDeclaringMember() instanceof IMethodBinding) {
outerMethod = (IMethodBinding) outerMethod.getDeclaringMember();
}
if (outerMethod == declaringMethod) {
// lookup failed, nothing we can do
return;
}
final ASTNode n = this.context.getCompilationUnit().findDeclaringNode(outerMethod);
if (!(n instanceof MethodDeclaration)) {
return;
}
final MethodDeclaration outerDeclaration = (MethodDeclaration) n;
ASTNode body = lambdaExpr.getBody();
// might be an expression
if (!(body instanceof Block)) {
body = null;
}
this.checkLocalVariableWithMappings(node, bindingNode, outerMethod, outerDeclaration, declaringMethod, (Block) body);
} else {
final ASTNode n = context.getCompilationUnit().findDeclaringNode(declaringMethod);
if (!(n instanceof MethodDeclaration)) {
return;
}
final MethodDeclaration methodDeclaration = (MethodDeclaration) n;
this.checkLocalVariableWithMappings(node, bindingNode, declaringMethod, methodDeclaration, declaringMethod, methodDeclaration.getBody());
}
}
/**
* Using the given mappings and bindings, check if there are mappings for the method and if any of them conflict
* with the given local variable name.
*
* @param node The local variable name to check
* @param bindingNode The binding of the local variable declaration
* @param binding The binding of the mapped method to check
* @param declaration The declaration node of the mapped method to check
* @param blockDeclaringMethod The method binding of the method which defines the {@code block}
* @param body The method body to check for local variables
*/
private void checkLocalVariableWithMappings(
SimpleName node,
ASTNode bindingNode,
IMethodBinding binding,
MethodDeclaration declaration,
IMethodBinding blockDeclaringMethod,
Block body
) {
final ITypeBinding declaringClass = binding.getDeclaringClass();
this.mappings.getClassMapping(declaringClass.getBinaryName())
.flatMap(classMapping -> {
classMapping.complete(this.inheritanceProvider, declaringClass);
return classMapping.getMethodMapping(convertSignature(binding));
})
.ifPresent(methodMapping -> {
if (!methodMapping.getParameterMappings().isEmpty()) {
final Set<String> newParamNames = newParamNames(declaration, methodMapping);
checkLocalVariableForConflicts(node, bindingNode, blockDeclaringMethod, body, newParamNames);
}
});
}
/**
* Check the method's body defined by {@code methodDeclaration} to collect all local variable names in order to
* find a suitable replacement name for {@code node} if it clashes with a name in {@code newParamNames}.
*
* @param node The local variable node to check
* @param bindingNode The binding of the local variable declaration
* @param blockDeclaringMethod The method binding of the method which defines the {@code block}
* @param block The method body implementation to collect local variable names from
* @param newParamNames The set of parameter names after mapping
*/
private void checkLocalVariableForConflicts(
SimpleName node,
ASTNode bindingNode,
IMethodBinding blockDeclaringMethod,
Block block,
Set<String> newParamNames
) {
final String name = node.getIdentifier();
if (!newParamNames.contains(name)) {
return;
}
// the new param name will screw up this local variable
final Set<String> localVariableNames = collectLocalVariableNames(blockDeclaringMethod, block);
int counter = 1;
String newName = name + counter;
while (localVariableNames.contains(newName) || newParamNames.contains(newName)) {
counter++;
newName = name + counter;
}
localVariableNames.add(newName);
bindingNode.setProperty(LOCAL_VARIABLE_NAME_PROPERTY, newName);
updateIdentifier(node, newName);
}
/**
* Find the declaration of the actual method block for the lambda method
*
* @param declaringMethod The method binding for the lambda method to check
* @return The {@link MethodDeclaration} corresponding to the code block of the lambda implementation
*/
private LambdaExpression getLambdaMethodDeclaration(IMethodBinding declaringMethod) {
final ASTNode node = this.context.getCompilationUnit().findDeclaringNode(declaringMethod.getKey());
if (node instanceof LambdaExpression) {
return (LambdaExpression) node;
}
return null;
}
/**
* Read the method body of {@code methodDeclaration} and return the set of local variable names defined inside of
* it. The set is cached on the {@code methodDeclaration} so it is only computed once.
*
* @param blockDeclaringMethod The method binding of the method which defines the {@code block}
* @param block The method body implementation to check.
* @return The set of local variable names defined in the method body.
*/
private Set<String> collectLocalVariableNames(IMethodBinding blockDeclaringMethod, Block block) {
if (block == null) {
return Collections.emptySet();
}
Set<String> result = checkProperty(LVT_NAMES_PROPERTY, block);
if (result != null) {
return result;
}
result = new HashSet<>();
block.setProperty(LVT_NAMES_PROPERTY, result);
final IVariableBinding[] synthLocals = blockDeclaringMethod.getSyntheticOuterLocals();
for (final IVariableBinding synthLocal : synthLocals) {
final String name = synthLocal.getName();
if (name.startsWith("val$")) {
result.add(name.substring(4));
}
}
@SuppressWarnings("unchecked")
final List<ASTNode> statements = (List<ASTNode>) block.statements();
for (final ASTNode statement : statements) {
if (!(statement instanceof VariableDeclaration)) {
continue;
}
final VariableDeclaration declaration = (VariableDeclaration) statement;
result.add(declaration.getName().getIdentifier());
}
return result;
}
/**
* Check the parameter names defined by the {@code methodDeclaration} and apply any mappings based on the given
* {@code mapping}. Return the list of params post-remap.
*
* @param methodDeclaration The method declaration to check the parameter names on.
* @param mapping The mapping to use to determine the new parameter names
* @return The set of parameter names after remapping them with {@code mapping}.
*/
private Set<String> newParamNames(MethodDeclaration methodDeclaration, MethodMapping mapping) {
Set<String> result = checkProperty(NEW_PARAM_NAMES_PROPERTY, methodDeclaration);
if (result != null) {
return result;
}
result = new HashSet<>();
methodDeclaration.setProperty(NEW_PARAM_NAMES_PROPERTY, result);
@SuppressWarnings("unchecked")
List<SingleVariableDeclaration> parameters = methodDeclaration.parameters();
for (int i = 0; i < parameters.size(); i++) {
final Optional<MethodParameterMapping> paramMapping = mapping.getParameterMapping(i);
if (paramMapping.isPresent()) {
result.add(paramMapping.get().getDeobfuscatedName());
} else {
result.add(parameters.get(i).getName().getIdentifier());
}
}
return result;
}
/**
* Check if the given node contains a {@code Set<String>} property named {@code propName} and return it if so.
* Returns {@code null} if not.
*
* @param propName The name of the property
* @param node The node to check the property on
* @return The set stored on the node or {@code null} if empty
*/
private static Set<String> checkProperty(String propName, ASTNode node) {
if (node == null) {
return null;
}
final Object value = node.getProperty(propName);
if (value instanceof Set) {
@SuppressWarnings("unchecked") final Set<String> result = (Set<String>) value;
return result;
}
return null;
}
protected void visit(SimpleName node, IBinding binding) {
switch (binding.getKind()) {
case IBinding.METHOD:
remapMethod(node, ((IMethodBinding) binding).getMethodDeclaration());
break;
case IBinding.VARIABLE:
remapField(node, ((IVariableBinding) binding).getVariableDeclaration());
break;
}
}
@Override
public final boolean visit(SimpleName node) {
IBinding binding = node.resolveBinding();
if (binding != null) {
visit(node, binding);
}
return false;
}
}
@@ -0,0 +1,42 @@
import modified.mercury.RemapperVisitor
import modified.mercury.SimpleRemapperVisitor
import paper.libs.org.cadixdev.lorenz.MappingSet
import paper.libs.org.cadixdev.mercury.RewriteContext
import paper.libs.org.cadixdev.mercury.SourceRewriter
import java.util.Objects
class MercuryRemapper private constructor(mappings: MappingSet, simple: Boolean, javadoc: Boolean) : SourceRewriter {
private val mappings: MappingSet
private val simple: Boolean
private val javadoc: Boolean
init {
this.mappings = Objects.requireNonNull(mappings, "mappings")
this.simple = simple
this.javadoc = javadoc
}
override fun getFlags(): Int = SourceRewriter.FLAG_RESOLVE_BINDINGS
override fun rewrite(context: RewriteContext) {
context.compilationUnit.accept(if (simple) SimpleRemapperVisitor(context, mappings, this.javadoc) else RemapperVisitor(context, mappings, this.javadoc))
}
companion object {
fun create(mappings: MappingSet): SourceRewriter {
return MercuryRemapper(mappings, false, true)
}
fun create(mappings: MappingSet, javadoc: Boolean): SourceRewriter {
return MercuryRemapper(mappings, false, javadoc)
}
fun createSimple(mappings: MappingSet): SourceRewriter {
return MercuryRemapper(mappings, true, true)
}
fun createSimple(mappings: MappingSet, javadoc: Boolean): SourceRewriter {
return MercuryRemapper(mappings, true, javadoc)
}
}
}
@@ -0,0 +1,98 @@
import io.papermc.paperweight.tasks.JavaLauncherTask
import io.papermc.paperweight.tasks.RemapSources
import io.papermc.paperweight.util.MappingFormats
import io.papermc.paperweight.util.constants.DEOBF_NAMESPACE
import io.papermc.paperweight.util.constants.SPIGOT_NAMESPACE
import io.papermc.paperweight.util.deleteRecursive
import io.papermc.paperweight.util.path
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.CompileClasspath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.submit
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkerExecutor
import paper.libs.org.cadixdev.mercury.Mercury
import paper.libs.org.eclipse.jdt.core.JavaCore
import java.nio.file.Files
import javax.inject.Inject
abstract class RemapPluginSources : JavaLauncherTask() {
@get:CompileClasspath
abstract val remapClasspath: ConfigurableFileCollection
@get:InputDirectory
abstract val inputSources: DirectoryProperty
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
@get:InputFile
abstract val mappingsFile: RegularFileProperty
@get:Input
abstract val addExplicitThis: Property<Boolean>
@get:Inject
abstract val workerExecutor: WorkerExecutor
override fun init() {
super.init()
addExplicitThis.convention(false)
}
@TaskAction
fun run() {
val queue = workerExecutor.processIsolation {
forkOptions.jvmArgs("-Xmx2G")
forkOptions.executable(launcher.get().executablePath.path.toAbsolutePath().toString())
}
queue.submit(RemapPluginSourcesAction::class) {
remapClasspath.setFrom(this@RemapPluginSources.remapClasspath)
inputSources.set(this@RemapPluginSources.inputSources)
outputDir.set(this@RemapPluginSources.outputDir)
mappingsFile.set(this@RemapPluginSources.mappingsFile)
addExplicitThis.set(this@RemapPluginSources.addExplicitThis)
}
}
abstract class RemapPluginSourcesAction : WorkAction<RemapPluginSourcesAction.Params> {
interface Params : WorkParameters {
val remapClasspath: ConfigurableFileCollection
val inputSources: DirectoryProperty
val outputDir: DirectoryProperty
val mappingsFile: RegularFileProperty
val addExplicitThis: Property<Boolean>
}
override fun execute() {
val mappingSet = MappingFormats.TINY.read(
parameters.mappingsFile.path,
SPIGOT_NAMESPACE,
DEOBF_NAMESPACE
)
val merc = Mercury()
merc.isGracefulClasspathChecks = true
merc.sourceCompatibility = JavaCore.VERSION_17
merc.classPath.addAll(parameters.remapClasspath.map { it.toPath() })
if (parameters.addExplicitThis.get()) {
merc.processors.add(RemapSources.ExplicitThisAdder)
}
merc.processors.add(MercuryRemapper.create(mappingSet, false))
parameters.outputDir.path.deleteRecursive()
Files.createDirectories(parameters.outputDir.path)
merc.rewrite(parameters.inputSources.path, parameters.outputDir.path)
}
}
}
@@ -0,0 +1,43 @@
import io.papermc.paperweight.util.MappingFormats
import io.papermc.paperweight.util.constants.DEOBF_NAMESPACE
import io.papermc.paperweight.util.constants.SPIGOT_NAMESPACE
import io.papermc.paperweight.util.path
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.nio.file.Files
abstract class ReverseMappings : io.papermc.paperweight.tasks.BaseTask() {
@get:InputFile
abstract val inputMappings: RegularFileProperty
@get:OutputFile
abstract val outputMappings: RegularFileProperty
@get:Input
abstract val fromNs: Property<String>
@get:Input
abstract val toNs: Property<String>
@TaskAction
fun run() {
val mappingSet = MappingFormats.TINY.read(
inputMappings.path,
fromNs.get(),
toNs.get()
).reverse()
Files.deleteIfExists(outputMappings.path)
Files.createDirectories(outputMappings.path.parent)
MappingFormats.TINY.write(
mappingSet,
outputMappings.path,
toNs.get(),
fromNs.get()
)
}
}
@@ -0,0 +1,46 @@
import io.papermc.paperweight.tasks.RemapJar
import io.papermc.paperweight.util.constants.DEOBF_NAMESPACE
import io.papermc.paperweight.util.constants.SPIGOT_NAMESPACE
import io.papermc.paperweight.util.registering
plugins {
java
id("io.papermc.paperweight.userdev")
}
tasks {
val reverseMappings by registering<ReverseMappings> {
inputMappings.set(layout.projectDirectory.file(".gradle/caches/paperweight/setupCache/extractDevBundle.dir/data/mojang+yarn-spigot-reobf.tiny"))
outputMappings.set(layout.buildDirectory.file("reversed-reobf-mappings.tiny"))
fromNs.set(DEOBF_NAMESPACE)
toNs.set(SPIGOT_NAMESPACE)
}
val reobfPaperJar by tasks.registering<RemapJar> {
// won't be runnable as we don't fixjarforreobf
// pretty hacky but should work
inputJar.set(layout.file(configurations.mojangMappedServerRuntime.flatMap {
it.elements.map { elements ->
elements.filter { element ->
val p = element.asFile.absolutePath
p.contains("ivyRepository") && p.contains("paper-server-userdev")
}.single().asFile
}
}))
outputJar.set(layout.buildDirectory.file("reobfPaper.jar"))
fromNamespace.set(DEOBF_NAMESPACE)
toNamespace.set(SPIGOT_NAMESPACE)
mappingsFile.set(reverseMappings.flatMap { it.outputMappings })
remapper.from(configurations.remapper)
}
@Suppress("unused_variable")
val remapPluginSources by registering<RemapPluginSources> {
remapClasspath.from(reobfPaperJar.flatMap { it.outputJar })
remapClasspath.from(configurations.compileClasspath)
inputSources.set(layout.projectDirectory.dir("src/main/java"))
outputDir.set(layout.projectDirectory.dir("src/main/mojangMappedJava"))
mappingsFile.set(reverseMappings.flatMap { it.outputMappings })
}
}