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) || (!classNameBiMap.isEmpty() && className.indexOf('/') == -1);
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}