summaryrefslogtreecommitdiff
path: root/src/cuchaz/enigma/convert/ClassMatcher.java
diff options
context:
space:
mode:
authorGravatar jeff2014-08-31 14:41:24 -0400
committerGravatar jeff2014-08-31 14:41:24 -0400
commitd3fc0b55515e81ae1b10fa16129f05b0241271f0 (patch)
treee0359362ca9af0cb1573c665392c7de10c9bead2 /src/cuchaz/enigma/convert/ClassMatcher.java
parentdebugging class matcher... almost got it! (diff)
downloadenigma-fork-d3fc0b55515e81ae1b10fa16129f05b0241271f0.tar.gz
enigma-fork-d3fc0b55515e81ae1b10fa16129f05b0241271f0.tar.xz
enigma-fork-d3fc0b55515e81ae1b10fa16129f05b0241271f0.zip
fixed lots of bugs in the mappings converter. It's finally ready. =)
Diffstat (limited to 'src/cuchaz/enigma/convert/ClassMatcher.java')
-rw-r--r--src/cuchaz/enigma/convert/ClassMatcher.java260
1 files changed, 156 insertions, 104 deletions
diff --git a/src/cuchaz/enigma/convert/ClassMatcher.java b/src/cuchaz/enigma/convert/ClassMatcher.java
index ac07a5b..b551da2 100644
--- a/src/cuchaz/enigma/convert/ClassMatcher.java
+++ b/src/cuchaz/enigma/convert/ClassMatcher.java
@@ -14,17 +14,14 @@ import java.io.File;
14import java.io.FileReader; 14import java.io.FileReader;
15import java.io.FileWriter; 15import java.io.FileWriter;
16import java.io.IOException; 16import java.io.IOException;
17import java.util.AbstractMap;
18import java.util.ArrayList; 17import java.util.ArrayList;
19import java.util.Arrays; 18import java.util.Arrays;
20import java.util.Collection; 19import java.util.Collection;
21import java.util.Collections; 20import java.util.Collections;
22import java.util.Comparator; 21import java.util.Comparator;
23import java.util.Iterator;
24import java.util.List; 22import java.util.List;
25import java.util.Map; 23import java.util.Map;
26import java.util.Set; 24import java.util.Set;
27import java.util.TreeMap;
28import java.util.jar.JarFile; 25import java.util.jar.JarFile;
29 26
30import javassist.CtBehavior; 27import javassist.CtBehavior;
@@ -32,9 +29,11 @@ import javassist.CtClass;
32 29
33import com.beust.jcommander.internal.Lists; 30import com.beust.jcommander.internal.Lists;
34import com.beust.jcommander.internal.Sets; 31import com.beust.jcommander.internal.Sets;
32import com.google.common.collect.ArrayListMultimap;
35import com.google.common.collect.BiMap; 33import com.google.common.collect.BiMap;
36import com.google.common.collect.HashBiMap; 34import com.google.common.collect.HashBiMap;
37import com.google.common.collect.Maps; 35import com.google.common.collect.Maps;
36import com.google.common.collect.Multimap;
38 37
39import cuchaz.enigma.TranslatingTypeLoader; 38import cuchaz.enigma.TranslatingTypeLoader;
40import cuchaz.enigma.analysis.JarIndex; 39import cuchaz.enigma.analysis.JarIndex;
@@ -55,22 +54,28 @@ public class ClassMatcher
55 { 54 {
56 // TEMP 55 // TEMP
57 JarFile sourceJar = new JarFile( new File( "input/1.8-pre1.jar" ) ); 56 JarFile sourceJar = new JarFile( new File( "input/1.8-pre1.jar" ) );
58 JarFile destJar = new JarFile( new File( "input/1.8-pre2.jar" ) ); 57 JarFile destJar = new JarFile( new File( "input/1.8-pre3.jar" ) );
59 File inMappingsFile = new File( "../minecraft-mappings/1.8-pre.mappings" ); 58 File inMappingsFile = new File( "../minecraft-mappings/1.8-pre.mappings" );
60 File outMappingsFile = new File( "../minecraft-mappings/1.8-pre2.mappings" ); 59 File outMappingsFile = new File( "../minecraft-mappings/1.8-pre3.mappings" );
60
61 // define a matching to use when the automated system cannot find a match
62 Map<String,String> fallbackMatching = Maps.newHashMap();
63 fallbackMatching.put( "none/ayb", "none/ayb" );
64 fallbackMatching.put( "none/ayd", "none/ayd" );
65 fallbackMatching.put( "none/bgk", "none/bgk" );
61 66
62 // do the conversion 67 // do the conversion
63 Mappings mappings = new MappingsReader().read( new FileReader( inMappingsFile ) ); 68 Mappings mappings = new MappingsReader().read( new FileReader( inMappingsFile ) );
64 convertMappings( sourceJar, destJar, mappings ); 69 convertMappings( sourceJar, destJar, mappings, fallbackMatching );
65 70
66 // write out the convert mappings 71 // write out the converted mappings
67 FileWriter writer = new FileWriter( outMappingsFile ); 72 FileWriter writer = new FileWriter( outMappingsFile );
68 new MappingsWriter().write( writer, mappings ); 73 new MappingsWriter().write( writer, mappings );
69 writer.close(); 74 writer.close();
70 System.out.println( "Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath() ); 75 System.out.println( "Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath() );
71 } 76 }
72 77
73 private static void convertMappings( JarFile sourceJar, JarFile destJar, Mappings mappings ) 78 private static void convertMappings( JarFile sourceJar, JarFile destJar, Mappings mappings, Map<String,String> fallbackMatching )
74 { 79 {
75 // index jars 80 // index jars
76 System.out.println( "Indexing source jar..." ); 81 System.out.println( "Indexing source jar..." );
@@ -83,58 +88,77 @@ public class ClassMatcher
83 TranslatingTypeLoader destLoader = new TranslatingTypeLoader( destJar, destIndex ); 88 TranslatingTypeLoader destLoader = new TranslatingTypeLoader( destJar, destIndex );
84 89
85 // compute the matching 90 // compute the matching
86 ClassMatching matching = ClassMatcher.computeMatching( sourceIndex, sourceLoader, destIndex, destLoader ); 91 ClassMatching matching = computeMatching( sourceIndex, sourceLoader, destIndex, destLoader );
87 92
88 // start the class conversion map with the unique and ambiguous matchings 93 // start the class conversion map with the unique and ambiguous matchings
89 Map<String,Map.Entry<ClassIdentity,List<ClassIdentity>>> conversionMap = matching.getConversionMap(); 94 Map<String,Map.Entry<ClassIdentity,List<ClassIdentity>>> conversionMap = matching.getConversionMap();
90 95
91 // probabilistically match the unmatched source classes 96 // get all the obf class names used in the mappings
92 for( ClassIdentity sourceClass : new ArrayList<ClassIdentity>( matching.getUnmatchedSourceClasses() ) ) 97 Set<String> usedClassNames = mappings.getAllObfClassNames();
98 Set<String> allClassNames = Sets.newHashSet();
99 for( ClassEntry classEntry : sourceIndex.getObfClassEntries() )
100 {
101 allClassNames.add( classEntry.getName() );
102 }
103 usedClassNames.retainAll( allClassNames );
104
105 // probabilistically match the non-uniquely-matched source classes
106 for( Map.Entry<ClassIdentity,List<ClassIdentity>> entry : conversionMap.values() )
93 { 107 {
108 ClassIdentity sourceClass = entry.getKey();
109 List<ClassIdentity> destClasses = entry.getValue();
110
111 // skip classes that are uniquely matched
112 if( destClasses.size() == 1 )
113 {
114 continue;
115 }
116
117 // skip classes that aren't used in the mappings
118 if( !usedClassNames.contains( sourceClass.getClassEntry().getName() ) )
119 {
120 continue;
121 }
122
94 System.out.println( "No exact match for source class " + sourceClass.getClassEntry() ); 123 System.out.println( "No exact match for source class " + sourceClass.getClassEntry() );
95 124
96 // find the closest classes 125 // find the closest classes
97 TreeMap<Integer,ClassIdentity> scoredMatches = Maps.newTreeMap( Collections.reverseOrder() ); 126 Multimap<Integer,ClassIdentity> scoredMatches = ArrayListMultimap.create();
98 for( ClassIdentity c : matching.getUnmatchedDestClasses() ) 127 for( ClassIdentity c : destClasses )
99 { 128 {
100 scoredMatches.put( sourceClass.getMatchScore( c ), c ); 129 scoredMatches.put( sourceClass.getMatchScore( c ), c );
101 } 130 }
102 Iterator<Map.Entry<Integer,ClassIdentity>> iter = scoredMatches.entrySet().iterator(); 131 List<Integer> scores = new ArrayList<Integer>( scoredMatches.keySet() );
103 for( int i=0; i<10 && iter.hasNext(); i++ ) 132 Collections.sort( scores, Collections.reverseOrder() );
104 { 133 printScoredMatches( sourceClass.getMaxMatchScore(), scores, scoredMatches );
105 Map.Entry<Integer,ClassIdentity> score = iter.next();
106 System.out.println( String.format( "\tScore: %3d %s", score.getKey(), score.getValue().getClassEntry().getName() ) );
107 }
108 134
109 // does the best match have a non-zero score and the same name? 135 // does the best match have a non-zero score and the same name?
110 Map.Entry<Integer,ClassIdentity> bestMatch = scoredMatches.firstEntry(); 136 int bestScore = scores.get( 0 );
111 if( bestMatch.getKey() > 0 && bestMatch.getValue().getClassEntry().equals( sourceClass.getClassEntry() ) ) 137 Collection<ClassIdentity> bestMatches = scoredMatches.get( bestScore );
138 if( bestScore > 0 && bestMatches.size() == 1 )
112 { 139 {
113 // use it 140 ClassIdentity bestMatch = bestMatches.iterator().next();
114 System.out.println( "\tAutomatically choosing likely match: " + bestMatch.getValue().getClassEntry().getName() ); 141 if( bestMatch.getClassEntry().equals( sourceClass.getClassEntry() ) )
115 conversionMap.put( 142 {
116 sourceClass.getClassEntry().getName(), 143 // use it
117 new AbstractMap.SimpleEntry<ClassIdentity,List<ClassIdentity>>( sourceClass, Arrays.asList( bestMatch.getValue() ) ) 144 System.out.println( "\tAutomatically choosing likely match: " + bestMatch.getClassEntry().getName() );
118 ); 145 destClasses.clear();
146 destClasses.add( bestMatch );
147 }
119 } 148 }
120 } 149 }
121 150
122 // use the matching to convert the mappings 151 // use the matching to convert the mappings
123 BiMap<String,String> classConversion = HashBiMap.create(); 152 BiMap<String,String> classConversion = HashBiMap.create();
124 Set<String> unmatchedSourceClasses = Sets.newHashSet(); 153 Set<String> unmatchedSourceClasses = Sets.newHashSet();
125 for( String className : mappings.getAllObfClassNames() ) 154 for( String className : usedClassNames )
126 { 155 {
127 // is there a match for this class? 156 // is there a match for this class?
128 Map.Entry<ClassIdentity,List<ClassIdentity>> entry = conversionMap.get( className ); 157 Map.Entry<ClassIdentity,List<ClassIdentity>> entry = conversionMap.get( className );
129 ClassIdentity sourceClass = entry.getKey(); 158 ClassIdentity sourceClass = entry.getKey();
130 List<ClassIdentity> matches = entry.getValue(); 159 List<ClassIdentity> matches = entry.getValue();
131 160
132 if( matches.isEmpty() ) 161 if( matches.size() == 1 )
133 {
134 // no match! =(
135 unmatchedSourceClasses.add( className );
136 }
137 else if( matches.size() == 1 )
138 { 162 {
139 // unique match! We're good to go! 163 // unique match! We're good to go!
140 classConversion.put( 164 classConversion.put(
@@ -142,10 +166,21 @@ public class ClassMatcher
142 matches.get( 0 ).getClassEntry().getName() 166 matches.get( 0 ).getClassEntry().getName()
143 ); 167 );
144 } 168 }
145 else if( matches.size() > 1 ) 169 else
146 { 170 {
147 // too many matches! =( 171 // no match, check the fallback matching
148 unmatchedSourceClasses.add( className ); 172 String fallbackMatch = fallbackMatching.get( className );
173 if( fallbackMatch != null )
174 {
175 classConversion.put(
176 sourceClass.getClassEntry().getName(),
177 fallbackMatch
178 );
179 }
180 else
181 {
182 unmatchedSourceClasses.add( className );
183 }
149 } 184 }
150 } 185 }
151 186
@@ -176,17 +211,11 @@ public class ClassMatcher
176 } 211 }
177 } 212 }
178 213
179 // TEMP: show some classes 214 // convert the mappings
180 for( String className : Arrays.asList( "none/em", "none/ej", "none/en" ) )
181 {
182 System.out.println( String.format( "check: %s -> %s", className, classConversion.get( className ) ) );
183 }
184
185 // convert the classes
186 mappings.renameObfClasses( classConversion ); 215 mappings.renameObfClasses( classConversion );
187 216
188 // look for method matches 217 // check the method matches
189 System.out.println( "Matching methods..." ); 218 System.out.println( "Checking methods..." );
190 for( ClassMapping classMapping : mappings.classes() ) 219 for( ClassMapping classMapping : mappings.classes() )
191 { 220 {
192 ClassEntry classEntry = new ClassEntry( classMapping.getObfName() ); 221 ClassEntry classEntry = new ClassEntry( classMapping.getObfName() );
@@ -234,91 +263,93 @@ public class ClassMatcher
234 } 263 }
235 } 264 }
236 } 265 }
266
267 System.out.println( "Done!" );
237 } 268 }
238 269
239 public static ClassMatching computeMatching( JarIndex sourceIndex, TranslatingTypeLoader sourceLoader, JarIndex destIndex, TranslatingTypeLoader destLoader ) 270 public static ClassMatching computeMatching( JarIndex sourceIndex, TranslatingTypeLoader sourceLoader, JarIndex destIndex, TranslatingTypeLoader destLoader )
240 { 271 {
241 System.out.println( "Matching classes..." ); 272 System.out.println( "Matching classes..." );
242 ClassMatching matching = null; 273 ClassMatching matching = null;
243 for( boolean useRawNames : Arrays.asList( false/*, true*/ ) ) 274 for( boolean useReferences : Arrays.asList( false, true ) )
244 { 275 {
245 for( boolean useReferences : Arrays.asList( false, true ) ) 276 int numMatches = 0;
277 do
246 { 278 {
247 int numMatches = 0; 279 SidedClassNamer sourceNamer = null;
248 do 280 SidedClassNamer destNamer = null;
281 if( matching != null )
249 { 282 {
250 SidedClassNamer sourceNamer = null; 283 // build a class namer
251 SidedClassNamer destNamer = null; 284 ClassNamer namer = new ClassNamer( matching.getUniqueMatches() );
252 if( matching != null ) 285 sourceNamer = namer.getSourceNamer();
253 { 286 destNamer = namer.getDestNamer();
254 // build a class namer
255 ClassNamer namer = new ClassNamer( matching.getUniqueMatches() );
256 sourceNamer = namer.getSourceNamer();
257 destNamer = namer.getDestNamer();
258
259 // note the number of matches
260 numMatches = matching.getUniqueMatches().size();
261 }
262 287
263 // get the entries left to match 288 // note the number of matches
264 Set<ClassEntry> sourceClassEntries = sourceIndex.getObfClassEntries(); 289 numMatches = matching.getUniqueMatches().size();
265 Set<ClassEntry> destClassEntries = destIndex.getObfClassEntries(); 290 }
266 if( matching != null ) 291
292 // get the entries left to match
293 Set<ClassEntry> sourceClassEntries = Sets.newHashSet();
294 Set<ClassEntry> destClassEntries = Sets.newHashSet();
295 if( matching == null )
296 {
297 sourceClassEntries.addAll( sourceIndex.getObfClassEntries() );
298 destClassEntries.addAll( destIndex.getObfClassEntries() );
299 matching = new ClassMatching();
300 }
301 else
302 {
303 for( Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : matching.getAmbiguousMatches().entrySet() )
267 { 304 {
268 sourceClassEntries.clear(); 305 for( ClassIdentity c : entry.getKey() )
269 destClassEntries.clear();
270 for( Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : matching.getAmbiguousMatches().entrySet() )
271 {
272 for( ClassIdentity c : entry.getKey() )
273 {
274 sourceClassEntries.add( c.getClassEntry() );
275 matching.removeSource( c );
276 }
277 for( ClassIdentity c : entry.getValue() )
278 {
279 destClassEntries.add( c.getClassEntry() );
280 matching.removeDest( c );
281 }
282 }
283 for( ClassIdentity c : matching.getUnmatchedSourceClasses() )
284 { 306 {
285 sourceClassEntries.add( c.getClassEntry() ); 307 sourceClassEntries.add( c.getClassEntry() );
286 matching.removeSource( c ); 308 matching.removeSource( c );
287 } 309 }
288 for( ClassIdentity c : matching.getUnmatchedDestClasses() ) 310 for( ClassIdentity c : entry.getValue() )
289 { 311 {
290 destClassEntries.add( c.getClassEntry() ); 312 destClassEntries.add( c.getClassEntry() );
291 matching.removeDest( c ); 313 matching.removeDest( c );
292 } 314 }
293 } 315 }
294 else 316 for( ClassIdentity c : matching.getUnmatchedSourceClasses() )
295 { 317 {
296 matching = new ClassMatching(); 318 sourceClassEntries.add( c.getClassEntry() );
319 matching.removeSource( c );
297 } 320 }
298 321 for( ClassIdentity c : matching.getUnmatchedDestClasses() )
299 // compute a matching for the classes
300 for( ClassEntry classEntry : sourceClassEntries )
301 {
302 CtClass c = sourceLoader.loadClass( classEntry.getName() );
303 ClassIdentity sourceClass = new ClassIdentity( c, sourceNamer, sourceIndex, useReferences, useRawNames );
304 matching.addSource( sourceClass );
305 }
306 for( ClassEntry classEntry : destClassEntries )
307 { 322 {
308 CtClass c = destLoader.loadClass( classEntry.getName() ); 323 destClassEntries.add( c.getClassEntry() );
309 ClassIdentity destClass = new ClassIdentity( c, destNamer, destIndex, useReferences, useRawNames ); 324 matching.removeDest( c );
310 matching.matchDestClass( destClass );
311 } 325 }
312
313 // TEMP
314 System.out.println( matching );
315 } 326 }
316 while( matching.getUniqueMatches().size() - numMatches > 0 ); 327
328 // compute a matching for the classes
329 for( ClassEntry classEntry : sourceClassEntries )
330 {
331 CtClass c = sourceLoader.loadClass( classEntry.getName() );
332 ClassIdentity sourceClass = new ClassIdentity( c, sourceNamer, sourceIndex, useReferences );
333 matching.addSource( sourceClass );
334 }
335 for( ClassEntry classEntry : destClassEntries )
336 {
337 CtClass c = destLoader.loadClass( classEntry.getName() );
338 ClassIdentity destClass = new ClassIdentity( c, destNamer, destIndex, useReferences );
339 matching.matchDestClass( destClass );
340 }
341
342 // TEMP
343 System.out.println( matching );
317 } 344 }
345 while( matching.getUniqueMatches().size() - numMatches > 0 );
318 } 346 }
319 347
320 // DEBUG: check the class matches 348 // check the class matches
321 System.out.println( "Checking class matches..." ); 349 System.out.println( "Checking class matches..." );
350 ClassNamer namer = new ClassNamer( matching.getUniqueMatches() );
351 SidedClassNamer sourceNamer = namer.getSourceNamer();
352 SidedClassNamer destNamer = namer.getDestNamer();
322 for( Map.Entry<ClassIdentity,ClassIdentity> entry : matching.getUniqueMatches().entrySet() ) 353 for( Map.Entry<ClassIdentity,ClassIdentity> entry : matching.getUniqueMatches().entrySet() )
323 { 354 {
324 // check source 355 // check source
@@ -327,7 +358,7 @@ public class ClassMatcher
327 assert( sourceC != null ) 358 assert( sourceC != null )
328 : "Unable to load source class " + sourceClass.getClassEntry(); 359 : "Unable to load source class " + sourceClass.getClassEntry();
329 assert( sourceClass.matches( sourceC ) ) 360 assert( sourceClass.matches( sourceC ) )
330 : "Source " + sourceClass + " doesn't match " + new ClassIdentity( sourceC, null, sourceIndex, false, false ); 361 : "Source " + sourceClass + " doesn't match " + new ClassIdentity( sourceC, sourceNamer, sourceIndex, false );
331 362
332 // check dest 363 // check dest
333 ClassIdentity destClass = entry.getValue(); 364 ClassIdentity destClass = entry.getValue();
@@ -335,7 +366,7 @@ public class ClassMatcher
335 assert( destC != null ) 366 assert( destC != null )
336 : "Unable to load dest class " + destClass.getClassEntry(); 367 : "Unable to load dest class " + destClass.getClassEntry();
337 assert( destClass.matches( destC ) ) 368 assert( destClass.matches( destC ) )
338 : "Dest " + destClass + " doesn't match " + new ClassIdentity( destC, null, destIndex, false, false ); 369 : "Dest " + destClass + " doesn't match " + new ClassIdentity( destC, destNamer, destIndex, false );
339 } 370 }
340 371
341 // warn about the ambiguous matchings 372 // warn about the ambiguous matchings
@@ -372,6 +403,27 @@ public class ClassMatcher
372 return matching; 403 return matching;
373 } 404 }
374 405
406 private static void printScoredMatches( int maxScore, List<Integer> scores, Multimap<Integer,ClassIdentity> scoredMatches )
407 {
408 int numScoredMatchesShown = 0;
409 for( int score : scores )
410 {
411 for( ClassIdentity scoredMatch : scoredMatches.get( score ) )
412 {
413 System.out.println( String.format( "\tScore: %3d %3.0f%% %s",
414 score,
415 100.0*score/maxScore,
416 scoredMatch.getClassEntry().getName()
417 ) );
418
419 if( numScoredMatchesShown++ > 10 )
420 {
421 return;
422 }
423 }
424 }
425 }
426
375 private static List<String> getClassNames( Collection<ClassIdentity> classes ) 427 private static List<String> getClassNames( Collection<ClassIdentity> classes )
376 { 428 {
377 List<String> out = Lists.newArrayList(); 429 List<String> out = Lists.newArrayList();