From 4be005617b3b8c3578cca07c5d085d12916f0d1d Mon Sep 17 00:00:00 2001 From: lclc98 Date: Thu, 30 Jun 2016 00:49:21 +1000 Subject: Json format (#2) * Added new format * Fixed bug * Updated Version --- .../cuchaz/enigma/bytecode/CheckCastIterator.java | 117 +++++ .../cuchaz/enigma/bytecode/ClassProtectifier.java | 51 ++ .../cuchaz/enigma/bytecode/ClassPublifier.java | 51 ++ .../java/cuchaz/enigma/bytecode/ClassRenamer.java | 514 +++++++++++++++++++++ .../cuchaz/enigma/bytecode/ClassTranslator.java | 151 ++++++ .../cuchaz/enigma/bytecode/ConstPoolEditor.java | 263 +++++++++++ src/main/java/cuchaz/enigma/bytecode/InfoType.java | 301 ++++++++++++ .../cuchaz/enigma/bytecode/InnerClassWriter.java | 132 ++++++ .../enigma/bytecode/LocalVariableRenamer.java | 119 +++++ .../enigma/bytecode/MethodParameterWriter.java | 66 +++ .../enigma/bytecode/MethodParametersAttribute.java | 86 ++++ .../bytecode/accessors/ClassInfoAccessor.java | 55 +++ .../bytecode/accessors/ConstInfoAccessor.java | 151 ++++++ .../accessors/InvokeDynamicInfoAccessor.java | 74 +++ .../bytecode/accessors/MemberRefInfoAccessor.java | 74 +++ .../accessors/MethodHandleInfoAccessor.java | 74 +++ .../bytecode/accessors/MethodTypeInfoAccessor.java | 55 +++ .../accessors/NameAndTypeInfoAccessor.java | 74 +++ .../bytecode/accessors/StringInfoAccessor.java | 55 +++ .../bytecode/accessors/Utf8InfoAccessor.java | 28 ++ 20 files changed, 2491 insertions(+) create mode 100644 src/main/java/cuchaz/enigma/bytecode/CheckCastIterator.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/InfoType.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/LocalVariableRenamer.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/MethodParameterWriter.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java (limited to 'src/main/java/cuchaz/enigma/bytecode') diff --git a/src/main/java/cuchaz/enigma/bytecode/CheckCastIterator.java b/src/main/java/cuchaz/enigma/bytecode/CheckCastIterator.java new file mode 100644 index 0000000..8058d0e --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/CheckCastIterator.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * 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.util.Iterator; + +import cuchaz.enigma.bytecode.CheckCastIterator.CheckCast; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.Signature; +import javassist.bytecode.*; + +public class CheckCastIterator implements Iterator { + + public static class CheckCast { + + public String className; + public MethodEntry prevMethodEntry; + + public CheckCast(String className, MethodEntry prevMethodEntry) { + this.className = className; + this.prevMethodEntry = prevMethodEntry; + } + } + + private ConstPool m_constants; + private CodeAttribute m_attribute; + private CodeIterator m_iter; + private CheckCast m_next; + + public CheckCastIterator(CodeAttribute codeAttribute) throws BadBytecode { + m_constants = codeAttribute.getConstPool(); + m_attribute = codeAttribute; + m_iter = m_attribute.iterator(); + + m_next = getNext(); + } + + @Override + public boolean hasNext() { + return m_next != null; + } + + @Override + public CheckCast next() { + CheckCast out = m_next; + try { + m_next = getNext(); + } catch (BadBytecode ex) { + throw new Error(ex); + } + return out; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + private CheckCast getNext() throws BadBytecode { + int prevPos = 0; + while (m_iter.hasNext()) { + int pos = m_iter.next(); + int opcode = m_iter.byteAt(pos); + switch (opcode) { + case Opcode.CHECKCAST: + + // get the type of this op code (next two bytes are a classinfo index) + MethodEntry prevMethodEntry = getMethodEntry(prevPos); + if (prevMethodEntry != null) { + return new CheckCast(m_constants.getClassInfo(m_iter.s16bitAt(pos + 1)), prevMethodEntry); + } + break; + } + prevPos = pos; + } + return null; + } + + private MethodEntry getMethodEntry(int pos) { + switch (m_iter.byteAt(pos)) { + case Opcode.INVOKEVIRTUAL: + case Opcode.INVOKESTATIC: + case Opcode.INVOKEDYNAMIC: + case Opcode.INVOKESPECIAL: { + int index = m_iter.s16bitAt(pos + 1); + return new MethodEntry( + new ClassEntry(Descriptor.toJvmName(m_constants.getMethodrefClassName(index))), + m_constants.getMethodrefName(index), + new Signature(m_constants.getMethodrefType(index)) + ); + } + + case Opcode.INVOKEINTERFACE: { + int index = m_iter.s16bitAt(pos + 1); + return new MethodEntry( + new ClassEntry(Descriptor.toJvmName(m_constants.getInterfaceMethodrefClassName(index))), + m_constants.getInterfaceMethodrefName(index), + new Signature(m_constants.getInterfaceMethodrefType(index)) + ); + } + } + return null; + } + + public Iterable casts() { + return () -> CheckCastIterator.this; + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java b/src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java new file mode 100644 index 0000000..ad5bab0 --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * 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 javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtField; +import javassist.bytecode.AccessFlag; +import javassist.bytecode.InnerClassesAttribute; + + +public class ClassProtectifier { + + public static CtClass protectify(CtClass c) { + + // protectify all the fields + for (CtField field : c.getDeclaredFields()) { + field.setModifiers(protectify(field.getModifiers())); + } + + // protectify all the methods and constructors + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + behavior.setModifiers(protectify(behavior.getModifiers())); + } + + // protectify all the inner classes + InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); + if (attr != null) { + for (int i = 0; i < attr.tableLength(); i++) { + attr.setAccessFlags(i, protectify(attr.accessFlags(i))); + } + } + + return c; + } + + private static int protectify(int flags) { + if (AccessFlag.isPrivate(flags)) { + flags = AccessFlag.setProtected(flags); + } + return flags; + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java b/src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java new file mode 100644 index 0000000..da86b2b --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * 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 javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtField; +import javassist.bytecode.AccessFlag; +import javassist.bytecode.InnerClassesAttribute; + + +public class ClassPublifier { + + public static CtClass publify(CtClass c) { + + // publify all the fields + for (CtField field : c.getDeclaredFields()) { + field.setModifiers(publify(field.getModifiers())); + } + + // publify all the methods and constructors + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + behavior.setModifiers(publify(behavior.getModifiers())); + } + + // publify all the inner classes + InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); + if (attr != null) { + for (int i = 0; i < attr.tableLength(); i++) { + attr.setAccessFlags(i, publify(attr.accessFlags(i))); + } + } + + return c; + } + + private static int publify(int flags) { + if (AccessFlag.isPrivate(flags) || AccessFlag.isProtected(flags)) { + flags = AccessFlag.setPublic(flags); + } + return flags; + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java b/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java new file mode 100644 index 0000000..548bea7 --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java @@ -0,0 +1,514 @@ +/******************************************************************************* + * 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 cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ClassNameReplacer; +import cuchaz.enigma.mapping.Translator; +import javassist.CtClass; +import javassist.bytecode.*; +import javassist.bytecode.SignatureAttribute.*; + +public class ClassRenamer { + + private 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, 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, 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, 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 < typeParamTypes.length; i++) { + TypeParameter newParamType = renameType(typeParamTypes[i], map); + if (newParamType != null) { + typeParamTypes[i] = newParamType; + } + } + } + + ClassType superclassType = type.getSuperClass(); + if (superclassType != ClassType.OBJECT) { + ClassType newSuperclassType = renameType(superclassType, map); + if (newSuperclassType != null) { + superclassType = newSuperclassType; + } + } + + ClassType[] interfaceTypes = type.getInterfaces(); + if (interfaceTypes != null) { + interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length); + for (int i = 0; i < interfaceTypes.length; i++) { + ClassType newInterfaceType = renameType(interfaceTypes[i], map); + if (newInterfaceType != null) { + interfaceTypes[i] = newInterfaceType; + } + } + } + + return new ClassSignature(typeParamTypes, superclassType, interfaceTypes); + } + + private static MethodSignature renameType(MethodSignature type, ReplacerClassMap map) { + + TypeParameter[] typeParamTypes = type.getTypeParameters(); + if (typeParamTypes != null) { + typeParamTypes = Arrays.copyOf(typeParamTypes, typeParamTypes.length); + for (int i = 0; i < typeParamTypes.length; i++) { + TypeParameter newParamType = renameType(typeParamTypes[i], map); + if (newParamType != null) { + typeParamTypes[i] = newParamType; + } + } + } + + Type[] paramTypes = type.getParameterTypes(); + if (paramTypes != null) { + paramTypes = Arrays.copyOf(paramTypes, paramTypes.length); + for (int i = 0; i < paramTypes.length; i++) { + Type newParamType = renameType(paramTypes[i], map); + if (newParamType != null) { + paramTypes[i] = newParamType; + } + } + } + + Type returnType = type.getReturnType(); + if (returnType != null) { + Type newReturnType = renameType(returnType, map); + if (newReturnType != null) { + returnType = newReturnType; + } + } + + ObjectType[] exceptionTypes = type.getExceptionTypes(); + if (exceptionTypes != null) { + exceptionTypes = Arrays.copyOf(exceptionTypes, exceptionTypes.length); + for (int i = 0; i < exceptionTypes.length; i++) { + ObjectType newExceptionType = renameType(exceptionTypes[i], map); + if (newExceptionType != null) { + exceptionTypes[i] = newExceptionType; + } + } + } + + return new MethodSignature(typeParamTypes, paramTypes, returnType, exceptionTypes); + } + + private static Type renameType(Type type, ReplacerClassMap map) { + if (type instanceof ObjectType) { + return renameType((ObjectType) type, map); + } else if (type instanceof BaseType) { + return renameType((BaseType) type, map); + } else { + throw new Error("Don't know how to rename type " + type.getClass()); + } + } + + private static ObjectType renameType(ObjectType type, ReplacerClassMap map) { + if (type instanceof ArrayType) { + return renameType((ArrayType) type, map); + } else if (type instanceof ClassType) { + return renameType((ClassType) type, map); + } else if (type instanceof TypeVariable) { + return renameType((TypeVariable) type, map); + } else { + throw new Error("Don't know how to rename type " + type.getClass()); + } + } + + private static BaseType renameType(BaseType type, ReplacerClassMap map) { + // don't have to rename primitives + return null; + } + + private static TypeVariable renameType(TypeVariable type, ReplacerClassMap map) { + // don't have to rename template args + return null; + } + + private static ClassType renameType(ClassType type, ReplacerClassMap map) { + + // translate type args + TypeArgument[] args = type.getTypeArguments(); + if (args != null) { + args = Arrays.copyOf(args, args.length); + for (int i = 0; i < args.length; i++) { + TypeArgument newType = renameType(args[i], map); + if (newType != null) { + args[i] = newType; + } + } + } + + if (type instanceof NestedClassType) { + NestedClassType nestedType = (NestedClassType) type; + + // translate the name + String name = getClassName(type); + String newName = map.get(name); + if (newName != null) { + name = new ClassEntry(newName).getInnermostClassName(); + } + + // translate the parent class too + ClassType parent = renameType(nestedType.getDeclaringClass(), map); + if (parent == null) { + parent = nestedType.getDeclaringClass(); + } + + return new NestedClassType(parent, name, args); + } else { + + // translate the name + String name = type.getName(); + String newName = renameClassName(name, map); + if (newName != null) { + name = newName; + } + + return new ClassType(name, args); + } + } + + private static String getClassName(ClassType type) { + if (type instanceof NestedClassType) { + NestedClassType nestedType = (NestedClassType) type; + return getClassName(nestedType.getDeclaringClass()) + "$" + Descriptor.toJvmName(type.getName().replace('.', '$')); + } else { + return Descriptor.toJvmName(type.getName()); + } + } + + private static String renameClassName(String name, ReplacerClassMap map) { + String newName = map.get(Descriptor.toJvmName(name)); + if (newName != null) { + return Descriptor.toJavaName(newName); + } + return null; + } + + private static TypeArgument renameType(TypeArgument type, ReplacerClassMap map) { + ObjectType subType = type.getType(); + if (subType != null) { + ObjectType newSubType = renameType(subType, map); + if (newSubType != null) { + switch (type.getKind()) { + case ' ': + return new TypeArgument(newSubType); + case '+': + return TypeArgument.subclassOf(newSubType); + case '-': + return TypeArgument.superOf(newSubType); + default: + throw new Error("Unknown type kind: " + type.getKind()); + } + } + } + return null; + } + + private static ArrayType renameType(ArrayType type, ReplacerClassMap map) { + Type newSubType = renameType(type.getComponentType(), map); + if (newSubType != null) { + return new ArrayType(type.getDimension(), newSubType); + } + return null; + } + + private static TypeParameter renameType(TypeParameter type, ReplacerClassMap map) { + + ObjectType superclassType = type.getClassBound(); + if (superclassType != null) { + ObjectType newSuperclassType = renameType(superclassType, map); + if (newSuperclassType != null) { + superclassType = newSuperclassType; + } + } + + ObjectType[] interfaceTypes = type.getInterfaceBound(); + if (interfaceTypes != null) { + interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length); + for (int i = 0; i < interfaceTypes.length; i++) { + ObjectType newInterfaceType = renameType(interfaceTypes[i], map); + if (newInterfaceType != null) { + interfaceTypes[i] = newInterfaceType; + } + } + } + + return new TypeParameter(type.getName(), superclassType, interfaceTypes); + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java b/src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java new file mode 100644 index 0000000..ef197cb --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * 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 cuchaz.enigma.mapping.*; +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtField; +import javassist.CtMethod; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; +import javassist.bytecode.EnclosingMethodAttribute; +import javassist.bytecode.SourceFileAttribute; + +public class ClassTranslator { + + private Translator m_translator; + + public ClassTranslator(Translator translator) { + m_translator = translator; + } + + public void translate(CtClass c) { + + // NOTE: the order of these translations is very important + + // translate all the field and method references in the code by editing the constant pool + ConstPool constants = c.getClassFile().getConstPool(); + ConstPoolEditor editor = new ConstPoolEditor(constants); + for (int i = 1; i < constants.getSize(); i++) { + switch (constants.getTag(i)) { + + case ConstPool.CONST_Fieldref: { + + // translate the name and type + FieldEntry entry = EntryFactory.getFieldEntry( + Descriptor.toJvmName(constants.getFieldrefClassName(i)), + constants.getFieldrefName(i), + constants.getFieldrefType(i) + ); + FieldEntry translatedEntry = m_translator.translateEntry(entry); + if (!entry.equals(translatedEntry)) { + editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getType().toString()); + } + } + break; + + case ConstPool.CONST_Methodref: + case ConstPool.CONST_InterfaceMethodref: { + + // translate the name and type (ie signature) + BehaviorEntry entry = EntryFactory.getBehaviorEntry( + Descriptor.toJvmName(editor.getMemberrefClassname(i)), + editor.getMemberrefName(i), + editor.getMemberrefType(i) + ); + BehaviorEntry translatedEntry = m_translator.translateEntry(entry); + if (!entry.equals(translatedEntry)) { + editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getSignature().toString()); + } + } + break; + } + } + + ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); + + // translate all the fields + for (CtField field : c.getDeclaredFields()) { + + // translate the name + FieldEntry entry = EntryFactory.getFieldEntry(field); + String translatedName = m_translator.translate(entry); + if (translatedName != null) { + field.setName(translatedName); + } + + // translate the type + Type translatedType = m_translator.translateType(entry.getType()); + field.getFieldInfo().setDescriptor(translatedType.toString()); + } + + // translate all the methods and constructors + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + + BehaviorEntry entry = EntryFactory.getBehaviorEntry(behavior); + + if (behavior instanceof CtMethod) { + CtMethod method = (CtMethod) behavior; + + // translate the name + String translatedName = m_translator.translate(entry); + if (translatedName != null) { + method.setName(translatedName); + } + } + + if (entry.getSignature() != null) { + // translate the signature + Signature translatedSignature = m_translator.translateSignature(entry.getSignature()); + behavior.getMethodInfo().setDescriptor(translatedSignature.toString()); + } + } + + // translate the EnclosingMethod attribute + EnclosingMethodAttribute enclosingMethodAttr = (EnclosingMethodAttribute) c.getClassFile().getAttribute(EnclosingMethodAttribute.tag); + if (enclosingMethodAttr != null) { + + if (enclosingMethodAttr.methodIndex() == 0) { + BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(Descriptor.toJvmName(enclosingMethodAttr.className())); + BehaviorEntry deobfBehaviorEntry = m_translator.translateEntry(obfBehaviorEntry); + c.getClassFile().addAttribute(new EnclosingMethodAttribute( + constants, + deobfBehaviorEntry.getClassName() + )); + } else { + BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry( + Descriptor.toJvmName(enclosingMethodAttr.className()), + enclosingMethodAttr.methodName(), + enclosingMethodAttr.methodDescriptor() + ); + BehaviorEntry deobfBehaviorEntry = m_translator.translateEntry(obfBehaviorEntry); + c.getClassFile().addAttribute(new EnclosingMethodAttribute( + constants, + deobfBehaviorEntry.getClassName(), + deobfBehaviorEntry.getName(), + deobfBehaviorEntry.getSignature().toString() + )); + } + } + + // translate all the class names referenced in the code + // the above code only changed method/field/reference names and types, but not the rest of the class references + ClassRenamer.renameClasses(c, m_translator); + + // translate the source file attribute too + ClassEntry deobfClassEntry = m_translator.translateEntry(classEntry); + if (deobfClassEntry != null) { + String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOutermostClassEntry().getSimpleName()) + ".java"; + c.getClassFile().addAttribute(new SourceFileAttribute(constants, sourceFile)); + } + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java b/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java new file mode 100644 index 0000000..0082a72 --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java @@ -0,0 +1,263 @@ +/******************************************************************************* + * 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.io.DataInputStream; +import java.io.DataOutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashMap; + +import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor; +import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; +import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; + +public class ConstPoolEditor { + + private static Method m_getItem; + private static Method m_addItem; + private static Method m_addItem0; + private static Field m_items; + private static Field m_cache; + private static Field m_numItems; + private static Field m_objects; + private static Field m_elements; + private static Method m_methodWritePool; + private static Constructor m_constructorPool; + + static { + try { + m_getItem = ConstPool.class.getDeclaredMethod("getItem", int.class); + m_getItem.setAccessible(true); + + m_addItem = ConstPool.class.getDeclaredMethod("addItem", Class.forName("javassist.bytecode.ConstInfo")); + m_addItem.setAccessible(true); + + m_addItem0 = ConstPool.class.getDeclaredMethod("addItem0", Class.forName("javassist.bytecode.ConstInfo")); + m_addItem0.setAccessible(true); + + m_items = ConstPool.class.getDeclaredField("items"); + m_items.setAccessible(true); + + m_cache = ConstPool.class.getDeclaredField("itemsCache"); + m_cache.setAccessible(true); + + m_numItems = ConstPool.class.getDeclaredField("numOfItems"); + m_numItems.setAccessible(true); + + m_objects = Class.forName("javassist.bytecode.LongVector").getDeclaredField("objects"); + m_objects.setAccessible(true); + + m_elements = Class.forName("javassist.bytecode.LongVector").getDeclaredField("elements"); + m_elements.setAccessible(true); + + m_methodWritePool = ConstPool.class.getDeclaredMethod("write", DataOutputStream.class); + m_methodWritePool.setAccessible(true); + + m_constructorPool = ConstPool.class.getDeclaredConstructor(DataInputStream.class); + m_constructorPool.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + private ConstPool m_pool; + + public ConstPoolEditor(ConstPool pool) { + m_pool = pool; + } + + public void writePool(DataOutputStream out) { + try { + m_methodWritePool.invoke(m_pool, out); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static ConstPool readPool(DataInputStream in) { + try { + return m_constructorPool.newInstance(in); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public String getMemberrefClassname(int memberrefIndex) { + return Descriptor.toJvmName(m_pool.getClassInfo(m_pool.getMemberClass(memberrefIndex))); + } + + public String getMemberrefName(int memberrefIndex) { + return m_pool.getUtf8Info(m_pool.getNameAndTypeName(m_pool.getMemberNameAndType(memberrefIndex))); + } + + public String getMemberrefType(int memberrefIndex) { + return m_pool.getUtf8Info(m_pool.getNameAndTypeDescriptor(m_pool.getMemberNameAndType(memberrefIndex))); + } + + public ConstInfoAccessor getItem(int index) { + try { + Object entry = m_getItem.invoke(m_pool, index); + if (entry == null) { + return null; + } + return new ConstInfoAccessor(entry); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int addItem(Object item) { + try { + return (Integer) m_addItem.invoke(m_pool, item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int addItemForceNew(Object item) { + try { + return (Integer) m_addItem0.invoke(m_pool, item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + @SuppressWarnings("rawtypes") + public void removeLastItem() { + try { + // remove the item from the cache + HashMap cache = getCache(); + if (cache != null) { + Object item = getItem(m_pool.getSize() - 1); + cache.remove(item); + } + + // remove the actual item + // based off of LongVector.addElement() + Object items = m_items.get(m_pool); + Object[][] objects = (Object[][]) m_objects.get(items); + int numElements = (Integer) m_elements.get(items) - 1; + int nth = numElements >> 7; + int offset = numElements & (128 - 1); + objects[nth][offset] = null; + + // decrement the number of items + m_elements.set(items, numElements); + m_numItems.set(m_pool, (Integer) m_numItems.get(m_pool) - 1); + } catch (Exception ex) { + throw new Error(ex); + } + } + + @SuppressWarnings("rawtypes") + public HashMap getCache() { + try { + return (HashMap) m_cache.get(m_pool); + } catch (Exception ex) { + throw new Error(ex); + } + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public void changeMemberrefNameAndType(int memberrefIndex, String newName, String newType) { + // NOTE: when changing values, we always need to copy-on-write + try { + // get the memberref item + Object item = getItem(memberrefIndex).getItem(); + + // update the cache + HashMap cache = getCache(); + if (cache != null) { + cache.remove(item); + } + + new MemberRefInfoAccessor(item).setNameAndTypeIndex(m_pool.addNameAndTypeInfo(newName, newType)); + + // update the cache + if (cache != null) { + cache.put(item, item); + } + } catch (Exception ex) { + throw new Error(ex); + } + + // make sure the change worked + assert (newName.equals(getMemberrefName(memberrefIndex))); + assert (newType.equals(getMemberrefType(memberrefIndex))); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public void changeClassName(int classNameIndex, String newName) { + // NOTE: when changing values, we always need to copy-on-write + try { + // get the class item + Object item = getItem(classNameIndex).getItem(); + + // update the cache + HashMap cache = getCache(); + if (cache != null) { + cache.remove(item); + } + + // add the new name and repoint the name-and-type to it + new ClassInfoAccessor(item).setNameIndex(m_pool.addUtf8Info(newName)); + + // update the cache + if (cache != null) { + cache.put(item, item); + } + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static ConstPool newConstPool() { + // const pool expects the name of a class to initialize itself + // but we want an empty pool + // so give it a bogus name, and then clear the entries afterwards + ConstPool pool = new ConstPool("a"); + + ConstPoolEditor editor = new ConstPoolEditor(pool); + int size = pool.getSize(); + for (int i = 0; i < size - 1; i++) { + editor.removeLastItem(); + } + + // make sure the pool is actually empty + // although, in this case "empty" means one thing in it + // the JVM spec says index 0 should be reserved + assert (pool.getSize() == 1); + assert (editor.getItem(0) == null); + assert (editor.getItem(1) == null); + assert (editor.getItem(2) == null); + assert (editor.getItem(3) == null); + + // also, clear the cache + editor.getCache().clear(); + + return pool; + } + + public String dump() { + StringBuilder buf = new StringBuilder(); + for (int i = 1; i < m_pool.getSize(); i++) { + buf.append(String.format("%4d", i)); + buf.append(" "); + buf.append(getItem(i).toString()); + buf.append("\n"); + } + return buf.toString(); + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/InfoType.java b/src/main/java/cuchaz/enigma/bytecode/InfoType.java new file mode 100644 index 0000000..89940d9 --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/InfoType.java @@ -0,0 +1,301 @@ +/******************************************************************************* + * 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 com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import cuchaz.enigma.bytecode.accessors.*; + +public enum InfoType { + + Utf8Info(1, 0), + IntegerInfo(3, 0), + FloatInfo(4, 0), + LongInfo(5, 0), + DoubleInfo(6, 0), + ClassInfo(7, 1) { + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getNameIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); + accessor.setNameIndex(remapIndex(map, accessor.getNameIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); + ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex()); + return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag(); + } + }, + StringInfo(8, 1) { + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getStringIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); + accessor.setStringIndex(remapIndex(map, accessor.getStringIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); + ConstInfoAccessor stringEntry = pool.getItem(accessor.getStringIndex()); + return stringEntry != null && stringEntry.getTag() == Utf8Info.getTag(); + } + }, + FieldRefInfo(9, 2) { + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getClassIndex()); + gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); + accessor.setClassIndex(remapIndex(map, accessor.getClassIndex())); + accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); + ConstInfoAccessor classEntry = pool.getItem(accessor.getClassIndex()); + ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex()); + return classEntry != null && classEntry.getTag() == ClassInfo.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag(); + } + }, + // same as FieldRefInfo + MethodRefInfo(10, 2) { + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + FieldRefInfo.gatherIndexTree(indices, editor, entry); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + FieldRefInfo.remapIndices(map, entry); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + return FieldRefInfo.subIndicesAreValid(entry, pool); + } + }, + // same as FieldRefInfo + InterfaceMethodRefInfo(11, 2) { + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + FieldRefInfo.gatherIndexTree(indices, editor, entry); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + FieldRefInfo.remapIndices(map, entry); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + return FieldRefInfo.subIndicesAreValid(entry, pool); + } + }, + NameAndTypeInfo(12, 1) { + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getNameIndex()); + gatherIndexTree(indices, editor, accessor.getTypeIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); + accessor.setNameIndex(remapIndex(map, accessor.getNameIndex())); + accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); + ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex()); + ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); + return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag() && typeEntry != null && typeEntry.getTag() == Utf8Info.getTag(); + } + }, + MethodHandleInfo(15, 3) { + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getTypeIndex()); + gatherIndexTree(indices, editor, accessor.getMethodRefIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); + accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); + accessor.setMethodRefIndex(remapIndex(map, accessor.getMethodRefIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); + ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); + ConstInfoAccessor methodRefEntry = pool.getItem(accessor.getMethodRefIndex()); + return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag() && methodRefEntry != null && methodRefEntry.getTag() == MethodRefInfo.getTag(); + } + }, + MethodTypeInfo(16, 1) { + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getTypeIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); + accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); + ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); + return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag(); + } + }, + InvokeDynamicInfo(18, 2) { + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getBootstrapIndex()); + gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); + accessor.setBootstrapIndex(remapIndex(map, accessor.getBootstrapIndex())); + accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); + ConstInfoAccessor bootstrapEntry = pool.getItem(accessor.getBootstrapIndex()); + ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex()); + return bootstrapEntry != null && bootstrapEntry.getTag() == Utf8Info.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag(); + } + }; + + private static Map m_types; + + static { + m_types = Maps.newTreeMap(); + for (InfoType type : values()) { + m_types.put(type.getTag(), type); + } + } + + private int m_tag; + private int m_level; + + InfoType(int tag, int level) { + m_tag = tag; + m_level = level; + } + + public int getTag() { + return m_tag; + } + + public int getLevel() { + return m_level; + } + + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + // by default, do nothing + } + + public void remapIndices(Map map, ConstInfoAccessor entry) { + // by default, do nothing + } + + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + // by default, everything is good + return true; + } + + public boolean selfIndexIsValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + ConstInfoAccessor entryCheck = pool.getItem(entry.getIndex()); + if (entryCheck == null) { + return false; + } + return entryCheck.getItem().equals(entry.getItem()); + } + + public static InfoType getByTag(int tag) { + return m_types.get(tag); + } + + public static List getByLevel(int level) { + List types = Lists.newArrayList(); + for (InfoType type : values()) { + if (type.getLevel() == level) { + types.add(type); + } + } + return types; + } + + public static List getSortedByLevel() { + List types = Lists.newArrayList(); + types.addAll(getByLevel(0)); + types.addAll(getByLevel(1)); + types.addAll(getByLevel(2)); + types.addAll(getByLevel(3)); + return types; + } + + public static void gatherIndexTree(Collection indices, ConstPoolEditor editor, int index) { + // add own index + indices.add(index); + + // recurse + ConstInfoAccessor entry = editor.getItem(index); + entry.getType().gatherIndexTree(indices, editor, entry); + } + + private static int remapIndex(Map map, int index) { + Integer newIndex = map.get(index); + if (newIndex == null) { + newIndex = index; + } + return newIndex; + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java new file mode 100644 index 0000000..25ac7d6 --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * 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 com.google.common.collect.Lists; + +import java.util.Collection; +import java.util.List; + +import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.EntryFactory; +import javassist.CtClass; +import javassist.bytecode.AccessFlag; +import javassist.bytecode.ConstPool; +import javassist.bytecode.EnclosingMethodAttribute; +import javassist.bytecode.InnerClassesAttribute; + +public class InnerClassWriter { + + private JarIndex m_index; + + public InnerClassWriter(JarIndex index) { + m_index = index; + } + + public void write(CtClass c) { + + // don't change anything if there's already an attribute there + InnerClassesAttribute oldAttr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); + if (oldAttr != null) { + // bail! + return; + } + + ClassEntry obfClassEntry = EntryFactory.getClassEntry(c); + List obfClassChain = m_index.getObfClassChain(obfClassEntry); + + boolean isInnerClass = obfClassChain.size() > 1; + if (isInnerClass) { + + // it's an inner class, rename it to the fully qualified name + c.setName(obfClassEntry.buildClassEntry(obfClassChain).getName()); + + BehaviorEntry caller = m_index.getAnonymousClassCaller(obfClassEntry); + if (caller != null) { + + // write the enclosing method attribute + if (caller.getName().equals("")) { + c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName())); + } else { + c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName(), caller.getName(), caller.getSignature().toString())); + } + } + } + + // does this class have any inner classes? + Collection obfInnerClassEntries = m_index.getInnerClasses(obfClassEntry); + + if (isInnerClass || !obfInnerClassEntries.isEmpty()) { + + // create an inner class attribute + InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool()); + c.getClassFile().addAttribute(attr); + + // write the ancestry, but not the outermost class + for (int i = 1; i < obfClassChain.size(); i++) { + ClassEntry obfInnerClassEntry = obfClassChain.get(i); + writeInnerClass(attr, obfClassChain, obfInnerClassEntry); + + // update references to use the fully qualified inner class name + c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(obfClassChain).getName()); + } + + // write the inner classes + for (ClassEntry obfInnerClassEntry : obfInnerClassEntries) { + + // extend the class chain + List extendedObfClassChain = Lists.newArrayList(obfClassChain); + extendedObfClassChain.add(obfInnerClassEntry); + + writeInnerClass(attr, extendedObfClassChain, obfInnerClassEntry); + + // update references to use the fully qualified inner class name + c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(extendedObfClassChain).getName()); + } + } + } + + private void writeInnerClass(InnerClassesAttribute attr, List obfClassChain, ClassEntry obfClassEntry) { + + // get the new inner class name + ClassEntry obfInnerClassEntry = obfClassEntry.buildClassEntry(obfClassChain); + ClassEntry obfOuterClassEntry = obfInnerClassEntry.getOuterClassEntry(); + + // here's what the JVM spec says about the InnerClasses attribute + // append(inner, parent, 0 if anonymous else simple name, flags); + + // update the attribute with this inner class + ConstPool constPool = attr.getConstPool(); + int innerClassIndex = constPool.addClassInfo(obfInnerClassEntry.getName()); + int parentClassIndex = constPool.addClassInfo(obfOuterClassEntry.getName()); + int innerClassNameIndex = 0; + int accessFlags = AccessFlag.PUBLIC; + // TODO: need to figure out if we can put static or not + if (!m_index.isAnonymousClass(obfClassEntry)) { + innerClassNameIndex = constPool.addUtf8Info(obfInnerClassEntry.getInnermostClassName()); + } + + attr.append(innerClassIndex, parentClassIndex, innerClassNameIndex, accessFlags); + + /* DEBUG + System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)", + obfClassEntry, + attr.innerClass(attr.tableLength() - 1), + attr.outerClass(attr.tableLength() - 1), + attr.innerName(attr.tableLength() - 1), + Constants.NonePackage + "/" + obfInnerClassName, + obfClassEntry.getName() + )); + */ + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/LocalVariableRenamer.java b/src/main/java/cuchaz/enigma/bytecode/LocalVariableRenamer.java new file mode 100644 index 0000000..d0ce107 --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/LocalVariableRenamer.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * 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 cuchaz.enigma.mapping.ArgumentEntry; +import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.EntryFactory; +import cuchaz.enigma.mapping.Translator; +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.bytecode.*; + + +public class LocalVariableRenamer { + + private Translator m_translator; + + public LocalVariableRenamer(Translator translator) { + m_translator = translator; + } + + public void rename(CtClass c) { + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + + // if there's a local variable table, just rename everything to v1, v2, v3, ... for now + CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute(); + if (codeAttribute == null) { + continue; + } + + BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); + ConstPool constants = c.getClassFile().getConstPool(); + + LocalVariableAttribute table = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag); + if (table != null) { + renameLVT(behaviorEntry, constants, table); + } + + LocalVariableTypeAttribute typeTable = (LocalVariableTypeAttribute) codeAttribute.getAttribute(LocalVariableAttribute.typeTag); + if (typeTable != null) { + renameLVTT(typeTable, table); + } + } + } + + // DEBUG + @SuppressWarnings("unused") + private void dumpTable(LocalVariableAttribute table) { + for (int i = 0; i < table.tableLength(); i++) { + System.out.println(String.format("\t%d (%d): %s %s", + i, table.index(i), table.variableName(i), table.descriptor(i) + )); + } + } + + private void renameLVT(BehaviorEntry behaviorEntry, ConstPool constants, LocalVariableAttribute table) { + + // skip empty tables + if (table.tableLength() <= 0) { + return; + } + + // where do we start counting variables? + int starti = 0; + if (table.variableName(0).equals("this")) { + // skip the "this" variable + starti = 1; + } + + // rename method arguments first + int numArgs = 0; + if (behaviorEntry.getSignature() != null) { + numArgs = behaviorEntry.getSignature().getArgumentTypes().size(); + for (int i = starti; i < starti + numArgs && i < table.tableLength(); i++) { + int argi = i - starti; + String argName = m_translator.translate(new ArgumentEntry(behaviorEntry, argi, "")); + if (argName == null) { + argName = "a" + (argi + 1); + } + renameVariable(table, i, constants.addUtf8Info(argName)); + } + } + + // then rename the rest of the args, if any + for (int i = starti + numArgs; i < table.tableLength(); i++) { + int firstIndex = table.index(starti + numArgs); + renameVariable(table, i, constants.addUtf8Info("v" + (table.index(i) - firstIndex + 1))); + } + } + + private void renameLVTT(LocalVariableTypeAttribute typeTable, LocalVariableAttribute table) { + // rename args to the same names as in the LVT + for (int i = 0; i < typeTable.tableLength(); i++) { + renameVariable(typeTable, i, getNameIndex(table, typeTable.index(i))); + } + } + + private void renameVariable(LocalVariableAttribute table, int i, int stringId) { + // based off of LocalVariableAttribute.nameIndex() + ByteArray.write16bit(stringId, table.get(), i * 10 + 6); + } + + private int getNameIndex(LocalVariableAttribute table, int index) { + for (int i = 0; i < table.tableLength(); i++) { + if (table.index(i) == index) { + return table.nameIndex(i); + } + } + return 0; + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/MethodParameterWriter.java b/src/main/java/cuchaz/enigma/bytecode/MethodParameterWriter.java new file mode 100644 index 0000000..e53e8e7 --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/MethodParameterWriter.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * 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.util.ArrayList; +import java.util.List; + +import cuchaz.enigma.mapping.*; +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.bytecode.CodeAttribute; +import javassist.bytecode.LocalVariableAttribute; + +public class MethodParameterWriter { + + private Translator m_translator; + + public MethodParameterWriter(Translator translator) { + m_translator = translator; + } + + public void writeMethodArguments(CtClass c) { + + // Procyon will read method arguments from the "MethodParameters" attribute, so write those + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + + // if there's a local variable table here, don't write a MethodParameters attribute + // let the local variable writer deal with it instead + // procyon starts doing really weird things if we give it both attributes + CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute(); + if (codeAttribute != null && codeAttribute.getAttribute(LocalVariableAttribute.tag) != null) { + continue; + } + + BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); + + // get the number of arguments + Signature signature = behaviorEntry.getSignature(); + if (signature == null) { + // static initializers have no signatures, or arguments + continue; + } + int numParams = signature.getArgumentTypes().size(); + if (numParams <= 0) { + continue; + } + + // get the list of argument names + List names = new ArrayList(numParams); + for (int i = 0; i < numParams; i++) { + names.add(m_translator.translate(new ArgumentEntry(behaviorEntry, i, ""))); + } + + // save the mappings to the class + MethodParametersAttribute.updateClass(behavior.getMethodInfo(), names); + } + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java b/src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java new file mode 100644 index 0000000..ee7ed87 --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * 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.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javassist.bytecode.AttributeInfo; +import javassist.bytecode.ConstPool; +import javassist.bytecode.MethodInfo; + +public class MethodParametersAttribute extends AttributeInfo { + + private MethodParametersAttribute(ConstPool pool, List parameterNameIndices) { + super(pool, "MethodParameters", writeStruct(parameterNameIndices)); + } + + public static void updateClass(MethodInfo info, List names) { + + // add the names to the class const pool + ConstPool constPool = info.getConstPool(); + List parameterNameIndices = new ArrayList(); + for (String name : names) { + if (name != null) { + parameterNameIndices.add(constPool.addUtf8Info(name)); + } else { + parameterNameIndices.add(0); + } + } + + // add the attribute to the method + info.addAttribute(new MethodParametersAttribute(constPool, parameterNameIndices)); + } + + private static byte[] writeStruct(List parameterNameIndices) { + // JVM 8 Spec says the struct looks like this: + // http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.24 + // uint8 num_params + // for each param: + // uint16 name_index -> points to UTF8 entry in constant pool, or 0 for no entry + // uint16 access_flags -> don't care, just set to 0 + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(buf); + + // NOTE: java hates unsigned integers, so we have to be careful here + // the writeShort(), writeByte() methods will read 16,8 low-order bits from the int argument + // as long as the int argument is in range of the unsigned short/byte type, it will be written as an unsigned short/byte + // if the int is out of range, the byte stream won't look the way we want and weird things will happen + final int SIZEOF_UINT8 = 1; + final int SIZEOF_UINT16 = 2; + final int MAX_UINT8 = (1 << 8) - 1; + final int MAX_UINT16 = (1 << 16) - 1; + + try { + assert (parameterNameIndices.size() >= 0 && parameterNameIndices.size() <= MAX_UINT8); + out.writeByte(parameterNameIndices.size()); + + for (Integer index : parameterNameIndices) { + assert (index >= 0 && index <= MAX_UINT16); + out.writeShort(index); + + // just write 0 for the access flags + out.writeShort(0); + } + + out.close(); + byte[] data = buf.toByteArray(); + assert (data.length == SIZEOF_UINT8 + parameterNameIndices.size() * (SIZEOF_UINT16 + SIZEOF_UINT16)); + return data; + } catch (IOException ex) { + throw new Error(ex); + } + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java new file mode 100644 index 0000000..fd987f5 --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * 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.accessors; + +import java.lang.reflect.Field; + +public class ClassInfoAccessor { + + private static Class m_class; + private static Field m_nameIndex; + + static { + try { + m_class = Class.forName("javassist.bytecode.ClassInfo"); + m_nameIndex = m_class.getDeclaredField("name"); + m_nameIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } + + private Object m_item; + + public ClassInfoAccessor(Object item) { + m_item = item; + } + + public int getNameIndex() { + try { + return (Integer) m_nameIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setNameIndex(int val) { + try { + m_nameIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java new file mode 100644 index 0000000..2692c06 --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * 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.accessors; + +import java.io.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import cuchaz.enigma.bytecode.InfoType; + +public class ConstInfoAccessor { + + private static Class m_class; + private static Field m_index; + private static Method m_getTag; + + static { + try { + m_class = Class.forName("javassist.bytecode.ConstInfo"); + m_index = m_class.getDeclaredField("index"); + m_index.setAccessible(true); + m_getTag = m_class.getMethod("getTag"); + m_getTag.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + private Object m_item; + + public ConstInfoAccessor(Object item) { + if (item == null) { + throw new IllegalArgumentException("item cannot be null!"); + } + m_item = item; + } + + public ConstInfoAccessor(DataInputStream in) throws IOException { + try { + // read the entry + String className = in.readUTF(); + int oldIndex = in.readInt(); + + // NOTE: ConstInfo instances write a type id (a "tag"), but they don't read it back + // so we have to read it here + in.readByte(); + + Constructor constructor = Class.forName(className).getConstructor(DataInputStream.class, int.class); + constructor.setAccessible(true); + m_item = constructor.newInstance(in, oldIndex); + } catch (IOException ex) { + throw ex; + } catch (Exception ex) { + throw new Error(ex); + } + } + + public Object getItem() { + return m_item; + } + + public int getIndex() { + try { + return (Integer) m_index.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setIndex(int val) { + try { + m_index.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int getTag() { + try { + return (Integer) m_getTag.invoke(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public ConstInfoAccessor copy() { + return new ConstInfoAccessor(copyItem()); + } + + public Object copyItem() { + // I don't know of a simpler way to copy one of these silly things... + try { + // serialize the item + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(buf); + write(out); + + // deserialize the item + DataInputStream in = new DataInputStream(new ByteArrayInputStream(buf.toByteArray())); + Object item = new ConstInfoAccessor(in).getItem(); + in.close(); + + return item; + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void write(DataOutputStream out) throws IOException { + try { + out.writeUTF(m_item.getClass().getName()); + out.writeInt(getIndex()); + + Method method = m_item.getClass().getMethod("write", DataOutputStream.class); + method.setAccessible(true); + method.invoke(m_item, out); + } catch (IOException ex) { + throw ex; + } catch (Exception ex) { + throw new Error(ex); + } + } + + @Override + public String toString() { + try { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + PrintWriter out = new PrintWriter(buf); + Method print = m_item.getClass().getMethod("print", PrintWriter.class); + print.setAccessible(true); + print.invoke(m_item, out); + out.close(); + return buf.toString().replace("\n", ""); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public InfoType getType() { + return InfoType.getByTag(getTag()); + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java new file mode 100644 index 0000000..0ca82b7 --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * 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.accessors; + +import java.lang.reflect.Field; + +public class InvokeDynamicInfoAccessor { + + private static Class m_class; + private static Field m_bootstrapIndex; + private static Field m_nameAndTypeIndex; + + static { + try { + m_class = Class.forName("javassist.bytecode.InvokeDynamicInfo"); + m_bootstrapIndex = m_class.getDeclaredField("bootstrap"); + m_bootstrapIndex.setAccessible(true); + m_nameAndTypeIndex = m_class.getDeclaredField("nameAndType"); + m_nameAndTypeIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } + + private Object m_item; + + public InvokeDynamicInfoAccessor(Object item) { + m_item = item; + } + + public int getBootstrapIndex() { + try { + return (Integer) m_bootstrapIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setBootstrapIndex(int val) { + try { + m_bootstrapIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int getNameAndTypeIndex() { + try { + return (Integer) m_nameAndTypeIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setNameAndTypeIndex(int val) { + try { + m_nameAndTypeIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java new file mode 100644 index 0000000..bb9d16b --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * 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.accessors; + +import java.lang.reflect.Field; + +public class MemberRefInfoAccessor { + + private static Class m_class; + private static Field m_classIndex; + private static Field m_nameAndTypeIndex; + + static { + try { + m_class = Class.forName("javassist.bytecode.MemberrefInfo"); + m_classIndex = m_class.getDeclaredField("classIndex"); + m_classIndex.setAccessible(true); + m_nameAndTypeIndex = m_class.getDeclaredField("nameAndTypeIndex"); + m_nameAndTypeIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } + + private Object m_item; + + public MemberRefInfoAccessor(Object item) { + m_item = item; + } + + public int getClassIndex() { + try { + return (Integer) m_classIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setClassIndex(int val) { + try { + m_classIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int getNameAndTypeIndex() { + try { + return (Integer) m_nameAndTypeIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setNameAndTypeIndex(int val) { + try { + m_nameAndTypeIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java new file mode 100644 index 0000000..88e42f4 --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * 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.accessors; + +import java.lang.reflect.Field; + +public class MethodHandleInfoAccessor { + + private static Class m_class; + private static Field m_kindIndex; + private static Field m_indexIndex; + + static { + try { + m_class = Class.forName("javassist.bytecode.MethodHandleInfo"); + m_kindIndex = m_class.getDeclaredField("refKind"); + m_kindIndex.setAccessible(true); + m_indexIndex = m_class.getDeclaredField("refIndex"); + m_indexIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } + + private Object m_item; + + public MethodHandleInfoAccessor(Object item) { + m_item = item; + } + + public int getTypeIndex() { + try { + return (Integer) m_kindIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setTypeIndex(int val) { + try { + m_kindIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int getMethodRefIndex() { + try { + return (Integer) m_indexIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setMethodRefIndex(int val) { + try { + m_indexIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java new file mode 100644 index 0000000..1d039f6 --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * 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.accessors; + +import java.lang.reflect.Field; + +public class MethodTypeInfoAccessor { + + private static Class m_class; + private static Field m_descriptorIndex; + + static { + try { + m_class = Class.forName("javassist.bytecode.MethodTypeInfo"); + m_descriptorIndex = m_class.getDeclaredField("descriptor"); + m_descriptorIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } + + private Object m_item; + + public MethodTypeInfoAccessor(Object item) { + m_item = item; + } + + public int getTypeIndex() { + try { + return (Integer) m_descriptorIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setTypeIndex(int val) { + try { + m_descriptorIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java new file mode 100644 index 0000000..acba779 --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * 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.accessors; + +import java.lang.reflect.Field; + +public class NameAndTypeInfoAccessor { + + private static Class m_class; + private static Field m_nameIndex; + private static Field m_typeIndex; + + static { + try { + m_class = Class.forName("javassist.bytecode.NameAndTypeInfo"); + m_nameIndex = m_class.getDeclaredField("memberName"); + m_nameIndex.setAccessible(true); + m_typeIndex = m_class.getDeclaredField("typeDescriptor"); + m_typeIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } + + private Object m_item; + + public NameAndTypeInfoAccessor(Object item) { + m_item = item; + } + + public int getNameIndex() { + try { + return (Integer) m_nameIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setNameIndex(int val) { + try { + m_nameIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int getTypeIndex() { + try { + return (Integer) m_typeIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setTypeIndex(int val) { + try { + m_typeIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java new file mode 100644 index 0000000..b40e0eb --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * 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.accessors; + +import java.lang.reflect.Field; + +public class StringInfoAccessor { + + private static Class m_class; + private static Field m_stringIndex; + + static { + try { + m_class = Class.forName("javassist.bytecode.StringInfo"); + m_stringIndex = m_class.getDeclaredField("string"); + m_stringIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } + + private Object m_item; + + public StringInfoAccessor(Object item) { + m_item = item; + } + + public int getStringIndex() { + try { + return (Integer) m_stringIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setStringIndex(int val) { + try { + m_stringIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java new file mode 100644 index 0000000..9303b41 --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * 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.accessors; + +public class Utf8InfoAccessor { + + private static Class m_class; + + static { + try { + m_class = Class.forName("javassist.bytecode.Utf8Info"); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } +} -- cgit v1.2.3