package coloredlightscore.src.asm.transformer.core;
import coloredlightscore.src.asm.ColoredLightsCoreLoadingPlugin;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.Iterables;
import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;
import net.minecraft.launchwrapper.IClassNameTransformer;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import static coloredlightscore.src.asm.ColoredLightsCoreDummyContainer.CLLog;
import static coloredlightscore.src.asm.ColoredLightsCoreLoadingPlugin.CLASSLOADER;
import static org.objectweb.asm.Opcodes.ACC_INTERFACE;
@SuppressWarnings("SameParameterValue")
public final class ASMUtils {
private ASMUtils() {
}
public static String deobfuscate(String className, FieldNode field) {
return deobfuscateField(className, field.name, field.desc);
}
public static String deobfuscateField(String className, String fieldName, String desc) {
return FMLDeobfuscatingRemapper.INSTANCE.mapFieldName(className, fieldName, desc);
}
public static String deobfuscate(String className, MethodNode method) {
return deobfuscateMethod(className, method.name, method.desc);
}
public static String deobfuscateMethod(String className, String methodName, String desc) {
return FMLDeobfuscatingRemapper.INSTANCE.mapMethodName(className, methodName, desc);
}
public static String getFieldDescriptor(ClassNode clazz, String fieldName) {
for (FieldNode field : clazz.fields) {
if (field.name.equals(fieldName)) {
return field.desc;
}
}
return null;
}
public static boolean useMcpNames() {
return ColoredLightsCoreLoadingPlugin.MCP_ENVIRONMENT;
}
public static AbstractInsnNode findLastReturn(MethodNode method) {
int searchFor = Type.getReturnType(method.desc).getOpcode(Opcodes.IRETURN);
return ASMUtils.findLastOpcode(method, searchFor);
}
public static AbstractInsnNode findLastOpcode(MethodNode method, int opcode) {
AbstractInsnNode found = null;
for (int i = 0; i < method.instructions.size(); i++) {
AbstractInsnNode insn = method.instructions.get(i);
if (insn.getOpcode() == opcode) {
found = insn;
}
}
return found;
}
/**
* Finds last occurance of LDC <ldcArgument>
*
* @param method Method to search
* @param ldcArgument String literal to be found
* @return LdcInsnNode instance when found, null if not found
*/
public static LdcInsnNode findLastLDC(MethodNode method, String ldcArgument) {
LdcInsnNode found = null;
LdcInsnNode candidate;
for (int i = 0; i < method.instructions.size(); i++) {
AbstractInsnNode insn = method.instructions.get(i);
if (insn.getOpcode() == Opcodes.LDC) {
candidate = (LdcInsnNode) insn;
if (ldcArgument.equals(candidate.cst))
found = candidate;
}
}
return found;
}
public static MethodInsnNode findLastInvoke(MethodNode method, int opcode, String className, String methodNameAndDescriptor, boolean dumpCandidates) {
MethodInsnNode found = null;
MethodInsnNode candidate;
String obfClass = NameMapper.getInstance().getClassName(className);
String obfName = NameMapper.getInstance().getMethodName(className, methodNameAndDescriptor);
String obfDesc = NameMapper.getInstance().getMethodDescriptor(className, methodNameAndDescriptor);
for (int i = 0; i < method.instructions.size(); i++) {
AbstractInsnNode insn = method.instructions.get(i);
if (insn.getOpcode() == opcode) {
candidate = (MethodInsnNode) insn;
if (dumpCandidates) {
CLLog.debug(String.format(" findLastInvoke@%s is %s/%s %s, looking for [%s|%s]/%s %s", i, candidate.owner, candidate.name, candidate.desc, className, obfClass, obfName, obfDesc));
}
if ((candidate.owner.equals(obfClass) | candidate.owner.equals(className)) && candidate.name.equals(obfName) && candidate.desc.equals(obfDesc))
found = candidate;
}
}
return found;
}
public static String makeNameInternal(String name) {
return name.replace('.', '/');
}
public static String undoInternalName(String name) {
return name.replace('/', '.');
}
public static MethodInsnNode generateMethodCall(Method method) {
int opcode = Modifier.isStatic(method.getModifiers()) ? Opcodes.INVOKESTATIC : Opcodes.INVOKEVIRTUAL;
return new MethodInsnNode(opcode, Type.getInternalName(method.getDeclaringClass()), method.getName(), Type.getMethodDescriptor(method));
}
public static MethodNode generateSetterMethod(String targetClass, String setterMethodName, String fieldName, String fieldTypeDescriptor) {
return generateSetterMethod(targetClass, setterMethodName, fieldName, fieldTypeDescriptor, Opcodes.ACC_PUBLIC);
}
public static MethodNode generateSetterMethod(String targetClass, String setterMethodName, String fieldName, String fieldTypeDescriptor, int methodAccess) {
Type fieldType = Type.getType(fieldTypeDescriptor);
MethodNode setter = new MethodNode();
setter.name = setterMethodName;
setter.desc = String.format("(%s)V", fieldTypeDescriptor);
setter.exceptions = new ArrayList<>();
setter.access = methodAccess;
setter.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // store [this]
setter.instructions.add(new VarInsnNode(fieldType.getOpcode(Opcodes.ILOAD), 1)); // store argument
setter.instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, targetClass, fieldName, fieldTypeDescriptor));
setter.instructions.add(new InsnNode(Opcodes.RETURN));
return setter;
}
public static MethodNode generateGetterMethod(String targetClass, String setterMethodName, String fieldName, String fieldTypeDescriptor) {
return generateGetterMethod(targetClass, setterMethodName, fieldName, fieldTypeDescriptor, Opcodes.ACC_PUBLIC);
}
public static MethodNode generateGetterMethod(String targetClass, String setterMethodName, String fieldName, String fieldTypeDescriptor, int methodAccess) {
Type fieldType = Type.getType(fieldTypeDescriptor);
MethodNode getter = new MethodNode();
getter.name = setterMethodName;
getter.desc = String.format("()%s", fieldTypeDescriptor);
getter.exceptions = new ArrayList<>();
getter.access = methodAccess;
getter.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // store [this]
getter.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, targetClass, fieldName, fieldTypeDescriptor));
getter.instructions.add(new InsnNode(fieldType.getOpcode(Opcodes.IRETURN)));
return getter;
}
public static ClassNode getClassNode(byte[] bytes) {
ClassReader reader = new ClassReader(bytes);
ClassNode clazz = new ClassNode();
reader.accept(clazz, 0);
return clazz;
}
private static IClassNameTransformer nameTransformer;
public static IClassNameTransformer getClassNameTransformer() {
boolean nameTransChecked = false;
if (!nameTransChecked) {
Iterable<IClassNameTransformer> nameTransformers = Iterables.filter(ColoredLightsCoreLoadingPlugin.CLASSLOADER.getTransformers(), IClassNameTransformer.class);
nameTransformer = Iterables.getOnlyElement(nameTransformers, null);
}
return nameTransformer;
}
public static String deobfuscateClass(String obfName) {
IClassNameTransformer t = getClassNameTransformer();
return ASMUtils.makeNameInternal(t == null ? obfName : t.remapClassName(obfName));
}
public static String obfuscateClass(String deobfName) {
IClassNameTransformer t = getClassNameTransformer();
return ASMUtils.makeNameInternal(t == null ? deobfName : t.unmapClassName(deobfName));
}
public static ClassNode getClassNode(String name) {
try {
return getClassNode(ColoredLightsCoreLoadingPlugin.CLASSLOADER.getClassBytes(obfuscateClass(name)));
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
public static AnnotationNode getAnnotation(FieldNode field, Class<? extends Annotation> ann) {
return getAnnotation(JavaUtils.concatNullable(field.visibleAnnotations, field.invisibleAnnotations), ann);
}
public static AnnotationNode getAnnotation(ClassNode clazz, Class<? extends Annotation> ann) {
return getAnnotation(JavaUtils.concatNullable(clazz.visibleAnnotations, clazz.invisibleAnnotations), ann);
}
public static AnnotationNode getAnnotation(MethodNode method, Class<? extends Annotation> ann) {
return getAnnotation(JavaUtils.concatNullable(method.visibleAnnotations, method.invisibleAnnotations), ann);
}
public static AnnotationNode getAnnotation(Iterable<AnnotationNode> annotations, Class<? extends Annotation> ann) {
String desc = Type.getDescriptor(ann);
for (AnnotationNode node : annotations) {
if (node.desc.equals(desc)) {
return node;
}
}
return null;
}
public static boolean hasAnnotation(FieldNode field, Class<? extends Annotation> annotation) {
return getAnnotation(field, annotation) != null;
}
public static boolean hasAnnotation(ClassNode clazz, Class<? extends Annotation> annotation) {
return getAnnotation(clazz, annotation) != null;
}
public static boolean hasAnnotation(MethodNode method, Class<? extends Annotation> annotation) {
return getAnnotation(method, annotation) != null;
}
public static boolean isPrimitive(Type type) {
return type.getSort() != Type.ARRAY && type.getSort() != Type.OBJECT && type.getSort() != Type.METHOD;
}
public static ClassInfo getClassInfo(Class<?> clazz) {
return new ClassInfoFromClazz(clazz);
}
public static ClassInfo getClassInfo(ClassNode clazz) {
return new ClassInfoFromNode(clazz);
}
public static ClassInfo getClassInfo(String className) {
try {
// 03-05-2014 heaton84 - Fixed NullPointerException
byte[] bytes = null;
try {
bytes = CLASSLOADER.getClassBytes(className);
} catch (NullPointerException npe) {
npe.printStackTrace();
}
if (bytes != null) {
return new ClassInfoFromNode(ASMUtils.getClassNode(bytes));
} else {
// heaton84: This is a no-no. Class.forName is a minefield during class transformation
return null;
//return new ClassInfoFromClazz(Class.forName(ASMUtils.undoInternalName(className)));
}
} catch (Exception e) {
throw JavaUtils.throwUnchecked(e);
}
}
public static interface ClassInfo {
Collection<String> interfaces();
String superName();
String internalName();
boolean isInterface();
}
private static final class ClassInfoFromClazz implements ClassInfo {
private final Class<?> clazz;
private final Collection<String> interfaces;
ClassInfoFromClazz(Class<?> clazz) {
this.clazz = clazz;
interfaces = Collections2.transform(Arrays.asList(clazz.getInterfaces()), ClassToNameFunc.INSTANCE);
}
@Override
public Collection<String> interfaces() {
return interfaces;
}
@Override
public String superName() {
Class<?> s = clazz.getSuperclass();
return s == null ? null : ASMUtils.makeNameInternal(s.getCanonicalName());
}
@Override
public String internalName() {
return ASMUtils.makeNameInternal(clazz.getCanonicalName());
}
@Override
public boolean isInterface() {
return clazz.isInterface();
}
}
private static final class ClassInfoFromNode implements ClassInfo {
private final ClassNode clazz;
ClassInfoFromNode(ClassNode clazz) {
this.clazz = clazz;
}
@Override
public Collection<String> interfaces() {
return clazz.interfaces;
}
@Override
public String superName() {
return clazz.superName;
}
@Override
public String internalName() {
return clazz.name;
}
@Override
public boolean isInterface() {
return (clazz.access & ACC_INTERFACE) == ACC_INTERFACE;
}
}
private static enum ClassToNameFunc implements Function<Class<?>, String> {
INSTANCE;
@Override
public String apply(Class<?> input) {
return ASMUtils.makeNameInternal(input.getCanonicalName());
}
}
public static boolean isAssignableFrom(ClassInfo parent, ClassInfo child) {
return parent.internalName().equals(child.internalName()) || parent.internalName().equals(child.superName()) || child.interfaces().contains(parent.internalName()) || child.superName() != null && !child.superName().equals("java/lang/Object") && isAssignableFrom(parent, getClassInfo(child.superName()));
}
/**
* Given a type, returns the java keyword for it. Used to assemble
* exception messages by the Transformer classes (I expected "int blah(float blah)").
* Not tested on arrays.
*
* @param type
* @return
*/
public static String getTypeKeyword(Type type) {
// Surely there must be a better way...
// Also not sure how this reacts with arrays...
if (type == Type.BOOLEAN_TYPE)
return "boolean";
if (type == Type.BYTE_TYPE)
return "byte";
if (type == Type.CHAR_TYPE)
return "char";
if (type == Type.DOUBLE_TYPE)
return "double";
if (type == Type.FLOAT_TYPE)
return "float";
if (type == Type.INT_TYPE)
return "int";
if (type == Type.LONG_TYPE)
return "long";
if (type == Type.SHORT_TYPE)
return "short";
if (type == Type.VOID_TYPE)
return "void";
String internalName = type.getInternalName();
int lastSlash = internalName.lastIndexOf('/');
if (lastSlash > -1)
return internalName.substring(lastSlash + 1);
else
return internalName;
}
/**
* Tests for the presence of a method with the given name and descriptor in a target
* class. If the method is not found, throws an IllegalArgumentException.
*
* @param className The name of the class to search
* @param methodName The name of the method to look for
* @param methodDescriptor The descriptor of the method to look for
* @throws IOException When the class cannot be found as named
* @throws IllegalArgumentException When the method is not found
* @author heaton84
*/
public static void assertClassContainsHelperMethod(String className,
String methodName, String methodDescriptor) throws IOException, IllegalArgumentException {
String classPath = "/" + ASMUtils.makeNameInternal(className) + ".class";
ClassReader classReader = new ClassReader(ASMUtils.class.getResourceAsStream(classPath));
ClassNode classNode = new ClassNode();
boolean foundStatic = false;
boolean foundNonstatic = false;
classReader.accept(classNode, 0);
for (MethodNode m : classNode.methods) {
if (m.name.equals(methodName) && m.desc.equals(methodDescriptor)) {
// Better make sure it's a static
if ((m.access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC)
foundStatic = true;
else
foundNonstatic = true;
}
}
if (!foundStatic) {
String exceptionMessage;
String plainTextDescriptor;
Type expectedReturnType = Type.getReturnType(methodDescriptor);
Type[] expectedArgs = Type.getArgumentTypes(methodDescriptor);
int argNum;
// Turn methodDescriptor (III)Z into plaintext boolean funcname(int, int, int)
plainTextDescriptor = String.format("%s %s(", ASMUtils.getTypeKeyword(expectedReturnType), methodName);
for (argNum = 0; argNum < expectedArgs.length; argNum++) {
if (argNum > 0 && argNum <= expectedArgs.length - 1)
plainTextDescriptor += ", ";
plainTextDescriptor += ASMUtils.getTypeKeyword(expectedArgs[argNum]); // getClassNameFromInternalName(expectedArgs[argNum].getInternalName());
}
if (foundNonstatic)
exceptionMessage = String.format("Missing STATIC modifier on helper method %s.%s %s!", className, methodName, methodDescriptor);
else
exceptionMessage = String.format("Unable to locate helper method \"static %s)\" in class %s!", plainTextDescriptor, className);
throw new IllegalArgumentException(exceptionMessage);
}
}
}