From 6e464ea251cab63c776ece0b2a356f1498ffa294 Mon Sep 17 00:00:00 2001 From: Thog Date: Wed, 8 Mar 2017 08:17:04 +0100 Subject: Follow Fabric guidelines --- .../java/cuchaz/enigma/convert/ClassForest.java | 79 +- .../cuchaz/enigma/convert/ClassIdentifier.java | 61 +- .../java/cuchaz/enigma/convert/ClassIdentity.java | 821 ++++++------ .../java/cuchaz/enigma/convert/ClassMatch.java | 106 +- .../java/cuchaz/enigma/convert/ClassMatches.java | 273 ++-- .../java/cuchaz/enigma/convert/ClassMatching.java | 256 ++-- .../java/cuchaz/enigma/convert/ClassNamer.java | 71 +- .../java/cuchaz/enigma/convert/FieldMatches.java | 261 ++-- .../cuchaz/enigma/convert/MappingsConverter.java | 1363 ++++++++++---------- .../java/cuchaz/enigma/convert/MatchesReader.java | 157 ++- .../java/cuchaz/enigma/convert/MatchesWriter.java | 185 +-- .../java/cuchaz/enigma/convert/MemberMatches.java | 315 +++-- 12 files changed, 1968 insertions(+), 1980 deletions(-) (limited to 'src/main/java/cuchaz/enigma/convert') diff --git a/src/main/java/cuchaz/enigma/convert/ClassForest.java b/src/main/java/cuchaz/enigma/convert/ClassForest.java index b08d48f..4542fb3 100644 --- a/src/main/java/cuchaz/enigma/convert/ClassForest.java +++ b/src/main/java/cuchaz/enigma/convert/ClassForest.java @@ -8,53 +8,52 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.convert; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; - -import java.util.Collection; - import cuchaz.enigma.mapping.ClassEntry; +import java.util.Collection; public class ClassForest { - private ClassIdentifier identifier; - private Multimap forest; - - public ClassForest(ClassIdentifier identifier) { - this.identifier = identifier; - this.forest = HashMultimap.create(); - } - - public void addAll(Iterable entries) { - for (ClassEntry entry : entries) { - add(entry); - } - } - - public void add(ClassEntry entry) { - try { - this.forest.put(this.identifier.identify(entry), entry); - } catch (ClassNotFoundException ex) { - throw new Error("Unable to find class " + entry.getName()); - } - } - - public Collection identities() { - return this.forest.keySet(); - } - - public Collection classes() { - return this.forest.values(); - } - - public Collection getClasses(ClassIdentity identity) { - return this.forest.get(identity); - } - - public boolean containsIdentity(ClassIdentity identity) { - return this.forest.containsKey(identity); - } + private ClassIdentifier identifier; + private Multimap forest; + + public ClassForest(ClassIdentifier identifier) { + this.identifier = identifier; + this.forest = HashMultimap.create(); + } + + public void addAll(Iterable entries) { + for (ClassEntry entry : entries) { + add(entry); + } + } + + public void add(ClassEntry entry) { + try { + this.forest.put(this.identifier.identify(entry), entry); + } catch (ClassNotFoundException ex) { + throw new Error("Unable to find class " + entry.getName()); + } + } + + public Collection identities() { + return this.forest.keySet(); + } + + public Collection classes() { + return this.forest.values(); + } + + public Collection getClasses(ClassIdentity identity) { + return this.forest.get(identity); + } + + public boolean containsIdentity(ClassIdentity identity) { + return this.forest.containsKey(identity); + } } diff --git a/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java b/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java index 557e608..0a72073 100644 --- a/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java +++ b/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java @@ -8,13 +8,10 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.convert; import com.google.common.collect.Maps; - -import java.util.Map; -import java.util.jar.JarFile; - import cuchaz.enigma.TranslatingTypeLoader; import cuchaz.enigma.analysis.JarIndex; import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; @@ -22,34 +19,36 @@ import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.Translator; import javassist.CtClass; +import java.util.Map; +import java.util.jar.JarFile; public class ClassIdentifier { - private JarIndex index; - private SidedClassNamer namer; - private boolean useReferences; - private TranslatingTypeLoader loader; - private Map cache; - - public ClassIdentifier(JarFile jar, JarIndex index, SidedClassNamer namer, boolean useReferences) { - this.index = index; - this.namer = namer; - this.useReferences = useReferences; - this.loader = new TranslatingTypeLoader(jar, index, new Translator(), new Translator()); - this.cache = Maps.newHashMap(); - } - - public ClassIdentity identify(ClassEntry classEntry) - throws ClassNotFoundException { - ClassIdentity identity = this.cache.get(classEntry); - if (identity == null) { - CtClass c = this.loader.loadClass(classEntry.getName()); - if (c == null) { - throw new ClassNotFoundException(classEntry.getName()); - } - identity = new ClassIdentity(c, this.namer, this.index, this.useReferences); - this.cache.put(classEntry, identity); - } - return identity; - } + private JarIndex index; + private SidedClassNamer namer; + private boolean useReferences; + private TranslatingTypeLoader loader; + private Map cache; + + public ClassIdentifier(JarFile jar, JarIndex index, SidedClassNamer namer, boolean useReferences) { + this.index = index; + this.namer = namer; + this.useReferences = useReferences; + this.loader = new TranslatingTypeLoader(jar, index, new Translator(), new Translator()); + this.cache = Maps.newHashMap(); + } + + public ClassIdentity identify(ClassEntry classEntry) + throws ClassNotFoundException { + ClassIdentity identity = this.cache.get(classEntry); + if (identity == null) { + CtClass c = this.loader.loadClass(classEntry.getName()); + if (c == null) { + throw new ClassNotFoundException(classEntry.getName()); + } + identity = new ClassIdentity(c, this.namer, this.index, this.useReferences); + this.cache.put(classEntry, identity); + } + return identity; + } } diff --git a/src/main/java/cuchaz/enigma/convert/ClassIdentity.java b/src/main/java/cuchaz/enigma/convert/ClassIdentity.java index f72bf70..a395b75 100644 --- a/src/main/java/cuchaz/enigma/convert/ClassIdentity.java +++ b/src/main/java/cuchaz/enigma/convert/ClassIdentity.java @@ -8,18 +8,10 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.convert; import com.google.common.collect.*; - -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Enumeration; -import java.util.List; -import java.util.Map; -import java.util.Set; - import cuchaz.enigma.analysis.ClassImplementationsTreeNode; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.analysis.JarIndex; @@ -33,408 +25,415 @@ import javassist.*; import javassist.bytecode.*; import javassist.expr.*; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; +import java.util.Set; + public class ClassIdentity { - private ClassEntry classEntry; - private SidedClassNamer namer; - private Multiset fields; - private Multiset methods; - private Multiset constructors; - private String staticInitializer; - private String extendz; - private Multiset implementz; - private Set stringLiterals; - private Multiset implementations; - private Multiset references; - private String outer; - - private final ClassNameReplacer classNameReplacer = new ClassNameReplacer() { - - private Map classNames = Maps.newHashMap(); - - @Override - public String replace(String className) { - - // classes not in the none package can be passed through - ClassEntry classEntry = new ClassEntry(className); - if (classEntry.getPackageName() != null) { - return className; - } - - // is this class ourself? - if (className.equals(classEntry.getName())) { - return "CSelf"; - } - - // try the namer - if (namer != null) { - String newName = namer.getName(className); - if (newName != null) { - return newName; - } - } - - // otherwise, use local naming - if (!classNames.containsKey(className)) { - classNames.put(className, getNewClassName()); - } - return classNames.get(className); - } - - private String getNewClassName() { - return String.format("C%03d", classNames.size()); - } - }; - - public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) { - this.namer = namer; - - // stuff from the bytecode - - this.classEntry = EntryFactory.getClassEntry(c); - this.fields = HashMultiset.create(); - for (CtField field : c.getDeclaredFields()) { - this.fields.add(scrubType(field.getSignature())); - } - this.methods = HashMultiset.create(); - for (CtMethod method : c.getDeclaredMethods()) { - this.methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method)); - } - this.constructors = HashMultiset.create(); - for (CtConstructor constructor : c.getDeclaredConstructors()) { - this.constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor)); - } - this.staticInitializer = ""; - if (c.getClassInitializer() != null) { - this.staticInitializer = getBehaviorSignature(c.getClassInitializer()); - } - this.extendz = ""; - if (c.getClassFile().getSuperclass() != null) { - this.extendz = scrubClassName(Descriptor.toJvmName(c.getClassFile().getSuperclass())); - } - this.implementz = HashMultiset.create(); - for (String interfaceName : c.getClassFile().getInterfaces()) { - this.implementz.add(scrubClassName(Descriptor.toJvmName(interfaceName))); - } - - this.stringLiterals = Sets.newHashSet(); - ConstPool constants = c.getClassFile().getConstPool(); - for (int i = 1; i < constants.getSize(); i++) { - if (constants.getTag(i) == ConstPool.CONST_String) { - this.stringLiterals.add(constants.getStringInfo(i)); - } - } - - // stuff from the jar index - - this.implementations = HashMultiset.create(); - ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, this.classEntry); - if (implementationsNode != null) { - @SuppressWarnings("unchecked") - Enumeration implementations = implementationsNode.children(); - while (implementations.hasMoreElements()) { - ClassImplementationsTreeNode node = implementations.nextElement(); - this.implementations.add(scrubClassName(node.getClassEntry().getName())); - } - } - - this.references = HashMultiset.create(); - if (useReferences) { - for (CtField field : c.getDeclaredFields()) { - FieldEntry fieldEntry = EntryFactory.getFieldEntry(field); - index.getFieldReferences(fieldEntry).forEach(this::addReference); - } - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - index.getBehaviorReferences(behaviorEntry).forEach(this::addReference); - } - } - - this.outer = null; - if (this.classEntry.isInnerClass()) { - this.outer = this.classEntry.getOuterClassName(); - } - } - - private void addReference(EntryReference reference) { - if (reference.context.getSignature() != null) { - this.references.add(String.format("%s_%s", - scrubClassName(reference.context.getClassName()), - scrubSignature(reference.context.getSignature()) - )); - } else { - this.references.add(String.format("%s_", - scrubClassName(reference.context.getClassName()) - )); - } - } - - public ClassEntry getClassEntry() { - return this.classEntry; - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - buf.append("class: "); - buf.append(this.classEntry.getName()); - buf.append(" "); - buf.append(hashCode()); - buf.append("\n"); - for (String field : this.fields) { - buf.append("\tfield "); - buf.append(field); - buf.append("\n"); - } - for (String method : this.methods) { - buf.append("\tmethod "); - buf.append(method); - buf.append("\n"); - } - for (String constructor : this.constructors) { - buf.append("\tconstructor "); - buf.append(constructor); - buf.append("\n"); - } - if (this.staticInitializer.length() > 0) { - buf.append("\tinitializer "); - buf.append(this.staticInitializer); - buf.append("\n"); - } - if (this.extendz.length() > 0) { - buf.append("\textends "); - buf.append(this.extendz); - buf.append("\n"); - } - for (String interfaceName : this.implementz) { - buf.append("\timplements "); - buf.append(interfaceName); - buf.append("\n"); - } - for (String implementation : this.implementations) { - buf.append("\timplemented by "); - buf.append(implementation); - buf.append("\n"); - } - for (String reference : this.references) { - buf.append("\treference "); - buf.append(reference); - buf.append("\n"); - } - buf.append("\touter "); - buf.append(this.outer); - buf.append("\n"); - return buf.toString(); - } - - private String scrubClassName(String className) { - return classNameReplacer.replace(className); - } - - private String scrubType(String typeName) { - return scrubType(new Type(typeName)).toString(); - } - - private Type scrubType(Type type) { - if (type.hasClass()) { - return new Type(type, classNameReplacer); - } else { - return type; - } - } - - private String scrubSignature(String signature) { - return scrubSignature(new Signature(signature)).toString(); - } - - private Signature scrubSignature(Signature signature) { - return new Signature(signature, classNameReplacer); - } - - private boolean isClassMatchedUniquely(String className) { - return this.namer != null && this.namer.getName(Descriptor.toJvmName(className)) != null; - } - - private String getBehaviorSignature(CtBehavior behavior) { - try { - // does this method have an implementation? - if (behavior.getMethodInfo().getCodeAttribute() == null) { - return "(none)"; - } - - // compute the hash from the opcodes - ConstPool constants = behavior.getMethodInfo().getConstPool(); - final MessageDigest digest = MessageDigest.getInstance("MD5"); - CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator(); - while (iter.hasNext()) { - int pos = iter.next(); - - // update the hash with the opcode - int opcode = iter.byteAt(pos); - digest.update((byte) opcode); - int constIndex; - switch (opcode) { - case Opcode.LDC: - constIndex = iter.byteAt(pos + 1); - updateHashWithConstant(digest, constants, constIndex); - break; - - case Opcode.LDC_W: - case Opcode.LDC2_W: - constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2); - updateHashWithConstant(digest, constants, constIndex); - break; - default: - break; - } - } - - // update hash with method and field accesses - behavior.instrument(new ExprEditor() { - @Override - public void edit(MethodCall call) { - updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName()))); - updateHashWithString(digest, scrubSignature(call.getSignature())); - if (isClassMatchedUniquely(call.getClassName())) { - updateHashWithString(digest, call.getMethodName()); - } - } - - @Override - public void edit(FieldAccess access) { - updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(access.getClassName()))); - updateHashWithString(digest, scrubType(access.getSignature())); - if (isClassMatchedUniquely(access.getClassName())) { - updateHashWithString(digest, access.getFieldName()); - } - } - - @Override - public void edit(ConstructorCall call) { - updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName()))); - updateHashWithString(digest, scrubSignature(call.getSignature())); - } - - @Override - public void edit(NewExpr expr) { - updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(expr.getClassName()))); - } - }); - - // convert the hash to a hex string - return toHex(digest.digest()); - } catch (BadBytecode | NoSuchAlgorithmException | CannotCompileException ex) { - throw new Error(ex); - } - } - - private void updateHashWithConstant(MessageDigest digest, ConstPool constants, int index) { - ConstPoolEditor editor = new ConstPoolEditor(constants); - ConstInfoAccessor item = editor.getItem(index); - if (item.getType() == InfoType.StringInfo) { - updateHashWithString(digest, constants.getStringInfo(index)); - } - // TODO: other constants - } - - private void updateHashWithString(MessageDigest digest, String val) { - try { - digest.update(val.getBytes("UTF8")); - } catch (UnsupportedEncodingException ex) { - throw new Error(ex); - } - } - - private String toHex(byte[] bytes) { - // function taken from: - // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java - final char[] hexArray = "0123456789ABCDEF".toCharArray(); - char[] hexChars = new char[bytes.length * 2]; - for (int j = 0; j < bytes.length; j++) { - int v = bytes[j] & 0xFF; - hexChars[j * 2] = hexArray[v >>> 4]; - hexChars[j * 2 + 1] = hexArray[v & 0x0F]; - } - return new String(hexChars); - } - - @Override - public boolean equals(Object other) { - return other instanceof ClassIdentity && equals((ClassIdentity) other); - } - - public boolean equals(ClassIdentity other) { - return this.fields.equals(other.fields) - && this.methods.equals(other.methods) - && this.constructors.equals(other.constructors) - && this.staticInitializer.equals(other.staticInitializer) - && this.extendz.equals(other.extendz) - && this.implementz.equals(other.implementz) - && this.implementations.equals(other.implementations) - && this.references.equals(other.references); - } - - @Override - public int hashCode() { - List objs = Lists.newArrayList(); - objs.addAll(this.fields); - objs.addAll(this.methods); - objs.addAll(this.constructors); - objs.add(this.staticInitializer); - objs.add(this.extendz); - objs.addAll(this.implementz); - objs.addAll(this.implementations); - objs.addAll(this.references); - return Utils.combineHashesOrdered(objs); - } - - public int getMatchScore(ClassIdentity other) { - return 2 * getNumMatches(this.extendz, other.extendz) - + 2 * getNumMatches(this.outer, other.outer) - + 2 * getNumMatches(this.implementz, other.implementz) - + getNumMatches(this.stringLiterals, other.stringLiterals) - + getNumMatches(this.fields, other.fields) - + getNumMatches(this.methods, other.methods) - + getNumMatches(this.constructors, other.constructors); - } - - public int getMaxMatchScore() { - return 2 + 2 + 2 * this.implementz.size() + this.stringLiterals.size() + this.fields.size() + this.methods.size() + this.constructors.size(); - } - - public boolean matches(CtClass c) { - // just compare declaration counts - return this.fields.size() == c.getDeclaredFields().length - && this.methods.size() == c.getDeclaredMethods().length - && this.constructors.size() == c.getDeclaredConstructors().length; - } - - private int getNumMatches(Set a, Set b) { - int numMatches = 0; - for (String val : a) { - if (b.contains(val)) { - numMatches++; - } - } - return numMatches; - } - - private int getNumMatches(Multiset a, Multiset b) { - int numMatches = 0; - for (String val : a) { - if (b.contains(val)) { - numMatches++; - } - } - return numMatches; - } - - private int getNumMatches(String a, String b) { - if (a == null && b == null) { - return 1; - } else if (a != null && b != null && a.equals(b)) { - return 1; - } - return 0; - } + private ClassEntry classEntry; + private SidedClassNamer namer; + private final ClassNameReplacer classNameReplacer = new ClassNameReplacer() { + + private Map classNames = Maps.newHashMap(); + + @Override + public String replace(String className) { + + // classes not in the none package can be passed through + ClassEntry classEntry = new ClassEntry(className); + if (classEntry.getPackageName() != null) { + return className; + } + + // is this class ourself? + if (className.equals(classEntry.getName())) { + return "CSelf"; + } + + // try the namer + if (namer != null) { + String newName = namer.getName(className); + if (newName != null) { + return newName; + } + } + + // otherwise, use local naming + if (!classNames.containsKey(className)) { + classNames.put(className, getNewClassName()); + } + return classNames.get(className); + } + + private String getNewClassName() { + return String.format("C%03d", classNames.size()); + } + }; + private Multiset fields; + private Multiset methods; + private Multiset constructors; + private String staticInitializer; + private String extendz; + private Multiset implementz; + private Set stringLiterals; + private Multiset implementations; + private Multiset references; + private String outer; + + public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) { + this.namer = namer; + + // stuff from the bytecode + + this.classEntry = EntryFactory.getClassEntry(c); + this.fields = HashMultiset.create(); + for (CtField field : c.getDeclaredFields()) { + this.fields.add(scrubType(field.getSignature())); + } + this.methods = HashMultiset.create(); + for (CtMethod method : c.getDeclaredMethods()) { + this.methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method)); + } + this.constructors = HashMultiset.create(); + for (CtConstructor constructor : c.getDeclaredConstructors()) { + this.constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor)); + } + this.staticInitializer = ""; + if (c.getClassInitializer() != null) { + this.staticInitializer = getBehaviorSignature(c.getClassInitializer()); + } + this.extendz = ""; + if (c.getClassFile().getSuperclass() != null) { + this.extendz = scrubClassName(Descriptor.toJvmName(c.getClassFile().getSuperclass())); + } + this.implementz = HashMultiset.create(); + for (String interfaceName : c.getClassFile().getInterfaces()) { + this.implementz.add(scrubClassName(Descriptor.toJvmName(interfaceName))); + } + + this.stringLiterals = Sets.newHashSet(); + ConstPool constants = c.getClassFile().getConstPool(); + for (int i = 1; i < constants.getSize(); i++) { + if (constants.getTag(i) == ConstPool.CONST_String) { + this.stringLiterals.add(constants.getStringInfo(i)); + } + } + + // stuff from the jar index + + this.implementations = HashMultiset.create(); + ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, this.classEntry); + if (implementationsNode != null) { + @SuppressWarnings("unchecked") + Enumeration implementations = implementationsNode.children(); + while (implementations.hasMoreElements()) { + ClassImplementationsTreeNode node = implementations.nextElement(); + this.implementations.add(scrubClassName(node.getClassEntry().getName())); + } + } + + this.references = HashMultiset.create(); + if (useReferences) { + for (CtField field : c.getDeclaredFields()) { + FieldEntry fieldEntry = EntryFactory.getFieldEntry(field); + index.getFieldReferences(fieldEntry).forEach(this::addReference); + } + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); + index.getBehaviorReferences(behaviorEntry).forEach(this::addReference); + } + } + + this.outer = null; + if (this.classEntry.isInnerClass()) { + this.outer = this.classEntry.getOuterClassName(); + } + } + + private void addReference(EntryReference reference) { + if (reference.context.getSignature() != null) { + this.references.add(String.format("%s_%s", + scrubClassName(reference.context.getClassName()), + scrubSignature(reference.context.getSignature()) + )); + } else { + this.references.add(String.format("%s_", + scrubClassName(reference.context.getClassName()) + )); + } + } + + public ClassEntry getClassEntry() { + return this.classEntry; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("class: "); + buf.append(this.classEntry.getName()); + buf.append(" "); + buf.append(hashCode()); + buf.append("\n"); + for (String field : this.fields) { + buf.append("\tfield "); + buf.append(field); + buf.append("\n"); + } + for (String method : this.methods) { + buf.append("\tmethod "); + buf.append(method); + buf.append("\n"); + } + for (String constructor : this.constructors) { + buf.append("\tconstructor "); + buf.append(constructor); + buf.append("\n"); + } + if (!this.staticInitializer.isEmpty()) { + buf.append("\tinitializer "); + buf.append(this.staticInitializer); + buf.append("\n"); + } + if (!this.extendz.isEmpty()) { + buf.append("\textends "); + buf.append(this.extendz); + buf.append("\n"); + } + for (String interfaceName : this.implementz) { + buf.append("\timplements "); + buf.append(interfaceName); + buf.append("\n"); + } + for (String implementation : this.implementations) { + buf.append("\timplemented by "); + buf.append(implementation); + buf.append("\n"); + } + for (String reference : this.references) { + buf.append("\treference "); + buf.append(reference); + buf.append("\n"); + } + buf.append("\touter "); + buf.append(this.outer); + buf.append("\n"); + return buf.toString(); + } + + private String scrubClassName(String className) { + return classNameReplacer.replace(className); + } + + private String scrubType(String typeName) { + return scrubType(new Type(typeName)).toString(); + } + + private Type scrubType(Type type) { + if (type.hasClass()) { + return new Type(type, classNameReplacer); + } else { + return type; + } + } + + private String scrubSignature(String signature) { + return scrubSignature(new Signature(signature)).toString(); + } + + private Signature scrubSignature(Signature signature) { + return new Signature(signature, classNameReplacer); + } + + private boolean isClassMatchedUniquely(String className) { + return this.namer != null && this.namer.getName(Descriptor.toJvmName(className)) != null; + } + + private String getBehaviorSignature(CtBehavior behavior) { + try { + // does this method have an implementation? + if (behavior.getMethodInfo().getCodeAttribute() == null) { + return "(none)"; + } + + // compute the hash from the opcodes + ConstPool constants = behavior.getMethodInfo().getConstPool(); + final MessageDigest digest = MessageDigest.getInstance("MD5"); + CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator(); + while (iter.hasNext()) { + int pos = iter.next(); + + // update the hash with the opcode + int opcode = iter.byteAt(pos); + digest.update((byte) opcode); + int constIndex; + switch (opcode) { + case Opcode.LDC: + constIndex = iter.byteAt(pos + 1); + updateHashWithConstant(digest, constants, constIndex); + break; + + case Opcode.LDC_W: + case Opcode.LDC2_W: + constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2); + updateHashWithConstant(digest, constants, constIndex); + break; + default: + break; + } + } + + // update hash with method and field accesses + behavior.instrument(new ExprEditor() { + @Override + public void edit(MethodCall call) { + updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName()))); + updateHashWithString(digest, scrubSignature(call.getSignature())); + if (isClassMatchedUniquely(call.getClassName())) { + updateHashWithString(digest, call.getMethodName()); + } + } + + @Override + public void edit(FieldAccess access) { + updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(access.getClassName()))); + updateHashWithString(digest, scrubType(access.getSignature())); + if (isClassMatchedUniquely(access.getClassName())) { + updateHashWithString(digest, access.getFieldName()); + } + } + + @Override + public void edit(ConstructorCall call) { + updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName()))); + updateHashWithString(digest, scrubSignature(call.getSignature())); + } + + @Override + public void edit(NewExpr expr) { + updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(expr.getClassName()))); + } + }); + + // convert the hash to a hex string + return toHex(digest.digest()); + } catch (BadBytecode | NoSuchAlgorithmException | CannotCompileException ex) { + throw new Error(ex); + } + } + + private void updateHashWithConstant(MessageDigest digest, ConstPool constants, int index) { + ConstPoolEditor editor = new ConstPoolEditor(constants); + ConstInfoAccessor item = editor.getItem(index); + if (item.getType() == InfoType.StringInfo) { + updateHashWithString(digest, constants.getStringInfo(index)); + } + // TODO: other constants + } + + private void updateHashWithString(MessageDigest digest, String val) { + try { + digest.update(val.getBytes("UTF8")); + } catch (UnsupportedEncodingException ex) { + throw new Error(ex); + } + } + + private String toHex(byte[] bytes) { + // function taken from: + // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java + final char[] hexArray = "0123456789ABCDEF".toCharArray(); + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + + @Override + public boolean equals(Object other) { + return other instanceof ClassIdentity && equals((ClassIdentity) other); + } + + public boolean equals(ClassIdentity other) { + return this.fields.equals(other.fields) + && this.methods.equals(other.methods) + && this.constructors.equals(other.constructors) + && this.staticInitializer.equals(other.staticInitializer) + && this.extendz.equals(other.extendz) + && this.implementz.equals(other.implementz) + && this.implementations.equals(other.implementations) + && this.references.equals(other.references); + } + + @Override + public int hashCode() { + List objs = Lists.newArrayList(); + objs.addAll(this.fields); + objs.addAll(this.methods); + objs.addAll(this.constructors); + objs.add(this.staticInitializer); + objs.add(this.extendz); + objs.addAll(this.implementz); + objs.addAll(this.implementations); + objs.addAll(this.references); + return Utils.combineHashesOrdered(objs); + } + + public int getMatchScore(ClassIdentity other) { + return 2 * getNumMatches(this.extendz, other.extendz) + + 2 * getNumMatches(this.outer, other.outer) + + 2 * getNumMatches(this.implementz, other.implementz) + + getNumMatches(this.stringLiterals, other.stringLiterals) + + getNumMatches(this.fields, other.fields) + + getNumMatches(this.methods, other.methods) + + getNumMatches(this.constructors, other.constructors); + } + + public int getMaxMatchScore() { + return 2 + 2 + 2 * this.implementz.size() + this.stringLiterals.size() + this.fields.size() + this.methods.size() + this.constructors.size(); + } + + public boolean matches(CtClass c) { + // just compare declaration counts + return this.fields.size() == c.getDeclaredFields().length + && this.methods.size() == c.getDeclaredMethods().length + && this.constructors.size() == c.getDeclaredConstructors().length; + } + + private int getNumMatches(Set a, Set b) { + int numMatches = 0; + for (String val : a) { + if (b.contains(val)) { + numMatches++; + } + } + return numMatches; + } + + private int getNumMatches(Multiset a, Multiset b) { + int numMatches = 0; + for (String val : a) { + if (b.contains(val)) { + numMatches++; + } + } + return numMatches; + } + + private int getNumMatches(String a, String b) { + if (a == null && b == null) { + return 1; + } else if (a != null && b != null && a.equals(b)) { + return 1; + } + return 0; + } } diff --git a/src/main/java/cuchaz/enigma/convert/ClassMatch.java b/src/main/java/cuchaz/enigma/convert/ClassMatch.java index 9fa35f0..bb3e4f4 100644 --- a/src/main/java/cuchaz/enigma/convert/ClassMatch.java +++ b/src/main/java/cuchaz/enigma/convert/ClassMatch.java @@ -8,76 +8,76 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.convert; import com.google.common.collect.Sets; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.utils.Utils; import java.util.Collection; import java.util.Set; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.utils.Utils; - public class ClassMatch { - public Set sourceClasses; - public Set destClasses; + public Set sourceClasses; + public Set destClasses; - public ClassMatch(Collection sourceClasses, Collection destClasses) { - this.sourceClasses = Sets.newHashSet(sourceClasses); - this.destClasses = Sets.newHashSet(destClasses); - } + public ClassMatch(Collection sourceClasses, Collection destClasses) { + this.sourceClasses = Sets.newHashSet(sourceClasses); + this.destClasses = Sets.newHashSet(destClasses); + } - public ClassMatch(ClassEntry sourceClass, ClassEntry destClass) { - sourceClasses = Sets.newHashSet(); - if (sourceClass != null) { - sourceClasses.add(sourceClass); - } - destClasses = Sets.newHashSet(); - if (destClass != null) { - destClasses.add(destClass); - } - } + public ClassMatch(ClassEntry sourceClass, ClassEntry destClass) { + sourceClasses = Sets.newHashSet(); + if (sourceClass != null) { + sourceClasses.add(sourceClass); + } + destClasses = Sets.newHashSet(); + if (destClass != null) { + destClasses.add(destClass); + } + } - public boolean isMatched() { - return sourceClasses.size() > 0 && destClasses.size() > 0; - } + public boolean isMatched() { + return !sourceClasses.isEmpty() && !destClasses.isEmpty(); + } - public boolean isAmbiguous() { - return sourceClasses.size() > 1 || destClasses.size() > 1; - } + public boolean isAmbiguous() { + return sourceClasses.size() > 1 || destClasses.size() > 1; + } - public ClassEntry getUniqueSource() { - if (sourceClasses.size() != 1) { - throw new IllegalStateException("Match has ambiguous source!"); - } - return sourceClasses.iterator().next(); - } + public ClassEntry getUniqueSource() { + if (sourceClasses.size() != 1) { + throw new IllegalStateException("Match has ambiguous source!"); + } + return sourceClasses.iterator().next(); + } - public ClassEntry getUniqueDest() { - if (destClasses.size() != 1) { - throw new IllegalStateException("Match has ambiguous source!"); - } - return destClasses.iterator().next(); - } + public ClassEntry getUniqueDest() { + if (destClasses.size() != 1) { + throw new IllegalStateException("Match has ambiguous source!"); + } + return destClasses.iterator().next(); + } - public Set intersectSourceClasses(Set classes) { - Set intersection = Sets.newHashSet(sourceClasses); - intersection.retainAll(classes); - return intersection; - } + public Set intersectSourceClasses(Set classes) { + Set intersection = Sets.newHashSet(sourceClasses); + intersection.retainAll(classes); + return intersection; + } - @Override - public int hashCode() { - return Utils.combineHashesOrdered(sourceClasses, destClasses); - } + @Override + public int hashCode() { + return Utils.combineHashesOrdered(sourceClasses, destClasses); + } - @Override - public boolean equals(Object other) { - return other instanceof ClassMatch && equals((ClassMatch) other); - } + @Override + public boolean equals(Object other) { + return other instanceof ClassMatch && equals((ClassMatch) other); + } - public boolean equals(ClassMatch other) { - return this.sourceClasses.equals(other.sourceClasses) && this.destClasses.equals(other.destClasses); - } + public boolean equals(ClassMatch other) { + return this.sourceClasses.equals(other.sourceClasses) && this.destClasses.equals(other.destClasses); + } } diff --git a/src/main/java/cuchaz/enigma/convert/ClassMatches.java b/src/main/java/cuchaz/enigma/convert/ClassMatches.java index 431c4f2..db2c550 100644 --- a/src/main/java/cuchaz/enigma/convert/ClassMatches.java +++ b/src/main/java/cuchaz/enigma/convert/ClassMatches.java @@ -8,152 +8,151 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.convert; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.Maps; import com.google.common.collect.Sets; - -import java.util.*; - import cuchaz.enigma.mapping.ClassEntry; +import java.util.*; public class ClassMatches implements Iterable { - private Collection matches; - private Map matchesBySource; - private Map matchesByDest; - private BiMap uniqueMatches; - private Map ambiguousMatchesBySource; - private Map ambiguousMatchesByDest; - private Set unmatchedSourceClasses; - private Set unmatchedDestClasses; - - public ClassMatches() { - this(new ArrayList<>()); - } - - public ClassMatches(Collection matches) { - this.matches = matches; - matchesBySource = Maps.newHashMap(); - matchesByDest = Maps.newHashMap(); - uniqueMatches = HashBiMap.create(); - ambiguousMatchesBySource = Maps.newHashMap(); - ambiguousMatchesByDest = Maps.newHashMap(); - unmatchedSourceClasses = Sets.newHashSet(); - unmatchedDestClasses = Sets.newHashSet(); - - for (ClassMatch match : matches) { - indexMatch(match); - } - } - - public void add(ClassMatch match) { - matches.add(match); - indexMatch(match); - } - - public void remove(ClassMatch match) { - for (ClassEntry sourceClass : match.sourceClasses) { - matchesBySource.remove(sourceClass); - uniqueMatches.remove(sourceClass); - ambiguousMatchesBySource.remove(sourceClass); - unmatchedSourceClasses.remove(sourceClass); - } - for (ClassEntry destClass : match.destClasses) { - matchesByDest.remove(destClass); - uniqueMatches.inverse().remove(destClass); - ambiguousMatchesByDest.remove(destClass); - unmatchedDestClasses.remove(destClass); - } - matches.remove(match); - } - - public int size() { - return matches.size(); - } - - @Override - public Iterator iterator() { - return matches.iterator(); - } - - private void indexMatch(ClassMatch match) { - if (!match.isMatched()) { - // unmatched - unmatchedSourceClasses.addAll(match.sourceClasses); - unmatchedDestClasses.addAll(match.destClasses); - } else { - if (match.isAmbiguous()) { - // ambiguously matched - for (ClassEntry entry : match.sourceClasses) { - ambiguousMatchesBySource.put(entry, match); - } - for (ClassEntry entry : match.destClasses) { - ambiguousMatchesByDest.put(entry, match); - } - } else { - // uniquely matched - uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest()); - } - } - for (ClassEntry entry : match.sourceClasses) { - matchesBySource.put(entry, match); - } - for (ClassEntry entry : match.destClasses) { - matchesByDest.put(entry, match); - } - } - - public BiMap getUniqueMatches() { - return uniqueMatches; - } - - public Set getUnmatchedSourceClasses() { - return unmatchedSourceClasses; - } - - public Set getUnmatchedDestClasses() { - return unmatchedDestClasses; - } - - public Set getAmbiguouslyMatchedSourceClasses() { - return ambiguousMatchesBySource.keySet(); - } - - public ClassMatch getAmbiguousMatchBySource(ClassEntry sourceClass) { - return ambiguousMatchesBySource.get(sourceClass); - } - - public ClassMatch getMatchBySource(ClassEntry sourceClass) { - return matchesBySource.get(sourceClass); - } - - public ClassMatch getMatchByDest(ClassEntry destClass) { - return matchesByDest.get(destClass); - } - - public void removeSource(ClassEntry sourceClass) { - ClassMatch match = matchesBySource.get(sourceClass); - if (match != null) { - remove(match); - match.sourceClasses.remove(sourceClass); - if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) { - add(match); - } - } - } - - public void removeDest(ClassEntry destClass) { - ClassMatch match = matchesByDest.get(destClass); - if (match != null) { - remove(match); - match.destClasses.remove(destClass); - if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) { - add(match); - } - } - } + private Collection matches; + private Map matchesBySource; + private Map matchesByDest; + private BiMap uniqueMatches; + private Map ambiguousMatchesBySource; + private Map ambiguousMatchesByDest; + private Set unmatchedSourceClasses; + private Set unmatchedDestClasses; + + public ClassMatches() { + this(new ArrayList<>()); + } + + public ClassMatches(Collection matches) { + this.matches = matches; + matchesBySource = Maps.newHashMap(); + matchesByDest = Maps.newHashMap(); + uniqueMatches = HashBiMap.create(); + ambiguousMatchesBySource = Maps.newHashMap(); + ambiguousMatchesByDest = Maps.newHashMap(); + unmatchedSourceClasses = Sets.newHashSet(); + unmatchedDestClasses = Sets.newHashSet(); + + for (ClassMatch match : matches) { + indexMatch(match); + } + } + + public void add(ClassMatch match) { + matches.add(match); + indexMatch(match); + } + + public void remove(ClassMatch match) { + for (ClassEntry sourceClass : match.sourceClasses) { + matchesBySource.remove(sourceClass); + uniqueMatches.remove(sourceClass); + ambiguousMatchesBySource.remove(sourceClass); + unmatchedSourceClasses.remove(sourceClass); + } + for (ClassEntry destClass : match.destClasses) { + matchesByDest.remove(destClass); + uniqueMatches.inverse().remove(destClass); + ambiguousMatchesByDest.remove(destClass); + unmatchedDestClasses.remove(destClass); + } + matches.remove(match); + } + + public int size() { + return matches.size(); + } + + @Override + public Iterator iterator() { + return matches.iterator(); + } + + private void indexMatch(ClassMatch match) { + if (!match.isMatched()) { + // unmatched + unmatchedSourceClasses.addAll(match.sourceClasses); + unmatchedDestClasses.addAll(match.destClasses); + } else { + if (match.isAmbiguous()) { + // ambiguously matched + for (ClassEntry entry : match.sourceClasses) { + ambiguousMatchesBySource.put(entry, match); + } + for (ClassEntry entry : match.destClasses) { + ambiguousMatchesByDest.put(entry, match); + } + } else { + // uniquely matched + uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest()); + } + } + for (ClassEntry entry : match.sourceClasses) { + matchesBySource.put(entry, match); + } + for (ClassEntry entry : match.destClasses) { + matchesByDest.put(entry, match); + } + } + + public BiMap getUniqueMatches() { + return uniqueMatches; + } + + public Set getUnmatchedSourceClasses() { + return unmatchedSourceClasses; + } + + public Set getUnmatchedDestClasses() { + return unmatchedDestClasses; + } + + public Set getAmbiguouslyMatchedSourceClasses() { + return ambiguousMatchesBySource.keySet(); + } + + public ClassMatch getAmbiguousMatchBySource(ClassEntry sourceClass) { + return ambiguousMatchesBySource.get(sourceClass); + } + + public ClassMatch getMatchBySource(ClassEntry sourceClass) { + return matchesBySource.get(sourceClass); + } + + public ClassMatch getMatchByDest(ClassEntry destClass) { + return matchesByDest.get(destClass); + } + + public void removeSource(ClassEntry sourceClass) { + ClassMatch match = matchesBySource.get(sourceClass); + if (match != null) { + remove(match); + match.sourceClasses.remove(sourceClass); + if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) { + add(match); + } + } + } + + public void removeDest(ClassEntry destClass) { + ClassMatch match = matchesByDest.get(destClass); + if (match != null) { + remove(match); + match.destClasses.remove(destClass); + if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) { + add(match); + } + } + } } diff --git a/src/main/java/cuchaz/enigma/convert/ClassMatching.java b/src/main/java/cuchaz/enigma/convert/ClassMatching.java index b05df87..f302f13 100644 --- a/src/main/java/cuchaz/enigma/convert/ClassMatching.java +++ b/src/main/java/cuchaz/enigma/convert/ClassMatching.java @@ -8,12 +8,14 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.convert; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import cuchaz.enigma.mapping.ClassEntry; import java.util.ArrayList; import java.util.Collection; @@ -21,134 +23,132 @@ import java.util.List; import java.util.Map.Entry; import java.util.Set; -import cuchaz.enigma.mapping.ClassEntry; - public class ClassMatching { - private ClassForest sourceClasses; - private ClassForest destClasses; - private BiMap knownMatches; - - public ClassMatching(ClassIdentifier sourceIdentifier, ClassIdentifier destIdentifier) { - sourceClasses = new ClassForest(sourceIdentifier); - destClasses = new ClassForest(destIdentifier); - knownMatches = HashBiMap.create(); - } - - public void addKnownMatches(BiMap knownMatches) { - this.knownMatches.putAll(knownMatches); - } - - public void match(Iterable sourceClasses, Iterable destClasses) { - for (ClassEntry sourceClass : sourceClasses) { - if (!knownMatches.containsKey(sourceClass)) { - this.sourceClasses.add(sourceClass); - } - } - for (ClassEntry destClass : destClasses) { - if (!knownMatches.containsValue(destClass)) { - this.destClasses.add(destClass); - } - } - } - - public Collection matches() { - List matches = Lists.newArrayList(); - for (Entry entry : knownMatches.entrySet()) { - matches.add(new ClassMatch( - entry.getKey(), - entry.getValue() - )); - } - for (ClassIdentity identity : sourceClasses.identities()) { - matches.add(new ClassMatch( - sourceClasses.getClasses(identity), - destClasses.getClasses(identity) - )); - } - for (ClassIdentity identity : destClasses.identities()) { - if (!sourceClasses.containsIdentity(identity)) { - matches.add(new ClassMatch( - new ArrayList<>(), - destClasses.getClasses(identity) - )); - } - } - return matches; - } - - public Collection sourceClasses() { - Set classes = Sets.newHashSet(); - for (ClassMatch match : matches()) { - classes.addAll(match.sourceClasses); - } - return classes; - } - - public Collection destClasses() { - Set classes = Sets.newHashSet(); - for (ClassMatch match : matches()) { - classes.addAll(match.destClasses); - } - return classes; - } - - public BiMap uniqueMatches() { - BiMap uniqueMatches = HashBiMap.create(); - for (ClassMatch match : matches()) { - if (match.isMatched() && !match.isAmbiguous()) { - uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest()); - } - } - return uniqueMatches; - } - - public Collection ambiguousMatches() { - List ambiguousMatches = Lists.newArrayList(); - for (ClassMatch match : matches()) { - if (match.isMatched() && match.isAmbiguous()) { - ambiguousMatches.add(match); - } - } - return ambiguousMatches; - } - - public Collection unmatchedSourceClasses() { - List classes = Lists.newArrayList(); - for (ClassMatch match : matches()) { - if (!match.isMatched() && !match.sourceClasses.isEmpty()) { - classes.addAll(match.sourceClasses); - } - } - return classes; - } - - public Collection unmatchedDestClasses() { - List classes = Lists.newArrayList(); - for (ClassMatch match : matches()) { - if (!match.isMatched() && !match.destClasses.isEmpty()) { - classes.addAll(match.destClasses); - } - } - return classes; - } - - @Override - public String toString() { - - // count the ambiguous classes - int numAmbiguousSource = 0; - int numAmbiguousDest = 0; - for (ClassMatch match : ambiguousMatches()) { - numAmbiguousSource += match.sourceClasses.size(); - numAmbiguousDest += match.destClasses.size(); - } - - String buf = String.format("%20s%8s%8s\n", "", "Source", "Dest") + String - .format("%20s%8d%8d\n", "Classes", sourceClasses().size(), destClasses().size()) + String - .format("%20s%8d%8d\n", "Uniquely matched", uniqueMatches().size(), uniqueMatches().size()) + String - .format("%20s%8d%8d\n", "Ambiguously matched", numAmbiguousSource, numAmbiguousDest) + String - .format("%20s%8d%8d\n", "Unmatched", unmatchedSourceClasses().size(), unmatchedDestClasses().size()); - return buf; - } + private ClassForest sourceClasses; + private ClassForest destClasses; + private BiMap knownMatches; + + public ClassMatching(ClassIdentifier sourceIdentifier, ClassIdentifier destIdentifier) { + sourceClasses = new ClassForest(sourceIdentifier); + destClasses = new ClassForest(destIdentifier); + knownMatches = HashBiMap.create(); + } + + public void addKnownMatches(BiMap knownMatches) { + this.knownMatches.putAll(knownMatches); + } + + public void match(Iterable sourceClasses, Iterable destClasses) { + for (ClassEntry sourceClass : sourceClasses) { + if (!knownMatches.containsKey(sourceClass)) { + this.sourceClasses.add(sourceClass); + } + } + for (ClassEntry destClass : destClasses) { + if (!knownMatches.containsValue(destClass)) { + this.destClasses.add(destClass); + } + } + } + + public Collection matches() { + List matches = Lists.newArrayList(); + for (Entry entry : knownMatches.entrySet()) { + matches.add(new ClassMatch( + entry.getKey(), + entry.getValue() + )); + } + for (ClassIdentity identity : sourceClasses.identities()) { + matches.add(new ClassMatch( + sourceClasses.getClasses(identity), + destClasses.getClasses(identity) + )); + } + for (ClassIdentity identity : destClasses.identities()) { + if (!sourceClasses.containsIdentity(identity)) { + matches.add(new ClassMatch( + new ArrayList<>(), + destClasses.getClasses(identity) + )); + } + } + return matches; + } + + public Collection sourceClasses() { + Set classes = Sets.newHashSet(); + for (ClassMatch match : matches()) { + classes.addAll(match.sourceClasses); + } + return classes; + } + + public Collection destClasses() { + Set classes = Sets.newHashSet(); + for (ClassMatch match : matches()) { + classes.addAll(match.destClasses); + } + return classes; + } + + public BiMap uniqueMatches() { + BiMap uniqueMatches = HashBiMap.create(); + for (ClassMatch match : matches()) { + if (match.isMatched() && !match.isAmbiguous()) { + uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest()); + } + } + return uniqueMatches; + } + + public Collection ambiguousMatches() { + List ambiguousMatches = Lists.newArrayList(); + for (ClassMatch match : matches()) { + if (match.isMatched() && match.isAmbiguous()) { + ambiguousMatches.add(match); + } + } + return ambiguousMatches; + } + + public Collection unmatchedSourceClasses() { + List classes = Lists.newArrayList(); + for (ClassMatch match : matches()) { + if (!match.isMatched() && !match.sourceClasses.isEmpty()) { + classes.addAll(match.sourceClasses); + } + } + return classes; + } + + public Collection unmatchedDestClasses() { + List classes = Lists.newArrayList(); + for (ClassMatch match : matches()) { + if (!match.isMatched() && !match.destClasses.isEmpty()) { + classes.addAll(match.destClasses); + } + } + return classes; + } + + @Override + public String toString() { + + // count the ambiguous classes + int numAmbiguousSource = 0; + int numAmbiguousDest = 0; + for (ClassMatch match : ambiguousMatches()) { + numAmbiguousSource += match.sourceClasses.size(); + numAmbiguousDest += match.destClasses.size(); + } + + String buf = String.format("%20s%8s%8s\n", "", "Source", "Dest") + String + .format("%20s%8d%8d\n", "Classes", sourceClasses().size(), destClasses().size()) + String + .format("%20s%8d%8d\n", "Uniquely matched", uniqueMatches().size(), uniqueMatches().size()) + String + .format("%20s%8d%8d\n", "Ambiguously matched", numAmbiguousSource, numAmbiguousDest) + String + .format("%20s%8d%8d\n", "Unmatched", unmatchedSourceClasses().size(), unmatchedDestClasses().size()); + return buf; + } } diff --git a/src/main/java/cuchaz/enigma/convert/ClassNamer.java b/src/main/java/cuchaz/enigma/convert/ClassNamer.java index e471c7d..3969910 100644 --- a/src/main/java/cuchaz/enigma/convert/ClassNamer.java +++ b/src/main/java/cuchaz/enigma/convert/ClassNamer.java @@ -8,49 +8,48 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.convert; import com.google.common.collect.BiMap; import com.google.common.collect.Maps; +import cuchaz.enigma.mapping.ClassEntry; import java.util.Map; -import cuchaz.enigma.mapping.ClassEntry; - public class ClassNamer { - public interface SidedClassNamer { - String getName(String name); - } - - private Map sourceNames; - private Map destNames; - - public ClassNamer(BiMap mappings) { - // convert the identity mappings to name maps - this.sourceNames = Maps.newHashMap(); - this.destNames = Maps.newHashMap(); - int i = 0; - for (Map.Entry entry : mappings.entrySet()) { - String name = String.format("M%04d", i++); - this.sourceNames.put(entry.getKey().getName(), name); - this.destNames.put(entry.getValue().getName(), name); - } - } - - public String getSourceName(String name) { - return this.sourceNames.get(name); - } - - public String getDestName(String name) { - return this.destNames.get(name); - } - - public SidedClassNamer getSourceNamer() { - return this::getSourceName; - } - - public SidedClassNamer getDestNamer() { - return this::getDestName; - } + private Map sourceNames; + private Map destNames; + public ClassNamer(BiMap mappings) { + // convert the identity mappings to name maps + this.sourceNames = Maps.newHashMap(); + this.destNames = Maps.newHashMap(); + int i = 0; + for (Map.Entry entry : mappings.entrySet()) { + String name = String.format("M%04d", i++); + this.sourceNames.put(entry.getKey().getName(), name); + this.destNames.put(entry.getValue().getName(), name); + } + } + + public String getSourceName(String name) { + return this.sourceNames.get(name); + } + + public String getDestName(String name) { + return this.destNames.get(name); + } + + public SidedClassNamer getSourceNamer() { + return this::getSourceName; + } + + public SidedClassNamer getDestNamer() { + return this::getDestName; + } + + public interface SidedClassNamer { + String getName(String name); + } } diff --git a/src/main/java/cuchaz/enigma/convert/FieldMatches.java b/src/main/java/cuchaz/enigma/convert/FieldMatches.java index 236cd4d..a528b27 100644 --- a/src/main/java/cuchaz/enigma/convert/FieldMatches.java +++ b/src/main/java/cuchaz/enigma/convert/FieldMatches.java @@ -8,144 +8,143 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.convert; import com.google.common.collect.*; - -import java.util.Collection; -import java.util.Set; - import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.FieldEntry; +import java.util.Collection; +import java.util.Set; public class FieldMatches { - private BiMap matches; - private Multimap matchedSourceFields; - private Multimap unmatchedSourceFields; - private Multimap unmatchedDestFields; - private Multimap unmatchableSourceFields; - - public FieldMatches() { - matches = HashBiMap.create(); - matchedSourceFields = HashMultimap.create(); - unmatchedSourceFields = HashMultimap.create(); - unmatchedDestFields = HashMultimap.create(); - unmatchableSourceFields = HashMultimap.create(); - } - - public void addMatch(FieldEntry srcField, FieldEntry destField) { - boolean wasAdded = matches.put(srcField, destField) == null; - assert (wasAdded); - wasAdded = matchedSourceFields.put(srcField.getClassEntry(), srcField); - assert (wasAdded); - } - - public void addUnmatchedSourceField(FieldEntry fieldEntry) { - boolean wasAdded = unmatchedSourceFields.put(fieldEntry.getClassEntry(), fieldEntry); - assert (wasAdded); - } - - public void addUnmatchedSourceFields(Iterable fieldEntries) { - for (FieldEntry fieldEntry : fieldEntries) { - addUnmatchedSourceField(fieldEntry); - } - } - - public void addUnmatchedDestField(FieldEntry fieldEntry) { - boolean wasAdded = unmatchedDestFields.put(fieldEntry.getClassEntry(), fieldEntry); - assert (wasAdded); - } - - public void addUnmatchedDestFields(Iterable fieldEntries) { - for (FieldEntry fieldEntry : fieldEntries) { - addUnmatchedDestField(fieldEntry); - } - } - - public void addUnmatchableSourceField(FieldEntry sourceField) { - boolean wasAdded = unmatchableSourceFields.put(sourceField.getClassEntry(), sourceField); - assert (wasAdded); - } - - public Set getSourceClassesWithUnmatchedFields() { - return unmatchedSourceFields.keySet(); - } - - public Collection getSourceClassesWithoutUnmatchedFields() { - Set out = Sets.newHashSet(); - out.addAll(matchedSourceFields.keySet()); - out.removeAll(unmatchedSourceFields.keySet()); - return out; - } - - public Collection getUnmatchedSourceFields() { - return unmatchedSourceFields.values(); - } - - public Collection getUnmatchedSourceFields(ClassEntry sourceClass) { - return unmatchedSourceFields.get(sourceClass); - } - - public Collection getUnmatchedDestFields() { - return unmatchedDestFields.values(); - } - - public Collection getUnmatchedDestFields(ClassEntry destClass) { - return unmatchedDestFields.get(destClass); - } - - public Collection getUnmatchableSourceFields() { - return unmatchableSourceFields.values(); - } - - public boolean hasSource(FieldEntry fieldEntry) { - return matches.containsKey(fieldEntry) || unmatchedSourceFields.containsValue(fieldEntry); - } - - public boolean hasDest(FieldEntry fieldEntry) { - return matches.containsValue(fieldEntry) || unmatchedDestFields.containsValue(fieldEntry); - } - - public BiMap matches() { - return matches; - } - - public boolean isMatchedSourceField(FieldEntry sourceField) { - return matches.containsKey(sourceField); - } - - public boolean isMatchedDestField(FieldEntry destField) { - return matches.containsValue(destField); - } - - public void makeMatch(FieldEntry sourceField, FieldEntry destField) { - boolean wasRemoved = unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField); - assert (wasRemoved); - wasRemoved = unmatchedDestFields.remove(destField.getClassEntry(), destField); - assert (wasRemoved); - addMatch(sourceField, destField); - } - - public boolean isMatched(FieldEntry sourceField, FieldEntry destField) { - FieldEntry match = matches.get(sourceField); - return match != null && match.equals(destField); - } - - public void unmakeMatch(FieldEntry sourceField, FieldEntry destField) { - boolean wasRemoved = matches.remove(sourceField) != null; - assert (wasRemoved); - wasRemoved = matchedSourceFields.remove(sourceField.getClassEntry(), sourceField); - assert (wasRemoved); - addUnmatchedSourceField(sourceField); - addUnmatchedDestField(destField); - } - - public void makeSourceUnmatchable(FieldEntry sourceField) { - assert (!isMatchedSourceField(sourceField)); - boolean wasRemoved = unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField); - assert (wasRemoved); - addUnmatchableSourceField(sourceField); - } + private BiMap matches; + private Multimap matchedSourceFields; + private Multimap unmatchedSourceFields; + private Multimap unmatchedDestFields; + private Multimap unmatchableSourceFields; + + public FieldMatches() { + matches = HashBiMap.create(); + matchedSourceFields = HashMultimap.create(); + unmatchedSourceFields = HashMultimap.create(); + unmatchedDestFields = HashMultimap.create(); + unmatchableSourceFields = HashMultimap.create(); + } + + public void addMatch(FieldEntry srcField, FieldEntry destField) { + boolean wasAdded = matches.put(srcField, destField) == null; + assert (wasAdded); + wasAdded = matchedSourceFields.put(srcField.getClassEntry(), srcField); + assert (wasAdded); + } + + public void addUnmatchedSourceField(FieldEntry fieldEntry) { + boolean wasAdded = unmatchedSourceFields.put(fieldEntry.getClassEntry(), fieldEntry); + assert (wasAdded); + } + + public void addUnmatchedSourceFields(Iterable fieldEntries) { + for (FieldEntry fieldEntry : fieldEntries) { + addUnmatchedSourceField(fieldEntry); + } + } + + public void addUnmatchedDestField(FieldEntry fieldEntry) { + boolean wasAdded = unmatchedDestFields.put(fieldEntry.getClassEntry(), fieldEntry); + assert (wasAdded); + } + + public void addUnmatchedDestFields(Iterable fieldEntries) { + for (FieldEntry fieldEntry : fieldEntries) { + addUnmatchedDestField(fieldEntry); + } + } + + public void addUnmatchableSourceField(FieldEntry sourceField) { + boolean wasAdded = unmatchableSourceFields.put(sourceField.getClassEntry(), sourceField); + assert (wasAdded); + } + + public Set getSourceClassesWithUnmatchedFields() { + return unmatchedSourceFields.keySet(); + } + + public Collection getSourceClassesWithoutUnmatchedFields() { + Set out = Sets.newHashSet(); + out.addAll(matchedSourceFields.keySet()); + out.removeAll(unmatchedSourceFields.keySet()); + return out; + } + + public Collection getUnmatchedSourceFields() { + return unmatchedSourceFields.values(); + } + + public Collection getUnmatchedSourceFields(ClassEntry sourceClass) { + return unmatchedSourceFields.get(sourceClass); + } + + public Collection getUnmatchedDestFields() { + return unmatchedDestFields.values(); + } + + public Collection getUnmatchedDestFields(ClassEntry destClass) { + return unmatchedDestFields.get(destClass); + } + + public Collection getUnmatchableSourceFields() { + return unmatchableSourceFields.values(); + } + + public boolean hasSource(FieldEntry fieldEntry) { + return matches.containsKey(fieldEntry) || unmatchedSourceFields.containsValue(fieldEntry); + } + + public boolean hasDest(FieldEntry fieldEntry) { + return matches.containsValue(fieldEntry) || unmatchedDestFields.containsValue(fieldEntry); + } + + public BiMap matches() { + return matches; + } + + public boolean isMatchedSourceField(FieldEntry sourceField) { + return matches.containsKey(sourceField); + } + + public boolean isMatchedDestField(FieldEntry destField) { + return matches.containsValue(destField); + } + + public void makeMatch(FieldEntry sourceField, FieldEntry destField) { + boolean wasRemoved = unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField); + assert (wasRemoved); + wasRemoved = unmatchedDestFields.remove(destField.getClassEntry(), destField); + assert (wasRemoved); + addMatch(sourceField, destField); + } + + public boolean isMatched(FieldEntry sourceField, FieldEntry destField) { + FieldEntry match = matches.get(sourceField); + return match != null && match.equals(destField); + } + + public void unmakeMatch(FieldEntry sourceField, FieldEntry destField) { + boolean wasRemoved = matches.remove(sourceField) != null; + assert (wasRemoved); + wasRemoved = matchedSourceFields.remove(sourceField.getClassEntry(), sourceField); + assert (wasRemoved); + addUnmatchedSourceField(sourceField); + addUnmatchedDestField(destField); + } + + public void makeSourceUnmatchable(FieldEntry sourceField) { + assert (!isMatchedSourceField(sourceField)); + boolean wasRemoved = unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField); + assert (wasRemoved); + addUnmatchableSourceField(sourceField); + } } diff --git a/src/main/java/cuchaz/enigma/convert/MappingsConverter.java b/src/main/java/cuchaz/enigma/convert/MappingsConverter.java index a5ded67..fa3e936 100644 --- a/src/main/java/cuchaz/enigma/convert/MappingsConverter.java +++ b/src/main/java/cuchaz/enigma/convert/MappingsConverter.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.convert; import com.google.common.collect.*; @@ -29,688 +30,682 @@ import java.util.jar.JarFile; public class MappingsConverter { - public static ClassMatches computeClassMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) { - - // index jars - System.out.println("Indexing source jar..."); - JarIndex sourceIndex = new JarIndex(); - sourceIndex.indexJar(sourceJar, false); - System.out.println("Indexing dest jar..."); - JarIndex destIndex = new JarIndex(); - destIndex.indexJar(destJar, false); - - // compute the matching - ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex, null); - return new ClassMatches(matching.matches()); - } - - public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex, BiMap knownMatches) { - - System.out.println("Iteratively matching classes"); - - ClassMatching lastMatching = null; - int round = 0; - SidedClassNamer sourceNamer = null; - SidedClassNamer destNamer = null; - for (boolean useReferences : Arrays.asList(false, true)) { - - int numUniqueMatchesLastTime = 0; - if (lastMatching != null) { - numUniqueMatchesLastTime = lastMatching.uniqueMatches().size(); - } - - while (true) { - - System.out.println("Round " + (++round) + "..."); - - // init the matching with identity settings - ClassMatching matching = new ClassMatching( - new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences), - new ClassIdentifier(destJar, destIndex, destNamer, useReferences) - ); - - if (knownMatches != null) { - matching.addKnownMatches(knownMatches); - } - - if (lastMatching == null) { - // search all classes - matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries()); - } else { - // we already know about these matches from last time - matching.addKnownMatches(lastMatching.uniqueMatches()); - - // search unmatched and ambiguously-matched classes - matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses()); - for (ClassMatch match : lastMatching.ambiguousMatches()) { - matching.match(match.sourceClasses, match.destClasses); - } - } - System.out.println(matching); - BiMap uniqueMatches = matching.uniqueMatches(); - - // did we match anything new this time? - if (uniqueMatches.size() > numUniqueMatchesLastTime) { - numUniqueMatchesLastTime = uniqueMatches.size(); - lastMatching = matching; - } else { - break; - } - - // update the namers - ClassNamer namer = new ClassNamer(uniqueMatches); - sourceNamer = namer.getSourceNamer(); - destNamer = namer.getDestNamer(); - } - } - - return lastMatching; - } - - public static Mappings newMappings(ClassMatches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) - throws MappingConflict { - // sort the unique matches by size of inner class chain - Multimap> matchesByDestChainSize = HashMultimap.create(); - for (java.util.Map.Entry match : matches.getUniqueMatches().entrySet()) { - int chainSize = destDeobfuscator.getJarIndex().getObfClassChain(match.getValue()).size(); - matchesByDestChainSize.put(chainSize, match); - } - - // build the mappings (in order of small-to-large inner chains) - Mappings newMappings = new Mappings(); - List chainSizes = Lists.newArrayList(matchesByDestChainSize.keySet()); - Collections.sort(chainSizes); - for (int chainSize : chainSizes) { - for (java.util.Map.Entry match : matchesByDestChainSize.get(chainSize)) { - // get class info - ClassEntry obfSourceClassEntry = match.getKey(); - ClassEntry obfDestClassEntry = match.getValue(); - List destClassChain = destDeobfuscator.getJarIndex().getObfClassChain(obfDestClassEntry); - - ClassMapping sourceMapping; - if (obfSourceClassEntry.isInnerClass()) { - List srcClassChain = sourceDeobfuscator.getMappings().getClassMappingChain(obfSourceClassEntry); - sourceMapping = srcClassChain.get(srcClassChain.size() - 1); - } else { - sourceMapping = sourceDeobfuscator.getMappings().getClassByObf(obfSourceClassEntry); - } - - if (sourceMapping == null) { - // if this class was never deobfuscated, don't try to match it - continue; - } - - // find out where to make the dest class mapping - if (destClassChain.size() == 1) { - // not an inner class, add directly to mappings - newMappings.addClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, false)); - } else { - // inner class, find the outer class mapping - ClassMapping destMapping = null; - for (int i = 0; i < destClassChain.size() - 1; i++) { - ClassEntry destChainClassEntry = destClassChain.get(i); - if (destMapping == null) { - destMapping = newMappings.getClassByObf(destChainClassEntry); - if (destMapping == null) { - destMapping = new ClassMapping(destChainClassEntry.getName()); - newMappings.addClassMapping(destMapping); - } - } else { - destMapping = destMapping.getInnerClassByObfSimple(destChainClassEntry.getInnermostClassName()); - if (destMapping == null) { - destMapping = new ClassMapping(destChainClassEntry.getName()); - destMapping.addInnerClassMapping(destMapping); - } - } - } - destMapping.addInnerClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, true)); - } - } - } - return newMappings; - } - - private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping oldClassMapping, final ClassMatches matches, boolean useSimpleName) { - - ClassNameReplacer replacer = className -> - { - ClassEntry newClassEntry = matches.getUniqueMatches().get(new ClassEntry(className)); - if (newClassEntry != null) { - return newClassEntry.getName(); - } - return null; - }; - - ClassMapping newClassMapping; - String deobfName = oldClassMapping.getDeobfName(); - if (deobfName != null) { - if (useSimpleName) { - deobfName = new ClassEntry(deobfName).getSimpleName(); - } - newClassMapping = new ClassMapping(newObfClass.getName(), deobfName); - } else { - newClassMapping = new ClassMapping(newObfClass.getName()); - } - - // migrate fields - for (FieldMapping oldFieldMapping : oldClassMapping.fields()) { - if (canMigrate(oldFieldMapping.getObfType(), matches)) { - newClassMapping.addFieldMapping(new FieldMapping(oldFieldMapping, replacer)); - } else { - System.out.println(String.format("Can't map field, dropping: %s.%s %s", - oldClassMapping.getDeobfName(), - oldFieldMapping.getDeobfName(), - oldFieldMapping.getObfType() - )); - } - } - - // migrate methods - for (MethodMapping oldMethodMapping : oldClassMapping.methods()) { - if (canMigrate(oldMethodMapping.getObfSignature(), matches)) { - newClassMapping.addMethodMapping(new MethodMapping(oldMethodMapping, replacer)); - } else { - System.out.println(String.format("Can't map method, dropping: %s.%s %s", - oldClassMapping.getDeobfName(), - oldMethodMapping.getDeobfName(), - oldMethodMapping.getObfSignature() - )); - } - } - - return newClassMapping; - } - - private static boolean canMigrate(Signature oldObfSignature, ClassMatches classMatches) { - for (Type oldObfType : oldObfSignature.types()) { - if (!canMigrate(oldObfType, classMatches)) { - return false; - } - } - return true; - } - - private static boolean canMigrate(Type oldObfType, ClassMatches classMatches) { - - // non classes can be migrated - if (!oldObfType.hasClass()) { - return true; - } - - // non obfuscated classes can be migrated - ClassEntry classEntry = oldObfType.getClassEntry(); - if (classEntry.getPackageName() != null) { - return true; - } - - // obfuscated classes with mappings can be migrated - return classMatches.getUniqueMatches().containsKey(classEntry); - } - - public static void convertMappings(Mappings mappings, BiMap changes) { - - // sort the changes so classes are renamed in the correct order - // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b - LinkedHashMap sortedChanges = Maps.newLinkedHashMap(); - int numChangesLeft = changes.size(); - while (!changes.isEmpty()) { - Iterator> iter = changes.entrySet().iterator(); - while (iter.hasNext()) { - Map.Entry change = iter.next(); - if (changes.containsKey(change.getValue())) { - sortedChanges.put(change.getKey(), change.getValue()); - iter.remove(); - } - } - - // did we remove any changes? - if (numChangesLeft - changes.size() > 0) { - // keep going - numChangesLeft = changes.size(); - } else { - // can't sort anymore. There must be a loop - break; - } - } - if (!changes.isEmpty()) { - throw new Error("Unable to sort class changes! There must be a cycle."); - } - - // convert the mappings in the correct class order - for (Map.Entry entry : sortedChanges.entrySet()) { - mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName()); - } - } - - public interface Doer { - Collection getDroppedEntries(MappingsChecker checker); - - Collection getObfEntries(JarIndex jarIndex); - - Collection> getMappings(ClassMapping destClassMapping); - - Set filterEntries(Collection obfEntries, T obfSourceEntry, ClassMatches classMatches); - - void setUpdateObfMember(ClassMapping classMapping, MemberMapping memberMapping, T newEntry); - - boolean hasObfMember(ClassMapping classMapping, T obfEntry); - - void removeMemberByObf(ClassMapping classMapping, T obfEntry); - } - - public static Doer getFieldDoer() { - return new Doer() { - - @Override - public Collection getDroppedEntries(MappingsChecker checker) { - return checker.getDroppedFieldMappings().keySet(); - } - - @Override - public Collection getObfEntries(JarIndex jarIndex) { - return jarIndex.getObfFieldEntries(); - } - - @Override - public Collection> getMappings(ClassMapping destClassMapping) { - return (Collection>) destClassMapping.fields(); - } - - @Override - public Set filterEntries(Collection obfDestFields, FieldEntry obfSourceField, ClassMatches classMatches) { - Set out = Sets.newHashSet(); - for (FieldEntry obfDestField : obfDestFields) { - Type translatedDestType = translate(obfDestField.getType(), classMatches.getUniqueMatches().inverse()); - if (translatedDestType.equals(obfSourceField.getType())) { - out.add(obfDestField); - } - } - return out; - } - - @Override - public void setUpdateObfMember(ClassMapping classMapping, MemberMapping memberMapping, FieldEntry newField) { - FieldMapping fieldMapping = (FieldMapping) memberMapping; - classMapping.setFieldObfNameAndType(fieldMapping.getObfName(), fieldMapping.getObfType(), newField.getName(), newField.getType()); - } - - @Override - public boolean hasObfMember(ClassMapping classMapping, FieldEntry obfField) { - return classMapping.getFieldByObf(obfField.getName(), obfField.getType()) != null; - } - - @Override - public void removeMemberByObf(ClassMapping classMapping, FieldEntry obfField) { - classMapping.removeFieldMapping(classMapping.getFieldByObf(obfField.getName(), obfField.getType())); - } - }; - } - - public static Doer getMethodDoer() { - return new Doer() { - - @Override - public Collection getDroppedEntries(MappingsChecker checker) { - return checker.getDroppedMethodMappings().keySet(); - } - - @Override - public Collection getObfEntries(JarIndex jarIndex) { - return jarIndex.getObfBehaviorEntries(); - } - - @Override - public Collection> getMappings(ClassMapping destClassMapping) { - return (Collection>) destClassMapping.methods(); - } - - @Override - public Set filterEntries(Collection obfDestFields, BehaviorEntry obfSourceField, ClassMatches classMatches) { - Set out = Sets.newHashSet(); - for (BehaviorEntry obfDestField : obfDestFields) { - // Try to translate the signature - Signature translatedDestSignature = translate(obfDestField.getSignature(), classMatches.getUniqueMatches().inverse()); - if (translatedDestSignature != null && obfSourceField.getSignature() != null && translatedDestSignature.equals(obfSourceField.getSignature())) - out.add(obfDestField); - } - return out; - } - - @Override - public void setUpdateObfMember(ClassMapping classMapping, MemberMapping memberMapping, BehaviorEntry newBehavior) { - MethodMapping methodMapping = (MethodMapping) memberMapping; - classMapping.setMethodObfNameAndSignature(methodMapping.getObfName(), methodMapping.getObfSignature(), newBehavior.getName(), newBehavior.getSignature()); - } - - @Override - public boolean hasObfMember(ClassMapping classMapping, BehaviorEntry obfBehavior) { - return classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()) != null; - } - - @Override - public void removeMemberByObf(ClassMapping classMapping, BehaviorEntry obfBehavior) { - classMapping.removeMethodMapping(classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature())); - } - }; - } - - public static int compareMethodByteCode(CodeIterator sourceIt, CodeIterator destIt) - { - int sourcePos = 0; - int destPos = 0; - while (sourceIt.hasNext() && destIt.hasNext()) - { - try - { - sourcePos = sourceIt.next(); - destPos = destIt.next(); - if (sourceIt.byteAt(sourcePos) != destIt.byteAt(destPos)) - return sourcePos; - } catch (BadBytecode badBytecode) - { - // Ignore bad bytecode (it might be a little bit dangerous...) - } - } - if (sourcePos < destPos) - return sourcePos; - else if (destPos < sourcePos) - return destPos; - return sourcePos; - } - - public static BehaviorEntry compareMethods(CtClass destCtClass, CtClass sourceCtClass, BehaviorEntry obfSourceEntry, - Set obfDestEntries) - { - try - { - // Get the source method with Javassist - CtMethod sourceCtClassMethod = sourceCtClass.getMethod(obfSourceEntry.getName(), obfSourceEntry.getSignature().toString()); - CodeAttribute sourceAttribute = sourceCtClassMethod.getMethodInfo().getCodeAttribute(); - - // Empty method body, ignore! - if (sourceAttribute == null) - return null; - for (BehaviorEntry desEntry : obfDestEntries) - { - try - { - CtMethod destCtClassMethod = destCtClass - .getMethod(desEntry.getName(), desEntry.getSignature().toString()); - CodeAttribute destAttribute = destCtClassMethod.getMethodInfo().getCodeAttribute(); - - // Ignore empty body methods - if (destAttribute == null) - continue; - CodeIterator destIterator = destAttribute.iterator(); - int maxPos = compareMethodByteCode(sourceAttribute.iterator(), destIterator); - - // The bytecode is identical to the original method, assuming that the method is correct! - if (sourceAttribute.getCodeLength() == (maxPos + 1) && maxPos > 1) - return desEntry; - } catch (NotFoundException e) - { - e.printStackTrace(); - } - } - } catch (NotFoundException e) - { - e.printStackTrace(); - return null; - } - return null; - } - - public static MemberMatches computeMethodsMatches(Deobfuscator destDeobfuscator, Mappings destMappings, Deobfuscator sourceDeobfuscator, Mappings sourceMappings, ClassMatches classMatches, Doer doer) { - - MemberMatches memberMatches = new MemberMatches<>(); - - // unmatched source fields are easy - MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); - checker.dropBrokenMappings(destMappings); - for (BehaviorEntry destObfEntry : doer.getDroppedEntries(checker)) { - BehaviorEntry srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse()); - memberMatches.addUnmatchedSourceEntry(srcObfEntry); - } - - // get matched fields (anything that's left after the checks/drops is matched( - for (ClassMapping classMapping : destMappings.classes()) - collectMatchedFields(memberMatches, classMapping, classMatches, doer); - - // get unmatched dest fields - doer.getObfEntries(destDeobfuscator.getJarIndex()).stream() - .filter(destEntry -> !memberMatches.isMatchedDestEntry(destEntry)) - .forEach(memberMatches::addUnmatchedDestEntry); - - // Apply mappings to deobfuscator - - // Create type loader - TranslatingTypeLoader destTypeLoader = destDeobfuscator.createTypeLoader(); - TranslatingTypeLoader sourceTypeLoader = sourceDeobfuscator.createTypeLoader(); - - System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries..."); - - // go through the unmatched source fields and try to pick out the easy matches - for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) { - for (BehaviorEntry obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) { - - // get the possible dest matches - ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); - - // filter by type/signature - Set obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches); - - if (obfDestEntries.size() == 1) { - // make the easy match - memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next()); - } else if (obfDestEntries.isEmpty()) { - // no match is possible =( - memberMatches.makeSourceUnmatchable(obfSourceEntry, null); - } else - { - // Multiple matches! Scan methods instructions - CtClass destCtClass = destTypeLoader.loadClass(obfDestClass.getClassName()); - CtClass sourceCtClass = sourceTypeLoader.loadClass(obfSourceClass.getClassName()); - BehaviorEntry match = compareMethods(destCtClass, sourceCtClass, obfSourceEntry, obfDestEntries); - // the method match correctly, match it on the member mapping! - if (match != null) - memberMatches.makeMatch(obfSourceEntry, match); - } - } - } - - System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries", - memberMatches.getUnmatchedSourceEntries().size(), - memberMatches.getUnmatchableSourceEntries().size() - )); - - return memberMatches; - } - - public static MemberMatches computeMemberMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches, Doer doer) { - - MemberMatches memberMatches = new MemberMatches<>(); - - // unmatched source fields are easy - MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); - checker.dropBrokenMappings(destMappings); - for (T destObfEntry : doer.getDroppedEntries(checker)) { - T srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse()); - memberMatches.addUnmatchedSourceEntry(srcObfEntry); - } - - // get matched fields (anything that's left after the checks/drops is matched( - for (ClassMapping classMapping : destMappings.classes()) { - collectMatchedFields(memberMatches, classMapping, classMatches, doer); - } - - // get unmatched dest fields - for (T destEntry : doer.getObfEntries(destDeobfuscator.getJarIndex())) { - if (!memberMatches.isMatchedDestEntry(destEntry)) { - memberMatches.addUnmatchedDestEntry(destEntry); - } - } - - System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries..."); - - // go through the unmatched source fields and try to pick out the easy matches - for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) { - for (T obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) { - - // get the possible dest matches - ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); - - // filter by type/signature - Set obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches); - - if (obfDestEntries.size() == 1) { - // make the easy match - memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next()); - } else if (obfDestEntries.isEmpty()) { - // no match is possible =( - memberMatches.makeSourceUnmatchable(obfSourceEntry, null); - } - } - } - - System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries", - memberMatches.getUnmatchedSourceEntries().size(), - memberMatches.getUnmatchableSourceEntries().size() - )); - - return memberMatches; - } - - private static void collectMatchedFields(MemberMatches memberMatches, ClassMapping destClassMapping, ClassMatches classMatches, Doer doer) { - - // get the fields for this class - for (MemberMapping destEntryMapping : doer.getMappings(destClassMapping)) { - T destObfField = destEntryMapping.getObfEntry(destClassMapping.getObfEntry()); - T srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse()); - memberMatches.addMatch(srcObfField, destObfField); - } - - // recurse - for (ClassMapping destInnerClassMapping : destClassMapping.innerClasses()) { - collectMatchedFields(memberMatches, destInnerClassMapping, classMatches, doer); - } - } - - @SuppressWarnings("unchecked") - private static T translate(T in, BiMap map) { - if (in instanceof FieldEntry) { - return (T) new FieldEntry( - map.get(in.getClassEntry()), - in.getName(), - translate(((FieldEntry) in).getType(), map) - ); - } else if (in instanceof MethodEntry) { - return (T) new MethodEntry( - map.get(in.getClassEntry()), - in.getName(), - translate(((MethodEntry) in).getSignature(), map) - ); - } else if (in instanceof ConstructorEntry) { - return (T) new ConstructorEntry( - map.get(in.getClassEntry()), - translate(((ConstructorEntry) in).getSignature(), map) - ); - } - throw new Error("Unhandled entry type: " + in.getClass()); - } - - private static Type translate(Type type, final BiMap map) { - return new Type(type, inClassName -> - { - ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); - if (outClassEntry == null) { - return null; - } - return outClassEntry.getName(); - }); - } - - private static Signature translate(Signature signature, final BiMap map) { - if (signature == null) { - return null; - } - return new Signature(signature, inClassName -> - { - ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); - if (outClassEntry == null) { - return null; - } - return outClassEntry.getName(); - }); - } - - public static void applyMemberMatches(Mappings mappings, ClassMatches classMatches, MemberMatches memberMatches, Doer doer) { - for (ClassMapping classMapping : mappings.classes()) { - applyMemberMatches(classMapping, classMatches, memberMatches, doer); - } - } - - private static void applyMemberMatches(ClassMapping classMapping, ClassMatches classMatches, MemberMatches memberMatches, Doer doer) { - - // get the classes - ClassEntry obfDestClass = new ClassEntry(classMapping.getObfFullName()); - - // make a map of all the renames we need to make - Map renames = Maps.newHashMap(); - for (MemberMapping memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) { - T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass); - T obfSourceEntry = getSourceEntryFromDestMapping(memberMapping, obfDestClass, classMatches); - - // but drop the unmatchable things - if (memberMatches.isUnmatchableSourceEntry(obfSourceEntry)) { - doer.removeMemberByObf(classMapping, obfOldDestEntry); - continue; - } - - T obfNewDestEntry = memberMatches.matches().get(obfSourceEntry); - if (obfNewDestEntry != null && !obfOldDestEntry.getName().equals(obfNewDestEntry.getName())) { - renames.put(obfOldDestEntry, obfNewDestEntry); - } - } - - if (!renames.isEmpty()) { - - // apply to this class (should never need more than n passes) - int numRenamesAppliedThisRound; - do { - numRenamesAppliedThisRound = 0; - - for (MemberMapping memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) { - T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass); - T obfNewDestEntry = renames.get(obfOldDestEntry); - if (obfNewDestEntry != null) { - // make sure this rename won't cause a collision - // otherwise, save it for the next round and try again next time - if (!doer.hasObfMember(classMapping, obfNewDestEntry)) { - doer.setUpdateObfMember(classMapping, memberMapping, obfNewDestEntry); - renames.remove(obfOldDestEntry); - numRenamesAppliedThisRound++; - } - } - } - } while (numRenamesAppliedThisRound > 0); - - if (!renames.isEmpty()) { - System.err.println(String.format("WARNING: Couldn't apply all the renames for class %s. %d renames left.", - classMapping.getObfFullName(), renames.size() - )); - for (Map.Entry entry : renames.entrySet()) { - System.err.println(String.format("\t%s -> %s", entry.getKey().getName(), entry.getValue().getName())); - } - } - } - - // recurse - for (ClassMapping innerClassMapping : classMapping.innerClasses()) { - applyMemberMatches(innerClassMapping, classMatches, memberMatches, doer); - } - } - - private static T getSourceEntryFromDestMapping(MemberMapping destMemberMapping, ClassEntry obfDestClass, ClassMatches classMatches) { - return translate(destMemberMapping.getObfEntry(obfDestClass), classMatches.getUniqueMatches().inverse()); - } + public static ClassMatches computeClassMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) { + + // index jars + System.out.println("Indexing source jar..."); + JarIndex sourceIndex = new JarIndex(); + sourceIndex.indexJar(sourceJar, false); + System.out.println("Indexing dest jar..."); + JarIndex destIndex = new JarIndex(); + destIndex.indexJar(destJar, false); + + // compute the matching + ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex, null); + return new ClassMatches(matching.matches()); + } + + public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex, BiMap knownMatches) { + + System.out.println("Iteratively matching classes"); + + ClassMatching lastMatching = null; + int round = 0; + SidedClassNamer sourceNamer = null; + SidedClassNamer destNamer = null; + for (boolean useReferences : Arrays.asList(false, true)) { + + int numUniqueMatchesLastTime = 0; + if (lastMatching != null) { + numUniqueMatchesLastTime = lastMatching.uniqueMatches().size(); + } + + while (true) { + + System.out.println("Round " + (++round) + "..."); + + // init the matching with identity settings + ClassMatching matching = new ClassMatching( + new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences), + new ClassIdentifier(destJar, destIndex, destNamer, useReferences) + ); + + if (knownMatches != null) { + matching.addKnownMatches(knownMatches); + } + + if (lastMatching == null) { + // search all classes + matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries()); + } else { + // we already know about these matches from last time + matching.addKnownMatches(lastMatching.uniqueMatches()); + + // search unmatched and ambiguously-matched classes + matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses()); + for (ClassMatch match : lastMatching.ambiguousMatches()) { + matching.match(match.sourceClasses, match.destClasses); + } + } + System.out.println(matching); + BiMap uniqueMatches = matching.uniqueMatches(); + + // did we match anything new this time? + if (uniqueMatches.size() > numUniqueMatchesLastTime) { + numUniqueMatchesLastTime = uniqueMatches.size(); + lastMatching = matching; + } else { + break; + } + + // update the namers + ClassNamer namer = new ClassNamer(uniqueMatches); + sourceNamer = namer.getSourceNamer(); + destNamer = namer.getDestNamer(); + } + } + + return lastMatching; + } + + public static Mappings newMappings(ClassMatches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) + throws MappingConflict { + // sort the unique matches by size of inner class chain + Multimap> matchesByDestChainSize = HashMultimap.create(); + for (java.util.Map.Entry match : matches.getUniqueMatches().entrySet()) { + int chainSize = destDeobfuscator.getJarIndex().getObfClassChain(match.getValue()).size(); + matchesByDestChainSize.put(chainSize, match); + } + + // build the mappings (in order of small-to-large inner chains) + Mappings newMappings = new Mappings(); + List chainSizes = Lists.newArrayList(matchesByDestChainSize.keySet()); + Collections.sort(chainSizes); + for (int chainSize : chainSizes) { + for (java.util.Map.Entry match : matchesByDestChainSize.get(chainSize)) { + // get class info + ClassEntry obfSourceClassEntry = match.getKey(); + ClassEntry obfDestClassEntry = match.getValue(); + List destClassChain = destDeobfuscator.getJarIndex().getObfClassChain(obfDestClassEntry); + + ClassMapping sourceMapping; + if (obfSourceClassEntry.isInnerClass()) { + List srcClassChain = sourceDeobfuscator.getMappings().getClassMappingChain(obfSourceClassEntry); + sourceMapping = srcClassChain.get(srcClassChain.size() - 1); + } else { + sourceMapping = sourceDeobfuscator.getMappings().getClassByObf(obfSourceClassEntry); + } + + if (sourceMapping == null) { + // if this class was never deobfuscated, don't try to match it + continue; + } + + // find out where to make the dest class mapping + if (destClassChain.size() == 1) { + // not an inner class, add directly to mappings + newMappings.addClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, false)); + } else { + // inner class, find the outer class mapping + ClassMapping destMapping = null; + for (int i = 0; i < destClassChain.size() - 1; i++) { + ClassEntry destChainClassEntry = destClassChain.get(i); + if (destMapping == null) { + destMapping = newMappings.getClassByObf(destChainClassEntry); + if (destMapping == null) { + destMapping = new ClassMapping(destChainClassEntry.getName()); + newMappings.addClassMapping(destMapping); + } + } else { + destMapping = destMapping.getInnerClassByObfSimple(destChainClassEntry.getInnermostClassName()); + if (destMapping == null) { + destMapping = new ClassMapping(destChainClassEntry.getName()); + destMapping.addInnerClassMapping(destMapping); + } + } + } + destMapping.addInnerClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, true)); + } + } + } + return newMappings; + } + + private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping oldClassMapping, final ClassMatches matches, boolean useSimpleName) { + + ClassNameReplacer replacer = className -> + { + ClassEntry newClassEntry = matches.getUniqueMatches().get(new ClassEntry(className)); + if (newClassEntry != null) { + return newClassEntry.getName(); + } + return null; + }; + + ClassMapping newClassMapping; + String deobfName = oldClassMapping.getDeobfName(); + if (deobfName != null) { + if (useSimpleName) { + deobfName = new ClassEntry(deobfName).getSimpleName(); + } + newClassMapping = new ClassMapping(newObfClass.getName(), deobfName); + } else { + newClassMapping = new ClassMapping(newObfClass.getName()); + } + + // migrate fields + for (FieldMapping oldFieldMapping : oldClassMapping.fields()) { + if (canMigrate(oldFieldMapping.getObfType(), matches)) { + newClassMapping.addFieldMapping(new FieldMapping(oldFieldMapping, replacer)); + } else { + System.out.println(String.format("Can't map field, dropping: %s.%s %s", + oldClassMapping.getDeobfName(), + oldFieldMapping.getDeobfName(), + oldFieldMapping.getObfType() + )); + } + } + + // migrate methods + for (MethodMapping oldMethodMapping : oldClassMapping.methods()) { + if (canMigrate(oldMethodMapping.getObfSignature(), matches)) { + newClassMapping.addMethodMapping(new MethodMapping(oldMethodMapping, replacer)); + } else { + System.out.println(String.format("Can't map method, dropping: %s.%s %s", + oldClassMapping.getDeobfName(), + oldMethodMapping.getDeobfName(), + oldMethodMapping.getObfSignature() + )); + } + } + + return newClassMapping; + } + + private static boolean canMigrate(Signature oldObfSignature, ClassMatches classMatches) { + for (Type oldObfType : oldObfSignature.types()) { + if (!canMigrate(oldObfType, classMatches)) { + return false; + } + } + return true; + } + + private static boolean canMigrate(Type oldObfType, ClassMatches classMatches) { + + // non classes can be migrated + if (!oldObfType.hasClass()) { + return true; + } + + // non obfuscated classes can be migrated + ClassEntry classEntry = oldObfType.getClassEntry(); + if (classEntry.getPackageName() != null) { + return true; + } + + // obfuscated classes with mappings can be migrated + return classMatches.getUniqueMatches().containsKey(classEntry); + } + + public static void convertMappings(Mappings mappings, BiMap changes) { + + // sort the changes so classes are renamed in the correct order + // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b + LinkedHashMap sortedChanges = Maps.newLinkedHashMap(); + int numChangesLeft = changes.size(); + while (!changes.isEmpty()) { + Iterator> iter = changes.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry change = iter.next(); + if (changes.containsKey(change.getValue())) { + sortedChanges.put(change.getKey(), change.getValue()); + iter.remove(); + } + } + + // did we remove any changes? + if (numChangesLeft - changes.size() > 0) { + // keep going + numChangesLeft = changes.size(); + } else { + // can't sort anymore. There must be a loop + break; + } + } + if (!changes.isEmpty()) { + throw new Error("Unable to sort class changes! There must be a cycle."); + } + + // convert the mappings in the correct class order + for (Map.Entry entry : sortedChanges.entrySet()) { + mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName()); + } + } + + public static Doer getFieldDoer() { + return new Doer() { + + @Override + public Collection getDroppedEntries(MappingsChecker checker) { + return checker.getDroppedFieldMappings().keySet(); + } + + @Override + public Collection getObfEntries(JarIndex jarIndex) { + return jarIndex.getObfFieldEntries(); + } + + @Override + public Collection> getMappings(ClassMapping destClassMapping) { + return (Collection>) destClassMapping.fields(); + } + + @Override + public Set filterEntries(Collection obfDestFields, FieldEntry obfSourceField, ClassMatches classMatches) { + Set out = Sets.newHashSet(); + for (FieldEntry obfDestField : obfDestFields) { + Type translatedDestType = translate(obfDestField.getType(), classMatches.getUniqueMatches().inverse()); + if (translatedDestType.equals(obfSourceField.getType())) { + out.add(obfDestField); + } + } + return out; + } + + @Override + public void setUpdateObfMember(ClassMapping classMapping, MemberMapping memberMapping, FieldEntry newField) { + FieldMapping fieldMapping = (FieldMapping) memberMapping; + classMapping.setFieldObfNameAndType(fieldMapping.getObfName(), fieldMapping.getObfType(), newField.getName(), newField.getType()); + } + + @Override + public boolean hasObfMember(ClassMapping classMapping, FieldEntry obfField) { + return classMapping.getFieldByObf(obfField.getName(), obfField.getType()) != null; + } + + @Override + public void removeMemberByObf(ClassMapping classMapping, FieldEntry obfField) { + classMapping.removeFieldMapping(classMapping.getFieldByObf(obfField.getName(), obfField.getType())); + } + }; + } + + public static Doer getMethodDoer() { + return new Doer() { + + @Override + public Collection getDroppedEntries(MappingsChecker checker) { + return checker.getDroppedMethodMappings().keySet(); + } + + @Override + public Collection getObfEntries(JarIndex jarIndex) { + return jarIndex.getObfBehaviorEntries(); + } + + @Override + public Collection> getMappings(ClassMapping destClassMapping) { + return (Collection>) destClassMapping.methods(); + } + + @Override + public Set filterEntries(Collection obfDestFields, BehaviorEntry obfSourceField, ClassMatches classMatches) { + Set out = Sets.newHashSet(); + for (BehaviorEntry obfDestField : obfDestFields) { + // Try to translate the signature + Signature translatedDestSignature = translate(obfDestField.getSignature(), classMatches.getUniqueMatches().inverse()); + if (translatedDestSignature != null && obfSourceField.getSignature() != null && translatedDestSignature.equals(obfSourceField.getSignature())) + out.add(obfDestField); + } + return out; + } + + @Override + public void setUpdateObfMember(ClassMapping classMapping, MemberMapping memberMapping, BehaviorEntry newBehavior) { + MethodMapping methodMapping = (MethodMapping) memberMapping; + classMapping.setMethodObfNameAndSignature(methodMapping.getObfName(), methodMapping.getObfSignature(), newBehavior.getName(), newBehavior.getSignature()); + } + + @Override + public boolean hasObfMember(ClassMapping classMapping, BehaviorEntry obfBehavior) { + return classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()) != null; + } + + @Override + public void removeMemberByObf(ClassMapping classMapping, BehaviorEntry obfBehavior) { + classMapping.removeMethodMapping(classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature())); + } + }; + } + + public static int compareMethodByteCode(CodeIterator sourceIt, CodeIterator destIt) { + int sourcePos = 0; + int destPos = 0; + while (sourceIt.hasNext() && destIt.hasNext()) { + try { + sourcePos = sourceIt.next(); + destPos = destIt.next(); + if (sourceIt.byteAt(sourcePos) != destIt.byteAt(destPos)) + return sourcePos; + } catch (BadBytecode badBytecode) { + // Ignore bad bytecode (it might be a little bit dangerous...) + } + } + if (sourcePos < destPos) + return sourcePos; + else if (destPos < sourcePos) + return destPos; + return sourcePos; + } + + public static BehaviorEntry compareMethods(CtClass destCtClass, CtClass sourceCtClass, BehaviorEntry obfSourceEntry, + Set obfDestEntries) { + try { + // Get the source method with Javassist + CtMethod sourceCtClassMethod = sourceCtClass.getMethod(obfSourceEntry.getName(), obfSourceEntry.getSignature().toString()); + CodeAttribute sourceAttribute = sourceCtClassMethod.getMethodInfo().getCodeAttribute(); + + // Empty method body, ignore! + if (sourceAttribute == null) + return null; + for (BehaviorEntry desEntry : obfDestEntries) { + try { + CtMethod destCtClassMethod = destCtClass + .getMethod(desEntry.getName(), desEntry.getSignature().toString()); + CodeAttribute destAttribute = destCtClassMethod.getMethodInfo().getCodeAttribute(); + + // Ignore empty body methods + if (destAttribute == null) + continue; + CodeIterator destIterator = destAttribute.iterator(); + int maxPos = compareMethodByteCode(sourceAttribute.iterator(), destIterator); + + // The bytecode is identical to the original method, assuming that the method is correct! + if (sourceAttribute.getCodeLength() == (maxPos + 1) && maxPos > 1) + return desEntry; + } catch (NotFoundException e) { + e.printStackTrace(); + } + } + } catch (NotFoundException e) { + e.printStackTrace(); + return null; + } + return null; + } + + public static MemberMatches computeMethodsMatches(Deobfuscator destDeobfuscator, + Mappings destMappings, + Deobfuscator sourceDeobfuscator, + Mappings sourceMappings, + ClassMatches classMatches, + Doer doer) { + + MemberMatches memberMatches = new MemberMatches<>(); + + // unmatched source fields are easy + MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); + checker.dropBrokenMappings(destMappings); + for (BehaviorEntry destObfEntry : doer.getDroppedEntries(checker)) { + BehaviorEntry srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse()); + memberMatches.addUnmatchedSourceEntry(srcObfEntry); + } + + // get matched fields (anything that's left after the checks/drops is matched( + for (ClassMapping classMapping : destMappings.classes()) + collectMatchedFields(memberMatches, classMapping, classMatches, doer); + + // get unmatched dest fields + doer.getObfEntries(destDeobfuscator.getJarIndex()).stream() + .filter(destEntry -> !memberMatches.isMatchedDestEntry(destEntry)) + .forEach(memberMatches::addUnmatchedDestEntry); + + // Apply mappings to deobfuscator + + // Create type loader + TranslatingTypeLoader destTypeLoader = destDeobfuscator.createTypeLoader(); + TranslatingTypeLoader sourceTypeLoader = sourceDeobfuscator.createTypeLoader(); + + System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries..."); + + // go through the unmatched source fields and try to pick out the easy matches + for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) { + for (BehaviorEntry obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) { + + // get the possible dest matches + ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); + + // filter by type/signature + Set obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches); + + if (obfDestEntries.size() == 1) { + // make the easy match + memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next()); + } else if (obfDestEntries.isEmpty()) { + // no match is possible =( + memberMatches.makeSourceUnmatchable(obfSourceEntry, null); + } else { + // Multiple matches! Scan methods instructions + CtClass destCtClass = destTypeLoader.loadClass(obfDestClass.getClassName()); + CtClass sourceCtClass = sourceTypeLoader.loadClass(obfSourceClass.getClassName()); + BehaviorEntry match = compareMethods(destCtClass, sourceCtClass, obfSourceEntry, obfDestEntries); + // the method match correctly, match it on the member mapping! + if (match != null) + memberMatches.makeMatch(obfSourceEntry, match); + } + } + } + + System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries", + memberMatches.getUnmatchedSourceEntries().size(), + memberMatches.getUnmatchableSourceEntries().size() + )); + + return memberMatches; + } + + public static MemberMatches computeMemberMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches, Doer doer) { + + MemberMatches memberMatches = new MemberMatches<>(); + + // unmatched source fields are easy + MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); + checker.dropBrokenMappings(destMappings); + for (T destObfEntry : doer.getDroppedEntries(checker)) { + T srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse()); + memberMatches.addUnmatchedSourceEntry(srcObfEntry); + } + + // get matched fields (anything that's left after the checks/drops is matched( + for (ClassMapping classMapping : destMappings.classes()) { + collectMatchedFields(memberMatches, classMapping, classMatches, doer); + } + + // get unmatched dest fields + for (T destEntry : doer.getObfEntries(destDeobfuscator.getJarIndex())) { + if (!memberMatches.isMatchedDestEntry(destEntry)) { + memberMatches.addUnmatchedDestEntry(destEntry); + } + } + + System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries..."); + + // go through the unmatched source fields and try to pick out the easy matches + for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) { + for (T obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) { + + // get the possible dest matches + ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); + + // filter by type/signature + Set obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches); + + if (obfDestEntries.size() == 1) { + // make the easy match + memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next()); + } else if (obfDestEntries.isEmpty()) { + // no match is possible =( + memberMatches.makeSourceUnmatchable(obfSourceEntry, null); + } + } + } + + System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries", + memberMatches.getUnmatchedSourceEntries().size(), + memberMatches.getUnmatchableSourceEntries().size() + )); + + return memberMatches; + } + + private static void collectMatchedFields(MemberMatches memberMatches, ClassMapping destClassMapping, ClassMatches classMatches, Doer doer) { + + // get the fields for this class + for (MemberMapping destEntryMapping : doer.getMappings(destClassMapping)) { + T destObfField = destEntryMapping.getObfEntry(destClassMapping.getObfEntry()); + T srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse()); + memberMatches.addMatch(srcObfField, destObfField); + } + + // recurse + for (ClassMapping destInnerClassMapping : destClassMapping.innerClasses()) { + collectMatchedFields(memberMatches, destInnerClassMapping, classMatches, doer); + } + } + + @SuppressWarnings("unchecked") + private static T translate(T in, BiMap map) { + if (in instanceof FieldEntry) { + return (T) new FieldEntry( + map.get(in.getClassEntry()), + in.getName(), + translate(((FieldEntry) in).getType(), map) + ); + } else if (in instanceof MethodEntry) { + return (T) new MethodEntry( + map.get(in.getClassEntry()), + in.getName(), + translate(((MethodEntry) in).getSignature(), map) + ); + } else if (in instanceof ConstructorEntry) { + return (T) new ConstructorEntry( + map.get(in.getClassEntry()), + translate(((ConstructorEntry) in).getSignature(), map) + ); + } + throw new Error("Unhandled entry type: " + in.getClass()); + } + + private static Type translate(Type type, final BiMap map) { + return new Type(type, inClassName -> + { + ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); + if (outClassEntry == null) { + return null; + } + return outClassEntry.getName(); + }); + } + + private static Signature translate(Signature signature, final BiMap map) { + if (signature == null) { + return null; + } + return new Signature(signature, inClassName -> + { + ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); + if (outClassEntry == null) { + return null; + } + return outClassEntry.getName(); + }); + } + + public static void applyMemberMatches(Mappings mappings, ClassMatches classMatches, MemberMatches memberMatches, Doer doer) { + for (ClassMapping classMapping : mappings.classes()) { + applyMemberMatches(classMapping, classMatches, memberMatches, doer); + } + } + + private static void applyMemberMatches(ClassMapping classMapping, ClassMatches classMatches, MemberMatches memberMatches, Doer doer) { + + // get the classes + ClassEntry obfDestClass = new ClassEntry(classMapping.getObfFullName()); + + // make a map of all the renames we need to make + Map renames = Maps.newHashMap(); + for (MemberMapping memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) { + T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass); + T obfSourceEntry = getSourceEntryFromDestMapping(memberMapping, obfDestClass, classMatches); + + // but drop the unmatchable things + if (memberMatches.isUnmatchableSourceEntry(obfSourceEntry)) { + doer.removeMemberByObf(classMapping, obfOldDestEntry); + continue; + } + + T obfNewDestEntry = memberMatches.matches().get(obfSourceEntry); + if (obfNewDestEntry != null && !obfOldDestEntry.getName().equals(obfNewDestEntry.getName())) { + renames.put(obfOldDestEntry, obfNewDestEntry); + } + } + + if (!renames.isEmpty()) { + + // apply to this class (should never need more than n passes) + int numRenamesAppliedThisRound; + do { + numRenamesAppliedThisRound = 0; + + for (MemberMapping memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) { + T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass); + T obfNewDestEntry = renames.get(obfOldDestEntry); + if (obfNewDestEntry != null) { + // make sure this rename won't cause a collision + // otherwise, save it for the next round and try again next time + if (!doer.hasObfMember(classMapping, obfNewDestEntry)) { + doer.setUpdateObfMember(classMapping, memberMapping, obfNewDestEntry); + renames.remove(obfOldDestEntry); + numRenamesAppliedThisRound++; + } + } + } + } while (numRenamesAppliedThisRound > 0); + + if (!renames.isEmpty()) { + System.err.println(String.format("WARNING: Couldn't apply all the renames for class %s. %d renames left.", + classMapping.getObfFullName(), renames.size() + )); + for (Map.Entry entry : renames.entrySet()) { + System.err.println(String.format("\t%s -> %s", entry.getKey().getName(), entry.getValue().getName())); + } + } + } + + // recurse + for (ClassMapping innerClassMapping : classMapping.innerClasses()) { + applyMemberMatches(innerClassMapping, classMatches, memberMatches, doer); + } + } + + private static T getSourceEntryFromDestMapping(MemberMapping destMemberMapping, ClassEntry obfDestClass, ClassMatches classMatches) { + return translate(destMemberMapping.getObfEntry(obfDestClass), classMatches.getUniqueMatches().inverse()); + } + + public interface Doer { + Collection getDroppedEntries(MappingsChecker checker); + + Collection getObfEntries(JarIndex jarIndex); + + Collection> getMappings(ClassMapping destClassMapping); + + Set filterEntries(Collection obfEntries, T obfSourceEntry, ClassMatches classMatches); + + void setUpdateObfMember(ClassMapping classMapping, MemberMapping memberMapping, T newEntry); + + boolean hasObfMember(ClassMapping classMapping, T obfEntry); + + void removeMemberByObf(ClassMapping classMapping, T obfEntry); + } } diff --git a/src/main/java/cuchaz/enigma/convert/MatchesReader.java b/src/main/java/cuchaz/enigma/convert/MatchesReader.java index d86d6c2..1cf50fa 100644 --- a/src/main/java/cuchaz/enigma/convert/MatchesReader.java +++ b/src/main/java/cuchaz/enigma/convert/MatchesReader.java @@ -8,99 +8,98 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.convert; import com.google.common.collect.Lists; +import cuchaz.enigma.mapping.*; import java.io.*; import java.nio.charset.Charset; import java.util.Collection; import java.util.List; -import cuchaz.enigma.mapping.*; - - public class MatchesReader { - public static ClassMatches readClasses(File file) - throws IOException { - try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")))) { - ClassMatches matches = new ClassMatches(); - String line; - while ((line = in.readLine()) != null) { - matches.add(readClassMatch(line)); - } - return matches; - } - } + public static ClassMatches readClasses(File file) + throws IOException { + try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")))) { + ClassMatches matches = new ClassMatches(); + String line; + while ((line = in.readLine()) != null) { + matches.add(readClassMatch(line)); + } + return matches; + } + } - private static ClassMatch readClassMatch(String line) { - String[] sides = line.split(":", 2); - return new ClassMatch(readClasses(sides[0]), readClasses(sides[1])); - } + private static ClassMatch readClassMatch(String line) { + String[] sides = line.split(":", 2); + return new ClassMatch(readClasses(sides[0]), readClasses(sides[1])); + } - private static Collection readClasses(String in) { - List entries = Lists.newArrayList(); - for (String className : in.split(",")) { - className = className.trim(); - if (className.length() > 0) { - entries.add(new ClassEntry(className)); - } - } - return entries; - } + private static Collection readClasses(String in) { + List entries = Lists.newArrayList(); + for (String className : in.split(",")) { + className = className.trim(); + if (!className.isEmpty()) { + entries.add(new ClassEntry(className)); + } + } + return entries; + } - public static MemberMatches readMembers(File file) - throws IOException { - try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")))) { - MemberMatches matches = new MemberMatches<>(); - String line; - while ((line = in.readLine()) != null) { - readMemberMatch(matches, line); - } - return matches; - } - } + public static MemberMatches readMembers(File file) + throws IOException { + try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")))) { + MemberMatches matches = new MemberMatches<>(); + String line; + while ((line = in.readLine()) != null) { + readMemberMatch(matches, line); + } + return matches; + } + } - private static void readMemberMatch(MemberMatches matches, String line) { - if (line.startsWith("!")) { - T source = readEntry(line.substring(1)); - matches.addUnmatchableSourceEntry(source); - } else { - String[] parts = line.split(":", 2); - T source = readEntry(parts[0]); - T dest = readEntry(parts[1]); - if (source != null && dest != null) { - matches.addMatch(source, dest); - } else if (source != null) { - matches.addUnmatchedSourceEntry(source); - } else if (dest != null) { - matches.addUnmatchedDestEntry(dest); - } - } - } + private static void readMemberMatch(MemberMatches matches, String line) { + if (line.startsWith("!")) { + T source = readEntry(line.substring(1)); + matches.addUnmatchableSourceEntry(source); + } else { + String[] parts = line.split(":", 2); + T source = readEntry(parts[0]); + T dest = readEntry(parts[1]); + if (source != null && dest != null) { + matches.addMatch(source, dest); + } else if (source != null) { + matches.addUnmatchedSourceEntry(source); + } else if (dest != null) { + matches.addUnmatchedDestEntry(dest); + } + } + } - @SuppressWarnings("unchecked") - private static T readEntry(String in) { - if (in.length() <= 0) { - return null; - } - String[] parts = in.split(" "); - if (parts.length == 3 && parts[2].indexOf('(') < 0) { - return (T) new FieldEntry( - new ClassEntry(parts[0]), - parts[1], - new Type(parts[2]) - ); - } else { - assert (parts.length == 2 || parts.length == 3); - if (parts.length == 2) { - return (T) EntryFactory.getBehaviorEntry(parts[0], parts[1]); - } else if (parts.length == 3) { - return (T) EntryFactory.getBehaviorEntry(parts[0], parts[1], parts[2]); - } else { - throw new Error("Malformed behavior entry: " + in); - } - } - } + @SuppressWarnings("unchecked") + private static T readEntry(String in) { + if (in.length() <= 0) { + return null; + } + String[] parts = in.split(" "); + if (parts.length == 3 && parts[2].indexOf('(') < 0) { + return (T) new FieldEntry( + new ClassEntry(parts[0]), + parts[1], + new Type(parts[2]) + ); + } else { + assert (parts.length == 2 || parts.length == 3); + if (parts.length == 2) { + return (T) EntryFactory.getBehaviorEntry(parts[0], parts[1]); + } else if (parts.length == 3) { + return (T) EntryFactory.getBehaviorEntry(parts[0], parts[1], parts[2]); + } else { + throw new Error("Malformed behavior entry: " + in); + } + } + } } diff --git a/src/main/java/cuchaz/enigma/convert/MatchesWriter.java b/src/main/java/cuchaz/enigma/convert/MatchesWriter.java index dccbf6f..8fe7326 100644 --- a/src/main/java/cuchaz/enigma/convert/MatchesWriter.java +++ b/src/main/java/cuchaz/enigma/convert/MatchesWriter.java @@ -8,113 +8,116 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ -package cuchaz.enigma.convert; -import java.io.*; -import java.nio.charset.Charset; -import java.util.Map; +package cuchaz.enigma.convert; import cuchaz.enigma.mapping.BehaviorEntry; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.Entry; import cuchaz.enigma.mapping.FieldEntry; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.Charset; +import java.util.Map; public class MatchesWriter { - public static void writeClasses(ClassMatches matches, File file) - throws IOException { - try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF-8"))) { - for (ClassMatch match : matches) { - writeClassMatch(out, match); - } - } - } + public static void writeClasses(ClassMatches matches, File file) + throws IOException { + try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF-8"))) { + for (ClassMatch match : matches) { + writeClassMatch(out, match); + } + } + } - private static void writeClassMatch(OutputStreamWriter out, ClassMatch match) - throws IOException { - writeClasses(out, match.sourceClasses); - out.write(":"); - writeClasses(out, match.destClasses); - out.write("\n"); - } + private static void writeClassMatch(OutputStreamWriter out, ClassMatch match) + throws IOException { + writeClasses(out, match.sourceClasses); + out.write(":"); + writeClasses(out, match.destClasses); + out.write("\n"); + } - private static void writeClasses(OutputStreamWriter out, Iterable classes) - throws IOException { - boolean isFirst = true; - for (ClassEntry entry : classes) { - if (isFirst) { - isFirst = false; - } else { - out.write(","); - } - out.write(entry.toString()); - } - } + private static void writeClasses(OutputStreamWriter out, Iterable classes) + throws IOException { + boolean isFirst = true; + for (ClassEntry entry : classes) { + if (isFirst) { + isFirst = false; + } else { + out.write(","); + } + out.write(entry.toString()); + } + } - public static void writeMembers(MemberMatches matches, File file) - throws IOException { - try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF-8"))) { - for (Map.Entry match : matches.matches().entrySet()) { - writeMemberMatch(out, match.getKey(), match.getValue()); - } - for (T entry : matches.getUnmatchedSourceEntries()) { - writeMemberMatch(out, entry, null); - } - for (T entry : matches.getUnmatchedDestEntries()) { - writeMemberMatch(out, null, entry); - } - for (T entry : matches.getUnmatchableSourceEntries()) { - writeUnmatchableEntry(out, entry); - } - } - } + public static void writeMembers(MemberMatches matches, File file) + throws IOException { + try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF-8"))) { + for (Map.Entry match : matches.matches().entrySet()) { + writeMemberMatch(out, match.getKey(), match.getValue()); + } + for (T entry : matches.getUnmatchedSourceEntries()) { + writeMemberMatch(out, entry, null); + } + for (T entry : matches.getUnmatchedDestEntries()) { + writeMemberMatch(out, null, entry); + } + for (T entry : matches.getUnmatchableSourceEntries()) { + writeUnmatchableEntry(out, entry); + } + } + } - private static void writeMemberMatch(OutputStreamWriter out, T source, T dest) - throws IOException { - if (source != null) { - writeEntry(out, source); - } - out.write(":"); - if (dest != null) { - writeEntry(out, dest); - } - out.write("\n"); - } + private static void writeMemberMatch(OutputStreamWriter out, T source, T dest) + throws IOException { + if (source != null) { + writeEntry(out, source); + } + out.write(":"); + if (dest != null) { + writeEntry(out, dest); + } + out.write("\n"); + } - private static void writeUnmatchableEntry(OutputStreamWriter out, T entry) - throws IOException { - out.write("!"); - writeEntry(out, entry); - out.write("\n"); - } + private static void writeUnmatchableEntry(OutputStreamWriter out, T entry) + throws IOException { + out.write("!"); + writeEntry(out, entry); + out.write("\n"); + } - private static void writeEntry(OutputStreamWriter out, T entry) - throws IOException { - if (entry instanceof FieldEntry) { - writeField(out, (FieldEntry) entry); - } else if (entry instanceof BehaviorEntry) { - writeBehavior(out, (BehaviorEntry) entry); - } - } + private static void writeEntry(OutputStreamWriter out, T entry) + throws IOException { + if (entry instanceof FieldEntry) { + writeField(out, (FieldEntry) entry); + } else if (entry instanceof BehaviorEntry) { + writeBehavior(out, (BehaviorEntry) entry); + } + } - private static void writeField(OutputStreamWriter out, FieldEntry fieldEntry) - throws IOException { - out.write(fieldEntry.getClassName()); - out.write(" "); - out.write(fieldEntry.getName()); - out.write(" "); - out.write(fieldEntry.getType().toString()); - } + private static void writeField(OutputStreamWriter out, FieldEntry fieldEntry) + throws IOException { + out.write(fieldEntry.getClassName()); + out.write(" "); + out.write(fieldEntry.getName()); + out.write(" "); + out.write(fieldEntry.getType().toString()); + } - private static void writeBehavior(OutputStreamWriter out, BehaviorEntry behaviorEntry) - throws IOException { - out.write(behaviorEntry.getClassName()); - out.write(" "); - out.write(behaviorEntry.getName()); - out.write(" "); - if (behaviorEntry.getSignature() != null) { - out.write(behaviorEntry.getSignature().toString()); - } - } + private static void writeBehavior(OutputStreamWriter out, BehaviorEntry behaviorEntry) + throws IOException { + out.write(behaviorEntry.getClassName()); + out.write(" "); + out.write(behaviorEntry.getName()); + out.write(" "); + if (behaviorEntry.getSignature() != null) { + out.write(behaviorEntry.getSignature().toString()); + } + } } diff --git a/src/main/java/cuchaz/enigma/convert/MemberMatches.java b/src/main/java/cuchaz/enigma/convert/MemberMatches.java index 51cee85..bd74311 100644 --- a/src/main/java/cuchaz/enigma/convert/MemberMatches.java +++ b/src/main/java/cuchaz/enigma/convert/MemberMatches.java @@ -8,6 +8,7 @@ * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ + package cuchaz.enigma.convert; import com.google.common.collect.*; @@ -18,165 +19,161 @@ import cuchaz.enigma.mapping.Entry; import java.util.Collection; import java.util.Set; - public class MemberMatches { - private BiMap matches; - private Multimap matchedSourceEntries; - private Multimap unmatchedSourceEntries; - private Multimap unmatchedDestEntries; - private Multimap unmatchableSourceEntries; - - public MemberMatches() { - matches = HashBiMap.create(); - matchedSourceEntries = HashMultimap.create(); - unmatchedSourceEntries = HashMultimap.create(); - unmatchedDestEntries = HashMultimap.create(); - unmatchableSourceEntries = HashMultimap.create(); - } - - public void addMatch(T srcEntry, T destEntry) { - boolean wasAdded = matches.put(srcEntry, destEntry) == null; - assert (wasAdded); - wasAdded = matchedSourceEntries.put(srcEntry.getClassEntry(), srcEntry); - assert (wasAdded); - } - - public void addUnmatchedSourceEntry(T sourceEntry) { - boolean wasAdded = unmatchedSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry); - assert (wasAdded); - } - - public void addUnmatchedSourceEntries(Iterable sourceEntries) { - for (T sourceEntry : sourceEntries) { - addUnmatchedSourceEntry(sourceEntry); - } - } - - public void addUnmatchedDestEntry(T destEntry) { - if (destEntry.getName().equals("") || destEntry.getName().equals("")) - return; - boolean wasAdded = unmatchedDestEntries.put(destEntry.getClassEntry(), destEntry); - assert (wasAdded); - } - - public void addUnmatchedDestEntries(Iterable destEntriesntries) { - for (T entry : destEntriesntries) { - addUnmatchedDestEntry(entry); - } - } - - public void addUnmatchableSourceEntry(T sourceEntry) { - boolean wasAdded = unmatchableSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry); - assert (wasAdded); - } - - public Set getSourceClassesWithUnmatchedEntries() { - return unmatchedSourceEntries.keySet(); - } - - public Collection getSourceClassesWithoutUnmatchedEntries() { - Set out = Sets.newHashSet(); - out.addAll(matchedSourceEntries.keySet()); - out.removeAll(unmatchedSourceEntries.keySet()); - return out; - } - - public Collection getUnmatchedSourceEntries() { - return unmatchedSourceEntries.values(); - } - - public Collection getUnmatchedSourceEntries(ClassEntry sourceClass) { - return unmatchedSourceEntries.get(sourceClass); - } - - public Collection getUnmatchedDestEntries() { - return unmatchedDestEntries.values(); - } - - public Collection getUnmatchedDestEntries(ClassEntry destClass) { - return unmatchedDestEntries.get(destClass); - } - - public Collection getUnmatchableSourceEntries() { - return unmatchableSourceEntries.values(); - } - - public boolean hasSource(T sourceEntry) { - return matches.containsKey(sourceEntry) || unmatchedSourceEntries.containsValue(sourceEntry); - } - - public boolean hasDest(T destEntry) { - return matches.containsValue(destEntry) || unmatchedDestEntries.containsValue(destEntry); - } - - public BiMap matches() { - return matches; - } - - public boolean isMatchedSourceEntry(T sourceEntry) { - return matches.containsKey(sourceEntry); - } - - public boolean isMatchedDestEntry(T destEntry) { - return matches.containsValue(destEntry); - } - - public boolean isUnmatchableSourceEntry(T sourceEntry) { - return unmatchableSourceEntries.containsEntry(sourceEntry.getClassEntry(), sourceEntry); - } - public void makeMatch(T sourceEntry, T destEntry) { - makeMatch(sourceEntry, destEntry, null, null); - } - - public void makeMatch(T sourceEntry, T destEntry, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { - if (sourceDeobfuscator != null && destDeobfuscator != null) - { - makeMatch(sourceEntry, destEntry); - sourceEntry = (T) sourceEntry.cloneToNewClass(sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true)); - destEntry = (T) destEntry.cloneToNewClass(destDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(destEntry, true)); - } - boolean wasRemoved = unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); - assert (wasRemoved); - wasRemoved = unmatchedDestEntries.remove(destEntry.getClassEntry(), destEntry); - assert (wasRemoved); - addMatch(sourceEntry, destEntry); - } - - public boolean isMatched(T sourceEntry, T destEntry) { - T match = matches.get(sourceEntry); - return match != null && match.equals(destEntry); - } - - public void unmakeMatch(T sourceEntry, T destEntry, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) - { - if (sourceDeobfuscator != null && destDeobfuscator != null) - { - unmakeMatch(sourceEntry, destEntry, null, null); - sourceEntry = (T) sourceEntry.cloneToNewClass( - sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true)); - destEntry = (T) destEntry.cloneToNewClass( - destDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(destEntry, true)); - } - - boolean wasRemoved = matches.remove(sourceEntry) != null; - assert (wasRemoved); - wasRemoved = matchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); - assert (wasRemoved); - addUnmatchedSourceEntry(sourceEntry); - addUnmatchedDestEntry(destEntry); - } - - public void makeSourceUnmatchable(T sourceEntry, Deobfuscator sourceDeobfuscator) { - if (sourceDeobfuscator != null) - { - makeSourceUnmatchable(sourceEntry, null); - sourceEntry = (T) sourceEntry.cloneToNewClass( - sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true)); - } - assert (!isMatchedSourceEntry(sourceEntry)); - boolean wasRemoved = unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); - assert (wasRemoved); - addUnmatchableSourceEntry(sourceEntry); - } + private BiMap matches; + private Multimap matchedSourceEntries; + private Multimap unmatchedSourceEntries; + private Multimap unmatchedDestEntries; + private Multimap unmatchableSourceEntries; + + public MemberMatches() { + matches = HashBiMap.create(); + matchedSourceEntries = HashMultimap.create(); + unmatchedSourceEntries = HashMultimap.create(); + unmatchedDestEntries = HashMultimap.create(); + unmatchableSourceEntries = HashMultimap.create(); + } + + public void addMatch(T srcEntry, T destEntry) { + boolean wasAdded = matches.put(srcEntry, destEntry) == null; + assert (wasAdded); + wasAdded = matchedSourceEntries.put(srcEntry.getClassEntry(), srcEntry); + assert (wasAdded); + } + + public void addUnmatchedSourceEntry(T sourceEntry) { + boolean wasAdded = unmatchedSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry); + assert (wasAdded); + } + + public void addUnmatchedSourceEntries(Iterable sourceEntries) { + for (T sourceEntry : sourceEntries) { + addUnmatchedSourceEntry(sourceEntry); + } + } + + public void addUnmatchedDestEntry(T destEntry) { + if (destEntry.getName().equals("") || destEntry.getName().equals("")) + return; + boolean wasAdded = unmatchedDestEntries.put(destEntry.getClassEntry(), destEntry); + assert (wasAdded); + } + + public void addUnmatchedDestEntries(Iterable destEntriesntries) { + for (T entry : destEntriesntries) { + addUnmatchedDestEntry(entry); + } + } + + public void addUnmatchableSourceEntry(T sourceEntry) { + boolean wasAdded = unmatchableSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry); + assert (wasAdded); + } + + public Set getSourceClassesWithUnmatchedEntries() { + return unmatchedSourceEntries.keySet(); + } + + public Collection getSourceClassesWithoutUnmatchedEntries() { + Set out = Sets.newHashSet(); + out.addAll(matchedSourceEntries.keySet()); + out.removeAll(unmatchedSourceEntries.keySet()); + return out; + } + + public Collection getUnmatchedSourceEntries() { + return unmatchedSourceEntries.values(); + } + + public Collection getUnmatchedSourceEntries(ClassEntry sourceClass) { + return unmatchedSourceEntries.get(sourceClass); + } + + public Collection getUnmatchedDestEntries() { + return unmatchedDestEntries.values(); + } + + public Collection getUnmatchedDestEntries(ClassEntry destClass) { + return unmatchedDestEntries.get(destClass); + } + + public Collection getUnmatchableSourceEntries() { + return unmatchableSourceEntries.values(); + } + + public boolean hasSource(T sourceEntry) { + return matches.containsKey(sourceEntry) || unmatchedSourceEntries.containsValue(sourceEntry); + } + + public boolean hasDest(T destEntry) { + return matches.containsValue(destEntry) || unmatchedDestEntries.containsValue(destEntry); + } + + public BiMap matches() { + return matches; + } + + public boolean isMatchedSourceEntry(T sourceEntry) { + return matches.containsKey(sourceEntry); + } + + public boolean isMatchedDestEntry(T destEntry) { + return matches.containsValue(destEntry); + } + + public boolean isUnmatchableSourceEntry(T sourceEntry) { + return unmatchableSourceEntries.containsEntry(sourceEntry.getClassEntry(), sourceEntry); + } + + public void makeMatch(T sourceEntry, T destEntry) { + makeMatch(sourceEntry, destEntry, null, null); + } + + public void makeMatch(T sourceEntry, T destEntry, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { + if (sourceDeobfuscator != null && destDeobfuscator != null) { + makeMatch(sourceEntry, destEntry); + sourceEntry = (T) sourceEntry.cloneToNewClass(sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true)); + destEntry = (T) destEntry.cloneToNewClass(destDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(destEntry, true)); + } + boolean wasRemoved = unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); + assert (wasRemoved); + wasRemoved = unmatchedDestEntries.remove(destEntry.getClassEntry(), destEntry); + assert (wasRemoved); + addMatch(sourceEntry, destEntry); + } + + public boolean isMatched(T sourceEntry, T destEntry) { + T match = matches.get(sourceEntry); + return match != null && match.equals(destEntry); + } + + public void unmakeMatch(T sourceEntry, T destEntry, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { + if (sourceDeobfuscator != null && destDeobfuscator != null) { + unmakeMatch(sourceEntry, destEntry, null, null); + sourceEntry = (T) sourceEntry.cloneToNewClass( + sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true)); + destEntry = (T) destEntry.cloneToNewClass( + destDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(destEntry, true)); + } + + boolean wasRemoved = matches.remove(sourceEntry) != null; + assert (wasRemoved); + wasRemoved = matchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); + assert (wasRemoved); + addUnmatchedSourceEntry(sourceEntry); + addUnmatchedDestEntry(destEntry); + } + + public void makeSourceUnmatchable(T sourceEntry, Deobfuscator sourceDeobfuscator) { + if (sourceDeobfuscator != null) { + makeSourceUnmatchable(sourceEntry, null); + sourceEntry = (T) sourceEntry.cloneToNewClass( + sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true)); + } + assert (!isMatchedSourceEntry(sourceEntry)); + boolean wasRemoved = unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); + assert (wasRemoved); + addUnmatchableSourceEntry(sourceEntry); + } } -- cgit v1.2.3