From 5540c815de36e316d0749ce2163f12c61895b327 Mon Sep 17 00:00:00 2001
From: asiekierka
Date: Wed, 17 Aug 2016 18:35:12 +0200
Subject: Revert "Removed unused methods"
This reverts commit 1742190f784d0d62e7cc869eebafdfe1927e448f.
---
src/main/java/cuchaz/enigma/ConvertMain.java | 361 +++++++++++++
src/main/java/cuchaz/enigma/Deobfuscator.java | 4 +
.../java/cuchaz/enigma/TranslatingTypeLoader.java | 22 +
src/main/java/cuchaz/enigma/Util.java | 31 ++
src/main/java/cuchaz/enigma/analysis/Access.java | 4 +-
.../java/cuchaz/enigma/analysis/BridgeMarker.java | 2 +-
.../analysis/ClassImplementationsTreeNode.java | 17 +
.../java/cuchaz/enigma/analysis/EntryRenamer.java | 53 ++
src/main/java/cuchaz/enigma/analysis/JarIndex.java | 16 +
.../java/cuchaz/enigma/analysis/SourceIndex.java | 8 +
.../cuchaz/enigma/analysis/TranslationIndex.java | 7 +
.../cuchaz/enigma/analysis/TreeDumpVisitor.java | 441 ++++++++++++++++
.../cuchaz/enigma/bytecode/CheckCastIterator.java | 117 +++++
.../java/cuchaz/enigma/bytecode/ClassRenamer.java | 10 +
.../cuchaz/enigma/bytecode/ConstPoolEditor.java | 122 +++++
.../bytecode/accessors/ClassInfoAccessor.java | 4 +
.../bytecode/accessors/ConstInfoAccessor.java | 42 ++
.../accessors/InvokeDynamicInfoAccessor.java | 4 +
.../bytecode/accessors/MemberRefInfoAccessor.java | 4 +
.../accessors/MethodHandleInfoAccessor.java | 4 +
.../bytecode/accessors/MethodTypeInfoAccessor.java | 4 +
.../accessors/NameAndTypeInfoAccessor.java | 4 +
.../bytecode/accessors/StringInfoAccessor.java | 4 +
.../bytecode/accessors/Utf8InfoAccessor.java | 28 +
.../java/cuchaz/enigma/convert/ClassForest.java | 60 +++
.../cuchaz/enigma/convert/ClassIdentifier.java | 56 ++
.../java/cuchaz/enigma/convert/ClassIdentity.java | 441 ++++++++++++++++
.../java/cuchaz/enigma/convert/ClassMatch.java | 84 +++
.../java/cuchaz/enigma/convert/ClassMatches.java | 159 ++++++
.../java/cuchaz/enigma/convert/ClassMatching.java | 155 ++++++
.../java/cuchaz/enigma/convert/ClassNamer.java | 56 ++
.../java/cuchaz/enigma/convert/FieldMatches.java | 151 ++++++
.../cuchaz/enigma/convert/MappingsConverter.java | 583 +++++++++++++++++++++
.../java/cuchaz/enigma/convert/MatchesReader.java | 109 ++++
.../java/cuchaz/enigma/convert/MatchesWriter.java | 121 +++++
.../java/cuchaz/enigma/convert/MemberMatches.java | 155 ++++++
src/main/java/cuchaz/enigma/gui/BrowserCaret.java | 2 +-
.../java/cuchaz/enigma/gui/ClassMatchingGui.java | 546 +++++++++++++++++++
src/main/java/cuchaz/enigma/gui/ClassSelector.java | 120 +++++
src/main/java/cuchaz/enigma/gui/CodeReader.java | 223 ++++++++
src/main/java/cuchaz/enigma/gui/Gui.java | 2 +-
src/main/java/cuchaz/enigma/gui/GuiTricks.java | 56 ++
.../java/cuchaz/enigma/gui/MemberMatchingGui.java | 490 +++++++++++++++++
.../java/cuchaz/enigma/gui/ScoredClassEntry.java | 30 ++
.../enigma/gui/node/ClassSelectorPackageNode.java | 4 +
.../java/cuchaz/enigma/mapping/ArgumentEntry.java | 6 +
.../cuchaz/enigma/mapping/ArgumentMapping.java | 5 +
.../java/cuchaz/enigma/mapping/ClassMapping.java | 87 +++
.../java/cuchaz/enigma/mapping/EntryFactory.java | 20 +
src/main/java/cuchaz/enigma/mapping/EntryPair.java | 22 +
.../java/cuchaz/enigma/mapping/FieldMapping.java | 39 ++
src/main/java/cuchaz/enigma/mapping/Mappings.java | 44 ++
.../enigma/mapping/MappingsEnigmaReader.java | 6 +-
.../cuchaz/enigma/mapping/MappingsRenamer.java | 40 ++
.../java/cuchaz/enigma/mapping/MemberMapping.java | 2 +
.../java/cuchaz/enigma/mapping/MethodMapping.java | 50 ++
.../java/cuchaz/enigma/mapping/NameValidator.java | 11 +
.../cuchaz/enigma/mapping/SignatureUpdater.java | 91 ++++
.../java/cuchaz/enigma/mapping/Translator.java | 8 +
src/main/java/cuchaz/enigma/utils/Utils.java | 43 --
60 files changed, 5340 insertions(+), 50 deletions(-)
create mode 100644 src/main/java/cuchaz/enigma/ConvertMain.java
create mode 100644 src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java
create mode 100644 src/main/java/cuchaz/enigma/bytecode/CheckCastIterator.java
create mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java
create mode 100644 src/main/java/cuchaz/enigma/convert/ClassForest.java
create mode 100644 src/main/java/cuchaz/enigma/convert/ClassIdentifier.java
create mode 100644 src/main/java/cuchaz/enigma/convert/ClassIdentity.java
create mode 100644 src/main/java/cuchaz/enigma/convert/ClassMatch.java
create mode 100644 src/main/java/cuchaz/enigma/convert/ClassMatches.java
create mode 100644 src/main/java/cuchaz/enigma/convert/ClassMatching.java
create mode 100644 src/main/java/cuchaz/enigma/convert/ClassNamer.java
create mode 100644 src/main/java/cuchaz/enigma/convert/FieldMatches.java
create mode 100644 src/main/java/cuchaz/enigma/convert/MappingsConverter.java
create mode 100644 src/main/java/cuchaz/enigma/convert/MatchesReader.java
create mode 100644 src/main/java/cuchaz/enigma/convert/MatchesWriter.java
create mode 100644 src/main/java/cuchaz/enigma/convert/MemberMatches.java
create mode 100644 src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
create mode 100644 src/main/java/cuchaz/enigma/gui/CodeReader.java
create mode 100644 src/main/java/cuchaz/enigma/gui/GuiTricks.java
create mode 100644 src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java
create mode 100644 src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java
create mode 100644 src/main/java/cuchaz/enigma/mapping/EntryPair.java
create mode 100644 src/main/java/cuchaz/enigma/mapping/SignatureUpdater.java
diff --git a/src/main/java/cuchaz/enigma/ConvertMain.java b/src/main/java/cuchaz/enigma/ConvertMain.java
new file mode 100644
index 00000000..4c6be7fd
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/ConvertMain.java
@@ -0,0 +1,361 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.jar.JarFile;
+
+import cuchaz.enigma.convert.*;
+import cuchaz.enigma.gui.ClassMatchingGui;
+import cuchaz.enigma.gui.MemberMatchingGui;
+import cuchaz.enigma.mapping.*;
+import cuchaz.enigma.throwables.MappingConflict;
+import cuchaz.enigma.throwables.MappingParseException;
+
+
+public class ConvertMain {
+
+ public static void main(String[] args)
+ throws IOException, MappingParseException {
+ try {
+ //Get all are args
+ String JarOld = getArg(args, 1, "Path to Old Jar", true);
+ String JarNew = getArg(args, 2, "Path to New Jar", true);
+ String OldMappings = getArg(args, 3, "Path to old .mappings file", true);
+ String NewMappings = getArg(args, 4, "Path to new .mappings file", true);
+ String ClassMatches = getArg(args, 5, "Path to Class .matches file", true);
+ String FieldMatches = getArg(args, 6, "Path to Field .matches file", true);
+ String MethodMatches = getArg(args, 7, "Path to Method .matches file", true);
+ //OldJar
+ JarFile sourceJar = new JarFile(new File(JarOld));
+ //NewJar
+ JarFile destJar = new JarFile(new File(JarNew));
+ //Get the mapping files
+ File inMappingsFile = new File(OldMappings);
+ File outMappingsFile = new File(NewMappings);
+ Mappings mappings = new MappingsEnigmaReader().read(inMappingsFile);
+ //Make the Match Files..
+ File classMatchesFile = new File(ClassMatches);
+ File fieldMatchesFile = new File(FieldMatches);
+ File methodMatchesFile = new File(MethodMatches);
+
+ String command = getArg(args, 0, "command", true);
+
+ if (command.equalsIgnoreCase("computeClassMatches")) {
+ computeClassMatches(classMatchesFile, sourceJar, destJar, mappings);
+ convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile);
+ } else if (command.equalsIgnoreCase("editClassMatches")) {
+ editClasssMatches(classMatchesFile, sourceJar, destJar, mappings);
+ convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile);
+ } else if (command.equalsIgnoreCase("computeFieldMatches")) {
+ computeFieldMatches(fieldMatchesFile, destJar, outMappingsFile, classMatchesFile);
+ convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile);
+ } else if (command.equalsIgnoreCase("editFieldMatches")) {
+ editFieldMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, fieldMatchesFile);
+ convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile);
+ } else if (command.equalsIgnoreCase("computeMethodMatches")) {
+ computeMethodMatches(methodMatchesFile, destJar, outMappingsFile, classMatchesFile);
+ convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile);
+ } else if (command.equalsIgnoreCase("editMethodMatches")) {
+ editMethodMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, methodMatchesFile);
+ convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile);
+ } else if (command.equalsIgnoreCase("convertMappings")) {
+ convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile);
+ }
+ } catch (MappingConflict ex) {
+ System.out.println(ex.getMessage());
+ ex.printStackTrace();
+ } catch (IllegalArgumentException ex) {
+ System.out.println(ex.getMessage());
+ printHelp();
+ }
+ }
+
+ private static void printHelp() {
+ System.out.println(String.format("%s - %s", Constants.NAME, Constants.VERSION));
+ System.out.println("Usage:");
+ System.out.println("\tjava -cp enigma.jar cuchaz.enigma.ConvertMain ");
+ System.out.println("\tWhere is one of:");
+ System.out.println("\t\tcomputeClassMatches");
+ System.out.println("\t\teditClassMatches");
+ System.out.println("\t\tcomputeFieldMatches");
+ System.out.println("\t\teditFieldMatches");
+ System.out.println("\t\teditMethodMatches");
+ System.out.println("\t\tconvertMappings");
+ System.out.println("\tWhere is the already mapped jar.");
+ System.out.println("\tWhere is the unmapped jar.");
+ System.out.println("\tWhere is the path to the mappings for the old jar.");
+ System.out.println("\tWhere is the new mappings. (Where you want to save them and there name)");
+ System.out.println("\tWhere is the class matches file.");
+ System.out.println("\tWhere is the field matches file.");
+ System.out.println("\tWhere is the method matches file.");
+ }
+
+ //Copy of getArg from CommandMain.... Should make a utils class.
+ private static String getArg(String[] args, int i, String name, boolean required) {
+ if (i >= args.length) {
+ if (required) {
+ throw new IllegalArgumentException(name + " is required");
+ } else {
+ return null;
+ }
+ }
+ return args[i];
+ }
+
+ private static void computeClassMatches(File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings)
+ throws IOException {
+ ClassMatches classMatches = MappingsConverter.computeClassMatches(sourceJar, destJar, mappings);
+ MatchesWriter.writeClasses(classMatches, classMatchesFile);
+ System.out.println("Wrote:\n\t" + classMatchesFile.getAbsolutePath());
+ }
+
+ private static void editClasssMatches(final File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings)
+ throws IOException {
+ System.out.println("Reading class matches...");
+ ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
+ Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
+ deobfuscators.source.setMappings(mappings);
+ System.out.println("Starting GUI...");
+ new ClassMatchingGui(classMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new ClassMatchingGui.SaveListener() {
+ @Override
+ public void save(ClassMatches matches) {
+ try {
+ MatchesWriter.writeClasses(matches, classMatchesFile);
+ } catch (IOException ex) {
+ throw new Error(ex);
+ }
+ }
+ });
+ }
+
+ @SuppressWarnings("unused")
+ private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile)
+ throws IOException, MappingConflict {
+ System.out.println("Reading class matches...");
+ ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
+ Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
+ deobfuscators.source.setMappings(mappings);
+
+ Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
+ new MappingsEnigmaWriter().write(outMappingsFile, newMappings, true);
+ System.out.println("Write converted mappings to: " + outMappingsFile.getAbsolutePath());
+ }
+
+ private static void computeFieldMatches(File memberMatchesFile, JarFile destJar, File destMappingsFile, File classMatchesFile)
+ throws IOException, MappingParseException {
+
+ System.out.println("Reading class matches...");
+ ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
+ System.out.println("Reading mappings...");
+ Mappings destMappings = new MappingsEnigmaReader().read(destMappingsFile);
+ System.out.println("Indexing dest jar...");
+ Deobfuscator destDeobfuscator = new Deobfuscator(destJar);
+
+ System.out.println("Writing matches...");
+
+ // get the matched and unmatched mappings
+ MemberMatches fieldMatches = MappingsConverter.computeMemberMatches(
+ destDeobfuscator,
+ destMappings,
+ classMatches,
+ MappingsConverter.getFieldDoer()
+ );
+
+ MatchesWriter.writeMembers(fieldMatches, memberMatchesFile);
+ System.out.println("Wrote:\n\t" + memberMatchesFile.getAbsolutePath());
+ }
+
+ private static void editFieldMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchesFile, final File fieldMatchesFile)
+ throws IOException, MappingParseException {
+
+ System.out.println("Reading matches...");
+ ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
+ MemberMatches fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
+
+ // prep deobfuscators
+ Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
+ deobfuscators.source.setMappings(sourceMappings);
+ Mappings destMappings = new MappingsEnigmaReader().read(destMappingsFile);
+ MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
+ checker.dropBrokenMappings(destMappings);
+ deobfuscators.dest.setMappings(destMappings);
+
+ new MemberMatchingGui<>(classMatches, fieldMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new MemberMatchingGui.SaveListener() {
+ @Override
+ public void save(MemberMatches matches) {
+ try {
+ MatchesWriter.writeMembers(matches, fieldMatchesFile);
+ } catch (IOException ex) {
+ throw new Error(ex);
+ }
+ }
+ });
+ }
+
+ @SuppressWarnings("unused")
+ private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile)
+ throws IOException, MappingConflict {
+
+ System.out.println("Reading matches...");
+ ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
+ MemberMatches fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
+
+ Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
+ deobfuscators.source.setMappings(mappings);
+
+ // apply matches
+ Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
+ MappingsConverter.applyMemberMatches(newMappings, classMatches, fieldMatches, MappingsConverter.getFieldDoer());
+
+ // write out the converted mappings
+
+ new MappingsEnigmaWriter().write(outMappingsFile, newMappings, true);
+ System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath());
+ }
+
+
+ private static void computeMethodMatches(File methodMatchesFile, JarFile destJar, File destMappingsFile, File classMatchesFile)
+ throws IOException, MappingParseException {
+
+ System.out.println("Reading class matches...");
+ ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
+ System.out.println("Reading mappings...");
+ Mappings destMappings = new MappingsEnigmaReader().read(destMappingsFile);
+ System.out.println("Indexing dest jar...");
+ Deobfuscator destDeobfuscator = new Deobfuscator(destJar);
+
+ System.out.println("Writing method matches...");
+
+ // get the matched and unmatched mappings
+ MemberMatches methodMatches = MappingsConverter.computeMemberMatches(
+ destDeobfuscator,
+ destMappings,
+ classMatches,
+ MappingsConverter.getMethodDoer()
+ );
+
+ MatchesWriter.writeMembers(methodMatches, methodMatchesFile);
+ System.out.println("Wrote:\n\t" + methodMatchesFile.getAbsolutePath());
+ }
+
+ private static void editMethodMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchesFile, final File methodMatchesFile)
+ throws IOException, MappingParseException {
+
+ System.out.println("Reading matches...");
+ ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
+ MemberMatches methodMatches = MatchesReader.readMembers(methodMatchesFile);
+
+ // prep deobfuscators
+ Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
+ deobfuscators.source.setMappings(sourceMappings);
+ Mappings destMappings = new MappingsEnigmaReader().read(destMappingsFile);
+ MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
+ checker.dropBrokenMappings(destMappings);
+ deobfuscators.dest.setMappings(destMappings);
+
+ new MemberMatchingGui<>(classMatches, methodMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new MemberMatchingGui.SaveListener() {
+ @Override
+ public void save(MemberMatches matches) {
+ try {
+ MatchesWriter.writeMembers(matches, methodMatchesFile);
+ } catch (IOException ex) {
+ throw new Error(ex);
+ }
+ }
+ });
+ }
+
+ private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile, File methodMatchesFile)
+ throws IOException, MappingConflict {
+
+ System.out.println("Reading matches...");
+ ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
+ MemberMatches fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
+ MemberMatches methodMatches = MatchesReader.readMembers(methodMatchesFile);
+
+ Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
+ deobfuscators.source.setMappings(mappings);
+
+ // apply matches
+ Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
+ MappingsConverter.applyMemberMatches(newMappings, classMatches, fieldMatches, MappingsConverter.getFieldDoer());
+ MappingsConverter.applyMemberMatches(newMappings, classMatches, methodMatches, MappingsConverter.getMethodDoer());
+
+ // check the final mappings
+ MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
+ checker.dropBrokenMappings(newMappings);
+
+ for (java.util.Map.Entry mapping : checker.getDroppedClassMappings().entrySet()) {
+ System.out.println("WARNING: Broken class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
+ }
+ for (java.util.Map.Entry mapping : checker.getDroppedInnerClassMappings().entrySet()) {
+ System.out.println("WARNING: Broken inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
+ }
+ for (java.util.Map.Entry mapping : checker.getDroppedFieldMappings().entrySet()) {
+ System.out.println("WARNING: Broken field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
+ }
+ for (java.util.Map.Entry mapping : checker.getDroppedMethodMappings().entrySet()) {
+ System.out.println("WARNING: Broken behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
+ }
+
+ //TODO Fix
+ // write out the converted mappings
+// try (FileWriter out = new FileWriter(outMappingsFile)) {
+// new MappingsWriter().write(out, newMappings);
+// }
+ System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath());
+ }
+
+ private static class Deobfuscators {
+
+ public Deobfuscator source;
+ public Deobfuscator dest;
+
+ public Deobfuscators(JarFile sourceJar, JarFile destJar) {
+ System.out.println("Indexing source jar...");
+ IndexerThread sourceIndexer = new IndexerThread(sourceJar);
+ sourceIndexer.start();
+ System.out.println("Indexing dest jar...");
+ IndexerThread destIndexer = new IndexerThread(destJar);
+ destIndexer.start();
+ sourceIndexer.joinOrBail();
+ destIndexer.joinOrBail();
+ source = sourceIndexer.deobfuscator;
+ dest = destIndexer.deobfuscator;
+ }
+ }
+
+ private static class IndexerThread extends Thread {
+
+ private JarFile m_jarFile;
+ public Deobfuscator deobfuscator;
+
+ public IndexerThread(JarFile jarFile) {
+ m_jarFile = jarFile;
+ deobfuscator = null;
+ }
+
+ public void joinOrBail() {
+ try {
+ join();
+ } catch (InterruptedException ex) {
+ throw new Error(ex);
+ }
+ }
+
+ @Override
+ public void run() {
+ deobfuscator = new Deobfuscator(m_jarFile);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/cuchaz/enigma/Deobfuscator.java b/src/main/java/cuchaz/enigma/Deobfuscator.java
index 4d9e6556..b2f361e9 100644
--- a/src/main/java/cuchaz/enigma/Deobfuscator.java
+++ b/src/main/java/cuchaz/enigma/Deobfuscator.java
@@ -78,6 +78,10 @@ public class Deobfuscator {
setMappings(new Mappings());
}
+ public JarFile getJar() {
+ return this.jar;
+ }
+
public String getJarName() {
return this.jar.getName();
}
diff --git a/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java b/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java
index b3e5226e..8dd075ec 100644
--- a/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java
+++ b/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java
@@ -52,6 +52,10 @@ public class TranslatingTypeLoader implements ITypeLoader {
this.defaultTypeLoader = new ClasspathTypeLoader();
}
+ public void clearCache() {
+ this.cache.clear();
+ }
+
@Override
public boolean tryLoadType(String className, Buffer out) {
@@ -76,6 +80,24 @@ public class TranslatingTypeLoader implements ITypeLoader {
return true;
}
+ public CtClass loadClass(String deobfClassName) {
+
+ byte[] data = loadType(deobfClassName);
+ if (data == null) {
+ return null;
+ }
+
+ // return a javassist handle for the class
+ String javaClassFileName = Descriptor.toJavaName(deobfClassName);
+ ClassPool classPool = new ClassPool();
+ classPool.insertClassPath(new ByteArrayClassPath(javaClassFileName, data));
+ try {
+ return classPool.get(javaClassFileName);
+ } catch (NotFoundException ex) {
+ throw new Error(ex);
+ }
+ }
+
private byte[] loadType(String className) {
// NOTE: don't know if class name is obf or deobf
diff --git a/src/main/java/cuchaz/enigma/Util.java b/src/main/java/cuchaz/enigma/Util.java
index 9445b2b4..1bcdb9ea 100644
--- a/src/main/java/cuchaz/enigma/Util.java
+++ b/src/main/java/cuchaz/enigma/Util.java
@@ -41,6 +41,27 @@ public class Util {
return result;
}
+ public static void closeQuietly(Closeable closeable) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ } catch (IOException ex) {
+ // just ignore any further exceptions
+ }
+ }
+ }
+
+ public static void closeQuietly(JarFile jarFile) {
+ // silly library should implement Closeable...
+ if (jarFile != null) {
+ try {
+ jarFile.close();
+ } catch (IOException ex) {
+ // just ignore any further exceptions
+ }
+ }
+ }
+
public static String readStreamToString(InputStream in) throws IOException {
return CharStreams.toString(new InputStreamReader(in, "UTF-8"));
}
@@ -65,4 +86,14 @@ public class Util {
}
}
}
+
+ public static void writeClass(CtClass c) {
+ String name = Descriptor.toJavaName(c.getName());
+ File file = new File(name + ".class");
+ try (FileOutputStream out = new FileOutputStream(file)) {
+ out.write(c.toBytecode());
+ } catch (IOException | CannotCompileException ex) {
+ throw new Error(ex);
+ }
+ }
}
diff --git a/src/main/java/cuchaz/enigma/analysis/Access.java b/src/main/java/cuchaz/enigma/analysis/Access.java
index ec5ac1ec..877327f1 100644
--- a/src/main/java/cuchaz/enigma/analysis/Access.java
+++ b/src/main/java/cuchaz/enigma/analysis/Access.java
@@ -30,7 +30,9 @@ public enum Access {
}
public static Access get(int modifiers) {
- if (Modifier.isProtected(modifiers)) {
+ if (Modifier.isPublic(modifiers)) {
+ return Public;
+ } else if (Modifier.isProtected(modifiers)) {
return Protected;
} else if (Modifier.isPrivate(modifiers)) {
return Private;
diff --git a/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java b/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java
index d6db11c0..cd185846 100644
--- a/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java
+++ b/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java
@@ -18,7 +18,7 @@ import javassist.bytecode.AccessFlag;
public class BridgeMarker {
- private final JarIndex m_jarIndex;
+ private JarIndex m_jarIndex;
public BridgeMarker(JarIndex jarIndex) {
this.m_jarIndex = jarIndex;
diff --git a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
index f5227bb9..2a231cb5 100644
--- a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
+++ b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
@@ -17,6 +17,7 @@ import java.util.List;
import javax.swing.tree.DefaultMutableTreeNode;
import cuchaz.enigma.mapping.ClassEntry;
+import cuchaz.enigma.mapping.MethodEntry;
import cuchaz.enigma.mapping.Translator;
public class ClassImplementationsTreeNode extends DefaultMutableTreeNode {
@@ -56,4 +57,20 @@ public class ClassImplementationsTreeNode extends DefaultMutableTreeNode {
// add them to this node
nodes.forEach(this::add);
}
+
+ public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) {
+ // is this the node?
+ if (node.entry.equals(entry)) {
+ return node;
+ }
+
+ // recurse
+ for (int i = 0; i < node.getChildCount(); i++) {
+ ClassImplementationsTreeNode foundNode = findNode((ClassImplementationsTreeNode) node.getChildAt(i), entry);
+ if (foundNode != null) {
+ return foundNode;
+ }
+ }
+ return null;
+ }
}
diff --git a/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java b/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java
index f0e73062..7233fcf9 100644
--- a/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java
+++ b/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java
@@ -56,6 +56,59 @@ public class EntryRenamer {
}
}
+ public static void renameMethodsInMultimap(Map renames, Multimap map) {
+ // for each key/value pair...
+ Set> entriesToAdd = Sets.newHashSet();
+ for (Map.Entry entry : map.entries()) {
+ entriesToAdd.add(new AbstractMap.SimpleEntry<>(renameMethodsInThing(renames, entry.getKey()), renameMethodsInThing(renames, entry.getValue())));
+ }
+ map.clear();
+ for (Map.Entry entry : entriesToAdd) {
+ map.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ public static void renameMethodsInMap(Map renames, Map map) {
+ // for each key/value pair...
+ Set> entriesToAdd = Sets.newHashSet();
+ for (Map.Entry entry : map.entrySet()) {
+ entriesToAdd.add(new AbstractMap.SimpleEntry<>(renameMethodsInThing(renames, entry.getKey()), renameMethodsInThing(renames, entry.getValue())));
+ }
+ map.clear();
+ for (Map.Entry entry : entriesToAdd) {
+ map.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static T renameMethodsInThing(Map renames, T thing) {
+ if (thing instanceof MethodEntry) {
+ MethodEntry methodEntry = (MethodEntry)thing;
+ MethodEntry newMethodEntry = renames.get(methodEntry);
+ if (newMethodEntry != null) {
+ return (T)new MethodEntry(
+ methodEntry.getClassEntry(),
+ newMethodEntry.getName(),
+ methodEntry.getSignature()
+ );
+ }
+ return thing;
+ } else if (thing instanceof ArgumentEntry) {
+ ArgumentEntry argumentEntry = (ArgumentEntry)thing;
+ return (T)new ArgumentEntry(
+ renameMethodsInThing(renames, argumentEntry.getBehaviorEntry()),
+ argumentEntry.getIndex(),
+ argumentEntry.getName()
+ );
+ } else if (thing instanceof EntryReference) {
+ EntryReference reference = (EntryReference)thing;
+ reference.entry = renameMethodsInThing(renames, reference.entry);
+ reference.context = renameMethodsInThing(renames, reference.context);
+ return thing;
+ }
+ return thing;
+ }
+
@SuppressWarnings("unchecked")
public static T renameClassesInThing(final Map renames, T thing) {
if (thing instanceof String) {
diff --git a/src/main/java/cuchaz/enigma/analysis/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/JarIndex.java
index 51a2543a..bb36c9ea 100644
--- a/src/main/java/cuchaz/enigma/analysis/JarIndex.java
+++ b/src/main/java/cuchaz/enigma/analysis/JarIndex.java
@@ -503,6 +503,22 @@ public class JarIndex {
return this.obfClassEntries;
}
+ public Collection getObfFieldEntries() {
+ return this.fields.values();
+ }
+
+ public Collection getObfFieldEntries(ClassEntry classEntry) {
+ return this.fields.get(classEntry);
+ }
+
+ public Collection getObfBehaviorEntries() {
+ return this.behaviors.values();
+ }
+
+ public Collection getObfBehaviorEntries(ClassEntry classEntry) {
+ return this.behaviors.get(classEntry);
+ }
+
public TranslationIndex getTranslationIndex() {
return this.translationIndex;
}
diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndex.java b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java
index 73e04319..719930e9 100644
--- a/src/main/java/cuchaz/enigma/analysis/SourceIndex.java
+++ b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java
@@ -145,6 +145,14 @@ public class SourceIndex {
return this.tokenToReference.keySet();
}
+ public Iterable declarationTokens() {
+ return this.declarationToToken.values();
+ }
+
+ public Iterable declarations() {
+ return this.declarationToToken.keySet();
+ }
+
public Token getDeclarationToken(Entry deobfEntry) {
return this.declarationToToken.get(deobfEntry);
}
diff --git a/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java b/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java
index 921fff44..17bf51ba 100644
--- a/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java
+++ b/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java
@@ -148,6 +148,13 @@ public class TranslationIndex {
return subclasses;
}
+ public void getSubclassesRecursively(Set out, ClassEntry classEntry) {
+ for (ClassEntry subclassEntry : getSubclass(classEntry)) {
+ out.add(subclassEntry);
+ getSubclassesRecursively(out, subclassEntry);
+ }
+ }
+
public void getSubclassNamesRecursively(Set out, ClassEntry classEntry) {
for (ClassEntry subclassEntry : getSubclass(classEntry)) {
out.add(subclassEntry.getName());
diff --git a/src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java b/src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java
new file mode 100644
index 00000000..ef8a190c
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java
@@ -0,0 +1,441 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.analysis;
+
+import com.strobel.componentmodel.Key;
+import com.strobel.decompiler.languages.java.ast.*;
+import com.strobel.decompiler.patterns.Pattern;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+public class TreeDumpVisitor implements IAstVisitor {
+
+ private File m_file;
+ private Writer m_out;
+
+ public TreeDumpVisitor(File file) {
+ m_file = file;
+ m_out = null;
+ }
+
+ @Override
+ public Void visitCompilationUnit(CompilationUnit node, Void ignored) {
+ try {
+ m_out = new FileWriter(m_file);
+ recurse(node, ignored);
+ m_out.close();
+ return null;
+ } catch (IOException ex) {
+ throw new Error(ex);
+ }
+ }
+
+ private Void recurse(AstNode node, Void ignored) {
+ // show the tree
+ try {
+ m_out.write(getIndent(node) + node.getClass().getSimpleName() + " " + getText(node) + " " + dumpUserData(node) + " " + node.getRegion() + "\n");
+ } catch (IOException ex) {
+ throw new Error(ex);
+ }
+
+ // recurse
+ for (final AstNode child : node.getChildren()) {
+ child.acceptVisitor(this, ignored);
+ }
+ return null;
+ }
+
+ private String getText(AstNode node) {
+ if (node instanceof Identifier) {
+ return "\"" + ((Identifier) node).getName() + "\"";
+ }
+ return "";
+ }
+
+ private String dumpUserData(AstNode node) {
+ StringBuilder buf = new StringBuilder();
+ for (Key> key : Keys.ALL_KEYS) {
+ Object val = node.getUserData(key);
+ if (val != null) {
+ buf.append(String.format(" [%s=%s]", key, val));
+ }
+ }
+ return buf.toString();
+ }
+
+ private String getIndent(AstNode node) {
+ StringBuilder buf = new StringBuilder();
+ int depth = getDepth(node);
+ for (int i = 0; i < depth; i++) {
+ buf.append("\t");
+ }
+ return buf.toString();
+ }
+
+ private int getDepth(AstNode node) {
+ int depth = -1;
+ while (node != null) {
+ depth++;
+ node = node.getParent();
+ }
+ return depth;
+ }
+
+ // OVERRIDES WE DON'T CARE ABOUT
+
+ @Override
+ public Void visitInvocationExpression(InvocationExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitMemberReferenceExpression(MemberReferenceExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitSimpleType(SimpleType node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitMethodDeclaration(MethodDeclaration node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitConstructorDeclaration(ConstructorDeclaration node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitParameterDeclaration(ParameterDeclaration node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitFieldDeclaration(FieldDeclaration node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitTypeDeclaration(TypeDeclaration node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitComment(Comment node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitPatternPlaceholder(AstNode node, Pattern pattern, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitTypeReference(TypeReferenceExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitJavaTokenNode(JavaTokenNode node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitIdentifier(Identifier node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitNullReferenceExpression(NullReferenceExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitThisReferenceExpression(ThisReferenceExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitSuperReferenceExpression(SuperReferenceExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitClassOfExpression(ClassOfExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitBlockStatement(BlockStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitExpressionStatement(ExpressionStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitBreakStatement(BreakStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitContinueStatement(ContinueStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitDoWhileStatement(DoWhileStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitEmptyStatement(EmptyStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitIfElseStatement(IfElseStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitLabelStatement(LabelStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitLabeledStatement(LabeledStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitReturnStatement(ReturnStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitSwitchStatement(SwitchStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitSwitchSection(SwitchSection node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitCaseLabel(CaseLabel node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitThrowStatement(ThrowStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitCatchClause(CatchClause node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitAnnotation(Annotation node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitNewLine(NewLineNode node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitVariableDeclaration(VariableDeclarationStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitVariableInitializer(VariableInitializer node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitText(TextNode node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitImportDeclaration(ImportDeclaration node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitInitializerBlock(InstanceInitializer node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitPackageDeclaration(PackageDeclaration node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitArraySpecifier(ArraySpecifier node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitComposedType(ComposedType node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitWhileStatement(WhileStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitPrimitiveExpression(PrimitiveExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitCastExpression(CastExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitInstanceOfExpression(InstanceOfExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitIndexerExpression(IndexerExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitIdentifierExpression(IdentifierExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitConditionalExpression(ConditionalExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitArrayInitializerExpression(ArrayInitializerExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitObjectCreationExpression(ObjectCreationExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitArrayCreationExpression(ArrayCreationExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitAssignmentExpression(AssignmentExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitForStatement(ForStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitForEachStatement(ForEachStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitTryCatchStatement(TryCatchStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitGotoStatement(GotoStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitParenthesizedExpression(ParenthesizedExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitSynchronizedStatement(SynchronizedStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitWildcardType(WildcardType node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitMethodGroupExpression(MethodGroupExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitAssertStatement(AssertStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitLambdaExpression(LambdaExpression node, Void ignored) {
+ return recurse(node, ignored);
+ }
+
+ @Override
+ public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, Void ignored) {
+ return recurse(node, ignored);
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/bytecode/CheckCastIterator.java b/src/main/java/cuchaz/enigma/bytecode/CheckCastIterator.java
new file mode 100644
index 00000000..19c39d3c
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/CheckCastIterator.java
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.bytecode;
+
+import java.util.Iterator;
+
+import cuchaz.enigma.bytecode.CheckCastIterator.CheckCast;
+import cuchaz.enigma.mapping.ClassEntry;
+import cuchaz.enigma.mapping.MethodEntry;
+import cuchaz.enigma.mapping.Signature;
+import javassist.bytecode.*;
+
+public class CheckCastIterator implements Iterator {
+
+ public static class CheckCast {
+
+ public String className;
+ public MethodEntry prevMethodEntry;
+
+ public CheckCast(String className, MethodEntry prevMethodEntry) {
+ this.className = className;
+ this.prevMethodEntry = prevMethodEntry;
+ }
+ }
+
+ private ConstPool constants;
+ private CodeAttribute attribute;
+ private CodeIterator iter;
+ private CheckCast next;
+
+ public CheckCastIterator(CodeAttribute codeAttribute) throws BadBytecode {
+ this.constants = codeAttribute.getConstPool();
+ this.attribute = codeAttribute;
+ this.iter = this.attribute.iterator();
+
+ this.next = getNext();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return this.next != null;
+ }
+
+ @Override
+ public CheckCast next() {
+ CheckCast out = this.next;
+ try {
+ this.next = getNext();
+ } catch (BadBytecode ex) {
+ throw new Error(ex);
+ }
+ return out;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ private CheckCast getNext() throws BadBytecode {
+ int prevPos = 0;
+ while (this.iter.hasNext()) {
+ int pos = this.iter.next();
+ int opcode = this.iter.byteAt(pos);
+ switch (opcode) {
+ case Opcode.CHECKCAST:
+
+ // get the type of this op code (next two bytes are a classinfo index)
+ MethodEntry prevMethodEntry = getMethodEntry(prevPos);
+ if (prevMethodEntry != null) {
+ return new CheckCast(this.constants.getClassInfo(this.iter.s16bitAt(pos + 1)), prevMethodEntry);
+ }
+ break;
+ }
+ prevPos = pos;
+ }
+ return null;
+ }
+
+ private MethodEntry getMethodEntry(int pos) {
+ switch (this.iter.byteAt(pos)) {
+ case Opcode.INVOKEVIRTUAL:
+ case Opcode.INVOKESTATIC:
+ case Opcode.INVOKEDYNAMIC:
+ case Opcode.INVOKESPECIAL: {
+ int index = this.iter.s16bitAt(pos + 1);
+ return new MethodEntry(
+ new ClassEntry(Descriptor.toJvmName(this.constants.getMethodrefClassName(index))),
+ this.constants.getMethodrefName(index),
+ new Signature(this.constants.getMethodrefType(index))
+ );
+ }
+
+ case Opcode.INVOKEINTERFACE: {
+ int index = this.iter.s16bitAt(pos + 1);
+ return new MethodEntry(
+ new ClassEntry(Descriptor.toJvmName(this.constants.getInterfaceMethodrefClassName(index))),
+ this.constants.getInterfaceMethodrefName(index),
+ new Signature(this.constants.getInterfaceMethodrefType(index))
+ );
+ }
+ }
+ return null;
+ }
+
+ public Iterable casts() {
+ return () -> CheckCastIterator.this;
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java b/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java
index 4a77eec1..c13aae4a 100644
--- a/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java
+++ b/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java
@@ -90,6 +90,16 @@ public class ClassRenamer {
});
}
+ public static void moveAllClassesIntoDefaultPackage(CtClass c, final String oldPackageName) {
+ renameClasses(c, className -> {
+ ClassEntry entry = new ClassEntry(className);
+ if (entry.getPackageName().equals(oldPackageName)) {
+ return entry.getSimpleName();
+ }
+ return null;
+ });
+ }
+
@SuppressWarnings("unchecked")
public static void renameClasses(CtClass c, ClassNameReplacer replacer) {
diff --git a/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java b/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java
index af8c79a1..256df61e 100644
--- a/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java
+++ b/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java
@@ -17,6 +17,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
+import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor;
import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor;
import javassist.bytecode.ConstPool;
@@ -77,6 +78,22 @@ public class ConstPoolEditor {
this.pool = pool;
}
+ public void writePool(DataOutputStream out) {
+ try {
+ methodWritePool.invoke(this.pool, out);
+ } catch (Exception ex) {
+ throw new Error(ex);
+ }
+ }
+
+ public static ConstPool readPool(DataInputStream in) {
+ try {
+ return constructorPool.newInstance(in);
+ } catch (Exception ex) {
+ throw new Error(ex);
+ }
+ }
+
public String getMemberrefClassname(int memberrefIndex) {
return Descriptor.toJvmName(this.pool.getClassInfo(this.pool.getMemberClass(memberrefIndex)));
}
@@ -101,6 +118,48 @@ public class ConstPoolEditor {
}
}
+ public int addItem(Object item) {
+ try {
+ return (Integer) addItem.invoke(this.pool, item);
+ } catch (Exception ex) {
+ throw new Error(ex);
+ }
+ }
+
+ public int addItemForceNew(Object item) {
+ try {
+ return (Integer) addItem0.invoke(this.pool, item);
+ } catch (Exception ex) {
+ throw new Error(ex);
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ public void removeLastItem() {
+ try {
+ // remove the item from the cache
+ HashMap cache = getCache();
+ if (cache != null) {
+ Object item = getItem(this.pool.getSize() - 1);
+ cache.remove(item);
+ }
+
+ // remove the actual item
+ // based off of LongVector.addElement()
+ Object item = items.get(this.pool);
+ Object[][] object = (Object[][]) objects.get(items);
+ int numElements = (Integer) elements.get(items) - 1;
+ int nth = numElements >> 7;
+ int offset = numElements & (128 - 1);
+ object[nth][offset] = null;
+
+ // decrement the number of items
+ elements.set(item, numElements);
+ numItems.set(this.pool, (Integer) numItems.get(this.pool) - 1);
+ } catch (Exception ex) {
+ throw new Error(ex);
+ }
+ }
@SuppressWarnings("rawtypes")
public HashMap getCache() {
@@ -138,4 +197,67 @@ public class ConstPoolEditor {
assert (newName.equals(getMemberrefName(memberrefIndex)));
assert (newType.equals(getMemberrefType(memberrefIndex)));
}
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public void changeClassName(int classNameIndex, String newName) {
+ // NOTE: when changing values, we always need to copy-on-write
+ try {
+ // get the class item
+ Object item = getItem(classNameIndex).getItem();
+
+ // update the cache
+ HashMap cache = getCache();
+ if (cache != null) {
+ cache.remove(item);
+ }
+
+ // add the new name and repoint the name-and-type to it
+ new ClassInfoAccessor(item).setNameIndex(this.pool.addUtf8Info(newName));
+
+ // update the cache
+ if (cache != null) {
+ cache.put(item, item);
+ }
+ } catch (Exception ex) {
+ throw new Error(ex);
+ }
+ }
+
+ public static ConstPool newConstPool() {
+ // const pool expects the name of a class to initialize itself
+ // but we want an empty pool
+ // so give it a bogus name, and then clear the entries afterwards
+ ConstPool pool = new ConstPool("a");
+
+ ConstPoolEditor editor = new ConstPoolEditor(pool);
+ int size = pool.getSize();
+ for (int i = 0; i < size - 1; i++) {
+ editor.removeLastItem();
+ }
+
+ // make sure the pool is actually empty
+ // although, in this case "empty" means one thing in it
+ // the JVM spec says index 0 should be reserved
+ assert (pool.getSize() == 1);
+ assert (editor.getItem(0) == null);
+ assert (editor.getItem(1) == null);
+ assert (editor.getItem(2) == null);
+ assert (editor.getItem(3) == null);
+
+ // also, clear the cache
+ editor.getCache().clear();
+
+ return pool;
+ }
+
+ public String dump() {
+ StringBuilder buf = new StringBuilder();
+ for (int i = 1; i < this.pool.getSize(); i++) {
+ buf.append(String.format("%4d", i));
+ buf.append(" ");
+ buf.append(getItem(i).toString());
+ buf.append("\n");
+ }
+ return buf.toString();
+ }
}
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java
index 316bb5e4..66f22839 100644
--- a/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java
@@ -39,6 +39,10 @@ public class ClassInfoAccessor {
}
}
+ public static boolean isType(ConstInfoAccessor accessor) {
+ return clazz.isAssignableFrom(accessor.getItem().getClass());
+ }
+
static {
try {
clazz = Class.forName("javassist.bytecode.ClassInfo");
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java
index 474a3ef0..bc7af870 100644
--- a/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java
@@ -10,8 +10,12 @@
******************************************************************************/
package cuchaz.enigma.bytecode.accessors;
+import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.reflect.Field;
@@ -55,6 +59,44 @@ public class ConstInfoAccessor {
}
}
+ public ConstInfoAccessor copy() {
+ return new ConstInfoAccessor(copyItem());
+ }
+
+ public Object copyItem() {
+ // I don't know of a simpler way to copy one of these silly things...
+ try {
+ // serialize the item
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ DataOutputStream out = new DataOutputStream(buf);
+ write(out);
+
+ // deserialize the item
+ DataInputStream in = new DataInputStream(new ByteArrayInputStream(buf.toByteArray()));
+ Object item = new ConstInfoAccessor(in).getItem();
+ in.close();
+
+ return item;
+ } catch (Exception ex) {
+ throw new Error(ex);
+ }
+ }
+
+ public void write(DataOutputStream out) throws IOException {
+ try {
+ out.writeUTF(this.item.getClass().getName());
+ out.writeInt(getIndex());
+
+ Method method = this.item.getClass().getMethod("write", DataOutputStream.class);
+ method.setAccessible(true);
+ method.invoke(this.item, out);
+ } catch (IOException ex) {
+ throw ex;
+ } catch (Exception ex) {
+ throw new Error(ex);
+ }
+ }
+
@Override
public String toString() {
try {
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java
index a1583945..69aee160 100644
--- a/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java
@@ -57,6 +57,10 @@ public class InvokeDynamicInfoAccessor {
}
}
+ public static boolean isType(ConstInfoAccessor accessor) {
+ return clazz.isAssignableFrom(accessor.getItem().getClass());
+ }
+
static {
try {
clazz = Class.forName("javassist.bytecode.InvokeDynamicInfo");
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java
index 2835508a..0e0297be 100644
--- a/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java
@@ -56,6 +56,10 @@ public class MemberRefInfoAccessor {
}
}
+ public static boolean isType(ConstInfoAccessor accessor) {
+ return clazz.isAssignableFrom(accessor.getItem().getClass());
+ }
+
static {
try {
clazz = Class.forName("javassist.bytecode.MemberrefInfo");
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java
index a203b43a..9a7dd698 100644
--- a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java
@@ -56,6 +56,10 @@ public class MethodHandleInfoAccessor {
}
}
+ public static boolean isType(ConstInfoAccessor accessor) {
+ return clazz.isAssignableFrom(accessor.getItem().getClass());
+ }
+
static {
try {
clazz = Class.forName("javassist.bytecode.MethodHandleInfo");
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java
index 993c79bb..5ec9c3b4 100644
--- a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java
@@ -39,6 +39,10 @@ public class MethodTypeInfoAccessor {
}
}
+ public static boolean isType(ConstInfoAccessor accessor) {
+ return clazz.isAssignableFrom(accessor.getItem().getClass());
+ }
+
static {
try {
clazz = Class.forName("javassist.bytecode.MethodTypeInfo");
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java
index d6c2531f..95df37c1 100644
--- a/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java
@@ -56,6 +56,10 @@ public class NameAndTypeInfoAccessor {
}
}
+ public static boolean isType(ConstInfoAccessor accessor) {
+ return clazz.isAssignableFrom(accessor.getItem().getClass());
+ }
+
static {
try {
clazz = Class.forName("javassist.bytecode.NameAndTypeInfo");
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java
index e211381b..1c55a443 100644
--- a/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java
@@ -39,6 +39,10 @@ public class StringInfoAccessor {
}
}
+ public static boolean isType(ConstInfoAccessor accessor) {
+ return clazz.isAssignableFrom(accessor.getItem().getClass());
+ }
+
static {
try {
clazz = Class.forName("javassist.bytecode.StringInfo");
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java
new file mode 100644
index 00000000..7a2cb667
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.bytecode.accessors;
+
+public class Utf8InfoAccessor {
+
+ private static Class> clazz;
+
+ static {
+ try {
+ clazz = Class.forName("javassist.bytecode.Utf8Info");
+ } catch (Exception ex) {
+ throw new Error(ex);
+ }
+ }
+
+ public static boolean isType(ConstInfoAccessor accessor) {
+ return clazz.isAssignableFrom(accessor.getItem().getClass());
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassForest.java b/src/main/java/cuchaz/enigma/convert/ClassForest.java
new file mode 100644
index 00000000..b08d48fb
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/ClassForest.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.convert;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+
+import java.util.Collection;
+
+import cuchaz.enigma.mapping.ClassEntry;
+
+
+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);
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java b/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java
new file mode 100644
index 00000000..f5454377
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.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;
+import cuchaz.enigma.mapping.ClassEntry;
+import cuchaz.enigma.mapping.TranslationDirection;
+import cuchaz.enigma.mapping.Translator;
+import javassist.CtClass;
+
+
+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;
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassIdentity.java b/src/main/java/cuchaz/enigma/convert/ClassIdentity.java
new file mode 100644
index 00000000..606c1df1
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/ClassIdentity.java
@@ -0,0 +1,441 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.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.Constants;
+import cuchaz.enigma.Util;
+import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
+import cuchaz.enigma.analysis.EntryReference;
+import cuchaz.enigma.analysis.JarIndex;
+import cuchaz.enigma.bytecode.ConstPoolEditor;
+import cuchaz.enigma.bytecode.InfoType;
+import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
+import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
+import cuchaz.enigma.mapping.*;
+import javassist.*;
+import javassist.bytecode.*;
+import javassist.expr.*;
+
+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 m_classNameReplacer = new ClassNameReplacer() {
+
+ private Map m_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().equals(Constants.NONE_PACKAGE)) {
+ 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 (!m_classNames.containsKey(className)) {
+ m_classNames.put(className, getNewClassName());
+ }
+ return m_classNames.get(className);
+ }
+
+ private String getNewClassName() {
+ return String.format("C%03d", m_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 extends Entry, BehaviorEntry> 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 m_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, m_classNameReplacer);
+ } else {
+ return type;
+ }
+ }
+
+ private String scrubSignature(String signature) {
+ return scrubSignature(new Signature(signature)).toString();
+ }
+
+ private Signature scrubSignature(Signature signature) {
+ return new Signature(signature, m_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);
+
+ switch (opcode) {
+ case Opcode.LDC: {
+ int constIndex = iter.byteAt(pos + 1);
+ updateHashWithConstant(digest, constants, constIndex);
+ }
+ break;
+
+ case Opcode.LDC_W:
+ case Opcode.LDC2_W: {
+ int constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2);
+ updateHashWithConstant(digest, constants, constIndex);
+ }
+ 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 Util.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
new file mode 100644
index 00000000..422529ec
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/ClassMatch.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.convert;
+
+import com.google.common.collect.Sets;
+
+import java.util.Collection;
+import java.util.Set;
+
+import cuchaz.enigma.Util;
+import cuchaz.enigma.mapping.ClassEntry;
+
+
+public class ClassMatch {
+
+ public Set sourceClasses;
+ public Set 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 boolean isMatched() {
+ return sourceClasses.size() > 0 && destClasses.size() > 0;
+ }
+
+ 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 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;
+ }
+
+ @Override
+ public int hashCode() {
+ return Util.combineHashesOrdered(sourceClasses, destClasses);
+ }
+
+ @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);
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassMatches.java b/src/main/java/cuchaz/enigma/convert/ClassMatches.java
new file mode 100644
index 00000000..3a254357
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/ClassMatches.java
@@ -0,0 +1,159 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.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;
+
+
+public class ClassMatches implements Iterable {
+
+ Collection m_matches;
+ Map m_matchesBySource;
+ Map m_matchesByDest;
+ BiMap m_uniqueMatches;
+ Map m_ambiguousMatchesBySource;
+ Map m_ambiguousMatchesByDest;
+ Set m_unmatchedSourceClasses;
+ Set m_unmatchedDestClasses;
+
+ public ClassMatches() {
+ this(new ArrayList<>());
+ }
+
+ public ClassMatches(Collection matches) {
+ m_matches = matches;
+ m_matchesBySource = Maps.newHashMap();
+ m_matchesByDest = Maps.newHashMap();
+ m_uniqueMatches = HashBiMap.create();
+ m_ambiguousMatchesBySource = Maps.newHashMap();
+ m_ambiguousMatchesByDest = Maps.newHashMap();
+ m_unmatchedSourceClasses = Sets.newHashSet();
+ m_unmatchedDestClasses = Sets.newHashSet();
+
+ for (ClassMatch match : matches) {
+ indexMatch(match);
+ }
+ }
+
+ public void add(ClassMatch match) {
+ m_matches.add(match);
+ indexMatch(match);
+ }
+
+ public void remove(ClassMatch match) {
+ for (ClassEntry sourceClass : match.sourceClasses) {
+ m_matchesBySource.remove(sourceClass);
+ m_uniqueMatches.remove(sourceClass);
+ m_ambiguousMatchesBySource.remove(sourceClass);
+ m_unmatchedSourceClasses.remove(sourceClass);
+ }
+ for (ClassEntry destClass : match.destClasses) {
+ m_matchesByDest.remove(destClass);
+ m_uniqueMatches.inverse().remove(destClass);
+ m_ambiguousMatchesByDest.remove(destClass);
+ m_unmatchedDestClasses.remove(destClass);
+ }
+ m_matches.remove(match);
+ }
+
+ public int size() {
+ return m_matches.size();
+ }
+
+ @Override
+ public Iterator iterator() {
+ return m_matches.iterator();
+ }
+
+ private void indexMatch(ClassMatch match) {
+ if (!match.isMatched()) {
+ // unmatched
+ m_unmatchedSourceClasses.addAll(match.sourceClasses);
+ m_unmatchedDestClasses.addAll(match.destClasses);
+ } else {
+ if (match.isAmbiguous()) {
+ // ambiguously matched
+ for (ClassEntry entry : match.sourceClasses) {
+ m_ambiguousMatchesBySource.put(entry, match);
+ }
+ for (ClassEntry entry : match.destClasses) {
+ m_ambiguousMatchesByDest.put(entry, match);
+ }
+ } else {
+ // uniquely matched
+ m_uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest());
+ }
+ }
+ for (ClassEntry entry : match.sourceClasses) {
+ m_matchesBySource.put(entry, match);
+ }
+ for (ClassEntry entry : match.destClasses) {
+ m_matchesByDest.put(entry, match);
+ }
+ }
+
+ public BiMap getUniqueMatches() {
+ return m_uniqueMatches;
+ }
+
+ public Set getUnmatchedSourceClasses() {
+ return m_unmatchedSourceClasses;
+ }
+
+ public Set getUnmatchedDestClasses() {
+ return m_unmatchedDestClasses;
+ }
+
+ public Set getAmbiguouslyMatchedSourceClasses() {
+ return m_ambiguousMatchesBySource.keySet();
+ }
+
+ public ClassMatch getAmbiguousMatchBySource(ClassEntry sourceClass) {
+ return m_ambiguousMatchesBySource.get(sourceClass);
+ }
+
+ public ClassMatch getMatchBySource(ClassEntry sourceClass) {
+ return m_matchesBySource.get(sourceClass);
+ }
+
+ public ClassMatch getMatchByDest(ClassEntry destClass) {
+ return m_matchesByDest.get(destClass);
+ }
+
+ public void removeSource(ClassEntry sourceClass) {
+ ClassMatch match = m_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 = m_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
new file mode 100644
index 00000000..9350ea7f
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/ClassMatching.java
@@ -0,0 +1,155 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.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 java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import cuchaz.enigma.mapping.ClassEntry;
+
+public class ClassMatching {
+
+ private ClassForest m_sourceClasses;
+ private ClassForest m_destClasses;
+ private BiMap m_knownMatches;
+
+ public ClassMatching(ClassIdentifier sourceIdentifier, ClassIdentifier destIdentifier) {
+ m_sourceClasses = new ClassForest(sourceIdentifier);
+ m_destClasses = new ClassForest(destIdentifier);
+ m_knownMatches = HashBiMap.create();
+ }
+
+ public void addKnownMatches(BiMap knownMatches) {
+ m_knownMatches.putAll(knownMatches);
+ }
+
+ public void match(Iterable sourceClasses, Iterable destClasses) {
+ for (ClassEntry sourceClass : sourceClasses) {
+ if (!m_knownMatches.containsKey(sourceClass)) {
+ m_sourceClasses.add(sourceClass);
+ }
+ }
+ for (ClassEntry destClass : destClasses) {
+ if (!m_knownMatches.containsValue(destClass)) {
+ m_destClasses.add(destClass);
+ }
+ }
+ }
+
+ public Collection matches() {
+ List matches = Lists.newArrayList();
+ for (Entry entry : m_knownMatches.entrySet()) {
+ matches.add(new ClassMatch(
+ entry.getKey(),
+ entry.getValue()
+ ));
+ }
+ for (ClassIdentity identity : m_sourceClasses.identities()) {
+ matches.add(new ClassMatch(
+ m_sourceClasses.getClasses(identity),
+ m_destClasses.getClasses(identity)
+ ));
+ }
+ for (ClassIdentity identity : m_destClasses.identities()) {
+ if (!m_sourceClasses.containsIdentity(identity)) {
+ matches.add(new ClassMatch(
+ new ArrayList<>(),
+ m_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();
+ }
+
+ StringBuilder buf = new StringBuilder();
+ buf.append(String.format("%20s%8s%8s\n", "", "Source", "Dest"));
+ buf.append(String.format("%20s%8d%8d\n", "Classes", sourceClasses().size(), destClasses().size()));
+ buf.append(String.format("%20s%8d%8d\n", "Uniquely matched", uniqueMatches().size(), uniqueMatches().size()));
+ buf.append(String.format("%20s%8d%8d\n", "Ambiguously matched", numAmbiguousSource, numAmbiguousDest));
+ buf.append(String.format("%20s%8d%8d\n", "Unmatched", unmatchedSourceClasses().size(), unmatchedDestClasses().size()));
+ return buf.toString();
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassNamer.java b/src/main/java/cuchaz/enigma/convert/ClassNamer.java
new file mode 100644
index 00000000..e471c7dd
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/ClassNamer.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.convert;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.Maps;
+
+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;
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/convert/FieldMatches.java b/src/main/java/cuchaz/enigma/convert/FieldMatches.java
new file mode 100644
index 00000000..0899cd2e
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/FieldMatches.java
@@ -0,0 +1,151 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.convert;
+
+import com.google.common.collect.*;
+
+import java.util.Collection;
+import java.util.Set;
+
+import cuchaz.enigma.mapping.ClassEntry;
+import cuchaz.enigma.mapping.FieldEntry;
+
+
+public class FieldMatches {
+
+ private BiMap m_matches;
+ private Multimap m_matchedSourceFields;
+ private Multimap m_unmatchedSourceFields;
+ private Multimap m_unmatchedDestFields;
+ private Multimap m_unmatchableSourceFields;
+
+ public FieldMatches() {
+ m_matches = HashBiMap.create();
+ m_matchedSourceFields = HashMultimap.create();
+ m_unmatchedSourceFields = HashMultimap.create();
+ m_unmatchedDestFields = HashMultimap.create();
+ m_unmatchableSourceFields = HashMultimap.create();
+ }
+
+ public void addMatch(FieldEntry srcField, FieldEntry destField) {
+ boolean wasAdded = m_matches.put(srcField, destField) == null;
+ assert (wasAdded);
+ wasAdded = m_matchedSourceFields.put(srcField.getClassEntry(), srcField);
+ assert (wasAdded);
+ }
+
+ public void addUnmatchedSourceField(FieldEntry fieldEntry) {
+ boolean wasAdded = m_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 = m_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 = m_unmatchableSourceFields.put(sourceField.getClassEntry(), sourceField);
+ assert (wasAdded);
+ }
+
+ public Set getSourceClassesWithUnmatchedFields() {
+ return m_unmatchedSourceFields.keySet();
+ }
+
+ public Collection getSourceClassesWithoutUnmatchedFields() {
+ Set out = Sets.newHashSet();
+ out.addAll(m_matchedSourceFields.keySet());
+ out.removeAll(m_unmatchedSourceFields.keySet());
+ return out;
+ }
+
+ public Collection getUnmatchedSourceFields() {
+ return m_unmatchedSourceFields.values();
+ }
+
+ public Collection getUnmatchedSourceFields(ClassEntry sourceClass) {
+ return m_unmatchedSourceFields.get(sourceClass);
+ }
+
+ public Collection getUnmatchedDestFields() {
+ return m_unmatchedDestFields.values();
+ }
+
+ public Collection getUnmatchedDestFields(ClassEntry destClass) {
+ return m_unmatchedDestFields.get(destClass);
+ }
+
+ public Collection getUnmatchableSourceFields() {
+ return m_unmatchableSourceFields.values();
+ }
+
+ public boolean hasSource(FieldEntry fieldEntry) {
+ return m_matches.containsKey(fieldEntry) || m_unmatchedSourceFields.containsValue(fieldEntry);
+ }
+
+ public boolean hasDest(FieldEntry fieldEntry) {
+ return m_matches.containsValue(fieldEntry) || m_unmatchedDestFields.containsValue(fieldEntry);
+ }
+
+ public BiMap matches() {
+ return m_matches;
+ }
+
+ public boolean isMatchedSourceField(FieldEntry sourceField) {
+ return m_matches.containsKey(sourceField);
+ }
+
+ public boolean isMatchedDestField(FieldEntry destField) {
+ return m_matches.containsValue(destField);
+ }
+
+ public void makeMatch(FieldEntry sourceField, FieldEntry destField) {
+ boolean wasRemoved = m_unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
+ assert (wasRemoved);
+ wasRemoved = m_unmatchedDestFields.remove(destField.getClassEntry(), destField);
+ assert (wasRemoved);
+ addMatch(sourceField, destField);
+ }
+
+ public boolean isMatched(FieldEntry sourceField, FieldEntry destField) {
+ FieldEntry match = m_matches.get(sourceField);
+ return match != null && match.equals(destField);
+ }
+
+ public void unmakeMatch(FieldEntry sourceField, FieldEntry destField) {
+ boolean wasRemoved = m_matches.remove(sourceField) != null;
+ assert (wasRemoved);
+ wasRemoved = m_matchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
+ assert (wasRemoved);
+ addUnmatchedSourceField(sourceField);
+ addUnmatchedDestField(destField);
+ }
+
+ public void makeSourceUnmatchable(FieldEntry sourceField) {
+ assert (!isMatchedSourceField(sourceField));
+ boolean wasRemoved = m_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
new file mode 100644
index 00000000..61b0e7ef
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/MappingsConverter.java
@@ -0,0 +1,583 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.convert;
+
+import com.google.common.collect.*;
+
+import java.util.*;
+import java.util.jar.JarFile;
+
+import cuchaz.enigma.Constants;
+import cuchaz.enigma.Deobfuscator;
+import cuchaz.enigma.analysis.JarIndex;
+import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
+import cuchaz.enigma.mapping.*;
+import cuchaz.enigma.throwables.MappingConflict;
+
+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 = 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 = new ClassNameReplacer() {
+ @Override
+ public String replace(String 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().equals(Constants.NONE_PACKAGE)) {
+ 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 extends MemberMapping> 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 extends MemberMapping> getMappings(ClassMapping destClassMapping) {
+ return (Collection extends MemberMapping>) 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 extends MemberMapping> getMappings(ClassMapping destClassMapping) {
+ return (Collection extends MemberMapping>) 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;
+ }
+
+ @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 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);
+ }
+ }
+ }
+
+ 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, new ClassNameReplacer() {
+ @Override
+ public String replace(String 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, new ClassNameReplacer() {
+ @Override
+ public String replace(String 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());
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/convert/MatchesReader.java b/src/main/java/cuchaz/enigma/convert/MatchesReader.java
new file mode 100644
index 00000000..773566df
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/MatchesReader.java
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.convert;
+
+import com.google.common.collect.Lists;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+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 FileReader(file))) {
+ ClassMatches matches = new ClassMatches();
+ String line = null;
+ while ((line = in.readLine()) != null) {
+ matches.add(readClassMatch(line));
+ }
+ return matches;
+ }
+ }
+
+ private static ClassMatch readClassMatch(String line)
+ throws IOException {
+ 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;
+ }
+
+ public static MemberMatches readMembers(File file)
+ throws IOException {
+ try (BufferedReader in = new BufferedReader(new FileReader(file))) {
+ MemberMatches matches = new MemberMatches();
+ String line = null;
+ 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);
+ }
+ }
+ }
+
+ @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
new file mode 100644
index 00000000..baf79293
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/MatchesWriter.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.convert;
+
+import java.io.File;
+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;
+
+
+public class MatchesWriter {
+
+ public static void writeClasses(ClassMatches matches, File file)
+ throws IOException {
+ try (FileWriter out = new FileWriter(file)) {
+ for (ClassMatch match : matches) {
+ writeClassMatch(out, match);
+ }
+ }
+ }
+
+ private static void writeClassMatch(FileWriter out, ClassMatch match)
+ throws IOException {
+ writeClasses(out, match.sourceClasses);
+ out.write(":");
+ writeClasses(out, match.destClasses);
+ out.write("\n");
+ }
+
+ private static void writeClasses(FileWriter 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 (FileWriter out = new FileWriter(file)) {
+ 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(FileWriter 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(FileWriter out, T entry)
+ throws IOException {
+ out.write("!");
+ 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());
+ out.write(" ");
+ out.write(fieldEntry.getName());
+ 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/main/java/cuchaz/enigma/convert/MemberMatches.java b/src/main/java/cuchaz/enigma/convert/MemberMatches.java
new file mode 100644
index 00000000..32850cca
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/MemberMatches.java
@@ -0,0 +1,155 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.convert;
+
+import com.google.common.collect.*;
+
+import java.util.Collection;
+import java.util.Set;
+
+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