/******************************************************************************* * 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 java.io.Serializable; import java.util.ArrayList; import java.util.Map; import com.google.common.collect.Maps; public class ClassMapping implements Serializable, Comparable<ClassMapping> { private static final long serialVersionUID = -5148491146902340107L; private String m_obfFullName; private String m_obfSimpleName; private String m_deobfName; private Map<String, ClassMapping> m_innerClassesByObfSimple; private Map<String, ClassMapping> m_innerClassesByDeobf; private Map<String, FieldMapping> m_fieldsByObf; private Map<String, FieldMapping> m_fieldsByDeobf; private Map<String, MethodMapping> m_methodsByObf; private Map<String, MethodMapping> m_methodsByDeobf; public ClassMapping(String obfFullName) { this(obfFullName, null); } public ClassMapping(String obfFullName, String deobfName) { m_obfFullName = obfFullName; ClassEntry classEntry = new ClassEntry(obfFullName); m_obfSimpleName = classEntry.isInnerClass() ? classEntry.getInnermostClassName() : classEntry.getSimpleName(); m_deobfName = NameValidator.validateClassName(deobfName, false); m_innerClassesByObfSimple = Maps.newHashMap(); m_innerClassesByDeobf = Maps.newHashMap(); m_fieldsByObf = Maps.newHashMap(); m_fieldsByDeobf = Maps.newHashMap(); m_methodsByObf = Maps.newHashMap(); m_methodsByDeobf = Maps.newHashMap(); } public String getObfFullName() { return m_obfFullName; } public String getObfSimpleName() { return m_obfSimpleName; } public String getDeobfName() { return m_deobfName; } public void setDeobfName(String val) { m_deobfName = NameValidator.validateClassName(val, false); } // // INNER CLASSES //////// public Iterable<ClassMapping> innerClasses() { assert m_innerClassesByObfSimple.size() >= m_innerClassesByDeobf.size(); return m_innerClassesByObfSimple.values(); } public void addInnerClassMapping(ClassMapping classMapping) { boolean obfWasAdded = m_innerClassesByObfSimple.put(classMapping.getObfSimpleName(), classMapping) == null; assert obfWasAdded; if(classMapping.getDeobfName() != null) { assert isSimpleClassName(classMapping.getDeobfName()); boolean deobfWasAdded = m_innerClassesByDeobf.put(classMapping.getDeobfName(), classMapping) == null; assert deobfWasAdded; } } public void removeInnerClassMapping(ClassMapping classMapping) { boolean obfWasRemoved = m_innerClassesByObfSimple.remove(classMapping.getObfSimpleName()) != null; assert obfWasRemoved; if(classMapping.getDeobfName() != null) { boolean deobfWasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null; assert deobfWasRemoved; } } public ClassMapping getOrCreateInnerClass(ClassEntry obfInnerClass) { ClassMapping classMapping = m_innerClassesByObfSimple .get(obfInnerClass.getInnermostClassName()); if(classMapping == null) { classMapping = new ClassMapping(obfInnerClass.getName()); boolean wasAdded = m_innerClassesByObfSimple.put(classMapping.getObfSimpleName(), classMapping) == null; assert wasAdded; } return classMapping; } public ClassMapping getInnerClassByObfSimple(String obfSimpleName) { assert isSimpleClassName(obfSimpleName); return m_innerClassesByObfSimple.get(obfSimpleName); } public ClassMapping getInnerClassByDeobf(String deobfName) { assert isSimpleClassName(deobfName); return m_innerClassesByDeobf.get(deobfName); } public ClassMapping getInnerClassByDeobfThenObfSimple(String name) { ClassMapping classMapping = getInnerClassByDeobf(name); if(classMapping == null) classMapping = getInnerClassByObfSimple(name); return classMapping; } public String getDeobfInnerClassName(String obfSimpleName) { assert isSimpleClassName(obfSimpleName); ClassMapping classMapping = m_innerClassesByObfSimple.get(obfSimpleName); if(classMapping != null) return classMapping.getDeobfName(); return null; } public void setInnerClassName(ClassEntry obfInnerClass, String deobfName) { ClassMapping classMapping = getOrCreateInnerClass(obfInnerClass); if(classMapping.getDeobfName() != null) { boolean wasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null; assert wasRemoved; } classMapping.setDeobfName(deobfName); if(deobfName != null) { assert isSimpleClassName(deobfName); boolean wasAdded = m_innerClassesByDeobf.put(deobfName, classMapping) == null; assert wasAdded; } } public boolean hasInnerClassByObfSimple(String obfSimpleName) { return m_innerClassesByObfSimple.containsKey(obfSimpleName); } public boolean hasInnerClassByDeobf(String deobfName) { return m_innerClassesByDeobf.containsKey(deobfName); } // // FIELDS //////// public Iterable<FieldMapping> fields() { assert m_fieldsByObf.size() == m_fieldsByDeobf.size(); return m_fieldsByObf.values(); } public boolean containsObfField(String obfName, Type obfType) { return m_fieldsByObf.containsKey(getFieldKey(obfName, obfType)); } public boolean containsDeobfField(String deobfName, Type deobfType) { return m_fieldsByDeobf.containsKey(getFieldKey(deobfName, deobfType)); } public void addFieldMapping(FieldMapping fieldMapping) { String obfKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()); if(m_fieldsByObf.containsKey(obfKey)) throw new Error("Already have mapping for " + m_obfFullName + "." + obfKey); String deobfKey = getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType()); if(m_fieldsByDeobf.containsKey(deobfKey)) throw new Error("Already have mapping for " + m_deobfName + "." + deobfKey); boolean obfWasAdded = m_fieldsByObf.put(obfKey, fieldMapping) == null; assert obfWasAdded; boolean deobfWasAdded = m_fieldsByDeobf.put(deobfKey, fieldMapping) == null; assert deobfWasAdded; assert m_fieldsByObf.size() == m_fieldsByDeobf.size(); } public void removeFieldMapping(FieldMapping fieldMapping) { boolean obfWasRemoved = m_fieldsByObf.remove(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType())) != null; assert obfWasRemoved; if(fieldMapping.getDeobfName() != null) { boolean deobfWasRemoved = m_fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType())) != null; assert deobfWasRemoved; } } public FieldMapping getFieldByObf(String obfName, Type obfType) { return m_fieldsByObf.get(getFieldKey(obfName, obfType)); } public FieldMapping getFieldByDeobf(String deobfName, Type obfType) { return m_fieldsByDeobf.get(getFieldKey(deobfName, obfType)); } public String getObfFieldName(String deobfName, Type obfType) { FieldMapping fieldMapping = m_fieldsByDeobf.get(getFieldKey(deobfName, obfType)); if(fieldMapping != null) return fieldMapping.getObfName(); return null; } public String getDeobfFieldName(String obfName, Type obfType) { FieldMapping fieldMapping = m_fieldsByObf.get(getFieldKey(obfName, obfType)); if(fieldMapping != null) return fieldMapping.getDeobfName(); return null; } private String getFieldKey(String name, Type type) { if(name == null) throw new IllegalArgumentException("name cannot be null!"); if(type == null) throw new IllegalArgumentException("type cannot be null!"); return name + ":" + type; } public void setFieldName(String obfName, Type obfType, String deobfName) { assert deobfName != null; FieldMapping fieldMapping = m_fieldsByObf.get(getFieldKey(obfName, obfType)); if(fieldMapping == null) { fieldMapping = new FieldMapping(obfName, obfType, deobfName); boolean obfWasAdded = m_fieldsByObf.put(getFieldKey(obfName, obfType), fieldMapping) == null; assert obfWasAdded; }else { boolean wasRemoved = m_fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), obfType)) != null; assert wasRemoved; } fieldMapping.setDeobfName(deobfName); if(deobfName != null) { boolean wasAdded = m_fieldsByDeobf.put(getFieldKey(deobfName, obfType), fieldMapping) == null; assert wasAdded; } } public void setFieldObfNameAndType(String oldObfName, Type obfType, String newObfName, Type newObfType) { assert newObfName != null; FieldMapping fieldMapping = m_fieldsByObf.remove(getFieldKey(oldObfName, obfType)); assert fieldMapping != null; fieldMapping.setObfName(newObfName); fieldMapping.setObfType(newObfType); boolean obfWasAdded = m_fieldsByObf .put(getFieldKey(newObfName, newObfType), fieldMapping) == null; assert obfWasAdded; } // // METHODS //////// public Iterable<MethodMapping> methods() { assert m_methodsByObf.size() >= m_methodsByDeobf.size(); return m_methodsByObf.values(); } public boolean containsObfMethod(String obfName, Signature obfSignature) { return m_methodsByObf.containsKey(getMethodKey(obfName, obfSignature)); } public boolean containsDeobfMethod(String deobfName, Signature obfSignature) { return m_methodsByDeobf.containsKey(getMethodKey(deobfName, obfSignature)); } public void addMethodMapping(MethodMapping methodMapping) { String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()); if(m_methodsByObf.containsKey(obfKey)) throw new Error("Already have mapping for " + m_obfFullName + "." + obfKey); boolean wasAdded = m_methodsByObf.put(obfKey, methodMapping) == null; assert wasAdded; if(methodMapping.getDeobfName() != null) { String deobfKey = getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature()); if(m_methodsByDeobf.containsKey(deobfKey)) throw new Error("Already have mapping for " + m_deobfName + "." + deobfKey); boolean deobfWasAdded = m_methodsByDeobf.put(deobfKey, methodMapping) == null; assert deobfWasAdded; } assert m_methodsByObf.size() >= m_methodsByDeobf.size(); } public void removeMethodMapping(MethodMapping methodMapping) { boolean obfWasRemoved = m_methodsByObf.remove(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature())) != null; assert obfWasRemoved; if(methodMapping.getDeobfName() != null) { boolean deobfWasRemoved = m_methodsByDeobf.remove(getMethodKey( methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null; assert deobfWasRemoved; } } public MethodMapping getMethodByObf(String obfName, Signature obfSignature) { return m_methodsByObf.get(getMethodKey(obfName, obfSignature)); } public MethodMapping getMethodByDeobf(String deobfName, Signature obfSignature) { return m_methodsByDeobf.get(getMethodKey(deobfName, obfSignature)); } private String getMethodKey(String name, Signature signature) { if(name == null) throw new IllegalArgumentException("name cannot be null!"); if(signature == null) throw new IllegalArgumentException("signature cannot be null!"); return name + signature; } public void setMethodName(String obfName, Signature obfSignature, String deobfName) { MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfName, obfSignature)); if(methodMapping == null) methodMapping = createMethodMapping(obfName, obfSignature); else if(methodMapping.getDeobfName() != null) { boolean wasRemoved = m_methodsByDeobf.remove(getMethodKey( methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null; assert wasRemoved; } methodMapping.setDeobfName(deobfName); if(deobfName != null) { boolean wasAdded = m_methodsByDeobf.put(getMethodKey(deobfName, obfSignature), methodMapping) == null; assert wasAdded; } } public void setMethodObfNameAndSignature(String oldObfName, Signature obfSignature, String newObfName, Signature newObfSignature) { assert newObfName != null; MethodMapping methodMapping = m_methodsByObf.remove(getMethodKey(oldObfName, obfSignature)); assert methodMapping != null; methodMapping.setObfName(newObfName); methodMapping.setObfSignature(newObfSignature); boolean obfWasAdded = m_methodsByObf.put(getMethodKey(newObfName, newObfSignature), methodMapping) == null; assert obfWasAdded; } // // ARGUMENTS //////// public void setArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex, String argumentName) { assert argumentName != null; MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)); if(methodMapping == null) methodMapping = createMethodMapping(obfMethodName, obfMethodSignature); methodMapping.setArgumentName(argumentIndex, argumentName); } public void removeArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex) { m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)) .removeArgumentName(argumentIndex); } private MethodMapping createMethodMapping(String obfName, Signature obfSignature) { MethodMapping methodMapping = new MethodMapping(obfName, obfSignature); boolean wasAdded = m_methodsByObf.put(getMethodKey(obfName, obfSignature), methodMapping) == null; assert wasAdded; return methodMapping; } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append(m_obfFullName); buf.append(" <-> "); buf.append(m_deobfName); buf.append("\n"); buf.append("Fields:\n"); for(FieldMapping fieldMapping : fields()) { buf.append("\t"); buf.append(fieldMapping.getObfName()); buf.append(" <-> "); buf.append(fieldMapping.getDeobfName()); buf.append("\n"); } buf.append("Methods:\n"); for(MethodMapping methodMapping : m_methodsByObf.values()) { buf.append(methodMapping.toString()); buf.append("\n"); } buf.append("Inner Classes:\n"); for(ClassMapping classMapping : m_innerClassesByObfSimple.values()) { buf.append("\t"); buf.append(classMapping.getObfSimpleName()); buf.append(" <-> "); buf.append(classMapping.getDeobfName()); buf.append("\n"); } return buf.toString(); } @Override public int compareTo(ClassMapping other) { // sort by a, b, c, ... aa, ab, etc if(m_obfFullName.length() != other.m_obfFullName.length()) return m_obfFullName.length() - other.m_obfFullName.length(); return m_obfFullName.compareTo(other.m_obfFullName); } public boolean renameObfClass(String oldObfClassName, String newObfClassName) { // rename inner classes for(ClassMapping innerClassMapping : new ArrayList<ClassMapping>( m_innerClassesByObfSimple.values())) if(innerClassMapping.renameObfClass(oldObfClassName, newObfClassName)) { boolean wasRemoved = m_innerClassesByObfSimple.remove(oldObfClassName) != null; assert wasRemoved; boolean wasAdded = m_innerClassesByObfSimple.put(newObfClassName, innerClassMapping) == null; assert wasAdded; } // rename field types for(FieldMapping fieldMapping : new ArrayList<FieldMapping>( m_fieldsByObf.values())) { String oldFieldKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()); if(fieldMapping.renameObfClass(oldObfClassName, newObfClassName)) { boolean wasRemoved = m_fieldsByObf.remove(oldFieldKey) != null; assert wasRemoved; boolean wasAdded = m_fieldsByObf.put( getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()), fieldMapping) == null; assert wasAdded; } } // rename method signatures for(MethodMapping methodMapping : new ArrayList<MethodMapping>( m_methodsByObf.values())) { String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()); if(methodMapping.renameObfClass(oldObfClassName, newObfClassName)) { boolean wasRemoved = m_methodsByObf.remove(oldMethodKey) != null; assert wasRemoved; boolean wasAdded = m_methodsByObf.put( getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()), methodMapping) == null; assert wasAdded; } } if(m_obfFullName.equals(oldObfClassName)) { // rename this class m_obfFullName = newObfClassName; return true; } return false; } public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) { MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfBehaviorEntry.getName(), obfBehaviorEntry.getSignature())); if(methodMapping != null) return methodMapping.containsArgument(name); return false; } public static boolean isSimpleClassName(String name) { return name.indexOf('/') < 0 && name.indexOf('$') < 0; } public ClassEntry getObfEntry() { return new ClassEntry(m_obfFullName); } }