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        className = className.replace('.', '/');
129        return classNameBiMap.containsKey(className) || mcpNameBiMap.containsKey(className) || (!classNameBiMap.isEmpty() && className.indexOf('/') == -1);
130    }
131
132    private void parseField(String[] parts)
133    {
134        String oldSrg = parts[1];
135        int lastOld = oldSrg.lastIndexOf('/');
136        String cl = oldSrg.substring(0,lastOld);
137        String oldName = oldSrg.substring(lastOld+1);
138        String newSrg = parts[2];
139        int lastNew = newSrg.lastIndexOf('/');
140        String newName = newSrg.substring(lastNew+1);
141        if (!rawFieldMaps.containsKey(cl))
142        {
143            rawFieldMaps.put(cl, Maps.<String,String>newHashMap());
144        }
145        rawFieldMaps.get(cl).put(oldName, newName);
146    }
147
148    private void parseClass(Builder<String, String> builder, String[] parts)
149    {
150        builder.put(parts[1],parts[2]);
151    }
152
153    private void parseMCPClass(Builder<String, String> builder, String[] parts)
154    {
155        int clIdx = parts[2].lastIndexOf('/');
156        builder.put("net/minecraft/src/"+parts[2].substring(clIdx+1),parts[2]);
157    }
158
159    private void parseMethod(String[] parts)
160    {
161        String oldSrg = parts[1];
162        int lastOld = oldSrg.lastIndexOf('/');
163        String cl = oldSrg.substring(0,lastOld);
164        String oldName = oldSrg.substring(lastOld+1);
165        String sig = parts[2];
166        String newSrg = parts[3];
167        int lastNew = newSrg.lastIndexOf('/');
168        String newName = newSrg.substring(lastNew+1);
169        if (!rawMethodMaps.containsKey(cl))
170        {
171            rawMethodMaps.put(cl, Maps.<String,String>newHashMap());
172        }
173        rawMethodMaps.get(cl).put(oldName+sig, newName);
174    }
175
176    @Override
177    public String mapFieldName(String owner, String name, String desc)
178    {
179        if (classNameBiMap == null || classNameBiMap.isEmpty())
180        {
181            return name;
182        }
183        Map<String, String> fieldMap = getFieldMap(owner);
184        return fieldMap!=null && fieldMap.containsKey(name) ? fieldMap.get(name) : name;
185    }
186
187    @Override
188    public String map(String typeName)
189    {
190        if (classNameBiMap == null || classNameBiMap.isEmpty())
191        {
192            return typeName;
193        }
194
195        int dollarIdx = typeName.indexOf('$');
196        String realType = dollarIdx > -1 ? typeName.substring(0, dollarIdx) : typeName;
197        String subType = dollarIdx > -1 ? typeName.substring(dollarIdx+1) : "";
198
199        String result = classNameBiMap.containsKey(realType) ? classNameBiMap.get(realType) : mcpNameBiMap.containsKey(realType) ? mcpNameBiMap.get(realType) : realType;
200        result = dollarIdx > -1 ? result+"$"+subType : result;
201//        System.out.printf("Mapping %s=>%s\n",typeName,result);
202        return result;
203    }
204
205    public String unmap(String typeName)
206    {
207        if (classNameBiMap == null || classNameBiMap.isEmpty())
208        {
209            return typeName;
210        }
211        int dollarIdx = typeName.indexOf('$');
212        String realType = dollarIdx > -1 ? typeName.substring(0, dollarIdx) : typeName;
213        String subType = dollarIdx > -1 ? typeName.substring(dollarIdx+1) : "";
214
215
216        String result = classNameBiMap.containsValue(realType) ? classNameBiMap.inverse().get(realType) : mcpNameBiMap.containsValue(realType) ? mcpNameBiMap.inverse().get(realType) : realType;
217        result = dollarIdx > -1 ? result+"$"+subType : result;
218//        System.out.printf("Unmapping %s=>%s\n",typeName,result);
219        return result;
220    }
221
222
223    @Override
224    public String mapMethodName(String owner, String name, String desc)
225    {
226        if (classNameBiMap==null || classNameBiMap.isEmpty())
227        {
228            return name;
229        }
230        Map<String, String> methodMap = getMethodMap(owner);
231        String methodDescriptor = name+desc;
232        return methodMap!=null && methodMap.containsKey(methodDescriptor) ? methodMap.get(methodDescriptor) : name;
233    }
234
235    private Map<String,String> getFieldMap(String className)
236    {
237        if (!fieldNameMaps.containsKey(className))
238        {
239            findAndMergeSuperMaps(className);
240        }
241        return fieldNameMaps.get(className);
242    }
243
244    private Map<String,String> getMethodMap(String className)
245    {
246        if (!methodNameMaps.containsKey(className))
247        {
248            findAndMergeSuperMaps(className);
249        }
250        return methodNameMaps.get(className);
251    }
252
253    private void findAndMergeSuperMaps(String name)
254    {
255        try
256        {
257            byte[] classBytes = classLoader.getClassBytes(name);
258            if (classBytes == null)
259            {
260                return;
261            }
262            ClassReader cr = new ClassReader(classBytes);
263            String superName = cr.getSuperName();
264            String[] interfaces = cr.getInterfaces();
265            if (interfaces == null)
266            {
267                interfaces = new String[0];
268            }
269            mergeSuperMaps(name, superName, interfaces);
270        }
271        catch (IOException e)
272        {
273            e.printStackTrace();
274        }
275    }
276    public void mergeSuperMaps(String name, String superName, String[] interfaces)
277    {
278//        System.out.printf("Computing super maps for %s: %s %s\n", name, superName, Arrays.asList(interfaces));
279        if (classNameBiMap == null || classNameBiMap.isEmpty())
280        {
281            return;
282        }
283        // Skip Object
284        if (Strings.isNullOrEmpty(superName))
285        {
286            return;
287        }
288
289        List<String> allParents = ImmutableList.<String>builder().add(superName).addAll(Arrays.asList(interfaces)).build();
290        // generate maps for all parent objects
291        for (String parentThing : allParents)
292        {
293            if (!methodNameMaps.containsKey(parentThing))
294            {
295                findAndMergeSuperMaps(parentThing);
296            }
297        }
298        Map<String, String> methodMap = Maps.<String,String>newHashMap();
299        Map<String, String> fieldMap = Maps.<String,String>newHashMap();
300        for (String parentThing : allParents)
301        {
302            if (methodNameMaps.containsKey(parentThing))
303            {
304                methodMap.putAll(methodNameMaps.get(parentThing));
305            }
306            if (fieldNameMaps.containsKey(parentThing))
307            {
308                fieldMap.putAll(fieldNameMaps.get(parentThing));
309            }
310        }
311        if (rawMethodMaps.containsKey(name))
312        {
313            methodMap.putAll(rawMethodMaps.get(name));
314        }
315        if (rawFieldMaps.containsKey(name))
316        {
317            fieldMap.putAll(rawFieldMaps.get(name));
318        }
319        methodNameMaps.put(name, ImmutableMap.copyOf(methodMap));
320        fieldNameMaps.put(name, ImmutableMap.copyOf(fieldMap));
321//        System.out.printf("Maps: %s %s\n", name, methodMap);
322    }
323}