summaryrefslogtreecommitdiff
path: root/src/cuchaz/enigma/convert/ClassMatcher.java
diff options
context:
space:
mode:
authorGravatar jeff2014-08-30 16:31:31 -0400
committerGravatar jeff2014-08-30 16:31:31 -0400
commit59c592673635e989fd0785d41d51d7c3dd17cc0b (patch)
treeccbf73bd2b45bdfa6901db03100e5ff7f4116220 /src/cuchaz/enigma/convert/ClassMatcher.java
parentfinished class matching for now, need to work on class member matching (diff)
downloadenigma-fork-59c592673635e989fd0785d41d51d7c3dd17cc0b.tar.gz
enigma-fork-59c592673635e989fd0785d41d51d7c3dd17cc0b.tar.xz
enigma-fork-59c592673635e989fd0785d41d51d7c3dd17cc0b.zip
debugging class matcher... almost got it!
Diffstat (limited to 'src/cuchaz/enigma/convert/ClassMatcher.java')
-rw-r--r--src/cuchaz/enigma/convert/ClassMatcher.java398
1 files changed, 398 insertions, 0 deletions
diff --git a/src/cuchaz/enigma/convert/ClassMatcher.java b/src/cuchaz/enigma/convert/ClassMatcher.java
new file mode 100644
index 0000000..ac07a5b
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassMatcher.java
@@ -0,0 +1,398 @@
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.AbstractMap;
18import java.util.ArrayList;
19import java.util.Arrays;
20import java.util.Collection;
21import java.util.Collections;
22import java.util.Comparator;
23import java.util.Iterator;
24import java.util.List;
25import java.util.Map;
26import java.util.Set;
27import java.util.TreeMap;
28import java.util.jar.JarFile;
29
30import javassist.CtBehavior;
31import javassist.CtClass;
32
33import com.beust.jcommander.internal.Lists;
34import com.beust.jcommander.internal.Sets;
35import com.google.common.collect.BiMap;
36import com.google.common.collect.HashBiMap;
37import com.google.common.collect.Maps;
38
39import cuchaz.enigma.TranslatingTypeLoader;
40import cuchaz.enigma.analysis.JarIndex;
41import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
42import cuchaz.enigma.mapping.ClassEntry;
43import cuchaz.enigma.mapping.ClassMapping;
44import cuchaz.enigma.mapping.MappingParseException;
45import cuchaz.enigma.mapping.Mappings;
46import cuchaz.enigma.mapping.MappingsReader;
47import cuchaz.enigma.mapping.MappingsWriter;
48import cuchaz.enigma.mapping.MethodEntry;
49import cuchaz.enigma.mapping.MethodMapping;
50
51public class ClassMatcher
52{
53 public static void main( String[] args )
54 throws IOException, MappingParseException
55 {
56 // TEMP
57 JarFile sourceJar = new JarFile( new File( "input/1.8-pre1.jar" ) );
58 JarFile destJar = new JarFile( new File( "input/1.8-pre2.jar" ) );
59 File inMappingsFile = new File( "../minecraft-mappings/1.8-pre.mappings" );
60 File outMappingsFile = new File( "../minecraft-mappings/1.8-pre2.mappings" );
61
62 // do the conversion
63 Mappings mappings = new MappingsReader().read( new FileReader( inMappingsFile ) );
64 convertMappings( sourceJar, destJar, mappings );
65
66 // write out the convert mappings
67 FileWriter writer = new FileWriter( outMappingsFile );
68 new MappingsWriter().write( writer, mappings );
69 writer.close();
70 System.out.println( "Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath() );
71 }
72
73 private static void convertMappings( JarFile sourceJar, JarFile destJar, Mappings mappings )
74 {
75 // index jars
76 System.out.println( "Indexing source jar..." );
77 JarIndex sourceIndex = new JarIndex();
78 sourceIndex.indexJar( sourceJar, false );
79 System.out.println( "Indexing dest jar..." );
80 JarIndex destIndex = new JarIndex();
81 destIndex.indexJar( destJar, false );
82 TranslatingTypeLoader sourceLoader = new TranslatingTypeLoader( sourceJar, sourceIndex );
83 TranslatingTypeLoader destLoader = new TranslatingTypeLoader( destJar, destIndex );
84
85 // compute the matching
86 ClassMatching matching = ClassMatcher.computeMatching( sourceIndex, sourceLoader, destIndex, destLoader );
87
88 // start the class conversion map with the unique and ambiguous matchings
89 Map<String,Map.Entry<ClassIdentity,List<ClassIdentity>>> conversionMap = matching.getConversionMap();
90
91 // probabilistically match the unmatched source classes
92 for( ClassIdentity sourceClass : new ArrayList<ClassIdentity>( matching.getUnmatchedSourceClasses() ) )
93 {
94 System.out.println( "No exact match for source class " + sourceClass.getClassEntry() );
95
96 // find the closest classes
97 TreeMap<Integer,ClassIdentity> scoredMatches = Maps.newTreeMap( Collections.reverseOrder() );
98 for( ClassIdentity c : matching.getUnmatchedDestClasses() )
99 {
100 scoredMatches.put( sourceClass.getMatchScore( c ), c );
101 }
102 Iterator<Map.Entry<Integer,ClassIdentity>> iter = scoredMatches.entrySet().iterator();
103 for( int i=0; i<10 && iter.hasNext(); i++ )
104 {
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
109 // does the best match have a non-zero score and the same name?
110 Map.Entry<Integer,ClassIdentity> bestMatch = scoredMatches.firstEntry();
111 if( bestMatch.getKey() > 0 && bestMatch.getValue().getClassEntry().equals( sourceClass.getClassEntry() ) )
112 {
113 // use it
114 System.out.println( "\tAutomatically choosing likely match: " + bestMatch.getValue().getClassEntry().getName() );
115 conversionMap.put(
116 sourceClass.getClassEntry().getName(),
117 new AbstractMap.SimpleEntry<ClassIdentity,List<ClassIdentity>>( sourceClass, Arrays.asList( bestMatch.getValue() ) )
118 );
119 }
120 }
121
122 // use the matching to convert the mappings
123 BiMap<String,String> classConversion = HashBiMap.create();
124 Set<String> unmatchedSourceClasses = Sets.newHashSet();
125 for( String className : mappings.getAllObfClassNames() )
126 {
127 // is there a match for this class?
128 Map.Entry<ClassIdentity,List<ClassIdentity>> entry = conversionMap.get( className );
129 ClassIdentity sourceClass = entry.getKey();
130 List<ClassIdentity> matches = entry.getValue();
131
132 if( matches.isEmpty() )
133 {
134 // no match! =(
135 unmatchedSourceClasses.add( className );
136 }
137 else if( matches.size() == 1 )
138 {
139 // unique match! We're good to go!
140 classConversion.put(
141 sourceClass.getClassEntry().getName(),
142 matches.get( 0 ).getClassEntry().getName()
143 );
144 }
145 else if( matches.size() > 1 )
146 {
147 // too many matches! =(
148 unmatchedSourceClasses.add( className );
149 }
150 }
151
152 // remove (and warn about) unmatched classes
153 if( !unmatchedSourceClasses.isEmpty() )
154 {
155 System.err.println( "WARNING: there were unmatched classes!" );
156 for( String className : unmatchedSourceClasses )
157 {
158 System.err.println( "\t" + className );
159 mappings.removeClassByObfName( className );
160 }
161 System.err.println( "Mappings for these classes have been removed." );
162 }
163
164 // show the class name changes
165 for( Map.Entry<String,String> entry : classConversion.entrySet() )
166 {
167 if( !entry.getKey().equals( entry.getValue() ) )
168 {
169 System.out.println( String.format( "Class change: %s -> %s", entry.getKey(), entry.getValue() ) );
170 /* DEBUG
171 System.out.println( String.format( "\n%s\n%s",
172 new ClassIdentity( sourceLoader.loadClass( entry.getKey() ), null, sourceIndex, false, false ),
173 new ClassIdentity( destLoader.loadClass( entry.getValue() ), null, destIndex, false, false )
174 ) );
175 */
176 }
177 }
178
179 // TEMP: show some classes
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 );
187
188 // look for method matches
189 System.out.println( "Matching methods..." );
190 for( ClassMapping classMapping : mappings.classes() )
191 {
192 ClassEntry classEntry = new ClassEntry( classMapping.getObfName() );
193 for( MethodMapping methodMapping : classMapping.methods() )
194 {
195 // skip constructors
196 if( methodMapping.getObfName().equals( "<init>" ) )
197 {
198 continue;
199 }
200
201 MethodEntry methodEntry = new MethodEntry(
202 classEntry,
203 methodMapping.getObfName(),
204 methodMapping.getObfSignature()
205 );
206 if( !destIndex.isMethodImplemented( methodEntry ) )
207 {
208 System.err.println( "WARNING: method doesn't match: " + methodEntry );
209
210 // show the available methods
211 System.err.println( "\tAvailable dest methods:" );
212 CtClass c = destLoader.loadClass( classMapping.getObfName() );
213 for( CtBehavior behavior : c.getDeclaredBehaviors() )
214 {
215 MethodEntry declaredMethodEntry = new MethodEntry(
216 new ClassEntry( classMapping.getObfName() ),
217 behavior.getName(),
218 behavior.getSignature()
219 );
220 System.err.println( "\t\t" + declaredMethodEntry );
221 }
222
223 System.err.println( "\tAvailable source methods:" );
224 c = sourceLoader.loadClass( classConversion.inverse().get( classMapping.getObfName() ) );
225 for( CtBehavior behavior : c.getDeclaredBehaviors() )
226 {
227 MethodEntry declaredMethodEntry = new MethodEntry(
228 new ClassEntry( classMapping.getObfName() ),
229 behavior.getName(),
230 behavior.getSignature()
231 );
232 System.err.println( "\t\t" + declaredMethodEntry );
233 }
234 }
235 }
236 }
237 }
238
239 public static ClassMatching computeMatching( JarIndex sourceIndex, TranslatingTypeLoader sourceLoader, JarIndex destIndex, TranslatingTypeLoader destLoader )
240 {
241 System.out.println( "Matching classes..." );
242 ClassMatching matching = null;
243 for( boolean useRawNames : Arrays.asList( false/*, true*/ ) )
244 {
245 for( boolean useReferences : Arrays.asList( false, true ) )
246 {
247 int numMatches = 0;
248 do
249 {
250 SidedClassNamer sourceNamer = null;
251 SidedClassNamer destNamer = null;
252 if( matching != null )
253 {
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
263 // get the entries left to match
264 Set<ClassEntry> sourceClassEntries = sourceIndex.getObfClassEntries();
265 Set<ClassEntry> destClassEntries = destIndex.getObfClassEntries();
266 if( matching != null )
267 {
268 sourceClassEntries.clear();
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 {
285 sourceClassEntries.add( c.getClassEntry() );
286 matching.removeSource( c );
287 }
288 for( ClassIdentity c : matching.getUnmatchedDestClasses() )
289 {
290 destClassEntries.add( c.getClassEntry() );
291 matching.removeDest( c );
292 }
293 }
294 else
295 {
296 matching = new ClassMatching();
297 }
298
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 {
308 CtClass c = destLoader.loadClass( classEntry.getName() );
309 ClassIdentity destClass = new ClassIdentity( c, destNamer, destIndex, useReferences, useRawNames );
310 matching.matchDestClass( destClass );
311 }
312
313 // TEMP
314 System.out.println( matching );
315 }
316 while( matching.getUniqueMatches().size() - numMatches > 0 );
317 }
318 }
319
320 // DEBUG: check the class matches
321 System.out.println( "Checking class matches..." );
322 for( Map.Entry<ClassIdentity,ClassIdentity> entry : matching.getUniqueMatches().entrySet() )
323 {
324 // check source
325 ClassIdentity sourceClass = entry.getKey();
326 CtClass sourceC = sourceLoader.loadClass( sourceClass.getClassEntry().getName() );
327 assert( sourceC != null )
328 : "Unable to load source class " + sourceClass.getClassEntry();
329 assert( sourceClass.matches( sourceC ) )
330 : "Source " + sourceClass + " doesn't match " + new ClassIdentity( sourceC, null, sourceIndex, false, false );
331
332 // check dest
333 ClassIdentity destClass = entry.getValue();
334 CtClass destC = destLoader.loadClass( destClass.getClassEntry().getName() );
335 assert( destC != null )
336 : "Unable to load dest class " + destClass.getClassEntry();
337 assert( destClass.matches( destC ) )
338 : "Dest " + destClass + " doesn't match " + new ClassIdentity( destC, null, destIndex, false, false );
339 }
340
341 // warn about the ambiguous matchings
342 List<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>> ambiguousMatches = new ArrayList<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>>( matching.getAmbiguousMatches().entrySet() );
343 Collections.sort( ambiguousMatches, new Comparator<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>>( )
344 {
345 @Override
346 public int compare( Map.Entry<List<ClassIdentity>,List<ClassIdentity>> a, Map.Entry<List<ClassIdentity>,List<ClassIdentity>> b )
347 {
348 String aName = a.getKey().get( 0 ).getClassEntry().getName();
349 String bName = b.getKey().get( 0 ).getClassEntry().getName();
350 return aName.compareTo( bName );
351 }
352 } );
353 for( Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : ambiguousMatches )
354 {
355 System.out.println( "Ambiguous matching:" );
356 System.out.println( "\tSource: " + getClassNames( entry.getKey() ) );
357 System.out.println( "\tDest: " + getClassNames( entry.getValue() ) );
358 }
359
360 /* DEBUG
361 Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry = ambiguousMatches.get( 7 );
362 for( ClassIdentity c : entry.getKey() )
363 {
364 System.out.println( c );
365 }
366 for( ClassIdentity c : entry.getKey() )
367 {
368 System.out.println( decompile( sourceLoader, c.getClassEntry() ) );
369 }
370 */
371
372 return matching;
373 }
374
375 private static List<String> getClassNames( Collection<ClassIdentity> classes )
376 {
377 List<String> out = Lists.newArrayList();
378 for( ClassIdentity c : classes )
379 {
380 out.add( c.getClassEntry().getName() );
381 }
382 Collections.sort( out );
383 return out;
384 }
385
386 /* DEBUG
387 private static String decompile( TranslatingTypeLoader loader, ClassEntry classEntry )
388 {
389 PlainTextOutput output = new PlainTextOutput();
390 DecompilerSettings settings = DecompilerSettings.javaDefaults();
391 settings.setForceExplicitImports( true );
392 settings.setShowSyntheticMembers( true );
393 settings.setTypeLoader( loader );
394 Decompiler.decompile( classEntry.getName(), output, settings );
395 return output.toString();
396 }
397 */
398}