From e33b4003a5c423894e7aef575faff359dd1d33b1 Mon Sep 17 00:00:00 2001 From: jeff Date: Wed, 11 Mar 2015 11:03:16 -0400 Subject: generalized field matching added method matching --- src/cuchaz/enigma/convert/MappingsConverter.java | 196 +++++++++++++++++------ src/cuchaz/enigma/convert/MatchesReader.java | 46 ++++-- src/cuchaz/enigma/convert/MatchesWriter.java | 50 ++++-- src/cuchaz/enigma/convert/MemberMatches.java | 145 +++++++++++++++++ 4 files changed, 361 insertions(+), 76 deletions(-) create mode 100644 src/cuchaz/enigma/convert/MemberMatches.java (limited to 'src/cuchaz/enigma/convert') diff --git a/src/cuchaz/enigma/convert/MappingsConverter.java b/src/cuchaz/enigma/convert/MappingsConverter.java index 9ab1baa..2987ea0 100644 --- a/src/cuchaz/enigma/convert/MappingsConverter.java +++ b/src/cuchaz/enigma/convert/MappingsConverter.java @@ -11,12 +11,12 @@ package cuchaz.enigma.convert; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.jar.JarFile; @@ -30,15 +30,20 @@ import com.google.common.collect.Sets; import cuchaz.enigma.Deobfuscator; import cuchaz.enigma.analysis.JarIndex; import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; +import cuchaz.enigma.mapping.BehaviorEntry; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.ClassMapping; import cuchaz.enigma.mapping.ClassNameReplacer; -import cuchaz.enigma.mapping.EntryFactory; +import cuchaz.enigma.mapping.ConstructorEntry; +import cuchaz.enigma.mapping.Entry; import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.FieldMapping; import cuchaz.enigma.mapping.Mappings; import cuchaz.enigma.mapping.MappingsChecker; +import cuchaz.enigma.mapping.MemberMapping; +import cuchaz.enigma.mapping.MethodEntry; import cuchaz.enigma.mapping.MethodMapping; +import cuchaz.enigma.mapping.Signature; import cuchaz.enigma.mapping.Type; public class MappingsConverter { @@ -124,8 +129,8 @@ public class MappingsConverter { public static Mappings newMappings(ClassMatches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { // sort the unique matches by size of inner class chain - Multimap> matchesByDestChainSize = HashMultimap.create(); - for (Entry match : matches.getUniqueMatches().entrySet()) { + 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); } @@ -135,7 +140,7 @@ public class MappingsConverter { List chainSizes = Lists.newArrayList(matchesByDestChainSize.keySet()); Collections.sort(chainSizes); for (int chainSize : chainSizes) { - for (Entry match : matchesByDestChainSize.get(chainSize)) { + for (java.util.Map.Entry match : matchesByDestChainSize.get(chainSize)) { // get class info ClassEntry obfSourceClassEntry = match.getKey(); @@ -251,89 +256,172 @@ public class MappingsConverter { mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName()); } } + + public static interface Doer { + Collection getDroppedEntries(MappingsChecker checker); + Collection getObfEntries(JarIndex jarIndex); + Collection> getMappings(ClassMapping destClassMapping); + Set filterEntries(Collection obfEntries, T obfSourceEntry, ClassMatches classMatches); + } + + public static MemberMatches computeFieldMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches) { + return computeMemberMatches(destDeobfuscator, destMappings, classMatches, 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; + } + }); + } + + public static MemberMatches computeBehaviorMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches) { + return computeMemberMatches(destDeobfuscator, destMappings, classMatches, new Doer() { + + @Override + public Collection getDroppedEntries(MappingsChecker checker) { + return checker.getDroppedMethodMappings().keySet(); + } + + @Override + public Collection getObfEntries(JarIndex jarIndex) { + return jarIndex.getObfBehaviorEntries(); + } - public static FieldMatches computeFieldMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches) { + @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) { + Signature translatedDestSignature = translate(obfDestField.getSignature(), classMatches.getUniqueMatches().inverse()); + if (translatedDestSignature == null && obfSourceField.getSignature() == null) { + out.add(obfDestField); + } else if (translatedDestSignature == null || obfSourceField.getSignature() == null) { + // skip it + } else if (translatedDestSignature.equals(obfSourceField.getSignature())) { + out.add(obfDestField); + } + } + return out; + } + }); + } + + public static MemberMatches computeMemberMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches, Doer doer) { - FieldMatches fieldMatches = new FieldMatches(); + MemberMatches memberMatches = new MemberMatches(); // unmatched source fields are easy MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); checker.dropBrokenMappings(destMappings); - for (FieldEntry destObfField : checker.getDroppedFieldMappings().keySet()) { - FieldEntry srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse()); - fieldMatches.addUnmatchedSourceField(srcObfField); + 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(fieldMatches, classMapping, classMatches); + collectMatchedFields(memberMatches, classMapping, classMatches, doer); } // get unmatched dest fields - for (FieldEntry destFieldEntry : destDeobfuscator.getJarIndex().getObfFieldEntries()) { - if (!fieldMatches.isMatchedDestField(destFieldEntry)) { - fieldMatches.addUnmatchedDestField(destFieldEntry); + for (T destEntry : doer.getObfEntries(destDeobfuscator.getJarIndex())) { + if (!memberMatches.isMatchedDestEntry(destEntry)) { + memberMatches.addUnmatchedDestEntry(destEntry); } } - System.out.println("Automatching " + fieldMatches.getUnmatchedSourceFields().size() + " unmatched source fields..."); + 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(fieldMatches.getSourceClassesWithUnmatchedFields())) { - for (FieldEntry obfSourceField : Lists.newArrayList(fieldMatches.getUnmatchedSourceFields(obfSourceClass))) { + 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 - Set obfDestFields = Sets.newHashSet(); - for (FieldEntry obfDestField : fieldMatches.getUnmatchedDestFields(obfDestClass)) { - Type translatedDestType = translate(obfDestField.getType(), classMatches.getUniqueMatches().inverse()); - if (translatedDestType.equals(obfSourceField.getType())) { - obfDestFields.add(obfDestField); - } - } + // filter by type/signature + Set obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches); - if (obfDestFields.size() == 1) { + if (obfDestEntries.size() == 1) { // make the easy match - FieldEntry obfDestField = obfDestFields.iterator().next(); - fieldMatches.makeMatch(obfSourceField, obfDestField); - } else if (obfDestFields.isEmpty()) { + memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next()); + } else if (obfDestEntries.isEmpty()) { // no match is possible =( - fieldMatches.makeSourceUnmatchable(obfSourceField); + memberMatches.makeSourceUnmatchable(obfSourceEntry); } } } - System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source fields", - fieldMatches.getUnmatchedSourceFields().size(), - fieldMatches.getUnmatchableSourceFields().size() + System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries", + memberMatches.getUnmatchedSourceEntries().size(), + memberMatches.getUnmatchableSourceEntries().size() )); - return fieldMatches; + return memberMatches; } - private static void collectMatchedFields(FieldMatches fieldMatches, ClassMapping destClassMapping, ClassMatches classMatches) { + private static void collectMatchedFields(MemberMatches memberMatches, ClassMapping destClassMapping, ClassMatches classMatches, Doer doer) { // get the fields for this class - for (FieldMapping destFieldMapping : destClassMapping.fields()) { - FieldEntry destObfField = EntryFactory.getObfFieldEntry(destClassMapping, destFieldMapping); - FieldEntry srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse()); - fieldMatches.addMatch(srcObfField, destObfField); + 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(fieldMatches, destInnerClassMapping, classMatches); + collectMatchedFields(memberMatches, destInnerClassMapping, classMatches, doer); } } - private static FieldEntry translate(FieldEntry in, BiMap map) { - return new FieldEntry( - map.get(in.getClassEntry()), - in.getName(), - translate(in.getType(), map) - ); + @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) { @@ -348,4 +436,20 @@ public class MappingsConverter { } }); } + + private static Signature translate(Signature signature, final BiMap map) { + if (signature == null) { + return null; + } + return new Signature(signature, new ClassNameReplacer() { + @Override + public String replace(String inClassName) { + ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); + if (outClassEntry == null) { + return null; + } + return outClassEntry.getName(); + } + }); + } } diff --git a/src/cuchaz/enigma/convert/MatchesReader.java b/src/cuchaz/enigma/convert/MatchesReader.java index 921ab1d..dac2f05 100644 --- a/src/cuchaz/enigma/convert/MatchesReader.java +++ b/src/cuchaz/enigma/convert/MatchesReader.java @@ -10,6 +10,8 @@ import java.util.List; import com.beust.jcommander.internal.Lists; import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.EntryFactory; import cuchaz.enigma.mapping.FieldEntry; import cuchaz.enigma.mapping.Type; @@ -45,45 +47,57 @@ public class MatchesReader { return entries; } - public static FieldMatches readFields(File file) + public static MemberMatches readMembers(File file) throws IOException { try (BufferedReader in = new BufferedReader(new FileReader(file))) { - FieldMatches matches = new FieldMatches(); + MemberMatches matches = new MemberMatches(); String line = null; while ((line = in.readLine()) != null) { - readFieldMatch(matches, line); + readMemberMatch(matches, line); } return matches; } } - private static void readFieldMatch(FieldMatches matches, String line) { + private static void readMemberMatch(MemberMatches matches, String line) { if (line.startsWith("!")) { - matches.addUnmatchableSourceField(readField(line.substring(1))); + T source = readEntry(line.substring(1)); + matches.addUnmatchableSourceEntry(source); } else { String[] parts = line.split(":", 2); - FieldEntry source = readField(parts[0]); - FieldEntry dest = readField(parts[1]); + T source = readEntry(parts[0]); + T dest = readEntry(parts[1]); if (source != null && dest != null) { matches.addMatch(source, dest); } else if (source != null) { - matches.addUnmatchedSourceField(source); + matches.addUnmatchedSourceEntry(source); } else if (dest != null) { - matches.addUnmatchedDestField(dest); + matches.addUnmatchedDestEntry(dest); } } } - private static FieldEntry readField(String in) { + @SuppressWarnings("unchecked") + private static T readEntry(String in) { if (in.length() <= 0) { return null; } String[] parts = in.split(" "); - assert(parts.length == 3); - return new FieldEntry( - new ClassEntry(parts[0]), - parts[1], - new Type(parts[2]) - ); + 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/cuchaz/enigma/convert/MatchesWriter.java b/src/cuchaz/enigma/convert/MatchesWriter.java index 2118dd0..9e9ead0 100644 --- a/src/cuchaz/enigma/convert/MatchesWriter.java +++ b/src/cuchaz/enigma/convert/MatchesWriter.java @@ -5,7 +5,9 @@ import java.io.FileWriter; import java.io.IOException; import java.util.Map; +import cuchaz.enigma.mapping.BehaviorEntry; import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Entry; import cuchaz.enigma.mapping.FieldEntry; @@ -41,43 +43,52 @@ public class MatchesWriter { } } - public static void writeFields(FieldMatches fieldMatches, File file) + public static void writeMembers(MemberMatches matches, File file) throws IOException { try (FileWriter out = new FileWriter(file)) { - for (Map.Entry match : fieldMatches.matches().entrySet()) { - writeFieldMatch(out, match.getKey(), match.getValue()); + for (Map.Entry match : matches.matches().entrySet()) { + writeMemberMatch(out, match.getKey(), match.getValue()); } - for (FieldEntry fieldEntry : fieldMatches.getUnmatchedSourceFields()) { - writeFieldMatch(out, fieldEntry, null); + for (T entry : matches.getUnmatchedSourceEntries()) { + writeMemberMatch(out, entry, null); } - for (FieldEntry fieldEntry : fieldMatches.getUnmatchedDestFields()) { - writeFieldMatch(out, null, fieldEntry); + for (T entry : matches.getUnmatchedDestEntries()) { + writeMemberMatch(out, null, entry); } - for (FieldEntry fieldEntry : fieldMatches.getUnmatchableSourceFields()) { - writeUnmatchableField(out, fieldEntry); + for (T entry : matches.getUnmatchableSourceEntries()) { + writeUnmatchableEntry(out, entry); } } } - private static void writeFieldMatch(FileWriter out, FieldEntry source, FieldEntry dest) + private static void writeMemberMatch(FileWriter out, T source, T dest) throws IOException { if (source != null) { - writeField(out, source); + writeEntry(out, source); } out.write(":"); if (dest != null) { - writeField(out, dest); + writeEntry(out, dest); } out.write("\n"); } - private static void writeUnmatchableField(FileWriter out, FieldEntry fieldEntry) + private static void writeUnmatchableEntry(FileWriter out, T entry) throws IOException { out.write("!"); - writeField(out, fieldEntry); + writeEntry(out, entry); out.write("\n"); } + private static void writeEntry(FileWriter 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(FileWriter out, FieldEntry fieldEntry) throws IOException { out.write(fieldEntry.getClassName()); @@ -86,4 +97,15 @@ public class MatchesWriter { out.write(" "); out.write(fieldEntry.getType().toString()); } + + private static void writeBehavior(FileWriter 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/cuchaz/enigma/convert/MemberMatches.java b/src/cuchaz/enigma/convert/MemberMatches.java new file mode 100644 index 0000000..1078ab7 --- /dev/null +++ b/src/cuchaz/enigma/convert/MemberMatches.java @@ -0,0 +1,145 @@ +package cuchaz.enigma.convert; + +import java.util.Collection; +import java.util.Set; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; + +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.Entry; + + +public class MemberMatches { + + private BiMap m_matches; + private Multimap m_matchedSourceEntries; + private Multimap m_unmatchedSourceEntries; + private Multimap m_unmatchedDestEntries; + private Multimap m_unmatchableSourceEntries; + + public MemberMatches() { + m_matches = HashBiMap.create(); + m_matchedSourceEntries = HashMultimap.create(); + m_unmatchedSourceEntries = HashMultimap.create(); + m_unmatchedDestEntries = HashMultimap.create(); + m_unmatchableSourceEntries = HashMultimap.create(); + } + + public void addMatch(T srcEntry, T destEntry) { + boolean wasAdded = m_matches.put(srcEntry, destEntry) == null; + assert (wasAdded); + wasAdded = m_matchedSourceEntries.put(srcEntry.getClassEntry(), srcEntry); + assert (wasAdded); + } + + public void addUnmatchedSourceEntry(T sourceEntry) { + boolean wasAdded = m_unmatchedSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry); + assert (wasAdded); + } + + public void addUnmatchedSourceEntries(Iterable sourceEntries) { + for (T sourceEntry : sourceEntries) { + addUnmatchedSourceEntry(sourceEntry); + } + } + + public void addUnmatchedDestEntry(T destEntry) { + boolean wasAdded = m_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 = m_unmatchableSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry); + assert (wasAdded); + } + + public Set getSourceClassesWithUnmatchedEntries() { + return m_unmatchedSourceEntries.keySet(); + } + + public Collection getSourceClassesWithoutUnmatchedEntries() { + Set out = Sets.newHashSet(); + out.addAll(m_matchedSourceEntries.keySet()); + out.removeAll(m_unmatchedSourceEntries.keySet()); + return out; + } + + public Collection getUnmatchedSourceEntries() { + return m_unmatchedSourceEntries.values(); + } + + public Collection getUnmatchedSourceEntries(ClassEntry sourceClass) { + return m_unmatchedSourceEntries.get(sourceClass); + } + + public Collection getUnmatchedDestEntries() { + return m_unmatchedDestEntries.values(); + } + + public Collection getUnmatchedDestEntries(ClassEntry destClass) { + return m_unmatchedDestEntries.get(destClass); + } + + public Collection getUnmatchableSourceEntries() { + return m_unmatchableSourceEntries.values(); + } + + public boolean hasSource(T sourceEntry) { + return m_matches.containsKey(sourceEntry) || m_unmatchedSourceEntries.containsValue(sourceEntry); + } + + public boolean hasDest(T destEntry) { + return m_matches.containsValue(destEntry) || m_unmatchedDestEntries.containsValue(destEntry); + } + + public BiMap matches() { + return m_matches; + } + + public boolean isMatchedSourceEntry(T sourceEntry) { + return m_matches.containsKey(sourceEntry); + } + + public boolean isMatchedDestEntry(T destEntry) { + return m_matches.containsValue(destEntry); + } + + public void makeMatch(T sourceEntry, T destEntry) { + boolean wasRemoved = m_unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); + assert (wasRemoved); + wasRemoved = m_unmatchedDestEntries.remove(destEntry.getClassEntry(), destEntry); + assert (wasRemoved); + addMatch(sourceEntry, destEntry); + } + + public boolean isMatched(T sourceEntry, T destEntry) { + T match = m_matches.get(sourceEntry); + return match != null && match.equals(destEntry); + } + + public void unmakeMatch(T sourceEntry, T destEntry) { + boolean wasRemoved = m_matches.remove(sourceEntry) != null; + assert (wasRemoved); + wasRemoved = m_matchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); + assert (wasRemoved); + addUnmatchedSourceEntry(sourceEntry); + addUnmatchedDestEntry(destEntry); + } + + public void makeSourceUnmatchable(T sourceEntry) { + assert(!isMatchedSourceEntry(sourceEntry)); + boolean wasRemoved = m_unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); + assert (wasRemoved); + addUnmatchableSourceEntry(sourceEntry); + } +} -- cgit v1.2.3