    001/*
    002 * Forge Mod Loader
    003 * Copyright (c) 2012-2013 cpw.
    004 * All rights reserved. This program and the accompanying materials
    005 * are made available under the terms of the GNU Lesser Public License v2.1
    006 * which accompanies this distribution, and is available at
    007 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
    008 *
    009 * Contributors:
    010 *     cpw - implementation
    011 */
    012
    013package cpw.mods.fml.common.asm.transformers.deobf;
    014
    015import java.io.File;
    016import java.io.IOException;
    017import java.io.InputStream;
    018import java.io.InputStreamReader;
    019import java.nio.charset.Charset;
    020import java.util.Arrays;
    021import java.util.HashMap;
    022import java.util.List;
    023import java.util.Map;
    024import java.util.Set;
    025import java.util.logging.Level;
    026import java.util.zip.ZipEntry;
    027import java.util.zip.ZipFile;
    028
    029import org.objectweb.asm.ClassReader;
    030import org.objectweb.asm.commons.Remapper;
    031
    032import com.google.common.base.CharMatcher;
    033import com.google.common.base.Charsets;
    034import com.google.common.base.Splitter;
    035import com.google.common.base.Strings;
    036import com.google.common.collect.BiMap;
    037import com.google.common.collect.HashBiMap;
    038import com.google.common.collect.ImmutableBiMap;
    039import com.google.common.collect.ImmutableList;
    040import com.google.common.collect.ImmutableMap;
    041import com.google.common.collect.Iterables;
    042import com.google.common.collect.Lists;
    043import com.google.common.collect.Maps;
    044import com.google.common.collect.Sets;
    045import com.google.common.collect.ImmutableBiMap.Builder;
    046import com.google.common.io.CharStreams;
    047import com.google.common.io.InputSupplier;
    048
    049import cpw.mods.fml.common.FMLLog;
    050import cpw.mods.fml.relauncher.FMLRelaunchLog;
    051import cpw.mods.fml.relauncher.RelaunchClassLoader;
    052
    053public class FMLDeobfuscatingRemapper extends Remapper {
    054    public static final FMLDeobfuscatingRemapper INSTANCE = new FMLDeobfuscatingRemapper();
    055
    056    private BiMap<String, String> classNameBiMap;
    057    private BiMap<String, String> mcpNameBiMap;
    058
    059    private Map<String,Map<String,String>> rawFieldMaps;
    060    private Map<String,Map<String,String>> rawMethodMaps;
    061
    062    private Map<String,Map<String,String>> fieldNameMaps;
    063    private Map<String,Map<String,String>> methodNameMaps;
    064
    065    private RelaunchClassLoader classLoader;
    066
    067    private FMLDeobfuscatingRemapper()
    068    {
    069        classNameBiMap=ImmutableBiMap.of();
    070        mcpNameBiMap=ImmutableBiMap.of();
    071    }
    072
    073    public void setup(File mcDir, RelaunchClassLoader classLoader, String deobfFileName)
    074    {
    075        this.classLoader = classLoader;
    076        try
    077        {
    078            File libDir = new File(mcDir, "lib");
    079            File mapData = new File(libDir, deobfFileName);
    080            mapData = mapData.getCanonicalFile();
    081            ZipFile mapZip = new ZipFile(mapData);
    082            ZipEntry classData = mapZip.getEntry("joined.srg");
    083            ZipInputSupplier zis = new ZipInputSupplier(mapZip, classData);
    084            InputSupplier<InputStreamReader> srgSupplier = CharStreams.newReaderSupplier(zis,Charsets.UTF_8);
    085            List<String> srgList = CharStreams.readLines(srgSupplier);
    086            rawMethodMaps = Maps.newHashMap();
    087            rawFieldMaps = Maps.newHashMap();
    088            Builder<String, String> builder = ImmutableBiMap.<String,String>builder();
    089            Builder<String, String> mcpBuilder = ImmutableBiMap.<String,String>builder();
    090            Splitter splitter = Splitter.on(CharMatcher.anyOf(": ")).omitEmptyStrings().trimResults();
    091            for (String line : srgList)
    092            {
    093                String[] parts = Iterables.toArray(splitter.split(line),String.class);
    094                String typ = parts[0];
    095                if ("CL".equals(typ))
    096                {
    097                    parseClass(builder, parts);
    098                    parseMCPClass(mcpBuilder,parts);
    099                }
    100                else if ("MD".equals(typ))
    101                {
    102                    parseMethod(parts);
    103                }
    104                else if ("FD".equals(typ))
    105                {
    106                    parseField(parts);
    107                }
    108            }
    109            classNameBiMap = builder.build();
    110            // Special case some mappings for modloader mods
    111            mcpBuilder.put("BaseMod","net/minecraft/src/BaseMod");
    112            mcpBuilder.put("ModLoader","net/minecraft/src/ModLoader");
    113            mcpBuilder.put("EntityRendererProxy","net/minecraft/src/EntityRendererProxy");
    114            mcpBuilder.put("MLProp","net/minecraft/src/MLProp");
    115            mcpBuilder.put("TradeEntry","net/minecraft/src/TradeEntry");
    116            mcpNameBiMap = mcpBuilder.build();
    117        }
    118        catch (IOException ioe)
    119        {
    120            FMLRelaunchLog.log(Level.SEVERE, ioe, "An error occurred loading the deobfuscation map data");
    121        }
    122        methodNameMaps = Maps.newHashMapWithExpectedSize(rawMethodMaps.size());
    123        fieldNameMaps = Maps.newHashMapWithExpectedSize(rawFieldMaps.size());
    124    }
    125
    126    public boolean isRemappedClass(String className)
    127    {
    128        return classNameBiMap.containsKey(className) || mcpNameBiMap.containsKey(className);
    129    }
    130
    131    private void parseField(String[] parts)
    132    {
    133        String oldSrg = parts[1];
    134        int lastOld = oldSrg.lastIndexOf('/');
    135        String cl = oldSrg.substring(0,lastOld);
    136        String oldName = oldSrg.substring(lastOld+1);
    137        String newSrg = parts[2];
    138        int lastNew = newSrg.lastIndexOf('/');
    139        String newName = newSrg.substring(lastNew+1);
    140        if (!rawFieldMaps.containsKey(cl))
    141        {
    142            rawFieldMaps.put(cl, Maps.<String,String>newHashMap());
    143        }
    144        rawFieldMaps.get(cl).put(oldName, newName);
    145    }
    146
    147    private void parseClass(Builder<String, String> builder, String[] parts)
    148    {
    149        builder.put(parts[1],parts[2]);
    150    }
    151
    152    private void parseMCPClass(Builder<String, String> builder, String[] parts)
    153    {
    154        int clIdx = parts[2].lastIndexOf('/');
    155        builder.put("net/minecraft/src/"+parts[2].substring(clIdx+1),parts[2]);
    156    }
    157
    158    private void parseMethod(String[] parts)
    159    {
    160        String oldSrg = parts[1];
    161        int lastOld = oldSrg.lastIndexOf('/');
    162        String cl = oldSrg.substring(0,lastOld);
    163        String oldName = oldSrg.substring(lastOld+1);
    164        String sig = parts[2];
    165        String newSrg = parts[3];
    166        int lastNew = newSrg.lastIndexOf('/');
    167        String newName = newSrg.substring(lastNew+1);
    168        if (!rawMethodMaps.containsKey(cl))
    169        {
    170            rawMethodMaps.put(cl, Maps.<String,String>newHashMap());
    171        }
    172        rawMethodMaps.get(cl).put(oldName+sig, newName);
    173    }
    174
    175    @Override
    176    public String mapFieldName(String owner, String name, String desc)
    177    {
    178        if (classNameBiMap == null || classNameBiMap.isEmpty())
    179        {
    180            return name;
    181        }
    182        Map<String, String> fieldMap = getFieldMap(owner);
    183        return fieldMap!=null && fieldMap.containsKey(name) ? fieldMap.get(name) : name;
    184    }
    185
    186    @Override
    187    public String map(String typeName)
    188    {
    189        if (classNameBiMap == null || classNameBiMap.isEmpty())
    190        {
    191            return typeName;
    192        }
    193
    194        int dollarIdx = typeName.indexOf('$');
    195        String realType = dollarIdx > -1 ? typeName.substring(0, dollarIdx) : typeName;
    196        String subType = dollarIdx > -1 ? typeName.substring(dollarIdx+1) : "";
    197
    198        String result = classNameBiMap.containsKey(realType) ? classNameBiMap.get(realType) : mcpNameBiMap.containsKey(realType) ? mcpNameBiMap.get(realType) : realType;
    199        result = dollarIdx > -1 ? result+"$"+subType : result;
    200//        System.out.printf("Mapping %s=>%s\n",typeName,result);
    201        return result;
    202    }
    203
    204    public String unmap(String typeName)
    205    {
    206        if (classNameBiMap == null || classNameBiMap.isEmpty())
    207        {
    208            return typeName;
    209        }
    210        int dollarIdx = typeName.indexOf('$');
    211        String realType = dollarIdx > -1 ? typeName.substring(0, dollarIdx) : typeName;
    212        String subType = dollarIdx > -1 ? typeName.substring(dollarIdx+1) : "";
    213
    214
    215        String result = classNameBiMap.containsValue(realType) ? classNameBiMap.inverse().get(realType) : mcpNameBiMap.containsValue(realType) ? mcpNameBiMap.inverse().get(realType) : realType;
    216        result = dollarIdx > -1 ? result+"$"+subType : result;
    217//        System.out.printf("Unmapping %s=>%s\n",typeName,result);
    218        return result;
    219    }
    220
    221
    222    @Override
    223    public String mapMethodName(String owner, String name, String desc)
    224    {
    225        if (classNameBiMap==null || classNameBiMap.isEmpty())
    226        {
    227            return name;
    228        }
    229        Map<String, String> methodMap = getMethodMap(owner);
    230        String methodDescriptor = name+desc;
    231        return methodMap!=null && methodMap.containsKey(methodDescriptor) ? methodMap.get(methodDescriptor) : name;
    232    }
    233
    234    private Map<String,String> getFieldMap(String className)
    235    {
    236        if (!fieldNameMaps.containsKey(className))
    237        {
    238            findAndMergeSuperMaps(className);
    239        }
    240        return fieldNameMaps.get(className);
    241    }
    242
    243    private Map<String,String> getMethodMap(String className)
    244    {
    245        if (!methodNameMaps.containsKey(className))
    246        {
    247            findAndMergeSuperMaps(className);
    248        }
    249        return methodNameMaps.get(className);
    250    }
    251
    252    private void findAndMergeSuperMaps(String name)
    253    {
    254        try
    255        {
    256            byte[] classBytes = classLoader.getClassBytes(name);
    257            if (classBytes == null)
    258            {
    259                return;
    260            }
    261            ClassReader cr = new ClassReader(classBytes);
    262            String superName = cr.getSuperName();
    263            String[] interfaces = cr.getInterfaces();
    264            if (interfaces == null)
    265            {
    266                interfaces = new String[0];
    267            }
    268            mergeSuperMaps(name, superName, interfaces);
    269        }
    270        catch (IOException e)
    271        {
    272            e.printStackTrace();
    273        }
    274    }
    275    public void mergeSuperMaps(String name, String superName, String[] interfaces)
    276    {
    277//        System.out.printf("Computing super maps for %s: %s %s\n", name, superName, Arrays.asList(interfaces));
    278        if (classNameBiMap == null || classNameBiMap.isEmpty())
    279        {
    280            return;
    281        }
    282        // Skip Object
    283        if (Strings.isNullOrEmpty(superName))
    284        {
    285            return;
    286        }
    287
    288        List<String> allParents = ImmutableList.<String>builder().add(superName).addAll(Arrays.asList(interfaces)).build();
    289        // generate maps for all parent objects
    290        for (String parentThing : allParents)
    291        {
    292            if (!methodNameMaps.containsKey(parentThing))
    293            {
    294                findAndMergeSuperMaps(parentThing);
    295            }
    296        }
    297        Map<String, String> methodMap = Maps.<String,String>newHashMap();
    298        Map<String, String> fieldMap = Maps.<String,String>newHashMap();
    299        for (String parentThing : allParents)
    300        {
    301            if (methodNameMaps.containsKey(parentThing))
    302            {
    303                methodMap.putAll(methodNameMaps.get(parentThing));
    304            }
    305            if (fieldNameMaps.containsKey(parentThing))
    306            {
    307                fieldMap.putAll(fieldNameMaps.get(parentThing));
    308            }
    309        }
    310        if (rawMethodMaps.containsKey(name))
    311        {
    312            methodMap.putAll(rawMethodMaps.get(name));
    313        }
    314        if (rawFieldMaps.containsKey(name))
    315        {
    316            fieldMap.putAll(rawFieldMaps.get(name));
    317        }
    318        methodNameMaps.put(name, ImmutableMap.copyOf(methodMap));
    319        fieldNameMaps.put(name, ImmutableMap.copyOf(fieldMap));
    320//        System.out.printf("Maps: %s %s\n", name, methodMap);
    321    }
    322}