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