summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar jeff2015-02-28 23:36:47 -0500
committerGravatar jeff2015-02-28 23:36:47 -0500
commit88671184e20b3ad3791125cf96c83ca048cb2861 (patch)
tree6c86f1ea61666ff2584b434d5f8df4d3fd83263c /src
parentswitch to better-maintained version of JSyntaxPane (diff)
downloadenigma-88671184e20b3ad3791125cf96c83ca048cb2861.tar.gz
enigma-88671184e20b3ad3791125cf96c83ca048cb2861.tar.xz
enigma-88671184e20b3ad3791125cf96c83ca048cb2861.zip
refactor converter a bit for upcoming convert gui
Diffstat (limited to 'src')
-rw-r--r--src/cuchaz/enigma/ConvertMain.java70
-rw-r--r--src/cuchaz/enigma/convert/ClassForest.java4
-rw-r--r--src/cuchaz/enigma/convert/ClassMatcher.java356
-rw-r--r--src/cuchaz/enigma/convert/ClassMatching.java8
-rw-r--r--src/cuchaz/enigma/convert/MappingsConverter.java180
-rw-r--r--src/cuchaz/enigma/convert/Matches.java89
-rw-r--r--src/cuchaz/enigma/convert/MatchesReader.java45
-rw-r--r--src/cuchaz/enigma/convert/MatchesWriter.java41
-rw-r--r--src/cuchaz/enigma/gui/MatchingGui.java140
9 files changed, 565 insertions, 368 deletions
diff --git a/src/cuchaz/enigma/ConvertMain.java b/src/cuchaz/enigma/ConvertMain.java
new file mode 100644
index 00000000..7ba47617
--- /dev/null
+++ b/src/cuchaz/enigma/ConvertMain.java
@@ -0,0 +1,70 @@
1package cuchaz.enigma;
2
3import java.io.File;
4import java.io.FileReader;
5import java.io.IOException;
6import java.util.jar.JarFile;
7
8import cuchaz.enigma.convert.MappingsConverter;
9import cuchaz.enigma.convert.Matches;
10import cuchaz.enigma.convert.MatchesReader;
11import cuchaz.enigma.convert.MatchesWriter;
12import cuchaz.enigma.gui.MatchingGui;
13import cuchaz.enigma.mapping.MappingParseException;
14import cuchaz.enigma.mapping.Mappings;
15import cuchaz.enigma.mapping.MappingsReader;
16
17
18public class ConvertMain {
19
20 public static void main(String[] args)
21 throws IOException, MappingParseException {
22
23 // init files
24 File home = new File(System.getProperty("user.home"));
25 JarFile sourceJar = new JarFile(new File(home, ".minecraft/versions/1.8/1.8.jar"));
26 JarFile destJar = new JarFile(new File(home, ".minecraft/versions/1.8.3/1.8.3.jar"));
27 File inMappingsFile = new File("../Enigma Mappings/1.8.mappings");
28 File outMappingsFile = new File("../Enigma Mappings/1.8.3.mappings");
29 Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile));
30 File matchingFile = new File(inMappingsFile.getName() + ".matching");
31
32 //computeMatches(matchingFile, sourceJar, destJar, mappings);
33 editMatches(matchingFile, sourceJar, destJar, mappings);
34 //convertMappings(outMappingsFile, mappings, matchingFile);
35
36 /* TODO
37 // write out the converted mappings
38 FileWriter writer = new FileWriter(outMappingsFile);
39 new MappingsWriter().write(writer, mappings);
40 writer.close();
41 System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath());
42 */
43 }
44
45 private static void computeMatches(File matchingFile, JarFile sourceJar, JarFile destJar, Mappings mappings)
46 throws IOException {
47 Matches matches = MappingsConverter.computeMatches(sourceJar, destJar, mappings);
48 MatchesWriter.write(matches, matchingFile);
49 System.out.println("Wrote:\n\t" + matchingFile.getAbsolutePath());
50 }
51
52 private static void editMatches(File matchingFile, JarFile sourceJar, JarFile destJar, Mappings mappings)
53 throws IOException {
54 System.out.println("Reading matches...");
55 Matches matches = MatchesReader.read(matchingFile);
56 System.out.println("Indexing source jar...");
57 Deobfuscator sourceDeobfuscator = new Deobfuscator(sourceJar);
58 sourceDeobfuscator.setMappings(mappings);
59 System.out.println("Indexing dest jar...");
60 Deobfuscator destDeobfuscator = new Deobfuscator(destJar);
61 System.out.println("Starting GUI...");
62 new MatchingGui(matches, sourceDeobfuscator, destDeobfuscator);
63 }
64
65 private static void convertMappings(File outMappingsFile, Mappings mappings, File matchingFile)
66 throws IOException {
67 Matches matches = MatchesReader.read(matchingFile);
68 MappingsConverter.convertMappings(mappings, matches.getUniqueMatches());
69 }
70}
diff --git a/src/cuchaz/enigma/convert/ClassForest.java b/src/cuchaz/enigma/convert/ClassForest.java
index e113eeb9..36731393 100644
--- a/src/cuchaz/enigma/convert/ClassForest.java
+++ b/src/cuchaz/enigma/convert/ClassForest.java
@@ -18,10 +18,6 @@ public class ClassForest {
18 m_forest = HashMultimap.create(); 18 m_forest = HashMultimap.create();
19 } 19 }
20 20
21 public ClassIdentifier getIdentifier() {
22 return m_identifier;
23 }
24
25 public void addAll(Iterable<ClassEntry> entries) { 21 public void addAll(Iterable<ClassEntry> entries) {
26 for (ClassEntry entry : entries) { 22 for (ClassEntry entry : entries) {
27 add(entry); 23 add(entry);
diff --git a/src/cuchaz/enigma/convert/ClassMatcher.java b/src/cuchaz/enigma/convert/ClassMatcher.java
deleted file mode 100644
index f43f5b20..00000000
--- a/src/cuchaz/enigma/convert/ClassMatcher.java
+++ /dev/null
@@ -1,356 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.io.File;
14import java.io.FileReader;
15import java.io.FileWriter;
16import java.io.IOException;
17import java.util.ArrayList;
18import java.util.Arrays;
19import java.util.Collection;
20import java.util.Collections;
21import java.util.Comparator;
22import java.util.Iterator;
23import java.util.LinkedHashMap;
24import java.util.List;
25import java.util.Map;
26import java.util.Set;
27import java.util.jar.JarFile;
28
29import com.google.common.collect.ArrayListMultimap;
30import com.google.common.collect.BiMap;
31import com.google.common.collect.HashBiMap;
32import com.google.common.collect.Lists;
33import com.google.common.collect.Maps;
34import com.google.common.collect.Multimap;
35import com.google.common.collect.Sets;
36
37import cuchaz.enigma.analysis.JarIndex;
38import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
39import cuchaz.enigma.mapping.ClassEntry;
40import cuchaz.enigma.mapping.ClassMapping;
41import cuchaz.enigma.mapping.MappingParseException;
42import cuchaz.enigma.mapping.Mappings;
43import cuchaz.enigma.mapping.MappingsReader;
44import cuchaz.enigma.mapping.MappingsWriter;
45import cuchaz.enigma.mapping.MethodEntry;
46import cuchaz.enigma.mapping.MethodMapping;
47
48public class ClassMatcher {
49
50 public static void main(String[] args)
51 throws IOException, MappingParseException {
52
53 // setup files
54 File home = new File(System.getProperty("user.home"));
55 JarFile sourceJar = new JarFile(new File(home, ".minecraft/versions/1.8/1.8.jar"));
56 JarFile destJar = new JarFile(new File(home, ".minecraft/versions/1.8.3/1.8.3.jar"));
57 File inMappingsFile = new File("../Enigma Mappings/1.8.mappings");
58 File outMappingsFile = new File("../Enigma Mappings/1.8.3.mappings");
59
60 // define a matching to use when the automated system cannot find a match
61 Map<String,String> fallbackMatching = Maps.newHashMap();
62 /*
63 fallbackMatching.put("none/ayb", "none/ayf");
64 fallbackMatching.put("none/ayd", "none/ayd");
65 fallbackMatching.put("none/bgk", "unknown/bgk");
66 */
67
68 // do the conversion
69 Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile));
70 convertMappings(sourceJar, destJar, mappings, fallbackMatching);
71
72 // write out the converted mappings
73 FileWriter writer = new FileWriter(outMappingsFile);
74 new MappingsWriter().write(writer, mappings);
75 writer.close();
76 System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath());
77 }
78
79 private static void convertMappings(JarFile sourceJar, JarFile destJar, Mappings mappings, Map<String,String> fallbackMatching) {
80
81 // index jars
82 System.out.println("Indexing source jar...");
83 JarIndex sourceIndex = new JarIndex();
84 sourceIndex.indexJar(sourceJar, false);
85 System.out.println("Indexing dest jar...");
86 JarIndex destIndex = new JarIndex();
87 destIndex.indexJar(destJar, false);
88
89 // compute the matching
90 ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex);
91
92 // get all the obf class names used in the mappings
93 Set<ClassEntry> usedClasses = Sets.newHashSet();
94 for (String className : mappings.getAllObfClassNames()) {
95 usedClasses.add(new ClassEntry(className));
96 }
97 System.out.println("Mappings reference " + usedClasses.size() + " classes");
98
99 // see what the used classes map to
100 BiMap<ClassEntry,ClassEntry> uniqueUsedMatches = HashBiMap.create();
101 Map<ClassEntry,ClassMatch> ambiguousUsedMatches = Maps.newHashMap();
102 Set<ClassEntry> unmatchedUsedClasses = Sets.newHashSet();
103 for (ClassMatch match : matching.matches()) {
104 Set<ClassEntry> matchUsedClasses = match.intersectSourceClasses(usedClasses);
105 if (matchUsedClasses.isEmpty()) {
106 continue;
107 }
108
109 // classify the match
110 if (!match.isMatched()) {
111 // unmatched
112 unmatchedUsedClasses.addAll(matchUsedClasses);
113 } else {
114 if (match.isAmbiguous()) {
115 // ambiguously matched
116 for (ClassEntry matchUsedClass : matchUsedClasses) {
117 ambiguousUsedMatches.put(matchUsedClass, match);
118 }
119 } else {
120 // uniquely matched
121 uniqueUsedMatches.put(match.getUniqueSource(), match.getUniqueDest());
122 }
123 }
124 }
125
126 // get unmatched dest classes
127 Set<ClassEntry> unmatchedDestClasses = Sets.newHashSet();
128 for (ClassMatch match : matching.matches()) {
129 if (!match.isMatched()) {
130 unmatchedDestClasses.addAll(match.destClasses);
131 }
132 }
133
134 // warn about the ambiguous used matches
135 if (ambiguousUsedMatches.size() > 0) {
136 System.out.println(String.format("%d source classes have ambiguous mappings", ambiguousUsedMatches.size()));
137 List<ClassMatch> ambiguousMatchesList = Lists.newArrayList(Sets.newHashSet(ambiguousUsedMatches.values()));
138 Collections.sort(ambiguousMatchesList, new Comparator<ClassMatch>() {
139 @Override
140 public int compare(ClassMatch a, ClassMatch b) {
141 String aName = a.sourceClasses.iterator().next().getName();
142 String bName = b.sourceClasses.iterator().next().getName();
143 return aName.compareTo(bName);
144 }
145 });
146 for (ClassMatch match : ambiguousMatchesList) {
147 System.out.println("Ambiguous matching:");
148 System.out.println("\tSource: " + getClassNames(match.sourceClasses));
149 System.out.println("\tDest: " + getClassNames(match.destClasses));
150 }
151 }
152
153 // warn about unmatched used classes
154 for (ClassEntry unmatchedUsedClass : unmatchedUsedClasses) {
155 System.out.println("No exact match for source class " + unmatchedUsedClass.getClassEntry());
156
157 // rank all the unmatched dest classes against the used class
158 ClassIdentity sourceIdentity = matching.getSourceIdentifier().identify(unmatchedUsedClass);
159 Multimap<Integer,ClassEntry> scoredDestClasses = ArrayListMultimap.create();
160 for (ClassEntry unmatchedDestClass : unmatchedDestClasses) {
161 ClassIdentity destIdentity = matching.getDestIdentifier().identify(unmatchedDestClass);
162 scoredDestClasses.put(sourceIdentity.getMatchScore(destIdentity), unmatchedDestClass);
163 }
164
165 List<Integer> scores = new ArrayList<Integer>(scoredDestClasses.keySet());
166 Collections.sort(scores, Collections.reverseOrder());
167 printScoredMatches(sourceIdentity.getMaxMatchScore(), scores, scoredDestClasses);
168
169 /* TODO: re-enable auto-pick logic
170 // does the best match have a non-zero score and the same name?
171 int bestScore = scores.get(0);
172 Collection<ClassIdentity> bestMatches = scoredMatches.get(bestScore);
173 if (bestScore > 0 && bestMatches.size() == 1) {
174 ClassIdentity bestMatch = bestMatches.iterator().next();
175 if (bestMatch.getClassEntry().equals(sourceClass.getClassEntry())) {
176 // use it
177 System.out.println("\tAutomatically choosing likely match: " + bestMatch.getClassEntry().getName());
178 destClasses.clear();
179 destClasses.add(bestMatch);
180 }
181 }
182 */
183 }
184
185 // bail if there were unmatched classes
186 if (!unmatchedUsedClasses.isEmpty()) {
187 throw new Error("There were " + unmatchedUsedClasses.size() + " unmatched classes!");
188 }
189
190 // sort the changes so classes are renamed in the correct order
191 // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b
192 BiMap<ClassEntry,ClassEntry> unsortedChanges = HashBiMap.create(uniqueUsedMatches);
193 LinkedHashMap<ClassEntry,ClassEntry> sortedChanges = Maps.newLinkedHashMap();
194 int numChangesLeft = unsortedChanges.size();
195 while (!unsortedChanges.isEmpty()) {
196 Iterator<Map.Entry<ClassEntry,ClassEntry>> iter = unsortedChanges.entrySet().iterator();
197 while (iter.hasNext()) {
198 Map.Entry<ClassEntry,ClassEntry> change = iter.next();
199 if (unsortedChanges.containsKey(change.getValue())) {
200 sortedChanges.put(change.getKey(), change.getValue());
201 iter.remove();
202 }
203 }
204
205 // did we remove any changes?
206 if (numChangesLeft - unsortedChanges.size() > 0) {
207 // keep going
208 numChangesLeft = unsortedChanges.size();
209 } else {
210 // can't sort anymore. There must be a loop
211 break;
212 }
213 }
214 if (!unsortedChanges.isEmpty()) {
215 throw new Error(String.format("Unable to sort %d/%d class changes!", unsortedChanges.size(), uniqueUsedMatches.size()));
216 }
217
218 // convert the mappings in the correct class order
219 for (Map.Entry<ClassEntry,ClassEntry> entry : sortedChanges.entrySet()) {
220 mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName());
221 }
222
223 // check the method matches
224 System.out.println("Checking methods...");
225 for (ClassMapping classMapping : mappings.classes()) {
226 ClassEntry classEntry = new ClassEntry(classMapping.getObfFullName());
227 for (MethodMapping methodMapping : classMapping.methods()) {
228
229 // skip constructors
230 if (methodMapping.getObfName().equals("<init>")) {
231 continue;
232 }
233
234 MethodEntry methodEntry = new MethodEntry(
235 classEntry,
236 methodMapping.getObfName(),
237 methodMapping.getObfSignature()
238 );
239 if (!destIndex.containsObfBehavior(methodEntry)) {
240 System.err.println("WARNING: method doesn't match: " + methodEntry);
241
242 /* TODO: show methods if needed
243 // show the available methods
244 System.err.println("\tAvailable dest methods:");
245 CtClass c = destLoader.loadClass(classMapping.getObfFullName());
246 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
247 System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior));
248 }
249
250 System.err.println("\tAvailable source methods:");
251 c = sourceLoader.loadClass(matchedClassNames.inverse().get(classMapping.getObfFullName()));
252 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
253 System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior));
254 }
255 */
256 }
257 }
258 }
259
260 System.out.println("Done!");
261 }
262
263 public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex) {
264
265 System.out.println("Iteratively matching classes...");
266
267 ClassMatching lastMatching = null;
268 int round = 0;
269 SidedClassNamer sourceNamer = null;
270 SidedClassNamer destNamer = null;
271 for (boolean useReferences : Arrays.asList(false, true)) {
272
273 int numUniqueMatchesLastTime = 0;
274 if (lastMatching != null) {
275 numUniqueMatchesLastTime = lastMatching.uniqueMatches().size();
276 }
277
278 while (true) {
279
280 System.out.println("Round " + (++round) + " ...");
281
282 // init the matching with identity settings
283 ClassMatching matching = new ClassMatching(
284 new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences),
285 new ClassIdentifier(destJar, destIndex, destNamer, useReferences)
286 );
287
288 if (lastMatching == null) {
289 // search all classes
290 matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries());
291 } else {
292 // we already know about these matches
293 matching.addKnownMatches(lastMatching.uniqueMatches());
294
295 // search unmatched and ambiguously-matched classes
296 matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses());
297 for (ClassMatch match : lastMatching.ambiguousMatches()) {
298 matching.match(match.sourceClasses, match.destClasses);
299 }
300 }
301 System.out.println(matching);
302 BiMap<ClassEntry,ClassEntry> uniqueMatches = matching.uniqueMatches();
303
304 // did we match anything new this time?
305 if (uniqueMatches.size() > numUniqueMatchesLastTime) {
306 numUniqueMatchesLastTime = uniqueMatches.size();
307 lastMatching = matching;
308 } else {
309 break;
310 }
311
312 // update the namers
313 ClassNamer namer = new ClassNamer(uniqueMatches);
314 sourceNamer = namer.getSourceNamer();
315 destNamer = namer.getDestNamer();
316 }
317 }
318
319 return lastMatching;
320 }
321
322 private static void printScoredMatches(int maxScore, List<Integer> scores, Multimap<Integer,ClassEntry> scoredMatches) {
323 int numScoredMatchesShown = 0;
324 for (int score : scores) {
325 for (ClassEntry classEntry : scoredMatches.get(score)) {
326 System.out.println(String.format("\tScore: %3d %3.0f%% %s",
327 score, 100.0 * score / maxScore, classEntry.getName()
328 ));
329 if (numScoredMatchesShown++ > 10) {
330 return;
331 }
332 }
333 }
334 }
335
336 private static List<String> getClassNames(Collection<ClassEntry> classes) {
337 List<String> out = Lists.newArrayList();
338 for (ClassEntry c : classes) {
339 out.add(c.getName());
340 }
341 Collections.sort(out);
342 return out;
343 }
344
345 /* DEBUG
346 private static String decompile(TranslatingTypeLoader loader, ClassEntry classEntry) {
347 PlainTextOutput output = new PlainTextOutput();
348 DecompilerSettings settings = DecompilerSettings.javaDefaults();
349 settings.setForceExplicitImports(true);
350 settings.setShowSyntheticMembers(true);
351 settings.setTypeLoader(loader);
352 Decompiler.decompile(classEntry.getName(), output, settings);
353 return output.toString();
354 }
355 */
356}
diff --git a/src/cuchaz/enigma/convert/ClassMatching.java b/src/cuchaz/enigma/convert/ClassMatching.java
index b94fd8bd..9f931300 100644
--- a/src/cuchaz/enigma/convert/ClassMatching.java
+++ b/src/cuchaz/enigma/convert/ClassMatching.java
@@ -125,14 +125,6 @@ public class ClassMatching {
125 return classes; 125 return classes;
126 } 126 }
127 127
128 public ClassIdentifier getSourceIdentifier() {
129 return m_sourceClasses.getIdentifier();
130 }
131
132 public ClassIdentifier getDestIdentifier() {
133 return m_destClasses.getIdentifier();
134 }
135
136 @Override 128 @Override
137 public String toString() { 129 public String toString() {
138 130
diff --git a/src/cuchaz/enigma/convert/MappingsConverter.java b/src/cuchaz/enigma/convert/MappingsConverter.java
new file mode 100644
index 00000000..d0f9382a
--- /dev/null
+++ b/src/cuchaz/enigma/convert/MappingsConverter.java
@@ -0,0 +1,180 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Arrays;
14import java.util.Iterator;
15import java.util.LinkedHashMap;
16import java.util.Map;
17import java.util.jar.JarFile;
18
19import com.google.common.collect.BiMap;
20import com.google.common.collect.Maps;
21
22import cuchaz.enigma.analysis.JarIndex;
23import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
24import cuchaz.enigma.mapping.ClassEntry;
25import cuchaz.enigma.mapping.Mappings;
26
27public class MappingsConverter {
28
29 public static Matches computeMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) {
30
31 // index jars
32 System.out.println("Indexing source jar...");
33 JarIndex sourceIndex = new JarIndex();
34 sourceIndex.indexJar(sourceJar, false);
35 System.out.println("Indexing dest jar...");
36 JarIndex destIndex = new JarIndex();
37 destIndex.indexJar(destJar, false);
38
39 // compute the matching
40 ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex);
41 return new Matches(matching.matches());
42 }
43
44 public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex) {
45
46 System.out.println("Iteratively matching classes");
47
48 ClassMatching lastMatching = null;
49 int round = 0;
50 SidedClassNamer sourceNamer = null;
51 SidedClassNamer destNamer = null;
52 for (boolean useReferences : Arrays.asList(false, true)) {
53
54 int numUniqueMatchesLastTime = 0;
55 if (lastMatching != null) {
56 numUniqueMatchesLastTime = lastMatching.uniqueMatches().size();
57 }
58
59 while (true) {
60
61 System.out.println("Round " + (++round) + "...");
62
63 // init the matching with identity settings
64 ClassMatching matching = new ClassMatching(
65 new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences),
66 new ClassIdentifier(destJar, destIndex, destNamer, useReferences)
67 );
68
69 if (lastMatching == null) {
70 // search all classes
71 matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries());
72 } else {
73 // we already know about these matches
74 matching.addKnownMatches(lastMatching.uniqueMatches());
75
76 // search unmatched and ambiguously-matched classes
77 matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses());
78 for (ClassMatch match : lastMatching.ambiguousMatches()) {
79 matching.match(match.sourceClasses, match.destClasses);
80 }
81 }
82 System.out.println(matching);
83 BiMap<ClassEntry,ClassEntry> uniqueMatches = matching.uniqueMatches();
84
85 // did we match anything new this time?
86 if (uniqueMatches.size() > numUniqueMatchesLastTime) {
87 numUniqueMatchesLastTime = uniqueMatches.size();
88 lastMatching = matching;
89 } else {
90 break;
91 }
92
93 // update the namers
94 ClassNamer namer = new ClassNamer(uniqueMatches);
95 sourceNamer = namer.getSourceNamer();
96 destNamer = namer.getDestNamer();
97 }
98 }
99
100 return lastMatching;
101 }
102
103 public static void convertMappings(Mappings mappings, BiMap<ClassEntry,ClassEntry> changes) {
104
105 // sort the changes so classes are renamed in the correct order
106 // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b
107 LinkedHashMap<ClassEntry,ClassEntry> sortedChanges = Maps.newLinkedHashMap();
108 int numChangesLeft = changes.size();
109 while (!changes.isEmpty()) {
110 Iterator<Map.Entry<ClassEntry,ClassEntry>> iter = changes.entrySet().iterator();
111 while (iter.hasNext()) {
112 Map.Entry<ClassEntry,ClassEntry> change = iter.next();
113 if (changes.containsKey(change.getValue())) {
114 sortedChanges.put(change.getKey(), change.getValue());
115 iter.remove();
116 }
117 }
118
119 // did we remove any changes?
120 if (numChangesLeft - changes.size() > 0) {
121 // keep going
122 numChangesLeft = changes.size();
123 } else {
124 // can't sort anymore. There must be a loop
125 break;
126 }
127 }
128 if (!changes.isEmpty()) {
129 throw new Error("Unable to sort class changes! There must be a cycle.");
130 }
131
132 // convert the mappings in the correct class order
133 for (Map.Entry<ClassEntry,ClassEntry> entry : sortedChanges.entrySet()) {
134 mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName());
135 }
136 }
137
138 /* TODO: after we get a mapping, check to see that the other entries match
139 public static void checkMethods() {
140
141 // check the method matches
142 System.out.println("Checking methods...");
143 for (ClassMapping classMapping : mappings.classes()) {
144 ClassEntry classEntry = new ClassEntry(classMapping.getObfFullName());
145 for (MethodMapping methodMapping : classMapping.methods()) {
146
147 // skip constructors
148 if (methodMapping.getObfName().equals("<init>")) {
149 continue;
150 }
151
152 MethodEntry methodEntry = new MethodEntry(
153 classEntry,
154 methodMapping.getObfName(),
155 methodMapping.getObfSignature()
156 );
157 if (!destIndex.containsObfBehavior(methodEntry)) {
158 System.err.println("WARNING: method doesn't match: " + methodEntry);
159
160 // TODO: show methods if needed
161 // show the available methods
162 System.err.println("\tAvailable dest methods:");
163 CtClass c = destLoader.loadClass(classMapping.getObfFullName());
164 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
165 System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior));
166 }
167
168 System.err.println("\tAvailable source methods:");
169 c = sourceLoader.loadClass(matchedClassNames.inverse().get(classMapping.getObfFullName()));
170 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
171 System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior));
172 }
173 }
174 }
175 }
176
177 System.out.println("Done!");
178 }
179 */
180}
diff --git a/src/cuchaz/enigma/convert/Matches.java b/src/cuchaz/enigma/convert/Matches.java
new file mode 100644
index 00000000..75ecc2a8
--- /dev/null
+++ b/src/cuchaz/enigma/convert/Matches.java
@@ -0,0 +1,89 @@
1package cuchaz.enigma.convert;
2
3import java.util.ArrayList;
4import java.util.Collection;
5import java.util.Iterator;
6import java.util.Map;
7import java.util.Set;
8
9import com.google.common.collect.BiMap;
10import com.google.common.collect.HashBiMap;
11import com.google.common.collect.Maps;
12import com.google.common.collect.Sets;
13
14import cuchaz.enigma.mapping.ClassEntry;
15
16
17public class Matches implements Iterable<ClassMatch> {
18
19 Collection<ClassMatch> m_matches;
20 BiMap<ClassEntry,ClassEntry> m_uniqueMatches;
21 Map<ClassEntry,ClassMatch> m_ambiguousMatchesBySource;
22 Map<ClassEntry,ClassMatch> m_ambiguousMatchesByDest;
23 Set<ClassEntry> m_unmatchedSourceClasses;
24 Set<ClassEntry> m_unmatchedDestClasses;
25
26 public Matches() {
27 this(new ArrayList<ClassMatch>());
28 }
29
30 public Matches(Collection<ClassMatch> matches) {
31 m_matches = matches;
32 m_uniqueMatches = HashBiMap.create();
33 m_ambiguousMatchesBySource = Maps.newHashMap();
34 m_ambiguousMatchesByDest = Maps.newHashMap();
35 m_unmatchedSourceClasses = Sets.newHashSet();
36 m_unmatchedDestClasses = Sets.newHashSet();
37
38 for (ClassMatch match : matches) {
39 indexMatch(match);
40 }
41 }
42
43 public void add(ClassMatch match) {
44 m_matches.add(match);
45 indexMatch(match);
46 }
47
48 public int size() {
49 return m_matches.size();
50 }
51
52 @Override
53 public Iterator<ClassMatch> iterator() {
54 return m_matches.iterator();
55 }
56
57 private void indexMatch(ClassMatch match) {
58 if (!match.isMatched()) {
59 // unmatched
60 m_unmatchedSourceClasses.addAll(match.sourceClasses);
61 m_unmatchedDestClasses.addAll(match.destClasses);
62 } else {
63 if (match.isAmbiguous()) {
64 // ambiguously matched
65 for (ClassEntry entry : match.sourceClasses) {
66 m_ambiguousMatchesBySource.put(entry, match);
67 }
68 for (ClassEntry entry : match.destClasses) {
69 m_ambiguousMatchesByDest.put(entry, match);
70 }
71 } else {
72 // uniquely matched
73 m_uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest());
74 }
75 }
76 }
77
78 public BiMap<ClassEntry,ClassEntry> getUniqueMatches() {
79 return m_uniqueMatches;
80 }
81
82 public Set<ClassEntry> getUnmatchedSourceClasses() {
83 return m_unmatchedSourceClasses;
84 }
85
86 public Set<ClassEntry> getUnmatchedDestClasses() {
87 return m_unmatchedDestClasses;
88 }
89}
diff --git a/src/cuchaz/enigma/convert/MatchesReader.java b/src/cuchaz/enigma/convert/MatchesReader.java
new file mode 100644
index 00000000..808f8d0a
--- /dev/null
+++ b/src/cuchaz/enigma/convert/MatchesReader.java
@@ -0,0 +1,45 @@
1package cuchaz.enigma.convert;
2
3import java.io.BufferedReader;
4import java.io.File;
5import java.io.FileReader;
6import java.io.IOException;
7import java.util.Collection;
8import java.util.List;
9
10import com.beust.jcommander.internal.Lists;
11
12import cuchaz.enigma.mapping.ClassEntry;
13
14
15public class MatchesReader {
16
17 public static Matches read(File file)
18 throws IOException {
19 try (BufferedReader in = new BufferedReader(new FileReader(file))) {
20 Matches matches = new Matches();
21 String line = null;
22 while ((line = in.readLine()) != null) {
23 matches.add(readMatch(line));
24 }
25 return matches;
26 }
27 }
28
29 private static ClassMatch readMatch(String line)
30 throws IOException {
31 String[] sides = line.split(":", 2);
32 return new ClassMatch(readClasses(sides[0]), readClasses(sides[1]));
33 }
34
35 private static Collection<ClassEntry> readClasses(String in) {
36 List<ClassEntry> entries = Lists.newArrayList();
37 for (String className : in.split(",")) {
38 className = className.trim();
39 if (className.length() > 0) {
40 entries.add(new ClassEntry(className));
41 }
42 }
43 return entries;
44 }
45}
diff --git a/src/cuchaz/enigma/convert/MatchesWriter.java b/src/cuchaz/enigma/convert/MatchesWriter.java
new file mode 100644
index 00000000..49ffb6d7
--- /dev/null
+++ b/src/cuchaz/enigma/convert/MatchesWriter.java
@@ -0,0 +1,41 @@
1package cuchaz.enigma.convert;
2
3import java.io.File;
4import java.io.FileWriter;
5import java.io.IOException;
6
7import cuchaz.enigma.mapping.ClassEntry;
8
9
10public class MatchesWriter {
11
12 public static void write(Matches matches, File file)
13 throws IOException {
14 try (FileWriter out = new FileWriter(file)) {
15 for (ClassMatch match : matches) {
16 writeMatch(out, match);
17 }
18 }
19 }
20
21 private static void writeMatch(FileWriter out, ClassMatch match)
22 throws IOException {
23 writeClasses(out, match.sourceClasses);
24 out.write(":");
25 writeClasses(out, match.destClasses);
26 out.write("\n");
27 }
28
29 private static void writeClasses(FileWriter out, Iterable<ClassEntry> classes)
30 throws IOException {
31 boolean isFirst = true;
32 for (ClassEntry entry : classes) {
33 if (isFirst) {
34 isFirst = false;
35 } else {
36 out.write(",");
37 }
38 out.write(entry.toString());
39 }
40 }
41}
diff --git a/src/cuchaz/enigma/gui/MatchingGui.java b/src/cuchaz/enigma/gui/MatchingGui.java
new file mode 100644
index 00000000..53c767ac
--- /dev/null
+++ b/src/cuchaz/enigma/gui/MatchingGui.java
@@ -0,0 +1,140 @@
1package cuchaz.enigma.gui;
2
3import cuchaz.enigma.Deobfuscator;
4import cuchaz.enigma.convert.Matches;
5
6
7public class MatchingGui {
8
9 public MatchingGui(Matches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
10 // TODO Auto-generated constructor stub
11 }
12
13
14 /* TODO: see if we can use any of this here
15 public static doTheThings() {
16
17 // get all the obf class names used in the mappings
18 Set<ClassEntry> usedClasses = Sets.newHashSet();
19 for (String className : mappings.getAllObfClassNames()) {
20 usedClasses.add(new ClassEntry(className));
21 }
22 System.out.println(String.format("Mappings reference %d/%d classes",
23 usedClasses.size(), sourceIndex.getObfClassEntries().size()
24 ));
25
26 // get the used matches
27 Collection<ClassMatch> matches = matching.matches();
28 Matches usedMatches = new Matches();
29 for (ClassMatch match : matching.matches()) {
30 if (!match.intersectSourceClasses(usedClasses).isEmpty()) {
31 usedMatches.add(match);
32 }
33 }
34 System.out.println(String.format("Mappings reference %d/%d match groups",
35 usedMatches.size(), matches.size()
36 ));
37
38 // see what the used classes map to
39 BiMap<ClassEntry,ClassEntry> uniqueUsedMatches = HashBiMap.create();
40 Map<ClassEntry,ClassMatch> ambiguousUsedMatches = Maps.newHashMap();
41 Set<ClassEntry> unmatchedUsedClasses = Sets.newHashSet();
42 for (ClassMatch match : matching.matches()) {
43 Set<ClassEntry> matchUsedClasses = match.intersectSourceClasses(usedClasses);
44 if (matchUsedClasses.isEmpty()) {
45 continue;
46 }
47
48 usedMatches.add(match);
49
50 // classify the match
51 if (!match.isMatched()) {
52 // unmatched
53 unmatchedUsedClasses.addAll(matchUsedClasses);
54 } else {
55 if (match.isAmbiguous()) {
56 // ambiguously matched
57 for (ClassEntry matchUsedClass : matchUsedClasses) {
58 ambiguousUsedMatches.put(matchUsedClass, match);
59 }
60 } else {
61 // uniquely matched
62 uniqueUsedMatches.put(match.getUniqueSource(), match.getUniqueDest());
63 }
64 }
65 }
66
67 // get unmatched dest classes
68 Set<ClassEntry> unmatchedDestClasses = Sets.newHashSet();
69 for (ClassMatch match : matching.matches()) {
70 if (!match.isMatched()) {
71 unmatchedDestClasses.addAll(match.destClasses);
72 }
73 }
74
75 // warn about the ambiguous used matches
76 if (ambiguousUsedMatches.size() > 0) {
77 System.out.println(String.format("%d source classes have ambiguous mappings", ambiguousUsedMatches.size()));
78 List<ClassMatch> ambiguousMatchesList = Lists.newArrayList(Sets.newHashSet(ambiguousUsedMatches.values()));
79 Collections.sort(ambiguousMatchesList, new Comparator<ClassMatch>() {
80 @Override
81 public int compare(ClassMatch a, ClassMatch b) {
82 String aName = a.sourceClasses.iterator().next().getName();
83 String bName = b.sourceClasses.iterator().next().getName();
84 return aName.compareTo(bName);
85 }
86 });
87 for (ClassMatch match : ambiguousMatchesList) {
88 System.out.println("Ambiguous matching:");
89 System.out.println("\tSource: " + getClassNames(match.sourceClasses));
90 System.out.println("\tDest: " + getClassNames(match.destClasses));
91 }
92 }
93
94 // warn about unmatched used classes
95 for (ClassEntry unmatchedUsedClass : unmatchedUsedClasses) {
96 System.out.println("No exact match for source class " + unmatchedUsedClass.getClassEntry());
97
98 // rank all the unmatched dest classes against the used class
99 ClassIdentity sourceIdentity = matching.getSourceIdentifier().identify(unmatchedUsedClass);
100 Multimap<Integer,ClassEntry> scoredDestClasses = ArrayListMultimap.create();
101 for (ClassEntry unmatchedDestClass : unmatchedDestClasses) {
102 ClassIdentity destIdentity = matching.getDestIdentifier().identify(unmatchedDestClass);
103 scoredDestClasses.put(sourceIdentity.getMatchScore(destIdentity), unmatchedDestClass);
104 }
105
106 List<Integer> scores = new ArrayList<Integer>(scoredDestClasses.keySet());
107 Collections.sort(scores, Collections.reverseOrder());
108 printScoredMatches(sourceIdentity.getMaxMatchScore(), scores, scoredDestClasses);
109 }
110
111 // bail if there were unmatched classes
112 if (!unmatchedUsedClasses.isEmpty()) {
113 throw new Error("There were " + unmatchedUsedClasses.size() + " unmatched classes!");
114 }
115 }
116
117 private static void printScoredMatches(int maxScore, List<Integer> scores, Multimap<Integer,ClassEntry> scoredMatches) {
118 int numScoredMatchesShown = 0;
119 for (int score : scores) {
120 for (ClassEntry classEntry : scoredMatches.get(score)) {
121 System.out.println(String.format("\tScore: %3d %3.0f%% %s",
122 score, 100.0 * score / maxScore, classEntry.getName()
123 ));
124 if (numScoredMatchesShown++ > 10) {
125 return;
126 }
127 }
128 }
129 }
130
131 private static List<String> getClassNames(Collection<ClassEntry> classes) {
132 List<String> out = Lists.newArrayList();
133 for (ClassEntry c : classes) {
134 out.add(c.getName());
135 }
136 Collections.sort(out);
137 return out;
138 }
139 */
140}