summaryrefslogtreecommitdiff
path: root/src/cuchaz/enigma/convert/ClassMatcher.java
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/cuchaz/enigma/convert/ClassMatcher.java
parentswitch to better-maintained version of JSyntaxPane (diff)
downloadenigma-fork-88671184e20b3ad3791125cf96c83ca048cb2861.tar.gz
enigma-fork-88671184e20b3ad3791125cf96c83ca048cb2861.tar.xz
enigma-fork-88671184e20b3ad3791125cf96c83ca048cb2861.zip
refactor converter a bit for upcoming convert gui
Diffstat (limited to 'src/cuchaz/enigma/convert/ClassMatcher.java')
-rw-r--r--src/cuchaz/enigma/convert/ClassMatcher.java356
1 files changed, 0 insertions, 356 deletions
diff --git a/src/cuchaz/enigma/convert/ClassMatcher.java b/src/cuchaz/enigma/convert/ClassMatcher.java
deleted file mode 100644
index f43f5b2..0000000
--- 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}