From e3f452250e51b7271f3989c7dfd12e4422934942 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 21 May 2015 23:30:00 +0100 Subject: Support Gradle alongside SSJB This makes builds faster, simpler and better automated but still keeps Cuchaz happy. :) --- src/cuchaz/enigma/bytecode/ClassRenamer.java | 544 +++++++++++++++++++++++++++ 1 file changed, 544 insertions(+) create mode 100644 src/cuchaz/enigma/bytecode/ClassRenamer.java (limited to 'src/cuchaz/enigma/bytecode/ClassRenamer.java') diff --git a/src/cuchaz/enigma/bytecode/ClassRenamer.java b/src/cuchaz/enigma/bytecode/ClassRenamer.java new file mode 100644 index 0000000..4d95f30 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/ClassRenamer.java @@ -0,0 +1,544 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.bytecode; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javassist.CtClass; +import javassist.bytecode.AttributeInfo; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.ByteArray; +import javassist.bytecode.ClassFile; +import javassist.bytecode.CodeAttribute; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; +import javassist.bytecode.FieldInfo; +import javassist.bytecode.InnerClassesAttribute; +import javassist.bytecode.LocalVariableTypeAttribute; +import javassist.bytecode.MethodInfo; +import javassist.bytecode.SignatureAttribute; +import javassist.bytecode.SignatureAttribute.ArrayType; +import javassist.bytecode.SignatureAttribute.BaseType; +import javassist.bytecode.SignatureAttribute.ClassSignature; +import javassist.bytecode.SignatureAttribute.ClassType; +import javassist.bytecode.SignatureAttribute.MethodSignature; +import javassist.bytecode.SignatureAttribute.NestedClassType; +import javassist.bytecode.SignatureAttribute.ObjectType; +import javassist.bytecode.SignatureAttribute.Type; +import javassist.bytecode.SignatureAttribute.TypeArgument; +import javassist.bytecode.SignatureAttribute.TypeParameter; +import javassist.bytecode.SignatureAttribute.TypeVariable; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ClassNameReplacer; +import cuchaz.enigma.mapping.Translator; + +public class ClassRenamer { + + private static enum SignatureType { + Class { + + @Override + public String rename(String signature, ReplacerClassMap map) { + return renameClassSignature(signature, map); + } + }, + Field { + + @Override + public String rename(String signature, ReplacerClassMap map) { + return renameFieldSignature(signature, map); + } + }, + Method { + + @Override + public String rename(String signature, ReplacerClassMap map) { + return renameMethodSignature(signature, map); + } + }; + + public abstract String rename(String signature, ReplacerClassMap map); + } + + private static class ReplacerClassMap extends HashMap { + + private static final long serialVersionUID = 317915213205066168L; + + private ClassNameReplacer m_replacer; + + public ReplacerClassMap(ClassNameReplacer replacer) { + m_replacer = replacer; + } + + @Override + public String get(Object obj) { + if (obj instanceof String) { + return get((String)obj); + } + return null; + } + + public String get(String className) { + return m_replacer.replace(className); + } + } + + public static void renameClasses(CtClass c, final Translator translator) { + renameClasses(c, new ClassNameReplacer() { + @Override + public String replace(String className) { + ClassEntry entry = translator.translateEntry(new ClassEntry(className)); + if (entry != null) { + return entry.getName(); + } + return null; + } + }); + } + + public static void moveAllClassesOutOfDefaultPackage(CtClass c, final String newPackageName) { + renameClasses(c, new ClassNameReplacer() { + @Override + public String replace(String className) { + ClassEntry entry = new ClassEntry(className); + if (entry.isInDefaultPackage()) { + return newPackageName + "/" + entry.getName(); + } + return null; + } + }); + } + + public static void moveAllClassesIntoDefaultPackage(CtClass c, final String oldPackageName) { + renameClasses(c, new ClassNameReplacer() { + @Override + public String replace(String className) { + ClassEntry entry = new ClassEntry(className); + if (entry.getPackageName().equals(oldPackageName)) { + return entry.getSimpleName(); + } + return null; + } + }); + } + + @SuppressWarnings("unchecked") + public static void renameClasses(CtClass c, ClassNameReplacer replacer) { + + // sadly, we can't use CtClass.renameClass() because SignatureAttribute.renameClass() is extremely buggy =( + + ReplacerClassMap map = new ReplacerClassMap(replacer); + ClassFile classFile = c.getClassFile(); + + // rename the constant pool (covers ClassInfo, MethodTypeInfo, and NameAndTypeInfo) + ConstPool constPool = c.getClassFile().getConstPool(); + constPool.renameClass(map); + + // rename class attributes + renameAttributes(classFile.getAttributes(), map, SignatureType.Class); + + // rename methods + for (MethodInfo methodInfo : (List)classFile.getMethods()) { + methodInfo.setDescriptor(Descriptor.rename(methodInfo.getDescriptor(), map)); + renameAttributes(methodInfo.getAttributes(), map, SignatureType.Method); + } + + // rename fields + for (FieldInfo fieldInfo : (List)classFile.getFields()) { + fieldInfo.setDescriptor(Descriptor.rename(fieldInfo.getDescriptor(), map)); + renameAttributes(fieldInfo.getAttributes(), map, SignatureType.Field); + } + + // rename the class name itself last + // NOTE: don't use the map here, because setName() calls the buggy SignatureAttribute.renameClass() + // we only want to replace exactly this class name + String newName = renameClassName(c.getName(), map); + if (newName != null) { + c.setName(newName); + } + + // replace simple names in the InnerClasses attribute too + InnerClassesAttribute attr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag); + if (attr != null) { + for (int i = 0; i < attr.tableLength(); i++) { + + // get the inner class full name (which has already been translated) + ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(attr.innerClass(i))); + + if (attr.innerNameIndex(i) != 0) { + // update the inner name + attr.setInnerNameIndex(i, constPool.addUtf8Info(classEntry.getInnermostClassName())); + } + + /* DEBUG + System.out.println(String.format("\tDEOBF: %s-> ATTR: %s,%s,%s", classEntry, attr.outerClass(i), attr.innerClass(i), attr.innerName(i))); + */ + } + } + } + + @SuppressWarnings("unchecked") + private static void renameAttributes(List attributes, ReplacerClassMap map, SignatureType type) { + try { + + // make the rename class method accessible + Method renameClassMethod = AttributeInfo.class.getDeclaredMethod("renameClass", Map.class); + renameClassMethod.setAccessible(true); + + for (AttributeInfo attribute : attributes) { + if (attribute instanceof SignatureAttribute) { + // this has to be handled specially because SignatureAttribute.renameClass() is buggy as hell + SignatureAttribute signatureAttribute = (SignatureAttribute)attribute; + String newSignature = type.rename(signatureAttribute.getSignature(), map); + if (newSignature != null) { + signatureAttribute.setSignature(newSignature); + } + } else if (attribute instanceof CodeAttribute) { + // code attributes have signature attributes too (indirectly) + CodeAttribute codeAttribute = (CodeAttribute)attribute; + renameAttributes(codeAttribute.getAttributes(), map, type); + } else if (attribute instanceof LocalVariableTypeAttribute) { + // lvt attributes have signature attributes too + LocalVariableTypeAttribute localVariableAttribute = (LocalVariableTypeAttribute)attribute; + renameLocalVariableTypeAttribute(localVariableAttribute, map); + } else { + renameClassMethod.invoke(attribute, map); + } + } + + } catch(NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + throw new Error("Unable to call javassist methods by reflection!", ex); + } + } + + private static void renameLocalVariableTypeAttribute(LocalVariableTypeAttribute attribute, ReplacerClassMap map) { + + // adapted from LocalVariableAttribute.renameClass() + ConstPool cp = attribute.getConstPool(); + int n = attribute.tableLength(); + byte[] info = attribute.get(); + for (int i = 0; i < n; ++i) { + int pos = i * 10 + 2; + int index = ByteArray.readU16bit(info, pos + 6); + if (index != 0) { + String signature = cp.getUtf8Info(index); + String newSignature = renameLocalVariableSignature(signature, map); + if (newSignature != null) { + ByteArray.write16bit(cp.addUtf8Info(newSignature), info, pos + 6); + } + } + } + } + + private static String renameLocalVariableSignature(String signature, ReplacerClassMap map) { + + // for some reason, signatures with . in them don't count as field signatures + // looks like anonymous classes delimit with . in stead of $ + // convert the . to $, but keep track of how many we replace + // we need to put them back after we translate + int start = signature.lastIndexOf('$') + 1; + int numConverted = 0; + StringBuilder buf = new StringBuilder(signature); + for (int i=buf.length()-1; i>=start; i--) { + char c = buf.charAt(i); + if (c == '.') { + buf.setCharAt(i, '$'); + numConverted++; + } + } + signature = buf.toString(); + + // translate + String newSignature = renameFieldSignature(signature, map); + if (newSignature != null) { + + // put the delimiters back + buf = new StringBuilder(newSignature); + for (int i=buf.length()-1; i>=0 && numConverted > 0; i--) { + char c = buf.charAt(i); + if (c == '$') { + buf.setCharAt(i, '.'); + numConverted--; + } + } + assert(numConverted == 0); + newSignature = buf.toString(); + + return newSignature; + } + + return null; + } + + private static String renameClassSignature(String signature, ReplacerClassMap map) { + try { + ClassSignature type = renameType(SignatureAttribute.toClassSignature(signature), map); + if (type != null) { + return type.encode(); + } + return null; + } catch (BadBytecode ex) { + throw new Error("Can't parse field signature: " + signature); + } + } + + private static String renameFieldSignature(String signature, ReplacerClassMap map) { + try { + ObjectType type = renameType(SignatureAttribute.toFieldSignature(signature), map); + if (type != null) { + return type.encode(); + } + return null; + } catch (BadBytecode ex) { + throw new Error("Can't parse class signature: " + signature); + } + } + + private static String renameMethodSignature(String signature, ReplacerClassMap map) { + try { + MethodSignature type = renameType(SignatureAttribute.toMethodSignature(signature), map); + if (type != null) { + return type.encode(); + } + return null; + } catch (BadBytecode ex) { + throw new Error("Can't parse method signature: " + signature); + } + } + + private static ClassSignature renameType(ClassSignature type, ReplacerClassMap map) { + + TypeParameter[] typeParamTypes = type.getParameters(); + if (typeParamTypes != null) { + typeParamTypes = Arrays.copyOf(typeParamTypes, typeParamTypes.length); + for (int i=0; i