mirror of
https://github.com/zaaarf/lillero-processor.git
synced 2024-11-22 16:04:50 +01:00
feat: initial implementation of new system
- multiple injectors/target - removed strict limitation of one patch per class - made finders far smarter - added boolean for obfuscation environment (currently never changed from default)
This commit is contained in:
parent
51d9375e0b
commit
f6539d4a07
12 changed files with 709 additions and 131 deletions
|
@ -15,7 +15,7 @@ repositories {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.squareup:javapoet:1.13.0'
|
implementation 'com.squareup:javapoet:1.13.0'
|
||||||
implementation 'ftbsc:lll:0.3.0'
|
implementation 'ftbsc:lll:0.3.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package ftbsc.lll.exceptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when the processor finds multiple methods matching the
|
||||||
|
* given criteria.
|
||||||
|
*/
|
||||||
|
public class AmbiguousDefinitionException extends RuntimeException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new ambiguous definition exception with the specified detail message.
|
||||||
|
* @param message the detail message
|
||||||
|
*/
|
||||||
|
public AmbiguousDefinitionException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new ambiguous definition exception with the specified detail message and cause.
|
||||||
|
* @param message the detail message
|
||||||
|
* @param cause the cause, may be null (indicating nonexistent or unknown cause)
|
||||||
|
*/
|
||||||
|
public AmbiguousDefinitionException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package ftbsc.lll.exceptions;
|
||||||
|
|
||||||
|
import ftbsc.lll.processor.tools.SrgMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown upon failure to find the requested mapping within a loaded {@link SrgMapper}.
|
||||||
|
*/
|
||||||
|
public class MappingNotFoundException extends RuntimeException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new mapping not found exception for the specified mapping.
|
||||||
|
* @param mapping the detail message
|
||||||
|
*/
|
||||||
|
public MappingNotFoundException(String mapping) {
|
||||||
|
super("Could not find mapping for " + mapping + "!");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package ftbsc.lll.exceptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown upon failure to find an existing method from a stub.
|
||||||
|
*/
|
||||||
|
public class TargetNotFoundException extends RuntimeException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new target not found exception for the specified method stub.
|
||||||
|
* @param stub the stub's name (and descriptor possibly)
|
||||||
|
*/
|
||||||
|
public TargetNotFoundException(String stub) {
|
||||||
|
super("Could not find member corresponding to stub: " + stub);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,33 +2,30 @@ package ftbsc.lll.processor;
|
||||||
|
|
||||||
import com.squareup.javapoet.*;
|
import com.squareup.javapoet.*;
|
||||||
import ftbsc.lll.IInjector;
|
import ftbsc.lll.IInjector;
|
||||||
|
import ftbsc.lll.exceptions.AmbiguousDefinitionException;
|
||||||
|
import ftbsc.lll.exceptions.MappingNotFoundException;
|
||||||
|
import ftbsc.lll.exceptions.TargetNotFoundException;
|
||||||
import ftbsc.lll.processor.annotations.*;
|
import ftbsc.lll.processor.annotations.*;
|
||||||
|
import ftbsc.lll.processor.tools.SrgMapper;
|
||||||
import ftbsc.lll.proxies.FieldProxy;
|
import ftbsc.lll.proxies.FieldProxy;
|
||||||
import ftbsc.lll.proxies.MethodProxy;
|
import ftbsc.lll.proxies.MethodProxy;
|
||||||
import ftbsc.lll.tools.SrgMapper;
|
|
||||||
|
|
||||||
import javax.annotation.processing.*;
|
import javax.annotation.processing.*;
|
||||||
import javax.lang.model.SourceVersion;
|
import javax.lang.model.SourceVersion;
|
||||||
import javax.lang.model.element.*;
|
import javax.lang.model.element.*;
|
||||||
import javax.lang.model.type.ExecutableType;
|
import javax.lang.model.type.ExecutableType;
|
||||||
import javax.lang.model.type.MirroredTypeException;
|
|
||||||
import javax.lang.model.type.TypeMirror;
|
import javax.lang.model.type.TypeMirror;
|
||||||
import javax.tools.Diagnostic;
|
import javax.tools.Diagnostic;
|
||||||
import javax.tools.FileObject;
|
import javax.tools.FileObject;
|
||||||
import javax.tools.JavaFileObject;
|
import javax.tools.JavaFileObject;
|
||||||
import javax.tools.StandardLocation;
|
import javax.tools.StandardLocation;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static ftbsc.lll.processor.ASTUtils.*;
|
import static ftbsc.lll.processor.tools.ASTUtils.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The actual annotation processor behind the magic.
|
* The actual annotation processor behind the magic.
|
||||||
|
@ -43,6 +40,11 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
*/
|
*/
|
||||||
private final Set<String> generatedInjectors = new HashSet<>();
|
private final Set<String> generatedInjectors = new HashSet<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A static boolean that should be set to true when ran in a non-obfuscated environment.
|
||||||
|
*/
|
||||||
|
public static boolean obfuscatedEnvironment = false; //todo: set this
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Where the actual processing happens.
|
* Where the actual processing happens.
|
||||||
* It filters through whatever annotated class it's fed, and checks whether it contains
|
* It filters through whatever annotated class it's fed, and checks whether it contains
|
||||||
|
@ -64,7 +66,7 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
.filter(this::isValidInjector)
|
.filter(this::isValidInjector)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
if(!validInjectors.isEmpty()) {
|
if(!validInjectors.isEmpty()) {
|
||||||
validInjectors.forEach(this::generateInjector);
|
validInjectors.forEach(this::generateInjectors);
|
||||||
if (!this.generatedInjectors.isEmpty()) {
|
if (!this.generatedInjectors.isEmpty()) {
|
||||||
generateServiceProvider();
|
generateServiceProvider();
|
||||||
return true;
|
return true;
|
||||||
|
@ -102,11 +104,190 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the Injector corresponding to the given class.
|
* Finds the class name and maps it to the correct format.
|
||||||
|
* @param patchAnn the {@link Patch} annotation containing target class info
|
||||||
|
* @param methodAnn the {@link FindMethod} annotation to fall back on, may be null
|
||||||
|
* @param mapper the {@link SrgMapper} to use
|
||||||
|
* @implNote De facto, there is never any difference between the SRG and MCP name of a class.
|
||||||
|
* In theory, differences only arise between SRG/MCP names and Notch (fully obfuscated)
|
||||||
|
* names. However, this method still performs a conversion - just in case there is an
|
||||||
|
* odd one out.
|
||||||
|
* @return the fully qualified class name
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
private static String findClassName(Patch patchAnn, FindMethod methodAnn, SrgMapper mapper) {
|
||||||
|
String fullyQualifiedName =
|
||||||
|
methodAnn == null || methodAnn.parent() == Object.class
|
||||||
|
? getClassFullyQualifiedName(patchAnn.value())
|
||||||
|
: getClassFullyQualifiedName(methodAnn.parent());
|
||||||
|
return mapper.mapClass(fullyQualifiedName, obfuscatedEnvironment).replace('/', '.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the class name and maps it to the correct format.
|
||||||
|
* @param patchAnn the {@link Patch} annotation containing target class info
|
||||||
|
* @param mapper the {@link SrgMapper} to use
|
||||||
|
* @return the internal class name
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
private static String findClassName(Patch patchAnn, SrgMapper mapper) {
|
||||||
|
return findClassName(patchAnn, null, mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the method name and maps it to the correct format.
|
||||||
|
* @param parentFQN the already mapped FQN of the parent class
|
||||||
|
* @param methodAnn the {@link FindMethod} annotation to fall back on, may be null
|
||||||
|
* @param stub the {@link ExecutableElement} for the stub
|
||||||
|
* @param mapper the {@link SrgMapper} to use
|
||||||
|
* @return the internal class name
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
private static String findMethodName(String parentFQN, FindMethod methodAnn, ExecutableElement stub, SrgMapper mapper) {
|
||||||
|
String methodName = methodAnn == null ? stub.getSimpleName().toString() : methodAnn.name();
|
||||||
|
try {
|
||||||
|
methodName = mapper.mapMember(
|
||||||
|
parentFQN,
|
||||||
|
methodName,
|
||||||
|
obfuscatedEnvironment
|
||||||
|
);
|
||||||
|
} catch(MappingNotFoundException e) {
|
||||||
|
//not found: try again with the name of the annotated method
|
||||||
|
if(methodAnn == null) {
|
||||||
|
methodName = mapper.mapMember(
|
||||||
|
parentFQN,
|
||||||
|
stub.getSimpleName().toString(),
|
||||||
|
obfuscatedEnvironment
|
||||||
|
);
|
||||||
|
} else throw e;
|
||||||
|
}
|
||||||
|
return methodName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the method name and maps it to the correct format.
|
||||||
|
* @param patchAnn the {@link Patch} annotation containing target class info
|
||||||
|
* @param methodAnn the {@link FindMethod} annotation to fall back on, may be null
|
||||||
|
* @param stub the {@link ExecutableElement} for the stub
|
||||||
|
* @param mapper the {@link SrgMapper} to use
|
||||||
|
* @return the internal class name
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
private static String findMethodName(Patch patchAnn, FindMethod methodAnn, ExecutableElement stub, SrgMapper mapper) {
|
||||||
|
return findMethodName(findClassName(patchAnn, methodAnn, mapper), methodAnn, stub, mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a method given name, container and descriptor.
|
||||||
|
* @param fullyQualifiedNameParent the fully qualified name of the parent class of the method
|
||||||
|
* @param name the name to search for
|
||||||
|
* @param descr the descriptor to search for
|
||||||
|
* @param strict whether the search should be strict (see {@link Target#strict()} for more info)
|
||||||
|
* @return the desired method, if it exists
|
||||||
|
* @throws AmbiguousDefinitionException if it finds more than one candidate
|
||||||
|
* @throws TargetNotFoundException if it finds no valid candidate
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
private ExecutableElement findMethod(String fullyQualifiedNameParent, String name, String descr, boolean strict) {
|
||||||
|
TypeElement parent = processingEnv.getElementUtils().getTypeElement(fullyQualifiedNameParent);
|
||||||
|
if(parent == null)
|
||||||
|
throw new AmbiguousDefinitionException("Could not find parent class " + fullyQualifiedNameParent + "!");
|
||||||
|
|
||||||
|
//try to find by name
|
||||||
|
List<ExecutableElement> candidates = parent.getEnclosedElements()
|
||||||
|
.stream()
|
||||||
|
.filter(e -> e instanceof ExecutableElement)
|
||||||
|
.map(e -> (ExecutableElement) e)
|
||||||
|
.filter(e -> e.getSimpleName().contentEquals(name))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if(candidates.size() == 0)
|
||||||
|
throw new TargetNotFoundException(name + " " + descr);
|
||||||
|
if(candidates.size() == 1 && !strict)
|
||||||
|
return candidates.get(0);
|
||||||
|
if(descr == null) {
|
||||||
|
throw new AmbiguousDefinitionException(
|
||||||
|
"Found " + candidates.size()
|
||||||
|
+ " methods named " + name
|
||||||
|
+ " in class " + fullyQualifiedNameParent + "!"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
candidates = candidates.stream()
|
||||||
|
.filter(strict
|
||||||
|
? c -> descr.equals(descriptorFromExecutableElement(c))
|
||||||
|
: c -> descr.split("\\)")[0].equalsIgnoreCase(descriptorFromExecutableElement(c).split("\\)")[0])
|
||||||
|
).collect(Collectors.toList());
|
||||||
|
if(candidates.size() == 0)
|
||||||
|
throw new TargetNotFoundException(name + " " + descr);
|
||||||
|
if(candidates.size() > 1)
|
||||||
|
throw new AmbiguousDefinitionException(
|
||||||
|
"Found " + candidates.size()
|
||||||
|
+ " methods named " + name
|
||||||
|
+ " in class " + fullyQualifiedNameParent + "!"
|
||||||
|
);
|
||||||
|
return candidates.get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the real method corresponding to a stub.
|
||||||
|
* @param stub the {@link ExecutableElement} for the stub
|
||||||
|
* @param mapper the {@link SrgMapper} to use
|
||||||
|
* @return the desired method, if it exists
|
||||||
|
* @throws AmbiguousDefinitionException if it finds more than one candidate
|
||||||
|
* @throws TargetNotFoundException if it finds no valid candidate
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
private ExecutableElement findRealMethod(ExecutableElement stub, SrgMapper mapper) {
|
||||||
|
Patch patchAnn = stub.getEnclosingElement().getAnnotation(Patch.class);
|
||||||
|
FindMethod findAnn = stub.getAnnotation(FindMethod.class); //this may be null, it means no fallback info
|
||||||
|
Target target = stub.getAnnotation(Target.class); //if this is null strict mode is always disabled
|
||||||
|
String parentFQN = findClassName(patchAnn, findAnn, mapper);
|
||||||
|
String methodName = findMethodName(patchAnn, findAnn, stub, mapper);
|
||||||
|
return findMethod(
|
||||||
|
parentFQN,
|
||||||
|
methodName,
|
||||||
|
descriptorFromExecutableElement(stub),
|
||||||
|
target != null && target.strict());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the real field corresponding to a stub.
|
||||||
|
* @param stub the {@link ExecutableElement} for the stub
|
||||||
|
* @param mapper the {@link SrgMapper} to use
|
||||||
|
* @return the desired method, if it exists
|
||||||
|
* @throws TargetNotFoundException if it finds no valid candidate
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
private VariableElement findField(ExecutableElement stub, SrgMapper mapper) {
|
||||||
|
Patch patchAnn = stub.getEnclosingElement().getAnnotation(Patch.class);
|
||||||
|
FindField fieldAnn = stub.getAnnotation(FindField.class);
|
||||||
|
String parentName = mapper.mapClass(getClassFullyQualifiedName(
|
||||||
|
fieldAnn.parent().equals(Object.class)
|
||||||
|
? patchAnn.value()
|
||||||
|
: fieldAnn.parent()
|
||||||
|
), obfuscatedEnvironment);
|
||||||
|
String name = fieldAnn.name().equals("")
|
||||||
|
? stub.getSimpleName().toString()
|
||||||
|
: fieldAnn.name();
|
||||||
|
TypeElement parent = processingEnv.getElementUtils().getTypeElement(parentName);
|
||||||
|
List<VariableElement> candidates =
|
||||||
|
parent.getEnclosedElements()
|
||||||
|
.stream()
|
||||||
|
.filter(f -> f instanceof VariableElement)
|
||||||
|
.filter(f -> f.getSimpleName().contentEquals(name))
|
||||||
|
.map(f -> (VariableElement) f)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if(candidates.size() == 0)
|
||||||
|
throw new TargetNotFoundException(stub.getSimpleName().toString());
|
||||||
|
else return candidates.get(0); //there can only ever be one
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the Injector(s) contained in the given class.
|
||||||
* Basically implements the {@link IInjector} interface for you.
|
* Basically implements the {@link IInjector} interface for you.
|
||||||
* @param cl the {@link TypeElement} for the given class
|
* @param cl the {@link TypeElement} for the given class
|
||||||
*/
|
*/
|
||||||
private void generateInjector(TypeElement cl) {
|
private void generateInjectors(TypeElement cl) {
|
||||||
SrgMapper mapper;
|
SrgMapper mapper;
|
||||||
try { //TODO: cant we get it from local?
|
try { //TODO: cant we get it from local?
|
||||||
URL url = new URL("https://data.fantabos.co/output.tsrg");
|
URL url = new URL("https://data.fantabos.co/output.tsrg");
|
||||||
|
@ -116,79 +297,149 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
is.close();
|
is.close();
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
throw new RuntimeException("Could not open the specified TSRG file!", e);
|
throw new RuntimeException("Could not open the specified TSRG file!", e);
|
||||||
}
|
} //todo attempt to proceed without mappings
|
||||||
|
|
||||||
Patch ann = cl.getAnnotation(Patch.class);
|
//find class information
|
||||||
String targetClassCanonicalName;
|
Patch patchAnn = cl.getAnnotation(Patch.class);
|
||||||
try {
|
String targetClassSrgName = findClassName(patchAnn, mapper);
|
||||||
targetClassCanonicalName = ann.value().getCanonicalName();
|
|
||||||
} catch(MirroredTypeException e) {
|
|
||||||
targetClassCanonicalName = e.getTypeMirror().toString();
|
|
||||||
} //pretty sure class names de facto never change but better safe than sorry
|
|
||||||
String targetClassSrgName = mapper.getMcpClass(targetClassCanonicalName.replace('.', '/'));
|
|
||||||
|
|
||||||
ExecutableElement targetMethod = findAnnotatedMethods(cl, Target.class).get(0); //there should only be one
|
|
||||||
String targetMethodDescriptor = descriptorFromMethodSpec(targetMethod);
|
|
||||||
String targetMethodSrgName = mapper.getSrgMember(
|
|
||||||
targetClassCanonicalName.replace('.', '/'),
|
|
||||||
targetMethod.getSimpleName() + " " + targetMethodDescriptor
|
|
||||||
);
|
|
||||||
|
|
||||||
ExecutableElement injectorMethod = findAnnotatedMethods(cl, Injector.class).get(0); //there should only be one
|
|
||||||
|
|
||||||
|
//find package information
|
||||||
Element packageElement = cl.getEnclosingElement();
|
Element packageElement = cl.getEnclosingElement();
|
||||||
while (packageElement.getKind() != ElementKind.PACKAGE)
|
while (packageElement.getKind() != ElementKind.PACKAGE)
|
||||||
packageElement = packageElement.getEnclosingElement();
|
packageElement = packageElement.getEnclosingElement();
|
||||||
|
|
||||||
String packageName = packageElement.toString();
|
String packageName = packageElement.toString();
|
||||||
String injectorSimpleClassName = cl.getSimpleName().toString() + "Injector";
|
|
||||||
String injectorClassName = packageName + "." + injectorSimpleClassName;
|
|
||||||
|
|
||||||
MethodSpec stubOverride = MethodSpec.overriding(targetMethod)
|
//find injector(s) and target(s)
|
||||||
.addStatement("throw new $T($S)", RuntimeException.class, "This is a stub and should not have been called")
|
List<ExecutableElement> injectors = findAnnotatedMethods(cl, MultipleInjectors.class);
|
||||||
.build();
|
|
||||||
|
|
||||||
MethodSpec inject = MethodSpec.methodBuilder("inject")
|
List<ExecutableElement> targets = findAnnotatedMethods(cl, Target.class);
|
||||||
.addModifiers(Modifier.PUBLIC)
|
|
||||||
.returns(void.class)
|
|
||||||
.addAnnotation(Override.class)
|
|
||||||
.addParameter(ParameterSpec.builder(
|
|
||||||
TypeName.get(processingEnv
|
|
||||||
.getElementUtils()
|
|
||||||
.getTypeElement("org.objectweb.asm.tree.ClassNode").asType()), "clazz").build())
|
|
||||||
.addParameter(ParameterSpec.builder(
|
|
||||||
TypeName.get(processingEnv
|
|
||||||
.getElementUtils()
|
|
||||||
.getTypeElement("org.objectweb.asm.tree.MethodNode").asType()), "main").build())
|
|
||||||
.addStatement("super." + injectorMethod.getSimpleName() + "(clazz, main)", TypeName.get(cl.asType()))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
TypeSpec injectorClass = TypeSpec.classBuilder(injectorSimpleClassName)
|
//declare it once for efficiency
|
||||||
.addModifiers(Modifier.PUBLIC)
|
List<String> targetNames =
|
||||||
.superclass(cl.asType())
|
targets.stream()
|
||||||
.addSuperinterface(ClassName.get(IInjector.class))
|
.map(ExecutableElement::getSimpleName)
|
||||||
.addMethod(buildStringReturnMethod("name", cl.getSimpleName().toString()))
|
.map(Object::toString)
|
||||||
.addMethod(buildStringReturnMethod("reason", ann.reason()))
|
.collect(Collectors.toList());
|
||||||
.addMethod(buildStringReturnMethod("targetClass", targetClassSrgName.replace('/', '.')))
|
|
||||||
.addMethod(buildStringReturnMethod("methodName", targetMethodSrgName))
|
|
||||||
.addMethod(buildStringReturnMethod("methodDesc", targetMethodDescriptor))
|
|
||||||
.addMethods(generateRequestedProxies(cl, mapper))
|
|
||||||
.addMethod(stubOverride)
|
|
||||||
.addMethod(inject)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
JavaFile javaFile = JavaFile.builder(packageName, injectorClass).build();
|
//this will contain the classes to generate: the key is the class name
|
||||||
|
Map<String, InjectorInfo> toGenerate = new HashMap<>();
|
||||||
|
|
||||||
try {
|
for(ExecutableElement inj : injectors) {
|
||||||
JavaFileObject injectorFile = processingEnv.getFiler().createSourceFile(injectorClassName);
|
MultipleInjectors minjAnn = inj.getAnnotation(MultipleInjectors.class);
|
||||||
PrintWriter out = new PrintWriter(injectorFile.openWriter());
|
int iterationNumber = 1;
|
||||||
javaFile.writeTo(out);
|
for(Injector injectorAnn : minjAnn.value()) { //java is dumb
|
||||||
out.close();
|
List<ExecutableElement> injectionCandidates = targets;
|
||||||
} catch(IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
//case 1: it has a name, try to match it
|
||||||
|
if(!injectorAnn.targetName().equals("") && targetNames.contains(injectorAnn.targetName()))
|
||||||
|
injectionCandidates =
|
||||||
|
injectionCandidates
|
||||||
|
.stream()
|
||||||
|
.filter(i -> i.getSimpleName().toString().equals(injectorAnn.targetName()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
//case 2: try to match by injectTargetName
|
||||||
|
String inferredName = inj.getSimpleName()
|
||||||
|
.toString()
|
||||||
|
.replaceFirst("inject", "");
|
||||||
|
injectionCandidates =
|
||||||
|
injectionCandidates
|
||||||
|
.stream()
|
||||||
|
.filter(t -> t.getSimpleName().toString().equalsIgnoreCase(inferredName))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
//case 3: there is only one target
|
||||||
|
if(targets.size() == 1)
|
||||||
|
injectionCandidates.add(targets.get(0));
|
||||||
|
|
||||||
|
ExecutableElement injectionTarget = null;
|
||||||
|
|
||||||
|
if(injectionCandidates.size() == 1)
|
||||||
|
injectionTarget = injectionCandidates.get(0);
|
||||||
|
|
||||||
|
if(injectorAnn.params().length != 0) {
|
||||||
|
StringBuilder descr = new StringBuilder("(");
|
||||||
|
for(Class<?> p : injectorAnn.params())
|
||||||
|
descr.append(descriptorFromType(TypeName.get(p)));
|
||||||
|
descr.append(")");
|
||||||
|
injectionCandidates =
|
||||||
|
injectionCandidates
|
||||||
|
.stream()
|
||||||
|
.filter(t -> //we care about arguments but not really about return type
|
||||||
|
descr.toString()
|
||||||
|
.split("\\)")[0]
|
||||||
|
.equalsIgnoreCase(descriptorFromExecutableElement(t).split("\\)")[0])
|
||||||
|
).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(injectionCandidates.size() == 1)
|
||||||
|
injectionTarget = injectionCandidates.get(0);
|
||||||
|
|
||||||
|
//if we haven't found it yet, it's an ambiguity
|
||||||
|
if(injectionTarget == null)
|
||||||
|
throw new AmbiguousDefinitionException("Unclear target for injector " + inj.getSimpleName().toString() + "!");
|
||||||
|
else toGenerate.put(
|
||||||
|
cl.getSimpleName().toString() + "Injector" + iterationNumber,
|
||||||
|
new InjectorInfo(
|
||||||
|
inj, findRealMethod(
|
||||||
|
injectionTarget,
|
||||||
|
mapper
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
iterationNumber++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.generatedInjectors.add(injectorClassName);
|
//iterate over the map and generate the classes
|
||||||
|
for(String injName : toGenerate.keySet()) {
|
||||||
|
MethodSpec stubOverride = MethodSpec.overriding(toGenerate.get(injName).target)
|
||||||
|
.addStatement("throw new $T($S)", RuntimeException.class, "This is a stub and should not have been called")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
MethodSpec inject = MethodSpec.methodBuilder("inject")
|
||||||
|
.addModifiers(Modifier.PUBLIC)
|
||||||
|
.returns(void.class)
|
||||||
|
.addAnnotation(Override.class)
|
||||||
|
.addParameter(ParameterSpec.builder(
|
||||||
|
TypeName.get(processingEnv
|
||||||
|
.getElementUtils()
|
||||||
|
.getTypeElement("org.objectweb.asm.tree.ClassNode").asType()), "clazz").build())
|
||||||
|
.addParameter(ParameterSpec.builder(
|
||||||
|
TypeName.get(processingEnv
|
||||||
|
.getElementUtils()
|
||||||
|
.getTypeElement("org.objectweb.asm.tree.MethodNode").asType()), "main").build())
|
||||||
|
.addStatement("super." + toGenerate.get(injName).injector.getSimpleName() + "(clazz, main)", TypeName.get(cl.asType()))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TypeSpec injectorClass = TypeSpec.classBuilder(injName)
|
||||||
|
.addModifiers(Modifier.PUBLIC)
|
||||||
|
.superclass(cl.asType())
|
||||||
|
.addSuperinterface(ClassName.get(IInjector.class))
|
||||||
|
.addMethod(buildStringReturnMethod("name", cl.getSimpleName().toString()))
|
||||||
|
.addMethod(buildStringReturnMethod("reason", patchAnn.reason()))
|
||||||
|
.addMethod(buildStringReturnMethod("targetClass", targetClassSrgName.replace('/', '.')))
|
||||||
|
.addMethod(buildStringReturnMethod("methodName", toGenerate.get(injName).target.getSimpleName().toString()))
|
||||||
|
.addMethod(buildStringReturnMethod("methodDesc", descriptorFromExecutableElement(toGenerate.get(injName).target)))
|
||||||
|
.addMethods(generateRequestedProxies(cl, mapper))
|
||||||
|
.addMethod(stubOverride)
|
||||||
|
.addMethod(inject)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
JavaFile javaFile = JavaFile.builder(packageName, injectorClass).build();
|
||||||
|
String injectorClassName = packageName + "." + injName;
|
||||||
|
|
||||||
|
try {
|
||||||
|
JavaFileObject injectorFile = processingEnv.getFiler().createSourceFile(injectorClassName);
|
||||||
|
PrintWriter out = new PrintWriter(injectorFile.openWriter());
|
||||||
|
javaFile.writeTo(out);
|
||||||
|
out.close();
|
||||||
|
} catch(IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.generatedInjectors.add(injectorClassName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -220,64 +471,44 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
.filter(m -> !m.getModifiers().contains(Modifier.STATIC)) //skip static stuff as we can't override it
|
.filter(m -> !m.getModifiers().contains(Modifier.STATIC)) //skip static stuff as we can't override it
|
||||||
.filter(m -> !m.getModifiers().contains(Modifier.FINAL)) //in case someone is trying to be funny
|
.filter(m -> !m.getModifiers().contains(Modifier.FINAL)) //in case someone is trying to be funny
|
||||||
.forEach(m -> {
|
.forEach(m -> {
|
||||||
FindMethod ann = m.getAnnotation(FindMethod.class);
|
ExecutableElement targetMethod = findRealMethod(m, mapper);
|
||||||
String targetMethodName = ann.name().equals("") ? m.getSimpleName().toString() : ann.name();
|
MethodSpec.Builder b = MethodSpec.overriding(m);
|
||||||
try {
|
b.addStatement("$T bd = $T.builder($S)",
|
||||||
MethodSpec.Builder b = MethodSpec.overriding(m);
|
MethodProxy.Builder.class,
|
||||||
Method targetMethod = ann.parent().getMethod(
|
MethodProxy.class,
|
||||||
targetMethodName,
|
m.getSimpleName().toString()
|
||||||
ann.params()
|
);
|
||||||
);
|
b.addStatement("bd.setParent($S)", ((TypeElement) targetMethod.getEnclosingElement()).getQualifiedName().toString());
|
||||||
b.addStatement("$T bd = $T.builder($S)",
|
for(Modifier mod : targetMethod.getModifiers())
|
||||||
MethodProxy.Builder.class,
|
b.addStatement("bd.addModifier($L)", mapModifier(mod));
|
||||||
MethodProxy.class,
|
for(TypeParameterElement p : targetMethod.getTypeParameters())
|
||||||
targetMethodName
|
b.addStatement("bd.addParameter($T.class)", p.asType());
|
||||||
);
|
b.addStatement("bd.setReturnType($T.class)", targetMethod.getReturnType());
|
||||||
b.addStatement("bd.setParent($S)", targetMethod.getDeclaringClass().getCanonicalName());
|
b.addStatement("return bd.build()");
|
||||||
b.addStatement("bd.setModifier($L)", targetMethod.getModifiers());
|
generated.add(b.build());
|
||||||
for(Class<?> p : targetMethod.getParameterTypes())
|
|
||||||
b.addStatement("bd.addParameter($T.class)", p);
|
|
||||||
b.addStatement("bd.setReturnType($T.class)", targetMethod.getReturnType());
|
|
||||||
b.addStatement("return bd.build()");
|
|
||||||
generated.add(b.build());
|
|
||||||
} catch(NoSuchMethodException e) {
|
|
||||||
processingEnv.getMessager().printMessage(
|
|
||||||
Diagnostic.Kind.ERROR,
|
|
||||||
"Method not found: " + targetMethodName
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
findAnnotatedMethods(cl, FindField.class)
|
findAnnotatedMethods(cl, FindField.class)
|
||||||
.stream()
|
.stream()
|
||||||
.filter(m -> !m.getModifiers().contains(Modifier.STATIC))
|
.filter(m -> !m.getModifiers().contains(Modifier.STATIC))
|
||||||
.filter(m -> !m.getModifiers().contains(Modifier.FINAL))
|
.filter(m -> !m.getModifiers().contains(Modifier.FINAL))
|
||||||
.forEach(m -> {
|
.forEach(m -> {
|
||||||
FindField ann = m.getAnnotation(FindField.class);
|
VariableElement targetField = findField(m, mapper);
|
||||||
String targetFieldName = ann.name().equals("") ? m.getSimpleName().toString() : ann.name();
|
MethodSpec.Builder b = MethodSpec.overriding(m);
|
||||||
try {
|
b.addStatement("$T bd = $T.builder($S)",
|
||||||
MethodSpec.Builder b = MethodSpec.overriding(m);
|
FieldProxy.Builder.class,
|
||||||
Field targetField = ann.parent().getField(targetFieldName);
|
FieldProxy.class,
|
||||||
b.addStatement("$T bd = $T.builder($S)",
|
targetField.getSimpleName().toString()
|
||||||
FieldProxy.Builder.class,
|
);
|
||||||
FieldProxy.class,
|
b.addStatement("bd.setParent($S)", ((TypeElement) targetField.getEnclosingElement()).getQualifiedName().toString());
|
||||||
targetFieldName
|
for(Modifier mod : targetField.getModifiers())
|
||||||
);
|
b.addStatement("bd.addModifier($L)", mapModifier(mod));
|
||||||
b.addStatement("bd.setParent($S)", targetField.getDeclaringClass().getCanonicalName());
|
b.addStatement("bd.setType($T.class)", targetField.asType());
|
||||||
b.addStatement("bd.setModifier($L)", targetField.getModifiers());
|
b.addStatement("return bd.build()");
|
||||||
b.addStatement("bd.setType($T.class)", targetField.getType());
|
generated.add(b.build());
|
||||||
b.addStatement("return bd.build()");
|
|
||||||
generated.add(b.build());
|
|
||||||
} catch(NoSuchFieldException e) {
|
|
||||||
processingEnv.getMessager().printMessage(
|
|
||||||
Diagnostic.Kind.ERROR,
|
|
||||||
"Field not found: " + targetFieldName + " in class " + ann.parent().getCanonicalName()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return generated;
|
return generated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the Service Provider file for the generated injectors.
|
* Generates the Service Provider file for the generated injectors.
|
||||||
*/
|
*/
|
||||||
|
@ -294,4 +525,31 @@ public class LilleroProcessor extends AbstractProcessor {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container for information about a class that is to be generated.
|
||||||
|
* Only used internally.
|
||||||
|
*/
|
||||||
|
private static class InjectorInfo {
|
||||||
|
/**
|
||||||
|
* The {@link ExecutableElement} corresponding to the injector method.
|
||||||
|
*/
|
||||||
|
public final ExecutableElement injector;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ExecutableElement} corresponding to the target method.
|
||||||
|
*/
|
||||||
|
public final ExecutableElement target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public constructor.
|
||||||
|
* @param injector the injector {@link ExecutableElement}
|
||||||
|
* @param target the target {@link ExecutableElement}
|
||||||
|
*/
|
||||||
|
public InjectorInfo(ExecutableElement injector, ExecutableElement target) {
|
||||||
|
this.injector = injector;
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -16,6 +16,6 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
@Retention(RetentionPolicy.CLASS)
|
@Retention(RetentionPolicy.CLASS)
|
||||||
@java.lang.annotation.Target(ElementType.METHOD)
|
@java.lang.annotation.Target(ElementType.METHOD)
|
||||||
public @interface FindField {
|
public @interface FindField {
|
||||||
Class<?> parent();
|
Class<?> parent() default Object.class;
|
||||||
String name() default "";
|
String name() default "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
@Retention(RetentionPolicy.CLASS)
|
@Retention(RetentionPolicy.CLASS)
|
||||||
@java.lang.annotation.Target(ElementType.METHOD)
|
@java.lang.annotation.Target(ElementType.METHOD)
|
||||||
public @interface FindMethod {
|
public @interface FindMethod {
|
||||||
Class<?> parent();
|
Class<?> parent() default Object.class;
|
||||||
String name() default "";
|
String name() default "";
|
||||||
Class<?>[] params();
|
Class<?>[] params() default {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package ftbsc.lll.processor.annotations;
|
package ftbsc.lll.processor.annotations;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Repeatable;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
@ -14,5 +15,9 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
* @see Target
|
* @see Target
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.CLASS)
|
@Retention(RetentionPolicy.CLASS)
|
||||||
|
@Repeatable(MultipleInjectors.class)
|
||||||
@java.lang.annotation.Target(ElementType.METHOD)
|
@java.lang.annotation.Target(ElementType.METHOD)
|
||||||
public @interface Injector {}
|
public @interface Injector {
|
||||||
|
String targetName() default "";
|
||||||
|
Class<?>[] params() default {};
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package ftbsc.lll.processor.annotations;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.CLASS)
|
||||||
|
@java.lang.annotation.Target(ElementType.METHOD)
|
||||||
|
public @interface MultipleInjectors {
|
||||||
|
Injector[] value();
|
||||||
|
}
|
|
@ -15,4 +15,15 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.CLASS)
|
@Retention(RetentionPolicy.CLASS)
|
||||||
@java.lang.annotation.Target(ElementType.METHOD)
|
@java.lang.annotation.Target(ElementType.METHOD)
|
||||||
public @interface Target {}
|
public @interface Target {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When set to false, tells the processor to first try to match a single method by name,
|
||||||
|
* and to only check parameters if further clarification is needed.
|
||||||
|
* @implNote While non-strict mode is more computationally efficient, it's ultimately not
|
||||||
|
* relevant, as it only matters at compile time. Do not set this to true unless
|
||||||
|
* you are sure know what you're doing.
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
boolean strict() default true;
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package ftbsc.lll.processor;
|
package ftbsc.lll.processor.tools;
|
||||||
|
|
||||||
import com.squareup.javapoet.ArrayTypeName;
|
import com.squareup.javapoet.ArrayTypeName;
|
||||||
import com.squareup.javapoet.ClassName;
|
import com.squareup.javapoet.ClassName;
|
||||||
|
@ -9,6 +9,7 @@ import ftbsc.lll.tools.DescriptorBuilder;
|
||||||
import javax.lang.model.element.ExecutableElement;
|
import javax.lang.model.element.ExecutableElement;
|
||||||
import javax.lang.model.element.Modifier;
|
import javax.lang.model.element.Modifier;
|
||||||
import javax.lang.model.element.TypeElement;
|
import javax.lang.model.element.TypeElement;
|
||||||
|
import javax.lang.model.type.MirroredTypeException;
|
||||||
import javax.lang.model.type.TypeMirror;
|
import javax.lang.model.type.TypeMirror;
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -35,12 +36,11 @@ public class ASTUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a type descriptor from the given {@link TypeMirror}
|
* Builds a type descriptor from the given {@link TypeName}.
|
||||||
* @param t the {@link TypeMirror} representing the desired type
|
* @param type the {@link TypeName} representing the desired type
|
||||||
* @return a {@link String} containing the relevant descriptor
|
* @return a {@link String} containing the relevant descriptor
|
||||||
*/
|
*/
|
||||||
public static String descriptorFromType(TypeMirror t) {
|
public static String descriptorFromType(TypeName type) {
|
||||||
TypeName type = TypeName.get(t);
|
|
||||||
StringBuilder desc = new StringBuilder();
|
StringBuilder desc = new StringBuilder();
|
||||||
//add array brackets
|
//add array brackets
|
||||||
while(type instanceof ArrayTypeName) {
|
while(type instanceof ArrayTypeName) {
|
||||||
|
@ -73,6 +73,15 @@ public class ASTUtils {
|
||||||
return desc.toString();
|
return desc.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a type descriptor from the given {@link TypeMirror}.
|
||||||
|
* @param t the {@link TypeMirror} representing the desired type
|
||||||
|
* @return a {@link String} containing the relevant descriptor
|
||||||
|
*/
|
||||||
|
public static String descriptorFromType(TypeMirror t) {
|
||||||
|
return descriptorFromType(TypeName.get(t));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a method descriptor from the given {@link ExecutableElement}.
|
* Builds a method descriptor from the given {@link ExecutableElement}.
|
||||||
* @param m the {@link ExecutableElement} for the method
|
* @param m the {@link ExecutableElement} for the method
|
||||||
|
@ -123,4 +132,20 @@ public class ASTUtils {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely converts a {@link Class} to its fully qualified name. See
|
||||||
|
* <a href="https://area-51.blog/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor">this blogpost</a>
|
||||||
|
* for more information.
|
||||||
|
* @param clazz the class to get the name for
|
||||||
|
* @return the fully qualified name of the given class
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
public static String getClassFullyQualifiedName(Class<?> clazz) {
|
||||||
|
try {
|
||||||
|
return clazz.getCanonicalName();
|
||||||
|
} catch(MirroredTypeException e) {
|
||||||
|
return e.getTypeMirror().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
211
src/main/java/ftbsc/lll/processor/tools/SrgMapper.java
Normal file
211
src/main/java/ftbsc/lll/processor/tools/SrgMapper.java
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
package ftbsc.lll.processor.tools;
|
||||||
|
|
||||||
|
import ftbsc.lll.exceptions.MappingNotFoundException;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a .tsrg file into a mapper capable of converting from
|
||||||
|
* deobfuscated names to SRG names.
|
||||||
|
* Obviously, it may only be used at runtime if the .tsrg file is
|
||||||
|
* included in the resources. However, in that case, I'd recommend
|
||||||
|
* using the built-in Forge one and refrain from including an extra
|
||||||
|
* resource for no good reason.
|
||||||
|
* @since 0.2.0
|
||||||
|
*/
|
||||||
|
public class SrgMapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Map using the deobfuscated names as keys,
|
||||||
|
* holding information for that Srg class as value.
|
||||||
|
*/
|
||||||
|
private final Map<String, ObfuscationData> mapper = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The public constructor.
|
||||||
|
* Should be passed a {@link Stream} of Strings, one representing each line.
|
||||||
|
* Whether they contain line endings or not is irrelevant.
|
||||||
|
* @param str a {@link Stream} of strings
|
||||||
|
*/
|
||||||
|
public SrgMapper(Stream<String> str) {
|
||||||
|
AtomicReference<String> currentClass = new AtomicReference<>("");
|
||||||
|
str.forEach(l -> {
|
||||||
|
if(l.startsWith("\t"))
|
||||||
|
mapper.get(currentClass.get()).addMember(l);
|
||||||
|
else {
|
||||||
|
ObfuscationData s = new ObfuscationData(l);
|
||||||
|
currentClass.set(s.mcpName);
|
||||||
|
mapper.put(s.mcpName, s);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the SRG-obfuscated name of the class.
|
||||||
|
* @param mcp the MCP (deobfuscated) internal name of the desired class
|
||||||
|
* @return the SRG name of the class
|
||||||
|
* @throws MappingNotFoundException if no mapping is found
|
||||||
|
*/
|
||||||
|
public String getSrgClass(String mcp) {
|
||||||
|
ObfuscationData data = mapper.get(mcp);
|
||||||
|
if(data == null)
|
||||||
|
throw new MappingNotFoundException(mcp);
|
||||||
|
else return data.srgName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the MCP (deobfuscated) name of the class.
|
||||||
|
* Due to how it's implemented, it's considerably less efficient than its
|
||||||
|
* opposite operation.
|
||||||
|
* @param srg the SRG-obfuscated internal name of the desired class
|
||||||
|
* @return the MCP name of the class
|
||||||
|
*/
|
||||||
|
public String getMcpClass(String srg) {
|
||||||
|
ObfuscationData data = getObfuscationData(srg);
|
||||||
|
return data.mcpName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets one between the SRG and MCP names.
|
||||||
|
* @param name the internal name of the desired class in either format
|
||||||
|
* @param obf whether it should return the obfuscated name
|
||||||
|
* @return a {@link String} containing the internal name of the class
|
||||||
|
* @throws MappingNotFoundException if no mapping is found
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
public String mapClass(String name, boolean obf) {
|
||||||
|
String srg;
|
||||||
|
try {
|
||||||
|
srg = this.getSrgClass(name);
|
||||||
|
} catch(MappingNotFoundException e) {
|
||||||
|
srg = name;
|
||||||
|
name = this.getMcpClass(srg);
|
||||||
|
}
|
||||||
|
if(obf) return srg;
|
||||||
|
else return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the SRG-obfuscated name of a class member (field or method).
|
||||||
|
* The method signature must be in this format: "methodName methodDescriptor",
|
||||||
|
* with a space, because that's how it is in .tsrg files.
|
||||||
|
* @param mcpClass the MCP (deobfuscated) internal name of the container class
|
||||||
|
* @param member the field name or method signature
|
||||||
|
* @return the SRG name of the given member
|
||||||
|
* @throws MappingNotFoundException if no mapping is found
|
||||||
|
*/
|
||||||
|
public String getSrgMember(String mcpClass, String member) {
|
||||||
|
ObfuscationData data = mapper.get(mcpClass);
|
||||||
|
if(data == null)
|
||||||
|
throw new MappingNotFoundException(mcpClass + "::" + member);
|
||||||
|
return data.members.get(member);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the MCP (deobfuscated) name of the given member.
|
||||||
|
* Due to how it's implemented, it's considerably less efficient than its
|
||||||
|
* opposite operation.
|
||||||
|
* @param srgClass the SRG-obfuscated internal name of the container class
|
||||||
|
* @param member the field name or method signature
|
||||||
|
* @return the MCP name of the given member
|
||||||
|
*/
|
||||||
|
public String getMcpMember(String srgClass, String member) {
|
||||||
|
ObfuscationData data = getObfuscationData(srgClass);
|
||||||
|
for(String mcp : data.members.keySet())
|
||||||
|
if(data.members.get(mcp).equals(member))
|
||||||
|
return mcp;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obfuscates or deobfuscates a member, given one of its names and the effective.
|
||||||
|
* @param className the internal or fully qualified name of the container class
|
||||||
|
* @param memberName the member of the class
|
||||||
|
* @param obf whether it should return the obfuscated name
|
||||||
|
* @return the mapped member name
|
||||||
|
* @throws MappingNotFoundException if no mapping is found
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
public String mapMember(String className, String memberName, boolean obf) {
|
||||||
|
className = className.replace('.', '/');
|
||||||
|
String effectiveClassName = this.mapClass(className, obf);
|
||||||
|
String srgMemberName;
|
||||||
|
try {
|
||||||
|
srgMemberName = this.getSrgMember(effectiveClassName, memberName);
|
||||||
|
} catch(MappingNotFoundException e) {
|
||||||
|
srgMemberName = memberName;
|
||||||
|
memberName = this.getMcpMember(effectiveClassName, memberName);
|
||||||
|
}
|
||||||
|
if(obf) return srgMemberName;
|
||||||
|
else return memberName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used internally. Gets the obfuscation data corresponding to the given SRG name.
|
||||||
|
* @return the desired {@link ObfuscationData} object
|
||||||
|
* @throws MappingNotFoundException if no {@link ObfuscationData} object is found
|
||||||
|
*/
|
||||||
|
private ObfuscationData getObfuscationData(String srg) {
|
||||||
|
for(ObfuscationData s : mapper.values())
|
||||||
|
if(s.srgName.equals(srg))
|
||||||
|
return s;
|
||||||
|
throw new MappingNotFoundException(srg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private class used internally for storing information about each
|
||||||
|
* class. It's private because there is no good reason anyone would
|
||||||
|
* want to access this outside of this class.
|
||||||
|
*/
|
||||||
|
private static class ObfuscationData {
|
||||||
|
/**
|
||||||
|
* The MCP internal name (FQN with '/' instad of '.') of the class.
|
||||||
|
*/
|
||||||
|
private final String mcpName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SRG internal name (FQN with '/' instad of '.') of the class.
|
||||||
|
*/
|
||||||
|
private final String srgName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link Map} tying each member's deobfuscated name or signature to its
|
||||||
|
* SRG name.
|
||||||
|
*/
|
||||||
|
private final Map<String, String> members;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The constructor. It takes in the line where the class is declared,
|
||||||
|
* which looks something like this:
|
||||||
|
* {@code internal/name/mcp internal/name/srg }
|
||||||
|
* @param s the String represeting the declaration line
|
||||||
|
*/
|
||||||
|
private ObfuscationData(String s) {
|
||||||
|
String[] split = s.trim().split(" ");
|
||||||
|
this.mcpName = split[0];
|
||||||
|
this.srgName = split[1];
|
||||||
|
this.members = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a member to the target class. It takes in the line where the
|
||||||
|
* member is declared.
|
||||||
|
* For fields it looks like this:
|
||||||
|
* {@code fieldMcpName field_srg_name}
|
||||||
|
* For methods it looks like this:
|
||||||
|
* {@code methodName methodDescriptor method_srg_name}
|
||||||
|
* @param s the String representing the declaration line
|
||||||
|
*/
|
||||||
|
public void addMember(String s) {
|
||||||
|
String[] split = s.trim().split(" ");
|
||||||
|
if(split.length == 2) //field
|
||||||
|
members.put(split[0], split[1]);
|
||||||
|
else if (split.length == 3) //method
|
||||||
|
members.put(split[0] + " " + split[1], split[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue