Package cuchaz.enigma.convert

Source Code of cuchaz.enigma.convert.ClassMatcher

/*******************************************************************************
* Copyright (c) 2014 Jeff Martin.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*
* Contributors:
*     Jeff Martin - initial API and implementation
******************************************************************************/
package cuchaz.enigma.convert;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarFile;

import javassist.CtBehavior;
import javassist.CtClass;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;

import cuchaz.enigma.TranslatingTypeLoader;
import cuchaz.enigma.analysis.JarIndex;
import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
import cuchaz.enigma.mapping.ClassEntry;
import cuchaz.enigma.mapping.ClassMapping;
import cuchaz.enigma.mapping.MappingParseException;
import cuchaz.enigma.mapping.Mappings;
import cuchaz.enigma.mapping.MappingsReader;
import cuchaz.enigma.mapping.MappingsWriter;
import cuchaz.enigma.mapping.MethodEntry;
import cuchaz.enigma.mapping.MethodMapping;

public class ClassMatcher
{
  public static void main( String[] args )
  throws IOException, MappingParseException
  {
    // TEMP
    JarFile sourceJar = new JarFile( new File( "input/1.8-pre3.jar" ) );
    JarFile destJar = new JarFile( new File( "input/1.8.jar" ) );
    File inMappingsFile = new File( "../Enigma Mappings/1.8-pre3.mappings" );
    File outMappingsFile = new File( "../Enigma Mappings/1.8.mappings" );
   
    // define a matching to use when the automated system cannot find a match
    Map<String,String> fallbackMatching = Maps.newHashMap();
    fallbackMatching.put( "none/ayb", "none/ayf" );
    fallbackMatching.put( "none/ayd", "none/ayd" );
    fallbackMatching.put( "none/bgk", "unknown/bgk" );
   
    // do the conversion
    Mappings mappings = new MappingsReader().read( new FileReader( inMappingsFile ) );
    convertMappings( sourceJar, destJar, mappings, fallbackMatching );
   
    // write out the converted mappings
    FileWriter writer = new FileWriter( outMappingsFile );
    new MappingsWriter().write( writer, mappings );
    writer.close();
    System.out.println( "Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath() );
  }
 
  private static void convertMappings( JarFile sourceJar, JarFile destJar, Mappings mappings, Map<String,String> fallbackMatching )
  {
    // 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 );
    TranslatingTypeLoader sourceLoader = new TranslatingTypeLoader( sourceJar, sourceIndex );
    TranslatingTypeLoader destLoader = new TranslatingTypeLoader( destJar, destIndex );

    // compute the matching
    ClassMatching matching = computeMatching( sourceIndex, sourceLoader, destIndex, destLoader );
    Map<String,Map.Entry<ClassIdentity,List<ClassIdentity>>> matchingIndex = matching.getIndex();
   
    // get all the obf class names used in the mappings
    Set<String> usedClassNames = mappings.getAllObfClassNames();
    Set<String> allClassNames = Sets.newHashSet();
    for( ClassEntry classEntry : sourceIndex.getObfClassEntries() )
    {
      allClassNames.add( classEntry.getName() );
    }
    usedClassNames.retainAll( allClassNames );
    System.out.println( "Used " + usedClassNames.size() + " classes in the mappings" );
   
    // probabilistically match the non-uniquely-matched source classes
    for( Map.Entry<ClassIdentity,List<ClassIdentity>> entry : matchingIndex.values() )
    {
      ClassIdentity sourceClass = entry.getKey();
      List<ClassIdentity> destClasses = entry.getValue();
     
      // skip classes that are uniquely matched
      if( destClasses.size() == 1 )
      {
        continue;
      }
     
      // skip classes that aren't used in the mappings
      if( !usedClassNames.contains( sourceClass.getClassEntry().getName() ) )
      {
        continue;
      }
     
      System.out.println( "No exact match for source class " + sourceClass.getClassEntry() );
     
      // find the closest classes
      Multimap<Integer,ClassIdentity> scoredMatches = ArrayListMultimap.create();
      for( ClassIdentity c : destClasses )
      {
        scoredMatches.put( sourceClass.getMatchScore( c ), c );
      }
      List<Integer> scores = new ArrayList<Integer>( scoredMatches.keySet() );
      Collections.sort( scores, Collections.reverseOrder() );
      printScoredMatches( sourceClass.getMaxMatchScore(), scores, scoredMatches );
     
      // does the best match have a non-zero score and the same name?
      int bestScore = scores.get( 0 );
      Collection<ClassIdentity> bestMatches = scoredMatches.get( bestScore );
      if( bestScore > 0 && bestMatches.size() == 1 )
      {
        ClassIdentity bestMatch = bestMatches.iterator().next();
        if( bestMatch.getClassEntry().equals( sourceClass.getClassEntry() ) )
        {
          // use it
          System.out.println( "\tAutomatically choosing likely match: " + bestMatch.getClassEntry().getName() );
          destClasses.clear();
          destClasses.add( bestMatch );
        }
      }
    }
   
    // group the matching into unique and non-unique matches
    BiMap<String,String> matchedClassNames = HashBiMap.create();
    Set<String> unmatchedSourceClassNames = Sets.newHashSet();
    for( String className : usedClassNames )
    {
      // is there a match for this class?
      Map.Entry<ClassIdentity,List<ClassIdentity>> entry = matchingIndex.get( className );
      ClassIdentity sourceClass = entry.getKey();
      List<ClassIdentity> matches = entry.getValue();
     
      if( matches.size() == 1 )
      {
        // unique match! We're good to go!
        matchedClassNames.put(
          sourceClass.getClassEntry().getName(),
          matches.get( 0 ).getClassEntry().getName()
        );
      }
      else
      {
        // no match, check the fallback matching
        String fallbackMatch = fallbackMatching.get( className );
        if( fallbackMatch != null )
        {
          matchedClassNames.put(
            sourceClass.getClassEntry().getName(),
            fallbackMatch
          );
        }
        else
        {
          unmatchedSourceClassNames.add( className );
        }
      }
    }
   
    // report unmatched classes
    if( !unmatchedSourceClassNames.isEmpty() )
    {
      System.err.println( "ERROR: there were unmatched classes!" );
      for( String className : unmatchedSourceClassNames )
      {
        System.err.println( "\t" + className );
      }
      return;
    }
   
    // get the class name changes from the matched class names
    Map<String,String> classChanges = Maps.newHashMap();
    for( Map.Entry<String,String> entry : matchedClassNames.entrySet() )
    {
      if( !entry.getKey().equals( entry.getValue() ) )
      {
        classChanges.put( entry.getKey(), entry.getValue() );
        System.out.println( String.format( "Class change: %s -> %s", entry.getKey(), entry.getValue() ) );
        /* DEBUG
        System.out.println( String.format( "\n%s\n%s",
          new ClassIdentity( sourceLoader.loadClass( entry.getKey() ), null, sourceIndex, false, false ),
          new ClassIdentity( destLoader.loadClass( entry.getValue() ), null, destIndex, false, false )
        ) );
        */
      }
    }
   
    // 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<String,String> orderedClassChanges = Maps.newLinkedHashMap();
    int numChangesLeft = classChanges.size();
    while( !classChanges.isEmpty() )
    {
      Iterator<Map.Entry<String,String>> iter = classChanges.entrySet().iterator();
      while( iter.hasNext() )
      {
        Map.Entry<String,String> entry = iter.next();
        if( classChanges.get( entry.getValue() ) == null )
        {
          orderedClassChanges.put( entry.getKey(), entry.getValue() );
          iter.remove();
        }
      }
     
      // did we remove any changes?
      if( numChangesLeft - classChanges.size() > 0 )
      {
        // keep going
        numChangesLeft = classChanges.size();       
      }
      else
      {
        // can't sort anymore. There must be a loop
        break;
      }
    }
    if( classChanges.size() > 0 )
    {
      throw new Error( String.format( "Unable to sort %d/%d class changes!", classChanges.size(), matchedClassNames.size() ) );
    }
   
    // convert the mappings in the correct class order
    for( Map.Entry<String,String> entry : orderedClassChanges.entrySet() )
    {
      mappings.renameObfClass( entry.getKey(), entry.getValue() );
    }
   
    // check the method matches
    System.out.println( "Checking methods..." );
    for( ClassMapping classMapping : mappings.classes() )
    {
      ClassEntry classEntry = new ClassEntry( classMapping.getObfName() );
      for( MethodMapping methodMapping : classMapping.methods() )
      {
        // skip constructors
        if( methodMapping.getObfName().equals( "<init>" ) )
        {
          continue;
        }
       
        MethodEntry methodEntry = new MethodEntry(
          classEntry,
          methodMapping.getObfName(),
          methodMapping.getObfSignature()
        );
        if( !destIndex.containsObfBehavior( methodEntry ) )
        {
          System.err.println( "WARNING: method doesn't match: " + methodEntry );
         
          // show the available methods
          System.err.println( "\tAvailable dest methods:" );
          CtClass c = destLoader.loadClass( classMapping.getObfName() );
          for( CtBehavior behavior : c.getDeclaredBehaviors() )
          {
            MethodEntry declaredMethodEntry = new MethodEntry(
              new ClassEntry( classMapping.getObfName() ),
              behavior.getName(),
              behavior.getSignature()
            );
            System.err.println( "\t\t" + declaredMethodEntry );
          }
         
          System.err.println( "\tAvailable source methods:" );
          c = sourceLoader.loadClass( matchedClassNames.inverse().get( classMapping.getObfName() ) );
          for( CtBehavior behavior : c.getDeclaredBehaviors() )
          {
            MethodEntry declaredMethodEntry = new MethodEntry(
              new ClassEntry( classMapping.getObfName() ),
              behavior.getName(),
              behavior.getSignature()
            );
            System.err.println( "\t\t" + declaredMethodEntry );
          }
        }
      }
    }
   
    System.out.println( "Done!" );
  }
 
  public static ClassMatching computeMatching( JarIndex sourceIndex, TranslatingTypeLoader sourceLoader, JarIndex destIndex, TranslatingTypeLoader destLoader )
  {
    System.out.println( "Matching classes..." );
    ClassMatching matching = null;
    for( boolean useReferences : Arrays.asList( false, true ) )
    {
      int numMatches = 0;
      do
      {
        SidedClassNamer sourceNamer = null;
        SidedClassNamer destNamer = null;
        if( matching != null )
        {
          // build a class namer
          ClassNamer namer = new ClassNamer( matching.getUniqueMatches() );
          sourceNamer = namer.getSourceNamer();
          destNamer = namer.getDestNamer();
         
          // note the number of matches
          numMatches = matching.getUniqueMatches().size();
        }
       
        // get the entries left to match
        Set<ClassEntry> sourceClassEntries = Sets.newHashSet();
        Set<ClassEntry> destClassEntries = Sets.newHashSet();
        if( matching == null )
        {
          sourceClassEntries.addAll( sourceIndex.getObfClassEntries() );
          destClassEntries.addAll( destIndex.getObfClassEntries() );
          matching = new ClassMatching();
        }
        else
        {
          for( Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : matching.getAmbiguousMatches().entrySet() )
          {
            for( ClassIdentity c : entry.getKey() )
            {
              sourceClassEntries.add( c.getClassEntry() );
              matching.removeSource( c );
            }
            for( ClassIdentity c : entry.getValue() )
            {
              destClassEntries.add( c.getClassEntry() );
              matching.removeDest( c );
            }
          }
          for( ClassIdentity c : matching.getUnmatchedSourceClasses() )
          {
            sourceClassEntries.add( c.getClassEntry() );
            matching.removeSource( c );
          }
          for( ClassIdentity c : matching.getUnmatchedDestClasses() )
          {
            destClassEntries.add( c.getClassEntry() );
            matching.removeDest( c );
          }
        }
       
        // compute a matching for the classes
        for( ClassEntry classEntry : sourceClassEntries )
        {
          CtClass c = sourceLoader.loadClass( classEntry.getName() );
          ClassIdentity sourceClass = new ClassIdentity( c, sourceNamer, sourceIndex, useReferences );
          matching.addSource( sourceClass );
        }
        for( ClassEntry classEntry : destClassEntries )
        {
          CtClass c = destLoader.loadClass( classEntry.getName() );
          ClassIdentity destClass = new ClassIdentity( c, destNamer, destIndex, useReferences );
          matching.matchDestClass( destClass );
        }
       
        // TEMP
        System.out.println( matching );
      }
      while( matching.getUniqueMatches().size() - numMatches > 0 );
    }
   
    // check the class matches
    System.out.println( "Checking class matches..." );
    ClassNamer namer = new ClassNamer( matching.getUniqueMatches() );
    SidedClassNamer sourceNamer = namer.getSourceNamer();
    SidedClassNamer destNamer = namer.getDestNamer();
    for( Map.Entry<ClassIdentity,ClassIdentity> entry : matching.getUniqueMatches().entrySet() )
    {
      // check source
      ClassIdentity sourceClass = entry.getKey();
      CtClass sourceC = sourceLoader.loadClass( sourceClass.getClassEntry().getName() );
      assert( sourceC != null )
        : "Unable to load source class " + sourceClass.getClassEntry();
      assert( sourceClass.matches( sourceC ) )
        : "Source " + sourceClass + " doesn't match " + new ClassIdentity( sourceC, sourceNamer, sourceIndex, false );
     
      // check dest
      ClassIdentity destClass = entry.getValue();
      CtClass destC = destLoader.loadClass( destClass.getClassEntry().getName() );
      assert( destC != null )
        : "Unable to load dest class " + destClass.getClassEntry();
      assert( destClass.matches( destC ) )
        : "Dest " + destClass + " doesn't match " + new ClassIdentity( destC, destNamer, destIndex, false );
    }
   
    // warn about the ambiguous matchings
    List<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>> ambiguousMatches = new ArrayList<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>>( matching.getAmbiguousMatches().entrySet() );
    Collections.sort( ambiguousMatches, new Comparator<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>>( )
    {
      @Override
      public int compare( Map.Entry<List<ClassIdentity>,List<ClassIdentity>> a, Map.Entry<List<ClassIdentity>,List<ClassIdentity>> b )
      {
        String aName = a.getKey().get( 0 ).getClassEntry().getName();
        String bName = b.getKey().get( 0 ).getClassEntry().getName();
        return aName.compareTo( bName );
      }
    } );
    for( Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : ambiguousMatches )
    {
      System.out.println( "Ambiguous matching:" );
      System.out.println( "\tSource: " + getClassNames( entry.getKey() ) );
      System.out.println( "\tDest:   " + getClassNames( entry.getValue() ) );
    }
   
    /* DEBUG
    Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry = ambiguousMatches.get( 7 );
    for( ClassIdentity c : entry.getKey() )
    {
      System.out.println( c );
    }
    for( ClassIdentity c : entry.getKey() )
    {
      System.out.println( decompile( sourceLoader, c.getClassEntry() ) );
    }
    */
   
    return matching;
  }
 
  private static void printScoredMatches( int maxScore, List<Integer> scores, Multimap<Integer,ClassIdentity> scoredMatches )
  {
    int numScoredMatchesShown = 0;
    for( int score : scores )
    {
      for( ClassIdentity scoredMatch : scoredMatches.get( score ) )
      {
        System.out.println( String.format( "\tScore: %3d %3.0f%%   %s",
          score,
          100.0*score/maxScore,
          scoredMatch.getClassEntry().getName()
        ) );
       
        if( numScoredMatchesShown++ > 10 )
        {
          return;
        }
      }
    }
  }
 
  private static List<String> getClassNames( Collection<ClassIdentity> classes )
  {
    List<String> out = Lists.newArrayList();
    for( ClassIdentity c : classes )
    {
      out.add( c.getClassEntry().getName() );
    }
    Collections.sort( out );
    return out;
  }
 
  /* DEBUG
  private static String decompile( TranslatingTypeLoader loader, ClassEntry classEntry )
  {
    PlainTextOutput output = new PlainTextOutput();
    DecompilerSettings settings = DecompilerSettings.javaDefaults();
    settings.setForceExplicitImports( true );
    settings.setShowSyntheticMembers( true );
    settings.setTypeLoader( loader );
    Decompiler.decompile( classEntry.getName(), output, settings );
    return output.toString();
  }
  */
TOP

Related Classes of cuchaz.enigma.convert.ClassMatcher

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.