/******************************************************************************* * 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.mapping; import com.google.common.collect.Lists; import cuchaz.enigma.analysis.JarIndex; import cuchaz.enigma.throwables.IllegalNameException; import cuchaz.enigma.throwables.MappingConflict; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.util.List; import java.util.Set; import java.util.zip.GZIPOutputStream; public class MappingsRenamer { private final JarIndex index; private final ReferencedEntryPool entryPool; private Mappings mappings; public MappingsRenamer(JarIndex index, Mappings mappings, ReferencedEntryPool entryPool) { this.index = index; this.mappings = mappings; this.entryPool = entryPool; } public void setMappings(Mappings mappings) { this.mappings = mappings; } public void setClassName(ClassEntry obf, String deobfName) { deobfName = NameValidator.validateClassName(deobfName, !obf.isInnerClass()); List mappingChain = getOrCreateClassMappingChain(obf); if (mappingChain.size() == 1) { if (deobfName != null) { // make sure we don't rename to an existing obf or deobf class if (mappings.containsDeobfClass(deobfName) || index.containsObfClass(entryPool.getClass(deobfName))) { throw new IllegalNameException(deobfName, "There is already a class with that name"); } } ClassMapping classMapping = mappingChain.get(0); mappings.setClassDeobfName(classMapping, deobfName); } else { ClassMapping outerClassMapping = mappingChain.get(mappingChain.size() - 2); if (deobfName != null) { // make sure we don't rename to an existing obf or deobf inner class if (outerClassMapping.hasInnerClassByDeobf(deobfName) || outerClassMapping.hasInnerClassByObfSimple(deobfName)) { throw new IllegalNameException(deobfName, "There is already a class with that name"); } } outerClassMapping.setInnerClassName(obf, deobfName); } } public void removeClassMapping(ClassEntry obf) { setClassName(obf, null); } public void markClassAsDeobfuscated(ClassEntry obf) { String deobfName = obf.isInnerClass() ? obf.getInnermostClassName() : obf.getName(); List mappingChain = getOrCreateClassMappingChain(obf); if (mappingChain.size() == 1) { ClassMapping classMapping = mappingChain.get(0); mappings.setClassDeobfName(classMapping, deobfName); } else { ClassMapping outerClassMapping = mappingChain.get(mappingChain.size() - 2); outerClassMapping.setInnerClassName(obf, deobfName); } } public void setFieldName(FieldEntry obf, String deobfName) { deobfName = NameValidator.validateFieldName(deobfName); FieldEntry targetEntry = entryPool.getField(obf.getOwnerClassEntry(), deobfName, obf.getDesc()); ClassEntry definedClass = null; if (mappings.containsDeobfField(obf.getOwnerClassEntry(), deobfName) || index.containsEntryWithSameName(targetEntry)) definedClass = obf.getOwnerClassEntry(); else { for (ClassEntry ancestorEntry : this.index.getTranslationIndex().getAncestry(obf.getOwnerClassEntry())) { if (mappings.containsDeobfField(ancestorEntry, deobfName) || index.containsEntryWithSameName(targetEntry.updateOwnership(ancestorEntry))) { definedClass = ancestorEntry; break; } } } if (definedClass != null) { Translator translator = mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index.getTranslationIndex()); String className = translator.getTranslatedClass(entryPool.getClass(definedClass.getClassName())).getName(); if (className == null) className = definedClass.getClassName(); throw new IllegalNameException(deobfName, "There is already a field with that name in " + className); } ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); classMapping.setFieldName(obf.getName(), obf.getDesc(), deobfName); } public void removeFieldMapping(FieldEntry obf) { ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); classMapping.removeFieldMapping(classMapping.getFieldByObf(obf.getName(), obf.getDesc())); } public void markFieldAsDeobfuscated(FieldEntry obf) { ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); classMapping.setFieldName(obf.getName(), obf.getDesc(), obf.getName()); } private void validateMethodTreeName(MethodEntry entry, String deobfName) { MethodEntry targetEntry = entryPool.getMethod(entry.getOwnerClassEntry(), deobfName, entry.getDesc()); // TODO: Verify if I don't break things ClassMapping classMapping = mappings.getClassByObf(entry.getOwnerClassEntry()); if ((classMapping != null && classMapping.containsDeobfMethod(deobfName, entry.getDesc()) && classMapping.getMethodByObf(entry.getName(), entry.getDesc()) != classMapping.getMethodByDeobf(deobfName, entry.getDesc())) || index.containsObfMethod(targetEntry)) { Translator translator = mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index.getTranslationIndex()); String deobfClassName = translator.getTranslatedClass(entryPool.getClass(entry.getClassName())).getClassName(); if (deobfClassName == null) { deobfClassName = entry.getClassName(); } throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName); } for (ClassEntry child : index.getTranslationIndex().getSubclass(entry.getOwnerClassEntry())) { validateMethodTreeName(entry.updateOwnership(child), deobfName); } } public void setMethodTreeName(MethodEntry obf, String deobfName) { Set implementations = index.getRelatedMethodImplementations(obf); deobfName = NameValidator.validateMethodName(deobfName); for (MethodEntry entry : implementations) { validateMethodTreeName(entry, deobfName); } for (MethodEntry entry : implementations) { setMethodName(entry, deobfName); } } public void setMethodName(MethodEntry obf, String deobfName) { deobfName = NameValidator.validateMethodName(deobfName); MethodEntry targetEntry = entryPool.getMethod(obf.getOwnerClassEntry(), deobfName, obf.getDesc()); ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); // TODO: Verify if I don't break things if ((mappings.containsDeobfMethod(obf.getOwnerClassEntry(), deobfName, obf.getDesc()) && classMapping.getMethodByObf(obf.getName(), obf.getDesc()) != classMapping.getMethodByDeobf(deobfName, obf.getDesc())) || index.containsObfMethod(targetEntry)) { Translator translator = mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index.getTranslationIndex()); String deobfClassName = translator.getTranslatedClass(entryPool.getClass(obf.getClassName())).getClassName(); if (deobfClassName == null) { deobfClassName = obf.getClassName(); } throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName); } classMapping.setMethodName(obf.getName(), obf.getDesc(), deobfName); } public void removeMethodTreeMapping(MethodEntry obf) { index.getRelatedMethodImplementations(obf).forEach(this::removeMethodMapping); } public void removeMethodMapping(MethodEntry obf) { ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); classMapping.setMethodName(obf.getName(), obf.getDesc(), null); } public void markMethodTreeAsDeobfuscated(MethodEntry obf) { index.getRelatedMethodImplementations(obf).forEach(this::markMethodAsDeobfuscated); } public void markMethodAsDeobfuscated(MethodEntry obf) { ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); classMapping.setMethodName(obf.getName(), obf.getDesc(), obf.getName()); } public void setLocalVariableTreeName(LocalVariableEntry obf, String deobfName) { MethodEntry obfMethod = obf.getOwnerEntry(); Set implementations = index.getRelatedMethodImplementations(obfMethod); for (MethodEntry entry : implementations) { ClassMapping classMapping = mappings.getClassByObf(entry.getOwnerClassEntry()); if (classMapping != null) { MethodMapping mapping = classMapping.getMethodByObf(entry.getName(), entry.getDesc()); // NOTE: don't need to check arguments for name collisions with names determined by Procyon // TODO: Verify if I don't break things if (mapping != null) { for (LocalVariableMapping localVariableMapping : Lists.newArrayList(mapping.arguments())) { if (localVariableMapping.getIndex() != obf.getIndex()) { if (mapping.getDeobfLocalVariableName(localVariableMapping.getIndex()).equals(deobfName) || localVariableMapping.getName().equals(deobfName)) { throw new IllegalNameException(deobfName, "There is already an argument with that name"); } } } } } } for (MethodEntry entry : implementations) { setLocalVariableName(new LocalVariableEntry(entry, obf.getIndex(), obf.getName()), deobfName); } } public void setLocalVariableName(LocalVariableEntry obf, String deobfName) { deobfName = NameValidator.validateArgumentName(deobfName); ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); MethodMapping mapping = classMapping.getMethodByObf(obf.getMethodName(), obf.getMethodDesc()); // NOTE: don't need to check arguments for name collisions with names determined by Procyon // TODO: Verify if I don't break things if (mapping != null) { for (LocalVariableMapping localVariableMapping : Lists.newArrayList(mapping.arguments())) { if (localVariableMapping.getIndex() != obf.getIndex()) { if (mapping.getDeobfLocalVariableName(localVariableMapping.getIndex()).equals(deobfName) || localVariableMapping.getName().equals(deobfName)) { throw new IllegalNameException(deobfName, "There is already an argument with that name"); } } } } classMapping.setArgumentName(obf.getMethodName(), obf.getMethodDesc(), obf.getIndex(), deobfName); } public void removeLocalVariableMapping(LocalVariableEntry obf) { ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodDesc(), obf.getIndex()); } public void markArgumentAsDeobfuscated(LocalVariableEntry obf) { ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); classMapping.setArgumentName(obf.getMethodName(), obf.getMethodDesc(), obf.getIndex(), obf.getName()); } public boolean moveFieldToObfClass(ClassMapping classMapping, FieldMapping fieldMapping, ClassEntry obfClass) { classMapping.removeFieldMapping(fieldMapping); ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass); if (!targetClassMapping.containsObfField(fieldMapping.getObfName(), fieldMapping.getObfDesc())) { if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName(), fieldMapping.getObfDesc())) { targetClassMapping.addFieldMapping(fieldMapping); return true; } else { System.err.println("WARNING: deobf field was already there: " + obfClass + "." + fieldMapping.getDeobfName()); } } return false; } public boolean moveMethodToObfClass(ClassMapping classMapping, MethodMapping methodMapping, ClassEntry obfClass) { classMapping.removeMethodMapping(methodMapping); ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass); if (!targetClassMapping.containsObfMethod(methodMapping.getObfName(), methodMapping.getObfDesc())) { if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfDesc())) { targetClassMapping.addMethodMapping(methodMapping); return true; } else { System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfDesc()); } } return false; } public void write(OutputStream out) throws IOException { // TEMP: just use the object output for now. We can find a more efficient storage format later GZIPOutputStream gzipout = new GZIPOutputStream(out); ObjectOutputStream oout = new ObjectOutputStream(gzipout); oout.writeObject(this); gzipout.finish(); } private ClassMapping getOrCreateClassMapping(ClassEntry obfClassEntry) { List mappingChain = getOrCreateClassMappingChain(obfClassEntry); return mappingChain.get(mappingChain.size() - 1); } private List getOrCreateClassMappingChain(ClassEntry obfClassEntry) { List classChain = obfClassEntry.getClassChain(); List mappingChain = mappings.getClassMappingChain(obfClassEntry); for (int i = 0; i < classChain.size(); i++) { ClassEntry classEntry = classChain.get(i); ClassMapping classMapping = mappingChain.get(i); if (classMapping == null) { // create it classMapping = new ClassMapping(classEntry.getName()); mappingChain.set(i, classMapping); // add it to the right parent try { if (i == 0) { mappings.addClassMapping(classMapping); } else { mappingChain.get(i - 1).addInnerClassMapping(classMapping); } } catch (MappingConflict mappingConflict) { mappingConflict.printStackTrace(); } } } return mappingChain; } public void setClassModifier(ClassEntry obEntry, Mappings.EntryModifier modifier) { ClassMapping classMapping = getOrCreateClassMapping(obEntry); classMapping.setModifier(modifier); } public void setFieldModifier(FieldEntry obEntry, Mappings.EntryModifier modifier) { ClassMapping classMapping = getOrCreateClassMapping(obEntry.getOwnerClassEntry()); classMapping.setFieldModifier(obEntry.getName(), obEntry.getDesc(), modifier); } public void setMethodModifier(MethodEntry obEntry, Mappings.EntryModifier modifier) { ClassMapping classMapping = getOrCreateClassMapping(obEntry.getOwnerClassEntry()); classMapping.setMethodModifier(obEntry.getName(), obEntry.getDesc(), modifier); } public Mappings.EntryModifier getClassModifier(ClassEntry obfEntry) { ClassMapping classMapping = getOrCreateClassMapping(obfEntry); return classMapping.getModifier(); } public Mappings.EntryModifier getFieldModifier(FieldEntry obfEntry) { ClassMapping classMapping = getOrCreateClassMapping(obfEntry.getOwnerClassEntry()); FieldMapping fieldMapping = classMapping.getFieldByObf(obfEntry); if (fieldMapping == null) { return Mappings.EntryModifier.UNCHANGED; } return fieldMapping.getModifier(); } public Mappings.EntryModifier getMethodModfifier(MethodEntry obfEntry) { ClassMapping classMapping = getOrCreateClassMapping(obfEntry.getOwnerClassEntry()); MethodMapping methodMapping = classMapping.getMethodByObf(obfEntry); if (methodMapping == null) { return Mappings.EntryModifier.UNCHANGED; } return methodMapping.getModifier(); } }