From 52ab426d8fad3dbee7e728f523a35af94facebda Mon Sep 17 00:00:00 2001 From: jeff Date: Tue, 3 Feb 2015 22:00:53 -0500 Subject: oops, don't depend on local procyon project --- src/cuchaz/enigma/mapping/ArgumentEntry.java | 116 ++++++ src/cuchaz/enigma/mapping/ArgumentMapping.java | 44 +++ src/cuchaz/enigma/mapping/BehaviorEntry.java | 15 + .../enigma/mapping/BehaviorEntryFactory.java | 57 +++ src/cuchaz/enigma/mapping/ClassEntry.java | 123 +++++++ src/cuchaz/enigma/mapping/ClassMapping.java | 405 +++++++++++++++++++++ src/cuchaz/enigma/mapping/ConstructorEntry.java | 116 ++++++ src/cuchaz/enigma/mapping/Entry.java | 18 + src/cuchaz/enigma/mapping/EntryPair.java | 22 ++ src/cuchaz/enigma/mapping/FieldEntry.java | 88 +++++ src/cuchaz/enigma/mapping/FieldMapping.java | 43 +++ .../enigma/mapping/IllegalNameException.java | 44 +++ src/cuchaz/enigma/mapping/JavassistUtil.java | 55 +++ .../enigma/mapping/MappingParseException.java | 29 ++ src/cuchaz/enigma/mapping/Mappings.java | 213 +++++++++++ src/cuchaz/enigma/mapping/MappingsReader.java | 176 +++++++++ src/cuchaz/enigma/mapping/MappingsRenamer.java | 237 ++++++++++++ src/cuchaz/enigma/mapping/MappingsWriter.java | 88 +++++ src/cuchaz/enigma/mapping/MethodEntry.java | 104 ++++++ src/cuchaz/enigma/mapping/MethodMapping.java | 162 +++++++++ src/cuchaz/enigma/mapping/NameValidator.java | 80 ++++ src/cuchaz/enigma/mapping/SignatureUpdater.java | 94 +++++ .../enigma/mapping/TranslationDirection.java | 29 ++ src/cuchaz/enigma/mapping/Translator.java | 235 ++++++++++++ 24 files changed, 2593 insertions(+) create mode 100644 src/cuchaz/enigma/mapping/ArgumentEntry.java create mode 100644 src/cuchaz/enigma/mapping/ArgumentMapping.java create mode 100644 src/cuchaz/enigma/mapping/BehaviorEntry.java create mode 100644 src/cuchaz/enigma/mapping/BehaviorEntryFactory.java create mode 100644 src/cuchaz/enigma/mapping/ClassEntry.java create mode 100644 src/cuchaz/enigma/mapping/ClassMapping.java create mode 100644 src/cuchaz/enigma/mapping/ConstructorEntry.java create mode 100644 src/cuchaz/enigma/mapping/Entry.java create mode 100644 src/cuchaz/enigma/mapping/EntryPair.java create mode 100644 src/cuchaz/enigma/mapping/FieldEntry.java create mode 100644 src/cuchaz/enigma/mapping/FieldMapping.java create mode 100644 src/cuchaz/enigma/mapping/IllegalNameException.java create mode 100644 src/cuchaz/enigma/mapping/JavassistUtil.java create mode 100644 src/cuchaz/enigma/mapping/MappingParseException.java create mode 100644 src/cuchaz/enigma/mapping/Mappings.java create mode 100644 src/cuchaz/enigma/mapping/MappingsReader.java create mode 100644 src/cuchaz/enigma/mapping/MappingsRenamer.java create mode 100644 src/cuchaz/enigma/mapping/MappingsWriter.java create mode 100644 src/cuchaz/enigma/mapping/MethodEntry.java create mode 100644 src/cuchaz/enigma/mapping/MethodMapping.java create mode 100644 src/cuchaz/enigma/mapping/NameValidator.java create mode 100644 src/cuchaz/enigma/mapping/SignatureUpdater.java create mode 100644 src/cuchaz/enigma/mapping/TranslationDirection.java create mode 100644 src/cuchaz/enigma/mapping/Translator.java (limited to 'src/cuchaz/enigma/mapping') diff --git a/src/cuchaz/enigma/mapping/ArgumentEntry.java b/src/cuchaz/enigma/mapping/ArgumentEntry.java new file mode 100644 index 0000000..2c15f4e --- /dev/null +++ b/src/cuchaz/enigma/mapping/ArgumentEntry.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.Serializable; + +import cuchaz.enigma.Util; + +public class ArgumentEntry implements Entry, Serializable { + + private static final long serialVersionUID = 4472172468162696006L; + + private BehaviorEntry m_behaviorEntry; + private int m_index; + private String m_name; + + public ArgumentEntry(BehaviorEntry behaviorEntry, int index, String name) { + if (behaviorEntry == null) { + throw new IllegalArgumentException("Behavior cannot be null!"); + } + if (index < 0) { + throw new IllegalArgumentException("Index must be non-negative!"); + } + if (name == null) { + throw new IllegalArgumentException("Argument name cannot be null!"); + } + + m_behaviorEntry = behaviorEntry; + m_index = index; + m_name = name; + } + + public ArgumentEntry(ArgumentEntry other) { + m_behaviorEntry = (BehaviorEntry)m_behaviorEntry.cloneToNewClass(getClassEntry()); + m_index = other.m_index; + m_name = other.m_name; + } + + public ArgumentEntry(ArgumentEntry other, String newClassName) { + m_behaviorEntry = (BehaviorEntry)other.m_behaviorEntry.cloneToNewClass(new ClassEntry(newClassName)); + m_index = other.m_index; + m_name = other.m_name; + } + + public BehaviorEntry getBehaviorEntry() { + return m_behaviorEntry; + } + + public int getIndex() { + return m_index; + } + + @Override + public String getName() { + return m_name; + } + + @Override + public ClassEntry getClassEntry() { + return m_behaviorEntry.getClassEntry(); + } + + @Override + public String getClassName() { + return m_behaviorEntry.getClassName(); + } + + @Override + public ArgumentEntry cloneToNewClass(ClassEntry classEntry) { + return new ArgumentEntry(this, classEntry.getName()); + } + + public String getMethodName() { + return m_behaviorEntry.getName(); + } + + public String getMethodSignature() { + return m_behaviorEntry.getSignature(); + } + + @Override + public int hashCode() { + return Util.combineHashesOrdered( + m_behaviorEntry, + Integer.valueOf(m_index).hashCode(), + m_name.hashCode() + ); + } + + @Override + public boolean equals(Object other) { + if (other instanceof ArgumentEntry) { + return equals((ArgumentEntry)other); + } + return false; + } + + public boolean equals(ArgumentEntry other) { + return m_behaviorEntry.equals(other.m_behaviorEntry) + && m_index == other.m_index + && m_name.equals(other.m_name); + } + + @Override + public String toString() { + return m_behaviorEntry.toString() + "(" + m_index + ":" + m_name + ")"; + } +} diff --git a/src/cuchaz/enigma/mapping/ArgumentMapping.java b/src/cuchaz/enigma/mapping/ArgumentMapping.java new file mode 100644 index 0000000..f4d8e77 --- /dev/null +++ b/src/cuchaz/enigma/mapping/ArgumentMapping.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.Serializable; + +public class ArgumentMapping implements Serializable, Comparable { + + private static final long serialVersionUID = 8610742471440861315L; + + private int m_index; + private String m_name; + + // NOTE: this argument order is important for the MethodReader/MethodWriter + public ArgumentMapping(int index, String name) { + m_index = index; + m_name = NameValidator.validateArgumentName(name); + } + + public int getIndex() { + return m_index; + } + + public String getName() { + return m_name; + } + + public void setName(String val) { + m_name = NameValidator.validateArgumentName(val); + } + + @Override + public int compareTo(ArgumentMapping other) { + return Integer.compare(m_index, other.m_index); + } +} diff --git a/src/cuchaz/enigma/mapping/BehaviorEntry.java b/src/cuchaz/enigma/mapping/BehaviorEntry.java new file mode 100644 index 0000000..f4200b8 --- /dev/null +++ b/src/cuchaz/enigma/mapping/BehaviorEntry.java @@ -0,0 +1,15 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +public interface BehaviorEntry extends Entry { + String getSignature(); +} diff --git a/src/cuchaz/enigma/mapping/BehaviorEntryFactory.java b/src/cuchaz/enigma/mapping/BehaviorEntryFactory.java new file mode 100644 index 0000000..386faeb --- /dev/null +++ b/src/cuchaz/enigma/mapping/BehaviorEntryFactory.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import javassist.CtBehavior; +import javassist.CtConstructor; +import javassist.CtMethod; +import javassist.bytecode.Descriptor; + +public class BehaviorEntryFactory { + + public static BehaviorEntry create(String className, String name, String signature) { + return create(new ClassEntry(className), name, signature); + } + + public static BehaviorEntry create(ClassEntry classEntry, String name, String signature) { + if (name.equals("")) { + return new ConstructorEntry(classEntry, signature); + } else if (name.equals("")) { + return new ConstructorEntry(classEntry); + } else { + return new MethodEntry(classEntry, name, signature); + } + } + + public static BehaviorEntry create(CtBehavior behavior) { + String className = Descriptor.toJvmName(behavior.getDeclaringClass().getName()); + if (behavior instanceof CtMethod) { + return create(className, behavior.getName(), behavior.getSignature()); + } else if (behavior instanceof CtConstructor) { + CtConstructor constructor = (CtConstructor)behavior; + if (constructor.isClassInitializer()) { + return create(className, "", null); + } else { + return create(className, "", constructor.getSignature()); + } + } else { + throw new IllegalArgumentException("Unable to create BehaviorEntry from " + behavior); + } + } + + public static BehaviorEntry createObf(ClassEntry classEntry, MethodMapping methodMapping) { + return create(classEntry, methodMapping.getObfName(), methodMapping.getObfSignature()); + } + + public static BehaviorEntry createDeobf(ClassEntry classEntry, MethodMapping methodMapping) { + return create(classEntry, methodMapping.getDeobfName(), methodMapping.getObfSignature()); + } +} diff --git a/src/cuchaz/enigma/mapping/ClassEntry.java b/src/cuchaz/enigma/mapping/ClassEntry.java new file mode 100644 index 0000000..cf41001 --- /dev/null +++ b/src/cuchaz/enigma/mapping/ClassEntry.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.Serializable; + +public class ClassEntry implements Entry, Serializable { + + private static final long serialVersionUID = 4235460580973955811L; + + private String m_name; + + public ClassEntry(String className) { + if (className == null) { + throw new IllegalArgumentException("Class name cannot be null!"); + } + if (className.indexOf('.') >= 0) { + throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className); + } + + m_name = className; + + if (isInnerClass() && getInnerClassName().indexOf('/') >= 0) { + throw new IllegalArgumentException("Inner class must not have a package: " + className); + } + } + + public ClassEntry(ClassEntry other) { + m_name = other.m_name; + } + + @Override + public String getName() { + return m_name; + } + + @Override + public String getClassName() { + return m_name; + } + + @Override + public ClassEntry getClassEntry() { + return this; + } + + @Override + public ClassEntry cloneToNewClass(ClassEntry classEntry) { + return classEntry; + } + + @Override + public int hashCode() { + return m_name.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other instanceof ClassEntry) { + return equals((ClassEntry)other); + } + return false; + } + + public boolean equals(ClassEntry other) { + return m_name.equals(other.m_name); + } + + @Override + public String toString() { + return m_name; + } + + public boolean isInnerClass() { + return m_name.lastIndexOf('$') >= 0; + } + + public String getOuterClassName() { + if (isInnerClass()) { + return m_name.substring(0, m_name.lastIndexOf('$')); + } + return m_name; + } + + public String getInnerClassName() { + if (!isInnerClass()) { + throw new Error("This is not an inner class!"); + } + return m_name.substring(m_name.lastIndexOf('$') + 1); + } + + public ClassEntry getOuterClassEntry() { + return new ClassEntry(getOuterClassName()); + } + + public boolean isInDefaultPackage() { + return m_name.indexOf('/') < 0; + } + + public String getPackageName() { + int pos = m_name.lastIndexOf('/'); + if (pos > 0) { + return m_name.substring(0, pos); + } + return null; + } + + public String getSimpleName() { + int pos = m_name.lastIndexOf('/'); + if (pos > 0) { + return m_name.substring(pos + 1); + } + return m_name; + } +} diff --git a/src/cuchaz/enigma/mapping/ClassMapping.java b/src/cuchaz/enigma/mapping/ClassMapping.java new file mode 100644 index 0000000..dbb8717 --- /dev/null +++ b/src/cuchaz/enigma/mapping/ClassMapping.java @@ -0,0 +1,405 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.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 { + + private static final long serialVersionUID = -5148491146902340107L; + + private String m_obfName; + private String m_deobfName; + private Map m_innerClassesByObf; + private Map m_innerClassesByDeobf; + private Map m_fieldsByObf; + private Map m_fieldsByDeobf; + private Map m_methodsByObf; + private Map m_methodsByDeobf; + + public ClassMapping(String obfName) { + this(obfName, null); + } + + public ClassMapping(String obfName, String deobfName) { + m_obfName = obfName; + m_deobfName = NameValidator.validateClassName(deobfName, false); + m_innerClassesByObf = Maps.newHashMap(); + m_innerClassesByDeobf = Maps.newHashMap(); + m_fieldsByObf = Maps.newHashMap(); + m_fieldsByDeobf = Maps.newHashMap(); + m_methodsByObf = Maps.newHashMap(); + m_methodsByDeobf = Maps.newHashMap(); + } + + public String getObfName() { + return m_obfName; + } + + public String getDeobfName() { + return m_deobfName; + } + + public void setDeobfName(String val) { + m_deobfName = NameValidator.validateClassName(val, false); + } + + //// INNER CLASSES //////// + + public Iterable innerClasses() { + assert (m_innerClassesByObf.size() >= m_innerClassesByDeobf.size()); + return m_innerClassesByObf.values(); + } + + public void addInnerClassMapping(ClassMapping classMapping) { + assert (isSimpleClassName(classMapping.getObfName())); + boolean obfWasAdded = m_innerClassesByObf.put(classMapping.getObfName(), 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_innerClassesByObf.remove(classMapping.getObfName()) != null; + assert (obfWasRemoved); + if (classMapping.getDeobfName() != null) { + boolean deobfWasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null; + assert (deobfWasRemoved); + } + } + + public ClassMapping getOrCreateInnerClass(String obfName) { + assert (isSimpleClassName(obfName)); + ClassMapping classMapping = m_innerClassesByObf.get(obfName); + if (classMapping == null) { + classMapping = new ClassMapping(obfName); + boolean wasAdded = m_innerClassesByObf.put(obfName, classMapping) == null; + assert (wasAdded); + } + return classMapping; + } + + public ClassMapping getInnerClassByObf(String obfName) { + assert (isSimpleClassName(obfName)); + return m_innerClassesByObf.get(obfName); + } + + public ClassMapping getInnerClassByDeobf(String deobfName) { + assert (isSimpleClassName(deobfName)); + return m_innerClassesByDeobf.get(deobfName); + } + + public ClassMapping getInnerClassByDeobfThenObf(String name) { + ClassMapping classMapping = getInnerClassByDeobf(name); + if (classMapping == null) { + classMapping = getInnerClassByObf(name); + } + return classMapping; + } + + public String getObfInnerClassName(String deobfName) { + assert (isSimpleClassName(deobfName)); + ClassMapping classMapping = m_innerClassesByDeobf.get(deobfName); + if (classMapping != null) { + return classMapping.getObfName(); + } + return null; + } + + public String getDeobfInnerClassName(String obfName) { + assert (isSimpleClassName(obfName)); + ClassMapping classMapping = m_innerClassesByObf.get(obfName); + if (classMapping != null) { + return classMapping.getDeobfName(); + } + return null; + } + + public void setInnerClassName(String obfName, String deobfName) { + assert (isSimpleClassName(obfName)); + ClassMapping classMapping = getOrCreateInnerClass(obfName); + 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); + } + } + + //// FIELDS //////// + + public Iterable fields() { + assert (m_fieldsByObf.size() == m_fieldsByDeobf.size()); + return m_fieldsByObf.values(); + } + + public boolean containsObfField(String obfName) { + return m_fieldsByObf.containsKey(obfName); + } + + public boolean containsDeobfField(String deobfName) { + return m_fieldsByDeobf.containsKey(deobfName); + } + + public void addFieldMapping(FieldMapping fieldMapping) { + if (m_fieldsByObf.containsKey(fieldMapping.getObfName())) { + throw new Error("Already have mapping for " + m_obfName + "." + fieldMapping.getObfName()); + } + if (m_fieldsByDeobf.containsKey(fieldMapping.getDeobfName())) { + throw new Error("Already have mapping for " + m_deobfName + "." + fieldMapping.getDeobfName()); + } + boolean obfWasAdded = m_fieldsByObf.put(fieldMapping.getObfName(), fieldMapping) == null; + assert (obfWasAdded); + boolean deobfWasAdded = m_fieldsByDeobf.put(fieldMapping.getDeobfName(), fieldMapping) == null; + assert (deobfWasAdded); + assert (m_fieldsByObf.size() == m_fieldsByDeobf.size()); + } + + public void removeFieldMapping(FieldMapping fieldMapping) { + boolean obfWasRemoved = m_fieldsByObf.remove(fieldMapping.getObfName()) != null; + assert (obfWasRemoved); + if (fieldMapping.getDeobfName() != null) { + boolean deobfWasRemoved = m_fieldsByDeobf.remove(fieldMapping.getDeobfName()) != null; + assert (deobfWasRemoved); + } + } + + public FieldMapping getFieldByObf(String obfName) { + return m_fieldsByObf.get(obfName); + } + + public FieldMapping getFieldByDeobf(String deobfName) { + return m_fieldsByDeobf.get(deobfName); + } + + public String getObfFieldName(String deobfName) { + FieldMapping fieldMapping = m_fieldsByDeobf.get(deobfName); + if (fieldMapping != null) { + return fieldMapping.getObfName(); + } + return null; + } + + public String getDeobfFieldName(String obfName) { + FieldMapping fieldMapping = m_fieldsByObf.get(obfName); + if (fieldMapping != null) { + return fieldMapping.getDeobfName(); + } + return null; + } + + public void setFieldName(String obfName, String deobfName) { + FieldMapping fieldMapping = m_fieldsByObf.get(obfName); + if (fieldMapping == null) { + fieldMapping = new FieldMapping(obfName, deobfName); + boolean obfWasAdded = m_fieldsByObf.put(obfName, fieldMapping) == null; + assert (obfWasAdded); + } else { + boolean wasRemoved = m_fieldsByDeobf.remove(fieldMapping.getDeobfName()) != null; + assert (wasRemoved); + } + fieldMapping.setDeobfName(deobfName); + if (deobfName != null) { + boolean wasAdded = m_fieldsByDeobf.put(deobfName, fieldMapping) == null; + assert (wasAdded); + } + } + + //// METHODS //////// + + public Iterable methods() { + assert (m_methodsByObf.size() >= m_methodsByDeobf.size()); + return m_methodsByObf.values(); + } + + public boolean containsObfMethod(String obfName, String obfSignature) { + return m_methodsByObf.containsKey(getMethodKey(obfName, obfSignature)); + } + + public boolean containsDeobfMethod(String deobfName, String deobfSignature) { + return m_methodsByDeobf.containsKey(getMethodKey(deobfName, deobfSignature)); + } + + 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_obfName + "." + 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, String signature) { + return m_methodsByObf.get(getMethodKey(obfName, signature)); + } + + public MethodMapping getMethodByDeobf(String deobfName, String signature) { + return m_methodsByDeobf.get(getMethodKey(deobfName, signature)); + } + + private String getMethodKey(String name, String 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, String 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); + } + } + + //// ARGUMENTS //////// + + public void setArgumentName(String obfMethodName, String obfMethodSignature, int argumentIndex, String argumentName) { + MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)); + if (methodMapping == null) { + methodMapping = createMethodMapping(obfMethodName, obfMethodSignature); + } + methodMapping.setArgumentName(argumentIndex, argumentName); + } + + public void removeArgumentName(String obfMethodName, String obfMethodSignature, int argumentIndex) { + m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)).removeArgumentName(argumentIndex); + } + + private MethodMapping createMethodMapping(String obfName, String 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_obfName); + 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_innerClassesByObf.values()) { + buf.append("\t"); + buf.append(classMapping.getObfName()); + 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_obfName.length() != other.m_obfName.length()) { + return m_obfName.length() - other.m_obfName.length(); + } + return m_obfName.compareTo(other.m_obfName); + } + + public boolean renameObfClass(String oldObfClassName, String newObfClassName) { + + // rename inner classes + for (ClassMapping innerClassMapping : new ArrayList(m_innerClassesByObf.values())) { + if (innerClassMapping.renameObfClass(oldObfClassName, newObfClassName)) { + boolean wasRemoved = m_innerClassesByObf.remove(oldObfClassName) != null; + assert (wasRemoved); + boolean wasAdded = m_innerClassesByObf.put(newObfClassName, innerClassMapping) == null; + assert (wasAdded); + } + } + + // rename method signatures + for (MethodMapping methodMapping : new ArrayList(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_obfName.equals(oldObfClassName)) { + // rename this class + m_obfName = 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; + } +} diff --git a/src/cuchaz/enigma/mapping/ConstructorEntry.java b/src/cuchaz/enigma/mapping/ConstructorEntry.java new file mode 100644 index 0000000..ea0535f --- /dev/null +++ b/src/cuchaz/enigma/mapping/ConstructorEntry.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.Serializable; + +import cuchaz.enigma.Util; + +public class ConstructorEntry implements BehaviorEntry, Serializable { + + private static final long serialVersionUID = -868346075317366758L; + + private ClassEntry m_classEntry; + private String m_signature; + + public ConstructorEntry(ClassEntry classEntry) { + this(classEntry, null); + } + + public ConstructorEntry(ClassEntry classEntry, String signature) { + if (classEntry == null) { + throw new IllegalArgumentException("Class cannot be null!"); + } + + m_classEntry = classEntry; + m_signature = signature; + } + + public ConstructorEntry(ConstructorEntry other) { + m_classEntry = new ClassEntry(other.m_classEntry); + m_signature = other.m_signature; + } + + public ConstructorEntry(ConstructorEntry other, String newClassName) { + m_classEntry = new ClassEntry(newClassName); + m_signature = other.m_signature; + } + + @Override + public ClassEntry getClassEntry() { + return m_classEntry; + } + + @Override + public String getName() { + if (isStatic()) { + return ""; + } + return ""; + } + + public boolean isStatic() { + return m_signature == null; + } + + @Override + public String getSignature() { + return m_signature; + } + + @Override + public String getClassName() { + return m_classEntry.getName(); + } + + @Override + public ConstructorEntry cloneToNewClass(ClassEntry classEntry) { + return new ConstructorEntry(this, classEntry.getName()); + } + + @Override + public int hashCode() { + if (isStatic()) { + return Util.combineHashesOrdered(m_classEntry); + } else { + return Util.combineHashesOrdered(m_classEntry, m_signature); + } + } + + @Override + public boolean equals(Object other) { + if (other instanceof ConstructorEntry) { + return equals((ConstructorEntry)other); + } + return false; + } + + public boolean equals(ConstructorEntry other) { + if (isStatic() != other.isStatic()) { + return false; + } + + if (isStatic()) { + return m_classEntry.equals(other.m_classEntry); + } else { + return m_classEntry.equals(other.m_classEntry) && m_signature.equals(other.m_signature); + } + } + + @Override + public String toString() { + if (isStatic()) { + return m_classEntry.getName() + "." + getName(); + } else { + return m_classEntry.getName() + "." + getName() + m_signature; + } + } +} diff --git a/src/cuchaz/enigma/mapping/Entry.java b/src/cuchaz/enigma/mapping/Entry.java new file mode 100644 index 0000000..39e1507 --- /dev/null +++ b/src/cuchaz/enigma/mapping/Entry.java @@ -0,0 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +public interface Entry { + String getName(); + String getClassName(); + ClassEntry getClassEntry(); + Entry cloneToNewClass(ClassEntry classEntry); +} diff --git a/src/cuchaz/enigma/mapping/EntryPair.java b/src/cuchaz/enigma/mapping/EntryPair.java new file mode 100644 index 0000000..60411c4 --- /dev/null +++ b/src/cuchaz/enigma/mapping/EntryPair.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +public class EntryPair { + + public T obf; + public T deobf; + + public EntryPair(T obf, T deobf) { + this.obf = obf; + this.deobf = deobf; + } +} diff --git a/src/cuchaz/enigma/mapping/FieldEntry.java b/src/cuchaz/enigma/mapping/FieldEntry.java new file mode 100644 index 0000000..6cc9eb7 --- /dev/null +++ b/src/cuchaz/enigma/mapping/FieldEntry.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.Serializable; + +import cuchaz.enigma.Util; + +public class FieldEntry implements Entry, Serializable { + + private static final long serialVersionUID = 3004663582802885451L; + + private ClassEntry m_classEntry; + private String m_name; + + // NOTE: this argument order is important for the MethodReader/MethodWriter + public FieldEntry(ClassEntry classEntry, String name) { + if (classEntry == null) { + throw new IllegalArgumentException("Class cannot be null!"); + } + if (name == null) { + throw new IllegalArgumentException("Field name cannot be null!"); + } + + m_classEntry = classEntry; + m_name = name; + } + + public FieldEntry(FieldEntry other) { + m_classEntry = new ClassEntry(other.m_classEntry); + m_name = other.m_name; + } + + public FieldEntry(FieldEntry other, String newClassName) { + m_classEntry = new ClassEntry(newClassName); + m_name = other.m_name; + } + + @Override + public ClassEntry getClassEntry() { + return m_classEntry; + } + + @Override + public String getName() { + return m_name; + } + + @Override + public String getClassName() { + return m_classEntry.getName(); + } + + @Override + public FieldEntry cloneToNewClass(ClassEntry classEntry) { + return new FieldEntry(this, classEntry.getName()); + } + + @Override + public int hashCode() { + return Util.combineHashesOrdered(m_classEntry, m_name); + } + + @Override + public boolean equals(Object other) { + if (other instanceof FieldEntry) { + return equals((FieldEntry)other); + } + return false; + } + + public boolean equals(FieldEntry other) { + return m_classEntry.equals(other.m_classEntry) && m_name.equals(other.m_name); + } + + @Override + public String toString() { + return m_classEntry.getName() + "." + m_name; + } +} diff --git a/src/cuchaz/enigma/mapping/FieldMapping.java b/src/cuchaz/enigma/mapping/FieldMapping.java new file mode 100644 index 0000000..5f5c270 --- /dev/null +++ b/src/cuchaz/enigma/mapping/FieldMapping.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.Serializable; + +public class FieldMapping implements Serializable, Comparable { + + private static final long serialVersionUID = 8610742471440861315L; + + private String m_obfName; + private String m_deobfName; + + public FieldMapping(String obfName, String deobfName) { + m_obfName = obfName; + m_deobfName = NameValidator.validateFieldName(deobfName); + } + + public String getObfName() { + return m_obfName; + } + + public String getDeobfName() { + return m_deobfName; + } + + public void setDeobfName(String val) { + m_deobfName = NameValidator.validateFieldName(val); + } + + @Override + public int compareTo(FieldMapping other) { + return m_obfName.compareTo(other.m_obfName); + } +} diff --git a/src/cuchaz/enigma/mapping/IllegalNameException.java b/src/cuchaz/enigma/mapping/IllegalNameException.java new file mode 100644 index 0000000..aacaf3b --- /dev/null +++ b/src/cuchaz/enigma/mapping/IllegalNameException.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +public class IllegalNameException extends RuntimeException { + + private static final long serialVersionUID = -2279910052561114323L; + + private String m_name; + private String m_reason; + + public IllegalNameException(String name) { + this(name, null); + } + + public IllegalNameException(String name, String reason) { + m_name = name; + m_reason = reason; + } + + public String getReason() { + return m_reason; + } + + @Override + public String getMessage() { + StringBuilder buf = new StringBuilder(); + buf.append("Illegal name: "); + buf.append(m_name); + if (m_reason != null) { + buf.append(" because "); + buf.append(m_reason); + } + return buf.toString(); + } +} diff --git a/src/cuchaz/enigma/mapping/JavassistUtil.java b/src/cuchaz/enigma/mapping/JavassistUtil.java new file mode 100644 index 0000000..b011e0b --- /dev/null +++ b/src/cuchaz/enigma/mapping/JavassistUtil.java @@ -0,0 +1,55 @@ +package cuchaz.enigma.mapping; + +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtField; +import javassist.CtMethod; +import javassist.bytecode.Descriptor; +import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ConstructorEntry; +import cuchaz.enigma.mapping.FieldEntry; +import cuchaz.enigma.mapping.MethodEntry; + +public class JavassistUtil { + + public static ClassEntry getClassEntry(CtClass c) { + return new ClassEntry(Descriptor.toJvmName(c.getName())); + } + + public static ClassEntry getSuperclassEntry(CtClass c) { + return new ClassEntry(Descriptor.toJvmName(c.getClassFile().getSuperclass())); + } + + public static MethodEntry getMethodEntry(CtMethod method) { + return new MethodEntry( + getClassEntry(method.getDeclaringClass()), + method.getName(), + method.getMethodInfo().getDescriptor() + ); + } + + public static ConstructorEntry getConstructorEntry(CtConstructor constructor) { + return new ConstructorEntry( + getClassEntry(constructor.getDeclaringClass()), + constructor.getMethodInfo().getDescriptor() + ); + } + + public static BehaviorEntry getBehaviorEntry(CtBehavior behavior) { + if (behavior instanceof CtMethod) { + return getMethodEntry((CtMethod)behavior); + } else if (behavior instanceof CtConstructor) { + return getConstructorEntry((CtConstructor)behavior); + } + throw new Error("behavior is neither Method nor Constructor!"); + } + + public static FieldEntry getFieldEntry(CtField field) { + return new FieldEntry( + getClassEntry(field.getDeclaringClass()), + field.getName() + ); + } +} diff --git a/src/cuchaz/enigma/mapping/MappingParseException.java b/src/cuchaz/enigma/mapping/MappingParseException.java new file mode 100644 index 0000000..1974c22 --- /dev/null +++ b/src/cuchaz/enigma/mapping/MappingParseException.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +public class MappingParseException extends Exception { + + private static final long serialVersionUID = -5487280332892507236L; + + private int m_line; + private String m_message; + + public MappingParseException(int line, String message) { + m_line = line; + m_message = message; + } + + @Override + public String getMessage() { + return "Line " + m_line + ": " + m_message; + } +} diff --git a/src/cuchaz/enigma/mapping/Mappings.java b/src/cuchaz/enigma/mapping/Mappings.java new file mode 100644 index 0000000..cc560a8 --- /dev/null +++ b/src/cuchaz/enigma/mapping/Mappings.java @@ -0,0 +1,213 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.zip.GZIPInputStream; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import cuchaz.enigma.Util; +import cuchaz.enigma.analysis.TranslationIndex; +import cuchaz.enigma.mapping.SignatureUpdater.ClassNameUpdater; + +public class Mappings implements Serializable { + + private static final long serialVersionUID = 4649790259460259026L; + + protected Map m_classesByObf; + protected Map m_classesByDeobf; + + public Mappings() { + m_classesByObf = Maps.newHashMap(); + m_classesByDeobf = Maps.newHashMap(); + } + + public Mappings(Iterable classes) { + this(); + + for (ClassMapping classMapping : classes) { + m_classesByObf.put(classMapping.getObfName(), classMapping); + if (classMapping.getDeobfName() != null) { + m_classesByDeobf.put(classMapping.getDeobfName(), classMapping); + } + } + } + + public static Mappings newFromResource(String resource) throws IOException { + InputStream in = null; + try { + in = Mappings.class.getResourceAsStream(resource); + return newFromStream(in); + } finally { + Util.closeQuietly(in); + } + } + + public Collection classes() { + assert (m_classesByObf.size() >= m_classesByDeobf.size()); + return m_classesByObf.values(); + } + + public void addClassMapping(ClassMapping classMapping) { + if (m_classesByObf.containsKey(classMapping.getObfName())) { + throw new Error("Already have mapping for " + classMapping.getObfName()); + } + boolean obfWasAdded = m_classesByObf.put(classMapping.getObfName(), classMapping) == null; + assert (obfWasAdded); + if (classMapping.getDeobfName() != null) { + if (m_classesByDeobf.containsKey(classMapping.getDeobfName())) { + throw new Error("Already have mapping for " + classMapping.getDeobfName()); + } + boolean deobfWasAdded = m_classesByDeobf.put(classMapping.getDeobfName(), classMapping) == null; + assert (deobfWasAdded); + } + } + + public void removeClassMapping(ClassMapping classMapping) { + boolean obfWasRemoved = m_classesByObf.remove(classMapping.getObfName()) != null; + assert (obfWasRemoved); + if (classMapping.getDeobfName() != null) { + boolean deobfWasRemoved = m_classesByDeobf.remove(classMapping.getDeobfName()) != null; + assert (deobfWasRemoved); + } + } + + public ClassMapping getClassByObf(ClassEntry entry) { + return getClassByObf(entry.getName()); + } + + public ClassMapping getClassByObf(String obfName) { + return m_classesByObf.get(obfName); + } + + public ClassMapping getClassByDeobf(ClassEntry entry) { + return getClassByDeobf(entry.getName()); + } + + public ClassMapping getClassByDeobf(String deobfName) { + return m_classesByDeobf.get(deobfName); + } + + public Translator getTranslator(TranslationDirection direction, TranslationIndex index) { + switch (direction) { + case Deobfuscating: + + return new Translator(direction, m_classesByObf, index); + + case Obfuscating: + + // fill in the missing deobf class entries with obf entries + Map classes = Maps.newHashMap(); + for (ClassMapping classMapping : classes()) { + if (classMapping.getDeobfName() != null) { + classes.put(classMapping.getDeobfName(), classMapping); + } else { + classes.put(classMapping.getObfName(), classMapping); + } + } + + // translate the translation index + // NOTE: this isn't actually recursive + TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.Deobfuscating, index)); + + return new Translator(direction, classes, deobfIndex); + + default: + throw new Error("Invalid translation direction!"); + } + } + + public static Mappings newFromStream(InputStream in) throws IOException { + try { + return (Mappings)new ObjectInputStream(new GZIPInputStream(in)).readObject(); + } catch (ClassNotFoundException ex) { + throw new Error(ex); + } + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + for (ClassMapping classMapping : m_classesByObf.values()) { + buf.append(classMapping.toString()); + buf.append("\n"); + } + return buf.toString(); + } + + public void renameObfClass(String oldObfName, String newObfName) { + for (ClassMapping classMapping : new ArrayList(classes())) { + if (classMapping.renameObfClass(oldObfName, newObfName)) { + boolean wasRemoved = m_classesByObf.remove(oldObfName) != null; + assert (wasRemoved); + boolean wasAdded = m_classesByObf.put(newObfName, classMapping) == null; + assert (wasAdded); + } + } + } + + public Set getAllObfClassNames() { + final Set classNames = Sets.newHashSet(); + for (ClassMapping classMapping : classes()) { + // add the class name + classNames.add(classMapping.getObfName()); + + // add classes from method signatures + for (MethodMapping methodMapping : classMapping.methods()) { + SignatureUpdater.update(methodMapping.getObfSignature(), new ClassNameUpdater() { + @Override + public String update(String className) { + classNames.add(className); + return className; + } + }); + } + } + return classNames; + } + + public boolean containsDeobfClass(String deobfName) { + return m_classesByDeobf.containsKey(deobfName); + } + + public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName) { + ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName()); + if (classMapping != null) { + return classMapping.containsDeobfField(deobfName); + } + return false; + } + + public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, String deobfSignature) { + ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName()); + if (classMapping != null) { + return classMapping.containsDeobfMethod(deobfName, deobfSignature); + } + return false; + } + + public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) { + ClassMapping classMapping = m_classesByObf.get(obfBehaviorEntry.getClassName()); + if (classMapping != null) { + return classMapping.containsArgument(obfBehaviorEntry, name); + } + return false; + } +} diff --git a/src/cuchaz/enigma/mapping/MappingsReader.java b/src/cuchaz/enigma/mapping/MappingsReader.java new file mode 100644 index 0000000..72e829d --- /dev/null +++ b/src/cuchaz/enigma/mapping/MappingsReader.java @@ -0,0 +1,176 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.util.Deque; + +import com.google.common.collect.Queues; + +import cuchaz.enigma.Constants; +import cuchaz.enigma.mapping.SignatureUpdater.ClassNameUpdater; + +public class MappingsReader { + + public Mappings read(Reader in) throws IOException, MappingParseException { + return read(new BufferedReader(in)); + } + + public Mappings read(BufferedReader in) throws IOException, MappingParseException { + Mappings mappings = new Mappings(); + Deque mappingStack = Queues.newArrayDeque(); + + int lineNumber = 0; + String line = null; + while ( (line = in.readLine()) != null) { + lineNumber++; + + // strip comments + int commentPos = line.indexOf('#'); + if (commentPos >= 0) { + line = line.substring(0, commentPos); + } + + // skip blank lines + if (line.trim().length() <= 0) { + continue; + } + + // get the indent of this line + int indent = 0; + for (int i = 0; i < line.length(); i++) { + if (line.charAt(i) != '\t') { + break; + } + indent++; + } + + // handle stack pops + while (indent < mappingStack.size()) { + mappingStack.pop(); + } + + String[] parts = line.trim().split("\\s"); + try { + // read the first token + String token = parts[0]; + + if (token.equalsIgnoreCase("CLASS")) { + ClassMapping classMapping; + if (indent == 0) { + // outer class + classMapping = readClass(parts, false); + mappings.addClassMapping(classMapping); + } else if (indent == 1) { + // inner class + if (! (mappingStack.getFirst() instanceof ClassMapping)) { + throw new MappingParseException(lineNumber, "Unexpected CLASS entry here!"); + } + + classMapping = readClass(parts, true); + ((ClassMapping)mappingStack.getFirst()).addInnerClassMapping(classMapping); + } else { + throw new MappingParseException(lineNumber, "Unexpected CLASS entry nesting!"); + } + mappingStack.push(classMapping); + } else if (token.equalsIgnoreCase("FIELD")) { + if (mappingStack.isEmpty() || ! (mappingStack.getFirst() instanceof ClassMapping)) { + throw new MappingParseException(lineNumber, "Unexpected FIELD entry here!"); + } + ((ClassMapping)mappingStack.getFirst()).addFieldMapping(readField(parts)); + } else if (token.equalsIgnoreCase("METHOD")) { + if (mappingStack.isEmpty() || ! (mappingStack.getFirst() instanceof ClassMapping)) { + throw new MappingParseException(lineNumber, "Unexpected METHOD entry here!"); + } + MethodMapping methodMapping = readMethod(parts); + ((ClassMapping)mappingStack.getFirst()).addMethodMapping(methodMapping); + mappingStack.push(methodMapping); + } else if (token.equalsIgnoreCase("ARG")) { + if (mappingStack.isEmpty() || ! (mappingStack.getFirst() instanceof MethodMapping)) { + throw new MappingParseException(lineNumber, "Unexpected ARG entry here!"); + } + ((MethodMapping)mappingStack.getFirst()).addArgumentMapping(readArgument(parts)); + } + } catch (ArrayIndexOutOfBoundsException | NumberFormatException ex) { + throw new MappingParseException(lineNumber, "Malformed line!"); + } + } + + return mappings; + } + + private ArgumentMapping readArgument(String[] parts) { + return new ArgumentMapping(Integer.parseInt(parts[1]), parts[2]); + } + + private ClassMapping readClass(String[] parts, boolean makeSimple) { + if (parts.length == 2) { + String obfName = processName(parts[1], makeSimple); + return new ClassMapping(obfName); + } else { + String obfName = processName(parts[1], makeSimple); + String deobfName = processName(parts[2], makeSimple); + return new ClassMapping(obfName, deobfName); + } + } + + private String processName(String name, boolean makeSimple) { + if (makeSimple) { + return new ClassEntry(name).getSimpleName(); + } else { + return moveClassOutOfDefaultPackage(name, Constants.NonePackage); + } + } + + private String moveClassOutOfDefaultPackage(String className, String newPackageName) { + ClassEntry classEntry = new ClassEntry(className); + if (classEntry.isInDefaultPackage()) { + return newPackageName + "/" + classEntry.getName(); + } + return className; + } + + private FieldMapping readField(String[] parts) { + return new FieldMapping(parts[1], parts[2]); + } + + private MethodMapping readMethod(String[] parts) { + if (parts.length == 3) { + String obfName = parts[1]; + String obfSignature = moveSignatureOutOfDefaultPackage(parts[2], Constants.NonePackage); + return new MethodMapping(obfName, obfSignature); + } else { + String obfName = parts[1]; + String deobfName = parts[2]; + String obfSignature = moveSignatureOutOfDefaultPackage(parts[3], Constants.NonePackage); + if (obfName.equals(deobfName)) { + return new MethodMapping(obfName, obfSignature); + } else { + return new MethodMapping(obfName, obfSignature, deobfName); + } + } + } + + private String moveSignatureOutOfDefaultPackage(String signature, final String newPackageName) { + return SignatureUpdater.update(signature, new ClassNameUpdater() { + @Override + public String update(String className) { + ClassEntry classEntry = new ClassEntry(className); + if (classEntry.isInDefaultPackage()) { + return newPackageName + "/" + className; + } + return className; + } + }); + } +} diff --git a/src/cuchaz/enigma/mapping/MappingsRenamer.java b/src/cuchaz/enigma/mapping/MappingsRenamer.java new file mode 100644 index 0000000..3aac65a --- /dev/null +++ b/src/cuchaz/enigma/mapping/MappingsRenamer.java @@ -0,0 +1,237 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.util.Set; +import java.util.zip.GZIPOutputStream; + +import cuchaz.enigma.Constants; +import cuchaz.enigma.analysis.JarIndex; + +public class MappingsRenamer { + + private JarIndex m_index; + private Mappings m_mappings; + + public MappingsRenamer(JarIndex index, Mappings mappings) { + m_index = index; + m_mappings = mappings; + } + + public void setClassName(ClassEntry obf, String deobfName) { + deobfName = NameValidator.validateClassName(deobfName, !obf.isInnerClass()); + ClassEntry targetEntry = new ClassEntry(deobfName); + if (m_mappings.containsDeobfClass(deobfName) || m_index.containsObfClass(targetEntry)) { + throw new IllegalNameException(deobfName, "There is already a class with that name"); + } + + ClassMapping classMapping = getOrCreateClassMapping(obf); + + if (obf.isInnerClass()) { + classMapping.setInnerClassName(obf.getInnerClassName(), deobfName); + } else { + if (classMapping.getDeobfName() != null) { + boolean wasRemoved = m_mappings.m_classesByDeobf.remove(classMapping.getDeobfName()) != null; + assert (wasRemoved); + } + classMapping.setDeobfName(deobfName); + boolean wasAdded = m_mappings.m_classesByDeobf.put(deobfName, classMapping) == null; + assert (wasAdded); + } + } + + public void removeClassMapping(ClassEntry obf) { + ClassMapping classMapping = getClassMapping(obf); + if (obf.isInnerClass()) { + classMapping.setInnerClassName(obf.getName(), null); + } else { + boolean wasRemoved = m_mappings.m_classesByDeobf.remove(classMapping.getDeobfName()) != null; + assert (wasRemoved); + classMapping.setDeobfName(null); + } + } + + public void markClassAsDeobfuscated(ClassEntry obf) { + ClassMapping classMapping = getOrCreateClassMapping(obf); + if (obf.isInnerClass()) { + String innerClassName = Constants.NonePackage + "/" + obf.getInnerClassName(); + classMapping.setInnerClassName(innerClassName, innerClassName); + } else { + classMapping.setDeobfName(obf.getName()); + boolean wasAdded = m_mappings.m_classesByDeobf.put(obf.getName(), classMapping) == null; + assert (wasAdded); + } + } + + public void setFieldName(FieldEntry obf, String deobfName) { + deobfName = NameValidator.validateFieldName(deobfName); + FieldEntry targetEntry = new FieldEntry(obf.getClassEntry(), deobfName); + if (m_mappings.containsDeobfField(obf.getClassEntry(), deobfName) || m_index.containsObfField(targetEntry)) { + throw new IllegalNameException(deobfName, "There is already a field with that name"); + } + + ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); + classMapping.setFieldName(obf.getName(), deobfName); + } + + public void removeFieldMapping(FieldEntry obf) { + ClassMapping classMapping = getClassMappingOrInnerClassMapping(obf.getClassEntry()); + classMapping.setFieldName(obf.getName(), null); + } + + public void markFieldAsDeobfuscated(FieldEntry obf) { + ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); + classMapping.setFieldName(obf.getName(), obf.getName()); + } + + public void setMethodTreeName(MethodEntry obf, String deobfName) { + Set implementations = m_index.getRelatedMethodImplementations(obf); + + deobfName = NameValidator.validateMethodName(deobfName); + for (MethodEntry entry : implementations) { + String deobfSignature = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateSignature(obf.getSignature()); + MethodEntry targetEntry = new MethodEntry(entry.getClassEntry(), deobfName, deobfSignature); + if (m_mappings.containsDeobfMethod(entry.getClassEntry(), deobfName, entry.getSignature()) || m_index.containsObfBehavior(targetEntry)) { + String deobfClassName = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateClass(entry.getClassName()); + throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName); + } + } + + for (MethodEntry entry : implementations) { + setMethodName(entry, deobfName); + } + } + + public void setMethodName(MethodEntry obf, String deobfName) { + deobfName = NameValidator.validateMethodName(deobfName); + MethodEntry targetEntry = new MethodEntry(obf.getClassEntry(), deobfName, obf.getSignature()); + if (m_mappings.containsDeobfMethod(obf.getClassEntry(), deobfName, obf.getSignature()) || m_index.containsObfBehavior(targetEntry)) { + String deobfClassName = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateClass(obf.getClassName()); + throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName); + } + + ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); + classMapping.setMethodName(obf.getName(), obf.getSignature(), deobfName); + } + + public void removeMethodTreeMapping(MethodEntry obf) { + for (MethodEntry implementation : m_index.getRelatedMethodImplementations(obf)) { + removeMethodMapping(implementation); + } + } + + public void removeMethodMapping(MethodEntry obf) { + ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); + classMapping.setMethodName(obf.getName(), obf.getSignature(), null); + } + + public void markMethodTreeAsDeobfuscated(MethodEntry obf) { + for (MethodEntry implementation : m_index.getRelatedMethodImplementations(obf)) { + markMethodAsDeobfuscated(implementation); + } + } + + public void markMethodAsDeobfuscated(MethodEntry obf) { + ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); + classMapping.setMethodName(obf.getName(), obf.getSignature(), obf.getName()); + } + + public void setArgumentName(ArgumentEntry obf, String deobfName) { + deobfName = NameValidator.validateArgumentName(deobfName); + // NOTE: don't need to check arguments for name collisions with names determined by Procyon + if (m_mappings.containsArgument(obf.getBehaviorEntry(), deobfName)) { + throw new IllegalNameException(deobfName, "There is already an argument with that name"); + } + + ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); + classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), deobfName); + } + + public void removeArgumentMapping(ArgumentEntry obf) { + ClassMapping classMapping = getClassMappingOrInnerClassMapping(obf.getClassEntry()); + classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex()); + } + + public void markArgumentAsDeobfuscated(ArgumentEntry obf) { + ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); + classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), 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())) { + if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName())) { + 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.getObfSignature())) { + if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfSignature())) { + targetClassMapping.addMethodMapping(methodMapping); + return true; + } else { + System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfSignature()); + } + } + 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 getClassMapping(ClassEntry obfClassEntry) { + return m_mappings.m_classesByObf.get(obfClassEntry.getOuterClassName()); + } + + private ClassMapping getOrCreateClassMapping(ClassEntry obfClassEntry) { + String obfClassName = obfClassEntry.getOuterClassName(); + ClassMapping classMapping = m_mappings.m_classesByObf.get(obfClassName); + if (classMapping == null) { + classMapping = new ClassMapping(obfClassName); + boolean obfWasAdded = m_mappings.m_classesByObf.put(classMapping.getObfName(), classMapping) == null; + assert (obfWasAdded); + } + return classMapping; + } + + private ClassMapping getClassMappingOrInnerClassMapping(ClassEntry obfClassEntry) { + ClassMapping classMapping = getClassMapping(obfClassEntry); + if (obfClassEntry.isInDefaultPackage()) { + classMapping = classMapping.getInnerClassByObf(obfClassEntry.getInnerClassName()); + } + return classMapping; + } + + private ClassMapping getOrCreateClassMappingOrInnerClassMapping(ClassEntry obfClassEntry) { + ClassMapping classMapping = getOrCreateClassMapping(obfClassEntry); + if (obfClassEntry.isInnerClass()) { + classMapping = classMapping.getOrCreateInnerClass(obfClassEntry.getInnerClassName()); + } + return classMapping; + } +} diff --git a/src/cuchaz/enigma/mapping/MappingsWriter.java b/src/cuchaz/enigma/mapping/MappingsWriter.java new file mode 100644 index 0000000..5ac409f --- /dev/null +++ b/src/cuchaz/enigma/mapping/MappingsWriter.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class MappingsWriter { + + public void write(Writer out, Mappings mappings) throws IOException { + write(new PrintWriter(out), mappings); + } + + public void write(PrintWriter out, Mappings mappings) throws IOException { + for (ClassMapping classMapping : sorted(mappings.classes())) { + write(out, classMapping, 0); + } + } + + private void write(PrintWriter out, ClassMapping classMapping, int depth) throws IOException { + if (classMapping.getDeobfName() == null) { + out.format("%sCLASS %s\n", getIndent(depth), classMapping.getObfName()); + } else { + out.format("%sCLASS %s %s\n", getIndent(depth), classMapping.getObfName(), classMapping.getDeobfName()); + } + + for (ClassMapping innerClassMapping : sorted(classMapping.innerClasses())) { + write(out, innerClassMapping, depth + 1); + } + + for (FieldMapping fieldMapping : sorted(classMapping.fields())) { + write(out, fieldMapping, depth + 1); + } + + for (MethodMapping methodMapping : sorted(classMapping.methods())) { + write(out, methodMapping, depth + 1); + } + } + + private void write(PrintWriter out, FieldMapping fieldMapping, int depth) throws IOException { + out.format("%sFIELD %s %s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getDeobfName()); + } + + private void write(PrintWriter out, MethodMapping methodMapping, int depth) throws IOException { + if (methodMapping.getDeobfName() == null) { + out.format("%sMETHOD %s %s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getObfSignature()); + } else { + out.format("%sMETHOD %s %s %s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getDeobfName(), methodMapping.getObfSignature()); + } + + for (ArgumentMapping argumentMapping : sorted(methodMapping.arguments())) { + write(out, argumentMapping, depth + 1); + } + } + + private void write(PrintWriter out, ArgumentMapping argumentMapping, int depth) throws IOException { + out.format("%sARG %d %s\n", getIndent(depth), argumentMapping.getIndex(), argumentMapping.getName()); + } + + private > List sorted(Iterable classes) { + List out = new ArrayList(); + for (T t : classes) { + out.add(t); + } + Collections.sort(out); + return out; + } + + private String getIndent(int depth) { + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < depth; i++) { + buf.append("\t"); + } + return buf.toString(); + } +} diff --git a/src/cuchaz/enigma/mapping/MethodEntry.java b/src/cuchaz/enigma/mapping/MethodEntry.java new file mode 100644 index 0000000..beb490d --- /dev/null +++ b/src/cuchaz/enigma/mapping/MethodEntry.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.Serializable; + +import cuchaz.enigma.Util; + +public class MethodEntry implements BehaviorEntry, Serializable { + + private static final long serialVersionUID = 4770915224467247458L; + + private ClassEntry m_classEntry; + private String m_name; + private String m_signature; + + public MethodEntry(ClassEntry classEntry, String name, String signature) { + if (classEntry == null) { + throw new IllegalArgumentException("Class cannot be null!"); + } + if (name == null) { + throw new IllegalArgumentException("Method name cannot be null!"); + } + if (signature == null) { + throw new IllegalArgumentException("Method signature cannot be null!"); + } + if (name.startsWith("<")) { + throw new IllegalArgumentException("Don't use MethodEntry for a constructor!"); + } + + m_classEntry = classEntry; + m_name = name; + m_signature = signature; + } + + public MethodEntry(MethodEntry other) { + m_classEntry = new ClassEntry(other.m_classEntry); + m_name = other.m_name; + m_signature = other.m_signature; + } + + public MethodEntry(MethodEntry other, String newClassName) { + m_classEntry = new ClassEntry(newClassName); + m_name = other.m_name; + m_signature = other.m_signature; + } + + @Override + public ClassEntry getClassEntry() { + return m_classEntry; + } + + @Override + public String getName() { + return m_name; + } + + @Override + public String getSignature() { + return m_signature; + } + + @Override + public String getClassName() { + return m_classEntry.getName(); + } + + @Override + public MethodEntry cloneToNewClass(ClassEntry classEntry) { + return new MethodEntry(this, classEntry.getName()); + } + + @Override + public int hashCode() { + return Util.combineHashesOrdered(m_classEntry, m_name, m_signature); + } + + @Override + public boolean equals(Object other) { + if (other instanceof MethodEntry) { + return equals((MethodEntry)other); + } + return false; + } + + public boolean equals(MethodEntry other) { + return m_classEntry.equals(other.m_classEntry) + && m_name.equals(other.m_name) + && m_signature.equals(other.m_signature); + } + + @Override + public String toString() { + return m_classEntry.getName() + "." + m_name + m_signature; + } +} diff --git a/src/cuchaz/enigma/mapping/MethodMapping.java b/src/cuchaz/enigma/mapping/MethodMapping.java new file mode 100644 index 0000000..4dab3c6 --- /dev/null +++ b/src/cuchaz/enigma/mapping/MethodMapping.java @@ -0,0 +1,162 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.Serializable; +import java.util.Map; +import java.util.TreeMap; + +import cuchaz.enigma.mapping.SignatureUpdater.ClassNameUpdater; + +public class MethodMapping implements Serializable, Comparable { + + private static final long serialVersionUID = -4409570216084263978L; + + private String m_obfName; + private String m_deobfName; + private String m_obfSignature; + private Map m_arguments; + + public MethodMapping(String obfName, String obfSignature) { + this(obfName, obfSignature, null); + } + + public MethodMapping(String obfName, String obfSignature, String deobfName) { + if (obfName == null) { + throw new IllegalArgumentException("obf name cannot be null!"); + } + if (obfSignature == null) { + throw new IllegalArgumentException("obf signature cannot be null!"); + } + m_obfName = obfName; + m_deobfName = NameValidator.validateMethodName(deobfName); + m_obfSignature = obfSignature; + m_arguments = new TreeMap(); + } + + public String getObfName() { + return m_obfName; + } + + public String getDeobfName() { + return m_deobfName; + } + + public void setDeobfName(String val) { + m_deobfName = NameValidator.validateMethodName(val); + } + + public String getObfSignature() { + return m_obfSignature; + } + + public Iterable arguments() { + return m_arguments.values(); + } + + public boolean isConstructor() { + return m_obfName.startsWith("<"); + } + + public void addArgumentMapping(ArgumentMapping argumentMapping) { + boolean wasAdded = m_arguments.put(argumentMapping.getIndex(), argumentMapping) == null; + assert (wasAdded); + } + + public String getObfArgumentName(int index) { + ArgumentMapping argumentMapping = m_arguments.get(index); + if (argumentMapping != null) { + return argumentMapping.getName(); + } + + return null; + } + + public String getDeobfArgumentName(int index) { + ArgumentMapping argumentMapping = m_arguments.get(index); + if (argumentMapping != null) { + return argumentMapping.getName(); + } + + return null; + } + + public void setArgumentName(int index, String name) { + ArgumentMapping argumentMapping = m_arguments.get(index); + if (argumentMapping == null) { + argumentMapping = new ArgumentMapping(index, name); + boolean wasAdded = m_arguments.put(index, argumentMapping) == null; + assert (wasAdded); + } else { + argumentMapping.setName(name); + } + } + + public void removeArgumentName(int index) { + boolean wasRemoved = m_arguments.remove(index) != null; + assert (wasRemoved); + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("\t"); + buf.append(m_obfName); + buf.append(" <-> "); + buf.append(m_deobfName); + buf.append("\n"); + buf.append("\t"); + buf.append(m_obfSignature); + buf.append("\n"); + buf.append("\tArguments:\n"); + for (ArgumentMapping argumentMapping : m_arguments.values()) { + buf.append("\t\t"); + buf.append(argumentMapping.getIndex()); + buf.append(" -> "); + buf.append(argumentMapping.getName()); + buf.append("\n"); + } + return buf.toString(); + } + + @Override + public int compareTo(MethodMapping other) { + return (m_obfName + m_obfSignature).compareTo(other.m_obfName + other.m_obfSignature); + } + + public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) { + // rename obf classes in the signature + String newSignature = SignatureUpdater.update(m_obfSignature, new ClassNameUpdater() { + @Override + public String update(String className) { + if (className.equals(oldObfClassName)) { + return newObfClassName; + } + return className; + } + }); + + if (newSignature != m_obfSignature) { + m_obfSignature = newSignature; + return true; + } + return false; + } + + public boolean containsArgument(String name) { + for (ArgumentMapping argumentMapping : m_arguments.values()) { + if (argumentMapping.getName().equals(name)) { + return true; + } + } + return false; + } +} diff --git a/src/cuchaz/enigma/mapping/NameValidator.java b/src/cuchaz/enigma/mapping/NameValidator.java new file mode 100644 index 0000000..35a17f9 --- /dev/null +++ b/src/cuchaz/enigma/mapping/NameValidator.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +import javassist.bytecode.Descriptor; + +public class NameValidator { + + private static final Pattern IdentifierPattern; + private static final Pattern ClassPattern; + private static final List ReservedWords = Arrays.asList( + "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", + "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte", + "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch", + "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally", + "long", "strictfp", "volatile", "const", "float", "native", "super", "while" + ); + + static { + + // java allows all kinds of weird characters... + StringBuilder startChars = new StringBuilder(); + StringBuilder partChars = new StringBuilder(); + for (int i = Character.MIN_CODE_POINT; i <= Character.MAX_CODE_POINT; i++) { + if (Character.isJavaIdentifierStart(i)) { + startChars.appendCodePoint(i); + } + if (Character.isJavaIdentifierPart(i)) { + partChars.appendCodePoint(i); + } + } + + String identifierRegex = "[A-Za-z_<][A-Za-z0-9_>]*"; + IdentifierPattern = Pattern.compile(identifierRegex); + ClassPattern = Pattern.compile(String.format("^(%s(\\.|/))*(%s)$", identifierRegex, identifierRegex)); + } + + public static String validateClassName(String name, boolean packageRequired) { + if (name == null) { + return null; + } + if (!ClassPattern.matcher(name).matches() || ReservedWords.contains(name)) { + throw new IllegalNameException(name, "This doesn't look like a legal class name"); + } + if (packageRequired && new ClassEntry(name).getPackageName() == null) { + throw new IllegalNameException(name, "Class must be in a package"); + } + return Descriptor.toJvmName(name); + } + + public static String validateFieldName(String name) { + if (name == null) { + return null; + } + if (!IdentifierPattern.matcher(name).matches() || ReservedWords.contains(name)) { + throw new IllegalNameException(name, "This doesn't look like a legal identifier"); + } + return name; + } + + public static String validateMethodName(String name) { + return validateFieldName(name); + } + + public static String validateArgumentName(String name) { + return validateFieldName(name); + } +} diff --git a/src/cuchaz/enigma/mapping/SignatureUpdater.java b/src/cuchaz/enigma/mapping/SignatureUpdater.java new file mode 100644 index 0000000..3477cd5 --- /dev/null +++ b/src/cuchaz/enigma/mapping/SignatureUpdater.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.io.IOException; +import java.io.StringReader; +import java.util.List; + +import com.google.common.collect.Lists; + +public class SignatureUpdater { + + public interface ClassNameUpdater { + String update(String className); + } + + public static String update(String signature, ClassNameUpdater updater) { + try { + StringBuilder buf = new StringBuilder(); + + // read the signature character-by-character + StringReader reader = new StringReader(signature); + int i = -1; + while ( (i = reader.read()) != -1) { + char c = (char)i; + + // does this character start a class name? + if (c == 'L') { + // update the class name and add it to the buffer + buf.append('L'); + String className = readClass(reader); + if (className == null) { + throw new IllegalArgumentException("Malformed signature: " + signature); + } + buf.append(updater.update(className)); + buf.append(';'); + } else { + // copy the character into the buffer + buf.append(c); + } + } + + return buf.toString(); + } catch (IOException ex) { + // I'm pretty sure a StringReader will never throw one of these + throw new Error(ex); + } + } + + private static String readClass(StringReader reader) throws IOException { + // read all the characters in the buffer until we hit a ';' + // remember to treat generics correctly + StringBuilder buf = new StringBuilder(); + int depth = 0; + int i = -1; + while ( (i = reader.read()) != -1) { + char c = (char)i; + + if (c == '<') { + depth++; + } else if (c == '>') { + depth--; + } else if (depth == 0) { + if (c == ';') { + return buf.toString(); + } else { + buf.append(c); + } + } + } + + return null; + } + + public static List getClasses(String signature) { + final List classNames = Lists.newArrayList(); + update(signature, new ClassNameUpdater() { + @Override + public String update(String className) { + classNames.add(className); + return className; + } + }); + return classNames; + } +} diff --git a/src/cuchaz/enigma/mapping/TranslationDirection.java b/src/cuchaz/enigma/mapping/TranslationDirection.java new file mode 100644 index 0000000..d1b14cd --- /dev/null +++ b/src/cuchaz/enigma/mapping/TranslationDirection.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +public enum TranslationDirection { + + Deobfuscating { + @Override + public T choose(T deobfChoice, T obfChoice) { + return deobfChoice; + } + }, + Obfuscating { + @Override + public T choose(T deobfChoice, T obfChoice) { + return obfChoice; + } + }; + + public abstract T choose(T deobfChoice, T obfChoice); +} diff --git a/src/cuchaz/enigma/mapping/Translator.java b/src/cuchaz/enigma/mapping/Translator.java new file mode 100644 index 0000000..a5a3e2f --- /dev/null +++ b/src/cuchaz/enigma/mapping/Translator.java @@ -0,0 +1,235 @@ +/******************************************************************************* + * Copyright (c) 2014 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ +package cuchaz.enigma.mapping; + +import java.util.Map; + +import com.google.common.collect.Maps; + +import cuchaz.enigma.analysis.TranslationIndex; +import cuchaz.enigma.mapping.SignatureUpdater.ClassNameUpdater; + +public class Translator { + + private TranslationDirection m_direction; + private Map m_classes; + private TranslationIndex m_index; + + public Translator() { + m_direction = null; + m_classes = Maps.newHashMap(); + } + + public Translator(TranslationDirection direction, Map classes, TranslationIndex index) { + m_direction = direction; + m_classes = classes; + m_index = index; + } + + @SuppressWarnings("unchecked") + public T translateEntry(T entry) { + if (entry instanceof ClassEntry) { + return (T)translateEntry((ClassEntry)entry); + } else if (entry instanceof FieldEntry) { + return (T)translateEntry((FieldEntry)entry); + } else if (entry instanceof MethodEntry) { + return (T)translateEntry((MethodEntry)entry); + } else if (entry instanceof ConstructorEntry) { + return (T)translateEntry((ConstructorEntry)entry); + } else if (entry instanceof ArgumentEntry) { + return (T)translateEntry((ArgumentEntry)entry); + } else { + throw new Error("Unknown entry type: " + entry.getClass().getName()); + } + } + + public String translateClass(String className) { + return translate(new ClassEntry(className)); + } + + public String translate(ClassEntry in) { + ClassMapping classMapping = m_classes.get(in.getOuterClassName()); + if (classMapping != null) { + if (in.isInnerClass()) { + // translate the inner class + String translatedInnerClassName = m_direction.choose( + classMapping.getDeobfInnerClassName(in.getInnerClassName()), + classMapping.getObfInnerClassName(in.getInnerClassName()) + ); + if (translatedInnerClassName != null) { + // try to translate the outer name + String translatedOuterClassName = m_direction.choose(classMapping.getDeobfName(), classMapping.getObfName()); + if (translatedOuterClassName != null) { + return translatedOuterClassName + "$" + translatedInnerClassName; + } else { + return in.getOuterClassName() + "$" + translatedInnerClassName; + } + } + } else { + // just return outer + return m_direction.choose(classMapping.getDeobfName(), classMapping.getObfName()); + } + } + return null; + } + + public ClassEntry translateEntry(ClassEntry in) { + + // can we translate the inner class? + String name = translate(in); + if (name != null) { + return new ClassEntry(name); + } + + if (in.isInnerClass()) { + + // guess not. just translate the outer class name then + String outerClassName = translate(in.getOuterClassEntry()); + if (outerClassName != null) { + return new ClassEntry(outerClassName + "$" + in.getInnerClassName()); + } + } + + return in; + } + + public String translate(FieldEntry in) { + + // resolve the class entry + ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in); + if (resolvedClassEntry != null) { + + // look for the class + ClassMapping classMapping = findClassMapping(resolvedClassEntry); + if (classMapping != null) { + + // look for the field + String translatedName = m_direction.choose( + classMapping.getDeobfFieldName(in.getName()), + classMapping.getObfFieldName(in.getName()) + ); + if (translatedName != null) { + return translatedName; + } + } + } + return null; + } + + public FieldEntry translateEntry(FieldEntry in) { + String name = translate(in); + if (name == null) { + name = in.getName(); + } + return new FieldEntry(translateEntry(in.getClassEntry()), name); + } + + public String translate(MethodEntry in) { + + // resolve the class entry + ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in); + if (resolvedClassEntry != null) { + + // look for class + ClassMapping classMapping = findClassMapping(resolvedClassEntry); + if (classMapping != null) { + + // look for the method + MethodMapping methodMapping = m_direction.choose( + classMapping.getMethodByObf(in.getName(), in.getSignature()), + classMapping.getMethodByDeobf(in.getName(), translateSignature(in.getSignature())) + ); + if (methodMapping != null) { + return m_direction.choose(methodMapping.getDeobfName(), methodMapping.getObfName()); + } + } + } + return null; + } + + public MethodEntry translateEntry(MethodEntry in) { + String name = translate(in); + if (name == null) { + name = in.getName(); + } + return new MethodEntry(translateEntry(in.getClassEntry()), name, translateSignature(in.getSignature())); + } + + public ConstructorEntry translateEntry(ConstructorEntry in) { + if (in.isStatic()) { + return new ConstructorEntry(translateEntry(in.getClassEntry())); + } else { + return new ConstructorEntry(translateEntry(in.getClassEntry()), translateSignature(in.getSignature())); + } + } + + public BehaviorEntry translateEntry(BehaviorEntry in) { + if (in instanceof MethodEntry) { + return translateEntry((MethodEntry)in); + } else if (in instanceof ConstructorEntry) { + return translateEntry((ConstructorEntry)in); + } + throw new Error("Wrong entry type!"); + } + + public String translate(ArgumentEntry in) { + + // look for the class + ClassMapping classMapping = findClassMapping(in.getClassEntry()); + if (classMapping != null) { + + // look for the method + MethodMapping methodMapping = m_direction.choose( + classMapping.getMethodByObf(in.getMethodName(), in.getMethodSignature()), + classMapping.getMethodByDeobf(in.getMethodName(), translateSignature(in.getMethodSignature())) + ); + if (methodMapping != null) { + return m_direction.choose( + methodMapping.getDeobfArgumentName(in.getIndex()), + methodMapping.getObfArgumentName(in.getIndex()) + ); + } + } + return null; + } + + public ArgumentEntry translateEntry(ArgumentEntry in) { + String name = translate(in); + if (name == null) { + name = in.getName(); + } + return new ArgumentEntry(translateEntry(in.getBehaviorEntry()), in.getIndex(), name); + } + + public String translateSignature(String signature) { + return SignatureUpdater.update(signature, new ClassNameUpdater() { + @Override + public String update(String className) { + String translatedName = translateClass(className); + if (translatedName != null) { + return translatedName; + } + return className; + } + }); + } + + private ClassMapping findClassMapping(ClassEntry classEntry) { + ClassMapping classMapping = m_classes.get(classEntry.getOuterClassName()); + if (classMapping != null && classEntry.isInnerClass()) { + classMapping = m_direction.choose( + classMapping.getInnerClassByObf(classEntry.getInnerClassName()), + classMapping.getInnerClassByDeobfThenObf(classEntry.getInnerClassName()) + ); + } + return classMapping; + } +} -- cgit v1.2.3